Skip to content

Commit 2f0a01b

Browse files
committed
Update README.md
1 parent acf44e2 commit 2f0a01b

1 file changed

Lines changed: 58 additions & 265 deletions

File tree

README.md

Lines changed: 58 additions & 265 deletions
Original file line numberDiff line numberDiff line change
@@ -1,298 +1,91 @@
1-
# xarray-plotly-accessor
2-
Convenience plotting accessor for xarray
1+
# xarray-plotly
32

4-
## Background: A PR to xarray which got rejected
3+
**Interactive Plotly Express plotting accessor for xarray**
54

6-
### Is your feature request related to a problem?
5+
[![PyPI version](https://badge.fury.io/py/xarray-plotly.svg)](https://badge.fury.io/py/xarray-plotly)
6+
[![Python](https://img.shields.io/pypi/pyversions/xarray-plotly.svg)](https://pypi.org/project/xarray-plotly/)
77

8-
The current `.plot` accessor in xarray is built on matplotlib, which has several limitations for modern data exploration workflows:
8+
xarray-plotly provides a `pxplot` accessor for xarray DataArray objects that enables interactive plotting using Plotly Express with automatic dimension-to-slot assignment.
99

10-
1. **Static outputs**: Matplotlib plots are non-interactive. Users cannot zoom, pan, hover for values, or toggle traces without significant additional code.
10+
## Installation
1111

12-
2. **Post-creation modification is cumbersome**: Customizing matplotlib plots after creation requires understanding the complex `Axes`/`Figure` object hierarchy. Common tasks like adjusting labels, colors, or adding annotations often require many lines of boilerplate.
13-
14-
3. **Multi-dimensional data visualization**: When datasets have 3+ dimensions, users must manually slice/aggregate before plotting. There's no built-in support for faceting, animation, or interactive dimension exploration.
15-
16-
4. **Maintenance burden**: The matplotlib plotting code in xarray is substantial and complex. A Plotly-based alternative could reduce this burden since Plotly's express API handles much of the layout logic internally.
17-
18-
5. **Missing modern visualization patterns**: Interactive heatmaps with hover info, animated time series, linked brushing between facets - these are increasingly expected in data science workflows but require significant custom code with matplotlib.
19-
20-
### Describe the solution you'd like
21-
22-
A new **`pxplot`** accessor (Plotly Express plot) that provides:
23-
24-
### Core API Design
25-
26-
```python
27-
import xarray as xr
28-
29-
ds = xr.Dataset(...)
30-
da = xr.DataArray(...)
31-
32-
# Basic usage - automatic dimension assignment
33-
ds.pxplot.line()
34-
ds.pxplot.bar()
35-
ds.pxplot.area()
36-
da.pxplot.heatmap()
37-
38-
# Explicit dimension-to-slot mapping
39-
ds.pxplot.line(x='time', color='scenario', facet_col='region')
40-
41-
# Returns plotly.graph_objects.Figure for easy customization
42-
fig = ds.pxplot.bar(color='variable')
43-
fig.update_layout(title='My Custom Title')
44-
fig.show()
45-
```
46-
47-
### Automatic Dimension → Slot Assignment
48-
49-
Dimensions are assigned to plot "slots" **by their order** in the Dataset/DataArray:
50-
51-
| Slot | Purpose |
52-
|------|---------|
53-
| `x` | X-axis |
54-
| `color` | Trace grouping/stacking |
55-
| `facet_col` | Subplot columns |
56-
| `facet_row` | Subplot rows |
57-
| `animation_frame` | Animation slider |
58-
59-
**Default slot order** (per plot type):
60-
```python
61-
SLOT_ORDER = ('x', 'color', 'facet_col', 'facet_row', 'animation_frame')
62-
```
63-
64-
**Assignment is purely positional** - no name-based heuristics:
65-
```python
66-
# Dataset with dims: ('time', 'scenario', 'region')
67-
# Auto-assigns: time→x, scenario→color, region→facet_col
68-
ds.pxplot.line()
69-
70-
# Dataset with dims: ('region', 'time', 'scenario')
71-
# Auto-assigns: region→x, time→color, scenario→facet_col
72-
ds.pxplot.line()
73-
```
74-
75-
**Override modes for each slot:**
76-
- `'dim_name'`: Explicitly use that dimension for this slot
77-
- `None`: Skip this slot (don't assign any dimension to it)
78-
79-
```python
80-
# Explicit assignment
81-
ds.pxplot.line(x='time', color='scenario')
82-
83-
# Skip color slot: time→x, scenario→facet_col (color unused)
84-
ds.pxplot.bar(x='time', color=None)
12+
```bash
13+
pip install xarray-plotly
8514
```
8615

87-
**Error on unassigned dimensions:**
88-
If there are more dimensions than available slots, an error is raised. Users must reduce dimensionality first:
89-
```python
90-
# 6 dims but only 5 slots → Error
91-
ds.pxplot.line() # raises ValueError: 1 unassigned dimension(s): ['extra_dim'].
92-
# Use .sel(), .isel(), or .mean() to reduce.
93-
94-
# Fix by reducing dimensions before plotting
95-
ds.sel(extra_dim='value').pxplot.line()
96-
ds.mean('extra_dim').pxplot.line()
97-
```
98-
99-
### Handling Multi-Variable Datasets
100-
101-
For Datasets with multiple data variables, treat `'variable'` as a pseudo-dimension:
102-
103-
```python
104-
ds = xr.Dataset({
105-
'temperature': (['time', 'station'], temp_data),
106-
'humidity': (['time', 'station'], humid_data),
107-
})
16+
Or with uv:
10817

109-
# 'variable' can be assigned to color to compare temperature vs humidity
110-
ds.pxplot.line(x='time', color='variable', facet_col='station')
18+
```bash
19+
uv add xarray-plotly
11120
```
11221

113-
### Proposed Methods
114-
115-
```python
116-
# Dataset accessor
117-
@xr.register_dataset_accessor('pxplot')
118-
class DatasetPxplotAccessor:
119-
def line(self, *, x='auto', color='auto', facet_col='auto',
120-
facet_row='auto', animation_frame='auto', **px_kwargs) -> go.Figure
121-
122-
def bar(self, *, x='auto', color='auto', ...) -> go.Figure
123-
124-
def area(self, *, x='auto', color='auto', ...) -> go.Figure # stacked area
125-
126-
def scatter(self, *, x='auto', y='auto', color='auto', ...) -> go.Figure
127-
128-
def heatmap(self, *, x='auto', y='auto', facet_col='auto', ...) -> go.Figure
129-
130-
131-
# DataArray accessor
132-
@xr.register_dataarray_accessor('pxplot')
133-
class DataArrayPxplotAccessor:
134-
def line(self, ...) -> go.Figure
135-
def heatmap(self, ...) -> go.Figure
136-
# etc.
137-
```
138-
139-
### Slot Order per Plot Type
140-
141-
Different plot types define their own default slot order:
142-
```python
143-
# line/bar/area: x is primary
144-
LINE_SLOT_ORDER = ('x', 'color', 'facet_col', 'facet_row', 'animation_frame')
145-
146-
# heatmap: needs x and y for the grid
147-
HEATMAP_SLOT_ORDER = ('x', 'y', 'facet_col', 'facet_row', 'animation_frame')
148-
149-
# scatter: x and y are primary
150-
SCATTER_SLOT_ORDER = ('x', 'y', 'color', 'facet_col', 'facet_row', 'animation_frame')
151-
```
152-
153-
Global configuration (e.g., custom slot orders, default colorscales) could be added later if needed.
154-
155-
### Example Usage
22+
## Quick Start
15623

15724
```python
15825
import xarray as xr
15926
import numpy as np
160-
161-
# Create sample dataset - dims order: (time, city, scenario)
162-
ds = xr.Dataset({
163-
'temperature': (['time', 'city', 'scenario'], np.random.randn(100, 3, 2)),
164-
'precipitation': (['time', 'city', 'scenario'], np.random.randn(100, 3, 2)),
165-
}, coords={
166-
'time': pd.date_range('2020', periods=100),
167-
'city': ['NYC', 'LA', 'Chicago'],
168-
'scenario': ['baseline', 'warming'],
169-
})
170-
171-
# Dims: (time, city, scenario) + 'variable' (2 data vars)
172-
# Slot order: (x, color, facet_col, facet_row)
173-
# Result: time→x, city→color, scenario→facet_col, variable→facet_row
174-
fig = ds.pxplot.line()
175-
176-
# Interactive: zoom, pan, hover for values, toggle traces
177-
fig.show()
178-
179-
# Easy customization after creation
180-
fig.update_layout(
181-
title='Climate Projections',
182-
xaxis_title='Date',
183-
template='plotly_dark',
27+
import xarray_plotly # registers the accessor
28+
29+
# Create sample data
30+
da = xr.DataArray(
31+
np.random.randn(100, 3, 2).cumsum(axis=0),
32+
dims=["time", "city", "scenario"],
33+
coords={
34+
"time": np.arange(100),
35+
"city": ["NYC", "LA", "Chicago"],
36+
"scenario": ["baseline", "warming"],
37+
},
38+
name="temperature",
18439
)
18540

186-
# Override: put variable on color instead
187-
# time→x, variable→color, city→facet_col, scenario→facet_row
188-
fig = ds.pxplot.line(color='variable')
41+
# Create an interactive line plot
42+
# Dimensions auto-assign: time→x, city→color, scenario→facet_col
43+
fig = da.pxplot.line()
44+
fig.show()
18945

190-
# Reduce dims first if you have too many
191-
ds.sel(scenario='baseline').pxplot.line() # 3 dims → fits in 3 slots
46+
# Easy customization
47+
fig.update_layout(title="Temperature Projections", template="plotly_dark")
19248
```
19349

194-
### Describe alternatives you've considered
195-
196-
### 1. External package (status quo)
197-
Users can use `hvplot` (HoloViews-based) which provides a similar accessor. However:
198-
- hvplot adds significant dependencies (HoloViews, Bokeh, Panel)
199-
- Returns HoloViews objects, not Plotly figures
200-
- Different ecosystem with its own learning curve
201-
202-
### 2. Improve matplotlib plotting
203-
Could add faceting/animation to current `.plot`, but:
204-
- Matplotlib's static nature is fundamental
205-
- Would require major refactoring of existing code
206-
- Doesn't address the post-creation modification pain point
207-
208-
### 3. Keep as third-party package
209-
A `pxplot` package could exist independently. However:
210-
- Discoverability suffers (users don't know it exists)
211-
- Integration with xarray options/config is harder
212-
- Fragmentation of the ecosystem
213-
214-
### Additional context
50+
## Features
21551

216-
### Why Plotly Express specifically?
52+
- **Interactive plots**: Zoom, pan, hover for values, toggle traces
53+
- **Automatic dimension assignment**: Dimensions fill plot slots by position
54+
- **Easy customization**: Returns Plotly `Figure` objects
55+
- **Multiple plot types**: `line()`, `bar()`, `area()`, `scatter()`, `box()`, `imshow()`
56+
- **Faceting and animation**: Built-in support for subplot grids and animations
21757

218-
1. **Minimal API surface**: `px.line()`, `px.bar()`, etc. handle most layout concerns internally
219-
2. **Returns modifiable objects**: `go.Figure` has a clean API for updates
220-
3. **Wide adoption**: Plotly is well-known in the data science community
221-
4. **Good defaults**: Sensible hover info, legends, and interactivity out of the box
222-
5. **Export flexibility**: HTML, PNG, PDF, or embed in Dash/Jupyter
58+
## Dimension Assignment
22359

224-
### Implementation Notes
225-
226-
The dimension→slot assignment algorithm is simple and predictable:
60+
Dimensions are automatically assigned to plot "slots" based on their order:
22761

22862
```python
229-
SLOT_ORDER = ('x', 'color', 'facet_col', 'facet_row', 'animation_frame')
230-
231-
def assign_slots(ds, *, x=auto, color=auto, facet_col=auto, ...):
232-
"""
233-
Positional assignment: dimensions fill slots in order.
234-
- Explicit assignments lock a dimension to a slot
235-
- None skips a slot
236-
- Remaining dims fill remaining slots by position
237-
- Error if dims left over after all slots filled
238-
"""
239-
dims = list(ds.dims)
240-
if len(ds.data_vars) > 1:
241-
dims.append('variable') # pseudo-dimension
242-
243-
slots = {}
244-
used = set()
245-
slot_queue = list(SLOT_ORDER)
246-
247-
# Pass 1: Process explicit assignments
248-
for slot, value in [('x', x), ('color', color), ...]:
249-
if value is None:
250-
slot_queue.remove(slot) # skip this slot
251-
elif value is not auto:
252-
slots[slot] = value
253-
used.add(value)
254-
slot_queue.remove(slot)
63+
# dims: (time, city, scenario)
64+
# auto-assigns: time→x, city→color, scenario→facet_col
65+
da.pxplot.line()
25566

256-
# Pass 2: Fill remaining slots with remaining dims (by position)
257-
remaining_dims = [d for d in dims if d not in used]
258-
for slot, dim in zip(slot_queue, remaining_dims):
259-
slots[slot] = dim
260-
used.add(dim)
67+
# Override with explicit assignments
68+
da.pxplot.line(x="time", color="scenario", facet_col="city")
26169

262-
# Check for unassigned dimensions
263-
unassigned = [d for d in dims if d not in used]
264-
if unassigned:
265-
raise ValueError(
266-
f"Unassigned dimension(s): {unassigned}. "
267-
"Reduce with .sel(), .isel(), or .mean() before plotting."
268-
)
269-
270-
return slots
70+
# Skip a slot with None
71+
da.pxplot.line(color=None) # time→x, city→facet_col
27172
```
27273

273-
### Proof of Concept
274-
275-
I have implemented this pattern in a domain-specific package ([flixopt](https://github.com/flixOpt/flixopt)) as `.fxplot` and it has proven valuable for exploring multi-dimensional optimization results. The patterns are generic and would benefit the broader xarray community.
74+
## Available Methods
27675

277-
### Dependency Considerations
76+
| Method | Description | Slot Order |
77+
|--------|-------------|------------|
78+
| `line()` | Line plot | x → color → line_dash → symbol → facet_col → facet_row → animation_frame |
79+
| `bar()` | Bar chart | x → color → pattern_shape → facet_col → facet_row → animation_frame |
80+
| `area()` | Stacked area | x → color → pattern_shape → facet_col → facet_row → animation_frame |
81+
| `scatter()` | Scatter plot | x → color → size → symbol → facet_col → facet_row → animation_frame |
82+
| `box()` | Box plot | x → color → facet_col → facet_row → animation_frame |
83+
| `imshow()` | Heatmap | y → x → facet_col → animation_frame |
27884

279-
This would add `plotly` as an optional dependency:
280-
```python
281-
try:
282-
import plotly.express as px
283-
import plotly.graph_objects as go
284-
except ImportError:
285-
raise ImportError("pxplot requires plotly. Install with: pip install plotly")
286-
```
85+
## Documentation
28786

288-
## Summary
87+
Full documentation with examples: [https://felix.github.io/xarray-plotly](https://felix.github.io/xarray-plotly)
28988

290-
A `pxplot` accessor would provide:
291-
- Interactive plots with zero additional code
292-
- Simple, predictable positional assignment of dimensions to visual slots
293-
- Explicit overrides and `None` to skip slots
294-
- Easy post-creation customization via Plotly's `go.Figure` API
295-
- Reduced maintenance burden compared to matplotlib's complexity
296-
- Modern visualization patterns (faceting, animation) built-in
89+
## License
29790

298-
This aligns with xarray's philosophy of making multi-dimensional data easy to work with, extending that ease to visualization.
91+
MIT

0 commit comments

Comments
 (0)