Skip to content

Instantly share code, notes, and snippets.

@maritrinez
Last active September 25, 2018 14:37
Show Gist options
  • Save maritrinez/90017147bdd5aecc9caa1567fba1f663 to your computer and use it in GitHub Desktop.
Save maritrinez/90017147bdd5aecc9caa1567fba1f663 to your computer and use it in GitHub Desktop.
canvas circles bounded
license: gpl-3.0

Canvas bounded circles

Circles bounded to different areas with canvas transition between them.

Based on Mike Bostock Circles. Canvas transition taken from this Bocoup's article

<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font-family: "Lucida Sans Unicode", "Lucida Grande", sans-serif;
}
</style>
<body>
<form>
<legend>Please select the particle limits</legend>
<div>
<input type="radio" id="large" name="bound-selector" data-w="940" data-h="480">
<label for="large">Large</label>
<input type="radio" id="medium" name="bound-selector" data-w="600" data-h="320" checked>
<label for="medium">Medium</label>
<input type="radio" id="small" name="bound-selector" data-w="220" data-h="120">
<label for="small">Small</label>
</div>
</form>
<script src="//d3js.org/d3.v5.min.js"></script>
<script>
// - - - - - constant values - - - - - //
const FILL = 'rgba(5, 5, 5, 0.5)',
STROKE = 'rgba(5, 5, 5, 0.2)',
SIZE = {w: 960, h: 500},
RADIUS = {min: 1, max: 3},
DENSITY = 0.00080, // particles per sq px;
DURATION = 750;
// - - - - - canvas - - - - - //
const canvas = d3.select('body').append('canvas')
.attr('width', SIZE.w + 'px')
.attr('height', SIZE.h + 'px');
const context = canvas.node().getContext('2d');
// - - - - - scales - - - - - //
const xScale = d3.scaleLinear()
.domain([0, SIZE.w])
.range(getBounds().x);
const yScale = d3.scaleLinear()
.domain([0, SIZE.h])
.range(getBounds().y);
// - - - - - random nodes - - - - - //
let N = Math.round(SIZE.w * SIZE.h * DENSITY);
const nodes = d3.range(N).map(function() {
return {
r: Math.round(Math.random() * (RADIUS.max - RADIUS.min) + RADIUS.min),
x: xScale(Math.round(Math.random() * SIZE.w)),
y: yScale(Math.round(Math.random() * SIZE.h)),
dx: (Math.random() - 0.5) * 0.5,
dy: (Math.random() - 0.5) * 0.5
};
});
// - - - - - interaction - - - - - //
d3.selectAll('input').on('click', function(d) {
// update scales
xScale
.domain(d3.extent(nodes, d => d.x))
.range(getBounds().x);
yScale
.domain(d3.extent(nodes, d => d.y))
.range(getBounds().y);
// set nodes source and target position
// for the canvas animation
// https://bocoup.com/blog/smoothly-animate-thousands-of-points-with-html5-canvas-and-d3
nodes.forEach(d => {
d.sx = d.x;
d.sy = d.y;
d.tx = xScale(d.x);
d.ty = yScale(d.y);
});
// trigger transition animation
timer.restart(transition);
});
// - - - - - trigger animation - - - - - //
var timer = d3.timer(floating);
// - - - - - functions - - - - - //
function floating(elapsed) {
if (elapsed > 10000) timer.stop()
nodes.forEach(d => {
d.x += d.dx; if (d.x > getBounds().x[1] || d.x < getBounds().x[0]) d.dx *= -1;
d.y += d.dy; if (d.y > getBounds().y[1] || d.y < getBounds().y[0]) d.dy *= -1;
});
draw();
}
function transition(elapsed) {
const t = Math.min(1, d3.easeCubic(elapsed / DURATION));
// At end, switch to 'floating' animation
if (t === 1) return timer.restart(floating);
nodes.forEach(d => {
d.x = (d.sx * (1 - t) + d.tx * t) + d.dx;
d.y = (d.sy * (1 - t) + d.ty * t) + d.dy;
});
draw();
}
function draw() {
context.clearRect(0, 0, SIZE.w, SIZE.h)
// background rect
context.beginPath();
context.strokeStyle = STROKE;
context.rect(getBounds().x[0], getBounds().y[0], getBounds().w, getBounds().h);
context.stroke();
// circles
for (let i = 0; i < nodes.length; ++i) {
context.beginPath()
context.fillStyle = FILL
context.arc(nodes[i].x, nodes[i].y, nodes[i].r, 0, 2 * Math.PI)
context.fill()
}
}
function getBounds() {
const target = d3.selectAll('input').filter(function(d) {return this.checked; }).node().dataset;
return {
get w() {
return parseInt(target.w);
},
get h() {
return parseInt(target.h)
},
get x() {
return [(SIZE.w - this.w) / 2, (SIZE.w + this.w) / 2];
},
get y() {
return [(SIZE.h - this.h) / 2, (SIZE.h + this.h) / 2];
}
}
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment