Scientific Visualization System
Comprehensive skill designed for create, publication, figures, matplotlib. Includes structured workflows, validation checks, and reusable patterns for scientific.
Scientific Visualization System
Create publication-quality scientific figures with multi-panel layouts, statistical annotations, colorblind-safe palettes, and journal-ready export using matplotlib, seaborn, and plotly. This skill covers figure composition, axis customization, 3D visualization, animated plots, and automated figure generation pipelines.
When to Use This Skill
Choose Scientific Visualization System when you need to:
- Create multi-panel figures with shared axes and consistent styling for journal submission
- Build publication-quality plots with proper error bars, significance markers, and annotations
- Generate colorblind-safe, grayscale-compatible visualizations
- Automate figure generation pipelines for reproducible research outputs
Consider alternatives when:
- You need interactive web dashboards (use Plotly Dash or Streamlit)
- You need geographic/spatial visualization (use GeoPandas or Folium)
- You need presentation-specific slides with figures (use Scientific Slides Elite)
Quick Start
pip install matplotlib seaborn numpy scipy
import matplotlib.pyplot as plt import matplotlib.gridspec as gridspec import seaborn as sns import numpy as np # Set publication style globally plt.rcParams.update({ 'font.family': 'sans-serif', 'font.sans-serif': ['Arial'], 'font.size': 10, 'axes.linewidth': 1.0, 'xtick.major.width': 1.0, 'ytick.major.width': 1.0, 'figure.dpi': 150, 'savefig.dpi': 300, 'savefig.bbox': 'tight', 'savefig.pad_inches': 0.05, }) # Create a multi-panel figure fig = plt.figure(figsize=(7, 6)) # Single column width gs = gridspec.GridSpec(2, 2, hspace=0.35, wspace=0.35) # Panel A: Bar plot with error bars ax1 = fig.add_subplot(gs[0, 0]) conditions = ['Control', 'Treatment A', 'Treatment B'] means = [12.3, 18.7, 15.2] sems = [1.1, 1.8, 1.3] bars = ax1.bar(conditions, means, yerr=sems, capsize=4, color=['#4C72B0', '#DD8452', '#55A868'], edgecolor='black', linewidth=0.5) ax1.set_ylabel('Expression Level (AU)') ax1.set_title('Gene Expression', fontweight='bold', fontsize=11) # Panel B: Scatter with regression ax2 = fig.add_subplot(gs[0, 1]) np.random.seed(42) x = np.random.randn(50) * 2 + 5 y = 1.5 * x + np.random.randn(50) * 2 + 3 ax2.scatter(x, y, s=30, alpha=0.7, color='#4C72B0', edgecolor='white', linewidth=0.5) z = np.polyfit(x, y, 1) xline = np.linspace(x.min(), x.max(), 100) ax2.plot(xline, np.polyval(z, xline), 'r-', linewidth=1.5) ax2.set_xlabel('Variable X') ax2.set_ylabel('Variable Y') ax2.set_title('Correlation', fontweight='bold', fontsize=11) # Panel C: Heatmap ax3 = fig.add_subplot(gs[1, 0]) data = np.random.randn(6, 6) sns.heatmap(data, ax=ax3, cmap='RdBu_r', center=0, square=True, linewidths=0.5, cbar_kws={'shrink': 0.8}) ax3.set_title('Expression Matrix', fontweight='bold', fontsize=11) # Panel D: Box plot ax4 = fig.add_subplot(gs[1, 1]) box_data = [np.random.randn(30) + i for i in range(4)] bp = ax4.boxplot(box_data, patch_artist=True, widths=0.6, medianprops=dict(color='black', linewidth=1.5)) colors_box = ['#4C72B0', '#DD8452', '#55A868', '#C44E52'] for patch, color in zip(bp['boxes'], colors_box): patch.set_facecolor(color) patch.set_alpha(0.7) ax4.set_xticklabels(['WT', 'KO1', 'KO2', 'Rescue']) ax4.set_ylabel('Measurement') ax4.set_title('Distribution', fontweight='bold', fontsize=11) # Add panel labels for ax, label in zip([ax1, ax2, ax3, ax4], ['A', 'B', 'C', 'D']): ax.text(-0.15, 1.05, label, transform=ax.transAxes, fontsize=14, fontweight='bold', va='bottom') fig.savefig('figure1.pdf', format='pdf') fig.savefig('figure1.tiff', format='tiff', dpi=300) print("Multi-panel figure saved as PDF and TIFF")
Core Concepts
Plot Types for Scientific Data
| Plot Type | Best For | matplotlib/seaborn Function |
|---|---|---|
| Bar + error bars | Group comparisons | ax.bar() + yerr |
| Box/violin | Distribution comparison | sns.boxplot(), sns.violinplot() |
| Scatter + regression | Correlations | ax.scatter() + np.polyfit() |
| Heatmap | Matrix data, expression | sns.heatmap() |
| Line + confidence band | Time series, dose-response | ax.plot() + ax.fill_between() |
| Kaplan-Meier | Survival analysis | lifelines + matplotlib |
| Volcano plot | Differential expression | Custom scatter with thresholds |
| PCA/UMAP | Dimensionality reduction | Scatter with cluster coloring |
Statistical Annotation System
import matplotlib.pyplot as plt import numpy as np from scipy import stats def add_significance_bracket(ax, x1, x2, y, p_value, height=0.02): """Add a significance bracket between two bars.""" if p_value < 0.001: sig_text = '***' elif p_value < 0.01: sig_text = '**' elif p_value < 0.05: sig_text = '*' else: sig_text = 'ns' y_range = ax.get_ylim()[1] - ax.get_ylim()[0] bar_height = y_range * height ax.plot([x1, x1, x2, x2], [y, y + bar_height, y + bar_height, y], color='black', linewidth=1.0) ax.text((x1 + x2) / 2, y + bar_height, sig_text, ha='center', va='bottom', fontsize=11, fontweight='bold') # Example with t-test annotations fig, ax = plt.subplots(figsize=(4, 4)) groups = ['Control', 'Drug A', 'Drug B'] data = [np.random.randn(20) + m for m in [5, 7.5, 6.2]] means = [d.mean() for d in data] sems = [d.std() / np.sqrt(len(d)) for d in data] ax.bar(range(3), means, yerr=sems, capsize=4, color=['#4C72B0', '#DD8452', '#55A868'], edgecolor='black', linewidth=0.5) ax.set_xticks(range(3)) ax.set_xticklabels(groups) ax.set_ylabel('Response (AU)') # Add significance brackets _, p_val = stats.ttest_ind(data[0], data[1]) add_significance_bracket(ax, 0, 1, max(means) + max(sems) + 0.5, p_val) fig.tight_layout() fig.savefig('annotated_bar.pdf')
Configuration
| Parameter | Description | Default |
|---|---|---|
figure.figsize | Figure dimensions in inches (width, height) | (7, 5) |
savefig.dpi | Output resolution for raster formats | 300 |
font.size | Base font size in points | 10 |
font.family | Font family | "sans-serif" |
axes.linewidth | Axis border thickness | 1.0 |
figure.facecolor | Background color | "white" |
savefig.format | Default save format | "pdf" |
image.cmap | Default colormap | "viridis" |
Best Practices
-
Use vector formats for line art, raster for complex images — Save line plots, bar charts, and schematics as PDF or SVG for infinite scalability. Save heatmaps, microscopy overlays, and photos as TIFF at 300 DPI. Most journals accept both and many require specific formats per figure type.
-
Apply colorblind-safe palettes by default — About 8% of men have color vision deficiency. Use palettes like
colorblindfrom seaborn, or manually select distinguishable colors (blue/orange rather than red/green). Test with a colorblindness simulator before submission. -
Size figures to their final print dimensions — Create figures at the exact width the journal specifies (typically 3.5" for single column, 7" for double column). This ensures fonts are readable at the printed size. Never rely on the journal to scale your figure — text will be too small or too large.
-
Use GridSpec for complex multi-panel layouts —
matplotlib.gridspec.GridSpecgives precise control over panel sizes and spacing. Usehspaceandwspacefor gaps, andheight_ratios/width_ratiosfor non-uniform panels. Add bold uppercase labels (A, B, C) in the top-left of each panel. -
Separate data processing from visualization — Load and process data in one script, save intermediates, then create figures in a separate script. This lets you regenerate figures without re-running expensive computations and makes it easy to tweak aesthetics without touching the analysis pipeline.
Common Issues
Fonts are substituted or missing in PDF output — matplotlib embeds fonts in PDFs, but some fonts aren't available on all systems. Use plt.rcParams['pdf.fonttype'] = 42 to embed fonts as TrueType (not Type 3), which journals and reviewers can open correctly. Stick to common fonts like Arial or Helvetica.
Axis labels or titles are cut off in saved figures — Use bbox_inches='tight' in savefig() to auto-expand the bounding box. Also set pad_inches=0.05 for a small margin. For very tight layouts, manually adjust subplot parameters with fig.subplots_adjust() or switch to constrained_layout=True.
Colorbar size doesn't match the plot height — Seaborn and matplotlib colorbars default to the full figure height, which looks wrong in multi-panel layouts. Use cbar_kws={'shrink': 0.8} in seaborn heatmaps, or create a dedicated colorbar axis with fig.colorbar(im, cax=cbar_ax) and control its size via GridSpec.
Reviews
No reviews yet. Be the first to review this template!
Similar Templates
Full-Stack Code Reviewer
Comprehensive code review skill that checks for security vulnerabilities, performance issues, accessibility, and best practices across frontend and backend code.
Test Suite Generator
Generates comprehensive test suites with unit tests, integration tests, and edge cases. Supports Jest, Vitest, Pytest, and Go testing.
Pro Architecture Workspace
Battle-tested skill for architectural, decision, making, framework. Includes structured workflows, validation checks, and reusable patterns for development.