michelin-nz / explorer.html
edithatogo's picture
deploy: update dashboard build
3049025 verified
Raw
History Blame Contribute Delete
13.6 kB
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta name="generator" content="Observable Framework v1.13.4">
<title>Country Explorer | Michelin Star Per-Capita Dashboard</title>
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="preload" as="style" href="https://fonts.googleapis.com/css2?family=Source+Serif+4:ital,opsz,wght@0,8..60,200..900;1,8..60,200..900&amp;display=swap" crossorigin>
<link rel="preload" as="style" href="./_import/style.7818a7a2.css">
<link rel="preload" as="style" href="./_observablehq/stdlib/inputs.f7bf0e12.css">
<link rel="stylesheet" type="text/css" href="https://fonts.googleapis.com/css2?family=Source+Serif+4:ital,opsz,wght@0,8..60,200..900;1,8..60,200..900&amp;display=swap" crossorigin>
<link rel="stylesheet" type="text/css" href="./_import/style.7818a7a2.css">
<link rel="stylesheet" type="text/css" href="./_observablehq/stdlib/inputs.f7bf0e12.css">
<link rel="modulepreload" href="./_observablehq/client.793bdc92.js">
<link rel="modulepreload" href="./_observablehq/runtime.607a8173.js">
<link rel="modulepreload" href="./_observablehq/stdlib.23790ac1.js">
<link rel="modulepreload" href="./_npm/htl@1.0.0/11521f02.js">
<link rel="modulepreload" href="./_observablehq/stdlib/inputs.f7175400.js">
<link rel="modulepreload" href="./_npm/@observablehq/plot@0.6.17/93ce672e.js">
<link rel="modulepreload" href="./_npm/isoformat@0.2.1/604cb63b.js">
<link rel="modulepreload" href="./_npm/d3@7.9.0/080cf928.js">
<link rel="modulepreload" href="./_npm/interval-tree-1d@1.0.4/f53f17b2.js">
<link rel="modulepreload" href="./_npm/d3-array@3.2.4/b8e751b6.js">
<link rel="modulepreload" href="./_npm/d3-axis@3.0.0/c662ea05.js">
<link rel="modulepreload" href="./_npm/d3-brush@3.0.0/d162d8c6.js">
<link rel="modulepreload" href="./_npm/d3-chord@3.0.1/5cedd0be.js">
<link rel="modulepreload" href="./_npm/d3-color@3.1.0/65d51007.js">
<link rel="modulepreload" href="./_npm/d3-contour@4.0.2/c576a91a.js">
<link rel="modulepreload" href="./_npm/d3-delaunay@6.0.4/8b43826d.js">
<link rel="modulepreload" href="./_npm/d3-dispatch@3.0.1/3fb06f14.js">
<link rel="modulepreload" href="./_npm/d3-drag@3.0.0/20b76948.js">
<link rel="modulepreload" href="./_npm/d3-dsv@3.0.1/097745a2.js">
<link rel="modulepreload" href="./_npm/d3-ease@3.0.1/8683b012.js">
<link rel="modulepreload" href="./_npm/d3-fetch@3.0.1/5c319046.js">
<link rel="modulepreload" href="./_npm/d3-force@3.0.0/33924f3e.js">
<link rel="modulepreload" href="./_npm/d3-format@3.1.2/8d67a1ae.js">
<link rel="modulepreload" href="./_npm/d3-geo@3.1.1/8037bd0b.js">
<link rel="modulepreload" href="./_npm/d3-hierarchy@3.1.2/a9ba0229.js">
<link rel="modulepreload" href="./_npm/d3-interpolate@3.0.1/e0a47f97.js">
<link rel="modulepreload" href="./_npm/d3-path@3.1.0/f9e9e0ab.js">
<link rel="modulepreload" href="./_npm/d3-polygon@3.0.1/2da8eb3b.js">
<link rel="modulepreload" href="./_npm/d3-quadtree@3.0.1/09002e01.js">
<link rel="modulepreload" href="./_npm/d3-random@3.0.1/5108797c.js">
<link rel="modulepreload" href="./_npm/d3-scale@4.0.2/c9f2571b.js">
<link rel="modulepreload" href="./_npm/d3-scale-chromatic@3.1.0/f3c78e0d.js">
<link rel="modulepreload" href="./_npm/d3-selection@3.0.0/28717d05.js">
<link rel="modulepreload" href="./_npm/d3-shape@3.2.0/7c6e2a50.js">
<link rel="modulepreload" href="./_npm/d3-time@3.1.0/3d1d57c6.js">
<link rel="modulepreload" href="./_npm/d3-time-format@4.1.0/643581a8.js">
<link rel="modulepreload" href="./_npm/d3-timer@3.0.1/e99232db.js">
<link rel="modulepreload" href="./_npm/d3-transition@3.0.1/bd07fd9a.js">
<link rel="modulepreload" href="./_npm/d3-zoom@3.0.0/d09f7cc9.js">
<link rel="modulepreload" href="./_npm/binary-search-bounds@2.0.5/5f640032.js">
<link rel="modulepreload" href="./_npm/internmap@2.0.3/af036d7e.js">
<link rel="modulepreload" href="./_npm/delaunator@5.1.0/7b06abe7.js">
<link rel="modulepreload" href="./_npm/robust-predicates@3.0.3/b42f30a7.js">
<script type="module">
import {define} from "./_observablehq/client.793bdc92.js";
import {registerFile} from "./_observablehq/stdlib.23790ac1.js";
registerFile("./data/country_metrics.json", {"name":"./data/country_metrics.json","mimeType":"application/json","path":"./_file/data/country_metrics.156e9867.json","lastModified":1784512367150,"size":17835});
define({id: "ef5edd4f", inputs: ["FileAttachment","view","Inputs"], outputs: ["countryMetrics","tierOptions","metricOptions","sortOptions","scopeOptions","metricLabels","allCountryNames","tierField","metric","sortDirection","minTierRestaurants","rowLimit","displayScope","focusCountry"], body: async (FileAttachment,view,Inputs) => {
const countryMetrics = await FileAttachment("./data/country_metrics.json").json();
const tierOptions = [
"total_restaurants",
"one_star_restaurants",
"two_star_restaurants",
"three_star_restaurants"
];
const metricOptions = [
"total_restaurants",
"total_stars",
"one_star_restaurants",
"two_star_restaurants",
"three_star_restaurants",
"stars_per_100k",
"stars_per_10b_gdp",
"gdp_per_capita",
"population",
"gdp"
];
const sortOptions = ["descending", "ascending"];
const scopeOptions = ["top", "all", "single"];
const metricLabels = {
total_restaurants: "Aggregate restaurants",
total_stars: "Total stars",
one_star_restaurants: "1-star restaurants",
two_star_restaurants: "2-star restaurants",
three_star_restaurants: "3-star restaurants",
stars_per_100k: "Stars per 100k residents",
stars_per_10b_gdp: "Stars per $10B GDP",
gdp_per_capita: "GDP per capita",
population: "Population",
gdp: "GDP current USD"
};
const allCountryNames = countryMetrics
.map((d) => d.country_name)
.sort((a, b) => a.localeCompare(b));
const tierField = view(
Inputs.select(tierOptions, { label: "Star tier", value: "total_restaurants" })
);
const metric = view(
Inputs.select(metricOptions, { label: "Chart metric", value: "total_restaurants" })
);
const sortDirection = view(Inputs.select(sortOptions, { label: "Sort", value: "descending" }));
const minTierRestaurants = view(
Inputs.range([1, 100], {
step: 1,
label: "Minimum tier restaurants",
value: 10
})
);
const rowLimit = view(Inputs.range([10, 52], { step: 1, label: "Rows shown", value: 25 }));
const displayScope = view(Inputs.select(scopeOptions, { label: "Display", value: "top" }));
const focusCountry = view(Inputs.select(allCountryNames, { label: "Country", value: "France" }));
return {countryMetrics,tierOptions,metricOptions,sortOptions,scopeOptions,metricLabels,allCountryNames,tierField,metric,sortDirection,minTierRestaurants,rowLimit,displayScope,focusCountry};
}});
define({id: "0a5d9bf6", inputs: ["countryMetrics","displayScope","focusCountry","tierField","minTierRestaurants","metric","sortDirection","rowLimit"], outputs: ["filteredRows","visibleRows","chartRows","chartDomain"], body: (countryMetrics,displayScope,focusCountry,tierField,minTierRestaurants,metric,sortDirection,rowLimit) => {
const filteredRows = countryMetrics
.filter((d) => displayScope !== "single" || d.country_name === focusCountry)
.filter((d) => Number(d[tierField] ?? 0) >= minTierRestaurants)
.toSorted((a, b) => {
const left = Number(a[metric] ?? 0);
const right = Number(b[metric] ?? 0);
return sortDirection === "ascending" ? left - right : right - left;
});
const visibleRows = displayScope === "all" ? filteredRows : filteredRows.slice(0, rowLimit);
const chartRows = visibleRows;
const chartDomain = chartRows.map((d) => d.country_name);
return {filteredRows,visibleRows,chartRows,chartDomain};
}});
define({id: "21f3aac0", inputs: ["display","html","filteredRows","tierField","metricLabels"], body: (display,html,filteredRows,tierField,metricLabels) => {
display(
html`<div class="summary-row">
<div><strong>${filteredRows.length}</strong><span>matching countries</span></div>
<div>
<strong>${filteredRows.reduce((sum, d) => sum + Number(d.total_restaurants ?? 0), 0)}</strong
><span>aggregate restaurants</span>
</div>
<div>
<strong>${filteredRows.reduce((sum, d) => sum + Number(d.total_stars ?? 0), 0)}</strong
><span>total stars</span>
</div>
<div>
<strong>${filteredRows.reduce((sum, d) => sum + Number(d[tierField] ?? 0), 0)}</strong
><span>${metricLabels[tierField]}</span>
</div>
</div>`
);
}});
define({id: "bad467d5", inputs: ["display","html","Plot","chartRows","metricLabels","metric","chartDomain"], body: (display,html,Plot,chartRows,metricLabels,metric,chartDomain) => {
display(
html`<div class="chart-frame">
${Plot.plot({
theme: "dark",
width: 1120,
height: Math.max(440, chartRows.length * 32),
marginLeft: 170,
x: {
grid: true,
label: metricLabels[metric],
type: metric === "population" || metric === "gdp" ? "log" : "linear"
},
y: { label: null, domain: chartDomain },
marks: [
Plot.ruleX([0]),
Plot.barX(chartRows, {
x: metric,
y: "country_name",
fill: "#22d3ee",
title: (d) =>
`${d.country_name}\n${metricLabels[metric]}: ${Number(d[metric]).toLocaleString(undefined, { maximumFractionDigits: 4 })}\n1-star: ${d.one_star_restaurants}\n2-star: ${d.two_star_restaurants}\n3-star: ${d.three_star_restaurants}`
}),
Plot.text(chartRows, {
x: metric,
y: "country_name",
text: (d) => Number(d[metric]).toLocaleString(undefined, { maximumFractionDigits: 2 }),
dx: 6,
fill: "#f8fafc",
textAnchor: "start"
})
]
})}
</div>`
);
}});
define({id: "88f9d25f", inputs: ["display","Inputs","visibleRows"], body: (display,Inputs,visibleRows) => {
display(
Inputs.table(visibleRows, {
columns: [
"country_name",
"country",
"total_restaurants",
"total_stars",
"one_star_restaurants",
"two_star_restaurants",
"three_star_restaurants",
"stars_per_100k",
"stars_per_10b_gdp",
"gdp_per_capita"
],
format: {
stars_per_100k: (d) => Number(d).toFixed(4),
stars_per_10b_gdp: (d) => Number(d).toFixed(4),
gdp_per_capita: (d) => `$${Number(d).toLocaleString(undefined, { maximumFractionDigits: 0 })}`
}
})
);
}});
</script>
</head>
<body>
<input id="observablehq-sidebar-toggle" type="checkbox" title="Toggle sidebar">
<label id="observablehq-sidebar-backdrop" for="observablehq-sidebar-toggle"></label>
<nav id="observablehq-sidebar">
<ol>
<label id="observablehq-sidebar-close" for="observablehq-sidebar-toggle"></label>
<li class="observablehq-link"><a href="./">Michelin Star Per-Capita Dashboard</a></li>
</ol>
<ol>
<li class="observablehq-link"><a href="./nz">NZ Aggregate</a></li>
<li class="observablehq-link"><a href="./gdp-stars">GDP &amp; Stars</a></li>
<li class="observablehq-link observablehq-link-active"><a href="./explorer">Country Explorer</a></li>
<li class="observablehq-link"><a href="./pivot">Pivot Table</a></li>
<li class="observablehq-link"><a href="./sources">Data &amp; Sources</a></li>
</ol>
</nav>
<script>{const e=document.querySelector("#observablehq-sidebar"),o=document.querySelector("#observablehq-sidebar-toggle"),r=sessionStorage.getItem("observablehq-sidebar");r?o.checked=r==="true":o.indeterminate=!0;for(const t of document.querySelectorAll("#observablehq-sidebar summary")){const s=t.parentElement;switch(sessionStorage.getItem(`observablehq-sidebar:${t.textContent}`)){case"true":s.open=!0;break;case"false":s.classList.contains("observablehq-section-active")||(s.open=!1);break}}addEventListener("beforeunload",()=>sessionStorage.setItem("observablehq-sidebar-scrolly",`${e.scrollTop}`));const a=sessionStorage.getItem("observablehq-sidebar-scrolly");a!=null&&(e.style.cssText="overflow: hidden;",e.scrollTop=+a,e.style.cssText="");}</script>
<div id="observablehq-center">
<aside id="observablehq-toc" data-selector="h1:not(:first-of-type)[id], h2:first-child[id], :not(h1) + h2[id]">
<nav>
<div>Contents</div>
<ol>
<li class="observablehq-secondary-link"><a href="#controls">Controls</a></li>
<li class="observablehq-secondary-link"><a href="#filtered-countries">Filtered Countries</a></li>
</ol>
</nav>
</aside>
<main id="observablehq-main" class="observablehq">
<h1 id="country-explorer" tabindex="-1"><a class="observablehq-header-anchor" href="#country-explorer">Country Explorer</a></h1>
<p>Filter the aggregate country dataset by star tier, displayed countries, metric, and sort order.</p>
<h2 id="controls" tabindex="-1"><a class="observablehq-header-anchor" href="#controls">Controls</a></h2>
<div class="observablehq observablehq--block"><!--:ef5edd4f:--></div>
<div class="observablehq observablehq--block"><!--:0a5d9bf6:--></div>
<div class="observablehq observablehq--block"><!--:21f3aac0:--></div>
<div class="observablehq observablehq--block"><!--:bad467d5:--></div>
<h2 id="filtered-countries" tabindex="-1"><a class="observablehq-header-anchor" href="#filtered-countries">Filtered Countries</a></h2>
<div class="observablehq observablehq--block"><!--:88f9d25f:--></div>
</main>
<footer id="observablehq-footer">
<nav><a rel="prev" href="./gdp-stars"><span>GDP &amp; Stars</span></a><a rel="next" href="./pivot"><span>Pivot Table</span></a></nav>
<div>Built with <a href="https://observablehq.com/" target="_blank" rel="noopener noreferrer">Observable</a> on <a title="2026-07-20T01:52:42">Jul 20, 2026</a>.</div>
</footer>
</div>
</body>
</html>