diff --git a/src/main/java/com/metaweb/gridworks/browsing/facets/ScatterplotFacet.java b/src/main/java/com/metaweb/gridworks/browsing/facets/ScatterplotFacet.java index b154c0498..12a73ae8f 100644 --- a/src/main/java/com/metaweb/gridworks/browsing/facets/ScatterplotFacet.java +++ b/src/main/java/com/metaweb/gridworks/browsing/facets/ScatterplotFacet.java @@ -7,14 +7,13 @@ 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.DualExpressionsNumberComparisonRowFilter; 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 { @@ -22,213 +21,184 @@ 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 String _x_expression; // expression to compute the x numeric value(s) per row + protected String _y_expression; // expression to compute the y numeric value(s) per row + protected String _x_columnName; // column to base the x expression on, if any + protected String _y_columnName; // column to base the y expression on, if any - 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; + protected double _x_from; // the numeric selection for the x axis + protected double _x_to; + protected double _y_from; // the numeric selection for the y axis + protected double _y_to; + + protected double _x_min; + protected double _x_max; + protected double _y_min; + protected double _y_max; /* * Derived configuration data */ - protected int _cellIndex; - protected Evaluable _eval; - protected String _errorMessage; + protected int _x_cellIndex; + protected int _y_cellIndex; + protected Evaluable _x_eval; + protected Evaluable _y_eval; + protected String _x_errorMessage; + protected String _y_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"; + private static final String X_MIN = "x_min"; + private static final String X_MAX = "x_max"; + private static final String X_TO = "x_to"; + private static final String X_FROM = "x_from"; + private static final String Y_MIN = "y_min"; + private static final String Y_MAX = "y_max"; + private static final String Y_TO = "y_to"; + private static final String Y_FROM = "y_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); + writer.key("x_expression"); writer.value(_x_expression); + writer.key("x_columnName"); writer.value(_x_columnName); - if (_errorMessage != null) { - writer.key("error"); writer.value(_errorMessage); + if (_x_errorMessage != null) { + writer.key("x_error"); writer.value(_x_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); - } + if (!Double.isInfinite(_x_min) && !Double.isInfinite(_x_max)) { + writer.key(X_MIN); writer.value(_x_min); + writer.key(X_MAX); writer.value(_x_max); + writer.key(X_FROM); writer.value(_x_from); + writer.key(X_TO); writer.value(_x_to); + } + if (!Double.isInfinite(_y_min) && !Double.isInfinite(_y_max)) { + writer.key(Y_MIN); writer.value(_y_min); + writer.key(Y_MAX); writer.value(_y_max); + writer.key(Y_FROM); writer.value(_y_from); + writer.key(Y_TO); writer.value(_y_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"); + + _x_expression = o.getString("x_expression"); + _x_columnName = o.getString("x_columnName"); - if (_columnName.length() > 0) { - Column column = project.columnModel.getColumnByName(_columnName); - if (column != null) { - _cellIndex = column.getCellIndex(); + if (_x_columnName.length() > 0) { + Column x_column = project.columnModel.getColumnByName(_x_columnName); + if (x_column != null) { + _x_cellIndex = x_column.getCellIndex(); } else { - _errorMessage = "No column named " + _columnName; + _x_errorMessage = "No column named " + _x_columnName; } } else { - _cellIndex = -1; + _x_cellIndex = -1; } try { - _eval = MetaParser.parse(_expression); + _x_eval = MetaParser.parse(_x_expression); } catch (ParsingException e) { - _errorMessage = e.getMessage(); + _x_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; + if (o.has(X_FROM) && o.has(X_TO)) { + _x_from = o.getDouble(X_FROM); + _x_to = o.getDouble(X_TO); + _selected = true; + } + + _y_expression = o.getString("y_expression"); + _y_columnName = o.getString("y_columnName"); + + if (_y_columnName.length() > 0) { + Column y_column = project.columnModel.getColumnByName(_y_columnName); + if (y_column != null) { + _y_cellIndex = y_column.getCellIndex(); + } else { + _y_errorMessage = "No column named " + _y_columnName; } } else { - if (o.has(FROM) && o.has(TO)) { - _from = o.getDouble(FROM); - _to = o.getDouble(TO); - _selected = true; - } + _y_cellIndex = -1; } - _selectNumeric = JSONUtilities.getBoolean(o, "selectNumeric", true); - _selectNonNumeric = JSONUtilities.getBoolean(o, "selectNonNumeric", true); - _selectBlank = JSONUtilities.getBoolean(o, "selectBlank", true); - _selectError = JSONUtilities.getBoolean(o, "selectError", true); + try { + _y_eval = MetaParser.parse(_y_expression); + } catch (ParsingException e) { + _y_errorMessage = e.getMessage(); + } - if (!_selectNumeric || !_selectNonNumeric || !_selectBlank || !_selectError) { + if (o.has(Y_FROM) && o.has(Y_TO)) { + _y_from = o.getDouble(Y_FROM); + _y_to = o.getDouble(Y_TO); _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; - }; + if (_selected && + _x_eval != null && _x_errorMessage == null && + _y_eval != null && _y_errorMessage == null) + { + return new DualExpressionsNumberComparisonRowFilter(_x_eval, _x_columnName, _x_cellIndex, _y_eval, _y_columnName, _y_cellIndex) { + protected boolean checkValues(double x, double y) { + return x >= _x_from && x < _x_to && y >= _y_from && y < _y_to; }; - } 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); + if (_x_eval != null && _y_eval != null && _x_errorMessage == null && _y_errorMessage == null) { + Column column_x = project.columnModel.getColumnByCellIndex(_x_cellIndex); + String key_x = "numeric-bin:" + _x_expression; + NumericBinIndex index_x = (NumericBinIndex) column_x.getPrecompute(key_x); + if (index_x == null) { + index_x = new NumericBinIndex(project, _x_columnName, _x_cellIndex, _x_eval); + column_x.setPrecompute(key_x, index_x); } - _min = index.getMin(); - _max = index.getMax(); - _step = index.getStep(); - _baseBins = index.getBins(); + _x_min = index_x.getMin(); + _x_max = index_x.getMax(); if (_selected) { - _from = Math.max(_from, _min); - _to = Math.min(_to, _max); + _x_from = Math.max(_x_from, _x_min); + _x_to = Math.min(_x_to, _x_max); } else { - _from = _min; - _to = _max; + _x_from = _x_min; + _x_to = _x_max; } - ExpressionNumericRowBinner binner = - new ExpressionNumericRowBinner(_eval, _columnName, _cellIndex, index); + Column column_y = project.columnModel.getColumnByCellIndex(_y_cellIndex); + String key_y = "numeric-bin:" + _y_expression; + NumericBinIndex index_y = (NumericBinIndex) column_y.getPrecompute(key_y); + if (index_y == null) { + index_y = new NumericBinIndex(project, _y_columnName, _y_cellIndex, _y_eval); + column_y.setPrecompute(key_y, index_y); + } - filteredRows.accept(project, binner); + _y_min = index_y.getMin(); + _y_max = index_y.getMax(); - _bins = binner.bins; - _numericCount = binner.numericCount; - _nonNumericCount = binner.nonNumericCount; - _blankCount = binner.blankCount; - _errorCount = binner.errorCount; + if (_selected) { + _y_from = Math.max(_y_from, _y_min); + _y_to = Math.min(_y_to, _y_max); + } else { + _y_from = _y_min; + _y_to = _y_max; + } } } } diff --git a/src/main/java/com/metaweb/gridworks/browsing/filters/DualExpressionsNumberComparisonRowFilter.java b/src/main/java/com/metaweb/gridworks/browsing/filters/DualExpressionsNumberComparisonRowFilter.java new file mode 100644 index 000000000..1ee712901 --- /dev/null +++ b/src/main/java/com/metaweb/gridworks/browsing/filters/DualExpressionsNumberComparisonRowFilter.java @@ -0,0 +1,80 @@ +package com.metaweb.gridworks.browsing.filters; + +import java.util.Collection; +import java.util.Properties; + +import com.metaweb.gridworks.expr.Evaluable; +import com.metaweb.gridworks.expr.ExpressionUtils; +import com.metaweb.gridworks.model.Cell; +import com.metaweb.gridworks.model.Project; +import com.metaweb.gridworks.model.Row; + +/** + * Judge if a row matches by evaluating two given expressions on the row, based on two different columns + * and checking the results. It's a match if the result satisfies some numeric comparisons. + */ +abstract public class DualExpressionsNumberComparisonRowFilter implements RowFilter { + + final protected Evaluable _x_evaluable; + final protected String _x_columnName; + final protected int _x_cellIndex; + final protected Evaluable _y_evaluable; + final protected String _y_columnName; + final protected int _y_cellIndex; + + public DualExpressionsNumberComparisonRowFilter( + Evaluable x_evaluable, + String x_columnName, + int x_cellIndex, + Evaluable y_evaluable, + String y_columnName, + int y_cellIndex + ) { + _x_evaluable = x_evaluable; + _x_columnName = x_columnName; + _x_cellIndex = x_cellIndex; + _y_evaluable = y_evaluable; + _y_columnName = y_columnName; + _y_cellIndex = y_cellIndex; + } + + public boolean filterRow(Project project, int rowIndex, Row row) { + Cell x_cell = _x_cellIndex < 0 ? null : row.getCell(_x_cellIndex); + Properties x_bindings = ExpressionUtils.createBindings(project); + ExpressionUtils.bind(x_bindings, row, rowIndex, _x_columnName, x_cell); + Object x_value = _x_evaluable.evaluate(x_bindings); + + Cell y_cell = _y_cellIndex < 0 ? null : row.getCell(_y_cellIndex); + Properties y_bindings = ExpressionUtils.createBindings(project); + ExpressionUtils.bind(y_bindings, row, rowIndex, _y_columnName, y_cell); + Object y_value = _y_evaluable.evaluate(y_bindings); + + if (x_value != null && y_value != null) { + if (x_value.getClass().isArray() || y_value.getClass().isArray()) { + return false; + } else if (x_value instanceof Collection> || y_value instanceof Collection>) { + return false; + } // else, fall through + } + + return checkValue(x_value,y_value); + } + + protected boolean checkValue(Object vx, Object vy) { + if (ExpressionUtils.isError(vx) || ExpressionUtils.isError(vy)) { + return false; + } else if (ExpressionUtils.isNonBlankData(vx) && ExpressionUtils.isNonBlankData(vy)) { + if (vx instanceof Number && vy instanceof Number) { + double dx = ((Number) vx).doubleValue(); + double dy = ((Number) vy).doubleValue(); + return (!Double.isInfinite(dx) && !Double.isNaN(dx) && !Double.isInfinite(dy) && !Double.isNaN(dy) && checkValue(dx,dy)); + } else { + return false; + } + } else { + return false; + } + } + + abstract protected boolean checkValues(double dx, double dy); +} diff --git a/src/main/webapp/scripts/dialogs/scatterplot-dialog.js b/src/main/webapp/scripts/dialogs/scatterplot-dialog.js index 445cc28a4..b3fc0f9c7 100644 --- a/src/main/webapp/scripts/dialogs/scatterplot-dialog.js +++ b/src/main/webapp/scripts/dialogs/scatterplot-dialog.js @@ -92,14 +92,28 @@ ScatterplotDialog.prototype._renderMatrix = function() { if (columns.length > 0) { var table = $('