Skip to content
Back to skills

Plotly Interactive Visualization

ASecurity

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.

  • 368 stars
  • 0 votes
  • 0 copies
  • 3 views
  • Added May 29, 2026
datapythongobashexpressapidocumentation

Works with

  • api

Security analysis

A96/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro shows the line behind each finding and how to fix it

Scanned May 29, 2026

npx -y skills add jaechang-hits/SciAgent-Skills --skill plotly-interactive-visualization --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Plotly Interactive Visualization?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Plotly Interactive Visualization
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/jaechang-hits-plotly-interactive-visualization/badge)](https://www.skillsdirectory.com/skills/jaechang-hits-plotly-interactive-visualization)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: plotly-interactive-visualization
description: "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."
license: MIT
---

# Plotly — Interactive Scientific Visualization

## Overview

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.

## When to Use

- Creating interactive charts with hover tooltips, zoom, and pan
- Building multi-panel exploratory dashboards for data analysis
- Visualizing 3D data (surfaces, scatter3d, mesh, volume)
- Making geographic/map visualizations (choropleth, scatter_geo)
- Presenting data in web-embeddable HTML format
- Statistical distribution comparison (violin, box, histogram with marginals)
- Time series with range sliders and animation frames
- For **static publication-quality figures** (journal submissions), use `matplotlib` instead
- For **statistical grammar-of-graphics** style, use `seaborn` instead

## Prerequisites

- **Python packages**: `plotly`, `pandas`, `numpy`
- **For static export**: `kaleido` (PNG/PDF/SVG rendering)
- **For web apps**: `dash` (optional)

```bash
pip install plotly kaleido
```

## Quick Start

```python
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")
```

## Core API

### 1. Plotly Express (High-Level API)

Quick, one-line charts from pandas DataFrames. Returns `go.Figure` objects that can be further customized.

```python
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")
```

```python
# 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")
```

### 2. Graph Objects (Low-Level API)

Full control over individual traces, layouts, and annotations.

```python
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")
```

```python
# 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")
```

### 3. Subplots and Multi-Panel Layouts

Create figure grids with shared or independent axes.

```python
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")
```

### 4. Statistical Charts

Distribution comparison, error bars, and statistical annotations.

```python
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")
```

```python
# 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")
```

### 5. Export and Rendering

Save to interactive HTML, static images, or embed in notebooks.

```python
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)}")
```

### 6. Interactivity Features

Customize hover, animations, buttons, and range sliders.

```python
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")
```

## Common Workflows

### Workflow 1: Exploratory Data Analysis Dashboard

**Goal**: Create a multi-panel interactive dashboard for dataset exploration.

```python
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")
```

### Workflow 2: Publication Figure with Annotations

**Goal**: Create a polished, annotated figure suitable for supplementary materials or presentations.

```python
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)")
```

## Key Parameters

| 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 |

## Best Practices

1. **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.
   ```python
   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)
   ```

2. **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.

3. **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).

4. **Use `scale=2` or higher for print-quality images**: Default `scale=1` produces 72 DPI equivalent. For publications, use `scale=3` (216 DPI effective).

5. **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.

## Common Recipes

### Recipe: Correlation Matrix Heatmap

```python
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")
```

### Recipe: Animated Scatter Plot

```python
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")
```

### Recipe: Geographic Choropleth Map

```python
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")
```

## Troubleshooting

| 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 |

## Related Skills

- **matplotlib** — static, publication-quality figures for journal submissions (more control over typography and layout)
- **seaborn** — statistical visualization with grammar-of-graphics approach (built on matplotlib)
- **scientific-visualization** — general principles of scientific figure design and color theory

## References

- [Plotly Python documentation](https://plotly.com/python/) — official guides and API reference
- [Plotly Express API](https://plotly.com/python-api-reference/plotly.express.html) — high-level API reference
- [Dash documentation](https://dash.plotly.com/) — interactive web application framework
- [Plotly community forum](https://community.plotly.com/) — community support and examples

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…