Published
Edited
Feb 24, 2021
2 stars
Insert cell
md`# Interaction in d3 Canvas based Scatter Plots`
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
{
const context = DOM.context2d(width, 500);

context.translate(margin.left, margin.right);

//draw window bounds.
context.beginPath();
context.fillStyle = 'rgba(0,0,0,0.3)';
context.fillRect(0, 0, W, H);
context.fill(); //Draw points

context.strokStyle = 'lightgrey';
pointData.map(d => {
context.beginPath();
context.arc(xScale(d[0]), yScale(d[1]), 5, 0, 2 * Math.PI);
context.stroke();
});

addInteractionSupport(
context,
pointData.map(d => [xScale(d[0]), yScale(d[1])]),
function(index) {
return `id : ${index}\nxy: [${pointData[index].map(d =>
d3.format("0.1f")(d)
)}]`;
}
);
context.restore();

return context.canvas;
}
Insert cell
height = 500
Insert cell
margin = ({ left: 25, right: 25, top: 25, bottom: 25 })
Insert cell
W = width - margin.left - margin.right
Insert cell
H = height - margin.top - margin.bottom
Insert cell
xScale = d3.scaleLinear()
.domain(d3.extent(pointData,d=>d[0]))
.range([0, W])
.nice();

Insert cell
yScale = d3
.scaleLinear()
.domain(d3.extent(pointData, d => d[1]))
.range([H, 0])
.nice()
Insert cell
mutable feedback = ""
Insert cell
addInteractionSupport = (context, xyData, tooltipTextGenerator) => {
const delaunay = d3.Delaunay.from(xyData);

const voronoi = delaunay.voronoi([0, 0, W, H]);

if (voronoiDrawOption == "true") {
context.globalAlpha = 0.5;
context.beginPath();
context.strokeStyle = 'rgba(0,0,0,0.3)';
voronoi.render(context);
context.stroke();
context.beginPath();
context.fillStyle = 'black';
delaunay.renderPoints(context, 2);
context.fill();
context.globalAlpha = 1;
}

d3.select(context.canvas)
.on("touchmove mousemove", handleMove)
.on("touchend mouseleave", hideTooltip);

function handleMove(event) {
event.preventDefault();
const mousePoint = d3.pointer(event, this);
mutable feedback = [event.pageX, event.pageY, mousePoint[0], mousePoint[1]];
if (
mousePoint[0] > margin.left &&
mousePoint[0] < width - margin.right &&
mousePoint[1] > margin.top &&
mousePoint[1] < height - margin.bottom
) {
const x = mousePoint[0] - margin.left,
y = mousePoint[1] - margin.top;
const index = delaunay.find(x, y);
const tooltipText = tooltipTextGenerator(index);
mutable feedback = tooltipText;
showTooltip(event, tooltipText);
} else {
hideTooltip();
}
}
}
Insert cell
tooltip = d3
.select('body')
.append('div')
.style('background-color', 'white')
.style('border', '1px solid')
.style('border-radius', '3px')
.style(
'box-shadow',
'0 3px 6px rgba(0, 0, 0, 0.3), 0 3px 6px rgba(0, 0, 0, 0.4)'
)
.style('opacity', 0)
.style('padding', '10px')
.style('position', 'absolute')
.style('pointer-events', 'none')
.style('z-index', -1)
Insert cell
function showTooltip(event, tooltipText) {
tooltip
.style('opacity', 0.8)
.style('top', event.pageY + 'px')
.style('left', event.pageX + 'px')
.style('z-index', 1)
.html(`<div>${tooltipText.split("\n").join("<br\>")}</div>`);
}
Insert cell
function hideTooltip() {
tooltip
.style('opacity', 0)
.style('z-index', -1);
}
Insert cell
Insert cell
md`## External Libraries and Imports`
Insert cell
import { radio, select, slider, button } from "@jashkenas/inputs"
Insert cell
d3 = require("d3@6")
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