(function () { const MOUNT_ID = "fig-1"; const DATA_URL = "assets/data/infra_lingdiv_ict.json"; const GROUP_ORDER = [ "Low-income", "Lower-middle-income", "Upper-middle-income", "High-income", ]; const GROUP_STYLE = { "Low-income": { color: "#C96A2E", symbol: "circle" }, "Lower-middle-income": { color: "#E39A5F", symbol: "square" }, "Upper-middle-income": { color: "#7B93B8", symbol: "diamond" }, "High-income": { color: "#254EFF", symbol: "triangle-up" }, }; const INK = "#35302E"; const RULE = "#e5ddcb"; const FONT_BODY = "Univers, Helvetica, Arial, sans-serif"; const FONT_HEAD = "'Tomato Grotesk', Helvetica, Arial, sans-serif"; function buildTraces(rows) { return GROUP_ORDER.map((group) => { const pts = rows.filter((r) => r.income_group === group); const style = GROUP_STYLE[group]; return { type: "scatter", mode: "markers", name: group, x: pts.map((r) => r.internet_users), y: pts.map((r) => r.num_living_languages), text: pts.map((r) => r.country), customdata: pts.map((r) => r.income_group), hovertemplate: "%{text}
" + "Internet users: %{x:.1f}%
" + "Living languages: %{y}
" + "%{customdata}", marker: { color: style.color, symbol: style.symbol, size: 10, opacity: 0.85, line: { color: INK, width: 0.6 }, }, }; }); } function buildAnnotations(rows) { return rows .filter((r) => r.annotate) .map((r) => { const color = GROUP_STYLE[r.income_group]?.color || INK; // Iceland is dense in the upper right; shift its label left. const iceland = r.country === "Iceland"; return { x: r.internet_users, y: Math.log10(r.num_living_languages), yref: "y", xref: "x", text: r.label, showarrow: false, xanchor: iceland ? "right" : "left", xshift: iceland ? -8 : 7, yshift: 4, font: { family: FONT_BODY, size: 11, color }, }; }); } function render(mount, rows) { mount.innerHTML = ""; const chartDiv = document.createElement("div"); chartDiv.style.width = "100%"; chartDiv.style.height = "100%"; chartDiv.style.flex = "1 1 auto"; chartDiv.style.alignSelf = "stretch"; mount.appendChild(chartDiv); mount.style.padding = "0.5rem"; mount.style.alignItems = "stretch"; mount.style.justifyContent = "stretch"; const traces = buildTraces(rows); const annotations = buildAnnotations(rows); const layout = { margin: { l: 56, r: 14, t: 10, b: 80 }, paper_bgcolor: "rgba(0,0,0,0)", plot_bgcolor: "rgba(0,0,0,0)", font: { family: FONT_BODY, color: INK, size: 12 }, xaxis: { title: { text: "% Individuals using the Internet (2023)", font: { family: FONT_HEAD, size: 13 }, standoff: 12, }, range: [-4, 104], gridcolor: RULE, zerolinecolor: RULE, linecolor: INK, showline: true, ticks: "outside", tickcolor: INK, tickfont: { family: FONT_BODY, size: 11 }, }, yaxis: { title: { text: "Number of living languages", font: { family: FONT_HEAD, size: 13 }, standoff: 8, }, type: "log", gridcolor: RULE, zerolinecolor: RULE, linecolor: INK, showline: true, ticks: "outside", tickcolor: INK, tickfont: { family: FONT_BODY, size: 11 }, }, legend: { orientation: "h", y: -0.22, x: 0.5, xanchor: "center", font: { family: FONT_BODY, size: 11 }, bgcolor: "rgba(0,0,0,0)", }, hoverlabel: { bgcolor: "#ffffff", bordercolor: INK, font: { family: FONT_BODY, size: 12, color: INK }, }, annotations, }; const config = { displaylogo: false, responsive: true, modeBarButtonsToRemove: [ "lasso2d", "select2d", "autoScale2d", "toggleSpikelines", "hoverClosestCartesian", "hoverCompareCartesian", ], toImageButtonOptions: { filename: "infra_lingdiv_ict", format: "svg" }, }; window.Plotly.newPlot(chartDiv, traces, layout, config); } async function boot() { const mount = document.getElementById(MOUNT_ID); if (!mount) return; if (!window.Plotly) { // Plotly CDN hasn't finished loading yet — retry briefly. return void setTimeout(boot, 50); } try { const res = await fetch(DATA_URL, { cache: "no-cache" }); if (!res.ok) throw new Error(`HTTP ${res.status}`); const json = await res.json(); render(mount, json.data); } catch (err) { console.error("fig-infra-lingdiv:", err); mount.innerHTML = '

[ figure failed to load ]

'; } } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", boot); } else { boot(); } })();