Public
Edited
Jun 7, 2024
2 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
legend = () => {
const wrapper = d3.create("div")
.attr("class", "legend");

wrapper.append("style").html(css);

wrapper.append("div")
.attr("class", "legend-title")
.text("Days with heat index at least 103°F, January through May");

const ticksize = 8;
const margin = {left: 42, right: 42, top: 1, bottom: 19 + ticksize};
const width = 400 - margin.left - margin.right;
const height = 12;
const svg = wrapper.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
svg.call(gradient);

const scale = d3.scaleLinear()
.domain([1, max])
.range([0, width]);

const g = svg.append("g")
.attr("transform", `translate(${[margin.left, margin.top]})`);

const rectGradient = g.append("rect")
.attr("fill", `url(#${gradient.id()})`)

.attr("width", width)
.attr("height", height);


const tick = g.selectAll(".tick")
.data([1, 30, 60, 90, max])
.join("g")
.attr("class", "tick")
.attr("transform", (d, i) => `translate(${scale(d)})`);

tick.append("line")
.attr("y2", height + ticksize)
tick.append("text")
.attr("x", (d, i, e) => i === e.length - 1 ? 10 : 0)
.attr("y", height + ticksize + 14)
.text((d, i, e) => `${d}${i === e.length - 1 ? " days" : ""}`);
return wrapper.node();
}
Insert cell
map = () => {
const wrapper = d3.create("div");

const svg = wrapper.append("svg")
.style("position", "absolute");
const canvas = wrapper.append("canvas")
.style("position", "absolute");

const annoSvg = wrapper.append("svg")
.style("position", "absolute");

const context = canvas.node().getContext("2d");
path.context(context);

const e = svg.append("path")
.datum(equator)
.attr("stroke", "#666")
.attr("stroke-dasharray", [5,5])
.attr("fill", "none");

const countries = svg.selectAll(".country")
.data(countriesGeo.features)
.join("path")
.attr("class", "country")
.attr("fill", "white")
.attr("fill-opacity", 0.9)
.attr("stroke", "#d5d5d5")
.attr("stroke-linejoin", "round");

const annotationG = annoSvg.selectAll(".anno-g")
.data(annotations)
.join("g")
.attr("class", "anno-g");

const annotationPolyline = annotationG.append("polyline");
const annotationNameG = annotationG.append("g")
.attr("class", "anno-name");

annotationNameG.append("text")
.attr("class", "bg")
.text(d => d.label);

annotationNameG.append("text")
.attr("class", "fg")
.text(d => d.label);

const annotationValueG = annotationG.append("g")
.attr("class", "anno-value");

annotationValueG.append("text")
.attr("class", "bg")
.text(d => `${d.days} days`);

annotationValueG.append("text")
.attr("class", "fg")
.attr("fill", d => color(Math.max(20, d.days)))
.text(d => `${d.days} days`);

return Object.assign(wrapper.node(), {
update(width, height) {
projection.fitSize([width, height], countriesGeo);

wrapper.style("height", `${height + 2}px`);
canvas.node().width = width;
canvas.node().height = height;
data.forEach(d => {
const [ x, y ] = projection([d.lon, d.lat]);
context.beginPath()
context.fillStyle = color(d.days);
context.arc(x, y, 2, 0, Math.PI * 2);
context.fill()
});

path.context(null);
svg
.attr("width", width)
.attr("height", height);
annoSvg
.attr("width", width)
.attr("height", height);
e.attr("d", path);

countries.attr("d", path);
annotationG
.attr("transform", d => `translate(${projection([d.lon, d.lat])})`);

annotationPolyline
.attr("points", d => d.leaderPoints);

annotationNameG
.attr("text-anchor", d => d.textAnchor)
.attr("transform", d => `translate(${d.leaderX + d.textX}, ${d.leaderY + d.textY})`)

annotationValueG
.attr("text-anchor", d => d.textAnchor)
.attr("transform", d => `translate(${d.leaderX + d.textX}, ${17 + d.leaderY + d.textY})`)
}
})
}
Insert cell
display.update(mapwidth, mapheight)
Insert cell
Insert cell
css = `
.legend {
display: table;
margin: 0 auto;
}
.legend .legend-title {
font-family: ${franklinLight};
font-size: 16px;
margin: 0 auto;
margin-bottom: 4px;
text-align: center;
}
.legend .rect-0 {
fill: ${color(0)};
}
.legend .tick line {
stroke: #202020;
shape-rendering: crispEdges;
}
.legend .tick text {
font-family: ${franklinLight};
font-size: 14px;
text-anchor: middle;
}

.anno-g polyline {
fill: none;
shape-rendering: crispEdges;
stroke: #202020;
}
.anno-g text {
font-family: ${franklinLight};
font-weight: bold;
}
.anno-g text.bg {
fill: none;
stroke: white;
stroke-linejoin: round;
stroke-opacity: 0.4;
stroke-width: 4px;
}
`
Insert cell
Insert cell
palette = [ "#ffdbcc", "#ff824d", "#cc3d00", "#4d1700" ]
Insert cell
interpolator = interpolatePalette(palette)
Insert cell
max = 120
Insert cell
color = days => {
if (days < 1) {
return "#fff";
}
else {
return d3.scaleSequential([1, max], interpolator)(days)
}
}
Insert cell
Insert cell
gradientN = 20
Insert cell
gradientValues = d3.range(1, (max - 1) + (max / gradientN), (max - 1) / (gradientN - 1))
Insert cell
gradientColors = gradientValues.map(color)
Insert cell
gradientStopScale = d3.scaleLinear(d3.extent(gradientValues), [0, 100])
Insert cell
gradientStops = gradientValues.map(gradientStopScale)
Insert cell
gradient = gradientLinear()
.id("legend-gradient")
.offsets(gradientStops)
.colors(gradientColors);
Insert cell
Insert cell
equator = ({
type: "MultiLineString",
coordinates: [d3.range(-180, 180 + precision, precision).map(lon => [lon, 0])]
})
Insert cell
precision = 90
Insert cell
projection = d3.geoNaturalEarth1()
Insert cell
path = d3.geoPath(projection)
Insert cell
Insert cell
aspect = 0.443
Insert cell
mapwidth = width
Insert cell
mapheight = width * aspect
Insert cell
Insert cell
annotations = (await FileAttachment("annotations.json").json())
.map(d => {
if (d.name.includes("San Antonio")) d.label = "San Antonio";
if (["ghs10715", "ghs6845"].includes(d.id)) d.label = d.name;
d.days = d.daily.filter(d0 => d0.heat_index >= 103).length
d.lon = +d.lon;
d.lat = +d.lat;
Object.assign(d, annotationPositions[d.name]);
d.leaderX = d3.extent(d.leaderPoints, c => c[0]).filter(n => n)[0] || 0;
d.leaderY = d3.extent(d.leaderPoints, c => c[1]).filter(n => n)[0] || 0;
return d;
})
Insert cell
annotationPositions = ({
"Accra": {
leaderPoints: [ [0, 0], [0, -68], [-23, -68] ],
textX: -4,
textY: 5,
textAnchor: "end"
},
"Bangkok": {
leaderPoints: [ [0, 0], [37.2, -26.9], [76.8, -26.9] ],
textX: 4,
textY: 5,
textAnchor: "start"
},
"Barranquilla": {
leaderPoints: [ [0, 0], [-50, 0] ],
textX: -4,
textY: 5,
textAnchor: "end"
},
"Dhaka": {
leaderPoints: [ [0, 0], [0, -54.3] ],
textX: -4,
textY: -20,
textAnchor: "start"
},
"Ho Chi Minh City": {
leaderPoints: [ [0, 0], [0, 115] ],
textX: 4,
textY: 12,
textAnchor: "end"
},
"Lagos": {
leaderPoints: [ [0, 0], [0, -105], [-34.9, -105] ],
textX: -4,
textY: 5,
textAnchor: "end"
},
"Mumbai": {
leaderPoints: [ [0, 0], [0, -72.9] ],
textX: 4,
textY: -20,
textAnchor: "end"
},
"San Antonio, Tex.": {
leaderPoints: [ [0, 0], [0, -20.4] ],
textX: 0,
textY: -20,
textAnchor: "middle"
},
"Singapore": {
leaderPoints: [ [0, 0], [0, 40], [-11.5, 40] ],
textX: -4,
textY: 4,
textAnchor: "end"
}
})
Insert cell
Insert cell
data = JSON.parse(JSON.stringify(cities))
.map(d => {
d.days = 0;
T.forEach(t => {
d.days += d[t];
});
return d;
})
.filter(d => d.days)
.sort((a, b) => d3.ascending(a.days, b.days))
Insert cell
Insert cell
T = thresholds.slice(thresholds.findIndex(d => d === threshold))
Insert cell
thresholds = Object.keys(cities[0]).slice(6)
Insert cell
cities = FileAttachment("cities@1.csv").csv({ typed: true })
Insert cell
countriesTopo = FileAttachment("countriesTopo.json").json()
Insert cell
countriesGeo = ({
type: "FeatureCollection",
features: topojson.feature(countriesTopo, countriesTopo.objects.ne_50m_admin_0_countries_lakes).features.filter(d => d.properties.ADMIN !== "Antarctica")
})
Insert cell
Insert cell
import { franklinLight } from "@climatelab/fonts@46"
Insert cell
import { gradientLinear } from "@climatelab/gradient@60"
Insert cell
import { interpolatePalette } from "@climatelab/roll-your-own-color-palette-interpolator@346";
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