
Plotly
- 1 installs
- 3.2k repo stars
- Updated August 4, 2026
- brycewang-stanford/awesome-agent-skills-for-empirical-research
plotly is a skill that is a reference for creating interactive Python data visualizations with Plotly Express and Graph Objects.
About
This skill is a reference for creating interactive data visualizations with Plotly in Python, using both Plotly Express and Graph Objects. A developer uses it for scatter, line, bar, histogram, box, heatmap, 3D, and geographic charts, plus subplots, styling, and HTML export, when hover, zoom, or pan interactivity is needed. It matters for producing web-based interactive charts in research and analysis pipelines.
- Interactive Plotly visualization with Express and Graph Objects APIs
- Covers scatter, line, bar, heatmap, 3D, and geographic charts
- Includes subplots, styling, and HTML export guidance
Plotly by the numbers
- 1 all-time installs (skills.sh)
- Ranked #1,803 of 2,064 Data Science & ML skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
plotly capabilities & compatibility
- Capabilities
- data visualization · interactive charts
- Use cases
- data analysis
- Pricing
- Free
What plotly says it does
Plotly interactive visualization library for Python.
Prefer over plotnine when interactivity is required; for spatial analysis, projections, or GIS-style mapping, use geopandas.
npx skills add https://github.com/brycewang-stanford/awesome-agent-skills-for-empirical-research --skill plotlyAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1 |
|---|---|
| repo stars | ★ 3.2k |
| Last updated | August 4, 2026 |
| Repository | brycewang-stanford/awesome-agent-skills-for-empirical-research ↗ |
What it does
Create interactive Plotly charts with hover, zoom, and pan for research and analysis, exported to HTML.
Who is it for?
Interactive charts needing hover, zoom, or pan, including 3D and geographic plots.
Skip if: Static publication figures (use plotnine) or GIS-style mapping (use geopandas).
When should I use this skill?
When interactivity like hover, zoom, or pan is needed for a chart.
What you get
An interactive Plotly figure exported to HTML.
- An interactive Plotly figure, typically exported as HTML
By the numbers
- 40+ chart types
- 2 APIs (Express and Graph Objects)
Files
Plotly Skill
Plotly interactive visualization library for Python. Covers Plotly Express and Graph Objects for scatter, line, bar, histogram, box, heatmap, 3D, and geographic charts; subplots and faceting; styling; and HTML/image export. Use when creating interactive visualizations with hover/zoom/pan, building web-based charts, or producing geographic or 3D plots. Prefer over plotnine when interactivity is required; for spatial analysis, projections, or GIS-style mapping, use geopandas.
Quick reference for creating interactive data visualizations with Plotly, featuring both the high-level Plotly Express API and low-level Graph Objects.
What is Plotly?
Plotly is an interactive visualization library for Python:
- Interactive: Hover, zoom, pan, and select built-in
- Two APIs: Plotly Express (simple) and Graph Objects (flexible)
- Web-based: Renders as HTML/JavaScript, works in notebooks and browsers
- Wide chart support: 40+ chart types including statistical, scientific, financial, and geographic
How to Use This Skill
Reference File Structure
| File | Purpose | When to Read |
|---|---|---|
quickstart.md | Installation, imports, px vs go | Starting out |
charts.md | Scatter, line, bar, histogram, box | Creating visualizations |
subplots-facets.md | Multi-panel layouts, faceting | Multiple charts together |
styling.md | Templates, colors, layout | Customizing appearance |
export.md | HTML, images, JSON | Saving and sharing |
gotchas.md | Common errors, best practices | Debugging |
Quick Decision Trees
"I need to create a chart"
What kind of chart?
├─ Scatter plot → ./references/charts.md
├─ Line chart → ./references/charts.md
├─ Bar chart → ./references/charts.md
├─ Histogram → ./references/charts.md
├─ Box/Violin plot → ./references/charts.md
├─ Heatmap → ./references/charts.md
├─ 3D/Maps/Financial → ./references/charts.md (Other Chart Types)
└─ Not sure → ./references/quickstart.md"I need multiple charts"
Multiple panels?
├─ Same chart, split by category → ./references/subplots-facets.md (faceting)
├─ Different charts in grid → ./references/subplots-facets.md (make_subplots)
├─ Shared axes → ./references/subplots-facets.md
└─ Secondary y-axis → ./references/subplots-facets.md"I need to customize appearance"
What to customize?
├─ Overall theme → ./references/styling.md (templates)
├─ Colors → ./references/styling.md
├─ Titles/labels → ./references/styling.md
├─ Axes → ./references/styling.md
├─ Legend → ./references/styling.md
└─ Hover info → ./references/styling.md"I need to save/export"
Export format?
├─ Interactive HTML → ./references/export.md
├─ Static image (PNG/SVG/PDF) → ./references/export.md
├─ JSON for API → ./references/export.md
└─ Embed in webpage → ./references/export.md"Something isn't working"
Common issues?
├─ Figure not showing → ./references/gotchas.md
├─ Image export fails → ./references/gotchas.md
├─ Performance issues → ./references/gotchas.md
├─ px vs go confusion → ./references/gotchas.md
└─ Column/data errors → ./references/gotchas.mdFile-First Execution in Research Workflows
Important: In data research pipelines (see CLAUDE.md), all visualizations are generated through script files in scripts/stage8_analysis/, not interactively. This ensures auditability and reproducibility.
The pattern: 1. Write plot code FIRST to scripts/stage8_analysis/{step}_{plot-name}.py 2. Execute via Bash with automatic output capture wrapper script 3. Validation results get automatically embedded in scripts as comments 4. If failed, create versioned copy for fixes
Closely read agent_reference/SCRIPT_EXECUTION_REFERENCE.md for the mandatory file-first execution protocol covering complete code file writing, output capture, and file versioning rules.
See:
agent_reference/WORKFLOW_PHASE4_ANALYSIS.md— Stage 8 (Analysis & Visualization)
The examples below show Plotly syntax. In research workflows, wrap them in scripts following the file-first pattern.
---
Quick Reference
Essential Imports
import plotly.express as px # High-level API
import plotly.graph_objects as go # Low-level API
from plotly.subplots import make_subplots # For subplots
import plotly.io as pio # For export/configPlotly Express Pattern
import plotly.express as px
fig = px.scatter(df, x="col_x", y="col_y", color="category")
fig.show()Graph Objects Pattern
import plotly.graph_objects as go
fig = go.Figure()
fig.add_trace(go.Scatter(x=x_data, y=y_data, mode="markers"))
fig.update_layout(title="My Plot")
fig.show()Common px Functions
| Function | Chart Type |
|---|---|
px.scatter() | Scatter plot |
px.line() | Line chart |
px.bar() | Bar chart |
px.histogram() | Histogram |
px.box() | Box plot |
px.violin() | Violin plot |
px.imshow() | Heatmap/Image |
px.pie() | Pie chart |
Common go Trace Types
| Trace | Use Case |
|---|---|
go.Scatter | Points, lines, or both |
go.Bar | Bar charts |
go.Histogram | Histograms |
go.Box | Box plots |
go.Heatmap | Heatmaps |
go.Pie | Pie charts |
Saving Plots
# Interactive HTML
fig.write_html("plot.html")
# Static image export (PNG/SVG/PDF) is NOT available in DAAF — kaleido is not
# installed due to its heavy Chromium dependency. Use plotnine for static figures.
# For interactive output, use HTML:
# fig.write_image("plot.png") # Would require: pip install kaleido + ChromiumTopic Index
| Topic | Reference File |
|---|---|
| Installation | ./references/quickstart.md |
| px vs go | ./references/quickstart.md |
| Built-in datasets | ./references/quickstart.md |
| Scatter plots | ./references/charts.md |
| Line charts | ./references/charts.md |
| Bar charts | ./references/charts.md |
| Histograms | ./references/charts.md |
| Box plots | ./references/charts.md |
| Other chart types | ./references/charts.md |
| Faceting | ./references/subplots-facets.md |
| make_subplots | ./references/subplots-facets.md |
| Templates/Themes | ./references/styling.md |
| Colors | ./references/styling.md |
| Layout customization | ./references/styling.md |
| Hover customization | ./references/styling.md |
| HTML export | ./references/export.md |
| Image export | ./references/export.md |
| JSON export | ./references/export.md |
| Common errors | ./references/gotchas.md |
| Performance | ./references/gotchas.md |
| Best practices | ./references/gotchas.md |
Citation
When this library is used as a primary analytical tool, include in the report's Software & Tools references:
Plotly Technologies Inc. Plotly: Interactive graphing library [Computer software]. https://plotly.com/
Cite when: Plotly is the primary visualization library producing interactive figures included in the report or notebook. Do not cite when: Only used for quick exploratory plots not included in deliverables.
Chart Types
Scatter Plots
Plotly Express
import plotly.express as px
# Basic scatter
fig = px.scatter(df, x="col_x", y="col_y")
# With color, size, and hover
fig = px.scatter(
df,
x="sepal_width",
y="sepal_length",
color="species", # Color by category
size="petal_length", # Size by value
hover_data=["petal_width"] # Extra hover info
)Graph Objects
import plotly.graph_objects as go
fig = go.Figure(go.Scatter(
x=[1, 2, 3, 4],
y=[10, 11, 12, 13],
mode="markers", # "markers", "lines", or "lines+markers"
marker=dict(size=10, color="blue")
))Common Options
| Parameter (px) | Purpose |
|---|---|
color | Color points by column |
size | Size points by column |
symbol | Shape by column |
hover_data | Additional hover columns |
hover_name | Main hover label |
text | Text labels on points |
trendline | Add trendline ("ols", "lowess") |
marginal_x/y | Marginal plots ("histogram", "box", "violin", "rug") |
---
Line Charts
Plotly Express
# Basic line
fig = px.line(df, x="date", y="value")
# Multiple lines by color
fig = px.line(df, x="date", y="value", color="category")
# With markers
fig = px.line(df, x="date", y="value", markers=True)Graph Objects
fig = go.Figure()
fig.add_trace(go.Scatter(
x=dates,
y=values,
mode="lines", # or "lines+markers"
name="Series A",
line=dict(color="blue", width=2, dash="dash") # dash: "solid", "dot", "dash"
))Line Styles
# Dashed line
fig.add_trace(go.Scatter(
x=x, y=y,
mode="lines",
line=dict(dash="dash") # "solid", "dot", "dash", "longdash", "dashdot"
))---
Bar Charts
Plotly Express
# Vertical bars
fig = px.bar(df, x="category", y="value")
# Horizontal bars
fig = px.bar(df, x="value", y="category", orientation="h")
# Grouped bars
fig = px.bar(df, x="category", y="value", color="group", barmode="group")
# Stacked bars
fig = px.bar(df, x="category", y="value", color="group", barmode="stack")Graph Objects
fig = go.Figure()
fig.add_trace(go.Bar(x=categories, y=values, name="Series A"))
fig.add_trace(go.Bar(x=categories, y=values2, name="Series B"))
# Set bar mode
fig.update_layout(barmode="group") # "group", "stack", "relative", "overlay"Bar Options
| Parameter | Purpose |
|---|---|
barmode | "group", "stack", "relative", "overlay" |
orientation | "v" (vertical) or "h" (horizontal) |
text | Labels on bars |
text_auto | Auto-format bar labels (px) |
---
Histograms
Plotly Express
# Basic histogram
fig = px.histogram(df, x="value")
# With bins
fig = px.histogram(df, x="value", nbins=30)
# Colored by category
fig = px.histogram(df, x="value", color="category")
# Stacked or overlaid
fig = px.histogram(df, x="value", color="category", barmode="overlay")Graph Objects
fig = go.Figure(go.Histogram(
x=data,
nbinsx=30,
name="Distribution"
))Histogram Options
| Parameter | Purpose |
|---|---|
nbins | Number of bins |
histnorm | Normalization: "percent", "probability", "density", "probability density" |
cumulative | Cumulative histogram |
barmode | "stack", "overlay", "group" |
marginal | Add marginal plot: "rug", "box", "violin" |
---
Box Plots
Plotly Express
# Basic box plot
fig = px.box(df, y="value")
# Grouped by category
fig = px.box(df, x="category", y="value")
# With individual points
fig = px.box(df, x="category", y="value", points="all") # "all", "outliers", "suspectedoutliers", False
# Notched (confidence interval)
fig = px.box(df, x="category", y="value", notched=True)Graph Objects
fig = go.Figure(go.Box(
y=data,
name="Distribution",
boxpoints="all", # "all", "outliers", "suspectedoutliers", False
jitter=0.3,
pointpos=-1.8
))---
Violin Plots
Plotly Express
# Basic violin
fig = px.violin(df, y="value")
# Grouped
fig = px.violin(df, x="category", y="value")
# With box and points
fig = px.violin(df, x="category", y="value", box=True, points="all")Graph Objects
fig = go.Figure(go.Violin(
y=data,
box_visible=True,
meanline_visible=True
))---
Heatmaps
Plotly Express
# From 2D array
fig = px.imshow(z_data)
# With labels
fig = px.imshow(
z_data,
labels=dict(x="X Label", y="Y Label", color="Value"),
x=x_labels,
y=y_labels
)
# Color scale
fig = px.imshow(z_data, color_continuous_scale="Viridis")Graph Objects
fig = go.Figure(go.Heatmap(
z=z_data,
x=x_labels,
y=y_labels,
colorscale="Viridis"
))Annotated Heatmap
import plotly.figure_factory as ff
fig = ff.create_annotated_heatmap(
z=z_data,
x=x_labels,
y=y_labels,
colorscale="Blues"
)---
Pie Charts
Plotly Express
fig = px.pie(df, values="value", names="category")
# Donut chart
fig = px.pie(df, values="value", names="category", hole=0.4)Graph Objects
fig = go.Figure(go.Pie(
labels=categories,
values=values,
hole=0.4 # For donut
))---
Other Chart Types
3D Scatter
fig = px.scatter_3d(df, x="x", y="y", z="z", color="category")3D Surface
fig = go.Figure(go.Surface(z=z_data, x=x, y=y))Geographic Maps
# Choropleth (colored regions)
fig = px.choropleth(
df,
locations="country_code",
color="value",
locationmode="ISO-3"
)
# Scatter on map
fig = px.scatter_geo(df, lat="latitude", lon="longitude", size="value")Financial Charts
# Candlestick
fig = go.Figure(go.Candlestick(
x=dates,
open=open_prices,
high=high_prices,
low=low_prices,
close=close_prices
))
# OHLC
fig = go.Figure(go.Ohlc(
x=dates,
open=open_prices,
high=high_prices,
low=low_prices,
close=close_prices
))Hierarchical Charts
# Sunburst
fig = px.sunburst(df, path=["continent", "country", "city"], values="population")
# Treemap
fig = px.treemap(df, path=["continent", "country"], values="population")Polar Charts
# Polar scatter
fig = px.scatter_polar(df, r="value", theta="angle", color="category")
# Polar bar (wind rose)
fig = px.bar_polar(df, r="frequency", theta="direction", color="strength")---
Chart Selection Guide
| Data Type | Chart |
|---|---|
| Two continuous variables | px.scatter() |
| Time series | px.line() |
| Categories vs values | px.bar() |
| Distribution (one var) | px.histogram() |
| Distribution comparison | px.box() or px.violin() |
| Matrix/correlation | px.imshow() (heatmap) |
| Part of whole | px.pie() |
| 3D relationships | px.scatter_3d() |
| Geographic data | px.choropleth() or px.scatter_geo() |
| Hierarchical data | px.sunburst() or px.treemap() |
Export & Saving
Interactive HTML
Basic HTML Export
# Save to file
fig.write_html("plot.html")
# Get HTML string
html_string = fig.to_html()Include Plotly.js Options
# Full file with embedded Plotly.js (~3MB)
fig.write_html("plot.html", include_plotlyjs=True)
# Use CDN (smaller file, requires internet)
fig.write_html("plot.html", include_plotlyjs="cdn")
# Separate directory (reusable across files)
fig.write_html("plot.html", include_plotlyjs="directory")
# No Plotly.js (for embedding in page that already has it)
fig.write_html("plot.html", include_plotlyjs=False)Full HTML vs Div Only
# Full HTML document
fig.write_html("plot.html", full_html=True)
# Just the div (for embedding)
fig.write_html("plot.html", full_html=False)
# Or get div string
div_string = fig.to_html(full_html=False)Config Options
fig.write_html(
"plot.html",
config={
"displayModeBar": False, # Hide toolbar
"scrollZoom": False, # Disable scroll zoom
"staticPlot": True, # Disable all interaction
"displaylogo": False, # Hide Plotly logo
"modeBarButtonsToRemove": ["zoom2d", "pan2d"] # Remove specific buttons
}
)Animation Options
fig.write_html(
"plot.html",
auto_play=False # Don't auto-play animations
)---
Static Images
DAAF note: kaleido is NOT installed in the DAAF container. Static image
export (write_image) is unavailable. Use plotnine for static PNG/SVGfigures in reports. Reserve Plotly for interactive HTML output. The kaleido
package requires a bundled Chromium browser (~300MB) plus 9 system shared
libraries, which is excessive for this use case. The reference below is
retained for completeness if kaleido is installed in a custom environment.
Installation
pip install -U kaleidoBasic Image Export
# PNG
fig.write_image("plot.png")
# JPEG
fig.write_image("plot.jpg")
# SVG (vector)
fig.write_image("plot.svg")
# PDF (vector)
fig.write_image("plot.pdf")
# WebP
fig.write_image("plot.webp")Size and Resolution
fig.write_image(
"plot.png",
width=1200, # Pixels
height=800, # Pixels
scale=2 # 2x resolution (for retina/print)
)Get Image Bytes
# Get bytes (for APIs, in-memory use)
img_bytes = fig.to_image(format="png")
# With size
img_bytes = fig.to_image(format="png", width=800, height=600)Supported Formats
| Format | Extension | Type | Notes |
|---|---|---|---|
| PNG | .png | Raster | Best for web |
| JPEG | .jpg | Raster | Smaller, lossy |
| WebP | .webp | Raster | Modern, efficient |
| SVG | .svg | Vector | Scalable, editable |
.pdf | Vector | Print quality |
---
JSON Export
Save to JSON
# Write to file
fig.write_json("plot.json")
# Get JSON string
json_string = fig.to_json()Load from JSON
import plotly.io as pio
# From file
fig = pio.read_json("plot.json")
# From string
fig = pio.from_json(json_string)Dictionary Representation
# Get figure as dict
fig_dict = fig.to_dict()
# Access parts
fig_dict["data"] # Traces
fig_dict["layout"] # Layout
# Create figure from dict
fig = go.Figure(fig_dict)---
Embedding in Web Pages
Basic Embed
<!DOCTYPE html>
<html>
<head>
<!-- Warning: plotly-latest.min.js is frozen at v1.58.5. For Plotly 6.x, use a versioned URL, e.g., plotly-2.35.2.min.js -->
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
</head>
<body>
<!-- Get div from fig.to_html(full_html=False) -->
<div id="plotly-div">
<!-- Plot will render here -->
</div>
</body>
</html>Using to_html for Embedding
# Get just the div + script
div_html = fig.to_html(
full_html=False,
include_plotlyjs="cdn" # Or False if page already has it
)
# Insert div_html into your page templateResponsive Sizing
fig.update_layout(
autosize=True,
width=None, # Let container control width
height=None
)
html = fig.to_html(
full_html=False,
include_plotlyjs="cdn",
default_width="100%",
default_height="500px"
)---
Batch Export
Multiple Figures
import plotly.io as pio
figures = [fig1, fig2, fig3]
for i, fig in enumerate(figures):
fig.write_image(f"plot_{i}.png")
fig.write_html(f"plot_{i}.html")Using pio Functions
import plotly.io as pio
# These work on figure objects directly
pio.write_html(fig, "plot.html")
pio.write_image(fig, "plot.png")
pio.write_json(fig, "plot.json")
# Read functions
fig = pio.read_json("plot.json")---
Export Configuration
Set Default Image Engine
import plotly.io as pio
# Use kaleido (recommended)
# Note (Plotly 6.2+): pio.kaleido.scope.* is deprecated. Use pio.defaults.* instead,
# e.g., pio.defaults.default_format = 'png'
pio.kaleido.scope.default_format = "png"
pio.kaleido.scope.default_width = 800
pio.kaleido.scope.default_height = 600
pio.kaleido.scope.default_scale = 2Default Export Settings
# Set defaults that apply to all exports
pio.kaleido.scope.default_format = "png"
pio.kaleido.scope.default_width = 1200
pio.kaleido.scope.default_height = 800---
Quick Reference
| Task | Method |
|---|---|
| Interactive HTML file | fig.write_html("plot.html") |
| HTML string | fig.to_html() |
| PNG image | fig.write_image("plot.png") |
| High-res PNG | fig.write_image("plot.png", scale=2) |
| SVG (vector) | fig.write_image("plot.svg") |
fig.write_image("plot.pdf") | |
| JSON file | fig.write_json("plot.json") |
| JSON string | fig.to_json() |
| Image bytes | fig.to_image(format="png") |
| Load from JSON | pio.read_json("plot.json") |
Gotchas & Best Practices
Plotly Express vs Graph Objects
The Key Insight
px functions return go.Figure objects. You can use all go methods on px figures.
import plotly.express as px
fig = px.scatter(df, x="x", y="y") # Returns go.Figure
fig.update_layout(title="My Title") # All go methods work
fig.add_trace(...) # Can add more tracesWhen to Use Each
| Situation | Recommendation |
|---|---|
| Quick exploration | px |
| Standard charts | px |
| Need full control | go |
| Multiple trace types | go (or px + add_trace) |
| Custom subplots | go + make_subplots |
| Complex animations | go |
Converting px to go
If you need more control, start with px and modify:
# Start with px
fig = px.scatter(df, x="x", y="y", color="category")
# Access and modify traces
for trace in fig.data:
trace.marker.size = 15
# Or update all at once
fig.update_traces(marker_size=15)---
Common Errors
"No module named 'kaleido'" / write_image fails
Error: ValueError: Image export using the "kaleido" engine requires the kaleido package
DAAF context: kaleido is intentionally excluded from the DAAF container due to its heavy Chromium dependency (~300MB binary + 9 system shared libraries). Use plotnine for static figure export (PNG/SVG) and reserve Plotly for interactive HTML output via write_html().
If you need kaleido in a custom environment:
pip install -U kaleidoFigure Not Displaying
Problem: Figure doesn't appear in Jupyter or script.
Fixes:
# In scripts, always call show()
fig.show()
# In Jupyter, ensure plotly extension is loaded
# Or explicitly show
fig.show()
# Check renderer
import plotly.io as pio
pio.renderers.default = "notebook" # For Jupyter
pio.renderers.default = "browser" # For scriptsColumn Not Found
Error: KeyError: 'column_name'
Causes:
- Column name misspelled
- DataFrame doesn't have the column
- Using variable instead of string
# WRONG: using variable name
px.scatter(df, x=column_x, y=column_y)
# CORRECT: using string
px.scatter(df, x="column_x", y="column_y")Empty or Blank Plot
Causes:
- Data has NaN/null values
- Wrong column names
- Data types mismatch
Fixes:
# Check your data
print(df.head())
print(df.dtypes)
print(df.isna().sum())
# Drop NaN for plotting
fig = px.scatter(df.dropna(subset=["x", "y"]), x="x", y="y")Legend Issues
# Hide legend
fig.update_layout(showlegend=False)
# Rename legend entries
fig.update_traces(name="New Name", selector=dict(name="Old Name"))
# Hide specific trace from legend
fig.update_traces(showlegend=False, selector=dict(name="Trace Name"))---
Performance
Large Datasets
For datasets with >10,000 points, use WebGL rendering:
# Plotly Express automatically uses WebGL for large data
# But you can force it with go.Scattergl
import plotly.graph_objects as go
fig = go.Figure(go.Scattergl( # Note: Scattergl, not Scatter
x=large_x,
y=large_y,
mode="markers"
))Performance Tips
| Points | Recommendation |
|---|---|
| < 1,000 | Standard Scatter works fine |
| 1,000 - 100,000 | Use Scattergl |
| > 100,000 | Sample data or aggregate |
Reduce Data
# Sample for exploration
fig = px.scatter(df.sample(1000), x="x", y="y")
# Aggregate for visualization
df_agg = df.groupby("category").mean().reset_index()
fig = px.bar(df_agg, x="category", y="value")Disable Animations
fig.update_layout(transition_duration=0)---
Data Type Issues
Categorical vs Numeric
# Problem: Numeric treated as categorical
fig = px.scatter(df, x="year", y="value") # year=2020 might be categorical
# Fix: Ensure correct type
df["year"] = df["year"].astype(int)
# Or explicitly set axis type
fig.update_xaxes(type="linear") # Force numeric
fig.update_xaxes(type="category") # Force categoricalDate/Time
# Ensure dates are datetime type
df["date"] = pd.to_datetime(df["date"])
# Plotly handles datetime automatically
fig = px.line(df, x="date", y="value")
# Format date axis
fig.update_xaxes(tickformat="%Y-%m-%d")---
Subplot Gotchas
Row/Col Indexing
Subplots use 1-based indexing:
# CORRECT: row=1, col=1 for top-left
fig.add_trace(trace, row=1, col=1)
# WRONG: 0-based indexing
fig.add_trace(trace, row=0, col=0) # Error!Updating Specific Axes
# Update specific subplot axes
fig.update_xaxes(title_text="X", row=1, col=1)
fig.update_yaxes(title_text="Y", row=1, col=1)
# Without row/col, updates ALL axes
fig.update_xaxes(showgrid=False) # All x-axesSecondary Y-Axis
Must be declared in make_subplots:
from plotly.subplots import make_subplots
fig = make_subplots(specs=[[{"secondary_y": True}]])
fig.add_trace(go.Scatter(...), secondary_y=False)
fig.add_trace(go.Bar(...), secondary_y=True)---
Styling Gotchas
Color in aes vs Fixed Color
# Map color to data (in aes)
fig = px.scatter(df, x="x", y="y", color="category")
# Fixed color for all points (outside aes)
fig = px.scatter(df, x="x", y="y")
fig.update_traces(marker_color="red")Template Overrides
Templates set defaults; explicit settings override:
fig = px.scatter(df, x="x", y="y", template="plotly_dark")
fig.update_layout(paper_bgcolor="white") # Overrides templateLegend Order
# Control order via category_orders
fig = px.bar(df, x="cat", y="val", color="group",
category_orders={"group": ["A", "B", "C"]})---
Best Practices
1. Start with px, Customize with go Methods
fig = (
px.scatter(df, x="x", y="y", color="cat")
.update_traces(marker_size=10)
.update_layout(title="My Plot")
)2. Use Templates for Consistency
import plotly.io as pio
pio.templates.default = "plotly_white"3. Save Output
fig.write_html("plot.html") # Interactive (primary export in DAAF)
# fig.write_image() is NOT available — kaleido not installed
# Use plotnine for static PNG/SVG figures in reports4. Use Meaningful Hover Templates
fig.update_traces(
hovertemplate="<b>%{x}</b><br>Value: %{y:.2f}<extra></extra>"
)5. Check Data Before Plotting
# Always verify
print(df.shape)
print(df.dtypes)
print(df.isna().sum())---
Quick Fixes
| Problem | Solution |
|---|---|
| Plot not showing | fig.show() or check renderer |
| Image export fails | Not available in DAAF (no kaleido); use plotnine for static images |
| Wrong colors | Check color= vs marker_color= |
| Axis wrong type | fig.update_xaxes(type="linear") |
| Slow with big data | Use go.Scattergl |
| Legend unwanted | showlegend=False |
| Grid lines ugly | showgrid=False |
| Title not centered | title=dict(x=0.5) |
| Overlapping labels | tickangle=45 |
| Too much whitespace | Adjust margin=dict(...) |
Quickstart
Installation
# Basic install
pip install plotly
# With image export support (not installed in DAAF — use plotnine for static figures)
# pip install plotly kaleido
# Using conda
conda install -c conda-forge plotlyCore Imports
import plotly.express as px # High-level API (recommended start)
import plotly.graph_objects as go # Low-level API (full control)
from plotly.subplots import make_subplots # Multi-panel figures
import plotly.io as pio # I/O and configurationPlotly Express vs Graph Objects
Plotly has two main APIs:
Plotly Express (px)
Best for: Quick exploration, standard charts, clean code.
import plotly.express as px
fig = px.scatter(df, x="sepal_width", y="sepal_length", color="species")
fig.show()- One function call creates complete figure
- Automatic legends, axes, hover info
- Returns a
go.Figure(can use all go methods) - Supports 30+ chart types
Graph Objects (go)
Best for: Custom layouts, fine-grained control, complex figures.
import plotly.graph_objects as go
fig = go.Figure()
fig.add_trace(go.Scatter(
x=[1, 2, 3],
y=[4, 5, 6],
mode="markers",
name="Series A"
))
fig.update_layout(title="My Plot")
fig.show()- Build figures trace by trace
- Full control over every property
- More verbose but more flexible
When to Use Each
| Use Case | Recommendation |
|---|---|
| Quick data exploration | px |
| Standard chart types | px |
| Complex customization | go |
| Multiple trace types | go |
| Subplots with mixed types | go + make_subplots |
| Starting point, then customize | px, then update_* methods |
Figure Anatomy
Every Plotly figure has two main parts:
fig = go.Figure(
data=[...], # List of traces (the actual data/charts)
layout={...} # Layout settings (title, axes, legend, etc.)
)Access them:
fig.data # Tuple of traces
fig.layout # Layout objectBuilt-in Datasets
Plotly Express includes sample datasets:
# Load built-in datasets
df = px.data.iris() # Iris flower measurements
df = px.data.tips() # Restaurant tips
df = px.data.gapminder() # Country statistics over time
df = px.data.stocks() # Stock prices
df = px.data.medals_long() # Olympic medals
df = px.data.wind() # Wind patterns
df = px.data.carshare() # Car sharing locations
df = px.data.election() # Election resultsQuick example:
import plotly.express as px
df = px.data.iris()
fig = px.scatter(df, x="sepal_width", y="sepal_length", color="species")
fig.show()Displaying Figures
In Jupyter Notebooks
Figures display automatically as the last expression:
px.scatter(df, x="x", y="y") # Auto-displaysOr explicitly:
fig = px.scatter(df, x="x", y="y")
fig.show()In Python Scripts
Always call .show():
fig = px.scatter(df, x="x", y="y")
fig.show() # Opens in browserOr save to file:
fig.write_html("plot.html") # Interactive (primary export in DAAF)
# fig.write_image("plot.png") # NOT available — kaleido not installed; use plotnine for staticRenderer Configuration
import plotly.io as pio
# See available renderers
print(pio.renderers)
# Set default renderer
pio.renderers.default = "browser" # Open in browser
pio.renderers.default = "notebook" # Jupyter notebook
pio.renderers.default = "svg" # Static SVGBasic Patterns
Creating a Figure with px
# Most px functions follow this pattern
fig = px.chart_type(
data_frame, # DataFrame or dict
x="column_name", # X-axis column
y="column_name", # Y-axis column
color="column_name", # Color by category (optional)
title="My Title" # Chart title (optional)
)Creating a Figure with go
# Create empty figure
fig = go.Figure()
# Add traces
fig.add_trace(go.Scatter(x=x, y=y, name="Series 1"))
fig.add_trace(go.Bar(x=x, y=y2, name="Series 2"))
# Update layout
fig.update_layout(title="My Plot", xaxis_title="X", yaxis_title="Y")
# Show
fig.show()Updating Figures
Both px and go figures support update methods:
# Update layout
fig.update_layout(title="New Title", height=500)
# Update all traces
fig.update_traces(marker_size=10)
# Update axes
fig.update_xaxes(title_text="X Axis")
fig.update_yaxes(title_text="Y Axis")Method Chaining
All update methods return the figure, enabling chaining:
fig = (
px.scatter(df, x="x", y="y", color="category")
.update_layout(title="My Plot")
.update_traces(marker_size=12)
.update_xaxes(showgrid=False)
)Quick Comparison
| Task | Plotly Express | Graph Objects |
|---|---|---|
| Scatter plot | px.scatter(df, x="a", y="b") | go.Figure(go.Scatter(x=x, y=y)) |
| Add color | color="col" parameter | marker_color= in trace |
| Add title | title="My Title" | .update_layout(title="My Title") |
| Multiple series | color="series_col" | Multiple add_trace() calls |
Styling & Customization
Templates (Themes)
Built-in Templates
import plotly.io as pio
# List available templates
print(pio.templates)| Template | Description |
|---|---|
plotly | Default Plotly theme |
plotly_white | White background, minimal |
plotly_dark | Dark theme |
ggplot2 | R ggplot2 style |
seaborn | Seaborn style |
simple_white | Clean, minimal |
presentation | Large fonts for slides |
none | No default styling |
Using Templates
# In Plotly Express
fig = px.scatter(df, x="x", y="y", template="plotly_dark")
# In Graph Objects
fig.update_layout(template="plotly_white")
# Set default for all figures
import plotly.io as pio
pio.templates.default = "plotly_dark"Combining Templates
fig.update_layout(template="plotly_dark+presentation")---
Layout Customization
Title
fig.update_layout(
title=dict(
text="My Chart Title",
x=0.5, # Center (0=left, 1=right)
font=dict(size=24)
)
)
# Simple version
fig.update_layout(title="My Chart Title")Size
fig.update_layout(
width=800,
height=600,
autosize=False
)Margins
fig.update_layout(
margin=dict(l=50, r=50, t=80, b=50) # left, right, top, bottom
)
# Tight margins
fig.update_layout(margin=dict(l=0, r=0, t=30, b=0))Background
fig.update_layout(
paper_bgcolor="white", # Area outside plot
plot_bgcolor="lightgray" # Plot area
)Font
fig.update_layout(
font=dict(
family="Arial, sans-serif",
size=14,
color="black"
)
)---
Axis Customization
Axis Titles
fig.update_xaxes(title_text="X Axis Label")
fig.update_yaxes(title_text="Y Axis Label")
# Or via layout
fig.update_layout(
xaxis_title="X Axis Label",
yaxis_title="Y Axis Label"
)Axis Range
fig.update_xaxes(range=[0, 100])
fig.update_yaxes(range=[-10, 10])
# Auto-range with padding
fig.update_yaxes(rangemode="tozero") # Start at zeroTick Formatting
fig.update_xaxes(
tickformat=".2f", # Number format
tickprefix="$", # Prefix
ticksuffix="%", # Suffix
tickangle=45, # Rotate labels
dtick=10 # Tick interval
)
# Date format
fig.update_xaxes(tickformat="%Y-%m-%d")Log Scale
fig.update_yaxes(type="log")Reversed Axis
fig.update_yaxes(autorange="reversed")Grid Lines
fig.update_xaxes(
showgrid=True,
gridwidth=1,
gridcolor="lightgray"
)
# Hide grid
fig.update_xaxes(showgrid=False)
fig.update_yaxes(showgrid=False)Axis Lines
fig.update_xaxes(
showline=True,
linewidth=2,
linecolor="black",
mirror=True # Show on opposite side too
)---
Legend
Position
fig.update_layout(
legend=dict(
x=1, # 0=left, 1=right
y=1, # 0=bottom, 1=top
xanchor="left",
yanchor="top"
)
)
# Horizontal legend at bottom
fig.update_layout(
legend=dict(
orientation="h",
x=0.5,
y=-0.1,
xanchor="center"
)
)Legend Title
fig.update_layout(legend_title_text="Categories")Hide Legend
fig.update_layout(showlegend=False)Legend Appearance
fig.update_layout(
legend=dict(
bgcolor="white",
bordercolor="black",
borderwidth=1
)
)---
Colors
Discrete Colors (Categories)
# Plotly Express - use built-in sequence
fig = px.scatter(df, x="x", y="y", color="category",
color_discrete_sequence=px.colors.qualitative.Set1)
# Custom color map
fig = px.scatter(df, x="x", y="y", color="category",
color_discrete_map={"A": "red", "B": "blue", "C": "green"})Built-in Color Sequences
import plotly.express as px
px.colors.qualitative.Plotly # Default
px.colors.qualitative.Set1
px.colors.qualitative.Set2
px.colors.qualitative.Pastel
px.colors.qualitative.Dark24
px.colors.qualitative.AlphabetContinuous Colors (Numeric)
# Plotly Express
fig = px.scatter(df, x="x", y="y", color="value",
color_continuous_scale="Viridis")
# Built-in scales: "Viridis", "Plasma", "Inferno", "Magma",
# "Blues", "Reds", "Greens", "RdBu", "Spectral", etc.Reversed Color Scale
fig = px.scatter(df, x="x", y="y", color="value",
color_continuous_scale="Viridis_r") # _r for reversedGraph Objects Colors
fig.add_trace(go.Scatter(
x=x, y=y,
marker=dict(
color=values, # Color by values
colorscale="Viridis",
showscale=True # Show colorbar
)
))
# Fixed color
fig.add_trace(go.Scatter(
x=x, y=y,
marker_color="red"
))---
Hover Customization
Hover Template
fig.update_traces(
hovertemplate="X: %{x}<br>Y: %{y}<br>Name: %{text}<extra></extra>"
)
# Format numbers
hovertemplate="Value: %{y:.2f}<extra></extra>"
# <extra></extra> removes trace name boxHover Info
# Control what appears on hover
fig.update_traces(hoverinfo="x+y") # Only x and y
# Options: "x", "y", "z", "text", "name", "all", "none", "skip"Hover Data (px)
fig = px.scatter(df, x="x", y="y",
hover_data=["col1", "col2"], # Add columns
hover_name="name_col") # Main labelHover Mode
fig.update_layout(hovermode="x unified") # All traces at same x
# Options: "x", "y", "closest", "x unified", "y unified", False---
Annotations
Text Annotations
fig.add_annotation(
x=2,
y=5,
text="Important point",
showarrow=True,
arrowhead=2
)Multiple Annotations
fig.update_layout(
annotations=[
dict(x=1, y=2, text="Point A", showarrow=True),
dict(x=3, y=4, text="Point B", showarrow=True),
]
)Annotation Styling
fig.add_annotation(
x=2, y=5,
text="Label",
font=dict(size=14, color="red"),
bgcolor="white",
bordercolor="black",
borderwidth=1
)---
Shapes
Reference Lines
# Horizontal line
fig.add_hline(y=50, line_dash="dash", line_color="red")
# Vertical line
fig.add_vline(x="2020-01-01", line_dash="dot")
# With annotation
fig.add_hline(y=50, annotation_text="Target")Rectangles
fig.add_vrect(x0="2020-01", x1="2020-06", fillcolor="green", opacity=0.2)
fig.add_hrect(y0=0, y1=50, fillcolor="red", opacity=0.1)Custom Shapes
fig.add_shape(
type="rect",
x0=1, x1=3,
y0=1, y1=4,
line=dict(color="blue"),
fillcolor="lightblue",
opacity=0.5
)---
Quick Styling Recipes
Publication-Ready
fig.update_layout(
template="simple_white",
font=dict(family="Arial", size=12),
title=dict(font=dict(size=16)),
margin=dict(l=60, r=20, t=40, b=40)
)
fig.update_xaxes(showline=True, linewidth=1, linecolor="black")
fig.update_yaxes(showline=True, linewidth=1, linecolor="black")Dark Theme
fig.update_layout(
template="plotly_dark",
paper_bgcolor="#1e1e1e",
plot_bgcolor="#1e1e1e"
)Minimal
fig.update_layout(
template="simple_white",
showlegend=False
)
fig.update_xaxes(showgrid=False)
fig.update_yaxes(showgrid=False)Subplots & Faceting
Faceting with Plotly Express
Faceting splits data into multiple panels by category. This is the easiest way to create subplots.
Basic Faceting
import plotly.express as px
# Facet by column (horizontal split)
fig = px.scatter(df, x="x", y="y", facet_col="category")
# Facet by row (vertical split)
fig = px.scatter(df, x="x", y="y", facet_row="category")
# Both row and column
fig = px.scatter(df, x="x", y="y", facet_row="cat1", facet_col="cat2")Wrapping Columns
# Wrap to multiple rows
fig = px.scatter(df, x="x", y="y", facet_col="category", facet_col_wrap=3)Facet Options
| Parameter | Purpose |
|---|---|
facet_col | Column for horizontal facets |
facet_row | Column for vertical facets |
facet_col_wrap | Max columns before wrapping |
facet_row_spacing | Vertical spacing (0-1) |
facet_col_spacing | Horizontal spacing (0-1) |
Independent Axes
By default, facets share axes. To make them independent:
fig = px.scatter(df, x="x", y="y", facet_col="category")
fig.update_yaxes(matches=None) # Independent y-axes
fig.update_xaxes(matches=None) # Independent x-axesCustomizing Facet Labels
fig.for_each_annotation(lambda a: a.update(text=a.text.split("=")[-1]))---
make_subplots
For more control, use make_subplots from plotly.subplots.
Basic Grid
from plotly.subplots import make_subplots
import plotly.graph_objects as go
# Create 2x2 grid
fig = make_subplots(rows=2, cols=2)
# Add traces to specific positions
fig.add_trace(go.Scatter(x=[1,2,3], y=[4,5,6]), row=1, col=1)
fig.add_trace(go.Bar(x=["A","B"], y=[1,2]), row=1, col=2)
fig.add_trace(go.Scatter(x=[1,2,3], y=[6,5,4]), row=2, col=1)
fig.add_trace(go.Histogram(x=[1,1,2,3,3,3]), row=2, col=2)
fig.show()With Titles
fig = make_subplots(
rows=2, cols=2,
subplot_titles=("Plot 1", "Plot 2", "Plot 3", "Plot 4")
)Shared Axes
# Share x-axes within columns
fig = make_subplots(rows=2, cols=1, shared_xaxes=True)
# Share y-axes within rows
fig = make_subplots(rows=1, cols=2, shared_yaxes=True)
# Share all axes
fig = make_subplots(rows=2, cols=2, shared_xaxes=True, shared_yaxes=True)Custom Spacing
fig = make_subplots(
rows=2, cols=2,
horizontal_spacing=0.1, # Space between columns (0-1)
vertical_spacing=0.1 # Space between rows (0-1)
)Column/Row Sizing
fig = make_subplots(
rows=2, cols=2,
column_widths=[0.7, 0.3], # First column 70%, second 30%
row_heights=[0.4, 0.6] # First row 40%, second 60%
)---
Spanning Rows/Columns
Colspan and Rowspan
fig = make_subplots(
rows=2, cols=2,
specs=[
[{"colspan": 2}, None], # First row spans both columns
[{}, {}] # Second row has 2 plots
]
)
fig.add_trace(go.Scatter(x=[1,2,3], y=[1,2,3]), row=1, col=1) # Spans both cols
fig.add_trace(go.Bar(x=["A","B"], y=[1,2]), row=2, col=1)
fig.add_trace(go.Bar(x=["A","B"], y=[2,1]), row=2, col=2)Row Span
fig = make_subplots(
rows=2, cols=2,
specs=[
[{"rowspan": 2}, {}], # Left plot spans both rows
[None, {}]
]
)---
Mixed Chart Types
Specifying Types
fig = make_subplots(
rows=1, cols=2,
specs=[
[{"type": "xy"}, {"type": "pie"}]
]
)
fig.add_trace(go.Scatter(x=[1,2,3], y=[4,5,6]), row=1, col=1)
fig.add_trace(go.Pie(labels=["A","B","C"], values=[1,2,3]), row=1, col=2)Common Types
| Type | Use For |
|---|---|
"xy" | Cartesian plots (scatter, bar, line) |
"pie" | Pie charts |
"polar" | Polar charts |
"scene" | 3D plots |
"geo" | Geographic maps |
"mapbox" | Mapbox maps |
"domain" | Sunburst, treemap, etc. |
Example: Mixed 2D and 3D
fig = make_subplots(
rows=1, cols=2,
specs=[[{"type": "xy"}, {"type": "scene"}]]
)
fig.add_trace(go.Scatter(x=[1,2,3], y=[1,2,3]), row=1, col=1)
fig.add_trace(go.Scatter3d(x=[1,2,3], y=[1,2,3], z=[1,2,3]), row=1, col=2)---
Secondary Y-Axis
With make_subplots
fig = make_subplots(specs=[[{"secondary_y": True}]])
fig.add_trace(go.Scatter(x=[1,2,3], y=[40,50,60], name="Left"), secondary_y=False)
fig.add_trace(go.Bar(x=[1,2,3], y=[4,5,6], name="Right"), secondary_y=True)
# Set axis titles
fig.update_yaxes(title_text="Left Axis", secondary_y=False)
fig.update_yaxes(title_text="Right Axis", secondary_y=True)---
Combining px Figures in Subplots
Extract traces from px figures and add to subplots:
from plotly.subplots import make_subplots
import plotly.express as px
# Create px figures
fig1 = px.scatter(df1, x="x", y="y")
fig2 = px.bar(df2, x="category", y="value")
# Create subplot
fig = make_subplots(rows=1, cols=2)
# Add traces from px figures
for trace in fig1.data:
fig.add_trace(trace, row=1, col=1)
for trace in fig2.data:
fig.add_trace(trace, row=1, col=2)
fig.show()---
Updating Subplot Axes
By Position
fig.update_xaxes(title_text="X Label", row=1, col=1)
fig.update_yaxes(title_text="Y Label", row=1, col=1)All Axes
fig.update_xaxes(showgrid=False) # All x-axes
fig.update_yaxes(showgrid=False) # All y-axes---
Common Patterns
Dashboard Layout
fig = make_subplots(
rows=2, cols=2,
specs=[
[{"colspan": 2}, None],
[{}, {}]
],
subplot_titles=("Overview", "Detail 1", "Detail 2"),
vertical_spacing=0.15
)
# Main chart spans top row
fig.add_trace(go.Scatter(x=x, y=y), row=1, col=1)
# Two detail charts below
fig.add_trace(go.Bar(x=cats, y=vals1), row=2, col=1)
fig.add_trace(go.Bar(x=cats, y=vals2), row=2, col=2)
fig.update_layout(height=600, title_text="Dashboard")Comparison Grid
fig = make_subplots(
rows=2, cols=2,
shared_xaxes=True,
shared_yaxes=True,
subplot_titles=("Q1", "Q2", "Q3", "Q4")
)
# Add same chart type to each
for i, (row, col) in enumerate([(1,1), (1,2), (2,1), (2,2)]):
fig.add_trace(
go.Scatter(x=x, y=data[i], name=f"Q{i+1}"),
row=row, col=col
)Related skills
FAQ
When should I use Plotly over plotnine?
Prefer Plotly over plotnine when interactivity like hover, zoom, or pan is required; use plotnine for static figures.
Can Plotly export static images here?
Static image export is not available in DAAF because kaleido is not installed; use HTML output or plotnine for static figures.