chart = {
const svg = d3.create("svg")
.attr("viewBox",[0, 0, width, innerHeight + margin.top + margin.bottom])
.attr("font-family", "sans-serif")
.attr("font-size", 10)
.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)
const zoomTargetX = svg.append("g")
.call(xAxis, xScale)
// 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();
}