Public
Edited
Dec 16
14 stars
Insert cell
# Philippines Population per Region (Flubber animation + force layout)
Insert cell
Insert cell
chart = {
const make_annotations = d3_annotation.annotation().notePadding(10).type(type).annotations(annotations)
const svg = d3.create("svg").attr("viewBox", [0, 0, width+margin.left+margin.right, height + margin.top + margin.bottom]).style("background", "#efefe7")
//.style("background", "#1f1f1f")
const wrapper = svg.append("g").attr("transform", `translate(${margin.left}, ${margin.top})`).style("background","red")


// Creating Annotations
const annotation_group = wrapper.append("g")
.attr("class", "annotation-group").attr("opacity", 0)
.call(make_annotations)

// X Axis
wrapper.append("g").call(xAxis)

// Y Axis
wrapper.append("g").attr('transform', `translate(50,0)`).call(yAxis)


// Color Legend
const x_legend = d3.scaleLinear()
.domain(color_scale.domain())
.rangeRound([0, 260]);
const color_legend = wrapper.append('g').style('font-size', "0.8rem").attr("transform", `translate(${width-300}, ${height/3 - 100})`)
const color_legend_label = color_legend.append("text").text("Population Count")
const color_legend_scale = color_legend.append('g').attr("transform", `translate(${0}, ${2})`)
color_legend_scale.selectAll("rect").data(d3.range( d3.extent(population_data)[0], d3.extent(population_data)[1], 1000000)).enter().append('rect').attr('x', d => x_legend(d)).attr("height", 10).attr("width", (260 / 15)).attr("fill", d => color_scale(d))

color_legend_scale.call(
d3.axisBottom(x_legend)
.tickFormat(v => `${parseFloat(v / 1000000).toFixed(0)}M`)
).call(g => g.select('.domain').remove()).call(g => g.selectAll('line').remove()).call(g => g.selectAll('text').attr("transform", `translate(${0}, ${8})`))


// Circle Legend
color_legend.append("g").attr("transform", `translate(${260/4}, ${50})`)
.call(circleLegend);

// Geo Projection
let projection = d3.geoMercator().translate([margin.left, margin.top]).fitSize([width, height], phl_regions2)
let geoGenerator = d3.geoPath().projection(projection)
let regions = wrapper.selectAll('region').data(nodes)

let areas = regions.enter().append("path").attr("class", "areas").attr("d", d => geoGenerator(d)).attr("fill", d => color_scale(d.properties.Population))
// Force Simualtion
const simulation = d3.forceSimulation();
simulation.nodes(nodes);
simulation.force("charge", d3.forceManyBody().strength(20))
.force('x', d3.forceX(d => x("All")))
.force('y', d3.forceY().y(d => y(parseFloat(d.properties.GrowthRate))))
.force('collide',d3.forceCollide().radius(function(d) { return d.r + 1}))
simulation.alpha(0.95).tick(5)
simulation.on('tick', () => {
areas.transition().ease(d3.easeLinear).attr('transform', function(d) {
return `translate(${d.x},${ d.y})`})
})

simulation.stop()
let inward = nodes.map((d,i) => {
return flubber.combine(flubber.splitPathString(geoGenerator(d)), circlePath(0,0, d.r),
{ single: true })
})

let outward = nodes.map((d,i) => {
return flubber.separate(circlePath(0,0, d.r), flubber.splitPathString(geoGenerator(d)),
{ single: true })
})


const morph = () => {
annotation_group.transition().delay(3000).ease(d3.easeLinear).attr('opacity', 1)
areas.transition().delay(2000).duration(1000).ease(d3.easeLinear).attr('transform', function(d) {
let node_centroid = geoGenerator.centroid(d.geometry)
return `translate(${ node_centroid[0]},${ node_centroid[1]})`}).attrTween('d', (d,i) => {return inward[i]}).on("end", () => {simulation.alpha(0.95).restart().on("end", changeBack2Map)})

const changeBack2Map = () => {
simulation.stop()
annotation_group.transition().delay(3000).ease(d3.easeLinear).attr('opacity', 0)
areas.transition().delay(3000).duration(1000).ease(d3.easeLinear).attr('transform', function(d) {
return `translate(${0},${0})`}).attrTween('d', (d,i) => {return outward[i]}).on('end', morph)
}

}
morph();

return svg.node()
}
Insert cell
// Create Annotations
annotations = {

const nec_annotations = ["Autonomous Region in Muslim Mindanao", "Region IV-A", "National Capital Region", "Region III"]
let nodes_ = nodes.filter(v => nec_annotations.includes(v.properties.ADM1_EN))
nodes_ = nodes_.map(v => {
let string_population = String((parseFloat(v.properties.Population) / 1000000).toFixed(2))

return {
note: {
label: `pop: ${string_population} M, growth rate: ${parseFloat(v.properties.GrowthRate).toFixed(2)}`,
title: v.properties.ADM1_EN,
bgPadding: {"top":15,"left":10,"right":10,"bottom":10},
orientation: "leftRight", align: "middle",
top:300,
wrap: 50
},
connector: {
end: "arrow",
},
x: x('All'),
y: y(v.properties.GrowthRate),
dy: 0,
dx: 100,
subject: {
radius: size_scale(v.properties.Population),
radiusPadding: 10
},
type:d3_annotation.annotationCalloutCircle
}
})
return nodes_
}
Insert cell
Insert cell
phl_regions2
Insert cell
nodes = {

// Create Projection
let projection =d3.geoMercator().fitSize([width, height], phl_regions2)
let geoGenerator = d3.geoPath().projection(projection)
let nodes_temp = phl_regions2.features

nodes_temp = nodes_temp.map(v => {
let node_centroid = geoGenerator.centroid(v.geometry)
return {...v, "cx": node_centroid[0], "cy":node_centroid[1], 'r': size_scale(v.properties.Population)}
})

let scaler = d3.scaleBand().domain(nodes_temp.sort((a, b) => a.cy - b.cy).map(v => v.properties.ADM1_EN)).range([50, height-10 ])


nodes_temp = nodes_temp.map(v => {
let node_centroid = geoGenerator.centroid(v.geometry)

let path2center = d3.path();
path2center.moveTo(v.cx, v.cy);
path2center.bezierCurveTo(v.cx, v.cy,v.cx, scaler(v.properties.ADM1_EN), v.cx+400, scaler(v.properties.ADM1_EN))
return {...v, "path1":path2center, "label_pos_y": scaler(v.properties.ADM1_EN)}
})
return nodes_temp
}
Insert cell
phl_regions2 = FileAttachment("phl_regions2.geojson").json()
Insert cell
population_data = phl_regions2.features.map(d => parseInt(d.properties.Population))
Insert cell
growthrate_data = phl_regions2.features.map(d => parseFloat(d.properties.GrowthRate))
Insert cell
Insert cell
flubber = require("flubber")
Insert cell
d3_annotation = require("https://rawgit.com/susielu/d3-annotation/master/d3-annotation.min.js")
Insert cell
import { legendCircle } from "@harrystevens/circle-legend";

Insert cell
Insert cell
function circlePath(x, y, radius) {
var l = `${x - radius},${y}`,
r = `${x + radius},${y}`,
pre = `A${radius},${radius},0,1,1`;
return `M${l}${pre},${r}${pre},${l}`;
}
Insert cell
size_scale = d3.scaleLinear().domain([0, d3.extent(population_data)[1]]).range([0,50])
Insert cell
color_scale = d3.scaleLinear()
.domain(d3.extent(population_data))
.range(['#f768a1','#7a0177'])
.interpolate(d3.interpolateRgb.gamma(2.2))
Insert cell
type = d3_annotation.annotationCalloutCircle
Insert cell
legend = legendCircle()
.scale(size_scale)
.tickValues([5000000, 10000000, 20000000])
.tickFormat((d, i, e) => i === e.length - 1 ? d + " bushels of hay" : d)
.tickSize(2); // defaults to 5
Insert cell
// Create a legend generator
circleLegend = legendCircle()
.scale(size_scale)
.tickValues([5000000, 10000000, 20000000])
.tickFormat((d, i, e) => `${parseFloat(d / 1000000).toFixed(0)}M Population`)
.tickSize(2); // defaults to 5
Insert cell
x = d3.scaleBand().domain(["All"]).range([0, width-margin.left-margin.right]).padding(1)
Insert cell
xAxis = g => g
.call(d3.axisTop(x).ticks(1))
.call(g => g.select('.domain').remove())
.call(g => g.select('text').remove())
.call(g => g.selectAll('.tick line').remove())
Insert cell
y = d3.scaleLinear()
.domain(d3.extent(nodes.map(v => parseFloat(v.properties.GrowthRate))))
.range([margin.top, height-margin.bottom])

Insert cell
yAxis = g => g
.call(d3.axisLeft(y)
.tickFormat(d => `${d} `).ticks(10))
.call(g => g.select('.domain').remove())
.call(g => g.append('text')
.attr('x', 50)
.attr('y', margin.top + 50)
.attr('font-weight', 'bold')
.attr('font-size', "0.87rem")
.attr('fill', 'currentColor')
.attr('text-anchor', 'end')
.text('Growth Rate'))
Insert cell
width = 900
Insert cell
height = 1200
Insert cell
labels_size = 400
Insert cell
labels_pos_x = 1000
Insert cell
margin = ({"top":50, "bottom":50, "left":10, "right":10})
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