chart = {
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]);
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();
}