skeleton code for scatterfacet
git-svn-id: http://google-refine.googlecode.com/svn/trunk@453 7d457c2a-affb-35e4-300a-418c747d4874
This commit is contained in:
parent
e154a7e02d
commit
7ab1acd801
@ -13,6 +13,7 @@ import com.metaweb.gridworks.Jsonizable;
|
|||||||
import com.metaweb.gridworks.browsing.facets.Facet;
|
import com.metaweb.gridworks.browsing.facets.Facet;
|
||||||
import com.metaweb.gridworks.browsing.facets.ListFacet;
|
import com.metaweb.gridworks.browsing.facets.ListFacet;
|
||||||
import com.metaweb.gridworks.browsing.facets.RangeFacet;
|
import com.metaweb.gridworks.browsing.facets.RangeFacet;
|
||||||
|
import com.metaweb.gridworks.browsing.facets.ScatterplotFacet;
|
||||||
import com.metaweb.gridworks.browsing.facets.TextSearchFacet;
|
import com.metaweb.gridworks.browsing.facets.TextSearchFacet;
|
||||||
import com.metaweb.gridworks.browsing.filters.RowFilter;
|
import com.metaweb.gridworks.browsing.filters.RowFilter;
|
||||||
import com.metaweb.gridworks.model.Project;
|
import com.metaweb.gridworks.model.Project;
|
||||||
@ -66,6 +67,8 @@ public class Engine implements Jsonizable {
|
|||||||
facet = new ListFacet();
|
facet = new ListFacet();
|
||||||
} else if ("range".equals(type)) {
|
} else if ("range".equals(type)) {
|
||||||
facet = new RangeFacet();
|
facet = new RangeFacet();
|
||||||
|
} else if ("scatterplot".equals(type)) {
|
||||||
|
facet = new ScatterplotFacet();
|
||||||
} else if ("text".equals(type)) {
|
} else if ("text".equals(type)) {
|
||||||
facet = new TextSearchFacet();
|
facet = new TextSearchFacet();
|
||||||
}
|
}
|
||||||
|
@ -50,6 +50,7 @@ public class ScatterplotCharter {
|
|||||||
private static final double px = 0.5f;
|
private static final double px = 0.5f;
|
||||||
|
|
||||||
boolean process = true;
|
boolean process = true;
|
||||||
|
boolean smoothed = false;
|
||||||
|
|
||||||
int width = 50;
|
int width = 50;
|
||||||
int height = 50;
|
int height = 50;
|
||||||
@ -68,7 +69,7 @@ public class ScatterplotCharter {
|
|||||||
|
|
||||||
BufferedImage image;
|
BufferedImage image;
|
||||||
Graphics2D g2;
|
Graphics2D g2;
|
||||||
|
|
||||||
public DrawingRowVisitor(Project project, JSONObject o) throws JSONException {
|
public DrawingRowVisitor(Project project, JSONObject o) throws JSONException {
|
||||||
String col_x_name = o.getString("cx");
|
String col_x_name = o.getString("cx");
|
||||||
Column column_x = project.columnModel.getColumnByName(col_x_name);
|
Column column_x = project.columnModel.getColumnByName(col_x_name);
|
||||||
@ -135,7 +136,7 @@ public class ScatterplotCharter {
|
|||||||
{
|
{
|
||||||
double xv = ((Number) cellx.value).doubleValue();
|
double xv = ((Number) cellx.value).doubleValue();
|
||||||
double yv = ((Number) celly.value).doubleValue();
|
double yv = ((Number) celly.value).doubleValue();
|
||||||
|
|
||||||
double x = (xv - min_x) * w / max_x;
|
double x = (xv - min_x) * w / max_x;
|
||||||
double y = (yv - min_y) * h / max_y;
|
double y = (yv - min_y) * h / max_y;
|
||||||
g2.fill(new Rectangle2D.Double(x, y, px, px));
|
g2.fill(new Rectangle2D.Double(x, y, px, px));
|
||||||
|
@ -0,0 +1,234 @@
|
|||||||
|
package com.metaweb.gridworks.browsing.facets;
|
||||||
|
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.json.JSONWriter;
|
||||||
|
|
||||||
|
import com.metaweb.gridworks.browsing.FilteredRows;
|
||||||
|
import com.metaweb.gridworks.browsing.filters.ExpressionNumberComparisonRowFilter;
|
||||||
|
import com.metaweb.gridworks.browsing.filters.RowFilter;
|
||||||
|
import com.metaweb.gridworks.expr.Evaluable;
|
||||||
|
import com.metaweb.gridworks.expr.MetaParser;
|
||||||
|
import com.metaweb.gridworks.expr.ParsingException;
|
||||||
|
import com.metaweb.gridworks.model.Column;
|
||||||
|
import com.metaweb.gridworks.model.Project;
|
||||||
|
import com.metaweb.gridworks.util.JSONUtilities;
|
||||||
|
|
||||||
|
public class ScatterplotFacet implements Facet {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Configuration, from the client side
|
||||||
|
*/
|
||||||
|
protected String _name; // name of facet
|
||||||
|
protected String _expression; // expression to compute numeric value(s) per row
|
||||||
|
protected String _columnName; // column to base expression on, if any
|
||||||
|
protected String _mode; // "range", MIN, MAX
|
||||||
|
|
||||||
|
protected double _from; // the numeric selection
|
||||||
|
protected double _to;
|
||||||
|
|
||||||
|
protected boolean _selectNumeric; // whether the numeric selection applies, default true
|
||||||
|
protected boolean _selectNonNumeric;
|
||||||
|
protected boolean _selectBlank;
|
||||||
|
protected boolean _selectError;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Derived configuration data
|
||||||
|
*/
|
||||||
|
protected int _cellIndex;
|
||||||
|
protected Evaluable _eval;
|
||||||
|
protected String _errorMessage;
|
||||||
|
protected boolean _selected; // false if we're certain that all rows will match
|
||||||
|
// and there isn't any filtering to do
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Computed data, to return to the client side
|
||||||
|
*/
|
||||||
|
protected double _min;
|
||||||
|
protected double _max;
|
||||||
|
protected double _step;
|
||||||
|
protected int[] _baseBins;
|
||||||
|
protected int[] _bins;
|
||||||
|
|
||||||
|
protected int _numericCount;
|
||||||
|
protected int _nonNumericCount;
|
||||||
|
protected int _blankCount;
|
||||||
|
protected int _errorCount;
|
||||||
|
|
||||||
|
public ScatterplotFacet() {
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final String MIN = "min";
|
||||||
|
private static final String MAX = "max";
|
||||||
|
private static final String TO = "to";
|
||||||
|
private static final String FROM = "from";
|
||||||
|
|
||||||
|
public void write(JSONWriter writer, Properties options)
|
||||||
|
throws JSONException {
|
||||||
|
|
||||||
|
writer.object();
|
||||||
|
writer.key("name"); writer.value(_name);
|
||||||
|
writer.key("expression"); writer.value(_expression);
|
||||||
|
writer.key("columnName"); writer.value(_columnName);
|
||||||
|
writer.key("mode"); writer.value(_mode);
|
||||||
|
|
||||||
|
if (_errorMessage != null) {
|
||||||
|
writer.key("error"); writer.value(_errorMessage);
|
||||||
|
} else {
|
||||||
|
if (!Double.isInfinite(_min) && !Double.isInfinite(_max)) {
|
||||||
|
writer.key(MIN); writer.value(_min);
|
||||||
|
writer.key(MAX); writer.value(_max);
|
||||||
|
writer.key("step"); writer.value(_step);
|
||||||
|
|
||||||
|
writer.key("bins"); writer.array();
|
||||||
|
for (int b : _bins) {
|
||||||
|
writer.value(b);
|
||||||
|
}
|
||||||
|
writer.endArray();
|
||||||
|
|
||||||
|
writer.key("baseBins"); writer.array();
|
||||||
|
for (int b : _baseBins) {
|
||||||
|
writer.value(b);
|
||||||
|
}
|
||||||
|
writer.endArray();
|
||||||
|
|
||||||
|
if (MIN.equals(_mode)) {
|
||||||
|
writer.key(FROM); writer.value(_from);
|
||||||
|
} else if (MAX.equals(_mode)) {
|
||||||
|
writer.key(TO); writer.value(_to);
|
||||||
|
} else {
|
||||||
|
writer.key(FROM); writer.value(_from);
|
||||||
|
writer.key(TO); writer.value(_to);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.key("numericCount"); writer.value(_numericCount);
|
||||||
|
writer.key("nonNumericCount"); writer.value(_nonNumericCount);
|
||||||
|
writer.key("blankCount"); writer.value(_blankCount);
|
||||||
|
writer.key("errorCount"); writer.value(_errorCount);
|
||||||
|
}
|
||||||
|
writer.endObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void initializeFromJSON(Project project, JSONObject o) throws Exception {
|
||||||
|
_name = o.getString("name");
|
||||||
|
_expression = o.getString("expression");
|
||||||
|
_columnName = o.getString("columnName");
|
||||||
|
|
||||||
|
if (_columnName.length() > 0) {
|
||||||
|
Column column = project.columnModel.getColumnByName(_columnName);
|
||||||
|
if (column != null) {
|
||||||
|
_cellIndex = column.getCellIndex();
|
||||||
|
} else {
|
||||||
|
_errorMessage = "No column named " + _columnName;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_cellIndex = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
_eval = MetaParser.parse(_expression);
|
||||||
|
} catch (ParsingException e) {
|
||||||
|
_errorMessage = e.getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
_mode = o.getString("mode");
|
||||||
|
if (MIN.equals(_mode)) {
|
||||||
|
if (o.has(FROM)) {
|
||||||
|
_from = o.getDouble(FROM);
|
||||||
|
_selected = true;
|
||||||
|
}
|
||||||
|
} else if (MAX.equals(_mode)) {
|
||||||
|
if (o.has(TO)) {
|
||||||
|
_to = o.getDouble(TO);
|
||||||
|
_selected = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (o.has(FROM) && o.has(TO)) {
|
||||||
|
_from = o.getDouble(FROM);
|
||||||
|
_to = o.getDouble(TO);
|
||||||
|
_selected = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_selectNumeric = JSONUtilities.getBoolean(o, "selectNumeric", true);
|
||||||
|
_selectNonNumeric = JSONUtilities.getBoolean(o, "selectNonNumeric", true);
|
||||||
|
_selectBlank = JSONUtilities.getBoolean(o, "selectBlank", true);
|
||||||
|
_selectError = JSONUtilities.getBoolean(o, "selectError", true);
|
||||||
|
|
||||||
|
if (!_selectNumeric || !_selectNonNumeric || !_selectBlank || !_selectError) {
|
||||||
|
_selected = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public RowFilter getRowFilter() {
|
||||||
|
if (_eval != null && _errorMessage == null && _selected) {
|
||||||
|
if (MIN.equals(_mode)) {
|
||||||
|
return new ExpressionNumberComparisonRowFilter(
|
||||||
|
_eval, _columnName, _cellIndex, _selectNumeric, _selectNonNumeric, _selectBlank, _selectError) {
|
||||||
|
|
||||||
|
protected boolean checkValue(double d) {
|
||||||
|
return d >= _from;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
} else if (MAX.equals(_mode)) {
|
||||||
|
return new ExpressionNumberComparisonRowFilter(
|
||||||
|
_eval, _columnName, _cellIndex, _selectNumeric, _selectNonNumeric, _selectBlank, _selectError) {
|
||||||
|
|
||||||
|
protected boolean checkValue(double d) {
|
||||||
|
return d < _to;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return new ExpressionNumberComparisonRowFilter(
|
||||||
|
_eval, _columnName, _cellIndex, _selectNumeric, _selectNonNumeric, _selectBlank, _selectError) {
|
||||||
|
|
||||||
|
protected boolean checkValue(double d) {
|
||||||
|
return d >= _from && d < _to;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void computeChoices(Project project, FilteredRows filteredRows) {
|
||||||
|
if (_eval != null && _errorMessage == null) {
|
||||||
|
Column column = project.columnModel.getColumnByCellIndex(_cellIndex);
|
||||||
|
|
||||||
|
String key = "numeric-bin:" + _expression;
|
||||||
|
NumericBinIndex index = (NumericBinIndex) column.getPrecompute(key);
|
||||||
|
if (index == null) {
|
||||||
|
index = new NumericBinIndex(project, _columnName, _cellIndex, _eval);
|
||||||
|
column.setPrecompute(key, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
_min = index.getMin();
|
||||||
|
_max = index.getMax();
|
||||||
|
_step = index.getStep();
|
||||||
|
_baseBins = index.getBins();
|
||||||
|
|
||||||
|
if (_selected) {
|
||||||
|
_from = Math.max(_from, _min);
|
||||||
|
_to = Math.min(_to, _max);
|
||||||
|
} else {
|
||||||
|
_from = _min;
|
||||||
|
_to = _max;
|
||||||
|
}
|
||||||
|
|
||||||
|
ExpressionNumericRowBinner binner =
|
||||||
|
new ExpressionNumericRowBinner(_eval, _columnName, _cellIndex, index);
|
||||||
|
|
||||||
|
filteredRows.accept(project, binner);
|
||||||
|
|
||||||
|
_bins = binner.bins;
|
||||||
|
_numericCount = binner.numericCount;
|
||||||
|
_nonNumericCount = binner.nonNumericCount;
|
||||||
|
_blankCount = binner.blankCount;
|
||||||
|
_errorCount = binner.errorCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -40,6 +40,7 @@ public class GetScatterplotCommand extends Command {
|
|||||||
|
|
||||||
//Gridworks.log("drawn scatterplot in " + (System.currentTimeMillis() - start) + "ms");
|
//Gridworks.log("drawn scatterplot in " + (System.currentTimeMillis() - start) + "ms");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
respondException(response, e);
|
respondException(response, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
File diff suppressed because one or more lines are too long
@ -46,17 +46,37 @@ ScatterplotDialog.prototype._renderMatrix = function(columns) {
|
|||||||
for (var i = 0; i < columns.length; i++) {
|
for (var i = 0; i < columns.length; i++) {
|
||||||
var tr = table.insertRow(table.rows.length);
|
var tr = table.insertRow(table.rows.length);
|
||||||
for (var j = 0; j < i; j++) {
|
for (var j = 0; j < i; j++) {
|
||||||
var url = "/command/get-scatterplot?" + $.param({
|
var cx = columns[i];
|
||||||
|
var cy = columns[j];
|
||||||
|
var plotter_params = {
|
||||||
|
'cx' : cx.name,
|
||||||
|
'cy' : cy.name,
|
||||||
|
'w' : 20,
|
||||||
|
'h' : 20
|
||||||
|
};
|
||||||
|
var params = {
|
||||||
project: theProject.id,
|
project: theProject.id,
|
||||||
engine: JSON.stringify(ui.browsingEngine.getJSON()),
|
engine: JSON.stringify(ui.browsingEngine.getJSON()),
|
||||||
plotter: JSON.stringify({
|
plotter: JSON.stringify(plotter_params)
|
||||||
'cx' : columns[i].name,
|
}
|
||||||
'cy' : columns[j].name,
|
var url = "/command/get-scatterplot?" + $.param(params);
|
||||||
'w' : 20,
|
var name = cx.name + '(x) vs. ' + cy.name + '(y)';
|
||||||
'h' : 20
|
var cell = $(tr.insertCell(j));
|
||||||
})
|
var link = $('<a href="javascript:{}"></a>').attr("title",name).click(function() {
|
||||||
|
ui.browsingEngine.addFacet(
|
||||||
|
"scatterplot",
|
||||||
|
{
|
||||||
|
"name" : name,
|
||||||
|
"x_column" : cx.name,
|
||||||
|
"y_column" : cy.name,
|
||||||
|
"expression" : "value",
|
||||||
|
"mode" : "scatterplot"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
//self._dismiss();
|
||||||
});
|
});
|
||||||
$(tr.insertCell(j)).html('<img class="scatterplot" title="' + columns[i].name + ' vs. ' + columns[j].name + '" src="' + url + '" />');
|
var plot = $('<img src="' + url + '" />').addClass("scatterplot").appendTo(link);
|
||||||
|
link.appendTo(cell);
|
||||||
}
|
}
|
||||||
$(tr.insertCell(i)).text(columns[i]);
|
$(tr.insertCell(i)).text(columns[i]);
|
||||||
for (var j = i + 1; j < columns.length; j++) {
|
for (var j = i + 1; j < columns.length; j++) {
|
||||||
|
156
src/main/webapp/scripts/facets/scatterplot-facet.js
Normal file
156
src/main/webapp/scripts/facets/scatterplot-facet.js
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
function ScatterplotFacet(div, config, options) {
|
||||||
|
this._div = div;
|
||||||
|
this._config = config;
|
||||||
|
this._options = options;
|
||||||
|
|
||||||
|
this._from_x = ("from_x" in this._config) ? this._config.from_x : null;
|
||||||
|
this._to_x = ("to_x" in this._config) ? this._config.to_x : null;
|
||||||
|
this._from_y = ("from_y" in this._config) ? this._config.from_y : null;
|
||||||
|
this._to_y = ("to_y" in this._config) ? this._config.to_y : null;
|
||||||
|
|
||||||
|
this._error = false;
|
||||||
|
this._initializedUI = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ScatterplotFacet.prototype.reset = function() {
|
||||||
|
// TODO
|
||||||
|
};
|
||||||
|
|
||||||
|
ScatterplotFacet.reconstruct = function(div, uiState) {
|
||||||
|
return new ScatterplotFacet(div, uiState.c, uiState.o);
|
||||||
|
};
|
||||||
|
|
||||||
|
ScatterplotFacet.prototype.getUIState = function() {
|
||||||
|
var json = {
|
||||||
|
c: this.getJSON(),
|
||||||
|
o: this._options
|
||||||
|
};
|
||||||
|
|
||||||
|
return json;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
ScatterplotFacet.prototype.getJSON = function() {
|
||||||
|
var o = {
|
||||||
|
type: "scatterplot",
|
||||||
|
name: this._config.name,
|
||||||
|
mode: this._config.mode,
|
||||||
|
expression: this._config.expression,
|
||||||
|
x_column : this._config.x_column,
|
||||||
|
y_column : this._config.y_column,
|
||||||
|
};
|
||||||
|
|
||||||
|
return o;
|
||||||
|
};
|
||||||
|
|
||||||
|
ScatterplotFacet.prototype.hasSelection = function() {
|
||||||
|
// TODO
|
||||||
|
};
|
||||||
|
|
||||||
|
ScatterplotFacet.prototype._initializeUI = function() {
|
||||||
|
var self = this;
|
||||||
|
var container = this._div.empty();
|
||||||
|
|
||||||
|
var headerDiv = $('<div></div>').addClass("facet-title").appendTo(container);
|
||||||
|
$('<span></span>').text(this._config.name).appendTo(headerDiv);
|
||||||
|
|
||||||
|
var resetButton = $('<a href="javascript:{}"></a>').addClass("facet-choice-link").text("reset").click(function() {
|
||||||
|
self.reset();
|
||||||
|
self._updateRest();
|
||||||
|
}).prependTo(headerDiv);
|
||||||
|
|
||||||
|
var removeButton = $('<img>')
|
||||||
|
.attr("src", "images/close.png")
|
||||||
|
.attr("title", "Remove this facet")
|
||||||
|
.addClass("facet-choice-link")
|
||||||
|
.click(function() {
|
||||||
|
self._remove();
|
||||||
|
}).prependTo(headerDiv);
|
||||||
|
|
||||||
|
var bodyDiv = $('<div></div>').addClass("facet-scatterplot-body").appendTo(container);
|
||||||
|
|
||||||
|
this._messageDiv = $('<div>').text("Loading...").addClass("facet-scatterplot-message").appendTo(bodyDiv);
|
||||||
|
this._plotDiv = $('<div>').addClass("facet-scatterplot-plot").appendTo(bodyDiv);
|
||||||
|
this._statusDiv = $('<div>').addClass("facet-scatterplot-status").appendTo(bodyDiv);
|
||||||
|
|
||||||
|
this._plot = new ScatterplotWidget(this._plotDiv, { binColors: [ "#ccccff", "#6666ff" ] });
|
||||||
|
};
|
||||||
|
|
||||||
|
ScatterplotFacet.prototype.updateState = function(data) {
|
||||||
|
if ("min" in data && "max" in data) {
|
||||||
|
this._error = false;
|
||||||
|
|
||||||
|
this._config.min = data.min;
|
||||||
|
this._config.max = data.max;
|
||||||
|
this._config.step = data.step;
|
||||||
|
this._baseBins = data.baseBins;
|
||||||
|
this._bins = data.bins;
|
||||||
|
|
||||||
|
switch (this._config.mode) {
|
||||||
|
case "min":
|
||||||
|
this._from = Math.max(data.from, this._config.min);
|
||||||
|
break;
|
||||||
|
case "max":
|
||||||
|
this._to = Math.min(data.to, this._config.max);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this._from = Math.max(data.from, this._config.min);
|
||||||
|
if ("to" in data) {
|
||||||
|
this._to = Math.min(data.to, this._config.max);
|
||||||
|
} else {
|
||||||
|
this._to = data.max;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this._numericCount = data.numericCount;
|
||||||
|
this._nonNumericCount = data.nonNumericCount;
|
||||||
|
this._blankCount = data.blankCount;
|
||||||
|
this._errorCount = data.errorCount;
|
||||||
|
} else {
|
||||||
|
this._error = true;
|
||||||
|
this._errorMessage = "error" in data ? data.error : "Unknown error.";
|
||||||
|
}
|
||||||
|
|
||||||
|
this.render();
|
||||||
|
};
|
||||||
|
|
||||||
|
ScatterplotFacet.prototype.render = function() {
|
||||||
|
if (!this._initializedUI) {
|
||||||
|
this._initializeUI();
|
||||||
|
this._initializedUI = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._error) {
|
||||||
|
this._messageDiv.text(this._errorMessage).show();
|
||||||
|
this._sliderDiv.hide();
|
||||||
|
this._histogramDiv.hide();
|
||||||
|
this._statusDiv.hide();
|
||||||
|
this._otherChoicesDiv.hide();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._messageDiv.hide();
|
||||||
|
this._plotDiv.show();
|
||||||
|
this._statusDiv.show();
|
||||||
|
|
||||||
|
this._plot.update(
|
||||||
|
this._config.min,
|
||||||
|
this._config.max,
|
||||||
|
this._config.step,
|
||||||
|
[ this._baseBins, this._bins ],
|
||||||
|
this._from,
|
||||||
|
this._to
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
ScatterplotFacet.prototype._remove = function() {
|
||||||
|
ui.browsingEngine.removeFacet(this);
|
||||||
|
|
||||||
|
this._div = null;
|
||||||
|
this._config = null;
|
||||||
|
this._data = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
ScatterplotFacet.prototype._updateRest = function() {
|
||||||
|
Gridworks.update({ engineChanged: true });
|
||||||
|
};
|
@ -15,6 +15,9 @@ function BrowsingEngine(div, facetConfigs) {
|
|||||||
case "range":
|
case "range":
|
||||||
facet = RangeFacet.reconstruct(elmt, facetConfig);
|
facet = RangeFacet.reconstruct(elmt, facetConfig);
|
||||||
break;
|
break;
|
||||||
|
case "scatterplot":
|
||||||
|
facet = ScatterplotFacet.reconstruct(elmt, facetConfig);
|
||||||
|
break;
|
||||||
case "text":
|
case "text":
|
||||||
facet = TextSearchFacet.reconstruct(elmt, facetConfig);
|
facet = TextSearchFacet.reconstruct(elmt, facetConfig);
|
||||||
break;
|
break;
|
||||||
@ -111,6 +114,9 @@ BrowsingEngine.prototype.addFacet = function(type, config, options) {
|
|||||||
case "range":
|
case "range":
|
||||||
facet = new RangeFacet(elmt, config, options);
|
facet = new RangeFacet(elmt, config, options);
|
||||||
break;
|
break;
|
||||||
|
case "scatterplot":
|
||||||
|
facet = new ScatterplotFacet(elmt, config, options);
|
||||||
|
break;
|
||||||
case "text":
|
case "text":
|
||||||
facet = new TextSearchFacet(elmt, config, options);
|
facet = new TextSearchFacet(elmt, config, options);
|
||||||
break;
|
break;
|
||||||
|
160
src/main/webapp/scripts/widgets/scatterplot-widget.js
Normal file
160
src/main/webapp/scripts/widgets/scatterplot-widget.js
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
function ScatterplotWidget(elmt, options) {
|
||||||
|
this._elmt = elmt;
|
||||||
|
this._options = options;
|
||||||
|
|
||||||
|
this._range = null;
|
||||||
|
this._binMatrix = null;
|
||||||
|
this._highlight = null;
|
||||||
|
|
||||||
|
this._initializeUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
ScatterplotWidget.prototype.highlight = function(from, to) {
|
||||||
|
this._highlight = { from: from, to: to };
|
||||||
|
this._update();
|
||||||
|
};
|
||||||
|
|
||||||
|
ScatterplotWidget.prototype.update = function(min, max, step, binMatrix, from, to) {
|
||||||
|
if (typeof min == "undefined" || typeof binMatrix == "undefined" || binMatrix.length === 0 || binMatrix[0].length === 0) {
|
||||||
|
this._range = null;
|
||||||
|
this._binMatrix = null;
|
||||||
|
this._highlight = null;
|
||||||
|
|
||||||
|
this._elmt.hide();
|
||||||
|
} else {
|
||||||
|
this._range = { min: min, max: max, step: step };
|
||||||
|
this._binMatrix = binMatrix;
|
||||||
|
|
||||||
|
this._peak = 0;
|
||||||
|
for (var r = 0; r < binMatrix.length; r++) {
|
||||||
|
var row = binMatrix[r];
|
||||||
|
for (var c = 0; c < row.length; c++) {
|
||||||
|
this._peak = Math.max(this._peak, row[c]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof from != "undefined" && typeof to != "undefined") {
|
||||||
|
this._highlight = { from: from, to: to };
|
||||||
|
}
|
||||||
|
|
||||||
|
this._update();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ScatterplotWidget.prototype._update = function() {
|
||||||
|
if (this._binMatrix !== null) {
|
||||||
|
if (this._highlight !== null) {
|
||||||
|
this._highlight.from = Math.max(this._highlight.from, this._range.min);
|
||||||
|
this._highlight.to = Math.min(this._highlight.to, this._range.max);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._elmt.show();
|
||||||
|
this._resize();
|
||||||
|
this._render();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ScatterplotWidget.prototype._initializeUI = function() {
|
||||||
|
this._elmt
|
||||||
|
.empty()
|
||||||
|
.hide()
|
||||||
|
.addClass("scatterplot-widget")
|
||||||
|
.html(
|
||||||
|
'<canvas bind="canvas"></canvas>'
|
||||||
|
);
|
||||||
|
|
||||||
|
this._elmts = DOM.bind(this._elmt);
|
||||||
|
};
|
||||||
|
|
||||||
|
ScatterplotWidget.prototype._resize = function() {
|
||||||
|
this._elmts.canvas.attr("height", "height" in this._options ? this._options.height : 50);
|
||||||
|
this._elmts.canvas.attr("width", this._elmts.canvas.width());
|
||||||
|
};
|
||||||
|
|
||||||
|
ScatterplotWidget.prototype._render = function() {
|
||||||
|
var self = this;
|
||||||
|
var options = this._options;
|
||||||
|
|
||||||
|
var canvas = this._elmts.canvas[0];
|
||||||
|
var ctx = canvas.getContext('2d');
|
||||||
|
ctx.fillStyle = "white";
|
||||||
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
|
ctx.save();
|
||||||
|
ctx.translate(0, canvas.height);
|
||||||
|
ctx.scale(1, -1);
|
||||||
|
|
||||||
|
var stepPixels = canvas.width / this._binMatrix[0].length;
|
||||||
|
var stepScale = stepPixels / this._range.step;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Draw axis
|
||||||
|
*/
|
||||||
|
ctx.save();
|
||||||
|
ctx.strokeStyle = "emptyBinColor" in options ? options.emptyBinColor : "#faa";
|
||||||
|
ctx.lineWidth = 1;
|
||||||
|
ctx.moveTo(0, 0);
|
||||||
|
ctx.lineTo(canvas.width, 0);
|
||||||
|
ctx.stroke();
|
||||||
|
ctx.restore();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Draw bins
|
||||||
|
*/
|
||||||
|
var makeColor = function(i) {
|
||||||
|
var n = Math.floor(15 * (self._binMatrix.length - i) / self._binMatrix.length);
|
||||||
|
var h = n.toString(16);
|
||||||
|
return "#" + h + h + h;
|
||||||
|
};
|
||||||
|
var renderRow = function(row, color) {
|
||||||
|
ctx.save();
|
||||||
|
ctx.lineWidth = 0;
|
||||||
|
ctx.fillStyle = color;
|
||||||
|
for (var c = 0; c < row.length; c++) {
|
||||||
|
var x = self._range.min + c * self._range.step;
|
||||||
|
var y = row[c];
|
||||||
|
if (y > 0) {
|
||||||
|
var left = c * stepPixels;
|
||||||
|
var width = Math.ceil(stepPixels);
|
||||||
|
var height = Math.ceil(y * canvas.height / self._peak);
|
||||||
|
|
||||||
|
ctx.fillRect(left, 0, width, height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.restore();
|
||||||
|
};
|
||||||
|
for (var r = 0; r < this._binMatrix.length; r++) {
|
||||||
|
renderRow(
|
||||||
|
this._binMatrix[r],
|
||||||
|
"binColors" in options && r < options.binColors.length ?
|
||||||
|
options.binColors[r] :
|
||||||
|
makeColor(r)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Draw highlight
|
||||||
|
*/
|
||||||
|
if (this._highlight !== null) {
|
||||||
|
ctx.fillStyle = "rgba(192,192,192, 0.5)";
|
||||||
|
ctx.globalCompositeOperation = "source-over";
|
||||||
|
if (this._highlight.from > this._range.min) {
|
||||||
|
ctx.fillRect(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
(this._highlight.from - this._range.min) * stepScale,
|
||||||
|
canvas.height
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (this._highlight.to < this._range.max) {
|
||||||
|
ctx.fillRect(
|
||||||
|
(this._highlight.to - this._range.min) * stepScale,
|
||||||
|
0,
|
||||||
|
canvas.width - (this._highlight.to - this._range.min) * stepScale,
|
||||||
|
canvas.height
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.restore();
|
||||||
|
};
|
9
src/main/webapp/styles/widgets/scatterplot-widget.css
Normal file
9
src/main/webapp/styles/widgets/scatterplot-widget.css
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
.scatterplot-widget {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scatterplot-widget canvas {
|
||||||
|
width: 100%;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user