Scaffold Chart Sins site with Astro + build-time Vega-Lite

Set up the static-site tech stack: Astro 5 with Markdown content
collections, Vega-Lite specs rendered to static SVG at build time
(zero client JS), plain scoped CSS, and a GitHub Pages deploy workflow.

Includes three sample sins (truncated y-axis, dual-axis deception,
pie chart overload), each with a bad and fixed chart spec, plus a
gallery index and per-sin detail pages.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XPEvmMbac2fCvKXpQcovj8
This commit is contained in:
Claude
2026-08-05 11:57:52 +00:00
commit 2e17f8fa59
23 changed files with 978 additions and 0 deletions
+80
View File
@@ -0,0 +1,80 @@
---
// Build-time Vega-Lite renderer.
//
// This component runs only during `astro build` (and dev SSR), never in the
// browser. It compiles a Vega-Lite spec to Vega, renders it to a static SVG
// string with Vega's headless view, and inlines that SVG. The result ships as
// plain markup — zero client-side JavaScript, no Vega runtime downloaded.
//
// To make a specific chart interactive later, you would swap this component
// out for a client-side vega-embed island on that page only.
import * as vega from 'vega';
import { compile, type TopLevelSpec } from 'vega-lite';
interface Props {
/** A Vega-Lite spec (object). Do not include $schema/width/height/config. */
spec: TopLevelSpec;
/** Accessible description of what the chart shows. */
title: string;
/** Optional caption rendered under the chart. */
caption?: string;
}
const { spec, title, caption } = Astro.props;
let svg = '';
let error: string | null = null;
try {
// Compile Vega-Lite -> Vega, then render headlessly to SVG.
const vgSpec = compile(spec as TopLevelSpec).spec;
const view = new vega.View(vega.parse(vgSpec), { renderer: 'none' }).initialize();
svg = await view.toSVG();
view.finalize();
} catch (e) {
error = e instanceof Error ? e.message : String(e);
// Fail loudly in the build log, but don't crash the whole build.
console.error(`[VegaChart] failed to render "${title}": ${error}`);
}
---
<figure class="vega-chart" role="group" aria-label={title}>
{error ? (
<div class="vega-chart__error">
<strong>Chart failed to render.</strong>
<code>{error}</code>
</div>
) : (
<div class="vega-chart__svg" set:html={svg} />
)}
{caption && <figcaption>{caption}</figcaption>}
</figure>
<style>
.vega-chart {
margin: 0;
}
.vega-chart__svg :global(svg) {
max-width: 100%;
height: auto;
}
.vega-chart figcaption {
margin-top: 0.5rem;
font-size: 0.85rem;
color: var(--muted);
text-align: center;
}
.vega-chart__error {
padding: 1rem;
border: 1px dashed var(--sin-red);
border-radius: 8px;
color: var(--sin-red);
font-size: 0.85rem;
}
.vega-chart__error code {
display: block;
margin-top: 0.5rem;
white-space: pre-wrap;
word-break: break-word;
}
</style>