Public
Edited
Apr 14, 2021
Insert cell
md`# Hedonometer Voronoi`
Insert cell
chart = {
const context = DOM.context2d(width, height);
buildCircles(context, topData, topY, 0, 25, d3.interpolateHsl('green', 'blue'));
buildCircles(context, bottomData, bottomY, -25, 0, d3.interpolateHsl('red', 'yellow'));
return context.canvas;
}
Insert cell
html`<style>canvas {background: #fff}</style>`
Insert cell
function buildCircles(context, data, y, extraY1, extraY2, color) {
let x = halfDeltaX;
for(let i = 0; i < data.length; i++) {
let curX = x;
let simulation = d3.forceSimulation(data[i])
.force('charge', d3.forceManyBody().strength(0))
.force('collision', d3.forceCollide().radius(radius + spacing))
.force('x', d3.forceX().x( curX ))
.force('y', d3.forceY().y( (d) => y(d['Happiness Score']) ))
simulation.on('tick', () => {
const delaunay = d3.Delaunay.from(data[i], d => d.x, d => d.y);
const bounds = [
curX - halfDeltaX + 0.5,
y.range()[1] + 0.5 + extraY1,
curX + halfDeltaX - 0.5,
y.range()[0] - 0.5 + extraY2
];
const voronoi = delaunay.voronoi(bounds);
context.clearRect(bounds[0], bounds[1], bounds[2] - bounds[0], bounds[3] - bounds[1]);
for(let j = 0; j < data[i].length; j++) {
let alpha = (data[i][j]['Happiness Score'] - y.domain()[0]) / (y.domain()[1] - y.domain()[0]);
context.fillStyle = color(alpha);
context.beginPath();
voronoi.renderCell(j, context);
context.fill();
}
context.beginPath();
voronoi.render(context);
voronoi.renderBounds(context);
context.strokeStyle = '#ccc';
context.stroke();
context.beginPath();
context.fillStyle = 'white';
delaunay.renderPoints(context);
context.fill();
})
x += deltaX;
}
}
Insert cell
halfDeltaX = deltaX / 2
Insert cell
deltaX = (width - margin.left - margin.right) / data.length;
Insert cell
bottomY = d3.scaleLinear()
.domain(d3.extent(bottomData.flat(), d => d['Happiness Score']))
.range([height - margin.bottom, (height / 2) + halfMiddleSize]);
Insert cell
bottomY.domain()
Insert cell
topY.domain()
Insert cell
topY = d3.scaleLinear()
.domain(d3.extent(topData.flat(), d => d['Happiness Score']))
.range([(height / 2) - halfMiddleSize, margin.top]);
Insert cell
bottomData = data.map(d => d.filter(d2 => d2['Happiness Score'] < 5))
Insert cell
topData = data.map(d => d.filter(d2 => d2['Happiness Score'] >= 5))
Insert cell
data = await FileAttachment("alice-in-wonderland-hedonometer.json").json()
Insert cell
halfMiddleSize = (height * middlePercent) / 2
Insert cell
middlePercent = 0.1
Insert cell
spacing = 2
Insert cell
radius = 3
Insert cell
height = 800
Insert cell
margin = ({top: 10, right: 10, bottom: 10, left: 10})
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