Public
Edited
Jul 24, 2023
Insert cell
Insert cell
chart = {
const vis = d3.select(map())
const italy = vis.select('.map')

const cells = data.map((d, i) => ({...d, cell: voronoi.cellPolygon(i)}) )

// uncomment to visually debug the label placement
italy.append("g")
.attr("stroke", "orange")
.selectAll("path")
.data(cells)
.join("path")
.attr("d", d => `M${d3.polygonCentroid(d.cell)}L${d.x} ${d.y}`)
.attr('vector-effect', 'non-scaling-stroke')

italy.append("path")
.attr("fill", "none")
.attr("stroke", "#99c")
.attr("d", delaunay.render())
.attr('vector-effect', 'non-scaling-stroke')

italy.append("path")
.attr("fill", "none")
.attr("stroke", "#0cc")
.attr("d", voronoi.render())
.attr('vector-effect', 'non-scaling-stroke')
italy.selectAll('.capital')
.data(data)
.join('circle')
.attr('class', 'capital')
.attr('cx', d => d.x)
.attr('cy', d => d.y)

italy.append("g")
.style("font", "10px sans-serif")
.selectAll(".label")
.data(cells)
.join("text")
.attr('class', 'label')
.each(function(d) {
const [cx, cy] = d3.polygonCentroid(d.cell)
const angle = (Math.round(Math.atan2(cy - d.y, cx - d.x) / Math.PI * 2) + 4) % 4
d3.select(this).call(angle === 0 ? orient.right
: angle === 3 ? orient.top
: angle === 1 ? orient.bottom
: orient.left)
})
.text((d, i) => d.label)

function zoomed(event) {
lod(event.transform.k)
}
function lod(z) {
italy.selectAll('.capital')
.attr('r', 1.5 / Math.sqrt(z))

italy.selectAll('.label')
.attr("transform", d => `translate(${d.x} ${d.y}) scale(${1/Math.sqrt(z)})`)
.attr("display", d => boundingBox(d.cell).width > 80/Math.sqrt(z) ? null : "none")
}
zoom.on('zoom', zoomed)

lod(d3.zoomTransform(vis).k)
return vis.node()
}
Insert cell
<style>
.provincia {
fill: #CCC;
}
.province_borders {
fill: none;
stroke: white;
stroke-width: 0.25;
vector-effect: non-scaling-stroke;
}
.italy, .regioni_borders {
fill: none;
stroke: white;
stroke-width: 1;
vector-effect: non-scaling-stroke;
}
.label {
pointer-events: none;
}
.italy {
fill: #CCC;
}
</style>
Insert cell
delaunay = d3.Delaunay.from(data.map(d => [d.x, d.y]))
Insert cell
voronoi = delaunay.voronoi([-1, -1, width + 1, height + 1])
Insert cell
orient = ({
top: text => text.attr("text-anchor", "middle").attr("y", -6),
right: text => text.attr("text-anchor", "start").attr("dy", "0.35em").attr("x", 6),
bottom: text => text.attr("text-anchor", "middle").attr("dy", "0.71em").attr("y", 6),
left: text => text.attr("text-anchor", "end").attr("dy", "0.35em").attr("x", -6)
})
Insert cell
data = raw_data.map(d => {
const p = projection([d.long, d.lat])
return {
x: p[0],
y: p[1],
label: d.capoluogo,
}
})
Insert cell
height = 600
Insert cell
function boundingBox(polygon) {
var maxX = polygon[0][0], maxY = polygon[0][1], minX = polygon[0][0], minY = polygon[0][1]
polygon.forEach(p => {
maxX = Math.max(maxX, p[0])
maxY = Math.max(maxY, p[1])
minX = Math.min(minX, p[0])
minY = Math.min(minY, p[1])
})
return {x: minX, y: minY, width: maxX-minX, height: maxY-minY}
}
Insert cell
d3 = require("d3@6")
Insert cell
raw_data = FileAttachment("province_capitals_edited_2021@1.csv").csv({typed: true})
Insert cell
import { map, projection, zoom } from '@nitaku/provinces-of-italy'
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