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:
parent
0562881a06
commit
f37d24e04a
@ -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));
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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() {
|
||||
|
Loading…
Reference in New Issue
Block a user