- name
- tufte-chart-design
- description
- Apply Edward Tufte's visualization principles to produce data-honest, high data-ink ratio charts with minimal chartjunk
- triggers
- ["make a chart","create a visualization","design a dashboard","improve this chart","what chart should I use","apply Tufte principles","visualize this data","build a graph"]
# Tufte Chart Design Skill
> Skill by [ara.so](https://ara.so) — Claude Code Skills collection.
This skill teaches you to create charts following Edward Tufte's principles from his three foundational books: *The Visual Display of Quantitative Information*, *Envisioning Information*, and *Visual Explanations*. It turns "make me a chart" into a Tufte-compliant visualization with maximum data-ink ratio and zero chartjunk.
## What This Skill Does
- **Activates automatically** when you're asked to create, improve, or critique any chart or visualization
- **Applies ten core principles** distilled from Tufte's work
- **Chooses the right chart type** based on data shape and analytical goal
- **Removes chartjunk** — 3D effects, unnecessary gridlines, redundant legends, pie charts, dual axes
- **Produces clean output** as inline SVG, React (Recharts), or D3-in-React
- **Enforces graphical integrity** — lie factor between 0.95 and 1.05, bars always start at zero
## The Ten Principles
1. **Show the data** — Above all else. Every decision asks "does this help see the data?"
2. **Maximize data-ink ratio** — Ink that changes when data changes is data-ink. Everything else is overhead.
3. **Erase non-data-ink** — Remove frame boxes, gridlines, tick marks at minor units.
4. **Erase redundant data-ink** — One encoding per quantity. Don't show "37" as a bar + number + gridline + tick.
5. **Graphical integrity** — Bars start at zero. Never use area for 1D quantities. Lie factor = (size of effect shown) / (size of effect in data).
6. **Small multiples** — N small charts on shared scales beats one chart with N overlapping lines.
7. **Layering and separation** — Data on top, labels next, scaffolding faintest. Avoid 1+1=3 visual noise.
8. **Micro/macro readings** — One shape from across the room, every point legible up close.
9. **Smallest effective difference** — Gray for context, one accent for focus. Subtle but clear.
10. **Word-data integration** — Sparklines in sentences. Numbers next to visuals. Tables are charts.
## When This Skill Activates
Auto-triggers on:
- "Make a chart of...", "visualize...", "graph...", "plot..."
- "Build a dashboard", "design a KPI tile"
- "Improve this chart", "critique this visual"
- "What chart should I use for...?"
- `/tufte` command or mentioning Tufte by name
- Any code editing session involving chart/visualization code
Does NOT trigger for:
- Logos, illustrations, decorative graphics
- Flowcharts, architecture diagrams, wireframes
- Pure typography or layout without quantitative data
## Chart Selection Guide
| Data Shape | Goal | Use | NOT |
|---|---|---|---|
| One time series | Show trend | Sparkline or line chart | Area chart, bar chart |
| Multiple time series | Compare trends | Small multiples (separate charts) | Multi-line spaghetti |
| Categories (≤10) | Compare values | Sorted dot plot or bar chart | Pie, donut, 3D bars |
| Categories (>10) | Show distribution | Strip plot or histogram | Rainbow color scales |
| Two time points | Show change | Slopegraph | Clustered bars |
| Part-to-whole | Show composition | Sorted table with percentages | Pie, donut, stacked bars |
| Process stages | Show flow | Horizontal bars + percentages | 3D funnel |
| Correlation | Show relationship | Scatterplot | Dual-axis line chart |
| Cohorts over time | Show retention | Sequential heatmap + sparklines | Rainbow heatmap |
| Geographic | Show regional pattern | Choropleth (single-hue sequential) | Rainbow choropleth |
## Kill List — Never Use
❌ **3D effects** — distort area perception, add no information
❌ **Pie charts** — humans can't compare angles; use sorted table instead
❌ **Donut charts** — same problem as pie, worse
❌ **Dual-axis charts** — viewer can't tell if correlation is real or axis-manipulation
❌ **Rainbow color scales** — perceptually non-uniform; use sequential single-hue
❌ **Gradient fills** — decorative ink, not data-ink
❌ **Drop shadows, glows** — chartjunk
❌ **Chart frames/boxes** — erase, data should float
❌ **Heavy gridlines** — if you need them, print values instead
❌ **Redundant legends** — direct-label instead
## HTML/SVG Output Pattern
When asked for inline SVG or given no preference, use this pattern:
```html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
font-size: 11px;
color: #1a1a1a;
background: white;
padding: 40px;
}
.label { font-size: 10px; fill: #666; }
.value { font-size: 13px; fill: #1a1a1a; font-weight: 500; }
.data-point { fill: #1a1a1a; }
.data-line { stroke: #1a1a1a; stroke-width: 1.5; fill: none; }
.reference-line { stroke: #e0e0e0; stroke-width: 1; }
.accent { fill: #d73027; }
</style>
</head>
<body>
<svg width="600" height="200" viewBox="0 0 600 200">
<!-- Data visualization here -->
</svg>
</body>
</html>
```
### Sparkline Example
```html
<div style="font-family: sans-serif; padding: 20px;">
<div style="font-size: 11px; color: #666; margin-bottom: 4px;">Monthly Active Users</div>
<div style="display: flex; align-items: baseline; gap: 12px;">
<span style="font-size: 24px; font-weight: 500; color: #1a1a1a;">247K</span>
<svg width="120" height="24" style="display: block;">
<polyline
points="0,18 10,15 20,16 30,14 40,12 50,13 60,10 70,9 80,11 90,8 100,6 110,4 120,3"
stroke="#1a1a1a"
stroke-width="1.5"
fill="none"
/>
<text x="0" y="24" font-size="9" fill="#999">Jan</text>
<text x="110" y="24" font-size="9" fill="#999" text-anchor="end">Dec</text>
</svg>
</div>
</div>
```
### Dot Plot Example
```html
<svg width="400" height="180" viewBox="0 0 400 180">
<!-- Sorted by value, descending -->
<circle cx="320" cy="20" r="4" fill="#1a1a1a"/>
<text x="330" y="24" font-size="11" fill="#1a1a1a">Product A</text>
<text x="310" y="24" font-size="11" fill="#666" text-anchor="end">$247K</text>
<circle cx="280" cy="45" r="4" fill="#1a1a1a"/>
<text x="290" y="49" font-size="11" fill="#1a1a1a">Product B</text>
<text x="270" y="49" font-size="11" fill="#666" text-anchor="end">$213K</text>
<circle cx="190" cy="70" r="4" fill="#1a1a1a"/>
<text x="200" y="74" font-size="11" fill="#1a1a1a">Product C</text>
<text x="180" y="74" font-size="11" fill="#666" text-anchor="end">$145K</text>
<!-- Scale reference at bottom -->
<line x1="0" y1="160" x2="320" y2="160" stroke="#e0e0e0" stroke-width="1"/>
<text x="0" y="175" font-size="9" fill="#999">$0</text>
<text x="320" y="175" font-size="9" fill="#999" text-anchor="end">$250K</text>
</svg>
```
## React/Recharts Output Pattern
When asked for React or Recharts:
```jsx
import { LineChart, Line, ResponsiveContainer, XAxis, YAxis } from 'recharts';
const data = [
{ month: 'Jan', value: 180 },
{ month: 'Feb', value: 195 },
{ month: 'Mar', value: 205 },
{ month: 'Apr', value: 198 },
{ month: 'May', value: 215 },
{ month: 'Jun', value: 247 },
];
export default function RevenueChart() {
return (
<div style={{ fontFamily: 'system-ui, sans-serif' }}>
<div style={{ fontSize: 11, color: '#666', marginBottom: 4 }}>
Monthly Revenue (thousands)
</div>
<ResponsiveContainer width="100%" height={120}>
<LineChart data={data} margin={{ top: 5, right: 5, bottom: 5, left: 5 }}>
<XAxis
dataKey="month"
axisLine={false}
tickLine={false}
tick={{ fontSize: 10, fill: '#999' }}
interval="preserveStartEnd"
/>
<YAxis
hide={true}
/>
<Line
type="monotone"
dataKey="value"
stroke="#1a1a1a"
strokeWidth={1.5}
dot={false}
/>
</LineChart>
</ResponsiveContainer>
<div style={{ fontSize: 20, fontWeight: 500, color: '#1a1a1a' }}>
$247K
</div>
</div>
);
}
```
### Recharts Theme Config
Apply these props to strip chartjunk:
```jsx
// Universal settings
const tufteTheme = {
xAxis: {
axisLine: false,
tickLine: false,
tick: { fontSize: 10, fill: '#999' },
},
yAxis: {
axisLine: false,
tickLine: false,
tick: { fontSize: 10, fill: '#999' },
// Often better to hide Y-axis and direct-label instead
},
grid: {
strokeDasharray: '3 3',
stroke: '#f0f0f0', // very faint if you must show
},
// NO tooltip by default — print values on chart
// NO legend — direct label instead
};
```
## D3-in-React Pattern (for Custom Charts)
For slopegraphs, custom dot plots, small multiples:
```jsx
import { useEffect, useRef } from 'react';
import * as d3 from 'd3';
export default function Slopegraph({ data }) {
const svgRef = useRef();
useEffect(() => {
const svg = d3.select(svgRef.current);
const width = 400;
const height = 300;
const margin = { top: 20, right: 80, bottom: 20, left: 80 };
// Clear previous render
svg.selectAll('*').remove();
const y = d3.scaleLinear()
.domain([0, d3.max(data, d => Math.max(d.q2, d.q3))])
.range([height - margin.bottom, margin.top]);
// Draw lines
svg.selectAll('line.slope')
.data(data)
.join('line')
.attr('class', 'slope')
.attr('x1', margin.left)
.attr('x2', width - margin.right)
.attr('y1', d => y(d.q2))
.attr('y2', d => y(d.q3))
.attr('stroke', d => d.q3 < d.q2 ? '#d73027' : '#ccc')
.attr('stroke-width', 1.5);
// Left labels + values
svg.selectAll('text.left-label')
.data(data)
.join('text')
.attr('class', 'left-label')
.attr('x', margin.left - 8)
.attr('y', d => y(d.q2))
.attr('text-anchor', 'end')
.attr('alignment-baseline', 'middle')
.attr('font-size', 11)
.attr('fill', '#1a1a1a')
.text(d => `${d.team} ${d.q2}`);
// Right values
svg.selectAll('text.right-label')
.data(data)
.join('text')
.attr('class', 'right-label')
.attr('x', width - margin.right + 8)
.attr('y', d => y(d.q3))
.attr('text-anchor', 'start')
.attr('alignment-baseline', 'middle')
.attr('font-size', 11)
.attr('fill', '#1a1a1a')
.text(d => d.q3);
// Column headers
svg.append('text')
.attr('x', margin.left)
.attr('y', 10)
.attr('text-anchor', 'middle')
.attr('font-size', 10)
.attr('fill', '#666')
.text('Q2');
svg.append('text')
Ver no GitHub