Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@larsvers
Last active April 3, 2024 20:18
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 larsvers/d187337850d58a444082841c739985ca to your computer and use it in GitHub Desktop.
Save larsvers/d187337850d58a444082841c739985ca to your computer and use it in GitHub Desktop.
d3 canvas tutorial - bind and draw
license: mit

Part 1 of a little d3 canvas tutorial - how to bind data and draw it to canvas

Built with blockbuilder.org

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>d3 and canvas</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="http://d3js.org/d3.v4.js"></script>
<script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script>
<style type="text/css">
body {
font-family: 'Open Sans', sans-serif;
}
canvas {
border: 1px dotted #ccc;
}
#text-explain {
display: inline-block;
font-size: 0.75em;
margin-bottom: 1em;
}
.alert {
color: tomato;
}
</style>
</head>
<body>
<h3>Coloured grids</h3>
<input type="text" id="text-input" value="5000">
<div id="text-explain">...takes numbers between 1 and 10k</div>
<div id="container"></div>
<script>
// === Set up canvas === //
var width = 750,
height = 400;
var data = [];
var value = 5000;
var colourScale;
var canvas = d3.select('#container')
.append('canvas')
.attr('width', width)
.attr('height', height);
var context = canvas.node().getContext('2d');
// === Load and prepare the data === //
d3.range(value).forEach(function(el) {
data.push({ value: el });
});
console.log(data);
// === Bind data to custom elements === //
var customBase = document.createElement('custom');
var custom = d3.select(customBase); // this is our svg replacement
// settings for a grid with 40 cells in a row and 2x5 cells in a group
var groupSpacing = 4;
var cellSpacing = 2;
var cellSize = Math.floor((width - 11 * groupSpacing) / 100) - cellSpacing;
// === First call === //
databind(data); // ...then update the databind function
var t = d3.timer(function(elapsed) {
draw();
if (elapsed > 300) t.stop();
}); // start a timer that runs the draw function for 500 ms (this needs to be higher than the transition in the databind function)
// === Bind and draw functions === //
function databind(data) {
colourScale = d3.scaleSequential(d3.interpolateSpectral).domain(d3.extent(data, function(d) { return d.value; }));
var join = custom.selectAll('custom.rect')
.data(data);
var enterSel = join.enter()
.append('custom')
.attr('class', 'rect')
.attr('x', function(d, i) {
var x0 = Math.floor(i / 100) % 10, x1 = Math.floor(i % 10);
return groupSpacing * x0 + (cellSpacing + cellSize) * (x1 + x0 * 10);
})
.attr('y', function(d, i) {
var y0 = Math.floor(i / 1000), y1 = Math.floor(i % 100 / 10);
return groupSpacing * y0 + (cellSpacing + cellSize) * (y1 + y0 * 10);
})
.attr('width', 0)
.attr('height', 0);
join
.merge(enterSel)
.transition()
.attr('width', cellSize)
.attr('height', cellSize)
.attr('fillStyle', function(d) { return colourScale(d.value); });
var exitSel = join.exit()
.transition()
.attr('width', 0)
.attr('height', 0)
.remove();
} // databind()
// === Draw canvas === //
function draw() {
// clear canvas
context.fillStyle = '#fff';
context.fillRect(0, 0, width, height);
// draw each individual custom element with their properties
var elements = custom.selectAll('custom.rect') // this is the same as the join variable, but used here to draw
elements.each(function(d,i) {
// for each virtual/custom element...
var node = d3.select(this);
context.fillStyle = node.attr('fillStyle');
context.fillRect(node.attr('x'), node.attr('y'), node.attr('width'), node.attr('height'))
});
} // draw()
// === Listeners/handlers === //
d3.select('#text-input').on('keydown', function() {
if (d3.event.keyCode === 13) {
d3.select('#alert').html('');
if (+this.value < 1 || +this.value > 10000) {
d3.select('#text-explain').classed('alert', true);
return;
} else {
d3.select('#text-explain').classed('alert', false);
data = [];
d3.range(+this.value).forEach(function(el) {
data.push({ value: el });
});
databind(data);
var t = d3.timer(function(elapsed) {
draw();
if (elapsed > 500) t.stop();
}); // start a timer that runs the draw function for 500 ms (this needs to be higher than the transition in the databind function)
} // value test
} // keyCode 13 === return
}); // text input listener/handler
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment