Public
Edited
May 23, 2023
1 fork
1 star
Insert cell
Insert cell
Insert cell
Insert cell
startYear = 1980
Insert cell
minAffected = 1000
Insert cell
overlap = 24
Insert cell
Insert cell
Insert cell
radialOrderedChart = {

const width = 700, height = 700
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, width, height])
.attr("style", "max-width: 100%; height: auto; height: intrinsic;")
.attr('font-family', 'Helvetica')

const sizeVar = 'affected_per_mil'

// aesthetic variables
const padding = 20,
innerRadius = 50,
maxRadius = width / 2 - padding,
maxCircleRadius = ((maxRadius - innerRadius) / (maxInOneYear * 2)) + overlap; // evenly divided by # of data points
const theta = 2 * Math.PI / years.length;
const rings = [20, 80, 100]
//const color = (type) => type === 'Flood' ? 'skyblue' : type === 'Drought' ? 'coral' : 'grey'

// Scales
const rScale = d3.scaleLinear()
.domain([0, maxInOneYear])
.range([innerRadius, maxRadius])


const sizeScale = d3.scaleSqrt()
.domain(d3.extent(chartData, d => d[sizeVar]))
.range([1, maxCircleRadius])

const arcScale = d3.scaleLinear()
.domain([0, d3.max(yearTotals, d => d[1] )])
.range([0, theta])
// utilities
const polarToX = (r, i) => r * Math.cos(i * theta - Math.PI/2 ) + width / 2
const polarToY = (r, i) => r * Math.sin(i * theta - Math.PI/2 ) + height / 2

function findIndex(data) {
return chartData.filter(d => d.year === data.year).indexOf(data)
}

// Define the arrowhead marker
svg.append("defs").append("marker")
.attr("id", "arrowhead")
.attr("markerWidth", 8)
.attr("markerHeight", 5)
.attr("refX", 0)
.attr("refY", 2.5)
.attr("orient", "auto")
.append("path")
.attr("d", "M 0 0 L 8 2.5 L 0 5 z")
.attr("fill", "darkgrey");

const yearSel = svg.selectAll('line')
.data(years)
.enter()

yearSel
.append('line')
.attr('x1', (d, i) => polarToX(innerRadius, i ) )
.attr('y1', (d, i) => polarToY(innerRadius, i ) )
.attr('x2', (d, i) => polarToX(maxRadius, i ) )
.attr('y2', (d, i) => polarToY(maxRadius, i ) )
.attr('stroke', d => d % 10 === 0 ? 'lightgrey' : '#F5F5F5')
.attr('stroke-width', d => d % 10 === 0 ? 1.5 : 1)
.attr('stroke-dasharray', '3 3')

const ringSel = svg.selectAll('ring')
.data(rings)
.enter()

ringSel
.append('circle')
.attr('cx', width / 2)
.attr('cy', height / 2)
.attr('r', d => rScale(d) )
.attr('fill', "none")
.attr('stroke', 'grey')
.attr('stroke-dasharray', '2 2')
ringSel
.append('text')
.attr('x', d => width / 2 + rScale(d) )
.attr('y', (d, i) => height / 2)
.attr('font-size', 12)
.attr('text-anchor', 'middle')
.attr('font-weight', 'bold')
.text(d =>d.toLocaleString('en') )

const arcGenerator = d3.arc()
.innerRadius(rScale(200))
.outerRadius(rScale(200) + 15)
.startAngle((d) => years.indexOf(d[0]) * theta )
.endAngle((d) => years.indexOf(d[0]) * theta + arcScale(d[1]) );
// paths
svg.selectAll('path')
.data(yearTotals)
.enter()
.append('path')
.attr('d', arcGenerator )
.attr('fill', 'lightgrey')
.attr('stroke', '#FFF')
.attr('transform', `translate(${width / 2},${height / 2})`)

const selected = (type) => dataFilter === 'All' || dataFilter === type
// circles
svg.selectAll('circle')
.data(chartData)
.enter()
.append("circle")
.attr('cx', (d, i) => polarToX(rScale(findIndex(d) ), years.indexOf(d.year) ) )
.attr('cy', (d, i) => polarToY(rScale(findIndex(d) ), years.indexOf(d.year) ) )
.attr('r', d => sizeScale(d[sizeVar]))
.attr('fill', d => selected(d.type) ? colorScale(d.type) : 'lightgrey')
.attr('stroke', d => selected(d.type) ? colorScale(d.type) : 'lightgrey')
.attr('fill-opacity', d => selected(d.type) ? .5 : .1)
.attr('stroke-width', .5)


yearSel
.append('text')
.attr('x', (d, i) => polarToX(maxRadius, i ))
.attr('y', (d, i) => polarToY(maxRadius + 5, i ))
.attr('text-anchor', 'middle')
.attr('font-size', d => d % 10 === 0 ? 14 : 10)
.attr('font-weight',d => d % 10 === 0 ? 600 : d % 5 === 0 ? 600 : 400)
.attr('opacity',d => countPerYear[d] >= 1 ? 1 : .2 )
.text(d =>d )

svg.append("line")
.attr("x1", width / 2)
.attr("y1", height / 2 - maxRadius + 120 )
.attr("x2", width / 2)
.attr("y2", height / 2 - maxRadius + 80 )
.attr("stroke", "darkgrey")
.attr("stroke-width", 2)
.attr("marker-end", "url(#arrowhead)");

svg.append('text')
.attr('x', width / 2)
.attr('y', height / 2 - maxRadius + 52)
.attr('text-anchor', 'middle')
.attr('stroke', '#FFF')
.attr('paint-order', 'stroke fill')
.attr('font-size', 12)
.text('Time')

return svg.node()
}
Insert cell
chartData = {

return rawData
.filter(d => ['Flood', 'Storm', 'Drought'].includes(d.type) )
.sort((a, b) => b.affected_per_mil - a.affected_per_mil)
.slice(0, 1000)
.sort((a,b) => a.year - b.year || a.month - b.month )
}
Insert cell
rawData = await FileAttachment("all_top_disasters@2.json").json()
Insert cell
threshold = d3.quantile(chartData.map(d => d.affected_per_mil), .75)
Insert cell
years = d3.range(startYear, 2023)
Insert cell
yearTotals = d3.rollups(chartData, v => d3.sum(v, d => d.affected_per_mil), d => d.year)
Insert cell
labelArray = {
const top = []
for (let disasterType of disasters) {
const top5 = chartData.filter(d => d.name !== 'unknown' && d.type == disasterType).sort((a, b) => b.affected - a.affected).slice(0, 1)
top.push(...top5)
}
return top
}


Insert cell
countPerYear = d3.rollups(chartData, v => v.length, d => d.year).reduce((a, v) => ({ ...a, [v[0]]: v[1]}), {})
Insert cell
maxInOneYear = d3.max(Object.values(countPerYear))
Insert cell
colorScale = {
const colors = ["#ee9b00", "#94d2bd","#005f73","#ca6702", "#0a9396", "#bb3e03", "#e9d8a6", 'tomato']
const disasters = ["Drought",
"Flood",
"Storm",
"Volcanic activity",
"Epidemic",
"Wildfire",
"Landslide",
"Extreme temperature"]
return d3.scaleOrdinal().domain(disasters).range(colors)
}
Insert cell
import {Swatches} from "@d3/color-legend"
Insert cell
monthData = await FileAttachment("disasters_by_month_type.json").json()
Insert cell
disasters = new Set(chartData.map(d => d.type))
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