Published
Edited
Feb 6, 2020
Fork of Drag & Zoom
2 forks
6 stars
Also listed in…
D3.js
Insert cell
Insert cell
chart = {
const context = DOM.context2d(width, height);

// Bind the drag behavior for interaction.
d3.select(context.canvas).call(
drag(data, context.canvas).on("start.render drag.render end.render", render)
);

d3.select(context.canvas).call(
d3
.zoom()
.scaleExtent([1, 8])
.on("zoom", () => render(d3.event.transform))
);

function render(transform = d3.zoomTransform(context.canvas)) {
context.save();
context.clearRect(0, 0, width, height);
context.translate(transform.x, transform.y);
context.scale(transform.k, transform.k);
data.forEach(({ x, y, color }, i) => {
context.beginPath();
context.moveTo(x + radius, y);
context.fillStyle = color;
context.arc(x, y, radius, 0, 2 * Math.PI);
context.fill();
});
context.restore();
}

// Render the initial canvas.
render();

return context.canvas;
}
Insert cell
drag = (circles, canvas) => {
// Choose the circle that is closest to the pointer for dragging.
function dragsubject() {
const transform = d3.zoomTransform(canvas);
let subject = null;
// Distance is expressed in unzoomed coordinates, ie. keeps proportion with circles radius
// at any zoom level.
let distance = maxDistance;
const x = transform.invertX(d3.event.x);
const y = transform.invertY(d3.event.y);
for (const c of circles) {
let d = Math.hypot(x - c.x, y - c.y);
if (d < distance) {
distance = d;
subject = c;
}
}
return subject
? {
circle: subject,
x: transform.applyX(subject.x),
y: transform.applyY(subject.y)
}
: null;
}

// When starting a drag gesture, move the subject to the top and mark it as active.
function dragstarted() {
circles.splice(circles.indexOf(d3.event.subject.circle), 1);
circles.push(d3.event.subject.circle);
d3.event.subject.active = true;
}

// When dragging, update the subject’s position.
function dragged() {
const transform = d3.zoomTransform(canvas);
d3.event.subject.circle.x = transform.invertX(d3.event.x);
d3.event.subject.circle.y = transform.invertY(d3.event.y);
}

// When ending a drag gesture, mark the subject as inactive again.
function dragended() {
d3.event.subject.active = false;
}

return d3
.drag()
.subject(dragsubject)
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended);
}
Insert cell
height = 500
Insert cell
maxDistance = radius // Limit the search distance, if desired.
Insert cell
radius = 6
Insert cell
step = radius * 2
Insert cell
data = Array.from({ length: 2000 }, (_, i) => {
const radius = step * Math.sqrt((i += 0.5)),
a = theta * i;
return {
x: width / 2 + radius * Math.cos(a),
y: height / 2 + radius * Math.sin(a),
color: d3.interpolateRainbow(i / 360)
};
})
Insert cell
theta = Math.PI * (3 - Math.sqrt(5))
Insert cell
d3 = require("d3@5")
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more