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
This commit is contained in:
David Huynh 2010-05-06 19:34:51 +00:00
parent 0562881a06
commit f37d24e04a
4 changed files with 158 additions and 101 deletions

View File

@ -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));
}

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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() {