Published
Edited
Apr 16, 2021
Insert cell
Insert cell
Insert cell
chart = {
//create: Given the specified element name, returns a single-element selection containing a detached element of the given name in the current document.
const svg = d3.create("svg")
//The viewBox attribute defines how an SVG scales up
//https://mathisonian.com/writing/easy-responsive-svgs-with-viewbox
//https://www.geeksforgeeks.org/svg-viewbox-attribute/
//https://webdesign.tutsplus.com/tutorials/svg-viewport-and-viewbox-for-beginners--cms-30844
.attr("viewBox",[0, 0, width, innerHeight + margin.top + margin.bottom])
.attr("font-family", "sans-serif")
.attr("font-size", 10)
//bg color for debugging the alignment
.style("background-color","#bdbdbd");
const clip = DOM.uid("clip");

svg.append("clipPath")
.attr("id", clip.id)
.append("rect")
.attr("x", margin.left)
.attr("y", margin.top)
.attr("width", width - margin.right)
.attr("height", innerHeight + margin.top + margin.bottom)
//bg color for debugging the alignment
// .style("background-color","black");
//Build X scales
const zoomTargetX = svg.append("g")
//.call allows a function to be called on the selection itself.
//https://www.d3indepth.com/selections/
.call(xAxis, xScale)
// .attr("class", "zoomBase")
// .transition().duration(2000);
// Build Y scales
//The <g> SVG element is a container used to group other SVG elements.
svg.append("g")
.call(yAxis)
.attr("class", "nozoom");
//Build square
//append "g" as container for all the squares
svg.append("g")
.attr("clip-path", clip)
.attr("transform", `translate( -37.5)`)
.attr("class", "clip");
//Selects all defined elements in the DOM and hands off a reference to the next step in the chain.
const heatMap = svg.select(".clip")
.selectAll("g")
.data(data.values)
//D3’s data join lets you specify exactly what happens to the DOM as data changes.
//https://observablehq.com/@d3/selection-join
//selection.join appends "g" as container of each row(country)
.join("g")
.attr("transform", (d,i) => `translate(0, ${yScale(data.countries[i])})`)
.attr("class", "countryRow")
//The selection.each() function in D3.js is used to call the particular function for each selected HTML elements. In function datum(d) and index(i) are given as the parameters. By using this method one can access parent and child data simultaneously.
//https://www.geeksforgeeks.org/d3-js-selection-each-function/
.each(function(e,j){
d3.select(this)
.selectAll("rect")
.data(e)
.join("rect")
.on("mouseover", mouseover)
.on("mouseleave", mouseleave)
.attr("class", "squares")
.attr("x", (f,k) => xScale(data.years[k]))
.attr("width", (f,k) => xScale(data.years[k] + 1) - xScale(data.years[k]) - 1)
.attr("height", yScale.bandwidth() - 1)
.attr("fill", e => color(e))
.append("title")
.text( (f,k) => `In year ${data.years[k]}, ${data.countries[j]} income: GDP per capita in PPP (constant 2011 international) ${d3.format("$,.0f")(f)} `)
})//end each function
function zoomed(event) {
//create new scale ojects based on event
let new_xScale = event.transform.rescaleX(xScale)
zoomTargetX.call(xAxis, new_xScale);
// event.transform.y = 0;
// event.transform.x = Math.min(event.transform.x, 5);
// event.transform.x = Math.max(event.transform.x, (1-event.transform.k) * width + margin.right - margin.left );
// heatMap.selectAll("rect").attr("transform", event.transform.toString().replace(/scale\((.*?)\)/, "scale($1, 1)"));
heatMap.selectAll("rect").attr("x", (f,k) => new_xScale(data.years[k]))
.attr("width", (f,k) => new_xScale(data.years[k] + 1) - new_xScale(data.years[k])-1)

// heatMap.each(function(e,j){
// d3.select(this).selectAll("rect")
// .attr("x", (f,k) => new_xScale(data.years[k]))
// .attr("width", (f,k) => new_xScale(data.years[k] + 1) - new_xScale(data.years[k])-1)
// })
}//end function zoomed
//https://www.tutorialspoint.com/d3js/d3js_zooming_api.htm
//set up the zoom behavior
let zoom = d3.zoom()
.scaleExtent([1,28])
.extent([[margin.left - 37.5, 0], [width, innerHeight + margin.top + margin.bottom]])
.translateExtent([[margin.left - 37.5, 0], [width, innerHeight + margin.top + margin.bottom]])
.on("zoom", zoomed)
//call zoom behaviour on base element
svg.call(zoom)
// .transition()
// .duration(750)
// // .call(zoom.scaleTo, 4, [xScale(2040), 0]);


return svg.node();
}
Insert cell
Insert cell
Insert cell
import{legend} from "@d3/color-legend"
Insert cell
import { rangeSlider } from '@mootari/range-slider'
Insert cell
color = d3.scaleThreshold([2890, 8000, 25000], d3.schemeBlues[4])
Insert cell
Insert cell
Insert cell
Insert cell
areaHeight = 15
Insert cell
innerHeight = areaHeight * (data.countries.length)
Insert cell
xScale = d3.scaleLinear()
//https://observablehq.com/@d3/d3-extent
//String data is coerce to numbers

.domain(d3.extent(data.years))
// .domain(year_range)
.range([margin.left , width + margin.right]);
// .rangeRound([margin.left-37.5, width-margin.right])
Insert cell
xAxis = (g, xScale) => g
.attr("transform", `translate( -37.5,${margin.top})`)
.call(d3.axisTop(xScale).ticks(null, "d"))
//Remove the main bar of the axis
.select(".domain").remove()
Insert cell
yScale = d3.scaleBand()
.domain(data.countries)
//rangeRound give the bandwith as integer values: https://observablehq.com/@d3/d3-scaleband
.rangeRound([margin.top, margin.top + innerHeight])


Insert cell
yAxis = g => g.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(yScale).tickSize(0))
.call(g => g.select(".domain").remove())
//Labels of y axix is too long: rotate the labels of an axis
//https://www.d3-graph-gallery.com/graph/custom_axis.html
.selectAll("text")
.attr("transform", `translate(-40,-2.5)rotate(-45)`)
Insert cell
mouseover = function() {
d3.select(this)
.style("stroke", "black")
.style("stroke-width", 0.5)
.style("opacity", 1)
}
Insert cell
mouseleave = function() {
d3.select(this)
.style("stroke", "none")
.style("opacity", 1)
}
Insert cell
Insert cell
//https://stackoverflow.com/questions/9481497/understanding-how-d3-js-binds-data-to-nodes
//https://stackoverflow.com/questions/64334450/how-to-find-min-and-max-value-in-a-array-of-objects
Insert cell
Insert cell
yearMinMaxTest = d3.max(originData.columns, s => +s)

Insert cell
Object.values(originData[0]) //return an array of all value in one object
Insert cell
d3.max(Object.values(originData[0])) //return the maximum value of an array
Insert cell
originData.map(country => d3.max(Object.values(country))) //return an array of maximum value in each object
Insert cell
max = d3.max(originData.map(country => d3.max(Object.values(country))));
//What it does is iterate over the each object(countries), grabbing from each country the maximum of all the values. Then, it takes the maximum of these maxima and returns that.

Insert cell
yScale.bandwidth()
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