# Graphing

> Compose polished charts (timeseries, bar, line, area, pie, scatter, or anything else the data calls for) from tabular data using the chartkit primitives, producing PNG, SVG, or self-contained interactive HTML. Use when the user asks to chart, graph, plot, or visualize data and wants something better than raw matplotlib defaults.

- Skill: `anthropics/graphing` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add anthropics/graphing`
- Raw SKILL.md: https://api.skillmd.com/api/skills/anthropics/graphing/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: anthropics (https://skillmd.com/u/anthropics)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/anthropics/graphing

---


# Graphing

You write the plotting code. The kit provides the parts that should stay consistent across every chart: typography, color derivation from the background, the title and caption frame, and offline HTML packaging. Everything about the chart itself is your judgement applied to the data in front of you.

## The primitives

Import `chartkit` by putting this skill's `scripts/` directory on `sys.path`. Use its absolute path, not a relative path, since your working directory is the user's project. The examples below write it as `/path/to/graphing/scripts`; substitute the real path.

Relevant primitives:
- `theme(bg, font)`: sets matplotlib rcParams and returns resolved colors. Foreground colors derive from background luminance, so a dark `bg` produces a correct dark chart. Fields: `bg` `dark` `text` `muted` `grid` `spine` `accent` `secondary` `series` `font_css`. 
- `palette(n, base)`: n colors. No base cycles the default series, a hex base builds a ramp from it, a list cycles the list. 
- `finish(ax, title, subtitle, source)`: The typographic frame. Left-aligned bold title, muted subtitle, small provenance caption. 
- `save(fig, stem, formats, dpi)`: Writes `stem.png`, `stem.svg`, or both. Returns the paths. 
- `write_html(out, data, component_js, title, bg, font)`: Self-contained interactive page. Inlines React, ReactDOM, react-is, and Recharts from `third_party/` so the file opens offline. Your component reads `window.__CHART_DATA__` and renders into `#root`. 
- `zero_fill_days(pairs)` `rolling_mean(values, w)` `log_floor(values)`: Small data helpers for the gotchas listed below. Use them only when they fit. 

## Steps

1. **Look at the data and decide what it deserves.** Shape, count, and meaning drive the choice: trends over time want lines or day bars, ranked categories want horizontal bars, parts of a whole with few slices can be a pie, correlation wants a scatter. Nothing limits you to those: stacked areas, dual axes, small multiples, annotated thresholds are all just code you write.

2. **Infer colors from context.** Check your conversation and memory for design indicators: a tailwind config, CSS variables, or brand guidelines, and consider the semantic meaning of the data (red or amber for errors, green for success). Pass the destination background to `theme(bg=...)` and brand colors to `palette(base=...)` or directly. Fall back to the defaults only when nothing is inferable. The font defaults to Inter and only changes when the user names a typeface or the destination has a documented brand font.

3. **Write a short script** in your scratchpad that imports chartkit, builds the figure with plain matplotlib (or a Recharts component for interactive output), calls `finish`, and saves.

4. **Render and look at the result.** Read the PNG back and check it with your own eyes before handing it over: labels legible, nothing overlapping, colors distinguishable, the story of the data actually visible. Fix and re-render until it is right.

## The shape of a chart script

PNG or SVG via matplotlib:

```python
import sys
sys.path.insert(0, "/path/to/graphing/scripts")
import chartkit as ck
import matplotlib.pyplot as plt

c = ck.theme()                       # or theme(bg="#0f1419") for a dark surface
fig, ax = plt.subplots(figsize=(10, 5))
# ...plain matplotlib against the data, using c.accent, c.secondary, ck.palette(...)
ck.finish(ax, title="What the chart shows", subtitle="scope or time range",
          source="source: where the data came from, date")
print(ck.save(fig, "out/report", formats=("png", "svg")))
```

Interactive HTML via Recharts, written as plain `React.createElement` with no JSX and no build step:

```python
component = """
(function () {
  var e = React.createElement
  var R = window.Recharts
  var DATA = window.__CHART_DATA__
  function App() {
    return e(R.ResponsiveContainer, { width: "100%", height: 420 },
      e(R.LineChart, { data: DATA },
        e(R.CartesianGrid, { stroke: "GRID", vertical: false }),
        e(R.XAxis, { dataKey: "x" }), e(R.YAxis, null),
        e(R.Tooltip, null),
        e(R.Line, { dataKey: "y", stroke: "ACCENT", strokeWidth: 2.4,
                    dot: false, isAnimationActive: false })))
  }
  ReactDOM.createRoot(document.getElementById("root")).render(e(App))
})()
"""
ck.write_html("out/report.html", data, component, title="What the chart shows")
```

Substitute real colors from the theme into the component and keep `isAnimationActive: false` so the chart is complete the moment the file opens.

## Judgement, not flags

These are the defaults of good charts. Deviate when the data argues for it.

- Rotate x labels only when they would otherwise collide. Short labels stay horizontal.
- Cap bar width when there are few categories. Two bars should not fill the canvas edge to edge.
- Label bars with their values when there are roughly a dozen or fewer. Axes and gridlines are for reading trends, not exact values.
- Rank categorical bars by value unless the categories have a natural order.
- Long category names read better on a horizontal bar chart.
- A title states what the chart shows. A subtitle carries the time range or scope. The source caption says where the data came from and when. Skip any of them only deliberately.
- Legends only when there is more than one series. A single series is named by the title.
- Annotate the chart with what matters: a deploy line, a threshold, the peak, the anomaly the user asked about. An annotation the reader did not need is clutter.

## Gotchas the helpers exist for

- Calendar gaps: a timeseries with missing days misleads on a continuous axis. `zero_fill_days` fills them with zero. Skip it when zeros would be the lie (sparse sampling rather than absence of events).
- Rolling averages: `rolling_mean` is trailing, early points average what exists so far. Do not center it on data that ends today.
- Log scales: bars whose value equals the axis minimum get zero height. `log_floor` gives a lower bound one decade down.
- Negative values: matplotlib handles them, but check the y limits include them and add a zero line when bars go both ways. Recharts y domains default to starting at zero, so pass an explicit `domain` on the YAxis when data is negative or log scaled.

## Verification

Run a minimal render and look at it.

```bash
python3 -c "
import sys
sys.path.insert(0, '/path/to/graphing/scripts')
import chartkit as ck
import matplotlib.pyplot as plt
c = ck.theme()
fig, ax = plt.subplots(figsize=(8, 4))
ax.bar(['A', 'B', 'C'], [10, 30, 20], color=c.accent, width=0.5)
ck.finish(ax, title='chartkit smoke test')
print(ck.save(fig, '/tmp/ck_smoke'))
"
```

Confirm a non-empty PNG is written, then read it back and check it renders correctly.

