Published
Edited
Mar 23, 2022
1 star
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
chart = {

// 1. Prepare the data
let yearlyDeathProb = (new Array(17).fill(0))
.concat(d3.quantize(d3.interpolateBasis(extendedLifeTable.map(d => d[gender])),85)
.map(d => +d.toFixed(6)))

let medianLifeExp = 0;
let yearlyData = [];
const updateData = (age) =>{
yearlyData = [];
medianLifeExp = 0;
let cumulProb = 1;
for(let i=0; i<yearlyDeathProb.length; i++){
if(i <= age){
yearlyData.push(1)
}
else{
cumulProb = cumulProb * (1 - yearlyDeathProb[i]);
yearlyData.push(Math.round(cumulProb*10000)/10000);
if(medianLifeExp === 0 && cumulProb < 0.51){
medianLifeExp = i;
}
}
}
yearlyData.splice(101)
}
updateData(yourAge[0]);
// 2. SVG container
const svg = d3.create("svg")
.attr("width", width)
.attr("height", 400)
.attr("viewBox", [0, 0, width, 400])
.attr("style", "max-width: 100%; height: auto; height: intrinsic;")
.on("touchstart", event => event.preventDefault())
.style("font-family","Helvetica");

// 3. Axes
const xAxis = d3.axisBottom()
.scale(xScale)
.tickSizeInner(-400 + margin.top + margin.bottom)
.tickSizeOuter(0);

svg.append('g')
.attr('transform', `translate(0, ${400 - margin.bottom})`)
.call(xAxis)
.attr("color","rgb(0,0,0,0.8)")
.style("stroke-dasharray", "15 15");

svg.append('text')
.attr("x",width - 30)
.attr("y",400 - 10)
.text("Age")
.attr("fill","rgb(0,0,0,0.8)")
.attr("font-size",12);
const yAxis = d3.axisLeft()
.scale(yScale)
.ticks(5)
.tickSizeInner(-width + margin.left + margin.right)
.tickSizeOuter(0);

svg.append('g')
.attr('transform', `translate(${margin.left}, 0)`)
.call(yAxis);

svg.append('text')
.attr("x", margin.left - 15)
.attr("y", margin.top - 10)
.text("% chance")
.attr("fill","rgb(0,0,0,0.8)")
.attr("font-size",12);

// 4. Legend
svg.append("rect")
.attr("x", width - margin.right - 140)
.attr("y", 20)
.attr("width", 10)
.attr("height", 10)
.attr('stroke', 'rgba(26, 137, 23, 1)')
.attr('fill', 'rgba(26, 137, 23, 0.3)')

svg.append('text')
.attr("x", width - margin.right - 125)
.attr("y", 30)
.text("% chance you are alive")
.attr("fill","rgb(0,0,0,0.8)")
.attr("font-size",12);

// 5. Area chart
const areaFunc = d3.area()
.x((d,i) => xScale(i))
.y1(d => yScale(d*100))
.y0(yScale(0));
svg.append("path")
.datum(yearlyData)
.classed("areaChart",true)
.attr('d', areaFunc)
.attr('stroke', 'rgba(26, 137, 23, 1)')
.attr('fill', 'rgba(26, 137, 23, 0.3)')

const updateChart = (data) => {
svg.selectAll(".areaChart")
.datum(data)
.transition(5000)
.attr('d', areaFunc)
}

// 6. "Your current age" label (draggable)
const dragstarted = (event) => {
svg.select("#yourAgeBorder").attr("stroke", "rgba(26, 137, 23, 0.7)");
}
const dragended = () => {
svg.select("#yourAgeBorder").attr("stroke", "rgba(0,0,0,0.1)");
updateChart(yearlyData);
svg.selectAll('.hoverLine, .hoverPoint, .hoverTextBoxShadow, .hoverTextBox, .hoverText1,.hoverText2, .hoverText3')
.transition()
.duration(800)
.style("opacity",1)
}
const dragged = (event, d) => {
yourAge[0] = Math.round(xScale.invert(event.x + 80));
yourAge[0] = Math.max(yourAge , 15);
yourAge[0] = Math.min(yourAge , 85);
updateData(yourAge[0]);
svg.selectAll('.hoverLine, .hoverPoint, .hoverTextBoxShadow, .hoverTextBox, .hoverText1,.hoverText2, .hoverText3')
.style("opacity",0)
updateYourAge(yourAge[0]);
}
const drag = d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended);
svg.append('circle')
.attr("id", "yourAgeCircle")
.attr('r', '4')
.attr('fill', 'rgba(26, 137, 23, 1)')
.attr('stroke', 'white');
svg.append('rect')
.attr("id", "yourAgeBorder")
.attr("width", 142)
.attr("height", 37)
.attr("stroke", "rgb(0,0,0,0.06)")
.attr("stroke-width",2)
.attr("fill","transparent")
.attr("rx",6)
svg.append('rect')
.attr("id", "yourAgeRect")
.attr("width", 140)
.attr("height", 35)
.attr("stroke", "rgb(0,0,0,0.12)")
.attr("fill","white")
.attr("rx",5)
.style("cursor","grab")
.call(drag);
svg.append("text")
.attr("id", "yourAgeText")
.attr("fill","rgba(26, 137, 23, 1)")
.style("font-size",14)
.style("text-anchor","end")
.style("cursor","grab")
.call(drag);

const updateYourAge = (age) => {
svg.selectAll("#yourAgeCircle")
.attr('cx', xScale(age))
.attr('cy', yScale(yearlyData[age]*100))
svg.selectAll("#yourAgeRect")
.attr("x", xScale(age) - 150)
.attr("y", yScale(yearlyData[age]*100) - 15)
svg.selectAll("#yourAgeBorder")
.attr("x", xScale(age) - 151)
.attr("y", yScale(yearlyData[age]*100) - 16)
svg.selectAll("#yourAgeText")
.text("Your current age: " + age)
.attr("x", xScale(age) - 17)
.attr("y", yScale(yearlyData[age]*100) + 5)
}
updateYourAge(yourAge[0])

// 7. Median line & tooltips
svg.append('line').classed('hoverLine', true)
.attr('y1', margin.top)
.attr('y2', 400 - margin.bottom)
.attr('stroke', 'rgba(26, 137, 23, 1)');

svg.append("rect").classed('hoverTextBox', true)
.attr("width", 140)
.attr("height", 75)
.attr("stroke", "rgb(0,0,0,0.12)")
.attr("fill","white")
.attr("rx",5);

svg.append("rect").classed('hoverTextBoxShadow', true)
.attr("width", 142)
.attr("height", 77)
.attr("stroke", "rgb(0,0,0,0.06)")
.attr("stroke-width",2)
.attr("fill","transparent")
.attr("rx",6);
svg.append('circle').classed('hoverPoint', true)
.attr('r', '2')
.attr('fill', 'rgba(26, 137, 23, 1)')
.attr('stroke', 'white');

for(let i=1; i<4; i++){
svg.append("text").classed('hoverText'+i, true)
.attr("fill","rgb(0,0,0,0.8)")
.style("font-size",14)
.style("text-anchor","end");
}

const pointermoved = (event) => {
let age = medianLifeExp;
if(event){
age = Math.round(xScale.invert(d3.pointer(event)[0]));
age = Math.min(age, 100);
if(age <= yourAge){
age = medianLifeExp;
}
}
svg.select('.hoverLine')
.attr('x1', xScale(Math.max(age , yourAge)))
.attr('x2', xScale(Math.max(age , yourAge)))
;
svg.select('.hoverPoint')
.attr('cx', xScale(age))
.attr('cy', yScale(yearlyData[age]*100));

if(yourAge < age){
svg.select('.hoverTextBoxShadow')
.attr("x", Math.max(xScale(age) - 146 , 4))
.attr("y", Math.min(yScale(yearlyData[age]*100) - 21, (400 - 71)))
svg.select('.hoverTextBox')
.attr("x", Math.max(xScale(age) - 145 , 5))
.attr("y", Math.min(yScale(yearlyData[age]*100) - 20, (400 - 70)));
svg.select('.hoverText1')
.text("You have about "+Math.round(yearlyData[age]*100) +"%")
.attr("x", Math.max(xScale(age) - 10 , 140))
.attr("y", Math.min(yScale(yearlyData[age]*100) , (400 - 50)));
svg.select('.hoverText2')
.text("chance of living")
.attr("x", Math.max(xScale(age) - 10 , 140))
.attr("y", Math.min(yScale(yearlyData[age]*100) + 20 , (400 - 30)));
svg.select('.hoverText3')
.text("to at least "+age)
.attr("x", Math.max(xScale(age) - 10 , 140))
.attr("y", Math.min(yScale(yearlyData[age]*100) + 40 , (400 - 10)));
}
}
svg.on("pointerenter pointermove", pointermoved);
pointermoved(null);
svg.call(style);
return svg.node();
}
Insert cell
Insert cell
margin = ({
top: 40,
right: 20,
bottom: 40,
left: 40,
})
Insert cell
xScale = {
const xScale = d3.scaleLinear()
.domain([0,100])
.range([margin.left, width - margin.right]);
return xScale;
}
Insert cell
yScale = {
const yScale = d3.scaleLinear()
.domain([0, 100])
.range([400 - margin.bottom, margin.top]);
return yScale;
}
Insert cell
Insert cell
extendedLifeTable = {
let lifeTable = lifeTables.filter(d => d.Location === country);
const deathProb82 = lifeTable[lifeTable.length - 1].All;
const deathProb82M = lifeTable[lifeTable.length - 1].Male;
const deathProb82F = lifeTable[lifeTable.length - 1].Female;
let extendedLifeTable = lifeTable.concat([
{age: 87, All: 1.8 * deathProb82, Male: 1.8 * deathProb82M, Female: 1.8 * deathProb82F},
{age: 92, All: 2.8 * deathProb82, Male: 2.8 * deathProb82M, Female: 2.8 * deathProb82F},
{age: 97, All: 4.4 * deathProb82, Male: 4.4 * deathProb82M, Female: 4.4 * deathProb82F},
{age: 102, All: 6.1 * deathProb82, Male: 6.1 * deathProb82M, Female: 6.1 * deathProb82F}]);
return extendedLifeTable
}
Insert cell
countryList = [... new Set(lifeTables.map(d => d.Location))]
Insert cell
style1 = html`<style> .tick line{} </style>`
Insert cell
style = svg => {svg.selectAll(".tick line").style("stroke","rgba(0,0,0,0.1)")}
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