diff --git a/src/main/java/com/metaweb/gridworks/browsing/Engine.java b/src/main/java/com/metaweb/gridworks/browsing/Engine.java index f5fd1bb7f..5a6f249e4 100644 --- a/src/main/java/com/metaweb/gridworks/browsing/Engine.java +++ b/src/main/java/com/metaweb/gridworks/browsing/Engine.java @@ -13,6 +13,7 @@ import com.metaweb.gridworks.Jsonizable; import com.metaweb.gridworks.browsing.facets.Facet; import com.metaweb.gridworks.browsing.facets.ListFacet; import com.metaweb.gridworks.browsing.facets.RangeFacet; +import com.metaweb.gridworks.browsing.facets.ScatterplotFacet; import com.metaweb.gridworks.browsing.facets.TextSearchFacet; import com.metaweb.gridworks.browsing.filters.RowFilter; import com.metaweb.gridworks.model.Project; @@ -66,6 +67,8 @@ public class Engine implements Jsonizable { facet = new ListFacet(); } else if ("range".equals(type)) { facet = new RangeFacet(); + } else if ("scatterplot".equals(type)) { + facet = new ScatterplotFacet(); } else if ("text".equals(type)) { facet = new TextSearchFacet(); } diff --git a/src/main/java/com/metaweb/gridworks/browsing/charting/ScatterplotCharter.java b/src/main/java/com/metaweb/gridworks/browsing/charting/ScatterplotCharter.java index 36caa391d..3bcabb204 100644 --- a/src/main/java/com/metaweb/gridworks/browsing/charting/ScatterplotCharter.java +++ b/src/main/java/com/metaweb/gridworks/browsing/charting/ScatterplotCharter.java @@ -50,6 +50,7 @@ public class ScatterplotCharter { private static final double px = 0.5f; boolean process = true; + boolean smoothed = false; int width = 50; int height = 50; @@ -68,7 +69,7 @@ public class ScatterplotCharter { BufferedImage image; Graphics2D g2; - + public DrawingRowVisitor(Project project, JSONObject o) throws JSONException { String col_x_name = o.getString("cx"); Column column_x = project.columnModel.getColumnByName(col_x_name); @@ -135,7 +136,7 @@ public class ScatterplotCharter { { double xv = ((Number) cellx.value).doubleValue(); double yv = ((Number) celly.value).doubleValue(); - + double x = (xv - min_x) * w / max_x; double y = (yv - min_y) * h / max_y; g2.fill(new Rectangle2D.Double(x, y, px, px)); diff --git a/src/main/java/com/metaweb/gridworks/browsing/facets/ScatterplotFacet.java b/src/main/java/com/metaweb/gridworks/browsing/facets/ScatterplotFacet.java new file mode 100644 index 000000000..b154c0498 --- /dev/null +++ b/src/main/java/com/metaweb/gridworks/browsing/facets/ScatterplotFacet.java @@ -0,0 +1,234 @@ +package com.metaweb.gridworks.browsing.facets; + +import java.util.Properties; + +import org.json.JSONException; +import org.json.JSONObject; +import org.json.JSONWriter; + +import com.metaweb.gridworks.browsing.FilteredRows; +import com.metaweb.gridworks.browsing.filters.ExpressionNumberComparisonRowFilter; +import com.metaweb.gridworks.browsing.filters.RowFilter; +import com.metaweb.gridworks.expr.Evaluable; +import com.metaweb.gridworks.expr.MetaParser; +import com.metaweb.gridworks.expr.ParsingException; +import com.metaweb.gridworks.model.Column; +import com.metaweb.gridworks.model.Project; +import com.metaweb.gridworks.util.JSONUtilities; + +public class ScatterplotFacet implements Facet { + + /* + * Configuration, from the client side + */ + protected String _name; // name of facet + protected String _expression; // expression to compute numeric value(s) per row + protected String _columnName; // column to base expression on, if any + protected String _mode; // "range", MIN, MAX + + protected double _from; // the numeric selection + protected double _to; + + protected boolean _selectNumeric; // whether the numeric selection applies, default true + protected boolean _selectNonNumeric; + protected boolean _selectBlank; + protected boolean _selectError; + + /* + * Derived configuration data + */ + protected int _cellIndex; + protected Evaluable _eval; + protected String _errorMessage; + protected boolean _selected; // false if we're certain that all rows will match + // and there isn't any filtering to do + + /* + * Computed data, to return to the client side + */ + protected double _min; + protected double _max; + protected double _step; + protected int[] _baseBins; + protected int[] _bins; + + protected int _numericCount; + protected int _nonNumericCount; + protected int _blankCount; + protected int _errorCount; + + public ScatterplotFacet() { + } + + private static final String MIN = "min"; + private static final String MAX = "max"; + private static final String TO = "to"; + private static final String FROM = "from"; + + public void write(JSONWriter writer, Properties options) + throws JSONException { + + writer.object(); + writer.key("name"); writer.value(_name); + writer.key("expression"); writer.value(_expression); + writer.key("columnName"); writer.value(_columnName); + writer.key("mode"); writer.value(_mode); + + if (_errorMessage != null) { + writer.key("error"); writer.value(_errorMessage); + } else { + if (!Double.isInfinite(_min) && !Double.isInfinite(_max)) { + writer.key(MIN); writer.value(_min); + writer.key(MAX); writer.value(_max); + writer.key("step"); writer.value(_step); + + writer.key("bins"); writer.array(); + for (int b : _bins) { + writer.value(b); + } + writer.endArray(); + + writer.key("baseBins"); writer.array(); + for (int b : _baseBins) { + writer.value(b); + } + writer.endArray(); + + if (MIN.equals(_mode)) { + writer.key(FROM); writer.value(_from); + } else if (MAX.equals(_mode)) { + writer.key(TO); writer.value(_to); + } else { + writer.key(FROM); writer.value(_from); + writer.key(TO); writer.value(_to); + } + } + + writer.key("numericCount"); writer.value(_numericCount); + writer.key("nonNumericCount"); writer.value(_nonNumericCount); + writer.key("blankCount"); writer.value(_blankCount); + writer.key("errorCount"); writer.value(_errorCount); + } + writer.endObject(); + } + + public void initializeFromJSON(Project project, JSONObject o) throws Exception { + _name = o.getString("name"); + _expression = o.getString("expression"); + _columnName = o.getString("columnName"); + + if (_columnName.length() > 0) { + Column column = project.columnModel.getColumnByName(_columnName); + if (column != null) { + _cellIndex = column.getCellIndex(); + } else { + _errorMessage = "No column named " + _columnName; + } + } else { + _cellIndex = -1; + } + + try { + _eval = MetaParser.parse(_expression); + } catch (ParsingException e) { + _errorMessage = e.getMessage(); + } + + _mode = o.getString("mode"); + if (MIN.equals(_mode)) { + if (o.has(FROM)) { + _from = o.getDouble(FROM); + _selected = true; + } + } else if (MAX.equals(_mode)) { + if (o.has(TO)) { + _to = o.getDouble(TO); + _selected = true; + } + } else { + if (o.has(FROM) && o.has(TO)) { + _from = o.getDouble(FROM); + _to = o.getDouble(TO); + _selected = true; + } + } + + _selectNumeric = JSONUtilities.getBoolean(o, "selectNumeric", true); + _selectNonNumeric = JSONUtilities.getBoolean(o, "selectNonNumeric", true); + _selectBlank = JSONUtilities.getBoolean(o, "selectBlank", true); + _selectError = JSONUtilities.getBoolean(o, "selectError", true); + + if (!_selectNumeric || !_selectNonNumeric || !_selectBlank || !_selectError) { + _selected = true; + } + } + + public RowFilter getRowFilter() { + if (_eval != null && _errorMessage == null && _selected) { + if (MIN.equals(_mode)) { + return new ExpressionNumberComparisonRowFilter( + _eval, _columnName, _cellIndex, _selectNumeric, _selectNonNumeric, _selectBlank, _selectError) { + + protected boolean checkValue(double d) { + return d >= _from; + }; + }; + } else if (MAX.equals(_mode)) { + return new ExpressionNumberComparisonRowFilter( + _eval, _columnName, _cellIndex, _selectNumeric, _selectNonNumeric, _selectBlank, _selectError) { + + protected boolean checkValue(double d) { + return d < _to; + }; + }; + } else { + return new ExpressionNumberComparisonRowFilter( + _eval, _columnName, _cellIndex, _selectNumeric, _selectNonNumeric, _selectBlank, _selectError) { + + protected boolean checkValue(double d) { + return d >= _from && d < _to; + }; + }; + } + } else { + return null; + } + } + + public void computeChoices(Project project, FilteredRows filteredRows) { + if (_eval != null && _errorMessage == null) { + Column column = project.columnModel.getColumnByCellIndex(_cellIndex); + + String key = "numeric-bin:" + _expression; + NumericBinIndex index = (NumericBinIndex) column.getPrecompute(key); + if (index == null) { + index = new NumericBinIndex(project, _columnName, _cellIndex, _eval); + column.setPrecompute(key, index); + } + + _min = index.getMin(); + _max = index.getMax(); + _step = index.getStep(); + _baseBins = index.getBins(); + + if (_selected) { + _from = Math.max(_from, _min); + _to = Math.min(_to, _max); + } else { + _from = _min; + _to = _max; + } + + ExpressionNumericRowBinner binner = + new ExpressionNumericRowBinner(_eval, _columnName, _cellIndex, index); + + filteredRows.accept(project, binner); + + _bins = binner.bins; + _numericCount = binner.numericCount; + _nonNumericCount = binner.nonNumericCount; + _blankCount = binner.blankCount; + _errorCount = binner.errorCount; + } + } +} diff --git a/src/main/java/com/metaweb/gridworks/commands/info/GetScatterplotCommand.java b/src/main/java/com/metaweb/gridworks/commands/info/GetScatterplotCommand.java index 9b95cc2ee..1cd0e0d4b 100644 --- a/src/main/java/com/metaweb/gridworks/commands/info/GetScatterplotCommand.java +++ b/src/main/java/com/metaweb/gridworks/commands/info/GetScatterplotCommand.java @@ -40,6 +40,7 @@ public class GetScatterplotCommand extends Command { //Gridworks.log("drawn scatterplot in " + (System.currentTimeMillis() - start) + "ms"); } catch (Exception e) { + e.printStackTrace(); respondException(response, e); } } diff --git a/src/main/webapp/project.html b/src/main/webapp/project.html index a6c21785c..569575883 100644 --- a/src/main/webapp/project.html +++ b/src/main/webapp/project.html @@ -1 +1 @@ -