From cafleet
Creates matplotlib charts and data visualizations, saving them as PNG files. Useful for generating plots from data during analysis or reporting.
How this skill is triggered — by the user, by Claude, or both
Slash command
/cafleet:cafleet-create-figureThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Generate matplotlib charts. Scripts, outputs, and data go in separate subdirectories under `figures/`.
Generate matplotlib charts. Scripts, outputs, and data go in separate subdirectories under figures/.
The skill writes a self-contained Python script that imports matplotlib. The run command is host-project-specific — refer to your project's .claude/rules/ for the canonical Python invocation. Any environment that provides matplotlib will run the script.
Before writing any script, read the Chart Type Selection and Color Rules sections. All charts in a deck share the same C_BAR / C_BAR_SEC palette regardless of data topic.
CRITICAL — placeholder convention. ${FIGURE_BASE}, ${BASE}, ${SRC_DIR}, ${OUTPUT_DIR}, and ${DATA_DIR} are template placeholders, NOT shell environment variables — resolve each to a concrete absolute path in your head and write that literal path into the script via the Write tool. Do NOT use export FIGURE_BASE=... or any shell variable assignment (Bash calls in Claude Code are ephemeral, so values do not persist between calls).
Resolve ${BASE} in this order:
cafleet-research-presentation skill passes its research folder as the figure base), use that path literally as ${BASE}. Skip base-dir resolution.cafleet-base-dir skill and follow its procedure (no path argument; CWD-based inference applies). Use the resolved ${BASE} verbatim — figures/scripts/data land under ${BASE}/figures/{src,output,data} whatever ${BASE} resolved to (repo root, /tmp/claude-code, etc.).Derive the subdirectories (each is a literal path string you will embed in the script):
${SRC_DIR} = ${BASE}/figures/src${OUTPUT_DIR} = ${BASE}/figures/output${DATA_DIR} = ${BASE}/figures/dataIf the directories do not exist yet, the Write tool auto-creates parent directories when you write the script file — do NOT call mkdir.
All subsequent steps use ${SRC_DIR}, ${OUTPUT_DIR}, and ${DATA_DIR} as literal resolved paths. Figure artifacts always live under ${BASE}/figures/; never directly at ${BASE} and never at /tmp unless ${BASE} itself is /tmp/claude-code.
Font: No setup needed. The theme font Noto Sans is available as a system font. Scripts set plt.rcParams['font.family'] = 'Noto Sans' (see template below).
Use the Write tool to create a .py file in ${SRC_DIR}.
The script must follow this pattern. Replace ${OUTPUT_DIR} and ${DATA_DIR} below with the literal concrete paths you resolved in Step 0 — the Python source you write must contain real path strings, not ${...} syntax:
import pathlib
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
# REPLACE these with literal paths from Step 0 resolution.
# e.g. pathlib.Path("/tmp/claude-code/researches/foo/figures/output")
OUTPUT_DIR = pathlib.Path("${OUTPUT_DIR}")
DATA_DIR = pathlib.Path("${DATA_DIR}")
plt.rcParams['font.family'] = 'Noto Sans'
# Data input
data_path = DATA_DIR / "data.csv"
# Create the figure
fig, ax = plt.subplots(constrained_layout=True)
# ... plotting logic ...
# Output — filename matches script name
script_stem = pathlib.Path(__file__).stem
output_path = OUTPUT_DIR / f"{script_stem}.png"
plt.savefig(output_path, dpi=150, bbox_inches="tight", facecolor='white')
plt.close()
print(f"Saved: {output_path}")
Key points:
ax.set_title() — when embedded in slides, the slide heading is the titleplt.show(), always plt.savefig() then plt.close()Run the script with the Python invocation documented in your host project's .claude/rules/ (typical patterns: uv run, python, or a mise task wrapper). This skill is invocation-agnostic — it only requires that the chosen environment provide matplotlib:
<project-python-runner> ${SRC_DIR}/script_name.py
Use the Read tool to load the output PNG from ${OUTPUT_DIR} and show it to the user.
Read input from ${DATA_DIR} with standard Python, using a with block so each file handle closes:
import csv, json
with open(DATA_DIR / "sales.csv") as f:
rows = list(csv.DictReader(f))
with open(DATA_DIR / "metrics.json") as f:
data = json.load(f)
Pick the right visualization for the data. Do NOT default to bar charts for everything.
| Data pattern | Chart type | matplotlib |
|---|---|---|
| Category comparison, ranking | Horizontal bar | ax.barh() |
| Time series, trend | Line chart | ax.plot() — format dates with mdates.DateFormatter (see below) |
| Correlation between 2 variables | Scatter plot | ax.scatter() |
| Distribution of one variable | Histogram | ax.hist() |
| Distribution comparison across groups | Box plot or violin plot | ax.boxplot() / ax.violinplot() |
| Matrix, cross-tabulation | Heatmap | ax.imshow() + annotate |
| Part-of-whole composition | Stacked bar | ax.bar(bottom=...) |
| Before/after, paired comparison | Dumbbell chart | ax.hlines() + ax.scatter() |
| Time-based categories (quarters, years) | Vertical bar | ax.bar() |
Horizontal vs vertical bars: Prefer barh when category labels are text. Use vertical bar only for time-based x-axes.
import matplotlib.dates as mdates
ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m'))
ax.xaxis.set_major_locator(mdates.MonthLocator(interval=3))
fig.autofmt_xdate() # auto-rotate date labels
For side-by-side comparison panels, use subplot_mosaic or GridSpec. All panels must share the same Y-axis range (see Prohibited section).
# Named panels — cleaner than index-based access
fig, axes = plt.subplot_mosaic(
[['left', 'right']],
figsize=(12, 5), constrained_layout=True,
)
axes['left'].barh(categories, values_a, color=C_BAR)
axes['right'].barh(categories, values_b, color=C_BAR_SEC)
For more complex grids, use matplotlib.gridspec.GridSpec.
1 chart = max 2 colors. This is the single most important design rule. Violating it produces amateurish, noisy charts.
These approximate the Slidev theme's CSS tokens (defined in OKLCH inside the my-slidev skill's theme stylesheet). When updating colors, keep both in sync.
# Primary (use for most bars/lines)
C_BAR = '#3B82F6' # blue-500
# Muted secondary (use for secondary series, negative values, or contrast)
C_BAR_SEC = '#94A3B8' # slate-400
# Accent (use for ONE highlighted item only — never for multiple bars)
C_ACCENT = '#1E40AF' # blue-800 (darker shade of primary)
# Negative accent (use only when one specific item is the "worst")
C_NEGATIVE = '#DC2626' # red-600
# Spine / grid
C_TEXT = '#1E293B'
C_TEXT_SEC = '#64748B'
C_GRID = '#E2E8F0'
| Data pattern | Colors to use |
|---|---|
| Single series, all same type | C_BAR for all |
| Single series, grouping by category | Lightness steps of blue (#1E40AF / #3B82F6 / #93C5FD) |
| Two series (e.g., Verified vs Pro) | C_BAR + C_BAR_SEC |
| Positive vs negative values | C_BAR (positive) + C_BAR_SEC (negative) |
| Highlight one worst item | C_BAR_SEC for all + C_NEGATIVE for worst |
| Highlight one best/top item | C_BAR for all + C_ACCENT (darker) for top |
Use annotations to call out a specific data point instead of (or alongside) color highlighting:
ax.annotate('Peak', xy=(x_peak, y_peak),
xytext=(x_peak + offset, y_peak + offset),
fontsize=10, color=C_TEXT,
arrowprops=dict(arrowstyle='->', color=C_TEXT_SEC, lw=1.5))
Keep annotation text short (1–3 words). Use C_TEXT for text, C_TEXT_SEC for arrows.
C_BAR (blue), not red. An adoption chart uses C_BAR, not green. Data meaning comes from axis labels and slide context. All charts in a deck must look like they belong togetherC_NEGATIVE or C_ACCENT for at most 1 highlighted item; everything else is C_BAR or C_BAR_SECC_NEGATIVE / C_ACCENT as primary color — highlight colors are only for emphasizing a single item, never for all data pointsApply to every figure:
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.spines['left'].set_color(C_GRID)
ax.spines['bottom'].set_color(C_GRID)
ax.tick_params(colors=C_TEXT_SEC)
ax.yaxis.grid(True, alpha=0.3, color='#CBD5E1')
ax.set_axisbelow(True)
Always use facecolor='white' in plt.savefig().
When a legend is needed, place it outside the plot area to avoid obscuring data:
ax.legend(bbox_to_anchor=(1.05, 1), loc='upper left', frameon=False)
For horizontal legends below the chart (multi-series line plots), use ax.legend(bbox_to_anchor=(0.5, -0.12), loc='upper center', ncol=3, frameon=False). Use frameon=False to keep the look clean; limit legend entries to ≤ 5, else reconsider the chart design.
plt.show()npx claudepluginhub himkt/cafleet --plugin cafleetGenerates publication-quality matplotlib/seaborn charts and diagrams with colorblind-accessible palettes, despined axes, and rich annotations using specific aesthetics. Use for data visualizations, plots, or diagrams.
Generates charts like bar, line, pie, scatter, heatmaps from data using Matplotlib. Analyzes structure, customizes styles, adds interactivity, exports to PNG, SVG, HTML.
Creates publication-quality charts from Python DataFrames or query results using matplotlib/seaborn/plotly. Recommends types for trends, comparisons, reports; supports interactive plots.