Skip to content

Commit

Permalink
Adding canvas option for rendering scatterplots.
Browse files Browse the repository at this point in the history
Adding `renderType` option for the scatterplot, and a `render` option for components created by the factory.  The canvas rendering uses `requesetAnimationFrame` to do gradual rendering, inspired by <http://bl.ocks.org/syntagmatic/2420080>.
  • Loading branch information
yelper committed May 27, 2016
1 parent 6f27251 commit 591747a
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 14 deletions.
119 changes: 105 additions & 14 deletions src/scatterplot.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export default function(dispatch) {
// 'global' declarations go here
var rendering = 'svg';
var scatterData = [];
var scatterDataKey = undefined;
var localDispatch = d3.dispatch('mouseover', 'mouseout');
Expand All @@ -25,12 +26,9 @@ export default function(dispatch) {

var duration = 500;

function redraw(selection) {
console.log("called scatterplot.redraw()");
selection.each(function(data, i) {
var g = d3.select(this);

// set the discovered groups
// the shared scales/groups needed by all rendering mechanisms
function setGlobals(data) {
// set the discovered groups
foundGroups = grpValue == null ? ["undefined"] : d3.set(data.map(function(e) { return grpValue(e); })).values();
colorScale = colorScale || d3.scale.category10();
colorScale.domain(foundGroups);
Expand All @@ -44,6 +42,15 @@ export default function(dispatch) {
.domain(xd).range([0, width]);
scale.y = d3.scale.linear()
.domain(yd).range([height, 0]);
};

function redrawSVG(selection) {
console.log("called scatterplot.redrawSVG()");
selection.each(function(data, i) {
var g = d3.select(this);

// set the scales and determine the groups and their colors
setGlobals(data);

// construct a brush object for this selection
// (TODO / BUG: one brush for multiple graphs?)
Expand All @@ -55,7 +62,7 @@ export default function(dispatch) {

// draw axes first so points can go over the axes
var xaxis = g.selectAll('g.xaxis')
.data([xd]);
.data([0]);

// add axis if it doesn't exist
xaxis.enter()
Expand All @@ -82,7 +89,7 @@ export default function(dispatch) {
xLabel.exit().remove();

var yaxis = g.selectAll('g.yaxis')
.data([yd]);
.data([0]);

// add axis if it doesn't exist
yaxis.enter()
Expand Down Expand Up @@ -237,13 +244,91 @@ export default function(dispatch) {
});
};

function redrawCanvas(selection) {
console.log("called scatterplot.redrawCanvas()");
selection.each(function(data, i) {
// only support points so far
var canvas = d3.select(this);
setGlobals(data);

if (!canvas.node().getContext){
console.error("Your browser does not support the 2D canvas element; reverting to SVG");
rendering = 'svg';
redrawSVG();
}

data = data.concat(data).concat(data).concat(data).concat(data);

var ctx = canvas.node().getContext('2d');
ctx.clearRect(0, 0, width, height);

// /* draw the points sequentially */
// for (var i = 0; i < data.length * 100; i++) {
// var d = data[i % data.length];
// var x = scale.x(xValue(d)), y = scale.y(yValue(d));

// ctx.fillStyle = colorScale(grpValue(d));
// ctx.beginPath();
// ctx.moveTo(x, y);
// ctx.arc(x, y, ptSize, 0, 2 * Math.PI);
// ctx.fill();
// };

// draw the points
renderPoints(data, ctx);

});

// inspired by <http://bl.ocks.org/syntagmatic/2420080>
function renderPoints(points, ctx, rate) {
var n = points.length;
var i = 0;
rate = rate || 250;
ctx.clearRect(0, 0, width, height);
function render() {
var max = Math.min(i + rate, n);
points.slice(i, max).forEach(function(d) {
renderPoint(
ctx, scale.x(xValue(d)),
scale.y(yValue(d)), colorScale(grpValue(d)));
});
i = max;
};

(function animloop() {
if (i >= n) return;
requestAnimationFrame(animloop);
render();
})();
}

function renderPoint(ctx, x, y, color) {
ctx.beginPath();
ctx.fillStyle = color;
ctx.moveTo(x, y);
ctx.arc(x, y, ptSize, 0, 2 * Math.PI);
ctx.fill();
}
};

function scatterplot(selection, name) {
selection.each(function(d, i) {
var g = d3.select(this);
g.data([scatterData], scatterDataKey);
});

redraw(selection);
switch (rendering) {
case 'svg':
redrawSVG(selection);
break;
case 'canvas':
redrawCanvas(selection);
break;
case 'webgl':
throw "webgl scatterplot not implemented";
redrawWebGL(selection);
break;
}

dispatch.on('highlight.' + name, function(selector) {
// console.log("scatterplot dispatch called for " + name + "!");
Expand Down Expand Up @@ -316,11 +401,6 @@ export default function(dispatch) {
selection.selectAll('g.voronoi').selectAll('path').remove();
}
}

// this lags the webpage too much... why?
// ^^ because it contains transitions that don't actually transition!
// d3.timer goes nuts trying to schedule nothing
// redraw(selection);
});
}

Expand All @@ -345,6 +425,17 @@ export default function(dispatch) {
return scatterplot;
};

/**
* Gets or sets the type of rendering mechanism. One of "svg", "canvas", or "webgl". Subsequent calls of `scatterplot` on a selection will populate the selections with the given rendering type
*/
scatterplot.renderType = function(renderType) {
if (!arguments.length) return rendering;
if (['svg', 'canvas', 'webgl'].indexOf(renderType) == -1)
throw "Expected value of 'svg', 'canvas', or 'webgl' to scatterplot.renderType";
rendering = renderType;
return scatterplot;
}

/**
* The width of the constructed scatterplot. The caller is responsible for maintaining sensible margins.
* @default 1 (pixel)
Expand Down
6 changes: 6 additions & 0 deletions src/twoDimFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ var twoDimFactory = function() {
* Creates a d3-twodim component with the given name
* @param {Object} options - The anonymous object with the required properties
* @param {string} options.type - The name of the component to instantiate (currently, one of the set of {dropdown, legend, objectlist, scatterplot})
* @param {string} options.renderType - The type of rendering for the requested component, usually one of 'webgl', 'canvas', or 'svg' (default).
* @returns {Object} The requested object, instantiated
*/
twoDimFactory.prototype.createComponent = function createTwoDimComponent(options) {
Expand All @@ -36,6 +37,11 @@ twoDimFactory.prototype.createComponent = function createTwoDimComponent(options
}

var newObject = new parentClass(this.dispatch);

if (options.hasOwnProperty('render')) {
newObject.renderType(options.render);
}

this.createdComponents.push(newObject);
return newObject;
};
Expand Down
1 change: 1 addition & 0 deletions test/scatterplot-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ tape("Scatterplot has the expected defaults", function(test) {
test.equal(s.doBrush(), false);
test.equal(s.changeDuration(), 500);
test.deepEqual(s.labels(), ["", ""]);
test.equal(s.renderType(), 'svg');
test.end();
});

Expand Down

0 comments on commit 591747a

Please sign in to comment.