| name | plotly |
| description | [Applies to: **/*.py] Definitive guidelines for writing maintainable, performant, and reproducible Plotly visualizations using modern Python best practices, especially for AI/ML workflows. |
| source | cursor_mdc |
Plotly Best Practices
Plotly 6.5.0 is a powerful tool for interactive, publication-quality visualizations, crucial for modern AI/ML workflows. These rules ensure your Plotly code is maintainable, reproducible, and production-ready, leveraging the latest features including AI-assisted Studio tools.
Code Organization and Structure
1. Separate Data Preparation from Visualization
Keep data preprocessing, model outputs, and metric calculations distinct from plotting logic. This improves debugging and reusability.
❌ BAD:
import pandas as pd
import plotly.express as px
def analyze_and_plot_data(raw_data: pd.DataFrame):
processed_df = raw_data[raw_data['value'] > 0]
processed_df['normalized'] = processed_df['value'] / processed_df['value'].max()
fig = px.scatter(processed_df, x='timestamp', y='normalized', title='Normalized Data')
fig.show()
✅ GOOD:
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
def preprocess_data(raw_data: pd.DataFrame) -> pd.DataFrame:
"""Processes raw data for visualization."""
processed_df = raw_data[raw_data['value'] > 0]
processed_df['normalized'] = processed_df['value'] / processed_df['value'].max()
return processed_df
def create_normalized_scatter_plot(data: pd.DataFrame) -> go.Figure:
"""Creates a scatter plot from processed data."""
fig = px.scatter(data, x='timestamp', y='normalized', title='Normalized Data Over Time')
return fig
2. Modular Figure Construction
Treat the Figure object as a modular data structure. Build traces, then define the layout. This mirrors Plotly's internal design and enhances readability.
❌ BAD:
import plotly.graph_objects as go
fig = go.Figure(
data=[{'type': 'bar', 'x': [1, 2, 3], 'y': [10, 15, 13]}],
layout={'title': {'text': 'Sales Data'}, 'xaxis': {'title': {'text': 'Month'}}}
)
✅ GOOD:
import plotly.graph_objects as go
trace1 = go.Bar(x=[1, 2, 3], y=[10, 15, 13], name='Product A')
trace2 = go.Bar(x=[1, 2, 3], y=[12, 10, 18], name='Product B')
fig = go.Figure(data=[trace1, trace2])
fig.update_layout(
title_text='Monthly Sales Data',
xaxis_title_text='Month',
yaxis_title_text='Revenue ($)',
legend_title_text='Product'
)
Common Patterns and Anti-patterns
3. Prefer Plotly Express for Most Charts
Use plotly.express for common chart types. It's terse, consistent, and returns a full go.Figure object, allowing easy customization with graph_objects methods.
❌ BAD:
import plotly.graph_objects as go
import pandas as pd
df = pd.DataFrame({'x': [1, 2, 3], 'y': [4, 1, 2], 'category': ['A', 'B', 'A']})
fig = go.Figure(data=[
go.Scatter(x=df['x'], y=df['y'], mode='markers', marker=dict(color=df['category'].map({'A': 'red', 'B': 'blue'})))
])
fig.update_layout(title_text='Custom Scatter Plot')
✅ GOOD:
import plotly.express as px
import pandas as pd
df = pd.DataFrame({'x': [1, 2, 3], 'y': [4, 1, 2], 'category': ['A', 'B', 'A']})
fig = px.scatter(df, x='x', y='y', color='category', title='Simple Scatter Plot')
fig.update_traces(marker_size=10)
4. Apply Consistent Labeling and Theming
Ensure all plots meet publication-quality standards with clear titles, descriptive axis labels, and consistent fonts. Leverage labels in px and update_layout for global styling.
❌ BAD:
import plotly.express as px
df = px.data.iris()
fig = px.scatter(df, x='sepal_length', y='sepal_width')
fig.show()
✅ GOOD:
import plotly.express as px
df = px.data.iris()
fig = px.scatter(df,
x='sepal_length',
y='sepal_width',
color='species',
labels={
'sepal_length': 'Sepal Length (cm)',
'sepal_width': 'Sepal Width (cm)',
'species': 'Iris Species'
},
title='Iris Sepal Dimensions by Species')
fig.update_layout(
font_family="Arial",
font_color="darkblue",
title_font_size=20,
hoverlabel_font_family="monospace"
)
fig.show()
Performance Considerations
5. Optimize for Large Datasets
For extremely large datasets, consider data aggregation or sampling before plotting to maintain interactivity. Plotly.js can handle many points, but browser rendering has limits. Use go.Scattergl for hardware-accelerated rendering of large scatter plots.
❌ BAD:
import plotly.express as px
import numpy as np
import pandas as pd
large_df = pd.DataFrame({'x': np.random.rand(10_000_000), 'y': np.random.rand(10_000_000)})
fig = px.scatter(large_df, x='x', y='y')
fig.show()
✅ GOOD:
import plotly.graph_objects as go
import numpy as np
import pandas as pd
large_df = pd.DataFrame({'x': np.random.rand(10_000_000), 'y': np.random.rand(10_000_000)})
fig = go.Figure(data=[go.Scattergl(x=large_df['x'], y=large_df['y'], mode='markers')])
fig.update_layout(title_text='Large Data Scatter Plot (WebGL)')
fig.show()
6. Choose Appropriate Output Format
For static reports or non-interactive embedding, export figures as images. For interactive dashboards, use fig.show() or integrate with Dash.
import plotly.express as px
import pandas as pd
df = px.data.iris()
fig = px.scatter(df, x='sepal_length', y='sepal_width', color='species')
fig.show()
fig.write_image("iris_scatter.png", width=800, height=600)
fig.write_html("iris_scatter.html")
Common Pitfalls and Gotchas
7. Avoid Direct Dictionary Manipulation for Figures
While figures can be represented as dictionaries, directly building or modifying them this way bypasses Plotly's validation and convenience methods. Always use plotly.graph_objects.Figure or plotly.express.
❌ BAD:
fig = {
"data": [{"type": "bar", "x": [1, 2], "y": [3, 4]}],
"layout": {"title": {"text": "My Bar Chart"}}
}
import plotly.io as pio
pio.show(fig)
✅ GOOD:
import plotly.graph_objects as go
fig = go.Figure(data=[go.Bar(x=[1, 2], y=[3, 4])])
fig.update_layout(title_text="My Bar Chart")
fig.show()
8. Leverage update_layout and update_traces
These methods provide a clean, efficient way to modify existing figures without recreating them. Use "magic underscores" for concise attribute setting.
❌ BAD:
import plotly.express as px
df = px.data.iris()
fig = px.scatter(df, x='sepal_length', y='sepal_width')
fig.layout.title.text = 'Iris Data'
fig.layout.xaxis.title.text = 'Length'
fig.layout.yaxis.title.text = 'Width'
✅ GOOD:
import plotly.express as px
df = px.data.iris()
fig = px.scatter(df, x='sepal_length', y='sepal_width')
fig.update_layout(
title_text='Iris Sepal Measurements',
xaxis_title_text='Sepal Length (cm)',
yaxis_title_text='Sepal Width (cm)',
font_size=12
)
fig.update_traces(marker_opacity=0.7, selector=dict(type='scatter'))
Type Hints
9. Use Type Hints for Clarity and Maintainability
Explicitly type hint Plotly Figure objects and related data structures (pd.DataFrame) in function signatures. This improves code readability and enables static analysis.
❌ BAD:
import plotly.express as px
def create_plot(data):
fig = px.line(data, x='date', y='value')
return fig
✅ GOOD:
import plotly.express as px
import plotly.graph_objects as go
import pandas as pd
def create_time_series_plot(data: pd.DataFrame) -> go.Figure:
"""Generates a time series line plot."""
fig = px.line(data, x='date', y='value', title='Time Series Analysis')
return fig
Virtual Environments
10. Always Use Virtual Environments
Isolate your project dependencies. This is a fundamental Python best practice, critical for reproducibility, especially in AI/ML where library versions can significantly impact results.
✅ GOOD:
python -m venv .venv
source .venv/bin/activate
.venv\Scripts\activate
pip install plotly pandas kaleido
Packaging
11. Package Visualization Code
When sharing or deploying your visualization logic, package it properly (e.g., using pyproject.toml and setuptools). This ensures consistent installation and dependency management.
✅ GOOD:
[project]
name = "my_ml_viz"
version = "0.1.0"
dependencies = [
"plotly>=6.5.0",
"pandas",
"kaleido"
]
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"
Testing Approaches
12. Implement Testing for Visualization Logic
Test functions that generate figures, focusing on data input validation and layout configuration. For complex visual changes, consider visual regression testing.
✅ GOOD:
import pytest
import pandas as pd
import plotly.graph_objects as go
def create_time_series_plot(data: pd.DataFrame) -> go.Figure:
"""Generates a time series line plot (for testing purposes)."""
fig = px.line(data, x='date', y='value', title='Time Series Analysis')
return fig
def test_create_time_series_plot():
test_data = pd.DataFrame({
'date': pd.to_datetime(['2023-01-01', '2023-01-02', '2023-01-03']),
'value': [10, 12, 8]
})
fig = create_time_series_plot(test_data)
assert isinstance(fig, go.Figure)
assert len(fig.data) == 1
assert fig.data[0].type == 'scatter'
assert fig.layout.title.text == 'Time Series Analysis'
assert fig.data[0].x.tolist() == test_data['date'].tolist()
assert fig.data[0].y.tolist() == test_data['value'].tolist()