Create and audit truthful, accessible, publication-ready scientific figures with Matplotlib, Seaborn, or Plotly. Use for figure design, multi-panel layouts, uncertainty and missing-data displays, color/contrast review, image metadata validation, and journal export planning.
Build figures that preserve scientific meaning before optimizing appearance. Separate universal principles from dated publisher rules, preserve raw data and transformations, use color redundantly, and inspect delivered files rather than trusting plotting defaults.
Non-negotiable guardrails
Never alter, hide, invent, or selectively enhance data to improve a figure.
Preserve raw tables/images, exclusions, missing-value codes, analysis code, normalization, binning, image adjustments, and random seeds.
Do not infer journal requirements. Identify the exact journal, article type, figure type, and submission phase; verify its live official guidance.
Do not claim that a palette, DPI value, format, or automated report makes a figure accessible or journal-compliant.
Do not silently connect missing observations, suppress inconvenient points, upsample images as if detail increased, or tune axes/dual axes to exaggerate a conclusion.
Keep interactive and static outputs as distinct deliverables. Interactive hover is not a substitute for labels, alt text, keyboard access, an accessible data table, or a static fallback.
Read references/publication_guidelines.md for deceptive-encoding and integrity checks. Read references/journal_requirements.md only after the target and phase are known.
Workflow
1. Define the evidence and destination
Record:
audience and medium: manuscript, web, slide, poster, supplement;
exact publisher/journal, article type, submission phase, and intended final width;
source-data paths/identifiers and output provenance.
If requirements are not known, create a provisional general figure and label all publisher choices as pending verification.
2. Choose an honest encoding
Prefer position on a common scale. Before coding, check:
Bars/areas: normally include zero because length/area is measured from a baseline.
Points/lines: nonzero limits can be valid; show context and disclose breaks.
Uncertainty: name SD, SE, CI, percentile, posterior, or another interval; state n and the unit of replication.
Raw observations: show them when feasible; do not let jitter obscure categories/values.
Missing data: distinguish missing, zero, censored, and excluded; use gaps or explicit model/interpolation styling.
Area/volume: scale area/volume, not radius/diameter; avoid decorative 3D.
Log axes: label the base/transform and declare how zero/negative values are handled.
Binning/smoothing: record edges, bandwidth/window, method, and sensitivity.
Normalization: state formula/reference and keep limits consistent across compared panels.
Dual axes: prefer aligned panels; if unavoidable, justify units and do not engineer apparent correlation.
Images: preserve originals, disclose whole-image adjustments, show scale bars, and avoid clipped/erased background.
3. Design accessibility in, not after
Use color plus marker, line style, hatching, direct label, or panel separation.
Choose qualitative, sequential, diverging, or cyclic color according to data semantics.
Audit foreground/background contrast at the rendered size.
Make missing and out-of-range values explicit.
Provide alt text, a longer description for complex figures, and underlying data for web delivery.
Treat WCAG 2.2 as web guidance: 4.5:1 normal text, 3:1 large text, and 3:1 for graphical objects required for understanding; color cannot be the only cue. Applicability and exceptions matter.
See references/color_palettes.md. A grayscale screen is useful but is not a complete color-vision or accessibility test.
4. Implement with scoped styles
Use Matplotlib's object-oriented API and temporary style contexts:
import matplotlib.pyplot as plt
from style_presets import style_context
with style_context("default", palette_name="okabe_ito_on_white"):
fig, ax = plt.subplots(
figsize=(89 / 25.4, 60 / 25.4),
layout="constrained",
)
ax.plot(x, y, marker="o", label="Observed")
ax.set(xlabel="Time (hours)", ylabel="Response (unit)")
ax.legend()
layout="constrained" supports colorbars, nested GridSpec, subfigures, and subplot_mosaic. Do not call tight_layout() afterward; it disables constrained layout.
For exact physical dimensions, do not use bbox_inches="tight" unless the changed page size is intentional.
Axes-level functions fit custom Matplotlib layouts; figure-level functions create their own figures/facets. Do not customize Seaborn's internal artist lists as if they were stable API.
Plotly
Use write_html() for interaction and write_image()/plotly.io.write_images() for static output.
Kaleido 1.3.0 requires Chrome/Chromium; it no longer bundles Chrome.
Current static formats: PNG, JPEG, WebP, SVG, PDF. EPS is Kaleido v0-only.
Do not pass deprecated engine= or use Orca/plotly.io.kaleido.scope.
width, height, and scale control pixels; scale=3 is not inherently “300 DPI.”
WebGL traces embed raster content in PDF/SVG.
Fully offline exports need local external assets when a figure references MathJax/topojson/tiles.
The exporter refuses implicit overwrite, writes atomically, keeps vector DPI for embedded rasters, uses TIFF LZW, and can use PDF/PS Type 42 fonts. It does not validate scientific content or publisher acceptance.
For editable fonts:
PDF/PS Type 42 embeds TrueType fonts.
svg.fonttype="none" keeps text editable/searchable but does not embed fonts; appearance depends on installed fonts.
svg.fonttype="path" preserves glyph appearance as paths but loses editable/searchable text.
Use an opaque explicit background unless transparency is required; blending against another background changes apparent contrast.
6. Inspect, compare, and review
Inspect file metadata.
Audit palette contrast/grayscale separation.
Compare against a dated publisher snapshot.
View at final size in the manuscript/web context.
Manually review fonts, embedded rasters, clipping, legends, scale bars, image integrity, caption, alt text, and source data.
Re-check the live target-journal page immediately before upload.
Pinned snapshot
The examples and smoke tests use direct package pins current on 2026-07-23:
This is a dated direct-dependency snapshot, not a transitive lock. Use the project's uv lock for exact replay; this skill intentionally ships no dependency lock.
Bundled CLIs
All helpers are deterministic, network-free, bounded, reject symlink inputs/destinations where relevant, and refuse overwrite unless --force is explicit.
Supports raster images (Pillow), SVG, PDF (pypdf), and EPS/PS. Reports dimensions, DPI/effective DPI, mode, alpha, ICC presence, compression, page size, and conservative first-page PDF font resources. It does not inspect every embedded raster in a vector container.
references/sources.md: official URLs, dates, versions, and research basis.
Final review checklist
Raw data/images and transformation code are preserved.
Missing values, exclusions, bins, normalization, and uncertainty are explicit.
Baselines, scales, limits, and area/volume encodings are honest.
Color is redundant and rendered contrast was reviewed.
Figure has an accessible description/data alternative where applicable.
Physical dimensions, DPI, format, fonts, transparency, and file size were inspected after export.
Publisher rules were verified for the exact journal and phase.
No automated report is presented as a scientific, accessibility, or compliance certification.
1---
2name: scientific-visualization
3description: Create and audit truthful, accessible, publication-ready scientific figures with Matplotlib, Seaborn, or Plotly. Use for figure design, multi-panel layouts, uncertainty and missing-data displays, color/contrast review, image metadata validation, and journal export planning.
4license: MIT
5compatibility: Requires Python 3.11+ and uv for pinned examples. Bundled CLIs are network-free and load Matplotlib, Pillow, or pypdf only when needed. Plotly static export with Kaleido v1 requires a compatible Chrome/Chromium installation.
6allowed-tools: Read Write Edit Bash Glob Grep
7metadata:
8 version: "1.1"
9 skill-author: K-Dense Inc.
10---
1112# Scientific Visualization
1314Build figures that preserve scientific meaning before optimizing appearance. Separate universal principles from dated publisher rules, preserve raw data and transformations, use color redundantly, and inspect delivered files rather than trusting plotting defaults.
1516## Non-negotiable guardrails
1718- Never alter, hide, invent, or selectively enhance data to improve a figure.
19- Preserve raw tables/images, exclusions, missing-value codes, analysis code, normalization, binning, image adjustments, and random seeds.
20- Do not infer journal requirements. Identify the exact journal, article type, figure type, and submission phase; verify its live official guidance.
21- Do not claim that a palette, DPI value, format, or automated report makes a figure accessible or journal-compliant.
22- Do not silently connect missing observations, suppress inconvenient points, upsample images as if detail increased, or tune axes/dual axes to exaggerate a conclusion.
23- Keep interactive and static outputs as distinct deliverables. Interactive hover is not a substitute for labels, alt text, keyboard access, an accessible data table, or a static fallback.
2425Read `references/publication_guidelines.md` for deceptive-encoding and integrity checks. Read `references/journal_requirements.md` only after the target and phase are known.
2627## Workflow
2829### 1. Define the evidence and destination
3031Record:
3233- audience and medium: manuscript, web, slide, poster, supplement;
34- exact publisher/journal, article type, submission phase, and intended final width;
35- variable semantics, units, sample/replicate structure, missing/censored values;
36- estimator and uncertainty definition;
37- transformations: filtering, aggregation, normalization, smoothing, bins, image processing;
38- source-data paths/identifiers and output provenance.
3940If requirements are not known, create a provisional general figure and label all publisher choices as pending verification.
4142### 2. Choose an honest encoding
4344Prefer position on a common scale. Before coding, check:
4546- **Bars/areas:** normally include zero because length/area is measured from a baseline.
47- **Points/lines:** nonzero limits can be valid; show context and disclose breaks.
48- **Uncertainty:** name SD, SE, CI, percentile, posterior, or another interval; state `n` and the unit of replication.
49- **Raw observations:** show them when feasible; do not let jitter obscure categories/values.
50- **Missing data:** distinguish missing, zero, censored, and excluded; use gaps or explicit model/interpolation styling.
51- **Area/volume:** scale area/volume, not radius/diameter; avoid decorative 3D.
52- **Log axes:** label the base/transform and declare how zero/negative values are handled.
53- **Binning/smoothing:** record edges, bandwidth/window, method, and sensitivity.
54- **Normalization:** state formula/reference and keep limits consistent across compared panels.
55- **Dual axes:** prefer aligned panels; if unavoidable, justify units and do not engineer apparent correlation.
56- **Images:** preserve originals, disclose whole-image adjustments, show scale bars, and avoid clipped/erased background.
5758### 3. Design accessibility in, not after
5960- Use color plus marker, line style, hatching, direct label, or panel separation.
61- Choose qualitative, sequential, diverging, or cyclic color according to data semantics.
62- Audit foreground/background contrast at the rendered size.
63- Make missing and out-of-range values explicit.
64- Provide alt text, a longer description for complex figures, and underlying data for web delivery.
65- Treat WCAG 2.2 as web guidance: 4.5:1 normal text, 3:1 large text, and 3:1 for graphical objects required for understanding; color cannot be the only cue. Applicability and exceptions matter.
6667See `references/color_palettes.md`. A grayscale screen is useful but is not a complete color-vision or accessibility test.
6869### 4. Implement with scoped styles
7071Use Matplotlib's object-oriented API and temporary style contexts:
7273```python
74import matplotlib.pyplot as plt
7576from style_presets import style_context
7778with style_context("default", palette_name="okabe_ito_on_white"):
79 fig, ax = plt.subplots(
80 figsize=(89 / 25.4, 60 / 25.4),
81 layout="constrained",
82 )
83 ax.plot(x, y, marker="o", label="Observed")
84 ax.set(xlabel="Time (hours)", ylabel="Response (unit)")
85 ax.legend()
86```
8788`layout="constrained"` supports colorbars, nested GridSpec, subfigures, and `subplot_mosaic`. Do not call `tight_layout()` afterward; it disables constrained layout.
8990For exact physical dimensions, do not use `bbox_inches="tight"` unless the changed page size is intentional.
9192#### Color normalization
9394```python
95import matplotlib as mpl
9697norm = mpl.colors.TwoSlopeNorm(vmin=-2, vcenter=0, vmax=5)
98cmap = mpl.colormaps["RdBu_r"].with_extremes(bad="#777777")
99image = ax.imshow(values, norm=norm, cmap=cmap, interpolation="nearest")
100fig.colorbar(image, ax=ax, label="Change (unit)")
101```
102103Use `LogNorm`, `CenteredNorm`, `SymLogNorm`, `BoundaryNorm`, or `TwoSlopeNorm` only when its mapping matches the scientific meaning.
104105#### Seaborn
106107Seaborn 0.13.2 uses the current `errorbar` API:
108109```python
110sns.lineplot(
111 data=frame,
112 x="time",
113 y="response",
114 hue="treatment",
115 style="treatment",
116 markers=True,
117 errorbar=("ci", 95),
118 n_boot=5000,
119 seed=20260723,
120 ax=ax,
121)
122```
123124Axes-level functions fit custom Matplotlib layouts; figure-level functions create their own figures/facets. Do not customize Seaborn's internal artist lists as if they were stable API.
125126#### Plotly
127128- Use `write_html()` for interaction and `write_image()`/`plotly.io.write_images()` for static output.
129- Kaleido 1.3.0 requires Chrome/Chromium; it no longer bundles Chrome.
130- Current static formats: PNG, JPEG, WebP, SVG, PDF. EPS is Kaleido v0-only.
131- Do not pass deprecated `engine=` or use Orca/`plotly.io.kaleido.scope`.
132- `width`, `height`, and `scale` control pixels; `scale=3` is not inherently “300 DPI.”
133- WebGL traces embed raster content in PDF/SVG.
134- Fully offline exports need local external assets when a figure references MathJax/topojson/tiles.
135136### 5. Export explicitly and record provenance
137138```python
139from figure_export import export_figure
140141report = export_figure(
142 fig,
143 "outputs/figure1",
144 formats=["pdf", "png"],
145 dpi=600,
146 bbox_inches=None, # preserve figure page dimensions
147 provenance={
148 "raw_data": "data/source.csv",
149 "transformations": ["predeclared QC filter", "group mean"],
150 "uncertainty": "95% bootstrap CI; seed 20260723",
151 "missing_data": "retained as gaps",
152 },
153 write_manifest=True,
154)
155```
156157The exporter refuses implicit overwrite, writes atomically, keeps vector DPI for embedded rasters, uses TIFF LZW, and can use PDF/PS Type 42 fonts. It does not validate scientific content or publisher acceptance.
158159For editable fonts:
160161- PDF/PS Type 42 embeds TrueType fonts.
162- `svg.fonttype="none"` keeps text editable/searchable but does not embed fonts; appearance depends on installed fonts.
163- `svg.fonttype="path"` preserves glyph appearance as paths but loses editable/searchable text.
164165Use an opaque explicit background unless transparency is required; blending against another background changes apparent contrast.
166167### 6. Inspect, compare, and review
1681691. Inspect file metadata.
1702. Audit palette contrast/grayscale separation.
1713. Compare against a dated publisher snapshot.
1724. View at final size in the manuscript/web context.
1735. Manually review fonts, embedded rasters, clipping, legends, scale bars, image integrity, caption, alt text, and source data.
1746. Re-check the live target-journal page immediately before upload.
175176## Pinned snapshot
177178The examples and smoke tests use direct package pins current on 2026-07-23:
179180```bash
181uv run --isolated --no-project --python 3.13 \
182 --with "matplotlib==3.11.1" \
183 --with "seaborn==0.13.2" \
184 --with "plotly==6.9.0" \
185 --with "kaleido==1.3.0" \
186 --with "pillow==12.3.0" \
187 --with "pypdf==6.14.2" \
188 python your_figure.py
189```
190191This is a dated direct-dependency snapshot, not a transitive lock. Use the project's uv lock for exact replay; this skill intentionally ships no dependency lock.
192193## Bundled CLIs
194195All helpers are deterministic, network-free, bounded, reject symlink inputs/destinations where relevant, and refuse overwrite unless `--force` is explicit.
196197### Inspect raster/vector metadata
198199```bash
200uv run --isolated --no-project --python 3.13 \
201 --with "pillow==12.3.0" \
202 python scripts/image_metadata.py figure.tiff \
203 --format tiff --mode RGB --min-dpi 300 --target-width-mm 85 \
204 --alpha-policy forbid
205```
206207Supports raster images (Pillow), SVG, PDF (pypdf), and EPS/PS. Reports dimensions, DPI/effective DPI, mode, alpha, ICC presence, compression, page size, and conservative first-page PDF font resources. It does not inspect every embedded raster in a vector container.
208209### Audit palette contrast and grayscale
210211```bash
212uv run --isolated --no-project --python 3.13 \
213 python scripts/palette_audit.py \
214 --palette okabe_ito_on_white \
215 --background FFFFFF \
216 --role graphical
217```
218219Reports exact WCAG sRGB contrast plus pairwise CIE L* grayscale screening. The grayscale threshold is a heuristic, not a standard.
220221### Plan/screen publisher export
222223```bash
224uv run --isolated --no-project --python 3.13 \
225 python scripts/export_plan.py \
226 --publisher nature \
227 --figure-type combination \
228 --width single \
229 --phase final
230```
231232Add `--input figure.pdf` to screen machine-readable properties. Profiles are official-source snapshots accessed 2026-07-23, not automatic compliance rules.
233234### Preview styles
235236```bash
237uv run --isolated --no-project --python 3.13 \
238 --with "matplotlib==3.11.1" \
239 python scripts/style_preview.py \
240 --output outputs/style-preview \
241 --style default \
242 --palette okabe_ito_on_white \
243 --formats png,svg
244```
245246### Inspect/write styles and smoke-test export
247248```bash
249uv run --isolated --no-project --python 3.13 \
250 python scripts/style_presets.py --list
251uv run --isolated --no-project --python 3.13 \
252 python scripts/style_presets.py --show nature
253uv run --isolated --no-project --python 3.13 \
254 --with "matplotlib==3.11.1" \
255 python scripts/figure_export.py --demo outputs/export-smoke --manifest
256```
257258## Assets
259260- `assets/publication.mplstyle`: general print starting point.
261- `assets/nature.mplstyle`: dated flagship Nature visual starting point, not a compliance preset.
262- `assets/presentation.mplstyle`: larger projected-display style.
263- `assets/color_palettes.py`: importable Okabe-Ito and Paul Tol values with metadata.
264- `assets/publisher_profiles.json`: dated, machine-readable planning snapshots.
265266Matplotlib style files omit `#` in hex colors because `#` begins comments in `.mplstyle` parsing.
267268## References
269270- `references/publication_guidelines.md`: integrity, deceptive encodings, accessibility, static/interactive output.
271- `references/color_palettes.md`: palette semantics, exact values, WCAG contrast, grayscale caveats, color management.
272- `references/journal_requirements.md`: phase-specific official publisher snapshots.
273- `references/matplotlib_examples.md`: current, runnable Matplotlib/Seaborn/Plotly patterns.
274- `references/sources.md`: official URLs, dates, versions, and research basis.
275276## Final review checklist
277278- [ ] Raw data/images and transformation code are preserved.
279- [ ] Missing values, exclusions, bins, normalization, and uncertainty are explicit.
280- [ ] Baselines, scales, limits, and area/volume encodings are honest.
281- [ ] Color is redundant and rendered contrast was reviewed.
282- [ ] Figure has an accessible description/data alternative where applicable.
283- [ ] Physical dimensions, DPI, format, fonts, transparency, and file size were inspected after export.
284- [ ] Publisher rules were verified for the exact journal and phase.
285- [ ] No automated report is presented as a scientific, accessibility, or compliance certification.
Run npx skillmds@latest add gabrielmoreira/scientific-visualization in your terminal (requires Node.js), paste this page's agent-chat prompt into Claude, Cursor, or any MCP-connected agent, or download the SKILL.md file and copy it into your agent's skills directory.
Create and audit truthful, accessible, publication-ready scientific figures with Matplotlib, Seaborn, or Plotly. Use for figure design, multi-panel layouts, uncertainty and missing-data displays, color/contrast review, image metadata validation, and journal export planning. It is listed under AI & ML on SkillMD.
This skill has not completed SkillMD's automated safety review yet. Independent scanners report: SkillSpector: PASS, Skill Scanner: PASS. Capability flags: executes scripts. SkillMD never runs a skill's scripts for you; review the SKILL.md before installing.
This skill is tagged as working with Claude Code, Claude.ai, OpenAI Codex. SKILL.md is an open format, so most agents that read a skills directory can load it too.
Yes. Installing skills from SkillMD is free. This skill is licensed under MIT.
gabrielmoreira (@gabrielmoreira) published this skill. Their other Agent Skills are listed on their SkillMD profile.