Public
Edited
Feb 14, 2023
Insert cell
Insert cell
data = FileAttachment("countryIndices@2.json").json();
Insert cell
selectedData = data.map(d => ({
name:d.countryName,
population: d.Population.lastValue,
lifeExpectancy:d.LifeExpectancy.lastValue,
nurses:d.Nurses.lastValue,
physicians:d.Physicians.lastValue,
pollution:d.PM25AirPollution.lastValue,
waterFacilities: d.SafelyDrinkingWaterServicesPerc.lastValue,
childMortality:d.MortalityRateUnder5.lastValue,
t_population: d.Population.tempData,
t_lifeExpectancy:d.LifeExpectancy.tempData,
t_nurses:d.Nurses.tempData,
t_physicians:d.Physicians.tempData,
t_pollution:d.PM25AirPollution.tempData,
t_waterFacilities: d.SafelyDrinkingWaterServicesPerc.tempData,
t_childMortality:d.MortalityRateUnder5.tempData,
})).sort((a,b) => b.population - a.population);

Insert cell
Insert cell
Insert cell
Insert cell
keylv
Insert cell
viewof keylv = Inputs.select(keys, {label: "Variable shown:"})
Insert cell
linkedViewChart = {
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox",[0, 0, width, height]);

//SCATTER PLOT
const scatter = svg.append("g");

//scales
const mortalityScale = d3.scaleLinear()
.domain([0,d3.max(selectedData, d => d.childMortality !== 'NA' ? d.childMortality : NaN)]).nice()
.range([marginsc.left, width - marginsc.right]);
const lifeExpectScale = d3.scaleLinear()
.domain(d3.extent(selectedData, d => d.lifeExpectancy!== 'NA' ? d.lifeExpectancy : NaN)).nice()
.range([heightsc - marginsc.bottom, marginsc.top]);
const populationScale = d3.scaleSqrt()
.domain([0,d3.max(selectedData, d => d.population!== 'NA' ? d.population : NaN)])
.range([0, 40]);
//axis
const xAxisSc = g => g
.attr("transform", `translate(0,${heightsc - marginsc.bottom})`)
.call(d3.axisBottom(mortalityScale).tickSizeOuter(0))
.call(g => g.select(".tick:last-of-type text").clone()
.attr("x", 10)
.attr("y", -3)
.attr("text-anchor", "start")
.attr("font-weight", "bold")
.text("Child Mortality"));
scatter.append("g")
.call(xAxisSc);

const yAxisSc = g => g
.attr("transform", `translate(${marginsc.left},0)`)
.call(d3.axisLeft(lifeExpectScale))
.call(g => g.select(".tick:last-of-type text").clone()
.attr("x", -3)
.attr("y", -10)
.attr("text-anchor", "end")
.attr("font-weight", "bold")
.text("Life Expectancy"));
scatter.append("g")
.call(yAxisSc);

//Tooltip
const tooltip = d3.select('body')
.append('div')
.attr('id', 'barchart-tooltip')
.style('position', 'absolute')
.style('z-index', '1')
.style('visibility', 'hidden')
.style('padding', '10px')
.style('background', 'rgba(0,0,0,0.6)')
.style('border-radius', '4px')
.style('color', '#fff');
//data points
scatter.selectAll('circle')
.data(selectedData)
.join('circle')
.attr('cx', d => mortalityScale(d.childMortality))
.attr('cy', d => lifeExpectScale(d.lifeExpectancy))
.attr('r', d => populationScale(d.population))
.attr('fill', 'steelblue')
.attr('fill-opacity', 0.4)
.attr('stroke', d => d3.schemeCategory10[d.cluster])
.on("mouseover", function(e,d) {
tooltip
.html(`<b>Country</b>: ${d.name}`);
let tooltipWidth = tooltip.node().offsetWidth;
let tooltipHeight = tooltip.node().offsetHeight;
tooltip
.style("left", e.pageX - tooltipWidth/2 +'px')
.style("top", e.pageY-tooltipHeight - 10+'px')
.style('visibility', 'visible');
})
.on("mousemove", function(e,d) {
let tooltipWidth = tooltip.node().offsetWidth;
let tooltipHeight = tooltip.node().offsetHeight;
tooltip
.style("left", e.pageX - tooltipWidth/2 +'px')
.style("top", e.pageY-tooltipHeight - 10+'px')
.style('visibility', 'visible');
})
.on("mouseout", function(e,d) {
tooltip
.style('visibility', 'hidden');
})
.on('click', function(e,d,i) {
drawBarchart(d); //This is the big point!!
})

//BARCHART
const barchart = svg.append("g")
.attr('transform',`translate(0,${heightsc+padding})`);

//scales
const heightScale = d3.scaleLinear()
.domain([0,d3.max(selectedData, d => d3.max(d[keylv], d=> d !== 'NA'?d: NaN))]).nice()
.range([heightbc - marginbc.bottom, marginbc.top]);
const widthScale = d3.scaleBand()
.domain(d3.range(2001, 2021))
.range([marginbc.left, width - marginbc.right])
.padding(0.1);
//Axis
const xAxisBc = g => g
.attr("transform", `translate(0,${heightbc - marginbc.bottom})`)
.call(d3.axisBottom(widthScale)
.tickSizeOuter(0));

barchart.append("g")
.call(xAxisBc);

const yAxisBc = g => g
.attr("id","yAxisBc")
.attr("transform", `translate(${marginbc.left},0)`)
.call(d3.axisLeft(heightScale));
barchart.append("g")
.call(yAxisBc);
//This element is empty at the beginning, but it will be updated latter on the function
barchart.append("text")
.attr("id","title")
.attr("x", width - marginbc.right)
.attr("y", marginbc.top + 25)
.attr("text-anchor", "end")
.attr("font-size", "25px")

barchart.append("text")
.attr("id","keyName")
.attr("x", marginbc.left + 5)
.attr("y", marginbc.top)
.attr("text-anchor", "start")
.attr("font-weight", "bold")
.attr("font-size", "14px")
.text(keylv);
//This function updater the bar chart according to which dot has been clicked
function drawBarchart(d){
let max = d3.max(d[keylv], d=> d !== 'NA'?d: NaN) || 0.01;
heightScale.domain([0,max]).nice();
//Selection is important when updating
barchart.select('#yAxisBc')
.call(d3.axisLeft(heightScale));
//We set the text of the title
barchart.select('#title')
.text(d.name);

//With the standard join function, the rect will be updated
//a transition has been added to have a smooth update
barchart.selectAll('rect') //('.class')
.data(d[keylv])
.join('rect')
.attr('x', (d, i) => widthScale(2001+i))
.attr('y', heightScale(0))
.attr('width', widthScale.bandwidth())
.attr('height', 0)
.attr('fill-opacity', 0.7)
.attr('fill', 'purple')
.transition().duration(500)
//.delay((d,i)=>100*i)
.attr('x', (d, i) => widthScale(2001+i))
.attr('y', d => d!=='NA'? heightScale(d):heightScale(0))
.attr('width', widthScale.bandwidth())
.attr('height', d => d!=='NA'? heightScale(0) - heightScale(d):0)
.attr('fill-opacity', 0.7)
.attr('fill', 'purple');
}
// Specific to Observable
return svg.node();
}
Insert cell
Insert cell
Insert cell
function randomArray() {
//Creates a random array with length between 10 and 30 and values between 0 and 10.
let array = [];
const size = parseInt(Math.random() * 21 + 10);
for(let i=0;i<size;i++){
array[i] = Math.random() * 10;
}
return array;
}
Insert cell
randomArray();
Insert cell
barchartwithaxis = {
const height = 300;
const margin = ({top: 30, right: 20, bottom: 20, left: 40}); //margin object
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox",[0, 0, width, height]);
const xScale = d3.scaleBand()
.domain(d3.range(0, 30))
.range([margin.left, width - margin.right])
.padding(0.1);
const xAxis = g => g
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(xScale));
svg.append("g")
.call(xAxis);

const yScale = d3.scaleLinear()
.domain([0,10])
.range([height - margin.bottom, margin.top]);
const yAxis = g => g
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(yScale));
svg.append("g")
.call(yAxis);


while (true) {

//The original code with the standard join operator

svg.selectAll('rect')
.data(randomArray())
.join('rect')
.attr('x', (d,i) => xScale(i))
.attr('y', d => yScale(d))
.attr('width', xScale.bandwidth())
.attr('height', d => yScale(0) - yScale(d))
.style('fill', 'purple');

/*
//The code with the enter, update, exit functions that do exactly the same as the standard join operator
svg.selectAll('rect')
.data(randomArray())
.join(
enter => enter.append('rect'),
update => update,
exit => exit.remove()
)
.attr('x', (d,i) => xScale(i))
.attr('y', d => yScale(d))
.attr('width', xScale.bandwidth())
.attr('height', d => yScale(0) - yScale(d))
.style('fill', 'purple');
*/
/*
//The code with the enter, update, exit functions that do the same as the standard join operator
svg.selectAll('rect')
.data(randomArray())
.join(
enter => enter.append('rect')
.attr('x', (d,i) => xScale(i))
.attr('y', d => yScale(d))
.attr('width', xScale.bandwidth())
.attr('height', d => yScale(0) - yScale(d))
.style('fill', 'purple'),
update => update
.attr('x', (d,i) => xScale(i))
.attr('y', d => yScale(d))
.attr('width', xScale.bandwidth())
.attr('height', d => yScale(0) - yScale(d))
.style('fill', 'purple'),
exit => exit.remove()
);
*/

/*
//The code with the enter, update, exit functions that have different behaviours than the standard join operator
const t = svg.transition()
.duration(500);

svg.selectAll('rect')
.data(randomArray())
.join(
enter => enter.append('rect')
.attr('x', (d,i) => xScale(i))
.attr('y', d => yScale(0))
.attr('width', xScale.bandwidth())
.attr('height', 0)
.style('fill', 'green')
.transition(t)
.attr('y', d => yScale(d))
.attr('height', d => yScale(0) - yScale(d)),
update => update
.transition(t)
.attr('x', (d,i) => xScale(i))
.attr('y', d => yScale(d))
.attr('width', xScale.bandwidth())
.style('fill', 'purple')
.attr('height', d => yScale(0) - yScale(d)),
exit => exit
.transition(t)
.attr('y', d => yScale(0))
.attr('height', 0)
.style('fill', 'white')
.remove()
);
*/
// Specific to Observable
yield svg.node();
await Promises.tick(4000);
}
}
Insert cell
Insert cell
width = 900;
Insert cell
heightsc = 400;
Insert cell
heightbc = 300;
Insert cell
height = heightsc + heightbc + padding;
Insert cell
padding = 30;
Insert cell
marginsc = ({top: 20, right: 250, bottom: 20, left: 250});
Insert cell
marginbc = ({top: 20, right: 40, bottom: 20, left: 80});
Insert cell
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