From f37d24e04aa6d163f1db0430f2ec586b3a052117 Mon Sep 17 00:00:00 2001 From: David Huynh Date: Thu, 6 May 2010 19:34:51 +0000 Subject: [PATCH] Scatterplot facets: - Changed coordinate systems of selection to zero to one, as fraction of plot's size - Used affine transform to do proper plot rotation - Filled negative quadrants of plot to indicate rotation - Set default dot size of actual facet's plot to regular git-svn-id: http://google-refine.googlecode.com/svn/trunk@612 7d457c2a-affb-35e4-300a-418c747d4874 --- .../facets/ScatterplotDrawingRowVisitor.java | 25 ++- .../browsing/facets/ScatterplotFacet.java | 144 ++++++++++-------- .../scripts/dialogs/scatterplot-dialog.js | 2 +- .../scripts/facets/scatterplot-facet.js | 88 +++++++---- 4 files changed, 158 insertions(+), 101 deletions(-) diff --git a/src/main/java/com/metaweb/gridworks/browsing/facets/ScatterplotDrawingRowVisitor.java b/src/main/java/com/metaweb/gridworks/browsing/facets/ScatterplotDrawingRowVisitor.java index 8ae16c535..144080c2a 100644 --- a/src/main/java/com/metaweb/gridworks/browsing/facets/ScatterplotDrawingRowVisitor.java +++ b/src/main/java/com/metaweb/gridworks/browsing/facets/ScatterplotDrawingRowVisitor.java @@ -34,6 +34,8 @@ public class ScatterplotDrawingRowVisitor implements RowVisitor { BufferedImage image; Graphics2D g2; + AffineTransform r; + public ScatterplotDrawingRowVisitor( int col_x, int col_y, double min_x, double max_x, double min_y, double max_y, int size, int dim_x, int dim_y, int rotation, double dot, Color color) @@ -50,16 +52,32 @@ public class ScatterplotDrawingRowVisitor implements RowVisitor { this.rotation = rotation; l = (double) size; + r = ScatterplotFacet.createRotationMatrix(rotation, l); image = new BufferedImage(size, size, BufferedImage.TYPE_4BYTE_ABGR); g2 = (Graphics2D) image.getGraphics(); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2.setStroke(new BasicStroke(1.0f)); - AffineTransform t = AffineTransform.getTranslateInstance(0,l); - t.concatenate(AffineTransform.getScaleInstance(1.0d, -1.0d)); + + AffineTransform t = AffineTransform.getTranslateInstance(0, l); + t.scale(1, -1); + g2.setTransform(t); g2.setColor(color); g2.setPaint(color); + + if (r != null) { + /* + * Fill in the negative quadrants to give a hint of how the plot has been rotated. + */ + Graphics2D g2r = (Graphics2D) g2.create(); + g2r.transform(r); + + g2r.setPaint(Color.lightGray); + g2r.fillRect(-size, 0, size, size); + g2r.fillRect(0, -size, size, size); + g2r.dispose(); + } } public boolean visit(Project project, int rowIndex, Row row, boolean includeContextual, boolean includeDependent) { @@ -73,7 +91,8 @@ public class ScatterplotDrawingRowVisitor implements RowVisitor { Point2D.Double p = new Point2D.Double(xv,yv); - p = ScatterplotFacet.translateCoordinates(p, dim_x, dim_y, rotation, l, min_x, max_x, min_y, max_y); + p = ScatterplotFacet.translateCoordinates( + p, min_x, max_x, min_y, max_y, dim_x, dim_y, l, r); g2.fill(new Rectangle2D.Double(p.x - dot / 2, p.y - dot / 2, dot, dot)); } 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 2ebd5736b..d4d1abe19 100644 --- a/src/main/java/com/metaweb/gridworks/browsing/facets/ScatterplotFacet.java +++ b/src/main/java/com/metaweb/gridworks/browsing/facets/ScatterplotFacet.java @@ -1,6 +1,7 @@ package com.metaweb.gridworks.browsing.facets; import java.awt.Color; +import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import java.awt.image.BufferedImage; import java.awt.image.RenderedImage; @@ -45,16 +46,6 @@ public class ScatterplotFacet implements Facet { protected String columnName_x; // column to base the x expression on, if any protected String columnName_y; // column to base the y expression on, if any - protected double from_x; // the numeric selection for the x axis - protected double to_x; - protected double from_y; // the numeric selection for the y axis - protected double to_y; - - protected double min_x; - protected double max_x; - protected double min_y; - protected double max_y; - protected int size; protected int dim_x; protected int dim_y; @@ -68,6 +59,11 @@ public class ScatterplotFacet implements Facet { protected String color_str; protected Color color; + protected double from_x; // the numeric selection for the x axis, from 0 to 1 + protected double to_x; + protected double from_y; // the numeric selection for the y axis, from 0 to 1 + protected double to_y; + /* * Derived configuration data */ @@ -78,6 +74,12 @@ public class ScatterplotFacet implements Facet { protected String errorMessage_x; protected String errorMessage_y; + protected double min_x; + protected double max_x; + protected double min_y; + protected double max_y; + protected AffineTransform t; + protected boolean selected; // false if we're certain that all rows will match // and there isn't any filtering to do @@ -144,8 +146,6 @@ public class ScatterplotFacet implements Facet { writer.key(ERROR_X); writer.value(errorMessage_x); } else { if (!Double.isInfinite(min_x) && !Double.isInfinite(max_x)) { - writer.key(MIN_X); writer.value(min_x); - writer.key(MAX_X); writer.value(max_x); writer.key(FROM_X); writer.value(from_x); writer.key(TO_X); writer.value(to_x); } @@ -155,8 +155,6 @@ public class ScatterplotFacet implements Facet { writer.key(ERROR_Y); writer.value(errorMessage_y); } else { if (!Double.isInfinite(min_y) && !Double.isInfinite(max_y)) { - writer.key(MIN_Y); writer.value(min_y); - writer.key(MAX_Y); writer.value(max_y); writer.key(FROM_Y); writer.value(from_y); writer.key(TO_Y); writer.value(to_y); } @@ -167,19 +165,34 @@ public class ScatterplotFacet implements Facet { public void initializeFromJSON(Project project, JSONObject o) throws Exception { name = o.getString(NAME); - - size = (o.has(SIZE)) ? o.getInt(SIZE) : 100; - l = size; - + l = size = (o.has(SIZE)) ? o.getInt(SIZE) : 100; dot = (o.has(DOT)) ? o.getInt(DOT) : 0.5d; dim_x = (o.has(DIM_X)) ? getAxisDim(o.getString(DIM_X)) : LIN; + if (o.has(FROM_X) && o.has(TO_X)) { + from_x = o.getDouble(FROM_X); + to_x = o.getDouble(TO_X); + selected = true; + } else { + from_x = 0; + to_x = 1; + } + dim_y = (o.has(DIM_Y)) ? getAxisDim(o.getString(DIM_Y)) : LIN; - + if (o.has(FROM_Y) && o.has(TO_Y)) { + from_y = o.getDouble(FROM_Y); + to_y = o.getDouble(TO_Y); + selected = true; + } else { + from_y = 0; + to_y = 1; + } + rotation = (o.has(ROTATION)) ? getRotation(o.getString(ROTATION)) : NO_ROTATION; + t = createRotationMatrix(rotation, l); color_str = (o.has(COLOR)) ? o.getString(COLOR) : "000000"; - color = new Color(Integer.parseInt(color_str,16)); + color = new Color(Integer.parseInt(color_str,16)); columnName_x = o.getString(X_COLUMN_NAME); expression_x = o.getString(X_EXPRESSION); @@ -205,12 +218,6 @@ public class ScatterplotFacet implements Facet { errorMessage_x = e.getMessage(); } - if (o.has(FROM_X) && o.has(TO_X)) { - from_x = o.getDouble(FROM_X); - to_x = o.getDouble(TO_X); - selected = true; - } - columnName_y = o.getString(Y_COLUMN_NAME); expression_y = o.getString(Y_EXPRESSION); @@ -235,11 +242,6 @@ public class ScatterplotFacet implements Facet { errorMessage_y = e.getMessage(); } - if (o.has(FROM_Y) && o.has(TO_Y)) { - from_y = o.getDouble(FROM_Y); - to_y = o.getDouble(TO_Y); - selected = true; - } } public RowFilter getRowFilter() { @@ -247,11 +249,18 @@ public class ScatterplotFacet implements Facet { eval_x != null && errorMessage_x == null && eval_y != null && errorMessage_y == null) { - return new DualExpressionsNumberComparisonRowFilter(eval_x, columnName_x, columnIndex_x, eval_y, columnName_y, columnIndex_y) { + return new DualExpressionsNumberComparisonRowFilter( + eval_x, columnName_x, columnIndex_x, eval_y, columnName_y, columnIndex_y) { + + double from_x_pixels = from_x * l; + double to_x_pixels = to_x * l; + double from_y_pixels = from_y * l; + double to_y_pixels = to_y * l; + protected boolean checkValues(double x, double y) { Point2D.Double p = new Point2D.Double(x,y); - p = translateCoordinates(p, dim_x, dim_y, rotation, l, min_x, max_x, min_y, max_y); - return p.x >= from_x && p.x <= to_x && p.y >= from_y && p.y <= to_y; + p = translateCoordinates(p, min_x, max_x, min_y, max_y, dim_x, dim_y, l, t); + return p.x >= from_x_pixels && p.x <= to_x_pixels && p.y >= from_y_pixels && p.y <= to_y_pixels; }; }; } else { @@ -266,14 +275,6 @@ public class ScatterplotFacet implements Facet { min_x = index_x.getMin(); max_x = index_x.getMax(); - - if (selected) { - from_x = Math.max(from_x, min_x); - to_x = Math.min(to_x, max_x); - } else { - from_x = min_x; - to_x = max_x; - } Column column_y = project.columnModel.getColumnByCellIndex(columnIndex_y); NumericBinIndex index_y = getBinIndex(project, column_y, eval_y, expression_y); @@ -281,14 +282,6 @@ public class ScatterplotFacet implements Facet { min_y = index_y.getMin(); max_y = index_y.getMax(); - if (selected) { - from_y = Math.max(from_y, min_y); - to_y = Math.min(to_y, max_y); - } else { - from_y = min_y; - to_y = max_y; - } - if (IMAGE_URI) { if (index_x.isNumeric() && index_y.isNumeric()) { ScatterplotDrawingRowVisitor drawer = new ScatterplotDrawingRowVisitor( @@ -350,36 +343,53 @@ public class ScatterplotFacet implements Facet { return index; } - public static Point2D.Double translateCoordinates(Point2D.Double p, int dim_x, int dim_y, int rotation, double l, double min_x, double max_x, double min_y, double max_y) { + private static double s_rotateScale = 1 / Math.sqrt(2.0); + + public static AffineTransform createRotationMatrix(int rotation, double l) { + if (rotation == ScatterplotFacet.ROTATE_CW) { + AffineTransform t = AffineTransform.getTranslateInstance(0, l / 2); + t.scale(s_rotateScale, s_rotateScale); + t.rotate(-Math.PI / 4); + return t; + } else if (rotation == ScatterplotFacet.ROTATE_CCW) { + AffineTransform t = AffineTransform.getTranslateInstance(l / 2, 0); + t.scale(s_rotateScale, s_rotateScale); + t.rotate(Math.PI / 4); + return t; + } else { + return null; + } + } + + public static Point2D.Double translateCoordinates( + Point2D.Double p, + double min_x, double max_x, double min_y, double max_y, + int dim_x, int dim_y, double l, AffineTransform t) { + double x = p.x; double y = p.y; + double relative_x = x - min_x; + double range_x = max_x - min_x; if (dim_x == ScatterplotFacet.LOG) { - x = Math.log10(p.x - min_x) * l / Math.log10(max_x - min_x); + x = Math.log10(relative_x) * l / Math.log10(range_x); } else { - x = (p.x - min_x) * l / (max_x - min_x); + x = relative_x * l / range_x; } + double relative_y = y - min_y; + double range_y = max_y - min_y; if (dim_y == ScatterplotFacet.LOG) { - y = Math.log10(p.y - min_y) * l / Math.log10(max_y - min_y); + y = Math.log10(relative_y) * l / Math.log10(range_y); } else { - y = (p.y - min_y) * l / (max_y - min_y); + y = relative_y * l / range_y; } - if (rotation == ScatterplotFacet.ROTATE_CW) { - double x1 = (x + y) / 2; - double y1 = (l - x + y) / 2; - x = x1; - y = y1; - } else if (rotation == ScatterplotFacet.ROTATE_CCW) { - double x1 = (l - y + x) / 2; - double y1 = (y + x) / 2; - x = x1; - y = y1; - } - p.x = x; p.y = y; + if (t != null) { + t.transform(p, p); + } return p; } diff --git a/src/main/webapp/scripts/dialogs/scatterplot-dialog.js b/src/main/webapp/scripts/dialogs/scatterplot-dialog.js index 36b36db2b..0c4d2cfcb 100644 --- a/src/main/webapp/scripts/dialogs/scatterplot-dialog.js +++ b/src/main/webapp/scripts/dialogs/scatterplot-dialog.js @@ -112,7 +112,7 @@ ScatterplotDialog.prototype._renderMatrix = function() { if (typeof self._plot_size == 'undefined') { self._plot_size = Math.max(Math.floor(500 / columns.length / 5) * 5,20); - self._dot_size = 0.4; + self._dot_size = 0.8; self._elmts.plotSize.val(self._plot_size); self._elmts.dotSize.val(self._dot_size); } diff --git a/src/main/webapp/scripts/facets/scatterplot-facet.js b/src/main/webapp/scripts/facets/scatterplot-facet.js index 68c99dbc2..0636508cc 100644 --- a/src/main/webapp/scripts/facets/scatterplot-facet.js +++ b/src/main/webapp/scripts/facets/scatterplot-facet.js @@ -47,8 +47,8 @@ ScatterplotFacet.prototype.getJSON = function() { ScatterplotFacet.prototype.hasSelection = function() { return ("from_x" in this._config && this._config.from_x !== 0) || ("from_y" in this._config && this._config.from_y !== 0) || - ("to_x" in this._config && this._config.to_x != this._config.l) || - ("to_y" in this._config && this._config.to_y != this._config.l); + ("to_x" in this._config && this._config.to_x !== 1) || + ("to_y" in this._config && this._config.to_y !== 1); }; ScatterplotFacet.prototype._initializeUI = function() { @@ -123,24 +123,12 @@ ScatterplotFacet.prototype._initializeUI = function() { parent: this._elmts.plotDiv, fadeSpeed: 70, onSelectEnd: function(elmt, selection) { - if (selection.height === 0 || selection.width === 0) { - self.reset(); - } else { - self._config.from_x = selection.x1; - self._config.to_x = selection.x2 - 2; - self._config.from_y = self._config.l - selection.y2 + 2; - self._config.to_y = self._config.l - selection.y1 - 1; - } + self._putSelectionOptions(selection); self._updateRest(); } }; - if (this.hasSelection()) { - ops.x1 = this._config.from_x; - ops.y1 = this._config.from_y; - ops.x2 = this._config.to_x; - ops.y2 = this._config.to_y; - } + this._fillSelectionOptions(ops); this._plotAreaSelector = this._elmts.plotImg.imgAreaSelect(ops); if (this._config.dim_x == 'lin' && this._config.dim_y == 'lin') { @@ -169,11 +157,15 @@ ScatterplotFacet.prototype._initializeUI = function() { var dim = $(this).find("input:checked").val(); self._config.dim_x = dim; self._config.dim_y = dim; + self.reset(); + self._updateRest(); self.changePlot(); }); this._elmts.selectors.find(".facet-scatterplot-rot-selector").change(function() { self._config.r = $(this).find("input:checked").val(); + self.reset(); + self._updateRest(); self.changePlot(); }); @@ -192,6 +184,32 @@ ScatterplotFacet.prototype._initializeUI = function() { this._elmts.selectors.find(".buttonset").buttonset(); }; +ScatterplotFacet.prototype._fillSelectionOptions = function(ops) { + if (this.hasSelection()) { + ops.x1 = this._config.l * this._config.from_x; + ops.x2 = this._config.l * this._config.to_x; + + ops.y1 = this._config.l - (this._config.l * this._config.to_y); + ops.y2 = this._config.l - (this._config.l * this._config.from_y); + } else { + ops.x1 = ops.y1 = 0; + ops.x2 = ops.y2 = this._config.l; + ops.hide = true; + } +}; + +ScatterplotFacet.prototype._putSelectionOptions = function(selection) { + if (selection.height === 0 || selection.width === 0) { + this.reset(); + } else { + this._config.from_x = selection.x1 / this._config.l; + this._config.to_x = selection.x2 / this._config.l; + + this._config.from_y = (this._config.l - selection.y2) / this._config.l; + this._config.to_y = (this._config.l - selection.y1) / this._config.l; + } +}; + ScatterplotFacet.prototype._formulateCurrentImageUrl = function() { return this._formulateImageUrl(ui.browsingEngine.getJSON(false, this), { color: "ff6a00" }); }; @@ -215,30 +233,41 @@ ScatterplotFacet.prototype._formulateImageUrl = function(engineConfig, conf) { }; ScatterplotFacet.prototype.updateState = function(data) { - if ("min_x" in data && "max_x" in data && "max_y" in data && "min_y" in data) { + if ("error" in data) { + this._error = true; + this._errorMessage = "error" in data ? data.error : "Unknown error."; + } else { this._error = false; - 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; - + // These are in 0 - 1 coordinates if ("from_x" in data) { - this._config.from_x = Math.max(data.from_x, 0); + this._config.from_x = Math.min(Math.max(data.from_x, 0), 1); + } else { + this._config.from_x = 0; } if ("to_x" in data) { - this._config.to_x = Math.min(data.to_x, this._config.l); + this._config.to_x = Math.min(Math.max(data.to_x, data.from_x), 1); + } else { + this._config.to_x = 1; } if ("from_y" in data) { - this._config.from_y = Math.max(data.from_y, 0); + this._config.from_y = Math.min(Math.max(data.from_y, 0), 1); + } else { + this._config.from_y = 0; } if ("to_y" in data) { - this._config.to_y = Math.min(data.to_y, this._config.l); + this._config.to_y = Math.min(Math.max(data.to_y, data.from_y), this._config.l); + } else { + this._config.to_y = 1; + } + + if (this._plotAreaSelector) { + var ops = {}; + this._fillSelectionOptions(ops); + this._plotAreaSelector.setOptions(ops); + this._plotAreaSelector.update(); } - } else { - this._error = true; - this._errorMessage = "error" in data ? data.error : "Unknown error."; } this.render(); @@ -247,7 +276,6 @@ ScatterplotFacet.prototype.updateState = function(data) { ScatterplotFacet.prototype.changePlot = function() { this._elmts.plotBaseImg.attr("src", this._formulateBaseImageUrl()); this._elmts.plotImg.attr("src", this._formulateCurrentImageUrl()); - this._updateRest(); }; ScatterplotFacet.prototype.render = function() {