- Rewrite pokes to name the sin itself, not our sample's exact numbers (a visitor didn't "start at 90" — they mis-set the baseline) - Remove the "cited:" line from the OG card; citations stay on the page Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XPEvmMbac2fCvKXpQcovj8
Chart Sins
A catalogue of data-visualization sins — for each one: the chart that misleads, why it fools the eye, and the honest version that fixes it.
Built with Astro and styled on the Carbon Design System. Charts are authored as Vega-Lite specs and rendered to static SVG at build time, so pages ship with zero client-side JavaScript.
Tech stack
| Concern | Choice |
|---|---|
| Framework | Astro 5 (static output) |
| Content | Markdown/MDX via Astro Content Collections |
| Charts | Vega-Lite specs → SVG at build (headless Vega) |
| Design | Carbon Design System — tokens, type scale, spacing, motion, themes |
| Typeface | IBM Plex Sans / Mono (self-hosted via @fontsource) |
| Styling | Token-driven CSS (src/styles/global.css) |
| Hosting | GitHub Pages (via GitHub Actions) |
| Language | TypeScript |
On the Carbon adoption
Carbon ships React/web-component libraries built for application UIs and they ship JavaScript; this is a static content site, so instead of the component runtime we adopt Carbon at the design-token level — its color tokens and White/Gray-100 themes, the 8px spacing scale, the productive/expressive type distinction (IBM Plex), motion tokens, square geometry, the focus ring, and Carbon's data-visualization palette for the charts (validated for colorblind-safety and contrast on the chart surface).
Project layout
src/
├── content.config.ts # typed frontmatter schema for a "sin"
├── content/sins/ # one Markdown file per sin
├── charts/ # Vega-Lite JSON specs (bad + fixed)
├── components/VegaChart.astro # build-time Vega-Lite → SVG renderer
├── layouts/BaseLayout.astro
├── lib/charts.ts # spec lookup + severity helper
├── pages/
│ ├── index.astro # the gallery
│ └── sins/[...slug].astro # a sin's detail page
└── styles/global.css
Adding a new sin
-
Author two Vega-Lite specs in
src/charts/, e.g.my-sin-bad.jsonandmy-sin-fixed.json.Omit
$schema,config, and outerwidth/heighttuning you don't need — keep specs focused on the data and encoding. -
Create
src/content/sins/my-sin.mdwith frontmatter:--- title: "My Sin" summary: "One-line description shown on the gallery." category: "Misleading Scales" severity: 3 # 1 (venial) … 5 (mortal) tags: ["bar chart"] badChart: "my-sin-bad" # basename in src/charts/, no .json fixedChart: "my-sin-fixed" date: 2026-08-05 --- Prose explaining the sin, why it deceives, and the repentance. -
npm run devand check it. The typed schema will flag a mistyped field or a missing chart spec at build time.
Making a chart interactive (opt-in)
Charts are static SVG by default. To make one interactive, render it client-side
with vega-embed in a small Astro island (client:visible) on that page only,
instead of using <VegaChart>. This keeps the default fast while allowing
hover/zoom where a specific sin benefits from it.
Commands
| Command | Action |
|---|---|
npm install |
Install dependencies |
npm run dev |
Start the dev server at localhost:4321 |
npm run build |
Build the static site to ./dist |
npm run preview |
Preview the production build locally |
Deployment
Pushing to main triggers .github/workflows/deploy.yml, which builds the site
and publishes it to GitHub Pages. Enable it once under
Settings → Pages → Build and deployment → Source: GitHub Actions.
The site is configured for the project path https://olehomelchenko.github.io/chart-sins
(site + base in astro.config.mjs). If you move to a custom domain, set
site to it and remove base.