Skip to content

Instantly share code, notes, and snippets.

@Niekes
Last active February 17, 2018 20:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Niekes/74a509b574e9e30b0e67d2c8cbd70699 to your computer and use it in GitHub Desktop.
Save Niekes/74a509b574e9e30b0e67d2c8cbd70699 to your computer and use it in GitHub Desktop.
data-binding and canvas with d3.js
license: gpl-3.0

In order to bind data to a lot of elements (62500) it is possible to first bind the data to virtual custom elements and draw them using d3-timer.

<!DOCTYPE html>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
<body>
<style type="text/css">
div {
align-items: center;
display: flex;
flex-direction: column;
position: absolute;
right: 10px;
top: 10px;
}
canvas {
margin: auto;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
</style>
<div id="hue">
<label>Hue <span></span></label>
<input type="range" min="0" max="360" step="0.01" value="0">
</div>
<div id="saturation" style="top: 60px">
<label>Saturation <span></span></label>
<input type="range" min="0" max="100" step="0.01" value="100">
</div>
<div id="lightness" style="top: 110px">
<label>Lightness <span></span></label>
<input type="range" min="0" max="100" step="0.01" value="100">
</div>
<div id="opacity" style="top: 160px">
<label>Opacity <span></span></label>
<input type="range" min="0" max="1" step="0.01" value="1">
</div>
<canvas id="canvas"></canvas>
<script>
var width = 150;
var height = 150;
var tt = 1500;
var hue = null;
var saturation = null;
var lightness = null;
var opacity = null;
var detachedContainer = document.createElement('custom');
var dataContainer = d3.select(detachedContainer);
var canvas = d3.select('#canvas').attr('width', width).attr('height', height);
var context = canvas.node().getContext('2d');
function databind(data) {
var dataBinding = dataContainer.selectAll('custom.pixel').data(data);
dataBinding
.enter()
.append('custom')
.attr('class', 'pixel')
.attr('size', 1)
.attr('fillStyle', setColorOfPixel)
.attr('x', setXPosOfPixel)
.attr('y', setYPosOfPixel)
.merge(dataBinding)
.transition().duration(tt)
.attr('size', 1)
.attr('x', setXPosOfPixel)
.attr('y', setYPosOfPixel)
.attr('fillStyle', setColorOfPixel);
}
function setColorOfPixel(d){
var hsl = d3.hsl(d.value);
hsl.h = hsl.h + hue;
hsl.s = hsl.s * (saturation/100);
hsl.l = hsl.l * (lightness/100);
hsl.opacity = opacity;
return hsl;
}
function setXPosOfPixel(d){
return d.x;
}
function setYPosOfPixel(d){
return d.y;
}
function draw() {
context.fillStyle = '#fff';
context.rect(0, 0, width, height);
context.fill();
var elements = dataContainer.selectAll('custom.pixel');
elements.each(function() {
var node = d3.select(this);
context.beginPath();
context.fillStyle = node.attr('fillStyle');
context.rect(node.attr('x'), node.attr('y'), node.attr('size'), node.attr('size'));
context.fill();
context.closePath();
});
}
function update(){
var h = document.querySelector('#hue');
var s = document.querySelector('#saturation');
var l = document.querySelector('#lightness');
var o = document.querySelector('#opacity');
var pixels = [];
hue = h.querySelector('input').value ? parseInt(h.querySelector('input').value) : 100;
saturation = s.querySelector('input').value ? parseInt(s.querySelector('input').value) : 100;
lightness = l.querySelector('input').value ? parseInt(l.querySelector('input').value) : 100;
opacity = o.querySelector('input').value ? o.querySelector('input').value : 100;
h.querySelector('span').innerHTML = hue + '°';
s.querySelector('span').innerHTML = saturation + '%';
l.querySelector('span').innerHTML = lightness + '%';
o.querySelector('span').innerHTML = opacity;
var img = new Image();
img.src = 'lena.png';
img.onload = function() {
var vis = document.createElement('canvas');
var ctx = vis.getContext('2d');
vis.width = width;
vis.height = height;
ctx.drawImage(img, 0, 0);
img.style.display = 'none';
var imageData = ctx.getImageData(0, 0, width, height);
var data = imageData.data;
for (var i = 0; i < data.length; i += 4) {
var index = i / 4;
var xCoord = index % width;
var yCoord = (index - xCoord) / width;
pixels.push({
x : xCoord,
y : yCoord,
value : d3.rgb(data[i], data[i + 1], data[i + 2], data[i + 3] / 255)
});
}
databind(pixels);
var t = d3.timer(function(elapsed) {
draw();
if (elapsed > tt) t.stop();
});
};
}
d3.selectAll('input').on('change', update);
update();
</script>
</body>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment