skills/data-viz-2025/SKILL.md
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 skillsauth add curiositech/windags-skills data-viz-2025Install this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Create visualizations that marry NYT Graphics rigor with modern web performance and accessibility standards.
What type of chart are you building?
├─ Exploratory analysis / rapid prototyping
│ ├─ Data size < 5K points → Observable Plot
│ └─ Data size > 5K points → D3.js with sampling
│
├─ Standard business charts (bars, lines, areas)
│ ├─ Need it fast & simple → Recharts
│ ├─ Premium aesthetics required → Nivo
│ └─ Custom interactions needed → Visx
│
├─ Complex/novel visualizations
│ ├─ React component structure preferred → Visx
│ └─ Maximum control needed → D3.js
│
└─ Dashboard with many charts
├─ Tailwind design system → Tremor
└─ Consistent theming → Nivo
How many data points?
├─ < 1K points
│ ├─ Need crisp scaling → SVG rendering
│ └─ Need interactivity → SVG with event handlers
│
├─ 1K - 10K points
│ ├─ Simple shapes → SVG (acceptable)
│ ├─ Complex animations → Canvas
│ └─ Mobile performance critical → Canvas
│
└─ > 10K points
├─ Static display → Canvas with aggregation
├─ Interactive exploration → WebGL (via deck.gl)
└─ Real-time updates → Canvas with data sampling
Is this for production use?
├─ Yes → Must implement ALL accessibility features
│ ├─ Keyboard navigation for all interactive elements
│ ├─ Screen reader support with data tables
│ ├─ Color-blind safe palettes (test with simulators)
│ └─ Respect prefers-reduced-motion
│
└─ Internal tool/prototype → Implement core features
├─ Alt text for chart images
├─ Sufficient color contrast (4.5:1 minimum)
└─ Keyboard access for primary interactions
What's the screen breakpoint?
├─ Mobile (< 640px)
│ ├─ Many data points → Aggregate to top 5-7 items
│ ├─ Time series → Show last 30 days, add "View All" button
│ ├─ Multi-series → Use small multiples instead of overlays
│ └─ Complex legends → Replace with direct labels
│
├─ Tablet (640px - 1024px)
│ ├─ Reduce axis labels by 50%
│ ├─ Simplify gridlines (remove minor ticks)
│ └─ Increase touch targets to 44px minimum
│
└─ Desktop (> 1024px)
└─ Show full detail, all interactions enabled
Symptom: Chart has 8+ colors, tiny legend, impossible to distinguish series Diagnosis: Trying to show too many categories simultaneously Fix:
Symptom: Bar chart with Y-axis starting at 95 instead of 0, making 2% difference look like 200% Diagnosis: Truncated axes exaggerating small differences Fix:
Symptom: Desktop chart squished to mobile width, text unreadable, interactions broken Diagnosis: No responsive design strategy, one-size-fits-all approach Fix:
Symptom: White screen for 3+ seconds, then chart appears suddenly Diagnosis: No skeleton loading, blocking data fetch Fix:
Symptom: Screen reader announces "image" with no context, keyboard navigation broken Diagnosis: Visual-only design, no programmatic access to data Fix:
Scenario: Executive dashboard showing revenue by 8 product lines over 12 months, needs to work on mobile.
Decision Process:
Desktop Implementation:
<LineChart data={monthlyData} width="100%" height={400}>
<XAxis dataKey="month" />
<YAxis />
<Tooltip />
<Legend />
{products.map((product, index) => (
<Line
key={product}
dataKey={product}
stroke={colors[index]}
strokeWidth={2}
/>
))}
</LineChart>
Mobile Adaptation:
const isMobile = useMediaQuery('(max-width: 640px)');
const topProducts = isMobile
? products.slice(0, 3) // Show only top 3
: products;
<ResponsiveContainer height={isMobile ? 250 : 400}>
<LineChart data={monthlyData}>
<XAxis
dataKey="month"
interval={isMobile ? 2 : 0} // Every 3rd month on mobile
tick={{ fontSize: isMobile ? 10 : 12 }}
/>
<YAxis tick={isMobile ? false : true} />
<Tooltip />
{!isMobile && <Legend />}
{topProducts.map((product, index) => (
<Line
key={product}
dataKey={product}
stroke={colors[index]}
strokeWidth={isMobile ? 3 : 2} // Thicker lines for mobile
/>
))}
</LineChart>
</ResponsiveContainer>
Key Decisions Made:
Scenario: User journey flow with 50K user sessions, 12 touchpoints, needs real-time updates.
Decision Process:
Implementation Strategy:
// Data aggregation first - group similar paths
const aggregatedFlows = useMemo(() => {
return rawUserJourney
.reduce((acc, session) => {
const pathKey = session.path.join('→');
acc[pathKey] = (acc[pathKey] || 0) + session.count;
return acc;
}, {})
// Keep only paths with 100+ users
.filter(([path, count]) => count >= 100);
}, [rawUserJourney]);
// Canvas rendering for performance
useEffect(() => {
const canvas = canvasRef.current;
const ctx = canvas.getContext('2d');
// Clear and render
ctx.clearRect(0, 0, width, height);
// Use Web Workers for heavy calculations
const worker = new Worker('/sankeyWorker.js');
worker.postMessage({ flows: aggregatedFlows, dimensions: { width, height } });
worker.onmessage = (e) => {
const { nodes, links } = e.data;
drawSankey(ctx, nodes, links);
};
}, [aggregatedFlows]);
Performance Optimizations Applied:
Before shipping any data visualization, verify ALL conditions:
This skill should NOT be used for:
<table> markup with CSS stylingWhen to delegate to other skills:
Library-specific boundaries:
data-ai
license: Apache-2.0 NOT for unrelated tasks outside this domain.
development
Use when designing caching strategies (cache-aside, write-through, write-behind), implementing distributed locks, building rate limiters, leaderboards, real-time streams (XADD/consumer groups), pub/sub, or tuning eviction policies. Triggers: thundering-herd on cache miss, dogpile on key expiry, Redlock vs SET-NX-PX choice, sliding-window rate limiter, hot-key on a single cluster slot, big-key blowup, MULTI/EXEC across slots, KEYS in production. NOT for Redis Cluster operations/admin (different domain), embedded KV (SQLite, leveldb), in-process LRU caches, or Memcached.
tools
Drawing the `'use client'` boundary correctly in React Server Components apps (Next.js App Router, RSC frameworks) — leaf-pushing, slot composition, serialization rules, and environment poisoning prevention. Grounded in react.dev and Next.js 16 docs.
development
Use when designing rate limiting for an API, choosing between token bucket / sliding window / leaky bucket / fixed window, implementing it in Redis, deciding edge (Cloudflare/Upstash) vs origin enforcement, sizing per-user vs per-IP vs per-endpoint quotas, returning the right 429 response with Retry-After, or fixing the boundary-burst bug in fixed-window limiters. Triggers: 429 too many requests, INCR + EXPIRE, ZADD + ZREMRANGEBYSCORE + ZCARD, X-RateLimit-Remaining header, Cloudflare WAF rate limiting rules, Upstash @upstash/ratelimit, leaky bucket shaping vs policing, distributed rate limiter consistency. NOT for DDoS mitigation specifically (different scale), CAPTCHA / bot management, full WAF design, or per-user quota billing.