graphing
$
npx mdskill add anthropics/claude-tag-plugins/graphingYou 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.
SKILL.md
.github/skills/graphingView on GitHub ↗
---
name: graphing
description: 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.
---
# 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.