first step at scatterplot facet selector

git-svn-id: http://google-refine.googlecode.com/svn/trunk@442 7d457c2a-affb-35e4-300a-418c747d4874
This commit is contained in:
Stefano Mazzocchi 2010-04-10 08:28:06 +00:00
parent ed0778f18d
commit 81fb2f1740
8 changed files with 335 additions and 8 deletions

View File

@ -18,18 +18,18 @@ import com.metaweb.gridworks.commands.edit.AnnotateRowsCommand;
import com.metaweb.gridworks.commands.edit.ApplyOperationsCommand;
import com.metaweb.gridworks.commands.edit.CreateProjectCommand;
import com.metaweb.gridworks.commands.edit.DeleteProjectCommand;
import com.metaweb.gridworks.commands.edit.EditOneCellCommand;
import com.metaweb.gridworks.commands.edit.ExportProjectCommand;
import com.metaweb.gridworks.commands.edit.ExtendDataCommand;
import com.metaweb.gridworks.commands.edit.ImportProjectCommand;
import com.metaweb.gridworks.commands.edit.JoinMultiValueCellsCommand;
import com.metaweb.gridworks.commands.edit.MassEditCommand;
import com.metaweb.gridworks.commands.edit.RemoveColumnCommand;
import com.metaweb.gridworks.commands.edit.RemoveRowsCommand;
import com.metaweb.gridworks.commands.edit.RenameColumnCommand;
import com.metaweb.gridworks.commands.edit.TextTransformCommand;
import com.metaweb.gridworks.commands.edit.EditOneCellCommand;
import com.metaweb.gridworks.commands.edit.MassEditCommand;
import com.metaweb.gridworks.commands.edit.JoinMultiValueCellsCommand;
import com.metaweb.gridworks.commands.edit.RemoveColumnCommand;
import com.metaweb.gridworks.commands.edit.SaveProtographCommand;
import com.metaweb.gridworks.commands.edit.SplitMultiValueCellsCommand;
import com.metaweb.gridworks.commands.edit.TextTransformCommand;
import com.metaweb.gridworks.commands.edit.UndoRedoCommand;
import com.metaweb.gridworks.commands.info.ComputeClustersCommand;
import com.metaweb.gridworks.commands.info.ComputeFacetsCommand;
@ -42,6 +42,7 @@ import com.metaweb.gridworks.commands.info.GetOperationsCommand;
import com.metaweb.gridworks.commands.info.GetProcessesCommand;
import com.metaweb.gridworks.commands.info.GetProjectMetadataCommand;
import com.metaweb.gridworks.commands.info.GetRowsCommand;
import com.metaweb.gridworks.commands.info.GetScatterplotCommand;
import com.metaweb.gridworks.commands.recon.ReconDiscardJudgmentsCommand;
import com.metaweb.gridworks.commands.recon.ReconJudgeOneCellCommand;
import com.metaweb.gridworks.commands.recon.ReconJudgeSimilarCellsCommand;
@ -82,6 +83,7 @@ public class GridworksServlet extends HttpServlet {
_commands.put("get-processes", new GetProcessesCommand());
_commands.put("get-history", new GetHistoryCommand());
_commands.put("get-operations", new GetOperationsCommand());
_commands.put("get-scatterplot", new GetScatterplotCommand());
_commands.put("undo-redo", new UndoRedoCommand());
_commands.put("apply-operations", new ApplyOperationsCommand());

View File

@ -0,0 +1,152 @@
package com.metaweb.gridworks.browsing.charting;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.io.IOException;
import java.io.OutputStream;
import javax.imageio.ImageIO;
import org.json.JSONException;
import org.json.JSONObject;
import com.metaweb.gridworks.browsing.Engine;
import com.metaweb.gridworks.browsing.FilteredRows;
import com.metaweb.gridworks.browsing.RowVisitor;
import com.metaweb.gridworks.browsing.facets.NumericBinIndex;
import com.metaweb.gridworks.expr.Evaluable;
import com.metaweb.gridworks.expr.MetaParser;
import com.metaweb.gridworks.expr.ParsingException;
import com.metaweb.gridworks.model.Cell;
import com.metaweb.gridworks.model.Column;
import com.metaweb.gridworks.model.Project;
import com.metaweb.gridworks.model.Row;
public class ScatterplotCharter {
private static final Color COLOR = Color.black;
public String getContentType() {
return "image/png";
}
public void draw(OutputStream output, Project project, Engine engine, JSONObject options) throws IOException, JSONException {
DrawingRowVisitor drawingVisitor = new DrawingRowVisitor(project, options);
FilteredRows filteredRows = engine.getAllFilteredRows(false);
filteredRows.accept(project, drawingVisitor);
ImageIO.write(drawingVisitor.getImage(), "png", output);
}
class DrawingRowVisitor implements RowVisitor {
private static final double px = 0.5f;
boolean process = true;
int width = 50;
int height = 50;
int col_x;
int col_y;
double w;
double h;
double min_x;
double min_y;
double max_x;
double max_y;
NumericBinIndex index_x;
NumericBinIndex index_y;
BufferedImage image;
Graphics2D g2;
public DrawingRowVisitor(Project project, JSONObject o) throws JSONException {
String col_x_name = o.getString("cx");
Column column_x = project.columnModel.getColumnByName(col_x_name);
if (column_x != null) {
col_x = column_x.getCellIndex();
index_x = getBinIndex(project, column_x);
min_x = index_x.getMin() * 1.1d;
max_x = index_x.getMax() * 1.1d;
}
String col_y_name = o.getString("cy");
Column column_y = project.columnModel.getColumnByName(col_y_name);
if (column_y != null) {
col_y = column_y.getCellIndex();
index_y = getBinIndex(project, column_y);
min_y = index_y.getMin() * 1.1d;
max_y = index_y.getMax() * 1.1d;
}
width = o.getInt("w");
height = o.getInt("h");
w = (double) width;
h = (double) height;
if (index_x.isNumeric() && index_y.isNumeric()) {
image = new BufferedImage(width, height, 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,h);
t.concatenate(AffineTransform.getScaleInstance(1.0d, -1.0d));
g2.setTransform(t);
g2.setColor(COLOR);
g2.setPaint(COLOR);
} else {
image = new BufferedImage(1, 1, BufferedImage.TYPE_4BYTE_ABGR);
process = false;
}
}
private NumericBinIndex getBinIndex(Project project, Column column) {
String key = "numeric-bin:value";
Evaluable eval = null;
try {
eval = MetaParser.parse("value");
} catch (ParsingException e) {
// this should never happen
}
NumericBinIndex index = (NumericBinIndex) column.getPrecompute(key);
if (index == null) {
index = new NumericBinIndex(project, column.getName(), column.getCellIndex(), eval);
column.setPrecompute(key, index);
}
return index;
}
public boolean visit(Project project, int rowIndex, Row row, boolean includeContextual, boolean includeDependent) {
if (process) {
Cell cellx = row.getCell(col_x);
Cell celly = row.getCell(col_y);
if ((cellx != null && cellx.value != null && cellx.value instanceof Number) &&
(celly != null && celly.value != null && celly.value instanceof Number))
{
double xv = ((Number) cellx.value).doubleValue();
double yv = ((Number) celly.value).doubleValue();
double x = (xv - min_x) * w / max_x;
double y = (yv - min_y) * h / max_y;
g2.fill(new Rectangle2D.Double(x, y, px, px));
}
}
return false;
}
public RenderedImage getImage() {
return image;
}
}
}

View File

@ -22,6 +22,9 @@ import com.metaweb.gridworks.model.Row;
* as the user interacts with the facet.
*/
public class NumericBinIndex {
private int _total_count;
private int _number_count;
private double _min;
private double _max;
private double _step;
@ -45,22 +48,29 @@ public class NumericBinIndex {
if (value.getClass().isArray()) {
Object[] a = (Object[]) value;
for (Object v : a) {
_total_count++;
if (v instanceof Number) {
processValue(((Number) v).doubleValue(), allValues);
}
}
} else if (value instanceof Collection<?>) {
for (Object v : ExpressionUtils.toObjectCollection(value)) {
_total_count++;
if (v instanceof Number) {
processValue(((Number) v).doubleValue(), allValues);
}
}
} else if (value instanceof Number) {
processValue(((Number) value).doubleValue(), allValues);
} else {
_total_count++;
if (value instanceof Number) {
processValue(((Number) value).doubleValue(), allValues);
}
}
}
}
_number_count = allValues.size();
if (_min >= _max) {
_step = 1;
_min = 0;
@ -105,6 +115,14 @@ public class NumericBinIndex {
}
}
public boolean isNumeric() {
return _number_count > _total_count / 2;
}
public int getNumberCount() {
return _number_count;
}
public double getMin() {
return _min;
}

View File

@ -0,0 +1,46 @@
package com.metaweb.gridworks.commands.info;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.json.JSONObject;
import com.metaweb.gridworks.browsing.Engine;
import com.metaweb.gridworks.browsing.charting.ScatterplotCharter;
import com.metaweb.gridworks.commands.Command;
import com.metaweb.gridworks.model.Project;
public class GetScatterplotCommand extends Command {
final private ScatterplotCharter charter = new ScatterplotCharter();
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
try {
//long start = System.currentTimeMillis();
Project project = getProject(request);
Engine engine = getEngine(request, project);
JSONObject conf = getJsonParameter(request,"plotter");
response.setHeader("Content-Type", charter.getContentType());
ServletOutputStream sos = null;
try {
sos = response.getOutputStream();
charter.draw(sos, project, engine, conf);
} finally {
sos.close();
}
//Gridworks.log("drawn scatterplot in " + (System.currentTimeMillis() - start) + "ms");
} catch (Exception e) {
respondException(response, e);
}
}
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,80 @@
function ScatterplotDialog() {
this._createDialog();
}
ScatterplotDialog.prototype._createDialog = function() {
var self = this;
var frame = DialogSystem.createDialog();
frame.width("900px");
var header = $('<div></div>').addClass("dialog-header").text('Scatterplot Matrix').appendTo(frame);
var body = $('<div></div>').addClass("dialog-body").appendTo(frame);
var footer = $(
'<div class="dialog-footer">' +
'<table width="100%"><tr>' +
'<td class="left" style="text-align: left"></td>' +
'<td class="right" style="text-align: right"></td>' +
'</tr></table>' +
'</div>'
).appendTo(frame);
var html = $(
'<div class="grid-layout layout-normal">' +
'<div bind="tableContainer" class="scatterplot-dialog-table-container"></div>' +
'</div>'
).appendTo(body);
this._elmts = DOM.bind(html);
var left_footer = footer.find(".left");
var right_footer = footer.find(".right");
$('<button></button>').text("Close").click(function() { self._dismiss(); }).appendTo(right_footer);
this._renderMatrix(theProject.columnModel.columns);
this._level = DialogSystem.showDialog(frame);
};
ScatterplotDialog.prototype._renderMatrix = function(columns) {
var self = this;
var container = this._elmts.tableContainer;
if (columns.length > 0) {
var table = $('<table></table>').addClass("scatterplot-matrix-table")[0];
for (var i = 0; i < columns.length; i++) {
var tr = table.insertRow(table.rows.length);
for (var j = 0; j < i; j++) {
var url = "/command/get-scatterplot?" + $.param({
project: theProject.id,
engine: JSON.stringify(ui.browsingEngine.getJSON()),
plotter: JSON.stringify({
'cx' : columns[i].name,
'cy' : columns[j].name,
'w' : 20,
'h' : 20
})
});
$(tr.insertCell(j)).html('<img class="scatterplot" title="' + columns[i].name + ' vs. ' + columns[j].name + '" src="' + url + '" />');
}
$(tr.insertCell(i)).text(columns[i]);
for (var j = i + 1; j < columns.length; j++) {
$(tr.insertCell(j)).text(" ");
}
}
container.empty().append(table);
} else {
container.html(
'<div style="margin: 2em;"><div style="font-size: 130%; color: #333;">There are no columns in this dataset</div></div>'
);
}
};
ScatterplotDialog.prototype._dismiss = function() {
DialogSystem.dismissUntil(this._level - 1);
};

View File

@ -62,6 +62,12 @@ MenuBar.prototype._initializeUI = function() {
click: function() {}
}
]);
this._createTopLevelMenuItem("Scatterplots", [
{
label: "Show scatterplot matrix ...",
click: function() { self._showScatterplotMatrix(); }
}
]);
this._wireAllMenuItemsInactive();
};
@ -217,3 +223,7 @@ MenuBar.prototype._doAutoSchemaAlignment = function() {
MenuBar.prototype._doEditSchemaAlignment = function(reset) {
new SchemaAlignmentDialog(reset ? null : theProject.protograph, function(newProtograph) {});
};
MenuBar.prototype._showScatterplotMatrix = function() {
new ScatterplotDialog();
};

View File

@ -0,0 +1,19 @@
.scatterplot-dialog-table-container {
height: 500px;
overflow: auto;
border: 1px solid #aaa;
}
table.scatterplot-matrix-table {
border-collapse: collapse;
}
table.scatterplot-matrix-table > tbody > tr > td {
padding: 2px;
text-align: center;
vertical-align: middle;
}
table.scatterplot-matrix-table img.scatterplot {
border: 1px solid #eee;
}