一键导入
tufte-chart-design
Apply Edward Tufte's visualization principles to produce data-honest, high data-ink ratio charts with minimal chartjunk
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Apply Edward Tufte's visualization principles to produce data-honest, high data-ink ratio charts with minimal chartjunk
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Create AI marketing videos and images using Arcads API — Seedance, Sora, Veo, Kling, Nano Banana, ChatGPT Image, and multi-step ad pipelines
Generate AI marketing videos and static image ads using the Arcads API with skills for Seedance 2.0, Sora 2, Veo 3.1, Kling 3.0, Nano Banana, and 37 Meta ad templates
Create AI marketing videos and images using Arcads API with Seedance 2.0, Sora 2, Veo 3.1, Kling 3.0, Nano Banana, and 37 static Meta ad templates
Generate AI marketing videos and images using Arcads creative stack (Seedance 2.0, Sora 2, Veo 3.1, Kling, Nano Banana, ChatGPT Image) from Claude Code or Cursor
Generate AI marketing videos and static image ads using Arcads external API with Seedance 2.0, Sora 2, Veo 3.1, Kling, Nano Banana, and 37-template Meta image library
Create AI marketing videos and static Meta image ads using the Arcads API with Seedance 2.0, Sora 2, Veo 3.1, Kling, Nano Banana, ChatGPT Image, and 37 static ad templates
| 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"] |
Skill by 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.
Auto-triggers on:
/tufte command or mentioning Tufte by nameDoes NOT trigger for:
| 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 |
❌ 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
When asked for inline SVG or given no preference, use this pattern:
<!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>
<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>
<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>
When asked for React or Recharts:
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>
);
}
Apply these props to strip chartjunk:
// 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
};
For slopegraphs, custom dot plots, small multiples:
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')
.attr('x', width - margin.right)
.attr('y', 10)
.attr('text-anchor', 'middle')
.attr('font-size', 10)
.attr('fill', '#666')
.text('Q3');
}, [data]);
return <svg ref={svgRef} width={400} height={300} />;
}
When comparing multiple series, create separate charts on a shared scale instead of overlaying:
const regions = ['North', 'South', 'East', 'West'];
const sharedYDomain = [0, 300]; // consistent scale across all
return (
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 20 }}>
{regions.map(region => (
<div key={region}>
<div style={{ fontSize: 11, color: '#666', marginBottom: 4 }}>
{region}
</div>
<ResponsiveContainer width="100%" height={100}>
<LineChart data={data.filter(d => d.region === region)}>
<YAxis domain={sharedYDomain} hide />
<XAxis dataKey="month" tick={{ fontSize: 9 }} />
<Line type="monotone" dataKey="value" stroke="#1a1a1a" strokeWidth={1.5} dot={false} />
</LineChart>
</ResponsiveContainer>
</div>
))}
</div>
);
Replace giant number tiles with table + sparkline + target delta:
const kpis = [
{ metric: 'Revenue', value: 247, trend: [180,195,205,198,215,247], target: 250, unit: 'K' },
{ metric: 'Users', value: 12.4, trend: [10.2,10.8,11.1,11.5,12.0,12.4], target: 12, unit: 'K' },
{ metric: 'Churn', value: 3.2, trend: [2.1,2.4,2.7,2.9,3.1,3.2], target: 2.5, unit: '%' },
];
return (
<table style={{ fontFamily: 'sans-serif', fontSize: 11, borderCollapse: 'collapse' }}>
<thead>
<tr style={{ borderBottom: '1px solid #e0e0e0', textAlign: 'left' }}>
<th style={{ padding: '8px 12px', fontWeight: 500 }}>Metric</th>
<th style={{ padding: '8px 12px', fontWeight: 500 }}>Current</th>
<th style={{ padding: '8px 12px', fontWeight: 500 }}>Trend</th>
<th style={{ padding: '8px 12px', fontWeight: 500 }}>vs Target</th>
</tr>
</thead>
<tbody>
{kpis.map(kpi => {
const delta = ((kpi.value - kpi.target) / kpi.target * 100).toFixed(0);
const isBad = (kpi.metric === 'Churn' ? delta > 0 : delta < 0);
return (
<tr key={kpi.metric}>
<td style={{ padding: '8px 12px' }}>{kpi.metric}</td>
<td style={{ padding: '8px 12px', fontWeight: 500 }}>
{kpi.value}{kpi.unit}
</td>
<td style={{ padding: '8px 12px' }}>
<Sparkline data={kpi.trend} width={60} height={20} />
</td>
<td style={{
padding: '8px 12px',
color: isBad ? '#d73027' : '#666',
fontWeight: isBad ? 500 : 400
}}>
{delta > 0 ? '+' : ''}{delta}%
</td>
</tr>
);
})}
</tbody>
</table>
);
Before declaring a chart done, verify:
If a user explicitly requests something on the kill list:
"Make a pie chart — the CFO wants a pie"
Comply, but add a comment noting the Tufte alternative:
<!-- Pie chart as requested. Tufte alternative: sorted table with percentages. -->
<svg>...</svg>
User stays in control; you provide the alternative for reference.
| Issue | Fix |
|---|---|
| "Chart feels cramped" | Increase padding, reduce font size, or use small multiples |
| "Can't read labels" | Direct-label at data points instead of axis; rotate if needed |
| "Too much white space" | Tufte prefers it — resist urge to fill; let data breathe |
| "Legend overlaps data" | Remove legend; direct-label each line/bar at its endpoint |
| "Gridlines requested" | Print values on chart instead; use faint reference line only at key thresholds |
| "Dual-axis requested" | Make two separate charts with shared X-axis; stack vertically |
No installation needed — this skill is already active in your environment.
Override strictness per request:
Stack preference (auto-detected from user request):
This skill distills:
Core Tufte quote (VDQI p.105):
Above all else show the data. Maximize the data-ink ratio. Erase non-data-ink. Erase redundant data-ink. Revise and edit.
When in doubt: Show the data. Remove everything else. Let the numbers speak.