Published
Edited
Jun 27, 2022
14 stars
SQLite Geospatial - intersections!
London Isodemographic Cartogram
Golden Tonal Palette AnalysisHexbin Transform / Observable Plot
NBA Finals, Game 4
Disk Sampling on a SphereNBA Finals, Game 1Build your first scatterplot with Observable PlotDisplaying IBM Carbon charts in ObservableModelo de Cúpula CatenáriaBivariate Bubble MapAnalyzing Star Wars movies + the first anniversary of Observable Plot!Single-family homes owned by large corporate landlords in North CarolinaThe MoMA Collection DataMapping the Vietnamese DiasporaElection map as striped circles #2They heard the callDiagrammatic EmbeddingsHegel's Complete System of PhilosophyGeneration Arc DiagramVulnerability of Mountain PeoplesVisualizing Air Raid Sirens in UkraineUkrainian refugees welcomeConcentric Circles in D3Daylight Saving Time Gripe Assistant ToolPerceptually uniform color models and their implications
Top Notebooks of February 2022Zillow Home PricesBlack History Month ActivityExamples from: Three Simple Flexible tools for Empowered Data VisualizationFull Covid Vaccination Rate in CA by County Over TimePlot CheatsheetsHunga Tonga–Hunga Ha'apai Global Pressure WaveVisualizing The New York Times’s Mini Crossword100 Beautiful and Informative Notebooks of 2021When Presidents Fade AwayThe Most Frequently Used Emoji of 2021Upset Plots with Observable PlotLucy mission animationSpreadsheet-based data collaboration gets supercharged with Observable30 Day Map Challenge: Day 4 - HexagonsDisputed territoriesMapping the Cities of US Highway SignsThirty day Map Challenge: Day 1 - PointsCube Pushing Loop 💪 🧊 🔄Guided Tour of an Infinite Sphere GridCanvas, P5.js and circle packing (collision and cluster force) on a mapWomen's History Month DataViz ContestWomen in DataViz EventSVG flowersMultiple Set Venn with Color BlendingA Stupid Emoji Utility FunctionQuilting with d3-curveCurvilinear RosettesStrange AttractorsSolo board gameDouble pendilumVertical SlidersDrawing and animating blobs with svg and javascriptOrbiting Particles III#genuary 2021 ~ Do some golf!Imitation Game 🖐Happy New Year (2019) (And other celebrations)Earth Temperature SpiralStar MapCorrelation FilteringBattle of ColorsPapercraft Christmas Ornaments GeneratorChristmas Tree Perspective 🎄bouncing circles2D (Non-physical) N-body Gravity with Poisson's EquationWaterfall Fill2020 TimelineTime Spiral with a COVID DemoTransition between Three.js camerasJulio Le Parc Replications and VariationsFlexible HeatmapSquare PackingSierpiński curve animationTrainsWhy use a radial data visualization?Earthquakes from the past 30 daysThe US COVID SyringeHello OGL - Minimal WebGL libraryVersor MultiplicationLabyrinthTruncated-octahedron-based 3D-space-filling curveNUTS regions (The Nomenclature of territorial units for statistics): PerspectiveStretchy FishTP3: Power Diagram and Semi-Discrete Optimal TransportEmoji ParticlesFirma de color de BogotáCovid19 WorldwideWeb 3.0 Explorable # 3: NFTs -- Game Items With Real World ValueUnemployment ExplorationSierpinski curveEight QueensVoronoi StipplingSpinning out of controlFragment shader raytracerSunny day, rainy day in SeattleAs votes are countedOrbit of the dayVoronoi ClothTry to impeach this? Challenge accepted!Dispersion in Water Surface WavesSelf-Organizing Maps meet DelaunayGenerative ArtReturn to a squareCreating a Canvas Noise Map for Generative ArtMARTINI: Real-Time RTIN Terrain MeshMunsell spinGenerative artAs votes come in, what would it take for the trailing candidate to win?3a. Historical participation in early voting vs. Election Day votingWaffle ChartSun settingRaymarch HelperF1 Constructor Standings 2010-2019Electoral College Decision TreeThe Woman Citizen's Wheel of Progress, 1917Equisurface bull's eye
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
run = {
for (let i = 0; i < 150; i++)
yield mapUrban.update(i);
}
Insert cell
function lloydsRelaxation(voronoi, radius) {
const {
delaunay: {points}
} = voronoi;
for (let i = 0; i < points.length / 2; i++) { //for every cell
const R = Math.hypot(points[2 * i], points[2 * i + 1]); //radius of points
if (R < radius) { // if the points are within the circle
const c = voronoi.cellPolygon(i)
//Return centroid of cell, squeezed withoun the bounding circle, if it goes outside the range
const p = d3.polygonCentroid(
c.map(([x, y]) => {
const r0 = Math.hypot(x, y); //radius of each corner of the polygon
const a = radius / Math.max(r0, radius); //reduction factor to fit corners back in the circle
return [x * a, y * a]; //reduction factor applied
})
);

//Move point towards the centroid of its cell
const f = 0.5;
points[2 * i] += (p[0] - points[2 * i]) * f;
points[2 * i + 1] += (p[1] - points[2 * i + 1]) * f;
}
}
}
Insert cell
discUniformTransportBatched = {
let indices = [],
projection,
deltas;

// batched
return (
points,
radius,
{ dirs = 3, strength = 1, profile = chordAreaInverse } = {}
) => {
const n = points.length / 2;
if (n !== indices.length) { //if indices array hasn't been set up yet
indices = Uint32Array.from(d3.range(n)); //create array of points
projection = new Float32Array(n); //list of points
deltas = new Float32Array(2 * n); // list of point coords
}

const a = 2 * Math.PI * Math.random(); //random angle along circle
deltas.fill(0);
for (let d = 0; d < dirs; d++) { //iterate n times
const ap = a + ((Math.PI * d) / dirs), //rotate round circle with successive iterations
sa = Math.sin(ap),
ca = Math.cos(ap);
for (let i = 0; i < n; i++)
projection[i] = ca * points[2 * i] + sa * points[2 * i + 1]; //

indices.sort((i, j) => projection[i] - projection[j]);

for (let k = 0; k < n; k++) { //for every point
const i = indices[k],
ideal = radius * profile(k / (n + 1)), //
delta = ideal - projection[i];
deltas[2 * i] += ca * delta;
deltas[2 * i + 1] += sa * delta;
}
}
for (let i = 0; i < points.length; i++)
points[i] += (deltas[i] / dirs) * strength;
};
}
Insert cell
function preserveContinuity(voronoi, codes) {
const {
delaunay: {points}
} = voronoi;

const counts = {};
const colors = codes.map((d) => (counts[d] = (counts[d] || 0) + 1) -1 );

//This bit returns whether all cells in a commune are contiguous or not
let cutoff = 10;
do {
let changed = false;
for (let i = 0; i < codes.length; i++) { //for each cell
for (const j of voronoi.delaunay.neighbors(i)) { //for every neighbour of this cell
if (codes[i] === codes[j] && colors[j] !== colors[i]) { //if the commune is the same, but the commune increment is different?
colors[j] = colors[i] = Math.min(colors[j], colors[i]); //re-assign the increment of both to be the same.
changed = true; //then mark as done
}
}
}
if (!changed) cutoff = 0; //if no re-assignments then stop
} while (cutoff-- > 0); //decrement cutoff on each iteration

for (let i = 0; i < colors.length; i++) { //for each cell
const f = colors[i]; //increment of each cell
if (f > 0) { //if cells are not completely contiguous within a commune
console.log("fix continuity for point", i, codes[i]);
const base = [];
//Return all the combinations of cells in the same commune
for (let j = 0; j < i; j++) if (codes[j] === codes[i]) base.push(j);

//Move towards the average poisition of all the cells in the same commune,
points[2 * i] +=
0.7 * (d3.mean(base, (i) => points[2 * i]) - points[2 * i]);
points[2 * i + 1] +=
0.7 * (d3.mean(base, (i) => points[2 * i + 1]) - points[2 * i + 1]);
}
}
}
Insert cell
Insert cell
thames2 = thames.places.map(d => {
const I = d3.range(points.length / 2);
return Object.assign(d,{blocs: I.filter((i) => names.get(codes[i]) === d.place)})
})
Insert cell
// jrus' fast (and presice) approximation of the chord area inverse function
import { chordAreaInverse2 as chordAreaInverse } from "@fil/chord-area-inverse"
Insert cell
codes = points0.map((d) => d.GSSCode)
Insert cell
stroke = (c) => (fill(c)) // highlight a commune
Insert cell
fill = colorBy === "commune"
? codeColor
: colorBy === "population density"
? (code) => densityColor(density.get(code))
: (code) => ruralColor(density.get(code))
Insert cell
ruralColor = d3
.scaleThreshold()
.domain([50, 100, 250])
.range("e6deb2-9ac1ae-ecb45b-e07a5f".split("-").map((d) => `#${d}`))
Insert cell
densityColor = d3
.scaleSequentialSqrt(d3.interpolateCool)
.domain(d3.extent(density, ([, v]) => v))
Insert cell
codeColor = d3
.scaleOrdinal()
.domain(density.keys())
.range(
[].concat(
d3.schemeTableau10,
d3.schemeAccent,
d3.schemeCategory10,
d3.schemeDark2
)
)
Insert cell
listToArray = function(voronoi) {
let points = voronoi.delaunay.points
let pointsTest = [];
for (let i = 0; i < points.length - 1; i+= 2) {
pointsTest.push([points[i], points[i+1]])
}
return pointsTest
}
Insert cell
Insert cell
density = new Map(
d3.sort(
geo.features.map((f) => [
f.properties.GSSCode,
(population.get(f.properties.GSSCode) / d3.geoArea(f)) *
((4 * Math.PI) / 510.1e6) //population per km2 (converting from steridians)
]),
([, v]) => v //sort by density asc
)
)
Insert cell
height = Math.min(670, width)
Insert cell
projection = d3.geoMercator().fitSize([height, height], geo)
Insert cell
geo.features.map((f) => f)
Insert cell
points = (
replay,
points0
.map((d) => d.coords)
.flat()
.map((d) => d - height / 2 + 0.01 * (Math.random() - 0.5)) //shift points left and up by half the height of the canvas...and add a small random variable
)
Insert cell
points0 = {
let rest = 0;
const phi = Math.PI * (3 - Math.sqrt(5));
return (
d3
.sort(geo.features, ({ properties: {GSSCode}}) => density.get(GSSCode))
.flatMap((f) => {
const coords = projection(d3.geoCentroid(f));
const {
properties: { GSSCode }
} = f;
const pop = population.get(GSSCode);
const length = Math.round ((pop + rest) / precision); //number of cells required to represent area
// we keep track of the difference between the current sum and the true sum, and add
// the people we missed (or overcounted) to the next unit, so that the grand total is correct.
rest += pop - length * precision;
return Array.from({ length }, (_,i) => {
const r = 0.5 * Math.sqrt(0.5 + i);
return {
coords: [
coords[0] + r * Math.sin(i * phi),
coords[1] + r * Math.cos(i * phi)
],
GSSCode
};
});
})
);
}
Insert cell
polygonClipping = require("polygon-clipping")
Insert cell
names = new Map(geo.features.map((f) => [f.properties.GSSCode, f.id]))
Insert cell
Insert cell
Insert cell
import { Legend, Swatches } from "@d3/color-legend"
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