Switched to a canvas-based implementation of histograms.

git-svn-id: http://google-refine.googlecode.com/svn/trunk@319 7d457c2a-affb-35e4-300a-418c747d4874
This commit is contained in:
David Huynh 2010-03-18 20:45:52 +00:00
parent b9b4bb0ab4
commit 91241539cf
6 changed files with 154 additions and 59 deletions

View File

@ -1 +1 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>Gridworks</title> <link type="text/css" rel="stylesheet" href="externals/suggest/css/suggest-1.0.3.min.css" /> <link type="text/css" rel="stylesheet" href="externals/jquery-ui/css/ui-lightness/jquery-ui-1.7.2.custom.css" /> <link rel="stylesheet" href="/styles/common.css" /> <link rel="stylesheet" href="/styles/util/menu.css" /> <link rel="stylesheet" href="/styles/util/dialog.css" /> <link rel="stylesheet" href="/styles/util/custom-suggest.css" /> <link rel="stylesheet" href="/styles/project.css" /> <link rel="stylesheet" href="/styles/project/history.css" /> <link rel="stylesheet" href="/styles/project/browsing.css" /> <link rel="stylesheet" href="/styles/project/process.css" /> <link rel="stylesheet" href="/styles/project/menu-bar.css" /> <link rel="stylesheet" href="/styles/views/data-table-view.css" /> <link rel="stylesheet" href="/styles/dialogs/expression-preview-dialog.css" /> <link rel="stylesheet" href="/styles/dialogs/recon-dialog.css" /> <link rel="stylesheet" href="/styles/dialogs/clustering-dialog.css" /> <link rel="stylesheet" href="/styles/dialogs/extend-data-preview-dialog.css" /> <link rel="stylesheet" href="/styles/protograph/schema-alignment-dialog.css" /> <script type="text/javascript" src="externals/jquery-1.4.1.min.js"></script> <script type="text/javascript" src="externals/suggest/suggest-1.0.3.min.js"></script> <script type="text/javascript" src="externals/jquery-ui/jquery-ui-1.7.2.custom.min.js"></script> <script type="text/javascript" src="externals/date.js"></script> <script type="text/javascript" src="scripts/util/misc.js"></script> <script type="text/javascript" src="scripts/util/url.js"></script> <script type="text/javascript" src="scripts/util/string.js"></script> <script type="text/javascript" src="scripts/util/ajax.js"></script> <script type="text/javascript" src="scripts/util/menu.js"></script> <script type="text/javascript" src="scripts/util/dialog.js"></script> <script type="text/javascript" src="scripts/util/dom.js"></script> <script type="text/javascript" src="scripts/util/custom-suggest.js"></script> <script type="text/javascript" src="scripts/project.js"></script> <script type="text/javascript" src="scripts/project/history-widget.js"></script> <script type="text/javascript" src="scripts/project/process-widget.js"></script> <script type="text/javascript" src="scripts/project/menu-bar.js"></script> <script type="text/javascript" src="scripts/project/browsing-engine.js"></script> <script type="text/javascript" src="scripts/project/scripting.js"></script> <script type="text/javascript" src="scripts/facets/list-facet.js"></script> <script type="text/javascript" src="scripts/facets/range-facet.js"></script> <script type="text/javascript" src="scripts/facets/text-search-facet.js"></script> <script type="text/javascript" src="scripts/views/data-table-view.js"></script> <script type="text/javascript" src="scripts/views/data-table-cell-ui.js"></script> <script type="text/javascript" src="scripts/views/data-table-column-header-ui.js"></script> <script type="text/javascript" src="scripts/dialogs/recon-dialog.js"></script> <script type="text/javascript" src="scripts/dialogs/expression-preview-dialog.js"></script> <script type="text/javascript" src="scripts/dialogs/clustering-dialog.js"></script> <script type="text/javascript" src="scripts/dialogs/extend-data-preview-dialog.js"></script> <script type="text/javascript" src="scripts/protograph/schema-alignment.js"></script> <script type="text/javascript" src="scripts/protograph/schema-alignment-ui-node.js"></script> <script type="text/javascript" src="scripts/protograph/schema-alignment-ui-link.js"></script> </head> <body> <div id="header"> <a id="logo" href="http://www.freebase.com/" title="Freebase"><img alt="Freebase" src="images/freebase-headerlogo.png" /></a> <div id="path"><a class="app-path-section" href="./index.html">Gridworks</a> &raquo; </div> </div> <div id="body"> <div id="loading-message"><img src="images/large-spinner.gif" /> starting up ...</div> </div> <div id="footer"> <a href="about.html">About Freebase Gridworks</a> &bull; &copy; 2010 <a href="http://www.metaweb.com/">Metaweb Technologies, Inc.</a> </div> </body> </html> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>Gridworks</title> <link type="text/css" rel="stylesheet" href="externals/suggest/css/suggest-1.0.3.min.css" /> <link type="text/css" rel="stylesheet" href="externals/jquery-ui/css/ui-lightness/jquery-ui-1.7.2.custom.css" /> <link rel="stylesheet" href="/styles/common.css" /> <link rel="stylesheet" href="/styles/util/menu.css" /> <link rel="stylesheet" href="/styles/util/dialog.css" /> <link rel="stylesheet" href="/styles/util/custom-suggest.css" /> <link rel="stylesheet" href="/styles/util/histogram-widget.css" /> <link rel="stylesheet" href="/styles/project.css" /> <link rel="stylesheet" href="/styles/project/history.css" /> <link rel="stylesheet" href="/styles/project/browsing.css" /> <link rel="stylesheet" href="/styles/project/process.css" /> <link rel="stylesheet" href="/styles/project/menu-bar.css" /> <link rel="stylesheet" href="/styles/views/data-table-view.css" /> <link rel="stylesheet" href="/styles/dialogs/expression-preview-dialog.css" /> <link rel="stylesheet" href="/styles/dialogs/recon-dialog.css" /> <link rel="stylesheet" href="/styles/dialogs/clustering-dialog.css" /> <link rel="stylesheet" href="/styles/dialogs/extend-data-preview-dialog.css" /> <link rel="stylesheet" href="/styles/protograph/schema-alignment-dialog.css" /> <script type="text/javascript" src="externals/jquery-1.4.1.min.js"></script> <script type="text/javascript" src="externals/suggest/suggest-1.0.3.min.js"></script> <script type="text/javascript" src="externals/jquery-ui/jquery-ui-1.7.2.custom.min.js"></script> <script type="text/javascript" src="externals/date.js"></script> <script type="text/javascript" src="scripts/util/misc.js"></script> <script type="text/javascript" src="scripts/util/url.js"></script> <script type="text/javascript" src="scripts/util/string.js"></script> <script type="text/javascript" src="scripts/util/ajax.js"></script> <script type="text/javascript" src="scripts/util/menu.js"></script> <script type="text/javascript" src="scripts/util/dialog.js"></script> <script type="text/javascript" src="scripts/util/dom.js"></script> <script type="text/javascript" src="scripts/util/custom-suggest.js"></script> <script type="text/javascript" src="scripts/util/histogram-widget.js"></script> <script type="text/javascript" src="scripts/project.js"></script> <script type="text/javascript" src="scripts/project/history-widget.js"></script> <script type="text/javascript" src="scripts/project/process-widget.js"></script> <script type="text/javascript" src="scripts/project/menu-bar.js"></script> <script type="text/javascript" src="scripts/project/browsing-engine.js"></script> <script type="text/javascript" src="scripts/project/scripting.js"></script> <script type="text/javascript" src="scripts/facets/list-facet.js"></script> <script type="text/javascript" src="scripts/facets/range-facet.js"></script> <script type="text/javascript" src="scripts/facets/text-search-facet.js"></script> <script type="text/javascript" src="scripts/views/data-table-view.js"></script> <script type="text/javascript" src="scripts/views/data-table-cell-ui.js"></script> <script type="text/javascript" src="scripts/views/data-table-column-header-ui.js"></script> <script type="text/javascript" src="scripts/dialogs/recon-dialog.js"></script> <script type="text/javascript" src="scripts/dialogs/expression-preview-dialog.js"></script> <script type="text/javascript" src="scripts/dialogs/clustering-dialog.js"></script> <script type="text/javascript" src="scripts/dialogs/extend-data-preview-dialog.js"></script> <script type="text/javascript" src="scripts/protograph/schema-alignment.js"></script> <script type="text/javascript" src="scripts/protograph/schema-alignment-ui-node.js"></script> <script type="text/javascript" src="scripts/protograph/schema-alignment-ui-link.js"></script> </head> <body> <div id="header"> <a id="logo" href="http://www.freebase.com/" title="Freebase"><img alt="Freebase" src="images/freebase-headerlogo.png" /></a> <div id="path"><a class="app-path-section" href="./index.html">Gridworks</a> &raquo; </div> </div> <div id="body"> <div id="loading-message"><img src="images/large-spinner.gif" /> starting up ...</div> </div> <div id="footer"> <a href="about.html">About Freebase Gridworks</a> &bull; &copy; 2010 <a href="http://www.metaweb.com/">Metaweb Technologies, Inc.</a> </div> </body> </html>

View File

@ -477,6 +477,9 @@ ClusteringDialog.Facet = function(dialog, title, property, elmt, clusters) {
).appendTo(elmt); ).appendTo(elmt);
this._elmts = DOM.bind(html); this._elmts = DOM.bind(html);
this._histogram = new HistogramWidget(this._elmts.histogramContainer, { binColors: [ "#aaaaff", "#000088" ] });
this._elmts.slider.slider({ this._elmts.slider.slider({
min: this._min, min: this._min,
max: this._max, max: this._max,
@ -518,29 +521,12 @@ ClusteringDialog.Facet.prototype.update = function(clusters) {
var bins = this._computeDistribution(clusters); var bins = this._computeDistribution(clusters);
var max = 0; this._histogram.update(
for (var i = 0; i < this._baseBins.length; i++) { this._min,
max = Math.max(max, this._baseBins[i]); this._max,
} this._step,
[ this._baseBins, bins ]
var values = []; );
var diffs = [];
for (var i = 0; i < this._baseBins.length; i++) {
var v = Math.ceil(100 * bins[i] / max);
var diff = Math.ceil(100 * this._baseBins[i] / max) - v;
values.push(v == 0 ? 0 : Math.max(2, v)); // use min 2 to make sure something shows up
diffs.push(diff == 0 ? 0 : Math.max(2, diff));
}
this._elmts.histogramContainer.empty();
$('<img />').attr("src",
"http://chart.apis.google.com/chart?" + [
"chs=" + this._elmts.histogramContainer[0].offsetWidth + "x50",
"cht=bvs&chbh=r,0&chco=000088,aaaaff",
"chd=t:" + values.join(",") + "|" + diffs.join(",")
].join("&")
).appendTo(this._elmts.histogramContainer);
}; };
ClusteringDialog.Facet.prototype._setRangeIndicators = function() { ClusteringDialog.Facet.prototype._setRangeIndicators = function() {

View File

@ -144,6 +144,8 @@ RangeFacet.prototype._initializeUI = function() {
this._statusDiv = $('<div>').addClass("facet-range-status").appendTo(bodyDiv); this._statusDiv = $('<div>').addClass("facet-range-status").appendTo(bodyDiv);
this._otherChoicesDiv = $('<div>').addClass("facet-range-other-choices").appendTo(bodyDiv); this._otherChoicesDiv = $('<div>').addClass("facet-range-other-choices").appendTo(bodyDiv);
this._histogram = new HistogramWidget(this._histogramDiv, { binColors: [ "#aaaaff", "#000088" ] });
var onSlide = function(event, ui) { var onSlide = function(event, ui) {
switch (self._config.mode) { switch (self._config.mode) {
case "min": case "min":
@ -327,35 +329,12 @@ RangeFacet.prototype.render = function() {
this._sliderDiv.slider("option", "max", this._config.max); this._sliderDiv.slider("option", "max", this._config.max);
this._sliderDiv.slider("option", "step", this._config.step); this._sliderDiv.slider("option", "step", this._config.step);
var max = 0; this._histogram.update(
for (var i = 0; i < this._baseBins.length; i++) { this._config.min,
max = Math.max(max, this._baseBins[i]); this._config.max,
} this._config.step,
[ this._baseBins, this._bins ]
if (max == 0) { );
this._histogramDiv.hide();
} else {
var values = [];
var diffs = [];
for (var i = 0; i < this._baseBins.length; i++) {
var v = Math.ceil(100 * this._bins[i] / max);
var diff = Math.ceil(100 * this._baseBins[i] / max) - v;
values.push(v == 0 ? 0 : Math.max(2, v)); // use min 2 to make sure something shows up
diffs.push(diff == 0 ? 0 : Math.max(2, diff));
}
this._histogramDiv.empty().show();
$('<img />').attr("src",
"http://chart.apis.google.com/chart?" + [
"chs=" + this._histogramDiv[0].offsetWidth + "x50",
//"cht=ls&chm=o,ff6600,0,-1,3&chls=0", // use for line plot
"cht=bvs&chbh=r,0&chco=000088,aaaaff", // use for histogram
"chd=t:" + values.join(",") + "|" + diffs.join(",")
].join("&")
).appendTo(this._histogramDiv);
}
this._setRangeIndicators(); this._setRangeIndicators();
this._renderOtherChoices(); this._renderOtherChoices();

View File

@ -0,0 +1,126 @@
function HistogramWidget(elmt, options) {
this._elmt = elmt;
this._options = options;
this._range = null;
this._binMatrix = null;
this._highlight = null;
this._initializeUI();
}
HistogramWidget.prototype.highlight = function(from, to) {
this._highlight = { from: from, to: to };
this._update();
};
HistogramWidget.prototype.update = function(min, max, step, binMatrix) {
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]);
}
}
this._update();
}
};
HistogramWidget.prototype._update = function() {
if (this._binMatrix != null) {
if (this._highlight != null) {
this._highlight.from = Math.max(this._highlight.from, min);
this._highlight.to = Math.min(this._highlight.to, max);
}
this._elmt.show();
this._resize();
this._render();
}
};
HistogramWidget.prototype._initializeUI = function() {
this._elmt
.empty()
.hide()
.addClass("histogram-widget")
.html(
'<canvas bind="canvas"></canvas>'
);
this._elmts = DOM.bind(this._elmt);
};
HistogramWidget.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());
};
HistogramWidget.prototype._render = function() {
var self = this;
var options = this._options;
var canvas = this._elmts.canvas[0];
var ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save();
ctx.translate(0, canvas.height);
ctx.scale(1, -1);
/*
* 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 stepPixels = canvas.width / this._binMatrix[0].length;
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 value = row[c];
if (value > 0) {
var left = c * stepPixels;
var height = Math.ceil(value * canvas.height / self._peak);
ctx.fillRect(left, 0, Math.ceil(stepPixels), 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)
);
}
ctx.restore();
};

View File

@ -128,11 +128,6 @@ img.facet-choice-link {
margin: 10px 4px; margin: 10px 4px;
overflow: hidden; overflow: hidden;
} }
.facet-range-histogram img {
height: 50px;
position: relative;
left: -2px;
}
.facet-range-slider { .facet-range-slider {
} }
.facet-range-status { .facet-range-status {

View File

@ -0,0 +1,9 @@
.histogram-widget {
margin: 0;
padding: 0;
position: relative;
}
.histogram-widget canvas {
width: 100%;
}