|
<!DOCTYPE html> |
|
<meta charset="utf-8"> |
|
<body> |
|
<script src="https://d3js.org/d3.v4.min.js"></script> |
|
<script src="https://d3js.org/topojson.v3.min.js"></script> |
|
|
|
<script> |
|
const padding = 2; |
|
|
|
const margin = { top: 10, right: 10, bottom: 10, left: 10 }; |
|
const width = 560 - margin.left - margin.right; |
|
const height = 560 - margin.top - margin.bottom; |
|
|
|
const svg = d3 |
|
.select('body') |
|
.append('svg') |
|
.attr('width', width + margin.left + margin.right) |
|
.attr('height', height + margin.top + margin.bottom) |
|
.append('g') |
|
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); |
|
|
|
const pop = d3.map(); |
|
const name = d3.map(); |
|
|
|
const size = d3.scaleSqrt().range([5, 120]); |
|
const font = d3.scaleLinear().range([6, 20]); |
|
|
|
d3 |
|
.queue() |
|
.defer(d3.json, 'comarques.json') |
|
.defer(d3.csv, 'data.csv', d => { |
|
d.pop = +d.pop; |
|
|
|
pop.set(d.id, d.pop); |
|
name.set(d.id, d.abbr); |
|
|
|
return d; |
|
}) |
|
.await(ready); |
|
|
|
function ready(error, cat, data) { |
|
if (error) throw error; |
|
|
|
size.domain(d3.extent(data, d => d.pop)); |
|
font.domain(d3.extent(data, d => d.pop)); |
|
|
|
const comarques = topojson.feature(cat, cat.objects.comarques); |
|
const features = comarques.features; |
|
|
|
// File is already projected |
|
const projection = d3 |
|
.geoIdentity() |
|
.reflectY(true) |
|
.fitSize([width, height], comarques); |
|
|
|
const path = d3.geoPath().projection(projection); |
|
|
|
features.forEach(function(d) { |
|
d.pos = path.centroid(d); |
|
d.area = size(pop.get(d.properties.CODICOMAR)); |
|
[d.x, d.y] = d.pos; |
|
}); |
|
|
|
const simulation = d3 |
|
.forceSimulation(features) |
|
.force('x', d3.forceX(d => d.x).strength(0.1)) |
|
.force('y', d3.forceY(d => d.y).strength(0.1)) |
|
.force('collide', collide); |
|
|
|
for (let i = 0; i < 120; ++i) simulation.tick(); |
|
|
|
const rect = svg |
|
.selectAll('g') |
|
.data(features) |
|
.enter() |
|
.append('g') |
|
.attr('transform', d => `translate(${d.x}, ${d.y})`); |
|
|
|
rect |
|
.append('rect') |
|
.attr('width', d => d.area) |
|
.attr('height', d => d.area) |
|
.attr('x', d => -d.area / 2) |
|
.attr('y', d => -d.area / 2) |
|
.attr('fill', '#ccc') |
|
.attr('stroke', 'white') |
|
.attr('rx', 2); |
|
|
|
rect |
|
.append('text') |
|
.filter(d => d.area > 18) // Only labeling after a threshold |
|
.style('font-family', 'sans-serif') |
|
.style('font-size', d => `${font(pop.get(d.properties.CODICOMAR))}px`) |
|
.attr('text-anchor', 'middle') |
|
.attr('dy', 2) |
|
.text(d => name.get(d.properties.CODICOMAR)); |
|
|
|
// From https://bl.ocks.org/mbostock/4055889 |
|
function collide() { |
|
for (var k = 0, iterations = 4, strength = 0.5; k < iterations; ++k) { |
|
for (var i = 0, n = features.length; i < n; ++i) { |
|
for (var a = features[i], j = i + 1; j < n; ++j) { |
|
var b = features[j], |
|
x = a.x + a.vx - b.x - b.vx, |
|
y = a.y + a.vy - b.y - b.vy, |
|
lx = Math.abs(x), |
|
ly = Math.abs(y), |
|
r = a.area / 2 + b.area / 2 + padding; |
|
if (lx < r && ly < r) { |
|
if (lx > ly) { |
|
lx = (lx - r) * (x < 0 ? -strength : strength); |
|
(a.vx -= lx), (b.vx += lx); |
|
} else { |
|
ly = (ly - r) * (y < 0 ? -strength : strength); |
|
(a.vy -= ly), (b.vy += ly); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
} |
|
} |
|
</script> |