Published
Edited
May 16, 2022
2 forks
Importers
24 stars
Insert cell
Insert cell
{
const context = DOM.context2d(width, height),
canvas = context.canvas;

function clear() {
context.clearRect(0, 0, width, height);
}
function draw(p) {
// pressure is 0 for touch, 0.5 for mouse, and between 0 and 1 for pen.
const pressure = 2 * p.sourceEvent.pressure || 1;
if (p.prev) {
context.beginPath();
context.lineWidth = 2 + 2 * pressure;
context.strokeStyle = p.color;
context.moveTo(...p.prev);
context.lineTo(...p.point);
context.lineCap = "round";
context.stroke();
}
}

d3.select(canvas)
.on("touchmove", e => e.preventDefault()) // prevent scrolling
.on("pointerenter", () => (canvas.dbltap = canvas.dbltap || trackDbltap()))
.on("pointerdown", e => {
if (canvas.dbltap(e)) return clear();

trackPointer(e, {
start: p => {
p.color = d3.hsl(360 * Math.random(), 0.9, 0.7).hex();
console.log("start", p);
},
move: p => {
draw(p);
console.log("move", p);
},
out: p => console.log("out", p),
end: p => console.log("end", p)
});
});

return canvas;
}
Insert cell
// track dbltap/dblclick:
// returns true if this pointerdown event is close in space and time to the previous one
function trackDbltap(delay = 500) {
let tap;
return function(e) {
const t = d3.pointer(e);
if (tap && Math.hypot(tap[0] - t[0], tap[1] - t[1]) < 20) return true;
tap = t;
setTimeout(() => (tap = null), delay);
};
}
Insert cell
// set up listeners that will follow this gesture all along
// (even outside the target canvas)
function trackPointer(e, { start, move, out, end }) {
const tracker = {},
id = (tracker.id = e.pointerId),
target = e.target;
tracker.point = d3.pointer(e, target);
target.setPointerCapture(id);

d3.select(target)
.on(`pointerup.${id} pointercancel.${id} lostpointercapture.${id}`, (e) => {
if (e.pointerId !== id) return;
tracker.sourceEvent = e;
d3.select(target).on(`.${id}`, null);
target.releasePointerCapture(id);
end && end(tracker);
})
.on(`pointermove.${id}`, (e) => {
if (e.pointerId !== id) return;
tracker.sourceEvent = e;
tracker.prev = tracker.point;
tracker.point = d3.pointer(e, target);
move && move(tracker);
})
.on(`pointerout.${id}`, (e) => {
if (e.pointerId !== id) return;
tracker.sourceEvent = e;
tracker.point = null;
out && out(tracker);
});

start && start(tracker);
}
Insert cell
height = Math.min(500, width * 0.7)
Insert cell
d3 = require("d3-selection@2", "d3-color@2")
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