Skip to content

Conversation

@FBumann
Copy link
Member

@FBumann FBumann commented Jan 3, 2026

Summary

New .fxplot accessor for convenient plotting of any xr.Dataset with automatic dimension handling, faceting, and animation support.

✨ Added

New DatasetPlotAccessor (ds.fxplot.*)

  • bar(), stacked_bar(), line(), area() - Standard chart types
  • heatmap() - 2D visualization with animation support
  • scatter() - Plot two variables against each other
  • pie() - Pie charts from aggregated data
  • duration_curve() - Sorted values with optional normalization

Smart Dimension Handling

  • Auto x-axis selection via CONFIG.Plotting.x_dim_priority
  • Auto faceting via CONFIG.Plotting.extra_dim_priority
  • xlabel/ylabel parameters for all chart types

New Config Options

  • CONFIG.Plotting.x_dim_priority - Priority list for x-axis dimension
  • CONFIG.Plotting.default_line_shape - Default line shape ('hv')

♻️ Changed

  • Internal plot accessors now leverage shared .fxplot implementation (reduced code duplication)

🐛 Fixed

  • heatmap() kwarg merge order - user kwargs now properly override defaults
  • to_duration_curve() sorting for multi-dimensional data
  • Docs build: excluded notebooks/data/*.py from mkdocs-jupyter execution
  • Missing return type annotation on ClusterResult.plot()

📚 Documentation

  • New fxplot_accessor_demo.ipynb notebook
  • Updated 09-plotting-and-data-access.ipynb

FBumann added 16 commits January 1, 2026 21:30
…ation, reducing code duplication while maintaining the same functionality (data preparation, color resolution from components, PlotResult wrapping).
… area(), and duration_curve() methods in both DatasetPlotAccessor and DataArrayPlotAccessor

  2. scatter() method - Plots two variables against each other with x and y parameters
  3. pie() method - Creates pie charts from aggregated (scalar) dataset values, e.g. ds.sum('time').fxplot.pie()
  4. duration_curve() method - Sorts values along the time dimension in descending order, with optional normalize parameter for percentage x-axis
  5. CONFIG.Plotting.default_line_shape - New config option (default 'hv') that controls the default line shape for line(), area(), and duration_curve() methods
  1. X-axis is now determined first using CONFIG.Plotting.x_dim_priority
  2. Facets are resolved from remaining dimensions (x-axis excluded)

  x_dim_priority expanded:
  x_dim_priority = ('time', 'duration', 'duration_pct', 'period', 'scenario', 'cluster')
  - Time-like dims first, then common grouping dims as fallback
  - variable stays excluded (it's used for color, not x-axis)

  _get_x_dim() refactored:
  - Now takes dims: list[str] instead of a DataFrame
  - More versatile - works with any list of dimension names
  - Add `x` parameter to bar/stacked_bar/line/area for explicit x-axis control
  - Add CONFIG.Plotting.x_dim_priority for auto x-axis selection order
  - X-axis determined first, facets from remaining dimensions
  - Refactor _get_x_column -> _get_x_dim (takes dim list, not DataFrame)
  - Support scalar data (no dims) by using 'variable' as x-axis
  - Add `x` parameter to bar/stacked_bar/line/area for explicit x-axis control
  - Add CONFIG.Plotting.x_dim_priority for auto x-axis selection
    Default: ('time', 'duration', 'duration_pct', 'period', 'scenario', 'cluster')
  - X-axis determined first, facets resolved from remaining dimensions
  - Refactor _get_x_column -> _get_x_dim (takes dim list, more versatile)
  - Support scalar data (no dims) by using 'variable' as x-axis
  - Skip color='variable' when x='variable' to avoid double encoding
  - Fix _dataset_to_long_df to use dims (not just coords) as id_vars
  - Add `x` parameter to bar/stacked_bar/line/area for explicit x-axis control
  - Add CONFIG.Plotting.x_dim_priority for auto x-axis selection
    Default: ('time', 'duration', 'duration_pct', 'period', 'scenario', 'cluster')
  - X-axis determined first, facets resolved from remaining dimensions
  - Refactor _get_x_column -> _get_x_dim (takes dim list, more versatile)
  - Support scalar data (no dims) by using 'variable' as x-axis
  - Skip color='variable' when x='variable' to avoid double encoding
  - Fix _dataset_to_long_df to use dims (not just coords) as id_vars
  - Ensure px_kwargs properly overrides all defaults (color, facets, etc.)
…wargs} so user can override

  2. scatter unused colors - Removed the unused parameter
  3. to_duration_curve sorting - Changed [::-1] to np.flip(..., axis=time_axis) for correct multi-dimensional handling
  4. DataArrayPlotAccessor.heatmap - Same kwarg merge fix
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 3, 2026

📝 Walkthrough

Walkthrough

This pull request introduces a comprehensive XArray accessor-based plotting API (fxplot) for interactive Plotly visualization and a stats accessor for data transformations. Changes include new configuration parameters for plot defaults, a Jupyter demonstration notebook, updated documentation, and refactoring of existing plotting code to delegate to the new accessor interface.

Changes

Cohort / File(s) Summary
Core Accessor Implementation
flixopt/dataset_plot_accessor.py
New 500+ line module introducing three XArray accessors: DatasetFxPlot (with bar, stacked_bar, line, area, heatmap, scatter, pie methods), DatasetFxStats (with to_duration_curve transformation), and DataArrayFxPlot (delegating to Dataset methods). Includes internal helpers for dimension auto-resolution (_get_x_dim), facet/animation inference (_resolve_auto_facets), and Dataset-to-DataFrame conversion (_dataset_to_long_df). Integrates color processing and Plotly layout customization.
Configuration & Registration
flixopt/config.py,
flixopt/__init__.py
Added default_line_shape ('hv') and x_dim_priority tuple to CONFIG.Plotting with public dictionary serialization support. Added side-effect import in __init__.py to register dataset_plot_accessor during package initialization.
Documentation & Examples
docs/notebooks/fxplot_accessor_demo.ipynb,
docs/user-guide/recipes/plotting-custom-data.md,
mkdocs.yml
New notebook demonstrating fxplot accessor with multiple plot types (line, stacked_bar, heatmap, pie, etc.), faceting, animation, color customization, and DataArray usage. Plotting guide refactored to replace long-form Plotly Express conversion pattern with direct ds.fxplot API calls. Navigation updated to reference new notebook.
Refactoring to New API
flixopt/statistics_accessor.py
Removed internal plotting helpers (_heatmap_figure, _create_stacked_bar, _create_line). Updated plotting call sites to delegate to new fxplot accessors instead, preserving facet/animation handling and color logic.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Accessor as fxplot Accessor
    participant Helpers as Internal Helpers
    participant PX as Plotly Express
    participant Figure as go.Figure

    User->>Accessor: ds.fxplot.line(x='auto', colors=...)
    Accessor->>Helpers: _get_x_dim(dims, 'auto')
    Helpers-->>Accessor: x_dim (from priority list)
    
    rect rgba(200, 220, 255, 0.3)
    note over Accessor,Helpers: Dimension Resolution
    Accessor->>Helpers: _resolve_auto_facets(facet_col='auto', ...)
    Helpers-->>Accessor: facet_col, facet_row, animation_frame
    end

    Accessor->>Helpers: _dataset_to_long_df(ds, value_name, var_name)
    Helpers-->>Accessor: long_form_df
    
    rect rgba(200, 240, 200, 0.3)
    note over Accessor: Color & Layout Processing
    Accessor->>Accessor: process_colors(colors, ...)
    Accessor->>Accessor: apply CONFIG defaults
    end
    
    Accessor->>PX: px.line(data=df, x=x_dim, ..., facet_col, animation_frame)
    PX-->>Figure: Figure (with facets/animation)
    Figure-->>User: Figure

Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • #508: Modifies plotting infrastructure (statistics_accessor helpers and color handling) to centralize plotting via fxplot/ColorType; shares overlapping refactoring scope and color integration patterns.

Poem

🐰 A whisker-twitch of joy—the plotter's dream takes flight!
XArray data dances with Plotly's gentle might,
Dimensions auto-resolved, colors bloom just right,
From fxplot accessor blooms, a plotting dawn so bright! ✨
hop hop 📊

Pre-merge checks and finishing touches

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Description check ❓ Inconclusive PR description provided by author contains additional details (✨ Added, ♻️ Changed, 🐛 Fixed, 📚 Documentation sections) beyond the template structure but lacks specific related issue number and testing verification. Clarify which issues are closed by this PR (add issue number to 'Related Issues' section), and explicitly verify if manual testing was performed by checking the Testing checkboxes.
✅ Passed checks (2 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 96.55% which is sufficient. The required threshold is 80.00%.
Title check ✅ Passed The title 'Feature: fxplot Plotting Accessor' clearly summarizes the main change—the introduction of a new plotting accessor (fxplot) for xarray objects.
✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@FBumann
Copy link
Member Author

FBumann commented Jan 3, 2026

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 3, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
docs/notebooks/fxplot_accessor_demo.ipynb (1)

29-37: Document or configure the Plotly renderer for offline environments.

The 'notebook_connected' renderer hardcoded in this cell requires internet access for CDN-based Plotly.js loading. While the docs build succeeds in CI because it sets PLOTLY_RENDERER=json as an environment variable (which overrides pio.renderers.default), this creates a fragile, implicit dependency. Local documentation builds or other CI contexts without this environment variable would attempt CDN access.

Consider either:

  • Configuring pio.renderers.default based on the execution environment (e.g., using PLOTLY_RENDERER or another environment variable)
  • Using 'notebook' (self-contained) instead for better portability
  • Documenting that PLOTLY_RENDERER=json must be set for offline builds
flixopt/dataset_plot_accessor.py (1)

30-68: Consider consolidating _resolve_auto_facets to reduce duplication.

This function is nearly identical to the one in statistics_accessor.py (lines 183-234), with the addition of exclude_dims. Consider:

  1. Moving the more complete version (this one with exclude_dims) to a shared module
  2. Having statistics_accessor.py import from here or the shared module

This would reduce maintenance burden and ensure consistent behavior.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 282594c and 22702e0.

📒 Files selected for processing (7)
  • docs/notebooks/fxplot_accessor_demo.ipynb
  • docs/user-guide/recipes/plotting-custom-data.md
  • flixopt/__init__.py
  • flixopt/config.py
  • flixopt/dataset_plot_accessor.py
  • flixopt/statistics_accessor.py
  • mkdocs.yml
🧰 Additional context used
🧬 Code graph analysis (2)
flixopt/statistics_accessor.py (2)
flixopt/dataset_plot_accessor.py (6)
  • stacked_bar (192-268)
  • stacked_bar (754-780)
  • heatmap (423-486)
  • heatmap (842-891)
  • line (270-346)
  • line (782-810)
flixopt/clustering/base.py (1)
  • heatmap (660-794)
flixopt/dataset_plot_accessor.py (3)
flixopt/color_processing.py (1)
  • process_colors (165-233)
flixopt/config.py (2)
  • CONFIG (185-916)
  • Plotting (551-595)
flixopt/statistics_accessor.py (6)
  • _resolve_auto_facets (183-234)
  • sizes (464-468)
  • sizes (1035-1078)
  • sizes (1766-1830)
  • _dataset_to_long_df (250-260)
  • heatmap (1539-1669)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: test (3.11)
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.14)
  • GitHub Check: test (3.13)
  • GitHub Check: Build documentation
🔇 Additional comments (31)
mkdocs.yml (1)

76-76: LGTM!

The navigation entry for the new fxplot accessor demo notebook is correctly placed and follows the existing navigation structure.

flixopt/__init__.py (1)

17-19: LGTM!

The side-effect import correctly registers the xarray accessors (.fxplot and .fxstats) at package initialization time. The comment clearly explains the purpose, and the noqa directive appropriately suppresses the unused import warning.

flixopt/config.py (4)

166-166: LGTM!

The default line shape of 'hv' (horizontal-then-vertical step) is appropriate for time-series energy data where values typically represent constant rates or states over each time period.


169-169: LGTM!

The x_dim_priority default provides a sensible hierarchy for automatic x-axis selection, prioritizing temporal dimensions (time, duration) followed by organizational dimensions (period, scenario, cluster).


592-595: LGTM!

The new plotting configuration attributes are correctly defined with appropriate type hints and default values from the _DEFAULTS mapping.


696-699: LGTM!

Both new plotting configuration parameters are correctly included in the to_dict() serialization method, maintaining consistency with the class attributes.

docs/notebooks/fxplot_accessor_demo.ipynb (3)

1-27: LGTM!

Clear introduction and setup that imports necessary dependencies and displays the flixopt version for reproducibility.


39-68: LGTM!

Clear section introducing sample data creation with a simple time-series dataset. The use of np.random.seed(42) ensures reproducibility.


70-541: LGTM!

Excellent comprehensive demonstration of the fxplot accessor functionality. The examples progress logically from basic plots to advanced features including:

  • All major plot types (line, bar, stacked_bar, area, heatmap, scatter, pie, duration_curve)
  • Automatic faceting and animation with clear explanations
  • Customization options (colors, axis labels, facet control)
  • Integration with xarray operations (filtering, selection)
  • DataArray accessor usage
  • Chaining with Plotly methods

The narrative structure and comments make it easy for users to understand and adapt the examples.

docs/user-guide/recipes/plotting-custom-data.md (3)

3-3: LGTM!

The introduction correctly describes the purpose of the .fxplot accessor and its relationship to the optimization results plot accessor.


5-19: LGTM!

The quick example is concise and clearly demonstrates the key advantage of the fxplot accessor - direct plotting without DataFrame conversion. The progression from creating a dataset to plotting it is intuitive.


21-29: Notebook link is correct and accessible.

The relative path ../../notebooks/fxplot_accessor_demo.ipynb resolves correctly to the existing notebook file docs/notebooks/fxplot_accessor_demo.ipynb.

flixopt/statistics_accessor.py (6)

1399-1406: LGTM - Clean delegation to fxplot API.

The balance method correctly delegates to ds.fxplot.stacked_bar() with pre-resolved facet dimensions. The title, colors, and facet parameters are properly passed through.


1523-1530: LGTM - Consistent pattern for carrier_balance.

Same clean delegation pattern as balance(). Pre-resolved facets and colors are correctly forwarded.


1656-1661: LGTM - Heatmap delegation via DataArray accessor.

Correctly uses da.fxplot.heatmap() on the DataArray with pre-resolved facet and animation dimensions.


1750-1757: LGTM - flows() method delegation.

Correctly delegates to ds.fxplot.line() for flow visualization.


1926-1933: LGTM - duration_curve() delegation.

Correctly uses result_ds.fxplot.line() for the duration curve visualization.


2145-2152: LGTM - charge_states() delegation.

Correctly delegates to ds.fxplot.line() for charge state visualization.

flixopt/dataset_plot_accessor.py (13)

16-28: LGTM - Clean x-dimension selection helper.

The _get_x_dim function correctly implements priority-based dimension selection with a sensible fallback to 'variable' for scalar data.


71-81: Slight duplication with statistics_accessor.py, but implementation differs appropriately.

This version correctly uses ds.dims for id_vars (line 80), while statistics_accessor.py uses ds.coords.keys() (line 259). The comment explains the reasoning well. If consolidating, this version's approach is more robust for handling dimensions without explicit coordinates.


119-191: LGTM - Grouped bar chart implementation.

The bar method correctly implements dimension resolution, color processing, and faceting. The check on line 173 to avoid coloring by variable when it's on the x-axis is a good edge case handling.


192-268: LGTM - Stacked bar with relative barmode.

The stacked_bar method correctly sets barmode='relative' for proper positive/negative stacking. Layout updates for gap removal are appropriate for continuous stacked bars.


270-346: LGTM - Line chart with configurable shape.

The line method correctly applies CONFIG.Plotting.default_line_shape when no explicit shape is provided.


348-421: LGTM - Area chart implementation.

Consistent with line chart pattern, correctly uses px.area for stacked areas.


423-486: LGTM - Heatmap with auto variable selection.

Good UX: auto-selects the variable if only one exists, provides clear error message otherwise. Using px.imshow directly with xarray DataArray is the correct approach.


488-554: LGTM - Scatter plot between two variables.

Clean implementation requiring explicit x and y variable names. Good validation with informative error messages.


556-636: LGTM - Pie chart with scalar and multi-dimensional support.

Good handling of both scalar (single pie) and multi-dimensional (faceted pies) cases. The scalar case detection via max_ndim == 0 is elegant.


639-696: LGTM - Duration curve transformation accessor.

The to_duration_curve method correctly:

  1. Sorts values descending along the time axis
  2. Handles multi-dimensional arrays properly via axis=time_axis
  3. Renames the time dimension to duration_pct or duration based on normalization

698-752: LGTM - DataArray accessor with delegation pattern.

Clean implementation that converts DataArray to Dataset for plotting. The _to_dataset helper correctly handles unnamed DataArrays by defaulting to 'value'.


754-840: LGTM - Delegating methods for DataArray.

The stacked_bar, line, and area methods correctly forward all parameters to the Dataset accessor.


842-891: LGTM - DataArray-native heatmap implementation.

Good design choice to implement heatmap directly rather than delegating, since px.imshow works optimally with DataArray. The implementation is consistent with DatasetPlotAccessor.heatmap.

Comment on lines 569 to 570
x_dim_priority: Order of dimensions to prefer for x-axis when 'auto'.
Default: ('time', 'duration', 'duration_pct').
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Correct the docstring to match the actual default value.

The docstring states the default is ('time', 'duration', 'duration_pct'), but the actual default at line 169 includes six dimensions: ('time', 'duration', 'duration_pct', 'period', 'scenario', 'cluster').

🔎 Proposed fix
-            x_dim_priority: Order of dimensions to prefer for x-axis when 'auto'.
-                Default: ('time', 'duration', 'duration_pct').
+            x_dim_priority: Order of dimensions to prefer for x-axis when 'auto'.
+                Default: ('time', 'duration', 'duration_pct', 'period', 'scenario', 'cluster').
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
x_dim_priority: Order of dimensions to prefer for x-axis when 'auto'.
Default: ('time', 'duration', 'duration_pct').
x_dim_priority: Order of dimensions to prefer for x-axis when 'auto'.
Default: ('time', 'duration', 'duration_pct', 'period', 'scenario', 'cluster').
🤖 Prompt for AI Agents
In flixopt/config.py around lines 569-570 (actual default tuple defined at line
169), the docstring incorrectly lists x_dim_priority default as ('time',
'duration', 'duration_pct') while the true default is ('time', 'duration',
'duration_pct', 'period', 'scenario', 'cluster'); update the docstring to
exactly match the actual default tuple at line 169 by replacing the shorter
tuple with the full six-element tuple so the docs and code stay consistent.

…ork-v2+plotting

# Conflicts:
#	docs/notebooks/08a-aggregation.ipynb
#	docs/notebooks/08b-rolling-horizon.ipynb
#	docs/notebooks/08c-clustering.ipynb
#	docs/notebooks/08c2-clustering-storage-modes.ipynb
#	docs/notebooks/08d-clustering-multiperiod.ipynb
#	docs/notebooks/08e-clustering-internals.ipynb
@FBumann FBumann changed the title Feature/aggregate rework v2+plotting Feature: fxplot Plotting Accessor Jan 3, 2026
@FBumann FBumann merged commit 93e7152 into feature/aggregate-rework-v2 Jan 3, 2026
10 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Jan 5, 2026
FBumann added a commit that referenced this pull request Jan 5, 2026
Add new dataset plotting accessor for enhanced visualization:
- fxplot accessor for xarray datasets
- Improved plotting defaults and styling
- Animation frame handling improvements

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants