Published
Edited
Dec 9, 2021
1 fork
8 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
chart = {
// Embeding and using and external SVG file
// https://observablehq.com/@mbostock/manipulating-svg-files
let text;
// Load contents of the SVG file as a string
if (myWidth < 459) {
text = await FileAttachment("ART-V06_mobile-300x460.svg").text();
} else {
text = await FileAttachment("ART-V06_desktop-460x460.svg").text();
}

// Convert that string into a Document by parsing it as SVG
const document = new DOMParser().parseFromString(text, "image/svg+xml");
// To extract the SVG element, we use document.documentElement. We also want to remove this SVG element from its document (the one created by DOMParser) so that when we return it from the cell it doesn’t have a parent, and thus the inspector will display it: if you return an element that already has a parent, the inspector will display it as Element {} to avoid removing the element from its current location.
const svg = d3.select(document.documentElement).remove();

// Adjusting SVG size
svg
.attr("id", "cartogram")
.attr("width", `${Math.min(460, myWidth - 20)}px`)
.style("display", "block")
.style("margin", "0 auto");

// ASSIGNING CLASSES
const mapGroup = svg.select("g#regions-group");
const regionsGroups = mapGroup.selectAll("g").attr("class", "region-group");
// Geolocation
if (detectLocation) {
const myCountryGroup = mapGroup
.select(`g#${myCountry}`)
.raise()
.classed("myCountry", true);
}
mapGroup
.selectAll("circle")
.attr("class", "auxCircles")
.style("opacity", 0)
.call((el) => setTooltipEvent(el));
mapGroup
.selectAll("polygon")
.attr("class", "region-hex")
.call((el) => setTooltipEvent(el));

// Show/hide context
const context = svg.select("g#context");
context.style("opacity", showContextGrid ? 1 : 0);

// DEFS elements: filter and patterns
const defs = svg.append("defs");
defs
.append("pattern")
.attr("width", 4)
.attr("height", 4)
.attr("patternUnits", "userSpaceOnUse")
.attr("id", "hatching")
.append("path")
.attr("d", "M-1,1 l2,-2 M0,4 l4,-4 M3,5 l2,-2")
.style("stroke", colorNeutral(100))
.style("strokeWidth", 1);

// Basic initialization styling (hiding things, etc)
svg
.select("g#countries-group")
.selectAll("circle")
.attr("class", "auxCircles")
.style("opacity", 0);

// DATA BINDING - to the groups
// http://jsfiddle.net/gyc4hg59/5/
const myMap = svg.selectAll(".region-group").datum(function (d) {
return {
region_code: d3.select(this).attr("id")
};
});
myMap.data(data, function (d, i) {
return d.region_code;
});
myMap.each(function (d, i) {
const myCircle = d3.select(this).select(".auxCircles");
const xPosit = +myCircle.attr("cx");
const yPosit = +myCircle.attr("cy");
// Adding x-y position to data, for convenience
d["x"] = xPosit;
d["y"] = yPosit;
// Binding infants per national births percent
d.ART_infants_per_national_births_percent = undefined;
for (let i = 0; i < data.length; i++) {
if (data[i].country_code === d.region_code) {
d.ART_infants_per_national_births_percent =
data[i].ART_infants_per_national_births_percent;
d.region_name = data[i].country_name;
d.national_births = data[i].national_births;
d.ART_infants = data[i].ART_infants;
break;
}
}
});

const geometryClass = ".region-hex";

// Hex: filling by data binded
regionsGroups
.select(geometryClass)
.style("fill", (d) => colorScale(d.ART_infants_per_national_births_percent))
.style("stroke", colorNeutral(0))
.style("stroke-width", "none");

// Texts: region codes
regionsGroups
.append("text")
.text((d) => d.region_code)
.attr("x", (d) => d.x)
.attr("y", (d) => d.y)
.style("font-size", isMobile ? "0.7rem" : "0.6rem")
.style("text-anchor", "middle")
.style("alignment-baseline", "central")
.style("dominant-baseline", "central")
.style("fill", colorNeutral(800))
.style("pointer-events", "none");
//.style("fill", colorNeutral(500));

// BASIC INTERACTIONS
regionsGroups.style("cursor", "auto");

// Lastly we extract the SVG element from the selection with selection.node and return it for display.
return svg.node();
}
Insert cell
Insert cell
setTooltipEvent = (el) => {
el.on("mouseover", function (ev, d) {
const ART_infants_per_national_births_percent = formatterNumber(
d.ART_infants_per_national_births_percent
);
const ART_infants = formatterNumber(d.ART_infants);
const national_births = formatterNumber(Math.round(d.national_births));
tooltip
.html(
`<div><h4>${d.region_name}</h4> <p>${
ART_infants_per_national_births_percent !== "NaN"
? locale[languageSelector].tooltipHTML(
ART_infants_per_national_births_percent,
ART_infants,
national_births
)
: locale[languageSelector].noData
}</p></div>`
)
.style("visibility", "visible");
})
.on("mousemove", function (ev, d) {
tooltip
.style("z-index", "300")
.style("top", function () {
const cartogramDimensions = d3
.select("#cartogram")
.node()
.getBoundingClientRect();
return `${
ev.pageY < cartogramDimensions.height / 2 + cartogramDimensions.top
? ev.pageY - 10
: ev.pageY - tooltip.node().getBoundingClientRect().height + 10
}px`;
})
.style("left", function () {
return `${
ev.pageX + 5 < myWidth / 2
? ev.pageX + 5
: ev.pageX - tooltip.node().getBoundingClientRect().width - 5
}px`;
})
.style("pointer-events", "none");
})
.on("mouseout", function () {
tooltip.html(``).style("visibility", "hidden");
});
}
Insert cell
Insert cell
Insert cell
Insert cell
dateId = new Date()
.toLocaleDateString("de", {
year: "numeric",
month: "numeric",
day: "numeric",
hour: "numeric",
minute: "numeric",
second: "numeric"
})
.replaceAll(/[:., ]/g, "")
Insert cell
data = d3.csv(
`https://data.civio.es/medicamentalia/access-ART/context_art_births_eu.csv?q=${dateId}`,
(d) => {
return {
country_name: locale[languageSelector].countries[d.country_code],
country_code: d.country_code,
ART_infants: +d.ART_infants,
national_births: +d.national_births_2017,
ART_infants: +d.ART_infants_2017,
ART_infants_per_national_births_percent:
d.ART_infants_per_national_births_percent === "n/a"
? undefined
: Math.round(+d.ART_infants_per_national_births_percent * 10) / 10,
note: d.note
};
}
)
Insert cell
Insert cell
maxWidth = 460
Insert cell
mediaQueryLimit = 459
Insert cell
myWidth = deviceSelection === "desktop" ? +Math.min(width, maxWidth) : 320 // 👈🏻 Just for testing
Insert cell
isMobile = myWidth < mediaQueryLimit
Insert cell
Insert cell
Insert cell
Insert cell
// Reference: https://observablehq.com/@planemad/mapbox-static-image-generator
/*geoIp = await fetch("https://freegeoip.app/json/")
.then((res) => res.json())
.catch(() => null)*/
Insert cell
Insert cell
// Trying another IP service, as the previous one failed due to a CORS problem at some point after publishing the article
geoIp2 = await fetch("https://freegeoip.live/json/")
.then((res) => res.json())
.catch(() => null)
Insert cell
myCountries = data.map((d) => d.country_code)
Insert cell
defaultCountry = "ES"
Insert cell
myCountry = {
if (geoIp2) {
const geoIpDetected = geoIp2.country_code;
const isOnTheMap = myCountries.includes(geoIpDetected);

// If the country detected is not in the map => mark our default country
const myIPCountry = isOnTheMap ? geoIpDetected : defaultCountry;
return myIPCountry;
} else {
// If the IP fetch fails, returns our default country
return defaultCountry;
}
}
Insert cell
Insert cell
colorScale = d3.scaleSequential(
[
d3.min(data, (d) => d.ART_infants_per_national_births_percent),
d3.max(data, (d) => d.ART_infants_per_national_births_percent)
],
["#F1FAFB", "#08A6BF"]
)
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// Typography
styles = html`
<link href="https://fonts.googleapis.com/css2?family=Lato:wght@400;700;900&display=swap" rel="stylesheet">
<style>

svg, #title {
font-family: 'Lato', sans-serif;
}
#cartogram {
cursor: pointer;
}

.region-group.myCountry > .region-hex {
stroke: black!important;
stroke-width: 4px;
}
.myCountry > text {
fill: black!important;
font-weight: 800;
}

.tooltip {
max-width: 150px;
}

.tooltip h4 {
margin: 0;
}

.tooltip p {
line-height: 20px;
margin: 14px 0;
}
#title {
font-size: 1.25rem;
text-align: center;
margin-top: 0;
}
</style>
`
Insert cell
Insert cell
Insert cell
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more