skills/data-visualization/plotly-interactive-visualization/SKILL.md
Interactive visualization with Plotly. 40+ chart types (scatter, line, heatmap, 3D, geographic) with hover, zoom, pan. Two APIs: Plotly Express (DataFrame) and Graph Objects (fine control). For static publication figures use matplotlib; for statistical grammar use seaborn.
npx skillsauth add jaechang-hits/scicraft plotly-interactive-visualizationInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Plotly is a Python graphing library for interactive, web-embeddable visualizations with 40+ chart types. It provides two APIs: Plotly Express (high-level, pandas-native) for quick plots and Graph Objects (low-level) for full customization. Output to interactive HTML, static PNG/PDF/SVG, or Dash web apps.
matplotlib insteadseaborn insteadplotly, pandas, numpykaleido (PNG/PDF/SVG rendering)dash (optional)pip install plotly kaleido
import plotly.express as px
import pandas as pd
import numpy as np
# Sample data
np.random.seed(42)
df = pd.DataFrame({
"x": np.random.randn(200),
"y": np.random.randn(200),
"group": np.random.choice(["A", "B", "C"], 200),
"size": np.random.uniform(5, 20, 200),
})
fig = px.scatter(df, x="x", y="y", color="group", size="size",
title="Interactive Scatter Plot", hover_data=["group"])
fig.write_html("scatter.html")
fig.write_image("scatter.png", width=800, height=500, scale=2)
print("Saved scatter.html and scatter.png")
Quick, one-line charts from pandas DataFrames. Returns go.Figure objects that can be further customized.
import plotly.express as px
import pandas as pd
import numpy as np
np.random.seed(42)
df = pd.DataFrame({
"temperature": np.linspace(20, 80, 50),
"yield": 50 + 0.8 * np.linspace(20, 80, 50) + np.random.randn(50) * 5,
"catalyst": np.random.choice(["Pd", "Pt", "Rh"], 50),
})
# Scatter with trendline
fig = px.scatter(df, x="temperature", y="yield", color="catalyst",
trendline="ols", title="Temperature vs Yield")
fig.write_image("scatter_trend.png", width=700, height=450)
print("Saved scatter_trend.png")
# Bar chart
summary = df.groupby("catalyst")["yield"].mean().reset_index()
fig = px.bar(summary, x="catalyst", y="yield", color="catalyst",
title="Mean Yield by Catalyst")
fig.write_image("bar_catalyst.png", width=600, height=400)
print("Saved bar_catalyst.png")
# Heatmap from correlation matrix
import plotly.express as px
import pandas as pd
import numpy as np
np.random.seed(42)
data = pd.DataFrame(np.random.randn(100, 5), columns=["Gene_A", "Gene_B", "Gene_C", "Gene_D", "Gene_E"])
corr = data.corr()
fig = px.imshow(corr, text_auto=".2f", color_continuous_scale="RdBu_r",
zmin=-1, zmax=1, title="Gene Expression Correlation")
fig.write_image("heatmap.png", width=600, height=500)
print("Saved heatmap.png")
Full control over individual traces, layouts, and annotations.
import plotly.graph_objects as go
import numpy as np
# 3D surface plot
x = np.linspace(-5, 5, 50)
y = np.linspace(-5, 5, 50)
X, Y = np.meshgrid(x, y)
Z = np.sin(np.sqrt(X**2 + Y**2))
fig = go.Figure(data=[go.Surface(z=Z, x=X[0], y=y, colorscale="Viridis")])
fig.update_layout(title="3D Surface Plot",
scene=dict(xaxis_title="X", yaxis_title="Y", zaxis_title="Z"))
fig.write_image("surface_3d.png", width=700, height=500)
print("Saved surface_3d.png")
# Multi-trace figure with custom styling
import plotly.graph_objects as go
import numpy as np
np.random.seed(42)
x = np.linspace(0, 10, 100)
fig = go.Figure()
fig.add_trace(go.Scatter(x=x, y=np.sin(x), mode="lines", name="sin(x)",
line=dict(color="blue", width=2)))
fig.add_trace(go.Scatter(x=x, y=np.cos(x), mode="lines", name="cos(x)",
line=dict(color="red", width=2, dash="dash")))
fig.add_hline(y=0, line_dash="dot", line_color="gray", opacity=0.5)
fig.add_annotation(x=np.pi/2, y=1, text="sin peak", showarrow=True, arrowhead=2)
fig.update_layout(template="plotly_white", title="Trigonometric Functions",
xaxis_title="x", yaxis_title="f(x)")
fig.write_image("multi_trace.png", width=700, height=400)
print("Saved multi_trace.png")
Create figure grids with shared or independent axes.
from plotly.subplots import make_subplots
import plotly.graph_objects as go
import numpy as np
np.random.seed(42)
data = np.random.randn(500)
fig = make_subplots(
rows=2, cols=2,
subplot_titles=("Histogram", "Box Plot", "Scatter", "Violin"),
specs=[[{"type": "histogram"}, {"type": "box"}],
[{"type": "scatter"}, {"type": "violin"}]],
)
fig.add_trace(go.Histogram(x=data, nbinsx=30, name="Hist"), row=1, col=1)
fig.add_trace(go.Box(y=data, name="Box"), row=1, col=2)
fig.add_trace(go.Scatter(x=data[:100], y=data[100:200], mode="markers", name="Scatter"), row=2, col=1)
fig.add_trace(go.Violin(y=data, name="Violin", box_visible=True), row=2, col=2)
fig.update_layout(height=700, width=800, title_text="Multi-Panel Dashboard", showlegend=False)
fig.write_image("subplots.png", width=800, height=700)
print("Saved subplots.png")
Distribution comparison, error bars, and statistical annotations.
import plotly.express as px
import pandas as pd
import numpy as np
np.random.seed(42)
df = pd.DataFrame({
"value": np.concatenate([np.random.normal(0, 1, 100), np.random.normal(2, 1.5, 100)]),
"group": ["Control"] * 100 + ["Treatment"] * 100,
})
# Histogram with marginal box plot
fig = px.histogram(df, x="value", color="group", marginal="box",
nbins=30, barmode="overlay", opacity=0.7,
title="Distribution Comparison")
fig.write_image("stat_hist.png", width=700, height=450)
print("Saved stat_hist.png")
# Violin plot with individual points
fig = px.violin(df, x="group", y="value", box=True, points="all",
title="Treatment Effect (Violin + Points)")
fig.write_image("violin.png", width=500, height=450)
print("Saved violin.png")
# Error bars
import plotly.graph_objects as go
import numpy as np
conditions = ["Control", "Low Dose", "Med Dose", "High Dose"]
means = [5.2, 7.1, 9.8, 11.3]
sems = [0.4, 0.6, 0.5, 0.8]
fig = go.Figure(data=[go.Bar(
x=conditions, y=means,
error_y=dict(type="data", array=sems, visible=True),
marker_color=["#636EFA", "#EF553B", "#00CC96", "#AB63FA"],
)])
fig.update_layout(title="Dose Response (mean ± SEM)", yaxis_title="Response",
template="plotly_white")
fig.write_image("error_bars.png", width=600, height=400)
print("Saved error_bars.png")
Save to interactive HTML, static images, or embed in notebooks.
import plotly.express as px
import pandas as pd
df = px.data.iris()
fig = px.scatter(df, x="sepal_width", y="sepal_length", color="species")
# Interactive HTML (full standalone)
fig.write_html("interactive.html")
# HTML with CDN (smaller file, needs internet)
fig.write_html("interactive_cdn.html", include_plotlyjs="cdn")
# Static images (requires kaleido)
fig.write_image("plot.png", width=800, height=500, scale=2) # 2x resolution
fig.write_image("plot.pdf") # Vector PDF
fig.write_image("plot.svg") # Vector SVG
# Get image as bytes (for embedding)
img_bytes = fig.to_image(format="png", width=600, height=400)
print(f"PNG bytes: {len(img_bytes)}")
Customize hover, animations, buttons, and range sliders.
import plotly.express as px
import pandas as pd
import numpy as np
# Custom hover template
np.random.seed(42)
df = pd.DataFrame({
"date": pd.date_range("2024-01-01", periods=100),
"price": 100 + np.cumsum(np.random.randn(100) * 2),
"volume": np.random.randint(1000, 5000, 100),
})
fig = px.line(df, x="date", y="price", title="Stock Price",
hover_data={"volume": True, "price": ":.2f"})
fig.update_traces(hovertemplate="<b>%{x|%Y-%m-%d}</b><br>Price: $%{y:.2f}<br>Volume: %{customdata[0]:,}<extra></extra>")
fig.update_xaxes(rangeslider_visible=True)
fig.write_html("timeseries.html")
print("Saved timeseries.html with range slider")
Goal: Create a multi-panel interactive dashboard for dataset exploration.
import plotly.express as px
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import pandas as pd
import numpy as np
# Sample dataset
np.random.seed(42)
n = 300
df = pd.DataFrame({
"gene_expression": np.random.lognormal(2, 1, n),
"protein_level": np.random.lognormal(1.5, 0.8, n),
"cell_type": np.random.choice(["Neuron", "Astrocyte", "Microglia"], n),
"treatment": np.random.choice(["Control", "Drug_A", "Drug_B"], n),
"viability": np.random.uniform(0.3, 1.0, n),
})
fig = make_subplots(rows=2, cols=2,
subplot_titles=("Expression vs Protein", "Expression by Cell Type",
"Viability by Treatment", "Expression Distribution"))
# Panel 1: Scatter
for ct in df["cell_type"].unique():
sub = df[df["cell_type"] == ct]
fig.add_trace(go.Scatter(x=sub["gene_expression"], y=sub["protein_level"],
mode="markers", name=ct, opacity=0.6), row=1, col=1)
# Panel 2: Box
for ct in df["cell_type"].unique():
fig.add_trace(go.Box(y=df[df["cell_type"]==ct]["gene_expression"],
name=ct, showlegend=False), row=1, col=2)
# Panel 3: Violin
for tx in df["treatment"].unique():
fig.add_trace(go.Violin(y=df[df["treatment"]==tx]["viability"],
name=tx, showlegend=False, box_visible=True), row=2, col=1)
# Panel 4: Histogram
fig.add_trace(go.Histogram(x=df["gene_expression"], nbinsx=30,
name="Expression", showlegend=False), row=2, col=2)
fig.update_layout(height=800, width=1000, title="Exploratory Data Analysis")
fig.write_html("eda_dashboard.html")
fig.write_image("eda_dashboard.png", width=1000, height=800)
print("Saved eda_dashboard.html and eda_dashboard.png")
Goal: Create a polished, annotated figure suitable for supplementary materials or presentations.
import plotly.graph_objects as go
import numpy as np
np.random.seed(42)
x = np.linspace(0, 24, 100)
control = 50 + 10 * np.sin(x * np.pi / 12) + np.random.randn(100) * 3
treatment = 70 + 15 * np.sin(x * np.pi / 12 + 0.5) + np.random.randn(100) * 4
fig = go.Figure()
fig.add_trace(go.Scatter(x=x, y=control, mode="lines", name="Control",
line=dict(color="#636EFA", width=2)))
fig.add_trace(go.Scatter(x=x, y=treatment, mode="lines", name="Treatment",
line=dict(color="#EF553B", width=2)))
# Add shaded region for treatment window
fig.add_vrect(x0=6, x1=18, fillcolor="yellow", opacity=0.1, line_width=0,
annotation_text="Treatment Window", annotation_position="top left")
# Add annotation at peak difference
fig.add_annotation(x=12, y=85, text="Peak difference<br>p < 0.001",
showarrow=True, arrowhead=2, font=dict(size=11))
fig.update_layout(
template="plotly_white",
title="Circadian Response to Treatment",
xaxis_title="Time (hours)", yaxis_title="Response (AU)",
font=dict(family="Arial", size=12),
legend=dict(x=0.02, y=0.98),
width=700, height=450,
)
fig.write_image("publication_figure.png", width=700, height=450, scale=3)
print("Saved publication_figure.png (3x resolution)")
| Parameter | Module | Default | Range / Options | Effect |
|-----------|--------|---------|-----------------|--------|
| template | update_layout | "plotly" | "plotly_white", "plotly_dark", "ggplot2", "seaborn", "simple_white" | Global figure styling theme |
| color_continuous_scale | px / go | varies | "Viridis", "Plasma", "RdBu", "RdBu_r", "Blues" | Color scale for continuous data |
| barmode | px.histogram | "relative" | "group", "overlay", "relative", "stack" | How multiple histograms are arranged |
| trendline | px.scatter | None | None, "ols", "lowess", "expanding" | Regression line overlay |
| marginal | px.scatter/histogram | None | None, "rug", "box", "violin", "histogram" | Marginal distribution display |
| scale | write_image | 1 | 1–5 | Image resolution multiplier (2=retina, 3=print) |
| include_plotlyjs | write_html | True | True, "cdn", "directory", False | How Plotly.js is bundled in HTML |
| opacity | most trace types | 1.0 | 0.0–1.0 | Trace transparency for overlapping data |
Start with Plotly Express, customize with Graph Objects: px functions return go.Figure objects, so you can always add Graph Objects methods after. Don't start with go unless px genuinely cannot express what you need.
fig = px.scatter(df, x="x", y="y", color="group")
fig.update_layout(template="plotly_white") # go method on px figure
fig.add_hline(y=threshold)
Use plotly_white template for publication figures: The default plotly template has a gray background that looks unprofessional in papers. plotly_white or simple_white gives a clean look.
Always set explicit width/height for static export: Without dimensions, write_image uses the default viewport size which may not match your target (journal column width, slide dimensions).
Use scale=2 or higher for print-quality images: Default scale=1 produces 72 DPI equivalent. For publications, use scale=3 (216 DPI effective).
Prefer HTML export for interactive data sharing: HTML files are self-contained and can be opened in any browser without Python. Use include_plotlyjs="cdn" to reduce file size.
import plotly.express as px
import pandas as pd
import numpy as np
np.random.seed(42)
df = pd.DataFrame(np.random.randn(100, 6), columns=[f"Var_{i}" for i in range(6)])
corr = df.corr()
# Mask upper triangle
mask = np.triu(np.ones_like(corr, dtype=bool), k=1)
corr_masked = corr.where(~mask)
fig = px.imshow(corr_masked, text_auto=".2f", color_continuous_scale="RdBu_r",
zmin=-1, zmax=1, title="Correlation Matrix")
fig.write_image("correlation.png", width=600, height=500, scale=2)
print("Saved correlation.png")
import plotly.express as px
import pandas as pd
import numpy as np
np.random.seed(42)
frames = []
for year in range(2010, 2025):
n = 50
frames.append(pd.DataFrame({
"x": np.random.randn(n) * (year - 2009),
"y": np.random.randn(n) * (year - 2009),
"size": np.random.uniform(5, 20, n),
"year": year,
}))
df = pd.concat(frames)
fig = px.scatter(df, x="x", y="y", size="size", animation_frame="year",
range_x=[-30, 30], range_y=[-30, 30], title="Animated Scatter")
fig.write_html("animated.html")
print("Saved animated.html")
import plotly.express as px
# Built-in gapminder dataset
df = px.data.gapminder().query("year == 2007")
fig = px.choropleth(df, locations="iso_alpha", color="gdpPercap",
hover_name="country", color_continuous_scale="Plasma",
title="GDP per Capita (2007)")
fig.write_image("choropleth.png", width=900, height=500, scale=2)
print("Saved choropleth.png")
| Problem | Cause | Solution |
|---------|-------|----------|
| write_image fails with ValueError | kaleido not installed | pip install kaleido |
| Blank/white image from write_image | Plotly version mismatch with kaleido | Update both: pip install --upgrade plotly kaleido |
| HTML file very large (>10 MB) | Full Plotly.js bundled | Use fig.write_html(path, include_plotlyjs="cdn") |
| Hover data not showing | Column not in DataFrame or wrong name | Check hover_data parameter matches DataFrame columns exactly |
| Subplot traces appear in wrong panel | Incorrect row/col in add_trace | Verify row= and col= match your make_subplots grid (1-indexed) |
| Colors don't match between px and go | Different default color sequences | Set explicitly: fig.update_layout(colorway=px.colors.qualitative.Plotly) |
| Animation slow/choppy | Too many points per frame | Reduce data points or use px.scatter with render_mode="webgl" for large datasets |
tools
Fast short-read DNA aligner for WGS/WES/ChIP-seq. 2× faster BWA-MEM successor; outputs SAM/BAM with read group headers for GATK. Primary plus supplementary records for chimeric reads. Use STAR for RNA-seq splice-aware alignment; Bowtie2 is a comparable alternative.
tools
smina molecular docking CLI. AutoDock Vina fork with customizable scoring functions, native SDF/MOL2/PDB ligand input, autoboxing, local energy minimization, and per-atom score breakdowns. Pipeline: receptor PDBQT prep -> ligand prep (RDKit/OpenBabel) -> dock via autobox or explicit grid -> rescore/minimize with custom scoring -> rank poses by affinity. Choose smina over Vina when you need custom scoring terms (--custom_scoring), local optimization of an existing pose (--local_only), per-atom contributions (--atom_term_data), or SDF/MOL2 ligands without manual PDBQT conversion. For unknown binding sites use diffdock-blind-docking; for the Python-bindings/Vinardo workflow use autodock-vina-docking.
development
mdtraj molecular dynamics trajectory analysis (Python). Reads DCD/XTC/TRR/NetCDF/H5/PDB topologies and trajectories; computes RMSD vs time, radius of gyration, per-residue RMSF, residue-residue contact frequency maps, phi/psi torsions for Ramachandran plots (general + Gly/Pro), and 8-state DSSP secondary structure. Modules: trajectory I/O, geometry (distances/angles/dihedrals), structural analysis (RMSD/Rg/RMSF/SASA), contacts, hydrogen bonds, secondary structure (DSSP), NMR observables. For broader atom-selection grammar use mdanalysis-trajectory; for running MD simulations use OpenMM/GROMACS.
development
Programmatic PubMed access via NCBI E-utilities REST API. Covers Boolean/MeSH queries, field-tagged search, endpoints (ESearch, EFetch, ESummary, EPost, ELink), history server for batches, citation matching, systematic review strategies. Use for biomedical literature search or automated pipelines.