Published
Edited
Jul 15, 2021
Insert cell
md`# Gender Ratio of China`
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
d3.interpolateRdBu(0)
Insert cell
Insert cell
Insert cell
colorScale = {
switch (scale){
case 'diverging':
default:
return divergingScale
case 'divergingSqrt':
return divergingSqrtScale
}
}
Insert cell
Insert cell
Insert cell
// viewof height = slider({min: "150", max: 700, step:10, title: "height:"})
height = 600
Insert cell
viewof step = Range([0, steps], {step: 1})
Insert cell
steps = 10
Insert cell
md`## Utils`
Insert cell
spreadProvinces = applySimulation(provinces)
Insert cell
Insert cell
populationMax = d3.max(filteredData, d => d.female + d.male)

Insert cell
filteredData = allData
.filter(d => ""+d.age===ages[step])
Insert cell
Insert cell
Insert cell
applySimulation = (nodes) => {
const simulation = d3.forceSimulation(nodes)
.force("x", d3.forceX(width / 2))
.force("y", d3.forceY(height / 2))
// .force("collide", d3.forceCollide().radius(d => d.r + 1))
// .force("cx", d3.forceX().x(d => width / 2).strength(0.02))
// .force("cy", d3.forceY().y(d => height / 2).strength(0.02))
// .force("x", d3.forceX().x(d => d.properties.centroid ? d.properties.centroid[0] : 0).strength(0.3))
// .force("y", d3.forceY().y(d => d.properties.centroid ? d.properties.centroid[1] : 0).strength(0.3))
.force("charge", d3.forceManyBody().strength(-1))
.force("collide", d3.forceCollide().radius(d => d.properties.radius + nodePadding).strength(1))
.stop()

let i = 0;
while (simulation.alpha() > 0.01 && i < 200) {
simulation.tick();
i++;
// console.log(`${Math.round(100*i/200)}%`)
}

return simulation.nodes();
}
Insert cell
// projection = d3.geoTransverseMercator().rotate([-110,0]).fitExtent([[65, 80], [width, height]], china_p);
// projection = d3.geoAzimuthalEqualArea()
// .rotate([-110, -40])
// .scale(height)
projection = d3.geoMercator()
.center([110, 40])
.scale([height]);
Insert cell
path = d3.geoPath(projection)
Insert cell
function set(input, value) {
input.value = value;
input.dispatchEvent(new CustomEvent("input"));
}
Insert cell
// Create a legend generator like so:
legendPopulation = legendCircle()
.scale(radiusScale)
.tickValues([populationMax * 0.1, populationMax * 0.5, populationMax])
.tickFormat((d, i, e) => d3.format(",.1f")(d/1000000) + "M")
.tickSize(10); // defaults to 5
Insert cell
xMale = d3.scaleLinear()
.domain([0, d3.max(aggregateList.filter(d => d.gender =='male'), d => d.value)])
.rangeRound([width / 2, 10])
Insert cell
xFemale = d3.scaleLinear()
.domain([0, d3.max(aggregateList.filter(d => d.gender =='female'), d => d.value)])
.rangeRound([width / 2, width -10])
Insert cell
y = d3.scaleBand()
.domain(aggregateList.map(d => d.age))
.rangeRound([height - 10, 10])
.padding(0.1)
Insert cell
style = html`
<style>
.tooltip {
padding: 7px;
background: rgb(255, 255, 255, 0.6);
border: 2px;
border-style: solid;
border-radius: 2px;
pointer-events: none;
}
</style>
`
Insert cell
pyramidChart = {
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height])
.attr("font-family", "sans-serif")
.attr("font-size", 10);

svg.append("g")
.selectAll("rect")
.data(aggregateList)
.join("rect")
.attr("fill", d => d3.schemeSet1[d.gender === "male" ? 1 : 0])
.attr("x", d => d.gender === "male" ? xMale(d.value) : xFemale(0))
.attr("y", d => y(d.age))
.attr("width", d => d.gender === "male" ? xMale(0) - xMale(d.value) : xFemale(d.value) - xFemale(0))
.attr("height", y.bandwidth());

return svg.node();
}
Insert cell
Insert cell
data = new Map(filteredData.map(({place, male, female}) => ([place, male/female])))
Insert cell
aggregateList = {
let list = []
Object.keys(aggregateData.get("all")).forEach((gender) => {
Array.from(aggregateData, ([key, value]) => ({key: ""+key, value}))
.filter(d => d.key!='all')
.forEach((d) => {
list.push({
age: d.key,
gender,
value: d.value[gender]
})
})
})
return list;
}

Insert cell
aggregateData = d3.rollup(allData, v => ({male: d3.sum(v, d=> d.male), female: d3.sum(v, d=> d.female)}), d => d.age)
Insert cell
allData = d3.csvParse(await FileAttachment("total_gender@4.csv").text(), d3.autoType)
Insert cell
ageMap = (index) => ages[index]
Insert cell
ages = [...new Set(d3.csvParse(await FileAttachment("total_gender@4.csv").text(), ({age}) => (age)))]
Insert cell
china_p = topojson.feature(china_geo, china_geo.objects.China_adm)
Insert cell
Insert cell
md`## Library`
Insert cell
topojson = require("topojson-client@3")
Insert cell
d3 = require("d3@6", "d3-geo-projection@2")
Insert cell
turf = require("@turf/turf@5")
Insert cell
import { legendCircle } from "@harrystevens/circle-legend";
Insert cell
import {legend} from "@d3/color-legend"
Insert cell
import {slider, radio, text, select, color} from '@jashkenas/inputs'
Insert cell
import {Input, Button, Range, bind, html} from "@observablehq/inputs"
Insert cell
md`## Reference
* https://observablehq.com/@d3/diverging-scales
* https://observablehq.com/@harrystevens/circle-legend
`
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