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.

  • Design & UX
  • $data-visualization-design
  • 165 lines
  • 2 files
  • ~2,000 tokens
  • MIT
  • Updated

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- data-visualization-design

Installs to ~/.agents/skills/data-visualization-design/ for every project on this machine.

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

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.

~/.agents/skills/data-visualization-design/SKILL.md

--- name: data-visualization-design description: Designs charts that answer a specific question - picks the chart type from the comparison being made, sets honest scales and baselines, assigns… license: MIT ---

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…DefaultAvoid
Compare magnitudes across categoriesHorizontal bar, sortedPie with more than 3-4 slices, 3D bars
See change over time (many points)LineBars for 50+ time points
Compare a few periods per categoryGrouped bar (2-3 groups) or slope chartGrouped bars with 5+ series
Show part-to-whole at one momentStacked single bar or a pie with 2-4 slicesDonuts with 8 slices
Show part-to-whole over timeStacked area (few series) or 100% stacked barStacked lines (unreadable middle series)
Show distributionHistogram, box plot, strip/beeswarm for small nMean-only bar with no spread
Show relationship of two measuresScatter, with trend line if justifiedDual-axis line chart
Rank change between two pointsSlope chart or dumbbellTwo side-by-side pies
Show many series’ shapeSmall multiples with shared axesOne spaghetti chart with 12 lines
Show one number vs targetBig number with delta and sparkline, or bullet chartGauge
Show geographyChoropleth only if geography matters; else sorted barMap 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 typePaletteRule
Categories (nominal)Categorical, max 6-8 huesBeyond 8, group into “Other” or use small multiples
Ordered magnitudeSequential, light to dark one hueDarker = more on light backgrounds
Deviation from a meaningful midpointDiverging, two hues with neutral centreCentre must be a real value (0, target, average)
Highlight one seriesGray for context, one accent colorThe 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 an aria-label that states the takeaway, not “line chart”.
  • Interactive tooltips must be reachable by keyboard, or the values must be available another way.
  • Respect prefers-reduced-motion for animated transitions.

Verification

  1. 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?
  2. Render at the real display size (dashboard card, slide, mobile width), not only full screen. Check labels do not collide.
  3. Grayscale and deuteranopia emulation (Chrome DevTools Rendering panel): series still distinguishable.
  4. 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.yamlgenerated from this page

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.

esc
Just launched

Codex Skills Pro is live

79 premium skills for Codex, each with scripts, references and an evidence report that proves the result.

Bought one by one$882.21
All 79 skills$49
94% off

That's $0.62 a skill, paid once.

Get all 79 for $49Or pick single skills from $4.99 →

Secure checkout by Creem