Skip to content

Instantly share code, notes, and snippets.

@JMStewart
Last active January 12, 2023 06:16
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JMStewart/e6efab7dc6ca09702adb to your computer and use it in GitHub Desktop.
Save JMStewart/e6efab7dc6ca09702adb to your computer and use it in GitHub Desktop.
D3/Canvas Transitions & Events

This is a test of how to use d3 and canvas with transitions and mouse events. Rather than using d3's data binding to DOM nodes, this uses simple javascript objects as a data model to back the visualization.

Mouse events are handled using a brute-force search through all nodes to find the closest node to the mouse. This is sufficiently fast for this example, but a more efficient method is possible using d3's quadtree.

<!DOCTYPE html>
<html>
<head>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
</head>
<body>
<script>
var size = 1000;
var height = 500;
var width = 960;
var i = 0;
var data = d3.range(size)
.map(function() {
return {
r: Math.floor(Math.random() * 8 + 2),
x: Math.floor(Math.random() * width),
y: Math.floor(Math.random() * height),
i: i++
};
});
var ease = d3.ease('cubic-in-out');
var duration = 1000;
var delay = function(d){
return d.i;
}
var maxDelay = 0;
var timeScale = d3.scale.linear()
.domain([0, duration])
.range([0,1]);
data.forEach(function(d){
d.trans = {
i: d3.interpolate(height, d.y),
delay: delay(d)
};
if (d.trans.delay > maxDelay) {
maxDelay = d.trans.delay;
}
});
var renderTime = 0;
d3.timer(moveCircles);
var canvas = d3.select('body')
.append('canvas')
.attr({
height: height,
width: width
});
canvas.on('mousemove', clicked);
canvas.on('mouseout', drawCircles);
var context = canvas.node()
.getContext("2d");
function moveCircles(t) {
data.forEach(function(d){
var time = ease(timeScale(t - d.trans.delay));
d.y = d.trans.i(time);
});
drawCircles();
if(t >= duration + maxDelay){
console.log('Render time:', renderTime);
return true;
}
}
function drawCircles(point) {
var start = new Date();
context.clearRect(0, 0, width, height);
fill = point ? "#e4e5e5" : "steelblue";
data.forEach(function(d) {
if (d === point) {
context.fillStyle = 'steelblue';
} else {
context.fillStyle = fill;
}
context.beginPath();
context.moveTo(d.x, d.y);
context.arc(d.x, d.y, d.r, 0, 2 * Math.PI);
context.fill();
});
var end = new Date();
renderTime += (end-start);
}
function clicked() {
var point = d3.mouse(this);
var node;
var minDistance = Infinity;
var start = new Date();
data.forEach(function(d) {
var dx = d.x - point[0];
var dy = d.y - point[1];
var distance = Math.sqrt((dx * dx) + (dy * dy));
// if (distance < d.r) {
// if (distance < minDistance) {
if (distance < minDistance && distance < d.r + 10) {
// drawCircles(d);
minDistance = distance;
node = d;
}
});
var end = new Date();
console.log('Calc Time:', end-start);
drawCircles(node);
}
</script>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment