From plotly-consistency
Write, review, refactor, or debug Python plotting code with Plotly (plotly.express, graph_objects, interactive charts, fig.show, write_html, write_image, subplots) using one canonical, modern idiom set. Use this skill whenever code builds interactive charts, exports figures from scripts or CI, combines plot types, or when the user hits blank output in notebooks or scripts, "kaleido is required" export errors, manual trace loops where px would do, axes sorting categories wrong, or asks plotly.express vs graph_objects. Trigger it even when the user just says "make an interactive chart of this DataFrame" — without saying the word "Plotly."
How this skill is triggered — by the user, by Claude, or both
Slash command
/plotly-consistency:plotly-consistencyThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Plotly is stable, but it offers two APIs and generated code picks the wrong one or mixes
Plotly is stable, but it offers two APIs and generated code picks the wrong one or mixes
them: hand-assembling go.Figure(data=[go.Scatter(...)]) trace loops for charts
plotly.express builds in one line, then rebuilding figures to change a title instead
of update_layout. The other failure axis is environment: fig.show() in places with
no renderer. This skill pins the px-first idiom set for Plotly 5.x/6.x.
| Always | Never | Why |
|---|---|---|
px.line(df, x="date", y="price", color="ticker") | a for loop adding one go.Scatter per group | color=/symbol=/facet_* do the grouping, legend, and hover wiring; trace loops re-implement px badly. |
| long/tidy data into px | wide-format gymnastics per chart | px is built for tidy frames; reshape once (df.melt(...)) instead of fighting every call. |
customize after creation: fig.update_layout(...), fig.update_traces(...), fig.update_xaxes(...) | rebuilding in go because "px can't style it" | px returns a normal go.Figure; everything is reachable through update_* |
drop to go.Figure/fig.add_trace only for what px lacks | go-everything as a default style | go is the escape hatch (mixed types, fine trace control), not the house style. |
explicit delivery: fig.write_html("out.html") or fig.write_image("out.png") in scripts/CI | bare fig.show() everywhere | show() needs a renderer; scripts/CI have none — html/png are the artifacts. |
category_orders={"month": [...]} (or ordered categoricals) | trusting category order | Categories render in first-appearance order — plausible and wrong. |
labels={"gdp_per_cap": "GDP per capita"} in px calls | post-hoc axis-title surgery for renames | labels= renames axis titles, legend, and hover at once. |
make_subplots(rows=, cols=) + fig.add_trace(t, row=, col=) | nesting px figures inside subplots directly | px figures don't compose into make_subplots; move their fig.data traces in, or use px facets instead. |
px facets (facet_col="region") when the split is by a column | manual subplot grids for column splits | Facets keep shared axes/legend semantics for free. |
fig.update_layout(template="plotly_white") (one template, set deliberately) | mixing matplotlib idioms (plt.show(), plt.savefig) | Different library; the muscle-memory calls do nothing or error. |
House style:
import plotly.express as px
fig = px.scatter(
df,
x="gdp_per_cap",
y="life_exp",
color="continent",
size="population",
hover_name="country",
log_x=True,
labels={"gdp_per_cap": "GDP per capita (log)", "life_exp": "Life expectancy"},
title="Wealth and longevity",
)
fig.update_layout(template="plotly_white", legend_title_text="")
fig.update_traces(marker_opacity=0.7)
fig.write_html("wealth.html", include_plotlyjs="cdn")
category_orders= every time the order means
something.fig.show() blank or hanging: no renderer in scripts, wrong renderer in some
notebook setups. In notebooks fig.show() (or trailing fig) is fine; elsewhere write
files. pio.renderers.default is the knob when notebook display misbehaves.write_image requires kaleido (pip install -U kaleido) — and that's a static
raster/vector export; write_html needs nothing extra and stays interactive.df.dtypes when an axis looks
like a barcode.update_traces hits every trace unless filtered: fig.update_traces( marker_color="red", selector=dict(name="Losses")) — unfiltered restyles repaint the
whole figure identically (a "why is everything one color" bug).hover_data=["sku", "margin"] adds
more — manually building hovertemplates per trace is go-era ceremony.render_mode="webgl" in px (or
go.Scattergl) — silent browser crawl otherwise.Target Plotly 5.x/6.x (idioms identical across both; 6.0 modernized internals and
default renderers). Era tells in old code: plotly.plotly / py.iplot (the pre-4
online-mode era — dead), init_notebook_mode (rarely needed now), fig["layout"] item
assignment (works, but update_* is the style). px exists since 4.0; there is no reason
to generate pre-px patterns.
scatter, line, bar, histogram, box, violin,
imshow, choropleth...); map columns via color/size/symbol/facet_*;
rename with labels=; pin category_orders=.update_layout / update_xaxes / update_traces(selector=...);
one template per project.fig.show(); script/CI → write_html
(interactive artifact) and/or write_image (kaleido) with explicit width/height/scale.For the px↔go decision table, update_* selector reference, subplot/facet recipes, and
export options, read references/plotly-patterns.md.
Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub guidogl/plotly-consistency --plugin plotly-consistency