Data visualization implementation: chart type selection framework (when to use bar/line/scatter/pie/heatmap/treemap), D3.js patterns, Recharts/Chart.js/Victory integration, accessible charts (ARIA roles, color-blind safe palettes), responsive SVG patterns, and performance for large datasets. Use when implementing any chart or graph.
From clarcnpx claudepluginhub marvinrichter/clarc --plugin clarcThis skill uses the workspace's default tool permissions.
Designs and optimizes AI agent action spaces, tool definitions, observation formats, error recovery, and context for higher task completion rates.
Enables AI agents to execute x402 payments with per-task budgets, spending controls, and non-custodial wallets via MCP tools. Use when agents pay for APIs, services, or other agents.
Compares coding agents like Claude Code and Aider on custom YAML-defined codebase tasks using git worktrees, measuring pass rate, cost, time, and consistency.
| Goal | Chart Type | When NOT to use |
|---|---|---|
| Compare values across categories | Bar chart (vertical) | More than ~15 categories |
| Compare many categories | Horizontal bar | — |
| Show trend over time | Line chart | Non-continuous data |
| Show distribution | Histogram / Box plot | Fewer than 30 data points |
| Show correlation between two variables | Scatter plot | — |
| Show part-to-whole (≤5 segments) | Pie / Donut chart | More than 5 segments → use stacked bar |
| Show part-to-whole with many segments | Stacked bar chart | — |
| Show hierarchical data | Treemap / Sunburst | More than 3 levels deep |
| Show density across two dimensions | Heatmap | — |
| Show geographic data | Choropleth map | Non-geographic comparisons |
import * as d3 from 'd3';
const svg = d3.select('#chart').append('svg').attr('width', width).attr('height', height);
// Bind data
const bars = svg.selectAll('rect').data(data, d => d.id);
// Enter — new elements
bars.enter()
.append('rect')
.attr('x', d => xScale(d.category))
.attr('y', d => yScale(d.value))
.attr('width', xScale.bandwidth())
.attr('height', d => height - yScale(d.value))
.attr('fill', '#4f46e5');
// Update — existing elements
bars
.attr('y', d => yScale(d.value))
.attr('height', d => height - yScale(d.value));
// Exit — removed elements
bars.exit().remove();
// Linear scale — continuous numeric data
const xScale = d3.scaleLinear().domain([0, maxValue]).range([0, width]);
// Time scale — date/time axis
const xScale = d3.scaleTime().domain([startDate, endDate]).range([0, width]);
// Band scale — categorical axis (bar charts)
const xScale = d3.scaleBand()
.domain(categories)
.range([0, width])
.padding(0.1);
// Ordinal scale — color mapping
const colorScale = d3.scaleOrdinal()
.domain(categories)
.range(d3.schemeTableau10);
const xAxis = d3.axisBottom(xScale).ticks(5).tickFormat(d3.format('.2s'));
const yAxis = d3.axisLeft(yScale).tickFormat(d => `${d}%`);
svg.append('g')
.attr('class', 'x-axis')
.attr('transform', `translate(0, ${height})`)
.call(xAxis);
svg.append('g').attr('class', 'y-axis').call(yAxis);
// Use viewBox instead of fixed width/height
const svg = d3.select('#chart')
.append('svg')
.attr('viewBox', `0 0 ${width} ${height}`)
.attr('preserveAspectRatio', 'xMidYMid meet')
.style('width', '100%')
.style('height', 'auto');
bars.transition()
.duration(300)
.ease(d3.easeQuadOut)
.attr('height', d => height - yScale(d.value))
.attr('y', d => yScale(d.value));
| Library | Best for | Trade-offs |
|---|---|---|
| Recharts | Dashboards, simple charts, React-native API | Less customizable than D3 |
| Victory | Animated charts, React Native support | Larger bundle |
| Chart.js + react-chartjs-2 | Canvas-based, performance with many points | Canvas rendering |
| Observable Plot | Data exploration, R-style API | Not production-component-focused |
| D3 | Custom charts, complex interactions | High learning curve |
import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
function SalesChart({ data }: { data: { month: string; sales: number }[] }) {
return (
<ResponsiveContainer width="100%" height={300}>
<BarChart data={data} margin={{ top: 8, right: 16, left: 0, bottom: 0 }}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="month" />
<YAxis tickFormatter={(v) => `$${v.toLocaleString()}`} />
<Tooltip formatter={(v: number) => [`$${v.toLocaleString()}`, 'Sales']} />
<Bar dataKey="sales" fill="#4f46e5" radius={[4, 4, 0, 0]} />
</BarChart>
</ResponsiveContainer>
);
}
// Wrap SVG with role + aria-label
<svg
role="img"
aria-label="Monthly sales from January to December 2024, showing 40% growth"
viewBox="0 0 800 400"
>
{/* Provide title + description for screen readers */}
<title>Monthly Sales 2024</title>
<desc>Bar chart showing monthly sales revenue. January: $12,000. February: $15,000...</desc>
{/* Chart content */}
</svg>
Avoid encoding information in red/green alone. Use these tested palettes:
// Okabe-Ito — safe for all color vision deficiencies (8 colors)
const OKABE_ITO = [
'#E69F00', // orange
'#56B4E9', // sky blue
'#009E73', // bluish green
'#F0E442', // yellow
'#0072B2', // blue
'#D55E00', // vermillion
'#CC79A7', // reddish purple
'#000000', // black
];
// For sequential data — use perceptually uniform single-hue scale
const scale = d3.scaleSequential(d3.interpolateViridis).domain([0, maxValue]);
When color alone cannot be relied on, add patterns:
// SVG pattern for hatch fill
<defs>
<pattern id="hatch" patternUnits="userSpaceOnUse" width="4" height="4">
<path d="M-1,1 l2,-2 M0,4 l4,-4 M3,5 l2,-2" stroke="#4f46e5" strokeWidth="1" />
</pattern>
</defs>
<rect fill="url(#hatch)" />
import { useEffect, useRef, useState } from 'react';
function useChartDimensions(ref: React.RefObject<HTMLDivElement>) {
const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
useEffect(() => {
if (!ref.current) return;
const observer = new ResizeObserver(([entry]) => {
const { width, height } = entry.contentRect;
setDimensions({ width, height });
});
observer.observe(ref.current);
return () => observer.disconnect();
}, [ref]);
return dimensions;
}
// Rotate labels on small screens
const tickAngle = width < 400 ? -45 : 0;
const textAnchor = width < 400 ? 'end' : 'middle';
| Dataset size | Recommendation |
|---|---|
| < 500 points | SVG (better accessibility, hover events) |
| 500–5000 points | SVG with path aggregation or simplification |
| > 5000 points | Canvas rendering (Chart.js canvas, PixiJS) |
Do not render 100,000 points — aggregate first:
// Send aggregated data from server
// Bin into N buckets on backend, send max N=500 points to client
const binned = d3.bin()
.domain(xScale.domain())
.thresholds(200)(data.map(d => d.value));
import { useMemo } from 'react';
import { debounce } from 'lodash-es';
const debouncedResize = useMemo(
() => debounce(setDimensions, 100),
[]
);
role="img" and aria-label or <title> + <desc>viewBox set for responsive scaling