Published
Edited
Jul 14, 2021
5 forks
13 stars
Also listed in…
Libs
Insert cell
Insert cell
numPoints = 500000
Insert cell
chartEl = {
const el = d3.select(DOM.element('div'));
el.style("height", `${height}px`);
el.style("width", `${width}px`);
el.datum({ data, annotations }).call(chart);

const zoom = d3
.zoom()
.scaleExtent([1, 500])
.translateExtent([[0, 0], [width, height]])
.on("zoom", ({ transform }) => {
xScale.domain(transform.rescaleX(xScaleCopy).domain());
yScale.domain(transform.rescaleY(yScaleCopy).domain());
el.datum({ data, annotations }).call(chart);
});

el.select('d3fc-svg.plot-area')
.on("measure.range", ({ detail }) => {
xScaleCopy.range([0, detail.width]);
yScaleCopy.range([detail.height, 0]);
})
.call(zoom)
.on("dblclick.zoom", e => {
el.select('d3fc-svg.plot-area')
.transition()
.duration(750)
.call(zoom.transform, d3.zoomIdentity);
});

return el.node();
}
Insert cell
height = (width * 2) / 3
Insert cell
yScaleCopy.range()
Insert cell
bisect = d3.bisector(d => d.x)
Insert cell
pointer = fc.pointer().on("point", ([point]) => {
//from https://roadtolarissa.com/scan-sorted/
// annotations.pop();
if (!point) return;
const coord = {
x: xScale.invert(point.x),
y: yScale.invert(point.y)
};
const index = bisect.left(data, coord.x);

function checkPoint(d) {
if (!d) return Infinity;

const dx = d.x - coord.x;
const dy = d.y - coord.y;
const dist = Math.sqrt(dx * dx + dy * dy);

if (dist < minDist) {
minDist = dist;
minPoint = d;
}

return Math.abs(coord.x - d.x);
}

let minPoint = null;
let minDist = Infinity;
let lxDist = 0;
let rxDist = 0;
let i = 0;
while (lxDist < minDist && rxDist < minDist) {
lxDist = checkPoint(data[index - i]);
rxDist = checkPoint(data[index + i]);
i++;
}

if (minPoint) {
annotations[0] = createAnnotationData(minPoint);
}
d3.select('d3fc-svg')
.node()
.requestRedraw();
})
Insert cell
mutable annotations = []
Insert cell
color = d3.scaleSequential(d3.interpolatePlasma).domain([-1, 1])
Insert cell
xScale = d3.scaleLinear().domain([-1, 1])
Insert cell
yScale = d3.scaleLinear().domain([-1, 1])
Insert cell
xScaleCopy = xScale.copy()
Insert cell
yScaleCopy = yScale.copy()
Insert cell
chart = fc
.chartCartesian(xScale, yScale)
.webglPlotArea(
fc
.seriesWebglMulti()
.series([pointSeries])
.mapping(d => d.data)
)
.svgPlotArea(
fc
.seriesSvgMulti()
.series([annotationSeries])
.mapping(d => d.annotations)
)
.decorate(sel =>
sel
.enter()
.select("d3fc-svg.plot-area")
.call(pointer)
)
Insert cell
pointSeries = fc
.seriesWebglPoint()
.equals((a, b) => a === b)
.size(1)
.crossValue(d => d.x)
.mainValue(d => d.y)
.decorate(d => fillColor(d))
Insert cell
fillColor = fc
.webglFillColor()
.value(d => d.color)
.data(data)
Insert cell
randomNormal = d3.randomNormal()
Insert cell
data = d3
.range(numPoints)
.map(() => {
const p = 2 * Math.PI * Math.random();
const r = Math.random();
const x = r * Math.sin(p);
const y = r * Math.cos(p);
return {
x,
y,
color: webglColor(color(x))
};
})
.sort((a, b) => a.x - b.x)
Insert cell
webglColor = color => {
const { r, g, b, opacity } = d3.color(color).rgb();
return [r / 255, g / 255, b / 255, opacity];
};
Insert cell
annotationSeries = seriesSvgAnnotation()
.notePadding(15)
.type(d3.annotationCallout)
Insert cell
createAnnotationData = d => ({
note: {
label: `x: ${d.x} y:${d.y}`,
bgPadding: 5
},
x: d.x,
y: d.y,
dx: 20,
dy: 20
})
Insert cell
seriesSvgAnnotation = () => {
// the underlying component that we are wrapping
const d3Annotation = d3.annotation();

let xScale = d3.scaleLinear();
let yScale = d3.scaleLinear();

const join = fc.dataJoin("g", "annotation");

const series = selection => {
selection.each((data, index, group) => {
const projectedData = data.map(d => ({
...d,
x: xScale(d.x),
y: yScale(d.y)
}));

d3Annotation.annotations(projectedData);

join(d3.select(group[index]), projectedData).call(d3Annotation);
});
};

series.xScale = (...args) => {
if (!args.length) {
return xScale;
}
xScale = args[0];
return series;
};

series.yScale = (...args) => {
if (!args.length) {
return yScale;
}
yScale = args[0];
return series;
};

fc.rebindAll(series, d3Annotation);

return series;
}
Insert cell
html`
<style>

.annotation-note-bg {
fill-opacity: 0.8;
}

.annotation-note-label,
.annotation-note-title {
fill: black;
font-size: 0.8em;
}

</sytle>
`
Insert cell
d3 = require("d3@7", "d3-svg-annotation@2")
Insert cell
fc = require("d3fc@15")
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more