Skip to content

Commit 091c3ca

Browse files
committed
All fixes have been applied and verified. Here's a summary of the changes:
Changes Made 1. CHANGELOG.md (line 145) - Before: #### FXPlot Accessor (#548) - After: #### Plotly Accessor (#548) 2. CHANGELOG.md (lines 169-170) - Before: Two duplicate .plotly.bar() entries (grouped/stacked) - After: Single entry: .plotly.bar() | Bar charts (use barmode='group' or 'relative' for stacked) 3. flixopt/stats_accessor.py - to_duration_curve() (lines 43-66) - Before: Used da.values (breaks dask laziness) and tuple assignment (loses attrs) - After: Uses xr.apply_ufunc() with dask='parallelized' to preserve: - Dask lazy evaluation - DataArray attributes - Dataset attributes - Variables without time dimension (unchanged) 4. docs/user-guide/recipes/plotting-custom-data.md - Added note explaining that .plotly and .fxstats accessors are automatically registered on import
1 parent 8d77516 commit 091c3ca

3 files changed

Lines changed: 30 additions & 13 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ charge_state = fs_expanded.solution['SeasonalPit|charge_state']
142142
Use `'cyclic'` for short-term storage like batteries or hot water tanks where only daily patterns matter.
143143
Use `'independent'` for quick estimates when storage behavior isn't critical.
144144

145-
#### FXPlot Accessor (#548)
145+
#### Plotly Accessor (#548)
146146

147147
New global xarray accessors for universal plotting with automatic faceting and smart dimension handling. Works on any xarray Dataset, not just flixopt results.
148148

@@ -166,8 +166,7 @@ dataset.fxstats.to_duration_curve()
166166

167167
| Method | Description |
168168
|--------|-------------|
169-
| `.plotly.bar()` | Grouped bar charts |
170-
| `.plotly.bar()` | Stacked bar charts |
169+
| `.plotly.bar()` | Bar charts (use `barmode='group'` or `'relative'` for stacked) |
171170
| `.plotly.line()` | Line charts with faceting |
172171
| `.plotly.area()` | Stacked area charts |
173172
| `.plotly.imshow()` | Heatmap visualizations |

docs/user-guide/recipes/plotting-custom-data.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@
22

33
While the plot accessor (`flow_system.statistics.plot`) is designed for optimization results, you often need to plot custom xarray data. The `.plotly` accessor provides the same convenience for any `xr.Dataset` or `xr.DataArray`.
44

5+
!!! note "Accessor Registration"
6+
The `.plotly` and `.fxstats` accessors are automatically registered when you import flixopt.
7+
Just `import flixopt` and they become available on all xarray objects.
8+
59
## Quick Example
610

711
```python
8-
import flixopt as fx
12+
import flixopt as fx # Registers .plotly and .fxstats accessors
913
import xarray as xr
1014

1115
ds = xr.Dataset({

flixopt/stats_accessor.py

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,17 +40,31 @@ def to_duration_curve(self, *, normalize: bool = True) -> xr.Dataset:
4040
if 'time' not in self._ds.dims:
4141
raise ValueError("Duration curve requires a 'time' dimension.")
4242

43-
# Sort each variable along time dimension (descending)
44-
sorted_ds = self._ds.copy()
45-
for var in sorted_ds.data_vars:
46-
da = sorted_ds[var]
43+
def _sort_descending(data: np.ndarray, axis: int) -> np.ndarray:
44+
"""Sort array in descending order along axis."""
45+
return np.flip(np.sort(data, axis=axis), axis=axis)
46+
47+
# Sort each variable along time dimension (descending) using apply_ufunc
48+
# This preserves dask laziness and DataArray attributes
49+
sorted_vars = {}
50+
for var in self._ds.data_vars:
51+
da = self._ds[var]
4752
if 'time' not in da.dims:
48-
# Skip variables without time dimension (e.g., scalar metadata)
53+
# Keep variables without time dimension unchanged
54+
sorted_vars[var] = da
4955
continue
50-
time_axis = da.dims.index('time')
51-
# Sort along time axis (descending) - use flip for correct axis
52-
sorted_values = np.flip(np.sort(da.values, axis=time_axis), axis=time_axis)
53-
sorted_ds[var] = (da.dims, sorted_values)
56+
sorted_da = xr.apply_ufunc(
57+
_sort_descending,
58+
da,
59+
input_core_dims=[['time']],
60+
output_core_dims=[['time']],
61+
kwargs={'axis': -1}, # Core dim is moved to last position
62+
dask='parallelized',
63+
output_dtypes=[da.dtype],
64+
)
65+
sorted_vars[var] = sorted_da
66+
67+
sorted_ds = xr.Dataset(sorted_vars, attrs=self._ds.attrs)
5468

5569
# Replace time coordinate with duration
5670
n_timesteps = sorted_ds.sizes['time']

0 commit comments

Comments
 (0)