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

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