Data Visualization Design skill for Codex
Charts that state their point in five seconds. Codex starts every chart from the question it must answer, then picks the type from the comparison being made, sets honest baselines, and assigns categorical, sequential or diverging color that survives color blindness. Legends give way to direct labels, titles state the finding, and each chart ships with a table or text alternative. Examples cover Vega-Lite, matplotlib and Recharts.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- data-visualization-design Installs to ~/.agents/skills/data-visualization-design/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install data-visualization-design The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project data-visualization-design Installs to .agents/skills/data-visualization-design/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/data-visualization-design.zipunzip -o data-visualization-design.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) data-visualization-design PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
104-line POSIX script. No sudo, no telemetry. Read install.sh
Use it in Codex
Start a prompt with $data-visualization-design, or pick it from /skills.
You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.
What Codex reads before loading it
Designs charts that answer a specific question - picks the chart type from the comparison being made, sets honest scales and baselines, assigns categorical, sequential or diverging color that survives color blindness, replaces legends with direct labels, writes insight titles and annotations, and adds table or text alternatives. Library-agnostic, with examples for Vega-Lite, Recharts and matplotlib. Use when choosing a chart type, critiquing a chart, fixing a misleading or cluttered graph, or picking chart colors.
Use it when
- You are choosing a chart type for a report, slide or product screen
- A chart is cluttered, misleading or needs a legend to be understood
- Chart colors fail for color-blind readers or clash across charts
- A chart needs an insight title, annotations or an accessible alternative
Use something else when
- You need D3 scales, joins and transitions in code: use d3-visualizations
- You are arranging KPIs and many charts on one screen: use dashboard-design
How to check the result
A reader can state the takeaway from a screenshot without the legend, labels do not collide at the real display size, and series stay distinct in grayscale.
The mistake it prevents
Truncating the y-axis on a bar chart, which turns a small difference into a dramatic one.
A good chart lets the reader answer one stated question in about five seconds, without the legend, and the visual encoding cannot be read as a different conclusion than the data supports. Start from the question, not the chart type the library makes easiest.
Workflow
- Write the question the chart answers and the one-sentence takeaway.
- Identify data shape: how many measures, categories, time points, rows.
- Pick the chart from the comparison table below.
- Set scales and baselines honestly; sort categories meaningfully.
- Assign color by data type; check color-blind safety.
- Label directly, annotate the point that matters, title with the takeaway.
- Add the accessible alternative; verify at the real size and in grayscale.
Read AGENTS.md and check which charting library the project already uses
(rg -l "recharts|chart.js|echarts|vega|d3-|plotly|matplotlib|seaborn|altair") before
proposing one.
Choose the chart from the comparison
| The reader needs to… | Default | Avoid |
|---|---|---|
| Compare magnitudes across categories | Horizontal bar, sorted | Pie with more than 3-4 slices, 3D bars |
| See change over time (many points) | Line | Bars for 50+ time points |
| Compare a few periods per category | Grouped bar (2-3 groups) or slope chart | Grouped bars with 5+ series |
| Show part-to-whole at one moment | Stacked single bar or a pie with 2-4 slices | Donuts with 8 slices |
| Show part-to-whole over time | Stacked area (few series) or 100% stacked bar | Stacked lines (unreadable middle series) |
| Show distribution | Histogram, box plot, strip/beeswarm for small n | Mean-only bar with no spread |
| Show relationship of two measures | Scatter, with trend line if justified | Dual-axis line chart |
| Rank change between two points | Slope chart or dumbbell | Two side-by-side pies |
| Show many series’ shape | Small multiples with shared axes | One spaghetti chart with 12 lines |
| Show one number vs target | Big number with delta and sparkline, or bullet chart | Gauge |
| Show geography | Choropleth only if geography matters; else sorted bar | Map of absolute counts (shows population) |
If two charts are plausible, pick the one with position on a common scale; people read position more accurately than length, angle, area or color intensity.
Scales and honesty
- Bars and areas start at zero. Truncating a bar axis exaggerates differences.
- Lines may use a non-zero baseline when the change is the point; label the axis clearly.
- Never use dual y-axes to imply correlation; use two aligned charts or index both to 100.
- Use log scales for data spanning orders of magnitude, and say “log scale” on the axis.
- Normalise counts by population or base when comparing regions or groups of different size (rate per 1,000, percentage).
- Keep aspect ratio so typical slopes sit near 45 degrees; very flat or very tall plots hide or exaggerate trends.
- Sort categorical bars by value unless the category has natural order (months, sizes).
- Show uncertainty when it exists (confidence bands, error bars) and say what it is.
Color
| Data type | Palette | Rule |
|---|---|---|
| Categories (nominal) | Categorical, max 6-8 hues | Beyond 8, group into “Other” or use small multiples |
| Ordered magnitude | Sequential, light to dark one hue | Darker = more on light backgrounds |
| Deviation from a meaningful midpoint | Diverging, two hues with neutral centre | Centre must be a real value (0, target, average) |
| Highlight one series | Gray for context, one accent color | The best default for presentations |
A color-blind-safe categorical starting set (Okabe-Ito):
#E69F00 #56B4E9 #009E73 #F0E442 #0072B2 #D55E00 #CC79A7 #000000. For sequential and
diverging scales use perceptually uniform maps (viridis, cividis, ColorBrewer Blues,
RdBu). Never rainbow/jet for ordered data.
- Keep a series’ color identical across every chart in a report or dashboard.
- Lines and marks need 3:1 contrast against the background (WCAG 1.4.11). Yellow on white fails; darken or outline it.
- Do not encode meaning by red/green alone; add direct labels, markers or dash styles.
- Gray is a color: use it for context, gridlines and de-emphasised series.
Labels, titles and annotation
- Title states the finding: “Checkout errors doubled after the 3.2 release”, not “Errors by week”. Subtitle gives the unit, scope and source.
- Label series at the end of the line instead of a legend. If a legend is unavoidable, order it to match the visual order.
- Annotate the event that explains the shape (release, outage, campaign) with a short note and a thin rule.
- Format numbers for reading:
1.2M,34%, thin axis ticks at round values, units on the axis title once rather than on every tick. - Remove chart junk: heavy gridlines, borders, background fills, 3D, drop shadows. Keep light horizontal gridlines when exact reading matters.
Vega-Lite: highlight one series, direct label
{
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
"title": {"text": "EU signups overtook US in March", "subtitle": "Weekly signups, 2026"},
"data": {"url": "signups.csv"},
"encoding": {
"x": {"field": "week", "type": "temporal", "title": null},
"y": {"field": "signups", "type": "quantitative", "title": "Signups per week"},
"color": {"condition": {"test": "datum.region === 'EU'", "value": "#0072B2"}, "value": "#c4c4c4"},
"detail": {"field": "region"}
},
"layer": [
{"mark": {"type": "line", "strokeWidth": 2}},
{"transform": [{"window": [{"op": "rank", "as": "r"}], "sort": [{"field": "week", "order": "descending"}], "groupby": ["region"]},
{"filter": "datum.r === 1"}],
"mark": {"type": "text", "align": "left", "dx": 6},
"encoding": {"text": {"field": "region"}}}
]
}
matplotlib: clean defaults
fig, ax = plt.subplots(figsize=(7, 4), layout="constrained")
for name, s in series.items():
color = "#0072B2" if name == focus else "#c4c4c4"
ax.plot(s.index, s.values, color=color, lw=2 if name == focus else 1)
ax.annotate(name, (s.index[-1], s.values[-1]), xytext=(4, 0),
textcoords="offset points", va="center", color=color)
ax.spines[["top", "right"]].set_visible(False)
ax.set_title("EU signups overtook US in March", loc="left", fontweight="bold")
ax.yaxis.grid(True, color="#eeeeee"); ax.set_axisbelow(True)
Recharts and Chart.js: remove the default <Legend /> when lines can be labelled, set
isAnimationActive={false} for exported or printed charts, and pass formatted tick
functions rather than raw numbers.
Accessibility
- Provide the data: a visually hidden or toggleable
<table>, or a CSV download. - Give the SVG or canvas
role="img"and anaria-labelthat states the takeaway, not “line chart”. - Interactive tooltips must be reachable by keyboard, or the values must be available another way.
- Respect
prefers-reduced-motionfor animated transitions.
Verification
- Show the chart for five seconds to someone (or describe it back yourself from a screenshot): can the takeaway be stated without reading the legend?
- Render at the real display size (dashboard card, slide, mobile width), not only full screen. Check labels do not collide.
- Grayscale and deuteranopia emulation (Chrome DevTools Rendering panel): series still distinguishable.
- Recompute one plotted value from the source data and confirm the chart shows it.
Critique output
## Chart review: <chart name>
Question it should answer: <...>
Current takeaway a reader would get: <...>
| Issue | Why it misleads or slows reading | Fix |
|---|---|---|
| Y axis starts at 80 on a bar chart | 3% difference looks like 4x | Start at 0 or switch to dot plot |
Recommended chart: <type> because <comparison>
For building charts in D3 code use $d3-visualizations; for laying out many charts and
KPIs on one screen use $dashboard-design.
What the zip contains
SKILL.md plus the interface file below. Nothing runs on install.
agents/openai.yaml
Gives the Codex app a display name, a one-line description and a starter prompt for this skill.
interface:
display_name: "Data Visualization Design"
short_description: "Charts that state their point in five seconds"
default_prompt: "$data-visualization-design Which chart should I use to show churn by plan across the last 12 months?" About this skill
Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.