Published
Edited
May 16, 2022
1 fork
Importers
12 stars
Insert cell
Insert cell
viewof features = {
const svg = DOM.svg(width, height);

const features = [];
svg.value = { type: "FeatureCollection", features };

const path = geoCurvePath(d3.curveBasis); // d3.geoPath() with a curve

function clear() {
features.splice(0, features.length);
draw();
}

function draw() {
svg.dispatchEvent(new CustomEvent('input'));

d3.select(svg)
.attr("fill", "none")
.attr("stroke-width", 2)
.selectAll("path")
.data(features)
.join("path")
.attr("fill-opacity", .1)
.attr("fill", d => d.properties.color)
.attr("stroke", d => d.properties.color)
.attr("d", path);
}

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

trackPointer(e, {
start: p =>
features.push({
type: "Feature",
properties: { color: d3.hsl(360 * Math.random(), 0.9, 0.7).hex() },
geometry: { type: "LineString", coordinates: p.line = [] }
}),
move: p => {
// save the pressure to the third dimension of the point
// though unfortunately geoPath doesn't know how to render variable width lines (yet)
// see https://github.com/d3/d3-geo/issues/200
const z = 2 * p.sourceEvent.pressure || 1;
p.line.push([...p.point, z]);
draw();
}
});
});

return svg;
}
Insert cell
// svg is a viewof so we can show the features reactively
features
Insert cell
height = Math.min(500, width * 0.7)
Insert cell
import { trackPointer, trackDbltap } from "@fil/pointer-events"
Insert cell
import { geoCurvePath } from "@d3/context-to-curve"
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