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 = $('
').addClass("scatterplot-matrix-table")[0]; + var wrap = function(element,cx,cy) { + return element.click(function() { + var options = { + "name" : name, + "x_columnName" : cx, + "y_columnName" : cy, + "x_expression" : "value", + "y_expression" : "value", + }; + ui.browsingEngine.addFacet("scatterplot",options); + //self._dismiss(); + }); + }; + for (var i = 0; i < columns.length; i++) { var tr = table.insertRow(table.rows.length); for (var j = 0; j < i; j++) { - var cx = columns[i]; - var cy = columns[j]; + var cx = columns[i].name; + var cy = columns[j].name; var plotter_params = { - 'cx' : cx.name, - 'cy' : cy.name, + 'cx' : cx, + 'cy' : cy, 'w' : self._plot_size, 'h' : self._plot_size, 'dot': self._dot_size, @@ -111,21 +125,9 @@ ScatterplotDialog.prototype._renderMatrix = function() { plotter: JSON.stringify(plotter_params) } var url = "/command/get-scatterplot?" + $.param(params); - var name = cx.name + '(x) vs. ' + cy.name + '(y)'; + var name = cx + ' (x) vs. ' + cy + ' (y)'; var cell = $(tr.insertCell(j)); - var link = $('').attr("title",name).click(function() { - ui.browsingEngine.addFacet( - "scatterplot", - { - "name" : name, - "x_column" : cx.name, - "y_column" : cy.name, - "expression" : "value", - "mode" : "scatterplot" - } - ); - //self._dismiss(); - }); + var link = wrap($('').attr("title",name),cx,cy); var plot = $('').addClass("scatterplot").appendTo(link); link.appendTo(cell); } diff --git a/src/main/webapp/scripts/facets/scatterplot-facet.js b/src/main/webapp/scripts/facets/scatterplot-facet.js index 192703bcd..2dfd02a86 100644 --- a/src/main/webapp/scripts/facets/scatterplot-facet.js +++ b/src/main/webapp/scripts/facets/scatterplot-facet.js @@ -29,15 +29,14 @@ ScatterplotFacet.prototype.getUIState = function() { return json; }; - ScatterplotFacet.prototype.getJSON = function() { var o = { type: "scatterplot", name: this._config.name, - mode: this._config.mode, - expression: this._config.expression, - x_column : this._config.x_column, - y_column : this._config.y_column, + x_columnName : this._config.x_columnName, + y_columnName : this._config.y_columnName, + x_expression: this._config.x_expression, + y_expression: this._config.y_expression, }; return o; @@ -72,40 +71,33 @@ ScatterplotFacet.prototype._initializeUI = function() { this._messageDiv = $('
').text("Loading...").addClass("facet-scatterplot-message").appendTo(bodyDiv); this._plotDiv = $('
').addClass("facet-scatterplot-plot").appendTo(bodyDiv); this._statusDiv = $('
').addClass("facet-scatterplot-status").appendTo(bodyDiv); - - this._plot = new ScatterplotWidget(this._plotDiv, { binColors: [ "#ccccff", "#6666ff" ] }); + + this._plot = new ScatterplotWidget(this._plotDiv, this._config); }; ScatterplotFacet.prototype.updateState = function(data) { - if ("min" in data && "max" in data) { + if ("min_x" in data && "max_x" in data && "max_y" in data && "min_y" in data) { this._error = false; - this._config.min = data.min; - this._config.max = data.max; - this._config.step = data.step; - this._baseBins = data.baseBins; - this._bins = data.bins; + this._config.min_x = data.min_x; + this._config.max_x = data.max_x; + this._config.min_y = data.min_y; + this._config.max_y = data.max_y; - switch (this._config.mode) { - case "min": - this._from = Math.max(data.from, this._config.min); - break; - case "max": - this._to = Math.min(data.to, this._config.max); - break; - default: - this._from = Math.max(data.from, this._config.min); - if ("to" in data) { - this._to = Math.min(data.to, this._config.max); - } else { - this._to = data.max; - } + this._from_x = Math.max(data.from_x, this._config.min_x); + if ("to_x" in data) { + this._to_x = Math.min(data.to_x, this._config.max_x); + } else { + this._to_x = data.max_x; + } + + this._from_y = Math.max(data.from_y, this._config.min_y); + if ("to_y" in data) { + this._to_y = Math.min(data.to_y, this._config.max_y); + } else { + this._to_y = data.max_y; } - this._numericCount = data.numericCount; - this._nonNumericCount = data.nonNumericCount; - this._blankCount = data.blankCount; - this._errorCount = data.errorCount; } else { this._error = true; this._errorMessage = "error" in data ? data.error : "Unknown error."; @@ -122,10 +114,8 @@ ScatterplotFacet.prototype.render = function() { if (this._error) { this._messageDiv.text(this._errorMessage).show(); - this._sliderDiv.hide(); - this._histogramDiv.hide(); + this._plotDiv.hide(); this._statusDiv.hide(); - this._otherChoicesDiv.hide(); return; } @@ -134,12 +124,14 @@ ScatterplotFacet.prototype.render = function() { this._statusDiv.show(); this._plot.update( - this._config.min, - this._config.max, - this._config.step, - [ this._baseBins, this._bins ], - this._from, - this._to + this._config.x_min, + this._config.x_max, + this._x_from, + this._x_to, + this._config.y_min, + this._config.y_max, + this._y_from, + this._y_to ); }; @@ -148,7 +140,6 @@ ScatterplotFacet.prototype._remove = function() { this._div = null; this._config = null; - this._data = null; }; ScatterplotFacet.prototype._updateRest = function() { diff --git a/src/main/webapp/scripts/widgets/histogram-widget.js b/src/main/webapp/scripts/widgets/histogram-widget.js index b927da4e5..5c08026e8 100644 --- a/src/main/webapp/scripts/widgets/histogram-widget.js +++ b/src/main/webapp/scripts/widgets/histogram-widget.js @@ -84,77 +84,77 @@ HistogramWidget.prototype._render = function() { ctx.translate(0, canvas.height); ctx.scale(1, -1); - var stepPixels = canvas.width / this._binMatrix[0].length; - var stepScale = stepPixels / this._range.step; - - /* - * Draw axis - */ - ctx.save(); - ctx.strokeStyle = "emptyBinColor" in options ? options.emptyBinColor : "#faa"; - ctx.lineWidth = 1; - ctx.moveTo(0, 0); - ctx.lineTo(canvas.width, 0); - ctx.stroke(); - ctx.restore(); + var stepPixels = canvas.width / this._binMatrix[0].length; + var stepScale = stepPixels / this._range.step; - /* - * Draw bins - */ - var makeColor = function(i) { - var n = Math.floor(15 * (self._binMatrix.length - i) / self._binMatrix.length); - var h = n.toString(16); - return "#" + h + h + h; - }; - var renderRow = function(row, color) { - ctx.save(); - ctx.lineWidth = 0; - ctx.fillStyle = color; - for (var c = 0; c < row.length; c++) { - var x = self._range.min + c * self._range.step; - var y = row[c]; - if (y > 0) { - var left = c * stepPixels; - var width = Math.ceil(stepPixels); - var height = Math.ceil(y * canvas.height / self._peak); - - ctx.fillRect(left, 0, width, height); - } + /* + * Draw axis + */ + ctx.save(); + ctx.strokeStyle = "emptyBinColor" in options ? options.emptyBinColor : "#faa"; + ctx.lineWidth = 1; + ctx.moveTo(0, 0); + ctx.lineTo(canvas.width, 0); + ctx.stroke(); + ctx.restore(); + + /* + * Draw bins + */ + var makeColor = function(i) { + var n = Math.floor(15 * (self._binMatrix.length - i) / self._binMatrix.length); + var h = n.toString(16); + return "#" + h + h + h; + }; + var renderRow = function(row, color) { + ctx.save(); + ctx.lineWidth = 0; + ctx.fillStyle = color; + for (var c = 0; c < row.length; c++) { + var x = self._range.min + c * self._range.step; + var y = row[c]; + if (y > 0) { + var left = c * stepPixels; + var width = Math.ceil(stepPixels); + var height = Math.ceil(y * canvas.height / self._peak); + + ctx.fillRect(left, 0, width, height); } - ctx.restore(); - }; - for (var r = 0; r < this._binMatrix.length; r++) { - renderRow( - this._binMatrix[r], - "binColors" in options && r < options.binColors.length ? - options.binColors[r] : - makeColor(r) + } + ctx.restore(); + }; + for (var r = 0; r < this._binMatrix.length; r++) { + renderRow( + this._binMatrix[r], + "binColors" in options && r < options.binColors.length ? + options.binColors[r] : + makeColor(r) + ); + } + + /* + * Draw highlight + */ + if (this._highlight !== null) { + ctx.fillStyle = "rgba(192,192,192, 0.5)"; + ctx.globalCompositeOperation = "source-over"; + if (this._highlight.from > this._range.min) { + ctx.fillRect( + 0, + 0, + (this._highlight.from - this._range.min) * stepScale, + canvas.height ); } - - /* - * Draw highlight - */ - if (this._highlight !== null) { - ctx.fillStyle = "rgba(192,192,192, 0.5)"; - ctx.globalCompositeOperation = "source-over"; - if (this._highlight.from > this._range.min) { - ctx.fillRect( - 0, - 0, - (this._highlight.from - this._range.min) * stepScale, - canvas.height - ); - } - if (this._highlight.to < this._range.max) { - ctx.fillRect( - (this._highlight.to - this._range.min) * stepScale, - 0, - canvas.width - (this._highlight.to - this._range.min) * stepScale, - canvas.height - ); - } + if (this._highlight.to < this._range.max) { + ctx.fillRect( + (this._highlight.to - this._range.min) * stepScale, + 0, + canvas.width - (this._highlight.to - this._range.min) * stepScale, + canvas.height + ); } + } ctx.restore(); }; diff --git a/src/main/webapp/scripts/widgets/scatterplot-widget.js b/src/main/webapp/scripts/widgets/scatterplot-widget.js index 9898cbb08..ea92c4c7f 100644 --- a/src/main/webapp/scripts/widgets/scatterplot-widget.js +++ b/src/main/webapp/scripts/widgets/scatterplot-widget.js @@ -2,39 +2,37 @@ function ScatterplotWidget(elmt, options) { this._elmt = elmt; this._options = options; + this._plotter = { + 'cx' : options.x_column, + 'cy' : options.y_column, + 'ye' : options.x_expression, + 'ye' : options.x_expression, + }; + this._range = null; - this._binMatrix = null; this._highlight = null; this._initializeUI(); } -ScatterplotWidget.prototype.highlight = function(from, to) { - this._highlight = { from: from, to: to }; +ScatterplotWidget.prototype.highlight = function(from_x, to_x, from_y, to_y) { + this._highlight = { from_x: from_x, to_x: to_x, from_y: from_y, to_y: to_y }; this._update(); }; -ScatterplotWidget.prototype.update = function(min, max, step, binMatrix, from, to) { - if (typeof min == "undefined" || typeof binMatrix == "undefined" || binMatrix.length === 0 || binMatrix[0].length === 0) { +ScatterplotWidget.prototype.update = function(x_min, x_max, x_from, x_to, y_min, y_max, y_from, y_to) { + if (typeof x_min == "undefined" || typeof y_min == "undefined") { this._range = null; - this._binMatrix = null; this._highlight = null; - + this._elmt.hide(); } else { - this._range = { min: min, max: max, step: step }; - this._binMatrix = binMatrix; - - this._peak = 0; - for (var r = 0; r < binMatrix.length; r++) { - var row = binMatrix[r]; - for (var c = 0; c < row.length; c++) { - this._peak = Math.max(this._peak, row[c]); - } - } - - if (typeof from != "undefined" && typeof to != "undefined") { - this._highlight = { from: from, to: to }; + this._range = { x_min: x_min, x_max: x_max, y_min: y_min, y_max: y_max }; + + if (typeof from_x != "undefined" && typeof to_x != "undefined" && + typeof from_y != "undefined" && typeof to_y != "undefined") + { + this._highlight = { from_x: from_x, to_x: to_x, from_y: from_y, to_y: to_y }; } this._update(); @@ -42,16 +40,16 @@ ScatterplotWidget.prototype.update = function(min, max, step, binMatrix, from, t }; ScatterplotWidget.prototype._update = function() { - if (this._binMatrix !== null) { - if (this._highlight !== null) { - this._highlight.from = Math.max(this._highlight.from, this._range.min); - this._highlight.to = Math.min(this._highlight.to, this._range.max); - } - - this._elmt.show(); - this._resize(); - this._render(); + if (this._highlight !== null) { + this._highlight.from_x = Math.max(this._highlight.from_x, this._range.min_x); + this._highlight.to_x = Math.min(this._highlight.to_x, this._range.max_x); + this._highlight.from_y = Math.max(this._highlight.from_y, this._range.min_y); + this._highlight.to_y = Math.min(this._highlight.to_y, this._range.max_y); } + + this._elmt.show(); + this._resize(); + this._render(); }; ScatterplotWidget.prototype._initializeUI = function() { @@ -59,16 +57,16 @@ ScatterplotWidget.prototype._initializeUI = function() { .empty() .hide() .addClass("scatterplot-widget") - .html( - '' - ); + .html(''); this._elmts = DOM.bind(this._elmt); }; ScatterplotWidget.prototype._resize = function() { - this._elmts.canvas.attr("height", "height" in this._options ? this._options.height : 50); - this._elmts.canvas.attr("width", this._elmts.canvas.width()); + this._plotter.w = this._elmts.canvas.width(); + this._plotter.h = ("height" in this._options) ? this._options.height : w; + this._elmts.canvas.attr("width", this._plotter.w); + this._elmts.canvas.attr("height", this._plotter.h); }; ScatterplotWidget.prototype._render = function() { @@ -79,82 +77,33 @@ ScatterplotWidget.prototype._render = function() { var ctx = canvas.getContext('2d'); ctx.fillStyle = "white"; ctx.fillRect(0, 0, canvas.width, canvas.height); - + ctx.save(); - ctx.translate(0, canvas.height); - ctx.scale(1, -1); - var stepPixels = canvas.width / this._binMatrix[0].length; - var stepScale = stepPixels / this._range.step; - - /* - * Draw axis - */ - ctx.save(); - ctx.strokeStyle = "emptyBinColor" in options ? options.emptyBinColor : "#faa"; - ctx.lineWidth = 1; - ctx.moveTo(0, 0); - ctx.lineTo(canvas.width, 0); - ctx.stroke(); - ctx.restore(); - - /* - * Draw bins - */ - var makeColor = function(i) { - var n = Math.floor(15 * (self._binMatrix.length - i) / self._binMatrix.length); - var h = n.toString(16); - return "#" + h + h + h; - }; - var renderRow = function(row, color) { - ctx.save(); - ctx.lineWidth = 0; - ctx.fillStyle = color; - for (var c = 0; c < row.length; c++) { - var x = self._range.min + c * self._range.step; - var y = row[c]; - if (y > 0) { - var left = c * stepPixels; - var width = Math.ceil(stepPixels); - var height = Math.ceil(y * canvas.height / self._peak); - - ctx.fillRect(left, 0, width, height); - } - } + var img = new Image(); + img.onload = function(){ + ctx.drawImage(img,0,0); + var img2 = new Image(); + img2.onload = function(){ + ctx.drawImage(img2,0,0); + + ctx.translate(0, canvas.height); + ctx.scale(1, -1); + + // draw something else + ctx.restore(); - }; - for (var r = 0; r < this._binMatrix.length; r++) { - renderRow( - this._binMatrix[r], - "binColors" in options && r < options.binColors.length ? - options.binColors[r] : - makeColor(r) - ); - } - - /* - * Draw highlight - */ - if (this._highlight !== null) { - ctx.fillStyle = "rgba(192,192,192, 0.5)"; - ctx.globalCompositeOperation = "source-over"; - if (this._highlight.from > this._range.min) { - ctx.fillRect( - 0, - 0, - (this._highlight.from - this._range.min) * stepScale, - canvas.height - ); - } - if (this._highlight.to < this._range.max) { - ctx.fillRect( - (this._highlight.to - this._range.min) * stepScale, - 0, - canvas.width - (this._highlight.to - this._range.min) * stepScale, - canvas.height - ); - } - } - - ctx.restore(); + } + img2.src = self._get_image_url(self._plotter) + "&color=000088&dot=0.3"; + } + img.src = self._get_image_url(self._plotter) + "&color=000000&dot=0.2"; +}; + +ScatterplotWidget.prototype._get_image_url = function(o) { + var params = { + project: theProject.id, + engine: JSON.stringify(ui.browsingEngine.getJSON()), + plotter: JSON.stringify(o) + } + return "/command/get-scatterplot?" + $.param(params); };