From 81fb2f1740282bc06f75fca0bfdd456548a06ebc Mon Sep 17 00:00:00 2001 From: Stefano Mazzocchi Date: Sat, 10 Apr 2010 08:28:06 +0000 Subject: [PATCH] first step at scatterplot facet selector git-svn-id: http://google-refine.googlecode.com/svn/trunk@442 7d457c2a-affb-35e4-300a-418c747d4874 --- .../metaweb/gridworks/GridworksServlet.java | 12 +- .../browsing/charting/ScatterplotCharter.java | 152 ++++++++++++++++++ .../browsing/facets/NumericBinIndex.java | 22 ++- .../commands/info/GetScatterplotCommand.java | 46 ++++++ src/main/webapp/project.html | 2 +- .../scripts/dialogs/scatterplot-dialog.js | 80 +++++++++ src/main/webapp/scripts/project/menu-bar.js | 10 ++ .../styles/dialogs/scatterplot-dialog.css | 19 +++ 8 files changed, 335 insertions(+), 8 deletions(-) create mode 100644 src/main/java/com/metaweb/gridworks/browsing/charting/ScatterplotCharter.java create mode 100644 src/main/java/com/metaweb/gridworks/commands/info/GetScatterplotCommand.java create mode 100644 src/main/webapp/scripts/dialogs/scatterplot-dialog.js create mode 100644 src/main/webapp/styles/dialogs/scatterplot-dialog.css diff --git a/src/main/java/com/metaweb/gridworks/GridworksServlet.java b/src/main/java/com/metaweb/gridworks/GridworksServlet.java index 05f304203..b603650d9 100644 --- a/src/main/java/com/metaweb/gridworks/GridworksServlet.java +++ b/src/main/java/com/metaweb/gridworks/GridworksServlet.java @@ -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()); diff --git a/src/main/java/com/metaweb/gridworks/browsing/charting/ScatterplotCharter.java b/src/main/java/com/metaweb/gridworks/browsing/charting/ScatterplotCharter.java new file mode 100644 index 000000000..36caa391d --- /dev/null +++ b/src/main/java/com/metaweb/gridworks/browsing/charting/ScatterplotCharter.java @@ -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; + } + } +} diff --git a/src/main/java/com/metaweb/gridworks/browsing/facets/NumericBinIndex.java b/src/main/java/com/metaweb/gridworks/browsing/facets/NumericBinIndex.java index 889a91aae..6dbef5cf0 100644 --- a/src/main/java/com/metaweb/gridworks/browsing/facets/NumericBinIndex.java +++ b/src/main/java/com/metaweb/gridworks/browsing/facets/NumericBinIndex.java @@ -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; } diff --git a/src/main/java/com/metaweb/gridworks/commands/info/GetScatterplotCommand.java b/src/main/java/com/metaweb/gridworks/commands/info/GetScatterplotCommand.java new file mode 100644 index 000000000..9b95cc2ee --- /dev/null +++ b/src/main/java/com/metaweb/gridworks/commands/info/GetScatterplotCommand.java @@ -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); + } + } +} diff --git a/src/main/webapp/project.html b/src/main/webapp/project.html index 34128123d..a6c21785c 100644 --- a/src/main/webapp/project.html +++ b/src/main/webapp/project.html @@ -1 +1 @@ - Freebase Gridworks
starting up ...
\ No newline at end of file + Freebase Gridworks
starting up ...
\ No newline at end of file diff --git a/src/main/webapp/scripts/dialogs/scatterplot-dialog.js b/src/main/webapp/scripts/dialogs/scatterplot-dialog.js new file mode 100644 index 000000000..9ccf67a4c --- /dev/null +++ b/src/main/webapp/scripts/dialogs/scatterplot-dialog.js @@ -0,0 +1,80 @@ +function ScatterplotDialog() { + this._createDialog(); +} + +ScatterplotDialog.prototype._createDialog = function() { + var self = this; + var frame = DialogSystem.createDialog(); + frame.width("900px"); + + var header = $('
').addClass("dialog-header").text('Scatterplot Matrix').appendTo(frame); + var body = $('
').addClass("dialog-body").appendTo(frame); + var footer = $( + '' + ).appendTo(frame); + + var html = $( + '
' + + '
' + + '
' + ).appendTo(body); + + this._elmts = DOM.bind(html); + + var left_footer = footer.find(".left"); + + var right_footer = footer.find(".right"); + $('').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 = $('
').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(''); + } + $(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( + '
There are no columns in this dataset
' + ); + } + +}; + +ScatterplotDialog.prototype._dismiss = function() { + DialogSystem.dismissUntil(this._level - 1); +}; + diff --git a/src/main/webapp/scripts/project/menu-bar.js b/src/main/webapp/scripts/project/menu-bar.js index 65d88ada6..31b3fb073 100644 --- a/src/main/webapp/scripts/project/menu-bar.js +++ b/src/main/webapp/scripts/project/menu-bar.js @@ -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(); +}; diff --git a/src/main/webapp/styles/dialogs/scatterplot-dialog.css b/src/main/webapp/styles/dialogs/scatterplot-dialog.css new file mode 100644 index 000000000..6732d819c --- /dev/null +++ b/src/main/webapp/styles/dialogs/scatterplot-dialog.css @@ -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; +}