Loading...
Loading...
State-of-the-art data visualization for React/Next.js/TypeScript with Tailwind CSS. Creates compelling, tested, and accessible visualizations following Tufte principles and NYT Graphics standards. Activate on "data viz", "chart", "graph", "visualization", "dashboard", "plot", "Recharts", "Nivo", "D3". NOT for static images, print graphics, or basic HTML tables.
npx skill4agent add erichowens/some_claude_skills data-viz-2025What are you building?
├─ Exploratory analysis / many iterations
│ └─ → Observable Plot (grammar-of-graphics)
│
├─ Standard business charts (bars, lines, pies)
│ ├─ Simple React integration needed
│ │ └─ → Recharts (easiest, most popular)
│ └─ Premium aesthetics + theming
│ └─ → Nivo (beautiful out of the box)
│
├─ Custom, one-of-a-kind visualizations
│ ├─ Need low-level control
│ │ └─ → Visx (React + D3 primitives)
│ └─ Full D3 power
│ └─ → D3.js directly (steeper learning curve)
│
└─ Dashboard with Tailwind design system
├─ → Tremor (purpose-built for dashboards)
└─ → shadcn-ui Charts (Recharts + shadcn styling){
"dependencies": {
"@observablehq/plot": "^0.6.0", // Exploratory, grammar-of-graphics
"recharts": "^2.12.0", // React charts, simple & popular
"@nivo/core": "^0.87.0", // Beautiful, themeable charts
"@visx/visx": "^3.10.0", // Low-level D3 + React primitives
"d3": "^7.9.0", // Direct D3 for custom work
"@tremor/react": "^3.15.0", // Tailwind dashboard components
"framer-motion": "^11.0.0" // Smooth animations
},
"devDependencies": {
"@percy/cli": "^1.29.0", // Visual regression testing
"@testing-library/react": "^14.2.0", // Component testing
"@storybook/react": "^7.6.0" // Component playground
}
}<Component />useEffectuseRefreferences/tufte-principles.mdreferences/nyt-workflow.md// Use spring physics, not linear easing
const springConfig = {
type: "spring",
stiffness: 300,
damping: 30
};
// Stagger for multiple elements
const staggerChildren = {
delayChildren: 0.1,
staggerChildren: 0.05
};
// Respect prefers-reduced-motion
const shouldAnimate = !window.matchMedia('(prefers-reduced-motion: reduce)').matches;references/animation-patterns.md// Qualitative (categorical data)
const categorical = [
"#d97706", "#7c3aed", "#059669", "#dc2626", "#2563eb"
];
// Sequential (ordered data, low to high)
const sequential = [
"#fef3c7", "#fcd34d", "#f59e0b", "#d97706", "#92400e"
];
// Diverging (data with meaningful center)
const diverging = [
"#dc2626", "#f87171", "#fef2f2", "#c7d2fe", "#6366f1"
];# Percy - Automated visual testing
npx percy snapshot ./storybook-static
# Chromatic - For Storybook
npx chromatic --project-token=<token>// Verify rendered elements match data
test('bar chart renders correct number of bars', () => {
const data = [{ x: 'A', y: 10 }, { x: 'B', y: 20 }];
render(<BarChart data={data} />);
const bars = screen.getAllByTestId('bar');
expect(bars).toHaveLength(2);
});
// Verify scale accuracy
test('bar heights proportional to values', () => {
const data = [{ x: 'A', y: 10 }, { x: 'B', y: 20 }];
render(<BarChart data={data} />);
const bars = screen.getAllByTestId('bar');
const heights = bars.map(b => parseInt(b.style.height));
expect(heights[1]).toBe(heights[0] * 2); // B is 2x A
});references/testing-strategies.md// Desktop: Show everything
// Tablet: Simplify axes, reduce labels
// Mobile: Minimal chart, key insights only
const ChartResponsive = ({ data }: Props) => {
const isMobile = useMediaQuery('(max-width: 640px)');
return (
<ResponsiveContainer width="100%" height={isMobile ? 200 : 400}>
<LineChart data={data}>
{!isMobile && <CartesianGrid strokeDasharray="3 3" />}
<XAxis
dataKey="date"
tick={isMobile ? { fontSize: 10 } : undefined}
interval={isMobile ? 'preserveStartEnd' : 'auto'}
/>
<YAxis tick={isMobile ? false : undefined} />
<Tooltip />
<Line type="monotone" dataKey="value" stroke="#d97706" />
</LineChart>
</ResponsiveContainer>
);
};references/data-storytelling.mdreferences/antipatterns.md# Use Observable Plot for rapid iteration
npm install @observablehq/plot
# Create throwaway prototypes, iterate fast
# When you find the right chart, implement in production library// Use Recharts for standard charts
// Use Nivo for beautiful, themeable charts
// Use Visx/D3 for custom visualizations
// Always wrap in error boundaries
// Always show skeleton loading state
// Always handle empty/loading/error states# Visual regression testing
npx percy snapshot
# Component testing
npm test -- --coverage
# Accessibility testing
npx axe-core src/components/charts// Storybook for component playground
// Props documentation with TypeScript
// Usage examples for each chart typeconst generateInsight = async (data: DataPoint[]) => {
const response = await fetch('/api/claude', {
method: 'POST',
body: JSON.stringify({
model: 'claude-haiku',
prompt: `Analyze this data and provide ONE key insight (max 15 words): ${JSON.stringify(data)}`
})
});
return response.text(); // "Sales peaked in Q3, driven by mobile conversions"
};// ❌ DON'T import entire library
import { LineChart } from 'recharts';
// ✅ DO tree-shake where possible
import LineChart from 'recharts/lib/chart/LineChart';
// Use dynamic imports for heavy charts
const HeavyChart = dynamic(() => import('./HeavyChart'), {
loading: () => <ChartSkeleton />,
ssr: false // Disable SSR for client-only charts
});// Use react-window or react-virtualized for long lists
// Aggregate/sample data for chart display
// Store full dataset separately for exportprefers-reduced-motion: reduce<figure role="img" aria-labelledby="chart-title chart-desc">
<h2 id="chart-title">Sales Over Time</h2>
<p id="chart-desc">
Line chart showing sales increased 45% from Q1 to Q4,
peaking in November at $2.3M.
</p>
<LineChart data={data} />
{/* Provide data table alternative */}
<details>
<summary>View data table</summary>
<table>...</table>
</details>
</figure>references/tufte-principles.mdreferences/library-comparison.mdreferences/testing-strategies.mdreferences/animation-patterns.mdreferences/data-storytelling.mdreferences/antipatterns.mdreferences/nyt-workflow.mdscripts/data-transform.tsscripts/chart-test-helpers.tsscripts/color-palette-generator.tsscripts/performance-benchmark.ts// 1. Install dependencies
// npm install recharts framer-motion
// 2. Create a simple line chart
import { LineChart, Line, XAxis, YAxis, Tooltip, ResponsiveContainer } from 'recharts';
import { motion } from 'framer-motion';
const data = [
{ month: 'Jan', value: 400 },
{ month: 'Feb', value: 300 },
{ month: 'Mar', value: 600 },
];
export const SalesChart = () => (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
>
<ResponsiveContainer width="100%" height={300}>
<LineChart data={data}>
<XAxis dataKey="month" />
<YAxis />
<Tooltip />
<Line
type="monotone"
dataKey="value"
stroke="#d97706"
strokeWidth={2}
dot={{ fill: '#d97706', r: 4 }}
/>
</LineChart>
</ResponsiveContainer>
</motion.div>
);
// 3. Test it
// 4. Ship it with confidence