Public
Edited
Jan 13, 2023
Insert cell
Insert cell
Insert cell
dpi = window.devicePixelRatio || 1
Insert cell
{
const container = d3.create("div");
const height = 200;
const canvas = container
.append("canvas")
.attr("width", width * dpi)
.attr("height", height * dpi)
.style("width", width + "px")
.style("height", height + "px");

const svg = container
.append("svg")
.style("position", "absolute")
.style("pointer-events", "none")
.style("top", 0)
.style("left", 0)
.attr("width", width)
.attr("height", height);

svg.append("rect").attr("fill", "none").attr("stroke", "black");

const ctx = canvas.node().getContext("2d");
ctx.scale(dpi, dpi);
const radius = 1.5 * Math.PI;

// drawEnd(50, 20, 100, 10, 10, 100)
function drawEnd(
x1,
y1,
length,
height,
padding,
max_width,
inverse = false
) {
const line1_length = max_width - x1,
line2_length = length - (inverse ? line1_length * -1 : line1_length); // the remainder
const penalty = inverse ? -10 : 10; // remove 10 as a penalty to the loop
const x2 = max_width,
y2 = y1,
x3 = x2,
y3 = y1 + padding + height * 2,
x4 = line2_length + penalty,
y4 = y3,
x5 = x4,
y5 = y1 + padding + height,
x6 = x3,
y6 = y5,
x7 = x6,
y7 = y1 + height,
x8 = x1,
y8 = y7;

ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
inverse
? ctx.arc(
x2,
y3 - height - padding * 0.5,
(y3 - y2) / 2,
Math.PI * 1.5,
Math.PI * 0.5,
true
)
: ctx.arc(
x2,
y3 - height - padding * 0.5,
(y3 - y2) / 2,
Math.PI * 1.5,
Math.PI * 0.5,
false
);
ctx.lineTo(x3, y3);
ctx.lineTo(x4, y4);
ctx.lineTo(x5, y5);
ctx.lineTo(x6, y6);
inverse
? ctx.arc(
x6,
y6 - padding * 0.5,
(y6 - y7) / 2,
Math.PI * 0.5,
Math.PI * 1.5,
false
)
: ctx.arc(
x6,
y6 - padding * 0.5,
(y6 - y7) / 2,
Math.PI * 0.5,
Math.PI * 1.5,
true
);
ctx.lineTo(x7, y7);
ctx.lineTo(x8, y8);
ctx.lineTo(x1, y1);
ctx.strokeStyle = "white";
ctx.stroke();
ctx.fillStyle = "rgba(0,0,155,0.2)";
ctx.fill();
}

drawEnd(50, 20, 100, 10, 10, 100, false);
drawEnd(60, 40, 100, 10, 10, 20, true);
drawEnd(50, 60, 100, 10, 10, 100, false);

function drawRect(x1, y1, x2, y2) {
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y1);
ctx.arc(x2, y1 + radius, radius, -Math.PI / 2, Math.PI / 2, false);
ctx.lineTo(x1, y2);
ctx.arc(x1, y2 - radius, radius, Math.PI / 2, -Math.PI / 2, false);
ctx.fillStyle = "blue";
ctx.fill();
}
drawRect(5, 5, 100, 15);

const data = [1, 2, 13, 20, 23].map((i) => ({
x: i
}));

// create our Flatbush index
let index = new Flatbush(data.length);

const scale = d3
.scaleLinear()
.range([10, width])
.domain(d3.extent(data, (d) => d.x));

data.forEach((d, i) => {
ctx.beginPath();

const x = scale(d.x);
ctx.rect(x, 150, 10, 10);
ctx.fillStyle = "red";
ctx.fill();
ctx.closePath();
index.add(x, 150, x + 10, 150 + 10);
});

index.finish();

canvas.on("mousemove", mousemoved);

function mousemoved(e) {
const [x, y] = d3.pointer(e, canvas.node());
const results = index.search(x, y, x, y);
const feature = data[results];

// flatbush always returns and array of indexes and sometimes the good one is not the first one
// for (let idx = 0; idx < results.length; ++idx) {
// const feature = counties[idx];
// if (d3.geoContains(feature, inverted)) {
// j = idx;
// break;
// }
// }

// reset hover styles
canvas.style("cursor", null);
svg.select("rect").attr("x", -100);

// return if not found!
if (results.length === 0) return;

canvas.style("cursor", "pointer");

svg
.select("rect")
.datum(feature)
.attr("x", scale(feature.x))
.attr("y", 150)
.attr("width", 11)
.attr("height", 11);
}

return container.node();
}
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