Published
Edited
Jul 14, 2021
5 forks
13 stars
Also listed in…
Scatter Plots
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

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