Published
Edited
Sep 6, 2020
Importers
7 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
chart = {
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height])

svg.append("rect")
.attr("width", "100%")
.attr("height", "100%")
.attr("class", "svgBackground");
//attaching the standard mercator map as a .jpg
svg.append("image")
.attr("width", "100%")
.attr("height", mapHeight)
.attr("xlink:href", await FileAttachment("worldMap.JPG").url());
//a blue rectangle - water for the cartogram
svg.append("rect")
.attr("class", "cartogram")
.attr("width", "100%")
.attr("height", mapHeight)
.attr("fill","#bbe4ed");
//brown rectangles - landmass for the cartogram
svg.append("g")
.selectAll("rect#land")
.data(mapData)
.join("rect")
.attr("id", "land")
.attr("class", "cartogram")
.attr("fill","#daccbc")
.attr("stroke","#eadccc")
.attr("height", rowScale(1))
.attr("width", colScale(1))
.attr("x", d => colScale(d.Y) - colScale(0.5))
.attr("y", d => rowScale(d.X) - rowScale(0.5));
//white rectangles - islands and bins to hold the text labels for the cartogram
svg.append("g")
.selectAll("rect#boxes")
.data(countryData)
.join("rect")
.attr("id", "boxes")
.attr("class", "cartogram")
.attr("fill", "white")
.attr("width", colScale(1)-4)
.attr("height", rowScale(1)-4)
.attr("x", d => colScale(d.Col)-colScale(.5) + 2)
.attr("y", d => rowScale(d.Row)-rowScale(.5) + 2);
//text labels initalized with every variable text property - skew, weight, spacing, colour, xy location
let labels = svg.append("g")
.attr("font-size", "12px")
.attr("text-anchor", "middle")
.attr("font-family", "Roboto")
.selectAll("text")
.data(countryData)
.join("text")
.attr("id", "label")
.attr("x", d => colScale(d.Col) + 1)
.attr("y", d => rowScale(d.Row) + 4)
.attr("transform-origin", d => (colScale(d.Col) + 1) + " " + (rowScale(d.Row) + 4))
.attr("transform", d => `skewX(${inflationScale(d.Inflation)})`)
.attr("font-weight", d => gdpFontScale(d.GDPperCapita))
.attr("letter-spacing", d => gdpPctScale(d.PctGDPgrowth))
.attr("fill", d => regionScale(d.Continent))
.text(d => d.CountryCode)
.append("title").text(d => d.CountryCode + " " + d.ShortName +
"\n " + numberWithCommas(d.GDPperCapita.toFixed(0)) + " GDP per capita (USD)" +
"\n " + d.Inflation.toFixed(1) + " Inflation (%) " +
"\n " + d.PctGDPgrowth.toFixed(1) + " GDP growth (%) " );
yield svg.node(); ////// end of main code here //////
///### TITLE AND LEGEND ###///
const dataSourceTag = svg.append("a")
.attr("href", "https://data.worldbank.org")
.append("text")
.attr("class", "other")
.attr("x", 680)
.attr("y", mapHeight + 20)
.style("font-size", "14px")
.text("Data source: data.worldbank.org, Aug. 2013")
const legend1 = svg.append("g")
.attr("transform", `translate(10, ${mapHeight + 20})`)
.attr("class", "other")
.style("font-size", "16px");
legend1.append("text")
.text("GDP per Capita (USD)")
.attr("font-weight", 700);
legend1.selectAll("text#legend1")
.data(gdpLegend)
.join("text")
.text(d => d.text)
.attr("font-family","Roboto")
.attr("y", (d, i) => `${i * 1.5 + 1.75}em`)
.attr("font-weight", d => d.value)
const legend2 = svg.append("g")
.attr("transform", `translate(210, ${mapHeight + 20})`)
.attr("class", "other")
.style("font-size", "16px");
legend2.append("text")
.text("Percent GDP Growth")
.attr("font-weight", 700);
legend2.selectAll("text#legend2")
.data(gdpPctLegend)
.join("text")
.text(d => d.text)
.attr("y", (d, i) => `${i * 1.5 + 1.75}em`)
.attr("letter-spacing", d => d.value)
const legend3 = svg.append("g")
.attr("transform", `translate(500, ${mapHeight + 20})`)
.attr("class", "other")
.style("font-size", "16px");
legend3.append("text")
.text("Inflation")
.attr("font-weight", 700);
legend3.selectAll("text#legend3")
.data(inflationLegend)
.join("text")
.text(d => d.text)
.attr("y", (d, i) => `${i * 1.2 + 1.75}em`)
.attr("transform-origin", (d, i) => `10 ${i * 22.5 + 1.75}`)
.attr("transform", d => `skewX(${d.value})`)
///### * end * title+ legend ###///
}
Insert cell
Insert cell
// the main data actually being used to set variable properties of text, ect.
countryData = {
//the JSON data has many unneeded values, this script loads the file and and converts to required datatypes
const data = await FileAttachment("WorldBankStats2010@2.json").json()

return d3.range(data.length).map(function(d) {
return {//Year: "2010"
//YearCode: "YR2010"
//FullName: "Albania"
CountryCode: data[d].CountryCode,
ShortName: data[d].ShortName,
//AvgLong: "65.00"
Row: +data[d].Row,
Col: +data[d].Col,
Region: data[d].Region,
Continent: data[d].Continent,
Population: +data[d].Population,
//PercentFemale: "49.92341503"
//LifeExpectancy: "76.90095122"
Inflation: +data[d].Inflation,
//HealthSpendPctGDP: "6.547297762"
GDPperCapita: +data[d].GDPperCapita,
PctGDPgrowth: +data[d].PctGDPgrowth,
TradeBalance: +data[d].TradeBalance,
//PctEmploy: "51.79999924"
//PctChildEmployment: ""
//GovtDebt: ""
//PopOver65: "9.658975297"
//PctHIV: ""
Long: (+data[d].Long > -169.2)? +data[d].Long : +360 - Math.abs(data[d].Long), // see lonScale below
Lat: +data[d].Lat
}
})}
Insert cell
// data for creating the equal area cartogram
mapData = {
const data = await FileAttachment("landGrid@2.json").json()

return d3.range(data.length).map(function(d) {
return { X: +data[d].X, Y: +data[d].Y }
})}
Insert cell
//script that changes text attributes when the checkboxes are clicked
ui_update = {
// text label attributes changing
d3.selectAll("text#label")
.each(function(d){
d3.select(this)
.transition()
.duration(200)
.attr("letter-spacing", d => state.includes("space")? gdpPctScale(d.PctGDPgrowth) : "normal")
.attr("fill", d => state.includes("color")? regionScale(d.Continent) : "black")
.attr("font-weight", d => state.includes("weight")? gdpFontScale(d.GDPperCapita) : 300)
.attr("x", d => state.includes("map")? colScale(d.Col) + 1 : lonScale(d.Long))
.attr("y", d => state.includes("map")? rowScale(d.Row) + 4 : latScale(d.Lat))
.attr("transform-origin", d =>
state.includes("map")?
`${colScale(d.Col) + 1} ${rowScale(d.Row) + 4}` :
`${lonScale(d.Long)} ${latScale(d.Lat)}`)
.attr("transform", d =>
state.includes("slope")?
`skewX(${inflationScale(d.Inflation)})` :
"skewX(0)")
})
// cartogram attributes (rectangles) hiding and showing
d3.selectAll("rect.cartogram")
.each(function(d){
d3.select(this)
.transition()
.duration(200)
.attr("fill-opacity", state.includes("map")? 1 : 0)
.attr("stroke-opacity", state.includes("map")? 1 : 0)
})
}
Insert cell
Insert cell
rowScale = d3.scaleLinear()
.domain([d3.min(mapData, d => d.X) - 1, d3.max(mapData, d => d.X) + 1])
.range([0, mapHeight]);
Insert cell
colScale = d3.scaleLinear()
.domain([d3.min(mapData, d => d.Y) - 1, d3.max(mapData, d => d.Y) + 1])
.range([0, width]);
Insert cell
latScale = d3.scaleLinear()
.domain([-90, 90])
.range([mapHeight, 0])
Insert cell
//The image used for the underlying mercator map does not have the expected -180 on the far left to +180 on the far right. Instead the map's left edge is -169.2; at 0.97*width the map hits +/-180; and at the far right it has fully looped back to -169.2 degrees. This can be thought of as a x-shift of 10.2 in the positive x-axis. Thus we can make a new scale ranging from [-169.2 to (+180 + 10.2)] and shifting any coutries that fell into the (-180 to -169.2) range be in the (+180 to +190.8) range.
lonScale = d3.scaleLinear()
.domain([-169.2, (180 + 10.2)])
.range([0, width])
Insert cell
gdpFontScale = d3.scaleThreshold()
.domain([1, 500, 2000, 10000])
.range([100, 300, 500, 700, 900])
//quantiles of the data Q(0) = 0; Q(0.25) = 382.7; Q(0.5) = 1889.0; Q(0.75) = 6617.1; Q(1.0) = 63036.4;
Insert cell
//creating an array of objects that have a) text I want to use for the legend and b) the variable properties that correspond to the legend entry
gdpLegend = {
var array = gdpFontScale.domain().map(function(d, i, self){
if (i == 0) {return `Less than $${self[i + 1]}`}
else if (i == self.length - 1) {return `More than $${d}`}
else {return `$${d} - $${self[i + 1]}`}
})
array.unshift("Undefined")
var obj = array.map(function(d, i){return {"value": gdpFontScale.range()[i], "text": d}})
return obj;
}
Insert cell
inflationScale = d3.scaleThreshold()
.domain([0, 2, 5, 10, 20])
.range([15,0,-5,-10,-20,-30])
//quantiles of the data Q(0) = -4.9; Q(0.25) = 1.0; Q(0.5) = 3.6; Q(0.75) = 8.5; Q(1.0) = 45.9;
Insert cell
//creating an array of objects that have a) text I want to use for the legend and b) the variable properties that correspond to the legend entry
inflationLegend = {
var array = inflationScale.domain().map(function(d, i, self){
if (i == self.length - 1) {return `More than ${d}.0 %`}
else {return `${d}.0 - ${self[i + 1]}.0 %`}
})
array.unshift("Negative")
var obj = array.map(function(d, i){return {"value": inflationScale.range()[i], "text": d}})
return obj;
}
Insert cell
gdpPctScale = d3.scaleThreshold()
.domain([0,2,5,10])
.range(["-.1em","0",".1em",".25em",".5em"])
//quantiles of the data Q(0) = -7.9; Q(0.25) = 0.8; Q(0.5) = 3.3; Q(0.75) = 6.5; Q(1.0) = 27.0;
Insert cell
//creating an array of objects that have a) text I want to use for the legend and b) the variable properties that correspond to the legend entry
gdpPctLegend = {
var array = gdpPctScale.domain().map(function(d, i, self){
if (i == self.length - 1) {return `More than ${d}.0 %`}
else {return `${d}.0 - ${self[i + 1]}.0 %`}
})
array.unshift("Negative")
var obj = array.map(function(d, i){return {"value": gdpPctScale.range()[i], "text": d}})
return obj;
}
Insert cell
regionScale = d3.scaleOrdinal()
.domain(countryData.map(d => d.Continent) //get an array of all the continents
.filter(function(value, index, self) { //filter that array down to only the distinct values
return self.indexOf(value) === index;
}))
.range(d3.schemeCategory10)
// ["Indian","Europe","Africa","Oceania","Central America","South America","Middle East","North America","South-east // Asia","Central Asia"]
Insert cell
function numberWithCommas(x) {
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
Insert cell
Insert cell
d3 = require("d3@5")
Insert cell
import {swatches} from "@d3/color-legend"
Insert cell
import {checkbox} from "@jashkenas/inputs"
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
html`
<link href="https://fonts.googleapis.com/css2?family=Lato:wght@400;700&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@100;300;500;700;900&display=swap" rel="stylesheet">
<style>

.svgBackground {fill: #fffbeb;}

.title {
font: 24px "Lato", sans-serif;
fill: #263c54;
font-weight: 700;
}

.other {
font: 20px "Lato", sans-serif;
fill: #263c54;
font-weight: 400;
}
</style>`
Insert cell
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