loupe / charts_web /chart.html
anxuanng's picture
Redrawn charts: Inter, fewer labels, one number per chart
cc6a28d verified
Raw History Blame Contribute Delete
6.74 kB
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Loupe charts</title>
<style>
@font-face { font-family: "Inter"; src: url("InterVariable.woff2") format("woff2"); font-weight: 100 900; font-style: normal; }
:root { --ink: #f4f4f6; --muted: #8d93a3; --accent: #6a92ee; --plain: #c9ccd6; --faint: #4a4e5c; --track: #ffffff0d; }
html, body { margin: 0; }
body {
width: 1600px; height: 900px; overflow: hidden; color: var(--ink);
font-family: "Inter", system-ui, sans-serif; -webkit-font-smoothing: antialiased; font-optical-sizing: auto;
background: radial-gradient(70% 60% at 15% 0%, #1a2242 0%, transparent 70%), radial-gradient(60% 50% at 95% 100%, #132a22 0%, transparent 70%), #0e0e11;
}
.frame { position: absolute; inset: 0; padding: 84px 100px 0; box-sizing: border-box; }
h1 { margin: 0; font-size: 76px; line-height: 1.04; font-weight: 760; letter-spacing: -0.04em; }
.note { margin: 22px 0 0; font-size: 30px; line-height: 1.3; color: var(--muted); max-width: 1280px; }
.chart { position: absolute; left: 100px; right: 100px; top: 330px; bottom: 150px; display: flex; flex-direction: column; justify-content: center; gap: 30px; }
.bar-row { display: grid; grid-template-columns: 420px 1fr; align-items: center; column-gap: 28px; }
.bar-label { font-size: 30px; font-weight: 560; letter-spacing: -0.01em; text-align: right; color: var(--ink); }
.bar-row.faint .bar-label { color: var(--muted); }
.track { position: relative; height: 68px; border-radius: 18px; background: var(--track); }
.bar-row.compact .track { height: 44px; border-radius: 13px; }
.fill { position: absolute; left: 0; top: 0; bottom: 0; border-radius: inherit; background: var(--plain); min-width: 14px; }
.bar-row.strong .fill { background: var(--accent); }
.bar-row.faint .fill { background: var(--faint); }
.value { position: absolute; top: 50%; transform: translateY(-50%); font-size: 40px; font-weight: 740; letter-spacing: -0.03em; font-variant-numeric: tabular-nums; white-space: nowrap; }
.bar-row.compact .value { font-size: 28px; }
.bar-row.compact .bar-label { font-size: 26px; }
.marker { position: absolute; top: -14px; bottom: -14px; width: 0; border-left: 2px dashed #ffffff40; }
.marker span { position: absolute; top: -34px; left: -40px; width: 80px; text-align: center; font-size: 20px; color: var(--muted); }
.matrix { display: grid; grid-template-columns: 420px repeat(4, 1fr); gap: 16px 16px; align-items: center; }
.matrix .head { font-size: 26px; color: var(--muted); text-align: center; font-weight: 560; }
.matrix .label { font-size: 30px; font-weight: 560; text-align: right; padding-right: 12px; }
.cell { height: 104px; border-radius: 20px; display: flex; flex-direction: column; align-items: center; justify-content: center; background: var(--track); }
.cell b { font-size: 42px; font-weight: 740; letter-spacing: -0.03em; font-variant-numeric: tabular-nums; }
.cell span { font-size: 20px; color: #ffffffb0; margin-top: 2px; font-variant-numeric: tabular-nums; }
.foot { position: absolute; left: 100px; bottom: 62px; right: 420px; font-size: 22px; line-height: 1.35; color: var(--muted); }
.mark { position: absolute; right: 100px; bottom: 58px; display: flex; align-items: center; gap: 12px; font-size: 26px; font-weight: 600; letter-spacing: -0.02em; }
.mark small { font-size: 22px; font-weight: 460; color: var(--muted); }
.mark img { width: 38px; height: 38px; filter: invert(1); image-rendering: pixelated; }
</style>
</head>
<body>
<div class="frame">
<h1 id="title"></h1>
<p class="note" id="note"></p>
</div>
<div class="chart" id="chart"></div>
<div class="foot" id="foot"></div>
<div class="mark"><span>Loupe</span><small>by</small><img src="codepawl-logo.png" alt=""><span>codepawl</span></div>
<script type="module">
const element = (tag, className, text) => {
const node = document.createElement(tag);
if (className) node.className = className;
if (text !== undefined) node.textContent = text;
return node;
};
function drawBars(chart, holder) {
const compact = chart.bars.length > 4;
if (compact) holder.style.gap = '18px';
for (const bar of chart.bars) {
const row = element('div', `bar-row${bar.strong ? ' strong' : ''}${bar.faint ? ' faint' : ''}${compact ? ' compact' : ''}`);
row.append(element('div', 'bar-label', bar.label));
const track = element('div', 'track');
const share = Math.min(bar.value / chart.maximum, 1);
const fill = element('div', 'fill');
fill.style.width = `${share * 100}%`;
const value = element('div', 'value', bar.text);
// The number sits just past the bar, or inside its end when the bar is nearly full.
if (share > 0.82) { value.style.right = `${(1 - share) * 100 + 2}%`; value.style.color = '#0e0e11'; }
else value.style.left = `calc(${share * 100}% + 18px)`;
track.append(fill, value);
if (chart.marker !== undefined && bar === chart.bars[0]) {
const marker = element('div', 'marker');
marker.style.left = `${(chart.marker / chart.maximum) * 100}%`;
marker.style.bottom = `${-(chart.bars.length - 1) * (68 + 30) - 14}px`;
marker.append(element('span', '', chart.marker_label));
track.append(marker);
}
row.append(track);
holder.append(row);
}
}
function drawMatrix(chart, holder) {
const grid = element('div', 'matrix');
grid.append(element('div'));
for (const column of chart.columns) grid.append(element('div', 'head', column));
for (const row of chart.rows) {
grid.append(element('div', 'label', row.label));
for (const cell of row.cells) {
const box = element('div', 'cell');
// Fuller colour for a higher share; an empty cell stays on the track colour.
if (cell.value > 0) box.style.background = `rgba(106, 146, 238, ${0.16 + 0.84 * cell.value})`;
box.append(element('b', '', `${Math.round(cell.value * 100)}%`), element('span', '', cell.text));
if (cell.value >= 0.6) box.querySelector('b').style.color = '#0e0e11', box.querySelector('span').style.color = '#0e0e11b0';
grid.append(box);
}
}
holder.append(grid);
}
const charts = await (await fetch('data.json')).json();
const chart = charts[new URLSearchParams(location.search).get('chart')];
document.getElementById('title').textContent = chart.title;
document.getElementById('note').textContent = chart.note;
document.getElementById('foot').textContent = chart.foot;
(chart.kind === 'matrix' ? drawMatrix : drawBars)(chart, document.getElementById('chart'));
await document.fonts.ready;
window.chartReady = true;
</script>
</body>
</html>