Skip to content

Conversation

@FBumann
Copy link
Member

@FBumann FBumann commented Dec 7, 2025

Description

Brief description of the changes in this PR.

Type of Change

  • Bug fix
  • New feature
  • Documentation update
  • Code refactoring

Related Issues

Closes #(issue number)

Testing

  • I have tested my changes
  • Existing tests still pass

Checklist

  • My code follows the project style
  • I have updated documentation if needed
  • I have added tests for new functionality (if applicable)

Summary by CodeRabbit

  • New Features

    • Added flexible color input support across all visualization functions, now accepting named colorscales, color lists, and label-to-color mappings.
  • Improvements

    • Standardized visualization library usage with enhanced line styling for data representation.
    • Improved network visualization functionality with better control options.
  • Chores

    • Refactored internal color and plotting handling for consistency across modules.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 7, 2025

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

The pull request refactors the plotting subsystem by consolidating color handling into a centralized ColorType alias in color_processing.py, updating plotting modules to import and use it consistently, replacing custom plotting implementations with Plotly Express in clustering, and extracting shared helper functions for heatmap reshaping and network visualization across multiple accessor modules.

Changes

Cohort / File(s) Summary
Color handling centralization
flixopt/color_processing.py, flixopt/plotting.py
Introduced ColorType type alias in color_processing.py; updated plotting.py to import ColorType and process_colors from color_processing instead of defining locally.
Clustering plotting updates
flixopt/clustering.py
Replaced custom plotting wrapper with Plotly Express (px.line); converted dataframes to long-form for plotting; added explicit write_html/save and show behavior; reorganized imports with pathlib under TYPE_CHECKING.
Statistics accessor refactoring
flixopt/statistics_accessor.py
Added _reshape_time_for_heatmap and _heatmap_figure helper functions; updated all plotting function signatures to accept colors: ColorType instead of dict[str, str] | None; centralized color processing via new helpers across balance, heatmap, flows, sankey, sizes, duration_curve, and effects methods.
Topology accessor refactoring
flixopt/topology_accessor.py
Extracted new _plot_network helper function encapsulating PyVis network construction, styling, layout, and HTML output; updated TopologyAccessor.plot to use the new helper; adjusted pathlib imports.
Test updates
tests/test_solution_and_plotting.py
Reworked test_heatmap_with_plotly_v2 to require pre-reshaped input via plotting.reshape_data_for_heatmap() call; removed reshape_time argument from heatmap function call.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Key areas requiring attention:
    • ColorType alias definition and re-export chain across color_processing → plotting → statistics_accessor to verify consistency
    • Signature updates in statistics_accessor.py across multiple plotting functions (balance, heatmap, flows, sankey, sizes, duration_curve, effects) to ensure color parameter handling is correct
    • Helper function behavior in statistics_accessor (_reshape_time_for_heatmap, _heatmap_figure) and topology_accessor (_plot_network) to confirm they correctly encapsulate prior logic
    • Test modifications in test_solution_and_plotting.py to verify the new pre-reshaped input expectation aligns with updated API

Possibly related PRs

Suggested labels

v3.0.0

Poem

🐰 Hues are organized, paths grow bright,
Colors consolidated, plotting just right!
Helpers extracted, clean and lean,
The finest refactored code you've seen!
Type-safe and tidy, hop with cheer! ✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is incomplete; it contains only template placeholders without actual details about the changes, testing status, or specific issue references. Replace template placeholders with concrete information: describe the refactoring, specify the type of change (Code refactoring), reference the related issue, and check the testing and checklist boxes with accurate status.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main objective: inlining plotting methods to deprecate plotting.py, which aligns with the substantial refactoring shown in the changeset across multiple files.
Docstring Coverage ✅ Passed Docstring coverage is 88.89% which is sufficient. The required threshold is 80.00%.

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 Dec 7, 2025

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 7, 2025

✅ 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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
flixopt/statistics_accessor.py (1)

820-851: Narrow colors type for heatmap to exclude dicts, or add explicit validation

The heatmap() implementation looks solid for managing dimension reshaping. However, there's a type safety issue with the colors parameter:

colors is typed as ColorType (which allows dict[str, str]), but it's passed directly to px.imshow via color_continuous_scale, which only accepts a colorscale name (str) or a list of colors (list[str]). Dicts are not valid for continuous colorscales—they only work with categorical/discrete color parameters like color_discrete_map (used in other plot methods like flows() and line plots).

Passing a dict will fail at runtime. Either narrow the type signature to str | list[str] | None in both heatmap() and _heatmap_figure(), or add an explicit runtime check that rejects dicts with a clear error message.

Also applies to: 888-889, 915-917

🧹 Nitpick comments (2)
flixopt/topology_accessor.py (1)

24-45: Align _plot_network’s path type with TopologyAccessor.plot and avoid duplication

TopologyAccessor.plot accepts path: bool | str | pathlib.Path = 'flow_system.html' and passes that straight through to _plot_network, whose signature is path: str | pathlib.Path | None = None. At runtime path=False works (you hit the if not show and not path: early return), but the annotation and _plot_network docstring don't mention the False sentinel, so type-checkers will complain.

Consider either:

  • Widening the helper’s type to match the public API, e.g.:
-def _plot_network(
-    node_infos: dict,
-    edge_infos: dict,
-    path: str | pathlib.Path | None = None,
+def _plot_network(
+    node_infos: dict,
+    edge_infos: dict,
+    path: bool | str | pathlib.Path | None = None,

and documenting False explicitly, or

  • Normalizing path in TopologyAccessor.plot (e.g., converting False to None) before calling _plot_network.

Also, _plot_network largely duplicates the existing implementation in flixopt.plotting.plot_network. To keep behavior in sync as you deprecate plotting.py, it would be safer to have one implementation delegate to the other (or share a single internal helper) instead of maintaining two copies.

Also applies to: 76-99, 178-221

flixopt/statistics_accessor.py (1)

269-298: Use configured qualitative default when resolving colors for bars/lines

In _create_stacked_bar and _create_line you now do:

variables = df['variable'].unique().tolist()
color_map = process_colors(colors, variables)

which is a good centralization of discrete color handling. However, this implicitly uses process_colors’ default default_colorscale='turbo', while other plotting paths (e.g., in flixopt.plotting) explicitly route through CONFIG.Plotting.default_qualitative_colorscale.

For consistency with user-configurable plotting defaults, consider:

-    color_map = process_colors(colors, variables)
+    color_map = process_colors(
+        colors,
+        variables,
+        default_colorscale=CONFIG.Plotting.default_qualitative_colorscale,
+    )

and similarly in _create_line. That way, changing CONFIG.Plotting.default_qualitative_colorscale affects all Plotly-express based bar/line plots in the same way.

Also applies to: 300-326

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5f7a710 and 435ecd9.

📒 Files selected for processing (6)
  • flixopt/clustering.py (4 hunks)
  • flixopt/color_processing.py (1 hunks)
  • flixopt/plotting.py (1 hunks)
  • flixopt/statistics_accessor.py (23 hunks)
  • flixopt/topology_accessor.py (2 hunks)
  • tests/test_solution_and_plotting.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
flixopt/plotting.py (1)
flixopt/color_processing.py (1)
  • process_colors (113-181)
flixopt/statistics_accessor.py (2)
flixopt/color_processing.py (1)
  • process_colors (113-181)
flixopt/config.py (2)
  • CONFIG (181-814)
  • Plotting (547-576)
⏰ 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). (4)
  • GitHub Check: test (3.13)
  • GitHub Check: test (3.11)
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.10)
🔇 Additional comments (4)
flixopt/plotting.py (1)

42-42: Centralizing ColorType and process_colors import looks good

Importing ColorType and process_colors from flixopt.color_processing removes the local alias and keeps color handling definitions in one place, which should make future changes to color semantics easier to manage.

tests/test_solution_and_plotting.py (1)

350-356: Heatmap test update matches the new pre-reshaped API

Reshaping long_time_data with plotting.reshape_data_for_heatmap(..., reshape_time=('D', 'h')) before calling heatmap_with_plotly_v2 correctly reflects that v2 expects already reshaped input. The assertions remain valid and the test now follows the intended usage.

flixopt/statistics_accessor.py (2)

51-119: New heatmap helpers _reshape_time_for_heatmap / _heatmap_figure look solid

The new _reshape_time_for_heatmap and _heatmap_figure helpers cleanly encapsulate the time-reshaping and px.imshow wiring that was previously embedded in plotting code. The mapping from (outer_freq, inner_freq) to period_format / step_format, resampling, label construction, and reordering to ('timestep', 'timeframe', *other_dims) all look consistent with the rest of the codebase. Empty-data handling and facet/animation checks in _heatmap_figure are also handled gracefully. No issues here.

Also applies to: 121-166


740-769: ColorType-based colors parameters across statistics plots are cohesive

Updating the plotting methods on StatisticsPlotAccessor (balance, flows, sankey, sizes, duration_curve, effects) to accept colors: ColorType and then feed that through process_colors (or, for Sankey, onto nodes) gives a consistent and flexible color API: callers can pass a colorscale name, a list, or a mapping, and you resolve it once. The existing behavior for show flags and facet dimensions is preserved. From a public API standpoint this is an improvement and looks good.

Also applies to: 930-959, 1128-1188, 1189-1215, 1267-1295, 1375-1395

)

if TYPE_CHECKING:
import pathlib
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 | 🟠 Major

Guard Clustering.plot against unset aggregated_data and consider more robust color mapping

Clustering.__init__ sets self.aggregated_data = None, and cluster() is the only method that populates it. In plot(), you call self.aggregated_data.copy() unconditionally, so calling plot() before cluster() will fail with an AttributeError instead of a clear error message.

You can make this failure mode explicit and user-friendly:

     def plot(self, colormap: str | None = None, show: bool = True, save: pathlib.Path | None = None) -> go.Figure:
-        import plotly.express as px
+        import plotly.express as px
+
+        if self.aggregated_data is None:
+            raise RuntimeError("Clustering.plot() requires aggregated data. Call cluster() before plot().")

Separately, you derive colors via:

colors = list(
    process_colors(colormap or CONFIG.Plotting.default_qualitative_colorscale, list(df_org.columns)).values()
)

and pass that as color_discrete_sequence to both the original and aggregated plots. This assumes the aggregated dataframe has the same column order as the original to keep color pairing between “Original - X” and “Aggregated - X”, which is a bit fragile.

If you want stronger guarantees that each original/aggregated pair shares a color regardless of column ordering, consider building a color_discrete_map keyed by the full series names (or by a shared base label) and passing that into px.line instead of relying solely on sequence position.

Also applies to: 148-183

🤖 Prompt for AI Agents
In flixopt/clustering.py around lines 31 and 148-183, plot() currently calls
self.aggregated_data.copy() unguarded and derives colors as a sequence which
assumes identical column ordering; first add an explicit guard at the top of
plot() that checks if self.aggregated_data is None and raise a clear ValueError
(or return) telling the user to call cluster() first; second, replace the
positional color_discrete_sequence approach with a deterministic
color_discrete_map: generate a mapping from the actual full series names used in
the plot (e.g., original column names and their corresponding aggregated labels
like "Aggregated - X") to colors produced by process_colors (using the provided
colormap or default), and pass this map into px.line via color_discrete_map so
each original/aggregated pair always shares the same color regardless of column
ordering.

Comment on lines 18 to 68
# Type alias for flexible color input
ColorType = str | list[str] | dict[str, str] | None
ColorType = str | list[str] | dict[str, str]
"""Flexible color specification type supporting multiple input formats for visualization.
Color specifications can take several forms to accommodate different use cases:
**Named colorscales** (str):
- Standard colorscales: 'turbo', 'plasma', 'cividis', 'tab10', 'Set1'
- Energy-focused: 'portland' (custom flixopt colorscale for energy systems)
- Backend-specific maps available in Plotly and Matplotlib
**Color Lists** (list[str]):
- Explicit color sequences: ['red', 'blue', 'green', 'orange']
- HEX codes: ['#FF0000', '#0000FF', '#00FF00', '#FFA500']
- Mixed formats: ['red', '#0000FF', 'green', 'orange']
**Label-to-Color Mapping** (dict[str, str]):
- Explicit associations: {'Wind': 'skyblue', 'Solar': 'gold', 'Gas': 'brown'}
- Ensures consistent colors across different plots and datasets
- Ideal for energy system components with semantic meaning
Examples:
```python
# Named colorscale
colors = 'turbo' # Automatic color generation
# Explicit color list
colors = ['red', 'blue', 'green', '#FFD700']
# Component-specific mapping
colors = {
'Wind_Turbine': 'skyblue',
'Solar_Panel': 'gold',
'Natural_Gas': 'brown',
'Battery': 'green',
'Electric_Load': 'darkred'
}
```
Color Format Support:
- **Named Colors**: 'red', 'blue', 'forestgreen', 'darkorange'
- **HEX Codes**: '#FF0000', '#0000FF', '#228B22', '#FF8C00'
- **RGB Tuples**: (255, 0, 0), (0, 0, 255) [Matplotlib only]
- **RGBA**: 'rgba(255,0,0,0.8)' [Plotly only]
References:
- HTML Color Names: https://htmlcolorcodes.com/color-names/
- Matplotlib colorscales: https://matplotlib.org/stable/tutorials/colors/colorscales.html
- Plotly Built-in Colorscales: https://plotly.com/python/builtin-colorscales/
"""
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 | 🟠 Major

Fix ColorType alias shadowing and keep None in the type

ColorType is defined twice and the second assignment drops None, so the effective alias is str | list[str] | dict[str, str]. This conflicts with both process_colors (which explicitly accepts None) and multiple call sites that use colors: ColorType = None or ColorType | None. It will confuse type-checkers and makes those defaults formally invalid.

You can fix this by keeping a single alias that includes None and removing the duplicate line:

-# Type alias for flexible color input
-ColorType = str | list[str] | dict[str, str] | None
-ColorType = str | list[str] | dict[str, str]
+# Type alias for flexible color input
+ColorType = str | list[str] | dict[str, str] | None

Optionally, you may also mention the None case explicitly in the accompanying documentation to match the actual type.

📝 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
# Type alias for flexible color input
ColorType = str | list[str] | dict[str, str] | None
ColorType = str | list[str] | dict[str, str]
"""Flexible color specification type supporting multiple input formats for visualization.
Color specifications can take several forms to accommodate different use cases:
**Named colorscales** (str):
- Standard colorscales: 'turbo', 'plasma', 'cividis', 'tab10', 'Set1'
- Energy-focused: 'portland' (custom flixopt colorscale for energy systems)
- Backend-specific maps available in Plotly and Matplotlib
**Color Lists** (list[str]):
- Explicit color sequences: ['red', 'blue', 'green', 'orange']
- HEX codes: ['#FF0000', '#0000FF', '#00FF00', '#FFA500']
- Mixed formats: ['red', '#0000FF', 'green', 'orange']
**Label-to-Color Mapping** (dict[str, str]):
- Explicit associations: {'Wind': 'skyblue', 'Solar': 'gold', 'Gas': 'brown'}
- Ensures consistent colors across different plots and datasets
- Ideal for energy system components with semantic meaning
Examples:
```python
# Named colorscale
colors = 'turbo' # Automatic color generation
# Explicit color list
colors = ['red', 'blue', 'green', '#FFD700']
# Component-specific mapping
colors = {
'Wind_Turbine': 'skyblue',
'Solar_Panel': 'gold',
'Natural_Gas': 'brown',
'Battery': 'green',
'Electric_Load': 'darkred'
}
```
Color Format Support:
- **Named Colors**: 'red', 'blue', 'forestgreen', 'darkorange'
- **HEX Codes**: '#FF0000', '#0000FF', '#228B22', '#FF8C00'
- **RGB Tuples**: (255, 0, 0), (0, 0, 255) [Matplotlib only]
- **RGBA**: 'rgba(255,0,0,0.8)' [Plotly only]
References:
- HTML Color Names: https://htmlcolorcodes.com/color-names/
- Matplotlib colorscales: https://matplotlib.org/stable/tutorials/colors/colorscales.html
- Plotly Built-in Colorscales: https://plotly.com/python/builtin-colorscales/
"""
# Type alias for flexible color input
ColorType = str | list[str] | dict[str, str] | None
"""Flexible color specification type supporting multiple input formats for visualization.
Color specifications can take several forms to accommodate different use cases:
**Named colorscales** (str):
- Standard colorscales: 'turbo', 'plasma', 'cividis', 'tab10', 'Set1'
- Energy-focused: 'portland' (custom flixopt colorscale for energy systems)
- Backend-specific maps available in Plotly and Matplotlib
**Color Lists** (list[str]):
- Explicit color sequences: ['red', 'blue', 'green', 'orange']
- HEX codes: ['#FF0000', '#0000FF', '#00FF00', '#FFA500']
- Mixed formats: ['red', '#0000FF', 'green', 'orange']
**Label-to-Color Mapping** (dict[str, str]):
- Explicit associations: {'Wind': 'skyblue', 'Solar': 'gold', 'Gas': 'brown'}
- Ensures consistent colors across different plots and datasets
- Ideal for energy system components with semantic meaning
Examples:
🤖 Prompt for AI Agents
In flixopt/color_processing.py around lines 18 to 68, the ColorType alias is
declared twice and the second declaration drops None, causing type shadowing and
making defaults like colors: ColorType = None invalid; remove the duplicate line
and keep a single ColorType definition that includes None (e.g., ColorType = str
| list[str] | dict[str, str] | None), and update the docstring wording if
desired to explicitly mention that None is an allowed value.

…820-853)

  - Changed _heatmap_figure() parameter type from colors: ColorType = None to colors: str | list[str] | None = None
  - Changed heatmap() method parameter type similarly
  - Updated docstrings to clarify that dicts are not supported for heatmaps since px.imshow's color_continuous_scale only accepts colorscale names or lists

  2. statistics_accessor.py - Use configured qualitative colorscale (lines 284, 315)

  - Updated _create_stacked_bar() to use CONFIG.Plotting.default_qualitative_colorscale as the default colorscale
  - Updated _create_line() similarly
  - This ensures user-configured CONFIG.Plotting.default_qualitative_colorscale affects all bar/line plots consistently

  3. topology_accessor.py - Path type alignment (lines 219-222)

  - Added normalization of path=False to None before calling _plot_network()
  - This resolves the type mismatch where TopologyAccessor.plot() accepts bool | str | Path but _plot_network() only accepts str | Path | None
@FBumann FBumann merged commit e686932 into feature/solution-storage-change+plotting-acessors Dec 7, 2025
8 checks passed
FBumann added a commit that referenced this pull request Dec 8, 2025
* Add planning doc

* Finalize planning

* Add plotting acessor

* Add plotting acessor

* Add tests

* Improve

* Improve

* Update docs

* Updated the plotting API so that .data always returns xarray DataArray or Dataset instead of pandas DataFrame.

* All .data now returns xr.Dataset consistently.

* Fixed Inconsistencies and Unused Parameters

* New Plot Accessors

  results.plot.variable(pattern)

  Plots the same variable type across multiple elements for easy comparison.

  # All binary operation states across all components
  results.plot.variable('on')

  # All flow_rates, filtered to Boiler-related elements
  results.plot.variable('flow_rate', include='Boiler')

  # All storage charge states
  results.plot.variable('charge_state')

  # With aggregation
  results.plot.variable('flow_rate', aggregate='sum')

  Key features:
  - Searches all elements for variables matching the pattern
  - Filter with include/exclude on element names
  - Supports aggregation, faceting, and animation
  - Returns Dataset with element names as variables

  results.plot.duration_curve(variables)

  Plots load duration curves (sorted time series) showing utilization patterns.

  # Single variable
  results.plot.duration_curve('Boiler(Q_th)|flow_rate')

  # Multiple variables
  results.plot.duration_curve(['CHP|on', 'Boiler|on'])

  # Normalized x-axis (0-100%)
  results.plot.duration_curve('demand', normalize=True)

  Key features:
  - Sorts values from highest to lowest
  - Shows how often each power level is reached
  - normalize=True shows percentage of time on x-axis
  - Returns Dataset with duration_hours or duration_pct dimension

* Fix duration curve

* Fix duration curve

* Fix duration curve

* Fix duration curve

* xr.apply_ufunc to sort along the time axis while preserving all other dimensions automatically

* ⏺ The example runs successfully. Now let me summarize the fixes:

  Summary of Fixes

  I addressed the actionable code review comments from CodeRabbitAI:

  1. Documentation Issue - reshape parameter name ✓ (No fix needed)

  The CodeRabbitAI comment was incorrect. The public API parameter in PlotAccessor.heatmap() is correctly named reshape (line 335). The reshape_time parameter exists in the lower-level heatmap_with_plotly function, but the documentation correctly shows the public API parameter.

  2. Fixed simple_example.py (lines 129-130)

  Problem: The example called balance() and duration_curve() without required arguments, which would cause TypeError at runtime.

  Fix: Added the required arguments:
  - optimization.results.plot.balance('Fernwärme') - specifying the bus to plot
  - optimization.results.plot.duration_curve('Boiler(Q_th)|flow_rate') - specifying the variable to plot

  3. Fixed variable collision in plot_accessors.py (lines 985-988)

  Problem: When building the Dataset in the variable() method, using element names as keys could cause collisions if multiple variables from the same element matched the pattern (e.g., 'Boiler|flow_rate' and 'Boiler|flow_rate_max' would both map to 'Boiler', with the latter silently overwriting the former).

  Fix: Changed to use the full variable names as keys instead of just element names:
  ds = xr.Dataset({var_name: self._results.solution[var_name] for var_name in filtered_vars})

  All tests pass (40 passed, 1 skipped) and the example runs successfully.

* make variable names public in results

* Fix sankey

* Fix effects()

* Fix effects

* Remove bargaps

* made faceting consistent across all plot methods:

  | Method           | facet_col                                 | facet_row                   |
  |------------------|-------------------------------------------|-----------------------------|
  | balance()        | 'scenario'                                | 'period'                    |
  | heatmap()        | 'scenario'                                | 'period'                    |
  | storage()        | 'scenario'                                | 'period'                    |
  | flows()          | 'scenario'                                | 'period'                    |
  | effects()        | 'scenario'                                | 'period'                    |
  | variable()       | 'scenario'                                | 'period'                    |
  | duration_curve() | 'scenario'                                | 'period' (already had this) |
  | compare()        | N/A (uses its own mode='overlay'/'facet') |                             |
  | sankey()         | N/A (aggregates to single diagram)        |                             |

  Removed animate_by parameter from all methods

* Update storage method

* Remove mode parameter for simpli
  | Method           | Default mode                                      |
  |------------------|---------------------------------------------------|
  | balance()        | stacked_bar                                       |
  | storage()        | stacked_bar (flows) + line (charge state overlay) |
  | flows()          | line                                              |
  | variable()       | line                                              |
  | duration_curve() | line                                              |
  | effects()        | bar                                               |

* Make plotting_accessors.py more self contained

* Use faster to_long()

* Add 0-dim case

* sankey diagram now properly handles scenarios and periods:

  Changes made:
  1. Period aggregation with weights: Uses period_weights from flow_system to properly weight periods by their duration
  2. Scenario aggregation with weights: Uses normalized scenario_weights to compute a weighted average across scenarios
  3. Selection support: Users can filter specific scenarios/periods via select parameter before aggregation

  Weighting logic:
  - Periods (for aggregate='sum'): (da * period_weights).sum(dim='period') - this gives the total energy across all periods weighted by their duration
  - Periods (for aggregate='mean'): (da * period_weights).sum(dim='period') / period_weights.sum() - weighted mean
  - Scenarios: Always uses normalized weights (sum to 1) for weighted averaging, since scenarios represent probability-weighted alternatives

* Add colors to sankey

* Add sizes

* Add size filtering

* Include storage sizes

* Remove storage sizes

* Add charge state and status accessor

* Summary of Changes

  1. Added new methods to PlotAccessor (plot_accessors.py)

  charge_states() (line 658):
  - Returns a Dataset with each storage's charge state as a variable
  - Supports filtering with include/exclude parameters
  - Default plot: line chart

  on_states() (line 753):
  - Returns a Dataset with each component's |status variable
  - Supports filtering with include/exclude parameters
  - Default plot: heatmap (good for binary data visualization)

  2. Added data building helper functions (plot_accessors.py)

  build_flow_rates(results) (line 315):
  - Builds a DataArray containing flow rates for all flows
  - Used internally by PlotAccessor methods

  build_flow_hours(results) (line 333):
  - Builds a DataArray containing flow hours for all flows

  build_sizes(results) (line 347):
  - Builds a DataArray containing sizes for all flows

  _filter_dataarray_by_coord(da, **kwargs) (line 284):
  - Helper for filtering DataArrays by coordinate values

  3. Deprecated old Results methods (results.py)

  The following methods now emit DeprecationWarning:
  - results.flow_rates() → Use results.plot.flows(plot=False).data
  - results.flow_hours() → Use results.plot.flows(unit='flow_hours', plot=False).data
  - results.sizes() → Use results.plot.sizes(plot=False).data

  4. Updated PlotAccessor methods to use new helpers

  - flows() now uses build_flow_rates() / build_flow_hours() directly
  - sizes() now uses build_sizes() directly
  - sankey() now uses build_flow_hours() directly

  This ensures the deprecation warnings only fire when users directly call the old methods, not when using the plot accessor

* 1. New methods added to PlotAccessor

  - charge_states(): Returns Dataset with all storage charge states
  - on_states(): Returns Dataset with all component status variables (heatmap display)

  2. Data building helper functions (plot_accessors.py)

  - build_flow_rates(results): Builds DataArray of flow rates
  - build_flow_hours(results): Builds DataArray of flow hours
  - build_sizes(results): Builds DataArray of sizes
  - _filter_dataarray_by_coord(da, **kwargs): Filter helper
  - _assign_flow_coords(da, results): Add flow coordinates

  3. Caching in PlotAccessor

  Added lazy-cached properties for expensive computations:
  - _all_flow_rates - cached DataArray of all flow rates
  - _all_flow_hours - cached DataArray of all flow hours
  - _all_sizes - cached DataArray of all sizes
  - _all_charge_states - cached Dataset of all storage charge states
  - _all_status_vars - cached Dataset of all status variables

  4. Deprecated methods in Results class

  Added deprecation warnings to:
  - results.flow_rates() → Use results.plot.flows(plot=False).data
  - results.flow_hours() → Use results.plot.flows(unit='flow_hours', plot=False).data
  - results.sizes() → Use results.plot.sizes(plot=False).data

  5. Updated PlotAccessor methods to use cached properties

  - flows() uses _all_flow_rates / _all_flow_hours
  - sankey() uses _all_flow_hours
  - sizes() uses _all_sizes
  - charge_states() uses _all_charge_states
  - on_states() uses _all_status_vars

* Move deprectated functionality into results.py instead of porting to the new module

* Revert to simply deprectae old methods without forwarding to new code

* Remove planning file

* Update plotting methods for new datasets

* 1. Renamed data properties in PlotAccessor to use all_ prefix:
    - all_flow_rates - All flow rates as Dataset
    - all_flow_hours - All flow hours as Dataset
    - all_sizes - All flow sizes as Dataset
    - all_charge_states - All storage charge states as Dataset
    - all_on_states - All component on/off status as Dataset
  2. Updated internal references - All usages in flows(), sankey(), sizes(), charge_states(), and on_states() methods now use the new names.
  3. Updated deprecation messages in results.py to point to the new API:
    - results.flow_rates() → results.plot.all_flow_rates
    - results.flow_hours() → results.plot.all_flow_hours
    - results.sizes() → results.plot.all_sizes
  4. Updated docstring examples in PlotAccessor to use the new all_* names.

* Update deprecations messages

* Update deprecations messages

* Thsi seems much better.

* Updaet docstrings and variable name generation in plotting acessor

* Change __ to _ in private dataset caching

* Revert breaking io changes

* New solution storing interface

* Add new focused statistics and plot accessors

* Renamed all properties:
  - all_flow_rates → flow_rates
  - all_flow_hours → flow_hours
  - all_sizes → sizes
  - all_charge_states → charge_states

* Cache Statistics

* Invalidate caches

* Add effect related statistics

* Simplify statistics accessor to rely on flow_system directly instead of solution attrs

* Fix heatma fallback for 1D Data

* Add topology accessor

* All deprecation warnings in the codebase now consistently use the format will be removed in v{DEPRECATION_REMOVAL_VERSION}.

* Update tests

* created comprehensive documentation for all FlowSystem accessors

* Update results documentation

* Update results documentation

* Update effect statistics

* Update effect statistics

* Update effect statistics

* Add mkdocs plotly plugin

* Add section about custom constraints

* documentation updates:

  docs/user-guide/results/index.md:
  - Updated table to replace effects_per_component with temporal_effects, periodic_effects, total_effects, and effect_share_factors
  - Fixed flow_hours['Boiler(Q_th)|flow_rate'] → flow_hours['Boiler(Q_th)']
  - Fixed sizes['Boiler(Q_th)|size'] → sizes['Boiler(Q_th)']
  - Replaced effects_per_component example with new effect properties and groupby examples
  - Updated complete example to use total_effects

  docs/user-guide/results-plotting.md:
  - Fixed colors example from 'Boiler(Q_th)|flow_rate' → 'Boiler(Q_th)'
  - Fixed duration_curve examples to use clean labels

  docs/user-guide/migration-guide-v6.md:
  - Added new "Statistics Accessor" section explaining the clean labels and new effect properties

* implemented the effects() method in StatisticsPlotAccessor at flixopt/statistics_accessor.py:1132-1258.

  Summary of what was done:

  1. Implemented effects() method in StatisticsPlotAccessor class that was missing but documented
    - Takes aspect parameter: 'total', 'temporal', or 'periodic'
    - Takes effect parameter to filter to a specific effect (e.g., 'costs', 'CO2')
    - Takes by parameter: 'component' or 'time' for grouping
    - Supports all standard plotting parameters: select, colors, facet_col, facet_row, show
    - Returns PlotResult with both data and figure
  2. Verified the implementation works with all parameter combinations:
    - Default call: flow_system.statistics.plot.effects()
    - Specific effect: flow_system.statistics.plot.effects(effect='costs')
    - Temporal aspect: flow_system.statistics.plot.effects(aspect='temporal')
    - Temporal by time: flow_system.statistics.plot.effects(aspect='temporal', by='time')
    - Periodic aspect: flow_system.statistics.plot.effects(aspect='periodic')

* Remove intermediate plot accessor

* 1. pyproject.toml: Removed duplicate mkdocs-plotly-plugin>=0.1.3 entry (kept the exact pin ==0.1.3)
  2. flixopt/plotting.py: Fixed dimension name consistency by using squeezed_data.name instead of data.name in the fallback heatmap logic
  3. flixopt/statistics_accessor.py:
    - Fixed _dataset_to_long_df() to only use coordinates that are actually present as columns after reset_index()
    - Fixed the nested loop inefficiency with include_flows by pre-computing the flows list outside the loop
    - (Previously fixed) Fixed asymmetric NaN handling in validation check

* _create_effects_dataset method in statistics_accessor.py was simplified:

  1. Detect contributors from solution data variables instead of assuming they're only flows
    - Uses regex pattern to find {contributor}->{effect}(temporal|periodic) variables
    - Contributors can be flows OR components (e.g., components with effects_per_active_hour)
  2. Exclude effect-to-effect shares
    - Filters out contributors whose base name matches any effect label
    - For example, costs(temporal) is excluded because costs is an effect label
    - These intermediate shares are already included in the computation
  3. Removed the unused _compute_effect_total method
    - The new simplified implementation directly looks up shares from the solution
    - Uses effect_share_factors for conversion between effects
  4. Key insight from user: The solution already contains properly computed share values including all effect-to-effect conversions. The computation uses conversion
  factors because derived effects (like Effect1 which shares 0.5 from costs) don't have direct {flow}->Effect1(temporal) variables - only the source effect shares exist
   ({flow}->costs(temporal)).

* Update docs

* Improve to_netcdf method

* Update examples

* Fix IIS computaion flag

* Fix examples

* Fix faceting in heatmap and use period as facet col everywhere

* Inline plotting methods to deprecate plotting.py (#508)

* Inline plotting methods to deprecate plotting.py

* Fix test

* Simplify Color Management

* ColorType is now defined in color_processing.py and imported into statistics_accessor.py.

* Fix ColorType typing

* statistics_accessor.py - Heatmap colors type safety (lines 121-148, 820-853)

  - Changed _heatmap_figure() parameter type from colors: ColorType = None to colors: str | list[str] | None = None
  - Changed heatmap() method parameter type similarly
  - Updated docstrings to clarify that dicts are not supported for heatmaps since px.imshow's color_continuous_scale only accepts colorscale names or lists

  2. statistics_accessor.py - Use configured qualitative colorscale (lines 284, 315)

  - Updated _create_stacked_bar() to use CONFIG.Plotting.default_qualitative_colorscale as the default colorscale
  - Updated _create_line() similarly
  - This ensures user-configured CONFIG.Plotting.default_qualitative_colorscale affects all bar/line plots consistently

  3. topology_accessor.py - Path type alignment (lines 219-222)

  - Added normalization of path=False to None before calling _plot_network()
  - This resolves the type mismatch where TopologyAccessor.plot() accepts bool | str | Path but _plot_network() only accepts str | Path | None

* fix usage if index name in aggregation plot
FBumann added a commit that referenced this pull request Dec 8, 2025
* Add planning doc

* Finalize planning

* Add plotting acessor

* Add plotting acessor

* Add tests

* Improve

* Improve

* Update docs

* Updated the plotting API so that .data always returns xarray DataArray or Dataset instead of pandas DataFrame.

* All .data now returns xr.Dataset consistently.

* Fixed Inconsistencies and Unused Parameters

* New Plot Accessors

  results.plot.variable(pattern)

  Plots the same variable type across multiple elements for easy comparison.

  # All binary operation states across all components
  results.plot.variable('on')

  # All flow_rates, filtered to Boiler-related elements
  results.plot.variable('flow_rate', include='Boiler')

  # All storage charge states
  results.plot.variable('charge_state')

  # With aggregation
  results.plot.variable('flow_rate', aggregate='sum')

  Key features:
  - Searches all elements for variables matching the pattern
  - Filter with include/exclude on element names
  - Supports aggregation, faceting, and animation
  - Returns Dataset with element names as variables

  results.plot.duration_curve(variables)

  Plots load duration curves (sorted time series) showing utilization patterns.

  # Single variable
  results.plot.duration_curve('Boiler(Q_th)|flow_rate')

  # Multiple variables
  results.plot.duration_curve(['CHP|on', 'Boiler|on'])

  # Normalized x-axis (0-100%)
  results.plot.duration_curve('demand', normalize=True)

  Key features:
  - Sorts values from highest to lowest
  - Shows how often each power level is reached
  - normalize=True shows percentage of time on x-axis
  - Returns Dataset with duration_hours or duration_pct dimension

* Fix duration curve

* Fix duration curve

* Fix duration curve

* Fix duration curve

* xr.apply_ufunc to sort along the time axis while preserving all other dimensions automatically

* ⏺ The example runs successfully. Now let me summarize the fixes:

  Summary of Fixes

  I addressed the actionable code review comments from CodeRabbitAI:

  1. Documentation Issue - reshape parameter name ✓ (No fix needed)

  The CodeRabbitAI comment was incorrect. The public API parameter in PlotAccessor.heatmap() is correctly named reshape (line 335). The reshape_time parameter exists in the lower-level heatmap_with_plotly function, but the documentation correctly shows the public API parameter.

  2. Fixed simple_example.py (lines 129-130)

  Problem: The example called balance() and duration_curve() without required arguments, which would cause TypeError at runtime.

  Fix: Added the required arguments:
  - optimization.results.plot.balance('Fernwärme') - specifying the bus to plot
  - optimization.results.plot.duration_curve('Boiler(Q_th)|flow_rate') - specifying the variable to plot

  3. Fixed variable collision in plot_accessors.py (lines 985-988)

  Problem: When building the Dataset in the variable() method, using element names as keys could cause collisions if multiple variables from the same element matched the pattern (e.g., 'Boiler|flow_rate' and 'Boiler|flow_rate_max' would both map to 'Boiler', with the latter silently overwriting the former).

  Fix: Changed to use the full variable names as keys instead of just element names:
  ds = xr.Dataset({var_name: self._results.solution[var_name] for var_name in filtered_vars})

  All tests pass (40 passed, 1 skipped) and the example runs successfully.

* make variable names public in results

* Fix sankey

* Fix effects()

* Fix effects

* Remove bargaps

* made faceting consistent across all plot methods:

  | Method           | facet_col                                 | facet_row                   |
  |------------------|-------------------------------------------|-----------------------------|
  | balance()        | 'scenario'                                | 'period'                    |
  | heatmap()        | 'scenario'                                | 'period'                    |
  | storage()        | 'scenario'                                | 'period'                    |
  | flows()          | 'scenario'                                | 'period'                    |
  | effects()        | 'scenario'                                | 'period'                    |
  | variable()       | 'scenario'                                | 'period'                    |
  | duration_curve() | 'scenario'                                | 'period' (already had this) |
  | compare()        | N/A (uses its own mode='overlay'/'facet') |                             |
  | sankey()         | N/A (aggregates to single diagram)        |                             |

  Removed animate_by parameter from all methods

* Update storage method

* Remove mode parameter for simpli
  | Method           | Default mode                                      |
  |------------------|---------------------------------------------------|
  | balance()        | stacked_bar                                       |
  | storage()        | stacked_bar (flows) + line (charge state overlay) |
  | flows()          | line                                              |
  | variable()       | line                                              |
  | duration_curve() | line                                              |
  | effects()        | bar                                               |

* Make plotting_accessors.py more self contained

* Use faster to_long()

* Add 0-dim case

* sankey diagram now properly handles scenarios and periods:

  Changes made:
  1. Period aggregation with weights: Uses period_weights from flow_system to properly weight periods by their duration
  2. Scenario aggregation with weights: Uses normalized scenario_weights to compute a weighted average across scenarios
  3. Selection support: Users can filter specific scenarios/periods via select parameter before aggregation

  Weighting logic:
  - Periods (for aggregate='sum'): (da * period_weights).sum(dim='period') - this gives the total energy across all periods weighted by their duration
  - Periods (for aggregate='mean'): (da * period_weights).sum(dim='period') / period_weights.sum() - weighted mean
  - Scenarios: Always uses normalized weights (sum to 1) for weighted averaging, since scenarios represent probability-weighted alternatives

* Add colors to sankey

* Add sizes

* Add size filtering

* Include storage sizes

* Remove storage sizes

* Add charge state and status accessor

* Summary of Changes

  1. Added new methods to PlotAccessor (plot_accessors.py)

  charge_states() (line 658):
  - Returns a Dataset with each storage's charge state as a variable
  - Supports filtering with include/exclude parameters
  - Default plot: line chart

  on_states() (line 753):
  - Returns a Dataset with each component's |status variable
  - Supports filtering with include/exclude parameters
  - Default plot: heatmap (good for binary data visualization)

  2. Added data building helper functions (plot_accessors.py)

  build_flow_rates(results) (line 315):
  - Builds a DataArray containing flow rates for all flows
  - Used internally by PlotAccessor methods

  build_flow_hours(results) (line 333):
  - Builds a DataArray containing flow hours for all flows

  build_sizes(results) (line 347):
  - Builds a DataArray containing sizes for all flows

  _filter_dataarray_by_coord(da, **kwargs) (line 284):
  - Helper for filtering DataArrays by coordinate values

  3. Deprecated old Results methods (results.py)

  The following methods now emit DeprecationWarning:
  - results.flow_rates() → Use results.plot.flows(plot=False).data
  - results.flow_hours() → Use results.plot.flows(unit='flow_hours', plot=False).data
  - results.sizes() → Use results.plot.sizes(plot=False).data

  4. Updated PlotAccessor methods to use new helpers

  - flows() now uses build_flow_rates() / build_flow_hours() directly
  - sizes() now uses build_sizes() directly
  - sankey() now uses build_flow_hours() directly

  This ensures the deprecation warnings only fire when users directly call the old methods, not when using the plot accessor

* 1. New methods added to PlotAccessor

  - charge_states(): Returns Dataset with all storage charge states
  - on_states(): Returns Dataset with all component status variables (heatmap display)

  2. Data building helper functions (plot_accessors.py)

  - build_flow_rates(results): Builds DataArray of flow rates
  - build_flow_hours(results): Builds DataArray of flow hours
  - build_sizes(results): Builds DataArray of sizes
  - _filter_dataarray_by_coord(da, **kwargs): Filter helper
  - _assign_flow_coords(da, results): Add flow coordinates

  3. Caching in PlotAccessor

  Added lazy-cached properties for expensive computations:
  - _all_flow_rates - cached DataArray of all flow rates
  - _all_flow_hours - cached DataArray of all flow hours
  - _all_sizes - cached DataArray of all sizes
  - _all_charge_states - cached Dataset of all storage charge states
  - _all_status_vars - cached Dataset of all status variables

  4. Deprecated methods in Results class

  Added deprecation warnings to:
  - results.flow_rates() → Use results.plot.flows(plot=False).data
  - results.flow_hours() → Use results.plot.flows(unit='flow_hours', plot=False).data
  - results.sizes() → Use results.plot.sizes(plot=False).data

  5. Updated PlotAccessor methods to use cached properties

  - flows() uses _all_flow_rates / _all_flow_hours
  - sankey() uses _all_flow_hours
  - sizes() uses _all_sizes
  - charge_states() uses _all_charge_states
  - on_states() uses _all_status_vars

* Move deprectated functionality into results.py instead of porting to the new module

* Revert to simply deprectae old methods without forwarding to new code

* Remove planning file

* Update plotting methods for new datasets

* 1. Renamed data properties in PlotAccessor to use all_ prefix:
    - all_flow_rates - All flow rates as Dataset
    - all_flow_hours - All flow hours as Dataset
    - all_sizes - All flow sizes as Dataset
    - all_charge_states - All storage charge states as Dataset
    - all_on_states - All component on/off status as Dataset
  2. Updated internal references - All usages in flows(), sankey(), sizes(), charge_states(), and on_states() methods now use the new names.
  3. Updated deprecation messages in results.py to point to the new API:
    - results.flow_rates() → results.plot.all_flow_rates
    - results.flow_hours() → results.plot.all_flow_hours
    - results.sizes() → results.plot.all_sizes
  4. Updated docstring examples in PlotAccessor to use the new all_* names.

* Update deprecations messages

* Update deprecations messages

* Thsi seems much better.

* Updaet docstrings and variable name generation in plotting acessor

* Change __ to _ in private dataset caching

* Revert breaking io changes

* New solution storing interface

* Add new focused statistics and plot accessors

* Renamed all properties:
  - all_flow_rates → flow_rates
  - all_flow_hours → flow_hours
  - all_sizes → sizes
  - all_charge_states → charge_states

* Cache Statistics

* Invalidate caches

* Add effect related statistics

* Simplify statistics accessor to rely on flow_system directly instead of solution attrs

* Fix heatma fallback for 1D Data

* Add topology accessor

* All deprecation warnings in the codebase now consistently use the format will be removed in v{DEPRECATION_REMOVAL_VERSION}.

* Update tests

* created comprehensive documentation for all FlowSystem accessors

* Update results documentation

* Update results documentation

* Update effect statistics

* Update effect statistics

* Update effect statistics

* Add mkdocs plotly plugin

* Add section about custom constraints

* documentation updates:

  docs/user-guide/results/index.md:
  - Updated table to replace effects_per_component with temporal_effects, periodic_effects, total_effects, and effect_share_factors
  - Fixed flow_hours['Boiler(Q_th)|flow_rate'] → flow_hours['Boiler(Q_th)']
  - Fixed sizes['Boiler(Q_th)|size'] → sizes['Boiler(Q_th)']
  - Replaced effects_per_component example with new effect properties and groupby examples
  - Updated complete example to use total_effects

  docs/user-guide/results-plotting.md:
  - Fixed colors example from 'Boiler(Q_th)|flow_rate' → 'Boiler(Q_th)'
  - Fixed duration_curve examples to use clean labels

  docs/user-guide/migration-guide-v6.md:
  - Added new "Statistics Accessor" section explaining the clean labels and new effect properties

* implemented the effects() method in StatisticsPlotAccessor at flixopt/statistics_accessor.py:1132-1258.

  Summary of what was done:

  1. Implemented effects() method in StatisticsPlotAccessor class that was missing but documented
    - Takes aspect parameter: 'total', 'temporal', or 'periodic'
    - Takes effect parameter to filter to a specific effect (e.g., 'costs', 'CO2')
    - Takes by parameter: 'component' or 'time' for grouping
    - Supports all standard plotting parameters: select, colors, facet_col, facet_row, show
    - Returns PlotResult with both data and figure
  2. Verified the implementation works with all parameter combinations:
    - Default call: flow_system.statistics.plot.effects()
    - Specific effect: flow_system.statistics.plot.effects(effect='costs')
    - Temporal aspect: flow_system.statistics.plot.effects(aspect='temporal')
    - Temporal by time: flow_system.statistics.plot.effects(aspect='temporal', by='time')
    - Periodic aspect: flow_system.statistics.plot.effects(aspect='periodic')

* Remove intermediate plot accessor

* 1. pyproject.toml: Removed duplicate mkdocs-plotly-plugin>=0.1.3 entry (kept the exact pin ==0.1.3)
  2. flixopt/plotting.py: Fixed dimension name consistency by using squeezed_data.name instead of data.name in the fallback heatmap logic
  3. flixopt/statistics_accessor.py:
    - Fixed _dataset_to_long_df() to only use coordinates that are actually present as columns after reset_index()
    - Fixed the nested loop inefficiency with include_flows by pre-computing the flows list outside the loop
    - (Previously fixed) Fixed asymmetric NaN handling in validation check

* _create_effects_dataset method in statistics_accessor.py was simplified:

  1. Detect contributors from solution data variables instead of assuming they're only flows
    - Uses regex pattern to find {contributor}->{effect}(temporal|periodic) variables
    - Contributors can be flows OR components (e.g., components with effects_per_active_hour)
  2. Exclude effect-to-effect shares
    - Filters out contributors whose base name matches any effect label
    - For example, costs(temporal) is excluded because costs is an effect label
    - These intermediate shares are already included in the computation
  3. Removed the unused _compute_effect_total method
    - The new simplified implementation directly looks up shares from the solution
    - Uses effect_share_factors for conversion between effects
  4. Key insight from user: The solution already contains properly computed share values including all effect-to-effect conversions. The computation uses conversion
  factors because derived effects (like Effect1 which shares 0.5 from costs) don't have direct {flow}->Effect1(temporal) variables - only the source effect shares exist
   ({flow}->costs(temporal)).

* Update docs

* Improve to_netcdf method

* Update examples

* Fix IIS computaion flag

* Fix examples

* Fix faceting in heatmap and use period as facet col everywhere

* Inline plotting methods to deprecate plotting.py

* Fix test

* Simplify Color Management

* ColorType is now defined in color_processing.py and imported into statistics_accessor.py.

* Fix ColorType typing

* Add color accessor

* Ensure io

* Add carrier class

* implemented Carrier as a proper Interface subclass with container support. Here's what was done:

  1. Carrier class (flixopt/carrier.py)

  - Now inherits from Interface for serialization capabilities
  - Has transform_data() method (no-op since carriers have no time-series data)
  - Has label property for container keying
  - Maintains equality comparison with both Carrier objects and strings

  2. CarrierContainer class (flixopt/carrier.py)

  - Inherits from ContainerMixin['Carrier']
  - Provides dict-like access with nice repr and error messages
  - Uses carrier.name for keying

  3. FlowSystem updates (flixopt/flow_system.py)

  - _carriers is now a CarrierContainer instead of a plain dict
  - carriers property returns the CarrierContainer
  - add_carrier() uses the container's add() method
  - Serialization updated to include carriers in to_dataset() and restore them in from_dataset()

  4. Exports (flixopt/__init__.py)

  - Both Carrier and CarrierContainer are now exported

* Inline plotting methods to deprecate plotting.py (#508)

* Inline plotting methods to deprecate plotting.py

* Fix test

* Simplify Color Management

* ColorType is now defined in color_processing.py and imported into statistics_accessor.py.

* Fix ColorType typing

* statistics_accessor.py - Heatmap colors type safety (lines 121-148, 820-853)

  - Changed _heatmap_figure() parameter type from colors: ColorType = None to colors: str | list[str] | None = None
  - Changed heatmap() method parameter type similarly
  - Updated docstrings to clarify that dicts are not supported for heatmaps since px.imshow's color_continuous_scale only accepts colorscale names or lists

  2. statistics_accessor.py - Use configured qualitative colorscale (lines 284, 315)

  - Updated _create_stacked_bar() to use CONFIG.Plotting.default_qualitative_colorscale as the default colorscale
  - Updated _create_line() similarly
  - This ensures user-configured CONFIG.Plotting.default_qualitative_colorscale affects all bar/line plots consistently

  3. topology_accessor.py - Path type alignment (lines 219-222)

  - Added normalization of path=False to None before calling _plot_network()
  - This resolves the type mismatch where TopologyAccessor.plot() accepts bool | str | Path but _plot_network() only accepts str | Path | None

* fix usage if index name in aggregation plot

* Add to docs

* Improve carrier colors and defaults

* Update default carriers and colors

* Update config

* Update config

* Move default carriers to config.py

* Change default carrier handling

* Add color handling

* Rmeove meta_data color handling

* Add carrierst to examples

* Improve plotting acessor

* Improve _resolve_variable_names

* Improve _resolve_variable_names

* Simplify coloring and remove color accessor

* Add connected_and_transformed handling

* Improve error message in container

* BUGFIX: Carrier from dataset
FBumann added a commit that referenced this pull request Dec 8, 2025
…or (#510)

* Add planning doc

* Finalize planning

* Add plotting acessor

* Add plotting acessor

* Add tests

* Improve

* Improve

* Update docs

* Updated the plotting API so that .data always returns xarray DataArray or Dataset instead of pandas DataFrame.

* All .data now returns xr.Dataset consistently.

* Fixed Inconsistencies and Unused Parameters

* New Plot Accessors

  results.plot.variable(pattern)

  Plots the same variable type across multiple elements for easy comparison.

  # All binary operation states across all components
  results.plot.variable('on')

  # All flow_rates, filtered to Boiler-related elements
  results.plot.variable('flow_rate', include='Boiler')

  # All storage charge states
  results.plot.variable('charge_state')

  # With aggregation
  results.plot.variable('flow_rate', aggregate='sum')

  Key features:
  - Searches all elements for variables matching the pattern
  - Filter with include/exclude on element names
  - Supports aggregation, faceting, and animation
  - Returns Dataset with element names as variables

  results.plot.duration_curve(variables)

  Plots load duration curves (sorted time series) showing utilization patterns.

  # Single variable
  results.plot.duration_curve('Boiler(Q_th)|flow_rate')

  # Multiple variables
  results.plot.duration_curve(['CHP|on', 'Boiler|on'])

  # Normalized x-axis (0-100%)
  results.plot.duration_curve('demand', normalize=True)

  Key features:
  - Sorts values from highest to lowest
  - Shows how often each power level is reached
  - normalize=True shows percentage of time on x-axis
  - Returns Dataset with duration_hours or duration_pct dimension

* Fix duration curve

* Fix duration curve

* Fix duration curve

* Fix duration curve

* xr.apply_ufunc to sort along the time axis while preserving all other dimensions automatically

* ⏺ The example runs successfully. Now let me summarize the fixes:

  Summary of Fixes

  I addressed the actionable code review comments from CodeRabbitAI:

  1. Documentation Issue - reshape parameter name ✓ (No fix needed)

  The CodeRabbitAI comment was incorrect. The public API parameter in PlotAccessor.heatmap() is correctly named reshape (line 335). The reshape_time parameter exists in the lower-level heatmap_with_plotly function, but the documentation correctly shows the public API parameter.

  2. Fixed simple_example.py (lines 129-130)

  Problem: The example called balance() and duration_curve() without required arguments, which would cause TypeError at runtime.

  Fix: Added the required arguments:
  - optimization.results.plot.balance('Fernwärme') - specifying the bus to plot
  - optimization.results.plot.duration_curve('Boiler(Q_th)|flow_rate') - specifying the variable to plot

  3. Fixed variable collision in plot_accessors.py (lines 985-988)

  Problem: When building the Dataset in the variable() method, using element names as keys could cause collisions if multiple variables from the same element matched the pattern (e.g., 'Boiler|flow_rate' and 'Boiler|flow_rate_max' would both map to 'Boiler', with the latter silently overwriting the former).

  Fix: Changed to use the full variable names as keys instead of just element names:
  ds = xr.Dataset({var_name: self._results.solution[var_name] for var_name in filtered_vars})

  All tests pass (40 passed, 1 skipped) and the example runs successfully.

* make variable names public in results

* Fix sankey

* Fix effects()

* Fix effects

* Remove bargaps

* made faceting consistent across all plot methods:

  | Method           | facet_col                                 | facet_row                   |
  |------------------|-------------------------------------------|-----------------------------|
  | balance()        | 'scenario'                                | 'period'                    |
  | heatmap()        | 'scenario'                                | 'period'                    |
  | storage()        | 'scenario'                                | 'period'                    |
  | flows()          | 'scenario'                                | 'period'                    |
  | effects()        | 'scenario'                                | 'period'                    |
  | variable()       | 'scenario'                                | 'period'                    |
  | duration_curve() | 'scenario'                                | 'period' (already had this) |
  | compare()        | N/A (uses its own mode='overlay'/'facet') |                             |
  | sankey()         | N/A (aggregates to single diagram)        |                             |

  Removed animate_by parameter from all methods

* Update storage method

* Remove mode parameter for simpli
  | Method           | Default mode                                      |
  |------------------|---------------------------------------------------|
  | balance()        | stacked_bar                                       |
  | storage()        | stacked_bar (flows) + line (charge state overlay) |
  | flows()          | line                                              |
  | variable()       | line                                              |
  | duration_curve() | line                                              |
  | effects()        | bar                                               |

* Make plotting_accessors.py more self contained

* Use faster to_long()

* Add 0-dim case

* sankey diagram now properly handles scenarios and periods:

  Changes made:
  1. Period aggregation with weights: Uses period_weights from flow_system to properly weight periods by their duration
  2. Scenario aggregation with weights: Uses normalized scenario_weights to compute a weighted average across scenarios
  3. Selection support: Users can filter specific scenarios/periods via select parameter before aggregation

  Weighting logic:
  - Periods (for aggregate='sum'): (da * period_weights).sum(dim='period') - this gives the total energy across all periods weighted by their duration
  - Periods (for aggregate='mean'): (da * period_weights).sum(dim='period') / period_weights.sum() - weighted mean
  - Scenarios: Always uses normalized weights (sum to 1) for weighted averaging, since scenarios represent probability-weighted alternatives

* Add colors to sankey

* Add sizes

* Add size filtering

* Include storage sizes

* Remove storage sizes

* Add charge state and status accessor

* Summary of Changes

  1. Added new methods to PlotAccessor (plot_accessors.py)

  charge_states() (line 658):
  - Returns a Dataset with each storage's charge state as a variable
  - Supports filtering with include/exclude parameters
  - Default plot: line chart

  on_states() (line 753):
  - Returns a Dataset with each component's |status variable
  - Supports filtering with include/exclude parameters
  - Default plot: heatmap (good for binary data visualization)

  2. Added data building helper functions (plot_accessors.py)

  build_flow_rates(results) (line 315):
  - Builds a DataArray containing flow rates for all flows
  - Used internally by PlotAccessor methods

  build_flow_hours(results) (line 333):
  - Builds a DataArray containing flow hours for all flows

  build_sizes(results) (line 347):
  - Builds a DataArray containing sizes for all flows

  _filter_dataarray_by_coord(da, **kwargs) (line 284):
  - Helper for filtering DataArrays by coordinate values

  3. Deprecated old Results methods (results.py)

  The following methods now emit DeprecationWarning:
  - results.flow_rates() → Use results.plot.flows(plot=False).data
  - results.flow_hours() → Use results.plot.flows(unit='flow_hours', plot=False).data
  - results.sizes() → Use results.plot.sizes(plot=False).data

  4. Updated PlotAccessor methods to use new helpers

  - flows() now uses build_flow_rates() / build_flow_hours() directly
  - sizes() now uses build_sizes() directly
  - sankey() now uses build_flow_hours() directly

  This ensures the deprecation warnings only fire when users directly call the old methods, not when using the plot accessor

* 1. New methods added to PlotAccessor

  - charge_states(): Returns Dataset with all storage charge states
  - on_states(): Returns Dataset with all component status variables (heatmap display)

  2. Data building helper functions (plot_accessors.py)

  - build_flow_rates(results): Builds DataArray of flow rates
  - build_flow_hours(results): Builds DataArray of flow hours
  - build_sizes(results): Builds DataArray of sizes
  - _filter_dataarray_by_coord(da, **kwargs): Filter helper
  - _assign_flow_coords(da, results): Add flow coordinates

  3. Caching in PlotAccessor

  Added lazy-cached properties for expensive computations:
  - _all_flow_rates - cached DataArray of all flow rates
  - _all_flow_hours - cached DataArray of all flow hours
  - _all_sizes - cached DataArray of all sizes
  - _all_charge_states - cached Dataset of all storage charge states
  - _all_status_vars - cached Dataset of all status variables

  4. Deprecated methods in Results class

  Added deprecation warnings to:
  - results.flow_rates() → Use results.plot.flows(plot=False).data
  - results.flow_hours() → Use results.plot.flows(unit='flow_hours', plot=False).data
  - results.sizes() → Use results.plot.sizes(plot=False).data

  5. Updated PlotAccessor methods to use cached properties

  - flows() uses _all_flow_rates / _all_flow_hours
  - sankey() uses _all_flow_hours
  - sizes() uses _all_sizes
  - charge_states() uses _all_charge_states
  - on_states() uses _all_status_vars

* Move deprectated functionality into results.py instead of porting to the new module

* Revert to simply deprectae old methods without forwarding to new code

* Remove planning file

* Update plotting methods for new datasets

* 1. Renamed data properties in PlotAccessor to use all_ prefix:
    - all_flow_rates - All flow rates as Dataset
    - all_flow_hours - All flow hours as Dataset
    - all_sizes - All flow sizes as Dataset
    - all_charge_states - All storage charge states as Dataset
    - all_on_states - All component on/off status as Dataset
  2. Updated internal references - All usages in flows(), sankey(), sizes(), charge_states(), and on_states() methods now use the new names.
  3. Updated deprecation messages in results.py to point to the new API:
    - results.flow_rates() → results.plot.all_flow_rates
    - results.flow_hours() → results.plot.all_flow_hours
    - results.sizes() → results.plot.all_sizes
  4. Updated docstring examples in PlotAccessor to use the new all_* names.

* Update deprecations messages

* Update deprecations messages

* Thsi seems much better.

* Updaet docstrings and variable name generation in plotting acessor

* Change __ to _ in private dataset caching

* Revert breaking io changes

* New solution storing interface

* Add new focused statistics and plot accessors

* Renamed all properties:
  - all_flow_rates → flow_rates
  - all_flow_hours → flow_hours
  - all_sizes → sizes
  - all_charge_states → charge_states

* Cache Statistics

* Invalidate caches

* Add effect related statistics

* Simplify statistics accessor to rely on flow_system directly instead of solution attrs

* Fix heatma fallback for 1D Data

* Add topology accessor

* All deprecation warnings in the codebase now consistently use the format will be removed in v{DEPRECATION_REMOVAL_VERSION}.

* Update tests

* created comprehensive documentation for all FlowSystem accessors

* Update results documentation

* Update results documentation

* Update effect statistics

* Update effect statistics

* Update effect statistics

* Add mkdocs plotly plugin

* Add section about custom constraints

* documentation updates:

  docs/user-guide/results/index.md:
  - Updated table to replace effects_per_component with temporal_effects, periodic_effects, total_effects, and effect_share_factors
  - Fixed flow_hours['Boiler(Q_th)|flow_rate'] → flow_hours['Boiler(Q_th)']
  - Fixed sizes['Boiler(Q_th)|size'] → sizes['Boiler(Q_th)']
  - Replaced effects_per_component example with new effect properties and groupby examples
  - Updated complete example to use total_effects

  docs/user-guide/results-plotting.md:
  - Fixed colors example from 'Boiler(Q_th)|flow_rate' → 'Boiler(Q_th)'
  - Fixed duration_curve examples to use clean labels

  docs/user-guide/migration-guide-v6.md:
  - Added new "Statistics Accessor" section explaining the clean labels and new effect properties

* implemented the effects() method in StatisticsPlotAccessor at flixopt/statistics_accessor.py:1132-1258.

  Summary of what was done:

  1. Implemented effects() method in StatisticsPlotAccessor class that was missing but documented
    - Takes aspect parameter: 'total', 'temporal', or 'periodic'
    - Takes effect parameter to filter to a specific effect (e.g., 'costs', 'CO2')
    - Takes by parameter: 'component' or 'time' for grouping
    - Supports all standard plotting parameters: select, colors, facet_col, facet_row, show
    - Returns PlotResult with both data and figure
  2. Verified the implementation works with all parameter combinations:
    - Default call: flow_system.statistics.plot.effects()
    - Specific effect: flow_system.statistics.plot.effects(effect='costs')
    - Temporal aspect: flow_system.statistics.plot.effects(aspect='temporal')
    - Temporal by time: flow_system.statistics.plot.effects(aspect='temporal', by='time')
    - Periodic aspect: flow_system.statistics.plot.effects(aspect='periodic')

* Remove intermediate plot accessor

* 1. pyproject.toml: Removed duplicate mkdocs-plotly-plugin>=0.1.3 entry (kept the exact pin ==0.1.3)
  2. flixopt/plotting.py: Fixed dimension name consistency by using squeezed_data.name instead of data.name in the fallback heatmap logic
  3. flixopt/statistics_accessor.py:
    - Fixed _dataset_to_long_df() to only use coordinates that are actually present as columns after reset_index()
    - Fixed the nested loop inefficiency with include_flows by pre-computing the flows list outside the loop
    - (Previously fixed) Fixed asymmetric NaN handling in validation check

* _create_effects_dataset method in statistics_accessor.py was simplified:

  1. Detect contributors from solution data variables instead of assuming they're only flows
    - Uses regex pattern to find {contributor}->{effect}(temporal|periodic) variables
    - Contributors can be flows OR components (e.g., components with effects_per_active_hour)
  2. Exclude effect-to-effect shares
    - Filters out contributors whose base name matches any effect label
    - For example, costs(temporal) is excluded because costs is an effect label
    - These intermediate shares are already included in the computation
  3. Removed the unused _compute_effect_total method
    - The new simplified implementation directly looks up shares from the solution
    - Uses effect_share_factors for conversion between effects
  4. Key insight from user: The solution already contains properly computed share values including all effect-to-effect conversions. The computation uses conversion
  factors because derived effects (like Effect1 which shares 0.5 from costs) don't have direct {flow}->Effect1(temporal) variables - only the source effect shares exist
   ({flow}->costs(temporal)).

* Update docs

* Improve to_netcdf method

* Update examples

* Fix IIS computaion flag

* Fix examples

* Fix faceting in heatmap and use period as facet col everywhere

* Inline plotting methods to deprecate plotting.py

* Fix test

* Simplify Color Management

* ColorType is now defined in color_processing.py and imported into statistics_accessor.py.

* Fix ColorType typing

* Add color accessor

* Ensure io

* Add carrier class

* implemented Carrier as a proper Interface subclass with container support. Here's what was done:

  1. Carrier class (flixopt/carrier.py)

  - Now inherits from Interface for serialization capabilities
  - Has transform_data() method (no-op since carriers have no time-series data)
  - Has label property for container keying
  - Maintains equality comparison with both Carrier objects and strings

  2. CarrierContainer class (flixopt/carrier.py)

  - Inherits from ContainerMixin['Carrier']
  - Provides dict-like access with nice repr and error messages
  - Uses carrier.name for keying

  3. FlowSystem updates (flixopt/flow_system.py)

  - _carriers is now a CarrierContainer instead of a plain dict
  - carriers property returns the CarrierContainer
  - add_carrier() uses the container's add() method
  - Serialization updated to include carriers in to_dataset() and restore them in from_dataset()

  4. Exports (flixopt/__init__.py)

  - Both Carrier and CarrierContainer are now exported

* Inline plotting methods to deprecate plotting.py (#508)

* Inline plotting methods to deprecate plotting.py

* Fix test

* Simplify Color Management

* ColorType is now defined in color_processing.py and imported into statistics_accessor.py.

* Fix ColorType typing

* statistics_accessor.py - Heatmap colors type safety (lines 121-148, 820-853)

  - Changed _heatmap_figure() parameter type from colors: ColorType = None to colors: str | list[str] | None = None
  - Changed heatmap() method parameter type similarly
  - Updated docstrings to clarify that dicts are not supported for heatmaps since px.imshow's color_continuous_scale only accepts colorscale names or lists

  2. statistics_accessor.py - Use configured qualitative colorscale (lines 284, 315)

  - Updated _create_stacked_bar() to use CONFIG.Plotting.default_qualitative_colorscale as the default colorscale
  - Updated _create_line() similarly
  - This ensures user-configured CONFIG.Plotting.default_qualitative_colorscale affects all bar/line plots consistently

  3. topology_accessor.py - Path type alignment (lines 219-222)

  - Added normalization of path=False to None before calling _plot_network()
  - This resolves the type mismatch where TopologyAccessor.plot() accepts bool | str | Path but _plot_network() only accepts str | Path | None

* fix usage if index name in aggregation plot

* Add to docs

* Improve carrier colors and defaults

* Update default carriers and colors

* Update config

* Update config

* Move default carriers to config.py

* Change default carrier handling

* Add color handling

* Rmeove meta_data color handling

* Add carrierst to examples

* Improve plotting acessor

* Improve _resolve_variable_names

* Improve _resolve_variable_names

* Simplify coloring and remove color accessor

* Add connected_and_transformed handling

* Improve error message in container

* Methods moved to TransformAccessor (transform_accessor.py):
    - sel() - select by label
    - isel() - select by integer index
    - resample() - resample time dimension
    - Helper methods: _dataset_sel, _dataset_isel, _dataset_resample, _resample_by_dimension_groups
  2. Solution is dropped: All transform methods return a new FlowSystem with no solution - the user must re-optimize the transformed system.
  3. Deprecation warnings: The old flow_system.sel(), flow_system.isel(), and flow_system.resample() methods now emit deprecation warnings and forward to the new
  TransformAccessor methods.
  4. Backward compatible: Existing code still works, just with deprecation warnings.

* Documentation updated

* Re-add _dataset_sel and other helper methods for proper deprectation. ALso fix new methods to be classmethods

* BUGFIX: Carrier from dataset

* Update docs
FBumann added a commit that referenced this pull request Dec 10, 2025
* Add planning doc

* Finalize planning

* Add plotting acessor

* Add plotting acessor

* Add tests

* Improve

* Improve

* Update docs

* Updated the plotting API so that .data always returns xarray DataArray or Dataset instead of pandas DataFrame.

* All .data now returns xr.Dataset consistently.

* Fixed Inconsistencies and Unused Parameters

* New Plot Accessors

  results.plot.variable(pattern)

  Plots the same variable type across multiple elements for easy comparison.

  # All binary operation states across all components
  results.plot.variable('on')

  # All flow_rates, filtered to Boiler-related elements
  results.plot.variable('flow_rate', include='Boiler')

  # All storage charge states
  results.plot.variable('charge_state')

  # With aggregation
  results.plot.variable('flow_rate', aggregate='sum')

  Key features:
  - Searches all elements for variables matching the pattern
  - Filter with include/exclude on element names
  - Supports aggregation, faceting, and animation
  - Returns Dataset with element names as variables

  results.plot.duration_curve(variables)

  Plots load duration curves (sorted time series) showing utilization patterns.

  # Single variable
  results.plot.duration_curve('Boiler(Q_th)|flow_rate')

  # Multiple variables
  results.plot.duration_curve(['CHP|on', 'Boiler|on'])

  # Normalized x-axis (0-100%)
  results.plot.duration_curve('demand', normalize=True)

  Key features:
  - Sorts values from highest to lowest
  - Shows how often each power level is reached
  - normalize=True shows percentage of time on x-axis
  - Returns Dataset with duration_hours or duration_pct dimension

* Fix duration curve

* Fix duration curve

* Fix duration curve

* Fix duration curve

* xr.apply_ufunc to sort along the time axis while preserving all other dimensions automatically

* ⏺ The example runs successfully. Now let me summarize the fixes:

  Summary of Fixes

  I addressed the actionable code review comments from CodeRabbitAI:

  1. Documentation Issue - reshape parameter name ✓ (No fix needed)

  The CodeRabbitAI comment was incorrect. The public API parameter in PlotAccessor.heatmap() is correctly named reshape (line 335). The reshape_time parameter exists in the lower-level heatmap_with_plotly function, but the documentation correctly shows the public API parameter.

  2. Fixed simple_example.py (lines 129-130)

  Problem: The example called balance() and duration_curve() without required arguments, which would cause TypeError at runtime.

  Fix: Added the required arguments:
  - optimization.results.plot.balance('Fernwärme') - specifying the bus to plot
  - optimization.results.plot.duration_curve('Boiler(Q_th)|flow_rate') - specifying the variable to plot

  3. Fixed variable collision in plot_accessors.py (lines 985-988)

  Problem: When building the Dataset in the variable() method, using element names as keys could cause collisions if multiple variables from the same element matched the pattern (e.g., 'Boiler|flow_rate' and 'Boiler|flow_rate_max' would both map to 'Boiler', with the latter silently overwriting the former).

  Fix: Changed to use the full variable names as keys instead of just element names:
  ds = xr.Dataset({var_name: self._results.solution[var_name] for var_name in filtered_vars})

  All tests pass (40 passed, 1 skipped) and the example runs successfully.

* make variable names public in results

* Fix sankey

* Fix effects()

* Fix effects

* Remove bargaps

* made faceting consistent across all plot methods:

  | Method           | facet_col                                 | facet_row                   |
  |------------------|-------------------------------------------|-----------------------------|
  | balance()        | 'scenario'                                | 'period'                    |
  | heatmap()        | 'scenario'                                | 'period'                    |
  | storage()        | 'scenario'                                | 'period'                    |
  | flows()          | 'scenario'                                | 'period'                    |
  | effects()        | 'scenario'                                | 'period'                    |
  | variable()       | 'scenario'                                | 'period'                    |
  | duration_curve() | 'scenario'                                | 'period' (already had this) |
  | compare()        | N/A (uses its own mode='overlay'/'facet') |                             |
  | sankey()         | N/A (aggregates to single diagram)        |                             |

  Removed animate_by parameter from all methods

* Update storage method

* Remove mode parameter for simpli
  | Method           | Default mode                                      |
  |------------------|---------------------------------------------------|
  | balance()        | stacked_bar                                       |
  | storage()        | stacked_bar (flows) + line (charge state overlay) |
  | flows()          | line                                              |
  | variable()       | line                                              |
  | duration_curve() | line                                              |
  | effects()        | bar                                               |

* Make plotting_accessors.py more self contained

* Use faster to_long()

* Add 0-dim case

* sankey diagram now properly handles scenarios and periods:

  Changes made:
  1. Period aggregation with weights: Uses period_weights from flow_system to properly weight periods by their duration
  2. Scenario aggregation with weights: Uses normalized scenario_weights to compute a weighted average across scenarios
  3. Selection support: Users can filter specific scenarios/periods via select parameter before aggregation

  Weighting logic:
  - Periods (for aggregate='sum'): (da * period_weights).sum(dim='period') - this gives the total energy across all periods weighted by their duration
  - Periods (for aggregate='mean'): (da * period_weights).sum(dim='period') / period_weights.sum() - weighted mean
  - Scenarios: Always uses normalized weights (sum to 1) for weighted averaging, since scenarios represent probability-weighted alternatives

* Add colors to sankey

* Add sizes

* Add size filtering

* Include storage sizes

* Remove storage sizes

* Add charge state and status accessor

* Summary of Changes

  1. Added new methods to PlotAccessor (plot_accessors.py)

  charge_states() (line 658):
  - Returns a Dataset with each storage's charge state as a variable
  - Supports filtering with include/exclude parameters
  - Default plot: line chart

  on_states() (line 753):
  - Returns a Dataset with each component's |status variable
  - Supports filtering with include/exclude parameters
  - Default plot: heatmap (good for binary data visualization)

  2. Added data building helper functions (plot_accessors.py)

  build_flow_rates(results) (line 315):
  - Builds a DataArray containing flow rates for all flows
  - Used internally by PlotAccessor methods

  build_flow_hours(results) (line 333):
  - Builds a DataArray containing flow hours for all flows

  build_sizes(results) (line 347):
  - Builds a DataArray containing sizes for all flows

  _filter_dataarray_by_coord(da, **kwargs) (line 284):
  - Helper for filtering DataArrays by coordinate values

  3. Deprecated old Results methods (results.py)

  The following methods now emit DeprecationWarning:
  - results.flow_rates() → Use results.plot.flows(plot=False).data
  - results.flow_hours() → Use results.plot.flows(unit='flow_hours', plot=False).data
  - results.sizes() → Use results.plot.sizes(plot=False).data

  4. Updated PlotAccessor methods to use new helpers

  - flows() now uses build_flow_rates() / build_flow_hours() directly
  - sizes() now uses build_sizes() directly
  - sankey() now uses build_flow_hours() directly

  This ensures the deprecation warnings only fire when users directly call the old methods, not when using the plot accessor

* 1. New methods added to PlotAccessor

  - charge_states(): Returns Dataset with all storage charge states
  - on_states(): Returns Dataset with all component status variables (heatmap display)

  2. Data building helper functions (plot_accessors.py)

  - build_flow_rates(results): Builds DataArray of flow rates
  - build_flow_hours(results): Builds DataArray of flow hours
  - build_sizes(results): Builds DataArray of sizes
  - _filter_dataarray_by_coord(da, **kwargs): Filter helper
  - _assign_flow_coords(da, results): Add flow coordinates

  3. Caching in PlotAccessor

  Added lazy-cached properties for expensive computations:
  - _all_flow_rates - cached DataArray of all flow rates
  - _all_flow_hours - cached DataArray of all flow hours
  - _all_sizes - cached DataArray of all sizes
  - _all_charge_states - cached Dataset of all storage charge states
  - _all_status_vars - cached Dataset of all status variables

  4. Deprecated methods in Results class

  Added deprecation warnings to:
  - results.flow_rates() → Use results.plot.flows(plot=False).data
  - results.flow_hours() → Use results.plot.flows(unit='flow_hours', plot=False).data
  - results.sizes() → Use results.plot.sizes(plot=False).data

  5. Updated PlotAccessor methods to use cached properties

  - flows() uses _all_flow_rates / _all_flow_hours
  - sankey() uses _all_flow_hours
  - sizes() uses _all_sizes
  - charge_states() uses _all_charge_states
  - on_states() uses _all_status_vars

* Move deprectated functionality into results.py instead of porting to the new module

* Revert to simply deprectae old methods without forwarding to new code

* Remove planning file

* Update plotting methods for new datasets

* 1. Renamed data properties in PlotAccessor to use all_ prefix:
    - all_flow_rates - All flow rates as Dataset
    - all_flow_hours - All flow hours as Dataset
    - all_sizes - All flow sizes as Dataset
    - all_charge_states - All storage charge states as Dataset
    - all_on_states - All component on/off status as Dataset
  2. Updated internal references - All usages in flows(), sankey(), sizes(), charge_states(), and on_states() methods now use the new names.
  3. Updated deprecation messages in results.py to point to the new API:
    - results.flow_rates() → results.plot.all_flow_rates
    - results.flow_hours() → results.plot.all_flow_hours
    - results.sizes() → results.plot.all_sizes
  4. Updated docstring examples in PlotAccessor to use the new all_* names.

* Update deprecations messages

* Update deprecations messages

* Thsi seems much better.

* Updaet docstrings and variable name generation in plotting acessor

* Change __ to _ in private dataset caching

* Revert breaking io changes

* New solution storing interface

* Add new focused statistics and plot accessors

* Renamed all properties:
  - all_flow_rates → flow_rates
  - all_flow_hours → flow_hours
  - all_sizes → sizes
  - all_charge_states → charge_states

* Cache Statistics

* Invalidate caches

* Add effect related statistics

* Simplify statistics accessor to rely on flow_system directly instead of solution attrs

* Fix heatma fallback for 1D Data

* Add topology accessor

* All deprecation warnings in the codebase now consistently use the format will be removed in v{DEPRECATION_REMOVAL_VERSION}.

* Update tests

* created comprehensive documentation for all FlowSystem accessors

* Update results documentation

* Update results documentation

* Update effect statistics

* Update effect statistics

* Update effect statistics

* Add mkdocs plotly plugin

* Add section about custom constraints

* documentation updates:

  docs/user-guide/results/index.md:
  - Updated table to replace effects_per_component with temporal_effects, periodic_effects, total_effects, and effect_share_factors
  - Fixed flow_hours['Boiler(Q_th)|flow_rate'] → flow_hours['Boiler(Q_th)']
  - Fixed sizes['Boiler(Q_th)|size'] → sizes['Boiler(Q_th)']
  - Replaced effects_per_component example with new effect properties and groupby examples
  - Updated complete example to use total_effects

  docs/user-guide/results-plotting.md:
  - Fixed colors example from 'Boiler(Q_th)|flow_rate' → 'Boiler(Q_th)'
  - Fixed duration_curve examples to use clean labels

  docs/user-guide/migration-guide-v6.md:
  - Added new "Statistics Accessor" section explaining the clean labels and new effect properties

* implemented the effects() method in StatisticsPlotAccessor at flixopt/statistics_accessor.py:1132-1258.

  Summary of what was done:

  1. Implemented effects() method in StatisticsPlotAccessor class that was missing but documented
    - Takes aspect parameter: 'total', 'temporal', or 'periodic'
    - Takes effect parameter to filter to a specific effect (e.g., 'costs', 'CO2')
    - Takes by parameter: 'component' or 'time' for grouping
    - Supports all standard plotting parameters: select, colors, facet_col, facet_row, show
    - Returns PlotResult with both data and figure
  2. Verified the implementation works with all parameter combinations:
    - Default call: flow_system.statistics.plot.effects()
    - Specific effect: flow_system.statistics.plot.effects(effect='costs')
    - Temporal aspect: flow_system.statistics.plot.effects(aspect='temporal')
    - Temporal by time: flow_system.statistics.plot.effects(aspect='temporal', by='time')
    - Periodic aspect: flow_system.statistics.plot.effects(aspect='periodic')

* Remove intermediate plot accessor

* 1. pyproject.toml: Removed duplicate mkdocs-plotly-plugin>=0.1.3 entry (kept the exact pin ==0.1.3)
  2. flixopt/plotting.py: Fixed dimension name consistency by using squeezed_data.name instead of data.name in the fallback heatmap logic
  3. flixopt/statistics_accessor.py:
    - Fixed _dataset_to_long_df() to only use coordinates that are actually present as columns after reset_index()
    - Fixed the nested loop inefficiency with include_flows by pre-computing the flows list outside the loop
    - (Previously fixed) Fixed asymmetric NaN handling in validation check

* _create_effects_dataset method in statistics_accessor.py was simplified:

  1. Detect contributors from solution data variables instead of assuming they're only flows
    - Uses regex pattern to find {contributor}->{effect}(temporal|periodic) variables
    - Contributors can be flows OR components (e.g., components with effects_per_active_hour)
  2. Exclude effect-to-effect shares
    - Filters out contributors whose base name matches any effect label
    - For example, costs(temporal) is excluded because costs is an effect label
    - These intermediate shares are already included in the computation
  3. Removed the unused _compute_effect_total method
    - The new simplified implementation directly looks up shares from the solution
    - Uses effect_share_factors for conversion between effects
  4. Key insight from user: The solution already contains properly computed share values including all effect-to-effect conversions. The computation uses conversion
  factors because derived effects (like Effect1 which shares 0.5 from costs) don't have direct {flow}->Effect1(temporal) variables - only the source effect shares exist
   ({flow}->costs(temporal)).

* Update docs

* Improve to_netcdf method

* Update examples

* Fix IIS computaion flag

* Fix examples

* Fix faceting in heatmap and use period as facet col everywhere

* Inline plotting methods to deprecate plotting.py (#508)

* Inline plotting methods to deprecate plotting.py

* Fix test

* Simplify Color Management

* ColorType is now defined in color_processing.py and imported into statistics_accessor.py.

* Fix ColorType typing

* statistics_accessor.py - Heatmap colors type safety (lines 121-148, 820-853)

  - Changed _heatmap_figure() parameter type from colors: ColorType = None to colors: str | list[str] | None = None
  - Changed heatmap() method parameter type similarly
  - Updated docstrings to clarify that dicts are not supported for heatmaps since px.imshow's color_continuous_scale only accepts colorscale names or lists

  2. statistics_accessor.py - Use configured qualitative colorscale (lines 284, 315)

  - Updated _create_stacked_bar() to use CONFIG.Plotting.default_qualitative_colorscale as the default colorscale
  - Updated _create_line() similarly
  - This ensures user-configured CONFIG.Plotting.default_qualitative_colorscale affects all bar/line plots consistently

  3. topology_accessor.py - Path type alignment (lines 219-222)

  - Added normalization of path=False to None before calling _plot_network()
  - This resolves the type mismatch where TopologyAccessor.plot() accepts bool | str | Path but _plot_network() only accepts str | Path | None

* fix usage if index name in aggregation plot
FBumann added a commit that referenced this pull request Dec 10, 2025
* Add planning doc

* Finalize planning

* Add plotting acessor

* Add plotting acessor

* Add tests

* Improve

* Improve

* Update docs

* Updated the plotting API so that .data always returns xarray DataArray or Dataset instead of pandas DataFrame.

* All .data now returns xr.Dataset consistently.

* Fixed Inconsistencies and Unused Parameters

* New Plot Accessors

  results.plot.variable(pattern)

  Plots the same variable type across multiple elements for easy comparison.

  # All binary operation states across all components
  results.plot.variable('on')

  # All flow_rates, filtered to Boiler-related elements
  results.plot.variable('flow_rate', include='Boiler')

  # All storage charge states
  results.plot.variable('charge_state')

  # With aggregation
  results.plot.variable('flow_rate', aggregate='sum')

  Key features:
  - Searches all elements for variables matching the pattern
  - Filter with include/exclude on element names
  - Supports aggregation, faceting, and animation
  - Returns Dataset with element names as variables

  results.plot.duration_curve(variables)

  Plots load duration curves (sorted time series) showing utilization patterns.

  # Single variable
  results.plot.duration_curve('Boiler(Q_th)|flow_rate')

  # Multiple variables
  results.plot.duration_curve(['CHP|on', 'Boiler|on'])

  # Normalized x-axis (0-100%)
  results.plot.duration_curve('demand', normalize=True)

  Key features:
  - Sorts values from highest to lowest
  - Shows how often each power level is reached
  - normalize=True shows percentage of time on x-axis
  - Returns Dataset with duration_hours or duration_pct dimension

* Fix duration curve

* Fix duration curve

* Fix duration curve

* Fix duration curve

* xr.apply_ufunc to sort along the time axis while preserving all other dimensions automatically

* ⏺ The example runs successfully. Now let me summarize the fixes:

  Summary of Fixes

  I addressed the actionable code review comments from CodeRabbitAI:

  1. Documentation Issue - reshape parameter name ✓ (No fix needed)

  The CodeRabbitAI comment was incorrect. The public API parameter in PlotAccessor.heatmap() is correctly named reshape (line 335). The reshape_time parameter exists in the lower-level heatmap_with_plotly function, but the documentation correctly shows the public API parameter.

  2. Fixed simple_example.py (lines 129-130)

  Problem: The example called balance() and duration_curve() without required arguments, which would cause TypeError at runtime.

  Fix: Added the required arguments:
  - optimization.results.plot.balance('Fernwärme') - specifying the bus to plot
  - optimization.results.plot.duration_curve('Boiler(Q_th)|flow_rate') - specifying the variable to plot

  3. Fixed variable collision in plot_accessors.py (lines 985-988)

  Problem: When building the Dataset in the variable() method, using element names as keys could cause collisions if multiple variables from the same element matched the pattern (e.g., 'Boiler|flow_rate' and 'Boiler|flow_rate_max' would both map to 'Boiler', with the latter silently overwriting the former).

  Fix: Changed to use the full variable names as keys instead of just element names:
  ds = xr.Dataset({var_name: self._results.solution[var_name] for var_name in filtered_vars})

  All tests pass (40 passed, 1 skipped) and the example runs successfully.

* make variable names public in results

* Fix sankey

* Fix effects()

* Fix effects

* Remove bargaps

* made faceting consistent across all plot methods:

  | Method           | facet_col                                 | facet_row                   |
  |------------------|-------------------------------------------|-----------------------------|
  | balance()        | 'scenario'                                | 'period'                    |
  | heatmap()        | 'scenario'                                | 'period'                    |
  | storage()        | 'scenario'                                | 'period'                    |
  | flows()          | 'scenario'                                | 'period'                    |
  | effects()        | 'scenario'                                | 'period'                    |
  | variable()       | 'scenario'                                | 'period'                    |
  | duration_curve() | 'scenario'                                | 'period' (already had this) |
  | compare()        | N/A (uses its own mode='overlay'/'facet') |                             |
  | sankey()         | N/A (aggregates to single diagram)        |                             |

  Removed animate_by parameter from all methods

* Update storage method

* Remove mode parameter for simpli
  | Method           | Default mode                                      |
  |------------------|---------------------------------------------------|
  | balance()        | stacked_bar                                       |
  | storage()        | stacked_bar (flows) + line (charge state overlay) |
  | flows()          | line                                              |
  | variable()       | line                                              |
  | duration_curve() | line                                              |
  | effects()        | bar                                               |

* Make plotting_accessors.py more self contained

* Use faster to_long()

* Add 0-dim case

* sankey diagram now properly handles scenarios and periods:

  Changes made:
  1. Period aggregation with weights: Uses period_weights from flow_system to properly weight periods by their duration
  2. Scenario aggregation with weights: Uses normalized scenario_weights to compute a weighted average across scenarios
  3. Selection support: Users can filter specific scenarios/periods via select parameter before aggregation

  Weighting logic:
  - Periods (for aggregate='sum'): (da * period_weights).sum(dim='period') - this gives the total energy across all periods weighted by their duration
  - Periods (for aggregate='mean'): (da * period_weights).sum(dim='period') / period_weights.sum() - weighted mean
  - Scenarios: Always uses normalized weights (sum to 1) for weighted averaging, since scenarios represent probability-weighted alternatives

* Add colors to sankey

* Add sizes

* Add size filtering

* Include storage sizes

* Remove storage sizes

* Add charge state and status accessor

* Summary of Changes

  1. Added new methods to PlotAccessor (plot_accessors.py)

  charge_states() (line 658):
  - Returns a Dataset with each storage's charge state as a variable
  - Supports filtering with include/exclude parameters
  - Default plot: line chart

  on_states() (line 753):
  - Returns a Dataset with each component's |status variable
  - Supports filtering with include/exclude parameters
  - Default plot: heatmap (good for binary data visualization)

  2. Added data building helper functions (plot_accessors.py)

  build_flow_rates(results) (line 315):
  - Builds a DataArray containing flow rates for all flows
  - Used internally by PlotAccessor methods

  build_flow_hours(results) (line 333):
  - Builds a DataArray containing flow hours for all flows

  build_sizes(results) (line 347):
  - Builds a DataArray containing sizes for all flows

  _filter_dataarray_by_coord(da, **kwargs) (line 284):
  - Helper for filtering DataArrays by coordinate values

  3. Deprecated old Results methods (results.py)

  The following methods now emit DeprecationWarning:
  - results.flow_rates() → Use results.plot.flows(plot=False).data
  - results.flow_hours() → Use results.plot.flows(unit='flow_hours', plot=False).data
  - results.sizes() → Use results.plot.sizes(plot=False).data

  4. Updated PlotAccessor methods to use new helpers

  - flows() now uses build_flow_rates() / build_flow_hours() directly
  - sizes() now uses build_sizes() directly
  - sankey() now uses build_flow_hours() directly

  This ensures the deprecation warnings only fire when users directly call the old methods, not when using the plot accessor

* 1. New methods added to PlotAccessor

  - charge_states(): Returns Dataset with all storage charge states
  - on_states(): Returns Dataset with all component status variables (heatmap display)

  2. Data building helper functions (plot_accessors.py)

  - build_flow_rates(results): Builds DataArray of flow rates
  - build_flow_hours(results): Builds DataArray of flow hours
  - build_sizes(results): Builds DataArray of sizes
  - _filter_dataarray_by_coord(da, **kwargs): Filter helper
  - _assign_flow_coords(da, results): Add flow coordinates

  3. Caching in PlotAccessor

  Added lazy-cached properties for expensive computations:
  - _all_flow_rates - cached DataArray of all flow rates
  - _all_flow_hours - cached DataArray of all flow hours
  - _all_sizes - cached DataArray of all sizes
  - _all_charge_states - cached Dataset of all storage charge states
  - _all_status_vars - cached Dataset of all status variables

  4. Deprecated methods in Results class

  Added deprecation warnings to:
  - results.flow_rates() → Use results.plot.flows(plot=False).data
  - results.flow_hours() → Use results.plot.flows(unit='flow_hours', plot=False).data
  - results.sizes() → Use results.plot.sizes(plot=False).data

  5. Updated PlotAccessor methods to use cached properties

  - flows() uses _all_flow_rates / _all_flow_hours
  - sankey() uses _all_flow_hours
  - sizes() uses _all_sizes
  - charge_states() uses _all_charge_states
  - on_states() uses _all_status_vars

* Move deprectated functionality into results.py instead of porting to the new module

* Revert to simply deprectae old methods without forwarding to new code

* Remove planning file

* Update plotting methods for new datasets

* 1. Renamed data properties in PlotAccessor to use all_ prefix:
    - all_flow_rates - All flow rates as Dataset
    - all_flow_hours - All flow hours as Dataset
    - all_sizes - All flow sizes as Dataset
    - all_charge_states - All storage charge states as Dataset
    - all_on_states - All component on/off status as Dataset
  2. Updated internal references - All usages in flows(), sankey(), sizes(), charge_states(), and on_states() methods now use the new names.
  3. Updated deprecation messages in results.py to point to the new API:
    - results.flow_rates() → results.plot.all_flow_rates
    - results.flow_hours() → results.plot.all_flow_hours
    - results.sizes() → results.plot.all_sizes
  4. Updated docstring examples in PlotAccessor to use the new all_* names.

* Update deprecations messages

* Update deprecations messages

* Thsi seems much better.

* Updaet docstrings and variable name generation in plotting acessor

* Change __ to _ in private dataset caching

* Revert breaking io changes

* New solution storing interface

* Add new focused statistics and plot accessors

* Renamed all properties:
  - all_flow_rates → flow_rates
  - all_flow_hours → flow_hours
  - all_sizes → sizes
  - all_charge_states → charge_states

* Cache Statistics

* Invalidate caches

* Add effect related statistics

* Simplify statistics accessor to rely on flow_system directly instead of solution attrs

* Fix heatma fallback for 1D Data

* Add topology accessor

* All deprecation warnings in the codebase now consistently use the format will be removed in v{DEPRECATION_REMOVAL_VERSION}.

* Update tests

* created comprehensive documentation for all FlowSystem accessors

* Update results documentation

* Update results documentation

* Update effect statistics

* Update effect statistics

* Update effect statistics

* Add mkdocs plotly plugin

* Add section about custom constraints

* documentation updates:

  docs/user-guide/results/index.md:
  - Updated table to replace effects_per_component with temporal_effects, periodic_effects, total_effects, and effect_share_factors
  - Fixed flow_hours['Boiler(Q_th)|flow_rate'] → flow_hours['Boiler(Q_th)']
  - Fixed sizes['Boiler(Q_th)|size'] → sizes['Boiler(Q_th)']
  - Replaced effects_per_component example with new effect properties and groupby examples
  - Updated complete example to use total_effects

  docs/user-guide/results-plotting.md:
  - Fixed colors example from 'Boiler(Q_th)|flow_rate' → 'Boiler(Q_th)'
  - Fixed duration_curve examples to use clean labels

  docs/user-guide/migration-guide-v6.md:
  - Added new "Statistics Accessor" section explaining the clean labels and new effect properties

* implemented the effects() method in StatisticsPlotAccessor at flixopt/statistics_accessor.py:1132-1258.

  Summary of what was done:

  1. Implemented effects() method in StatisticsPlotAccessor class that was missing but documented
    - Takes aspect parameter: 'total', 'temporal', or 'periodic'
    - Takes effect parameter to filter to a specific effect (e.g., 'costs', 'CO2')
    - Takes by parameter: 'component' or 'time' for grouping
    - Supports all standard plotting parameters: select, colors, facet_col, facet_row, show
    - Returns PlotResult with both data and figure
  2. Verified the implementation works with all parameter combinations:
    - Default call: flow_system.statistics.plot.effects()
    - Specific effect: flow_system.statistics.plot.effects(effect='costs')
    - Temporal aspect: flow_system.statistics.plot.effects(aspect='temporal')
    - Temporal by time: flow_system.statistics.plot.effects(aspect='temporal', by='time')
    - Periodic aspect: flow_system.statistics.plot.effects(aspect='periodic')

* Remove intermediate plot accessor

* 1. pyproject.toml: Removed duplicate mkdocs-plotly-plugin>=0.1.3 entry (kept the exact pin ==0.1.3)
  2. flixopt/plotting.py: Fixed dimension name consistency by using squeezed_data.name instead of data.name in the fallback heatmap logic
  3. flixopt/statistics_accessor.py:
    - Fixed _dataset_to_long_df() to only use coordinates that are actually present as columns after reset_index()
    - Fixed the nested loop inefficiency with include_flows by pre-computing the flows list outside the loop
    - (Previously fixed) Fixed asymmetric NaN handling in validation check

* _create_effects_dataset method in statistics_accessor.py was simplified:

  1. Detect contributors from solution data variables instead of assuming they're only flows
    - Uses regex pattern to find {contributor}->{effect}(temporal|periodic) variables
    - Contributors can be flows OR components (e.g., components with effects_per_active_hour)
  2. Exclude effect-to-effect shares
    - Filters out contributors whose base name matches any effect label
    - For example, costs(temporal) is excluded because costs is an effect label
    - These intermediate shares are already included in the computation
  3. Removed the unused _compute_effect_total method
    - The new simplified implementation directly looks up shares from the solution
    - Uses effect_share_factors for conversion between effects
  4. Key insight from user: The solution already contains properly computed share values including all effect-to-effect conversions. The computation uses conversion
  factors because derived effects (like Effect1 which shares 0.5 from costs) don't have direct {flow}->Effect1(temporal) variables - only the source effect shares exist
   ({flow}->costs(temporal)).

* Update docs

* Improve to_netcdf method

* Update examples

* Fix IIS computaion flag

* Fix examples

* Fix faceting in heatmap and use period as facet col everywhere

* Inline plotting methods to deprecate plotting.py

* Fix test

* Simplify Color Management

* ColorType is now defined in color_processing.py and imported into statistics_accessor.py.

* Fix ColorType typing

* Add color accessor

* Ensure io

* Add carrier class

* implemented Carrier as a proper Interface subclass with container support. Here's what was done:

  1. Carrier class (flixopt/carrier.py)

  - Now inherits from Interface for serialization capabilities
  - Has transform_data() method (no-op since carriers have no time-series data)
  - Has label property for container keying
  - Maintains equality comparison with both Carrier objects and strings

  2. CarrierContainer class (flixopt/carrier.py)

  - Inherits from ContainerMixin['Carrier']
  - Provides dict-like access with nice repr and error messages
  - Uses carrier.name for keying

  3. FlowSystem updates (flixopt/flow_system.py)

  - _carriers is now a CarrierContainer instead of a plain dict
  - carriers property returns the CarrierContainer
  - add_carrier() uses the container's add() method
  - Serialization updated to include carriers in to_dataset() and restore them in from_dataset()

  4. Exports (flixopt/__init__.py)

  - Both Carrier and CarrierContainer are now exported

* Inline plotting methods to deprecate plotting.py (#508)

* Inline plotting methods to deprecate plotting.py

* Fix test

* Simplify Color Management

* ColorType is now defined in color_processing.py and imported into statistics_accessor.py.

* Fix ColorType typing

* statistics_accessor.py - Heatmap colors type safety (lines 121-148, 820-853)

  - Changed _heatmap_figure() parameter type from colors: ColorType = None to colors: str | list[str] | None = None
  - Changed heatmap() method parameter type similarly
  - Updated docstrings to clarify that dicts are not supported for heatmaps since px.imshow's color_continuous_scale only accepts colorscale names or lists

  2. statistics_accessor.py - Use configured qualitative colorscale (lines 284, 315)

  - Updated _create_stacked_bar() to use CONFIG.Plotting.default_qualitative_colorscale as the default colorscale
  - Updated _create_line() similarly
  - This ensures user-configured CONFIG.Plotting.default_qualitative_colorscale affects all bar/line plots consistently

  3. topology_accessor.py - Path type alignment (lines 219-222)

  - Added normalization of path=False to None before calling _plot_network()
  - This resolves the type mismatch where TopologyAccessor.plot() accepts bool | str | Path but _plot_network() only accepts str | Path | None

* fix usage if index name in aggregation plot

* Add to docs

* Improve carrier colors and defaults

* Update default carriers and colors

* Update config

* Update config

* Move default carriers to config.py

* Change default carrier handling

* Add color handling

* Rmeove meta_data color handling

* Add carrierst to examples

* Improve plotting acessor

* Improve _resolve_variable_names

* Improve _resolve_variable_names

* Simplify coloring and remove color accessor

* Add connected_and_transformed handling

* Improve error message in container

* BUGFIX: Carrier from dataset
FBumann added a commit that referenced this pull request Dec 10, 2025
…or (#510)

* Add planning doc

* Finalize planning

* Add plotting acessor

* Add plotting acessor

* Add tests

* Improve

* Improve

* Update docs

* Updated the plotting API so that .data always returns xarray DataArray or Dataset instead of pandas DataFrame.

* All .data now returns xr.Dataset consistently.

* Fixed Inconsistencies and Unused Parameters

* New Plot Accessors

  results.plot.variable(pattern)

  Plots the same variable type across multiple elements for easy comparison.

  # All binary operation states across all components
  results.plot.variable('on')

  # All flow_rates, filtered to Boiler-related elements
  results.plot.variable('flow_rate', include='Boiler')

  # All storage charge states
  results.plot.variable('charge_state')

  # With aggregation
  results.plot.variable('flow_rate', aggregate='sum')

  Key features:
  - Searches all elements for variables matching the pattern
  - Filter with include/exclude on element names
  - Supports aggregation, faceting, and animation
  - Returns Dataset with element names as variables

  results.plot.duration_curve(variables)

  Plots load duration curves (sorted time series) showing utilization patterns.

  # Single variable
  results.plot.duration_curve('Boiler(Q_th)|flow_rate')

  # Multiple variables
  results.plot.duration_curve(['CHP|on', 'Boiler|on'])

  # Normalized x-axis (0-100%)
  results.plot.duration_curve('demand', normalize=True)

  Key features:
  - Sorts values from highest to lowest
  - Shows how often each power level is reached
  - normalize=True shows percentage of time on x-axis
  - Returns Dataset with duration_hours or duration_pct dimension

* Fix duration curve

* Fix duration curve

* Fix duration curve

* Fix duration curve

* xr.apply_ufunc to sort along the time axis while preserving all other dimensions automatically

* ⏺ The example runs successfully. Now let me summarize the fixes:

  Summary of Fixes

  I addressed the actionable code review comments from CodeRabbitAI:

  1. Documentation Issue - reshape parameter name ✓ (No fix needed)

  The CodeRabbitAI comment was incorrect. The public API parameter in PlotAccessor.heatmap() is correctly named reshape (line 335). The reshape_time parameter exists in the lower-level heatmap_with_plotly function, but the documentation correctly shows the public API parameter.

  2. Fixed simple_example.py (lines 129-130)

  Problem: The example called balance() and duration_curve() without required arguments, which would cause TypeError at runtime.

  Fix: Added the required arguments:
  - optimization.results.plot.balance('Fernwärme') - specifying the bus to plot
  - optimization.results.plot.duration_curve('Boiler(Q_th)|flow_rate') - specifying the variable to plot

  3. Fixed variable collision in plot_accessors.py (lines 985-988)

  Problem: When building the Dataset in the variable() method, using element names as keys could cause collisions if multiple variables from the same element matched the pattern (e.g., 'Boiler|flow_rate' and 'Boiler|flow_rate_max' would both map to 'Boiler', with the latter silently overwriting the former).

  Fix: Changed to use the full variable names as keys instead of just element names:
  ds = xr.Dataset({var_name: self._results.solution[var_name] for var_name in filtered_vars})

  All tests pass (40 passed, 1 skipped) and the example runs successfully.

* make variable names public in results

* Fix sankey

* Fix effects()

* Fix effects

* Remove bargaps

* made faceting consistent across all plot methods:

  | Method           | facet_col                                 | facet_row                   |
  |------------------|-------------------------------------------|-----------------------------|
  | balance()        | 'scenario'                                | 'period'                    |
  | heatmap()        | 'scenario'                                | 'period'                    |
  | storage()        | 'scenario'                                | 'period'                    |
  | flows()          | 'scenario'                                | 'period'                    |
  | effects()        | 'scenario'                                | 'period'                    |
  | variable()       | 'scenario'                                | 'period'                    |
  | duration_curve() | 'scenario'                                | 'period' (already had this) |
  | compare()        | N/A (uses its own mode='overlay'/'facet') |                             |
  | sankey()         | N/A (aggregates to single diagram)        |                             |

  Removed animate_by parameter from all methods

* Update storage method

* Remove mode parameter for simpli
  | Method           | Default mode                                      |
  |------------------|---------------------------------------------------|
  | balance()        | stacked_bar                                       |
  | storage()        | stacked_bar (flows) + line (charge state overlay) |
  | flows()          | line                                              |
  | variable()       | line                                              |
  | duration_curve() | line                                              |
  | effects()        | bar                                               |

* Make plotting_accessors.py more self contained

* Use faster to_long()

* Add 0-dim case

* sankey diagram now properly handles scenarios and periods:

  Changes made:
  1. Period aggregation with weights: Uses period_weights from flow_system to properly weight periods by their duration
  2. Scenario aggregation with weights: Uses normalized scenario_weights to compute a weighted average across scenarios
  3. Selection support: Users can filter specific scenarios/periods via select parameter before aggregation

  Weighting logic:
  - Periods (for aggregate='sum'): (da * period_weights).sum(dim='period') - this gives the total energy across all periods weighted by their duration
  - Periods (for aggregate='mean'): (da * period_weights).sum(dim='period') / period_weights.sum() - weighted mean
  - Scenarios: Always uses normalized weights (sum to 1) for weighted averaging, since scenarios represent probability-weighted alternatives

* Add colors to sankey

* Add sizes

* Add size filtering

* Include storage sizes

* Remove storage sizes

* Add charge state and status accessor

* Summary of Changes

  1. Added new methods to PlotAccessor (plot_accessors.py)

  charge_states() (line 658):
  - Returns a Dataset with each storage's charge state as a variable
  - Supports filtering with include/exclude parameters
  - Default plot: line chart

  on_states() (line 753):
  - Returns a Dataset with each component's |status variable
  - Supports filtering with include/exclude parameters
  - Default plot: heatmap (good for binary data visualization)

  2. Added data building helper functions (plot_accessors.py)

  build_flow_rates(results) (line 315):
  - Builds a DataArray containing flow rates for all flows
  - Used internally by PlotAccessor methods

  build_flow_hours(results) (line 333):
  - Builds a DataArray containing flow hours for all flows

  build_sizes(results) (line 347):
  - Builds a DataArray containing sizes for all flows

  _filter_dataarray_by_coord(da, **kwargs) (line 284):
  - Helper for filtering DataArrays by coordinate values

  3. Deprecated old Results methods (results.py)

  The following methods now emit DeprecationWarning:
  - results.flow_rates() → Use results.plot.flows(plot=False).data
  - results.flow_hours() → Use results.plot.flows(unit='flow_hours', plot=False).data
  - results.sizes() → Use results.plot.sizes(plot=False).data

  4. Updated PlotAccessor methods to use new helpers

  - flows() now uses build_flow_rates() / build_flow_hours() directly
  - sizes() now uses build_sizes() directly
  - sankey() now uses build_flow_hours() directly

  This ensures the deprecation warnings only fire when users directly call the old methods, not when using the plot accessor

* 1. New methods added to PlotAccessor

  - charge_states(): Returns Dataset with all storage charge states
  - on_states(): Returns Dataset with all component status variables (heatmap display)

  2. Data building helper functions (plot_accessors.py)

  - build_flow_rates(results): Builds DataArray of flow rates
  - build_flow_hours(results): Builds DataArray of flow hours
  - build_sizes(results): Builds DataArray of sizes
  - _filter_dataarray_by_coord(da, **kwargs): Filter helper
  - _assign_flow_coords(da, results): Add flow coordinates

  3. Caching in PlotAccessor

  Added lazy-cached properties for expensive computations:
  - _all_flow_rates - cached DataArray of all flow rates
  - _all_flow_hours - cached DataArray of all flow hours
  - _all_sizes - cached DataArray of all sizes
  - _all_charge_states - cached Dataset of all storage charge states
  - _all_status_vars - cached Dataset of all status variables

  4. Deprecated methods in Results class

  Added deprecation warnings to:
  - results.flow_rates() → Use results.plot.flows(plot=False).data
  - results.flow_hours() → Use results.plot.flows(unit='flow_hours', plot=False).data
  - results.sizes() → Use results.plot.sizes(plot=False).data

  5. Updated PlotAccessor methods to use cached properties

  - flows() uses _all_flow_rates / _all_flow_hours
  - sankey() uses _all_flow_hours
  - sizes() uses _all_sizes
  - charge_states() uses _all_charge_states
  - on_states() uses _all_status_vars

* Move deprectated functionality into results.py instead of porting to the new module

* Revert to simply deprectae old methods without forwarding to new code

* Remove planning file

* Update plotting methods for new datasets

* 1. Renamed data properties in PlotAccessor to use all_ prefix:
    - all_flow_rates - All flow rates as Dataset
    - all_flow_hours - All flow hours as Dataset
    - all_sizes - All flow sizes as Dataset
    - all_charge_states - All storage charge states as Dataset
    - all_on_states - All component on/off status as Dataset
  2. Updated internal references - All usages in flows(), sankey(), sizes(), charge_states(), and on_states() methods now use the new names.
  3. Updated deprecation messages in results.py to point to the new API:
    - results.flow_rates() → results.plot.all_flow_rates
    - results.flow_hours() → results.plot.all_flow_hours
    - results.sizes() → results.plot.all_sizes
  4. Updated docstring examples in PlotAccessor to use the new all_* names.

* Update deprecations messages

* Update deprecations messages

* Thsi seems much better.

* Updaet docstrings and variable name generation in plotting acessor

* Change __ to _ in private dataset caching

* Revert breaking io changes

* New solution storing interface

* Add new focused statistics and plot accessors

* Renamed all properties:
  - all_flow_rates → flow_rates
  - all_flow_hours → flow_hours
  - all_sizes → sizes
  - all_charge_states → charge_states

* Cache Statistics

* Invalidate caches

* Add effect related statistics

* Simplify statistics accessor to rely on flow_system directly instead of solution attrs

* Fix heatma fallback for 1D Data

* Add topology accessor

* All deprecation warnings in the codebase now consistently use the format will be removed in v{DEPRECATION_REMOVAL_VERSION}.

* Update tests

* created comprehensive documentation for all FlowSystem accessors

* Update results documentation

* Update results documentation

* Update effect statistics

* Update effect statistics

* Update effect statistics

* Add mkdocs plotly plugin

* Add section about custom constraints

* documentation updates:

  docs/user-guide/results/index.md:
  - Updated table to replace effects_per_component with temporal_effects, periodic_effects, total_effects, and effect_share_factors
  - Fixed flow_hours['Boiler(Q_th)|flow_rate'] → flow_hours['Boiler(Q_th)']
  - Fixed sizes['Boiler(Q_th)|size'] → sizes['Boiler(Q_th)']
  - Replaced effects_per_component example with new effect properties and groupby examples
  - Updated complete example to use total_effects

  docs/user-guide/results-plotting.md:
  - Fixed colors example from 'Boiler(Q_th)|flow_rate' → 'Boiler(Q_th)'
  - Fixed duration_curve examples to use clean labels

  docs/user-guide/migration-guide-v6.md:
  - Added new "Statistics Accessor" section explaining the clean labels and new effect properties

* implemented the effects() method in StatisticsPlotAccessor at flixopt/statistics_accessor.py:1132-1258.

  Summary of what was done:

  1. Implemented effects() method in StatisticsPlotAccessor class that was missing but documented
    - Takes aspect parameter: 'total', 'temporal', or 'periodic'
    - Takes effect parameter to filter to a specific effect (e.g., 'costs', 'CO2')
    - Takes by parameter: 'component' or 'time' for grouping
    - Supports all standard plotting parameters: select, colors, facet_col, facet_row, show
    - Returns PlotResult with both data and figure
  2. Verified the implementation works with all parameter combinations:
    - Default call: flow_system.statistics.plot.effects()
    - Specific effect: flow_system.statistics.plot.effects(effect='costs')
    - Temporal aspect: flow_system.statistics.plot.effects(aspect='temporal')
    - Temporal by time: flow_system.statistics.plot.effects(aspect='temporal', by='time')
    - Periodic aspect: flow_system.statistics.plot.effects(aspect='periodic')

* Remove intermediate plot accessor

* 1. pyproject.toml: Removed duplicate mkdocs-plotly-plugin>=0.1.3 entry (kept the exact pin ==0.1.3)
  2. flixopt/plotting.py: Fixed dimension name consistency by using squeezed_data.name instead of data.name in the fallback heatmap logic
  3. flixopt/statistics_accessor.py:
    - Fixed _dataset_to_long_df() to only use coordinates that are actually present as columns after reset_index()
    - Fixed the nested loop inefficiency with include_flows by pre-computing the flows list outside the loop
    - (Previously fixed) Fixed asymmetric NaN handling in validation check

* _create_effects_dataset method in statistics_accessor.py was simplified:

  1. Detect contributors from solution data variables instead of assuming they're only flows
    - Uses regex pattern to find {contributor}->{effect}(temporal|periodic) variables
    - Contributors can be flows OR components (e.g., components with effects_per_active_hour)
  2. Exclude effect-to-effect shares
    - Filters out contributors whose base name matches any effect label
    - For example, costs(temporal) is excluded because costs is an effect label
    - These intermediate shares are already included in the computation
  3. Removed the unused _compute_effect_total method
    - The new simplified implementation directly looks up shares from the solution
    - Uses effect_share_factors for conversion between effects
  4. Key insight from user: The solution already contains properly computed share values including all effect-to-effect conversions. The computation uses conversion
  factors because derived effects (like Effect1 which shares 0.5 from costs) don't have direct {flow}->Effect1(temporal) variables - only the source effect shares exist
   ({flow}->costs(temporal)).

* Update docs

* Improve to_netcdf method

* Update examples

* Fix IIS computaion flag

* Fix examples

* Fix faceting in heatmap and use period as facet col everywhere

* Inline plotting methods to deprecate plotting.py

* Fix test

* Simplify Color Management

* ColorType is now defined in color_processing.py and imported into statistics_accessor.py.

* Fix ColorType typing

* Add color accessor

* Ensure io

* Add carrier class

* implemented Carrier as a proper Interface subclass with container support. Here's what was done:

  1. Carrier class (flixopt/carrier.py)

  - Now inherits from Interface for serialization capabilities
  - Has transform_data() method (no-op since carriers have no time-series data)
  - Has label property for container keying
  - Maintains equality comparison with both Carrier objects and strings

  2. CarrierContainer class (flixopt/carrier.py)

  - Inherits from ContainerMixin['Carrier']
  - Provides dict-like access with nice repr and error messages
  - Uses carrier.name for keying

  3. FlowSystem updates (flixopt/flow_system.py)

  - _carriers is now a CarrierContainer instead of a plain dict
  - carriers property returns the CarrierContainer
  - add_carrier() uses the container's add() method
  - Serialization updated to include carriers in to_dataset() and restore them in from_dataset()

  4. Exports (flixopt/__init__.py)

  - Both Carrier and CarrierContainer are now exported

* Inline plotting methods to deprecate plotting.py (#508)

* Inline plotting methods to deprecate plotting.py

* Fix test

* Simplify Color Management

* ColorType is now defined in color_processing.py and imported into statistics_accessor.py.

* Fix ColorType typing

* statistics_accessor.py - Heatmap colors type safety (lines 121-148, 820-853)

  - Changed _heatmap_figure() parameter type from colors: ColorType = None to colors: str | list[str] | None = None
  - Changed heatmap() method parameter type similarly
  - Updated docstrings to clarify that dicts are not supported for heatmaps since px.imshow's color_continuous_scale only accepts colorscale names or lists

  2. statistics_accessor.py - Use configured qualitative colorscale (lines 284, 315)

  - Updated _create_stacked_bar() to use CONFIG.Plotting.default_qualitative_colorscale as the default colorscale
  - Updated _create_line() similarly
  - This ensures user-configured CONFIG.Plotting.default_qualitative_colorscale affects all bar/line plots consistently

  3. topology_accessor.py - Path type alignment (lines 219-222)

  - Added normalization of path=False to None before calling _plot_network()
  - This resolves the type mismatch where TopologyAccessor.plot() accepts bool | str | Path but _plot_network() only accepts str | Path | None

* fix usage if index name in aggregation plot

* Add to docs

* Improve carrier colors and defaults

* Update default carriers and colors

* Update config

* Update config

* Move default carriers to config.py

* Change default carrier handling

* Add color handling

* Rmeove meta_data color handling

* Add carrierst to examples

* Improve plotting acessor

* Improve _resolve_variable_names

* Improve _resolve_variable_names

* Simplify coloring and remove color accessor

* Add connected_and_transformed handling

* Improve error message in container

* Methods moved to TransformAccessor (transform_accessor.py):
    - sel() - select by label
    - isel() - select by integer index
    - resample() - resample time dimension
    - Helper methods: _dataset_sel, _dataset_isel, _dataset_resample, _resample_by_dimension_groups
  2. Solution is dropped: All transform methods return a new FlowSystem with no solution - the user must re-optimize the transformed system.
  3. Deprecation warnings: The old flow_system.sel(), flow_system.isel(), and flow_system.resample() methods now emit deprecation warnings and forward to the new
  TransformAccessor methods.
  4. Backward compatible: Existing code still works, just with deprecation warnings.

* Documentation updated

* Re-add _dataset_sel and other helper methods for proper deprectation. ALso fix new methods to be classmethods

* BUGFIX: Carrier from dataset

* Update docs
FBumann added a commit that referenced this pull request Dec 10, 2025
* Add planning doc

* Finalize planning

* Add plotting acessor

* Add plotting acessor

* Add tests

* Improve

* Improve

* Update docs

* Updated the plotting API so that .data always returns xarray DataArray or Dataset instead of pandas DataFrame.

* All .data now returns xr.Dataset consistently.

* Fixed Inconsistencies and Unused Parameters

* New Plot Accessors

  results.plot.variable(pattern)

  Plots the same variable type across multiple elements for easy comparison.

  # All binary operation states across all components
  results.plot.variable('on')

  # All flow_rates, filtered to Boiler-related elements
  results.plot.variable('flow_rate', include='Boiler')

  # All storage charge states
  results.plot.variable('charge_state')

  # With aggregation
  results.plot.variable('flow_rate', aggregate='sum')

  Key features:
  - Searches all elements for variables matching the pattern
  - Filter with include/exclude on element names
  - Supports aggregation, faceting, and animation
  - Returns Dataset with element names as variables

  results.plot.duration_curve(variables)

  Plots load duration curves (sorted time series) showing utilization patterns.

  # Single variable
  results.plot.duration_curve('Boiler(Q_th)|flow_rate')

  # Multiple variables
  results.plot.duration_curve(['CHP|on', 'Boiler|on'])

  # Normalized x-axis (0-100%)
  results.plot.duration_curve('demand', normalize=True)

  Key features:
  - Sorts values from highest to lowest
  - Shows how often each power level is reached
  - normalize=True shows percentage of time on x-axis
  - Returns Dataset with duration_hours or duration_pct dimension

* Fix duration curve

* Fix duration curve

* Fix duration curve

* Fix duration curve

* xr.apply_ufunc to sort along the time axis while preserving all other dimensions automatically

* ⏺ The example runs successfully. Now let me summarize the fixes:

  Summary of Fixes

  I addressed the actionable code review comments from CodeRabbitAI:

  1. Documentation Issue - reshape parameter name ✓ (No fix needed)

  The CodeRabbitAI comment was incorrect. The public API parameter in PlotAccessor.heatmap() is correctly named reshape (line 335). The reshape_time parameter exists in the lower-level heatmap_with_plotly function, but the documentation correctly shows the public API parameter.

  2. Fixed simple_example.py (lines 129-130)

  Problem: The example called balance() and duration_curve() without required arguments, which would cause TypeError at runtime.

  Fix: Added the required arguments:
  - optimization.results.plot.balance('Fernwärme') - specifying the bus to plot
  - optimization.results.plot.duration_curve('Boiler(Q_th)|flow_rate') - specifying the variable to plot

  3. Fixed variable collision in plot_accessors.py (lines 985-988)

  Problem: When building the Dataset in the variable() method, using element names as keys could cause collisions if multiple variables from the same element matched the pattern (e.g., 'Boiler|flow_rate' and 'Boiler|flow_rate_max' would both map to 'Boiler', with the latter silently overwriting the former).

  Fix: Changed to use the full variable names as keys instead of just element names:
  ds = xr.Dataset({var_name: self._results.solution[var_name] for var_name in filtered_vars})

  All tests pass (40 passed, 1 skipped) and the example runs successfully.

* make variable names public in results

* Fix sankey

* Fix effects()

* Fix effects

* Remove bargaps

* made faceting consistent across all plot methods:

  | Method           | facet_col                                 | facet_row                   |
  |------------------|-------------------------------------------|-----------------------------|
  | balance()        | 'scenario'                                | 'period'                    |
  | heatmap()        | 'scenario'                                | 'period'                    |
  | storage()        | 'scenario'                                | 'period'                    |
  | flows()          | 'scenario'                                | 'period'                    |
  | effects()        | 'scenario'                                | 'period'                    |
  | variable()       | 'scenario'                                | 'period'                    |
  | duration_curve() | 'scenario'                                | 'period' (already had this) |
  | compare()        | N/A (uses its own mode='overlay'/'facet') |                             |
  | sankey()         | N/A (aggregates to single diagram)        |                             |

  Removed animate_by parameter from all methods

* Update storage method

* Remove mode parameter for simpli
  | Method           | Default mode                                      |
  |------------------|---------------------------------------------------|
  | balance()        | stacked_bar                                       |
  | storage()        | stacked_bar (flows) + line (charge state overlay) |
  | flows()          | line                                              |
  | variable()       | line                                              |
  | duration_curve() | line                                              |
  | effects()        | bar                                               |

* Make plotting_accessors.py more self contained

* Use faster to_long()

* Add 0-dim case

* sankey diagram now properly handles scenarios and periods:

  Changes made:
  1. Period aggregation with weights: Uses period_weights from flow_system to properly weight periods by their duration
  2. Scenario aggregation with weights: Uses normalized scenario_weights to compute a weighted average across scenarios
  3. Selection support: Users can filter specific scenarios/periods via select parameter before aggregation

  Weighting logic:
  - Periods (for aggregate='sum'): (da * period_weights).sum(dim='period') - this gives the total energy across all periods weighted by their duration
  - Periods (for aggregate='mean'): (da * period_weights).sum(dim='period') / period_weights.sum() - weighted mean
  - Scenarios: Always uses normalized weights (sum to 1) for weighted averaging, since scenarios represent probability-weighted alternatives

* Add colors to sankey

* Add sizes

* Add size filtering

* Include storage sizes

* Remove storage sizes

* Add charge state and status accessor

* Summary of Changes

  1. Added new methods to PlotAccessor (plot_accessors.py)

  charge_states() (line 658):
  - Returns a Dataset with each storage's charge state as a variable
  - Supports filtering with include/exclude parameters
  - Default plot: line chart

  on_states() (line 753):
  - Returns a Dataset with each component's |status variable
  - Supports filtering with include/exclude parameters
  - Default plot: heatmap (good for binary data visualization)

  2. Added data building helper functions (plot_accessors.py)

  build_flow_rates(results) (line 315):
  - Builds a DataArray containing flow rates for all flows
  - Used internally by PlotAccessor methods

  build_flow_hours(results) (line 333):
  - Builds a DataArray containing flow hours for all flows

  build_sizes(results) (line 347):
  - Builds a DataArray containing sizes for all flows

  _filter_dataarray_by_coord(da, **kwargs) (line 284):
  - Helper for filtering DataArrays by coordinate values

  3. Deprecated old Results methods (results.py)

  The following methods now emit DeprecationWarning:
  - results.flow_rates() → Use results.plot.flows(plot=False).data
  - results.flow_hours() → Use results.plot.flows(unit='flow_hours', plot=False).data
  - results.sizes() → Use results.plot.sizes(plot=False).data

  4. Updated PlotAccessor methods to use new helpers

  - flows() now uses build_flow_rates() / build_flow_hours() directly
  - sizes() now uses build_sizes() directly
  - sankey() now uses build_flow_hours() directly

  This ensures the deprecation warnings only fire when users directly call the old methods, not when using the plot accessor

* 1. New methods added to PlotAccessor

  - charge_states(): Returns Dataset with all storage charge states
  - on_states(): Returns Dataset with all component status variables (heatmap display)

  2. Data building helper functions (plot_accessors.py)

  - build_flow_rates(results): Builds DataArray of flow rates
  - build_flow_hours(results): Builds DataArray of flow hours
  - build_sizes(results): Builds DataArray of sizes
  - _filter_dataarray_by_coord(da, **kwargs): Filter helper
  - _assign_flow_coords(da, results): Add flow coordinates

  3. Caching in PlotAccessor

  Added lazy-cached properties for expensive computations:
  - _all_flow_rates - cached DataArray of all flow rates
  - _all_flow_hours - cached DataArray of all flow hours
  - _all_sizes - cached DataArray of all sizes
  - _all_charge_states - cached Dataset of all storage charge states
  - _all_status_vars - cached Dataset of all status variables

  4. Deprecated methods in Results class

  Added deprecation warnings to:
  - results.flow_rates() → Use results.plot.flows(plot=False).data
  - results.flow_hours() → Use results.plot.flows(unit='flow_hours', plot=False).data
  - results.sizes() → Use results.plot.sizes(plot=False).data

  5. Updated PlotAccessor methods to use cached properties

  - flows() uses _all_flow_rates / _all_flow_hours
  - sankey() uses _all_flow_hours
  - sizes() uses _all_sizes
  - charge_states() uses _all_charge_states
  - on_states() uses _all_status_vars

* Move deprectated functionality into results.py instead of porting to the new module

* Revert to simply deprectae old methods without forwarding to new code

* Remove planning file

* Update plotting methods for new datasets

* 1. Renamed data properties in PlotAccessor to use all_ prefix:
    - all_flow_rates - All flow rates as Dataset
    - all_flow_hours - All flow hours as Dataset
    - all_sizes - All flow sizes as Dataset
    - all_charge_states - All storage charge states as Dataset
    - all_on_states - All component on/off status as Dataset
  2. Updated internal references - All usages in flows(), sankey(), sizes(), charge_states(), and on_states() methods now use the new names.
  3. Updated deprecation messages in results.py to point to the new API:
    - results.flow_rates() → results.plot.all_flow_rates
    - results.flow_hours() → results.plot.all_flow_hours
    - results.sizes() → results.plot.all_sizes
  4. Updated docstring examples in PlotAccessor to use the new all_* names.

* Update deprecations messages

* Update deprecations messages

* Thsi seems much better.

* Updaet docstrings and variable name generation in plotting acessor

* Change __ to _ in private dataset caching

* Revert breaking io changes

* New solution storing interface

* Add new focused statistics and plot accessors

* Renamed all properties:
  - all_flow_rates → flow_rates
  - all_flow_hours → flow_hours
  - all_sizes → sizes
  - all_charge_states → charge_states

* Cache Statistics

* Invalidate caches

* Add effect related statistics

* Simplify statistics accessor to rely on flow_system directly instead of solution attrs

* Fix heatma fallback for 1D Data

* Add topology accessor

* All deprecation warnings in the codebase now consistently use the format will be removed in v{DEPRECATION_REMOVAL_VERSION}.

* Update tests

* created comprehensive documentation for all FlowSystem accessors

* Update results documentation

* Update results documentation

* Update effect statistics

* Update effect statistics

* Update effect statistics

* Add mkdocs plotly plugin

* Add section about custom constraints

* documentation updates:

  docs/user-guide/results/index.md:
  - Updated table to replace effects_per_component with temporal_effects, periodic_effects, total_effects, and effect_share_factors
  - Fixed flow_hours['Boiler(Q_th)|flow_rate'] → flow_hours['Boiler(Q_th)']
  - Fixed sizes['Boiler(Q_th)|size'] → sizes['Boiler(Q_th)']
  - Replaced effects_per_component example with new effect properties and groupby examples
  - Updated complete example to use total_effects

  docs/user-guide/results-plotting.md:
  - Fixed colors example from 'Boiler(Q_th)|flow_rate' → 'Boiler(Q_th)'
  - Fixed duration_curve examples to use clean labels

  docs/user-guide/migration-guide-v6.md:
  - Added new "Statistics Accessor" section explaining the clean labels and new effect properties

* implemented the effects() method in StatisticsPlotAccessor at flixopt/statistics_accessor.py:1132-1258.

  Summary of what was done:

  1. Implemented effects() method in StatisticsPlotAccessor class that was missing but documented
    - Takes aspect parameter: 'total', 'temporal', or 'periodic'
    - Takes effect parameter to filter to a specific effect (e.g., 'costs', 'CO2')
    - Takes by parameter: 'component' or 'time' for grouping
    - Supports all standard plotting parameters: select, colors, facet_col, facet_row, show
    - Returns PlotResult with both data and figure
  2. Verified the implementation works with all parameter combinations:
    - Default call: flow_system.statistics.plot.effects()
    - Specific effect: flow_system.statistics.plot.effects(effect='costs')
    - Temporal aspect: flow_system.statistics.plot.effects(aspect='temporal')
    - Temporal by time: flow_system.statistics.plot.effects(aspect='temporal', by='time')
    - Periodic aspect: flow_system.statistics.plot.effects(aspect='periodic')

* Remove intermediate plot accessor

* 1. pyproject.toml: Removed duplicate mkdocs-plotly-plugin>=0.1.3 entry (kept the exact pin ==0.1.3)
  2. flixopt/plotting.py: Fixed dimension name consistency by using squeezed_data.name instead of data.name in the fallback heatmap logic
  3. flixopt/statistics_accessor.py:
    - Fixed _dataset_to_long_df() to only use coordinates that are actually present as columns after reset_index()
    - Fixed the nested loop inefficiency with include_flows by pre-computing the flows list outside the loop
    - (Previously fixed) Fixed asymmetric NaN handling in validation check

* _create_effects_dataset method in statistics_accessor.py was simplified:

  1. Detect contributors from solution data variables instead of assuming they're only flows
    - Uses regex pattern to find {contributor}->{effect}(temporal|periodic) variables
    - Contributors can be flows OR components (e.g., components with effects_per_active_hour)
  2. Exclude effect-to-effect shares
    - Filters out contributors whose base name matches any effect label
    - For example, costs(temporal) is excluded because costs is an effect label
    - These intermediate shares are already included in the computation
  3. Removed the unused _compute_effect_total method
    - The new simplified implementation directly looks up shares from the solution
    - Uses effect_share_factors for conversion between effects
  4. Key insight from user: The solution already contains properly computed share values including all effect-to-effect conversions. The computation uses conversion
  factors because derived effects (like Effect1 which shares 0.5 from costs) don't have direct {flow}->Effect1(temporal) variables - only the source effect shares exist
   ({flow}->costs(temporal)).

* Update docs

* Improve to_netcdf method

* Update examples

* Fix IIS computaion flag

* Fix examples

* Fix faceting in heatmap and use period as facet col everywhere

* Inline plotting methods to deprecate plotting.py

* Fix test

* Simplify Color Management

* ColorType is now defined in color_processing.py and imported into statistics_accessor.py.

* Fix ColorType typing

* Add color accessor

* Ensure io

* Add carrier class

* implemented Carrier as a proper Interface subclass with container support. Here's what was done:

  1. Carrier class (flixopt/carrier.py)

  - Now inherits from Interface for serialization capabilities
  - Has transform_data() method (no-op since carriers have no time-series data)
  - Has label property for container keying
  - Maintains equality comparison with both Carrier objects and strings

  2. CarrierContainer class (flixopt/carrier.py)

  - Inherits from ContainerMixin['Carrier']
  - Provides dict-like access with nice repr and error messages
  - Uses carrier.name for keying

  3. FlowSystem updates (flixopt/flow_system.py)

  - _carriers is now a CarrierContainer instead of a plain dict
  - carriers property returns the CarrierContainer
  - add_carrier() uses the container's add() method
  - Serialization updated to include carriers in to_dataset() and restore them in from_dataset()

  4. Exports (flixopt/__init__.py)

  - Both Carrier and CarrierContainer are now exported

* Inline plotting methods to deprecate plotting.py (#508)

* Inline plotting methods to deprecate plotting.py

* Fix test

* Simplify Color Management

* ColorType is now defined in color_processing.py and imported into statistics_accessor.py.

* Fix ColorType typing

* statistics_accessor.py - Heatmap colors type safety (lines 121-148, 820-853)

  - Changed _heatmap_figure() parameter type from colors: ColorType = None to colors: str | list[str] | None = None
  - Changed heatmap() method parameter type similarly
  - Updated docstrings to clarify that dicts are not supported for heatmaps since px.imshow's color_continuous_scale only accepts colorscale names or lists

  2. statistics_accessor.py - Use configured qualitative colorscale (lines 284, 315)

  - Updated _create_stacked_bar() to use CONFIG.Plotting.default_qualitative_colorscale as the default colorscale
  - Updated _create_line() similarly
  - This ensures user-configured CONFIG.Plotting.default_qualitative_colorscale affects all bar/line plots consistently

  3. topology_accessor.py - Path type alignment (lines 219-222)

  - Added normalization of path=False to None before calling _plot_network()
  - This resolves the type mismatch where TopologyAccessor.plot() accepts bool | str | Path but _plot_network() only accepts str | Path | None

* fix usage if index name in aggregation plot

* Add to docs

* Improve carrier colors and defaults

* Update default carriers and colors

* Update config

* Update config

* Move default carriers to config.py

* Change default carrier handling

* Add color handling

* Rmeove meta_data color handling

* Add carrierst to examples

* Improve plotting acessor

* Improve _resolve_variable_names

* Improve _resolve_variable_names

* Simplify coloring and remove color accessor

* Add connected_and_transformed handling

* Improve error message in container

* Methods moved to TransformAccessor (transform_accessor.py):
    - sel() - select by label
    - isel() - select by integer index
    - resample() - resample time dimension
    - Helper methods: _dataset_sel, _dataset_isel, _dataset_resample, _resample_by_dimension_groups
  2. Solution is dropped: All transform methods return a new FlowSystem with no solution - the user must re-optimize the transformed system.
  3. Deprecation warnings: The old flow_system.sel(), flow_system.isel(), and flow_system.resample() methods now emit deprecation warnings and forward to the new
  TransformAccessor methods.
  4. Backward compatible: Existing code still works, just with deprecation warnings.

* Documentation updated

* Re-add _dataset_sel and other helper methods for proper deprectation. ALso fix new methods to be classmethods

* BUGFIX: Carrier from dataset

* Update docs

* Add notebook examples instead of raw py files for docs

* Execute on docs buildt

* Add notebooks and new config util andmore notebook related stuff

* Fix notebooks

* Fix notebooks and config.py

* docstring typo

* Update notebooks

* Add fix_sizes method and use in two stage optimization notebook

* Change notebook config

* Dont get size as float in fix sizes!

* Update notebook

* fix: fix_sizes() to handle var names properly

* Add imbalance penalty to prevent infeasibility in Fernwärme Bus

* Remove putputs from all notbeooks for git

* Update the two stage notebook

* Update notebooks with user stories

* Add new index to docs

* Update notebooks to use plotly instead of matplotlib

* fix: Use plotly express

* fix: Use plotly express

* fix: Use plotly express

* Adjust plots

* Bugfix: Add _update_scenario_metadata method

* fix: _update_scenario_metadata method

* Get all notebooks running

* Improve notebooks for more interesting results

* Fix conversion factor

* Fix conversion factor

* Update notebooks and bugfix sankey

* Add charge state and storages plots

* improve charge state plot

* Add Piecewise plots and improve Notebooks (#519)

* Build notebooks in parralel in CI

* Revert "Build notebooks in parralel in CI"

This reverts commit 0f1153c.

* Fix dependencies in docs workflow

* Use extra css

* Use extra css

* Use extra css

* Use extra css

* Fix notebook

* Add new statistics for sizes, flow_sizes and storage_sizes

* Fixed broken links in docs

* Fix mkdocs buildt noebooks on build

* Remove old example notebooks and rename folder to notebooks

* Add .storages property to FlowSystem

* fix broken links in docs

* Imrpove data extraction

* Imrpove data extraction

* Remove examples and test_examples.py
FBumann added a commit that referenced this pull request Dec 13, 2025
* Add planning doc

* Finalize planning

* Add plotting acessor

* Add plotting acessor

* Add tests

* Improve

* Improve

* Update docs

* Updated the plotting API so that .data always returns xarray DataArray or Dataset instead of pandas DataFrame.

* All .data now returns xr.Dataset consistently.

* Fixed Inconsistencies and Unused Parameters

* New Plot Accessors

  results.plot.variable(pattern)

  Plots the same variable type across multiple elements for easy comparison.

  # All binary operation states across all components
  results.plot.variable('on')

  # All flow_rates, filtered to Boiler-related elements
  results.plot.variable('flow_rate', include='Boiler')

  # All storage charge states
  results.plot.variable('charge_state')

  # With aggregation
  results.plot.variable('flow_rate', aggregate='sum')

  Key features:
  - Searches all elements for variables matching the pattern
  - Filter with include/exclude on element names
  - Supports aggregation, faceting, and animation
  - Returns Dataset with element names as variables

  results.plot.duration_curve(variables)

  Plots load duration curves (sorted time series) showing utilization patterns.

  # Single variable
  results.plot.duration_curve('Boiler(Q_th)|flow_rate')

  # Multiple variables
  results.plot.duration_curve(['CHP|on', 'Boiler|on'])

  # Normalized x-axis (0-100%)
  results.plot.duration_curve('demand', normalize=True)

  Key features:
  - Sorts values from highest to lowest
  - Shows how often each power level is reached
  - normalize=True shows percentage of time on x-axis
  - Returns Dataset with duration_hours or duration_pct dimension

* Fix duration curve

* Fix duration curve

* Fix duration curve

* Fix duration curve

* xr.apply_ufunc to sort along the time axis while preserving all other dimensions automatically

* ⏺ The example runs successfully. Now let me summarize the fixes:

  Summary of Fixes

  I addressed the actionable code review comments from CodeRabbitAI:

  1. Documentation Issue - reshape parameter name ✓ (No fix needed)

  The CodeRabbitAI comment was incorrect. The public API parameter in PlotAccessor.heatmap() is correctly named reshape (line 335). The reshape_time parameter exists in the lower-level heatmap_with_plotly function, but the documentation correctly shows the public API parameter.

  2. Fixed simple_example.py (lines 129-130)

  Problem: The example called balance() and duration_curve() without required arguments, which would cause TypeError at runtime.

  Fix: Added the required arguments:
  - optimization.results.plot.balance('Fernwärme') - specifying the bus to plot
  - optimization.results.plot.duration_curve('Boiler(Q_th)|flow_rate') - specifying the variable to plot

  3. Fixed variable collision in plot_accessors.py (lines 985-988)

  Problem: When building the Dataset in the variable() method, using element names as keys could cause collisions if multiple variables from the same element matched the pattern (e.g., 'Boiler|flow_rate' and 'Boiler|flow_rate_max' would both map to 'Boiler', with the latter silently overwriting the former).

  Fix: Changed to use the full variable names as keys instead of just element names:
  ds = xr.Dataset({var_name: self._results.solution[var_name] for var_name in filtered_vars})

  All tests pass (40 passed, 1 skipped) and the example runs successfully.

* make variable names public in results

* Fix sankey

* Fix effects()

* Fix effects

* Remove bargaps

* made faceting consistent across all plot methods:

  | Method           | facet_col                                 | facet_row                   |
  |------------------|-------------------------------------------|-----------------------------|
  | balance()        | 'scenario'                                | 'period'                    |
  | heatmap()        | 'scenario'                                | 'period'                    |
  | storage()        | 'scenario'                                | 'period'                    |
  | flows()          | 'scenario'                                | 'period'                    |
  | effects()        | 'scenario'                                | 'period'                    |
  | variable()       | 'scenario'                                | 'period'                    |
  | duration_curve() | 'scenario'                                | 'period' (already had this) |
  | compare()        | N/A (uses its own mode='overlay'/'facet') |                             |
  | sankey()         | N/A (aggregates to single diagram)        |                             |

  Removed animate_by parameter from all methods

* Update storage method

* Remove mode parameter for simpli
  | Method           | Default mode                                      |
  |------------------|---------------------------------------------------|
  | balance()        | stacked_bar                                       |
  | storage()        | stacked_bar (flows) + line (charge state overlay) |
  | flows()          | line                                              |
  | variable()       | line                                              |
  | duration_curve() | line                                              |
  | effects()        | bar                                               |

* Make plotting_accessors.py more self contained

* Use faster to_long()

* Add 0-dim case

* sankey diagram now properly handles scenarios and periods:

  Changes made:
  1. Period aggregation with weights: Uses period_weights from flow_system to properly weight periods by their duration
  2. Scenario aggregation with weights: Uses normalized scenario_weights to compute a weighted average across scenarios
  3. Selection support: Users can filter specific scenarios/periods via select parameter before aggregation

  Weighting logic:
  - Periods (for aggregate='sum'): (da * period_weights).sum(dim='period') - this gives the total energy across all periods weighted by their duration
  - Periods (for aggregate='mean'): (da * period_weights).sum(dim='period') / period_weights.sum() - weighted mean
  - Scenarios: Always uses normalized weights (sum to 1) for weighted averaging, since scenarios represent probability-weighted alternatives

* Add colors to sankey

* Add sizes

* Add size filtering

* Include storage sizes

* Remove storage sizes

* Add charge state and status accessor

* Summary of Changes

  1. Added new methods to PlotAccessor (plot_accessors.py)

  charge_states() (line 658):
  - Returns a Dataset with each storage's charge state as a variable
  - Supports filtering with include/exclude parameters
  - Default plot: line chart

  on_states() (line 753):
  - Returns a Dataset with each component's |status variable
  - Supports filtering with include/exclude parameters
  - Default plot: heatmap (good for binary data visualization)

  2. Added data building helper functions (plot_accessors.py)

  build_flow_rates(results) (line 315):
  - Builds a DataArray containing flow rates for all flows
  - Used internally by PlotAccessor methods

  build_flow_hours(results) (line 333):
  - Builds a DataArray containing flow hours for all flows

  build_sizes(results) (line 347):
  - Builds a DataArray containing sizes for all flows

  _filter_dataarray_by_coord(da, **kwargs) (line 284):
  - Helper for filtering DataArrays by coordinate values

  3. Deprecated old Results methods (results.py)

  The following methods now emit DeprecationWarning:
  - results.flow_rates() → Use results.plot.flows(plot=False).data
  - results.flow_hours() → Use results.plot.flows(unit='flow_hours', plot=False).data
  - results.sizes() → Use results.plot.sizes(plot=False).data

  4. Updated PlotAccessor methods to use new helpers

  - flows() now uses build_flow_rates() / build_flow_hours() directly
  - sizes() now uses build_sizes() directly
  - sankey() now uses build_flow_hours() directly

  This ensures the deprecation warnings only fire when users directly call the old methods, not when using the plot accessor

* 1. New methods added to PlotAccessor

  - charge_states(): Returns Dataset with all storage charge states
  - on_states(): Returns Dataset with all component status variables (heatmap display)

  2. Data building helper functions (plot_accessors.py)

  - build_flow_rates(results): Builds DataArray of flow rates
  - build_flow_hours(results): Builds DataArray of flow hours
  - build_sizes(results): Builds DataArray of sizes
  - _filter_dataarray_by_coord(da, **kwargs): Filter helper
  - _assign_flow_coords(da, results): Add flow coordinates

  3. Caching in PlotAccessor

  Added lazy-cached properties for expensive computations:
  - _all_flow_rates - cached DataArray of all flow rates
  - _all_flow_hours - cached DataArray of all flow hours
  - _all_sizes - cached DataArray of all sizes
  - _all_charge_states - cached Dataset of all storage charge states
  - _all_status_vars - cached Dataset of all status variables

  4. Deprecated methods in Results class

  Added deprecation warnings to:
  - results.flow_rates() → Use results.plot.flows(plot=False).data
  - results.flow_hours() → Use results.plot.flows(unit='flow_hours', plot=False).data
  - results.sizes() → Use results.plot.sizes(plot=False).data

  5. Updated PlotAccessor methods to use cached properties

  - flows() uses _all_flow_rates / _all_flow_hours
  - sankey() uses _all_flow_hours
  - sizes() uses _all_sizes
  - charge_states() uses _all_charge_states
  - on_states() uses _all_status_vars

* Move deprectated functionality into results.py instead of porting to the new module

* Revert to simply deprectae old methods without forwarding to new code

* Remove planning file

* Update plotting methods for new datasets

* 1. Renamed data properties in PlotAccessor to use all_ prefix:
    - all_flow_rates - All flow rates as Dataset
    - all_flow_hours - All flow hours as Dataset
    - all_sizes - All flow sizes as Dataset
    - all_charge_states - All storage charge states as Dataset
    - all_on_states - All component on/off status as Dataset
  2. Updated internal references - All usages in flows(), sankey(), sizes(), charge_states(), and on_states() methods now use the new names.
  3. Updated deprecation messages in results.py to point to the new API:
    - results.flow_rates() → results.plot.all_flow_rates
    - results.flow_hours() → results.plot.all_flow_hours
    - results.sizes() → results.plot.all_sizes
  4. Updated docstring examples in PlotAccessor to use the new all_* names.

* Update deprecations messages

* Update deprecations messages

* Thsi seems much better.

* Updaet docstrings and variable name generation in plotting acessor

* Change __ to _ in private dataset caching

* Revert breaking io changes

* New solution storing interface

* Add new focused statistics and plot accessors

* Renamed all properties:
  - all_flow_rates → flow_rates
  - all_flow_hours → flow_hours
  - all_sizes → sizes
  - all_charge_states → charge_states

* Cache Statistics

* Invalidate caches

* Add effect related statistics

* Simplify statistics accessor to rely on flow_system directly instead of solution attrs

* Fix heatma fallback for 1D Data

* Add topology accessor

* All deprecation warnings in the codebase now consistently use the format will be removed in v{DEPRECATION_REMOVAL_VERSION}.

* Update tests

* created comprehensive documentation for all FlowSystem accessors

* Update results documentation

* Update results documentation

* Update effect statistics

* Update effect statistics

* Update effect statistics

* Add mkdocs plotly plugin

* Add section about custom constraints

* documentation updates:

  docs/user-guide/results/index.md:
  - Updated table to replace effects_per_component with temporal_effects, periodic_effects, total_effects, and effect_share_factors
  - Fixed flow_hours['Boiler(Q_th)|flow_rate'] → flow_hours['Boiler(Q_th)']
  - Fixed sizes['Boiler(Q_th)|size'] → sizes['Boiler(Q_th)']
  - Replaced effects_per_component example with new effect properties and groupby examples
  - Updated complete example to use total_effects

  docs/user-guide/results-plotting.md:
  - Fixed colors example from 'Boiler(Q_th)|flow_rate' → 'Boiler(Q_th)'
  - Fixed duration_curve examples to use clean labels

  docs/user-guide/migration-guide-v6.md:
  - Added new "Statistics Accessor" section explaining the clean labels and new effect properties

* implemented the effects() method in StatisticsPlotAccessor at flixopt/statistics_accessor.py:1132-1258.

  Summary of what was done:

  1. Implemented effects() method in StatisticsPlotAccessor class that was missing but documented
    - Takes aspect parameter: 'total', 'temporal', or 'periodic'
    - Takes effect parameter to filter to a specific effect (e.g., 'costs', 'CO2')
    - Takes by parameter: 'component' or 'time' for grouping
    - Supports all standard plotting parameters: select, colors, facet_col, facet_row, show
    - Returns PlotResult with both data and figure
  2. Verified the implementation works with all parameter combinations:
    - Default call: flow_system.statistics.plot.effects()
    - Specific effect: flow_system.statistics.plot.effects(effect='costs')
    - Temporal aspect: flow_system.statistics.plot.effects(aspect='temporal')
    - Temporal by time: flow_system.statistics.plot.effects(aspect='temporal', by='time')
    - Periodic aspect: flow_system.statistics.plot.effects(aspect='periodic')

* Remove intermediate plot accessor

* 1. pyproject.toml: Removed duplicate mkdocs-plotly-plugin>=0.1.3 entry (kept the exact pin ==0.1.3)
  2. flixopt/plotting.py: Fixed dimension name consistency by using squeezed_data.name instead of data.name in the fallback heatmap logic
  3. flixopt/statistics_accessor.py:
    - Fixed _dataset_to_long_df() to only use coordinates that are actually present as columns after reset_index()
    - Fixed the nested loop inefficiency with include_flows by pre-computing the flows list outside the loop
    - (Previously fixed) Fixed asymmetric NaN handling in validation check

* _create_effects_dataset method in statistics_accessor.py was simplified:

  1. Detect contributors from solution data variables instead of assuming they're only flows
    - Uses regex pattern to find {contributor}->{effect}(temporal|periodic) variables
    - Contributors can be flows OR components (e.g., components with effects_per_active_hour)
  2. Exclude effect-to-effect shares
    - Filters out contributors whose base name matches any effect label
    - For example, costs(temporal) is excluded because costs is an effect label
    - These intermediate shares are already included in the computation
  3. Removed the unused _compute_effect_total method
    - The new simplified implementation directly looks up shares from the solution
    - Uses effect_share_factors for conversion between effects
  4. Key insight from user: The solution already contains properly computed share values including all effect-to-effect conversions. The computation uses conversion
  factors because derived effects (like Effect1 which shares 0.5 from costs) don't have direct {flow}->Effect1(temporal) variables - only the source effect shares exist
   ({flow}->costs(temporal)).

* Update docs

* Improve to_netcdf method

* Update examples

* Fix IIS computaion flag

* Fix examples

* Fix faceting in heatmap and use period as facet col everywhere

* Inline plotting methods to deprecate plotting.py

* Fix test

* Simplify Color Management

* ColorType is now defined in color_processing.py and imported into statistics_accessor.py.

* Fix ColorType typing

* Add color accessor

* Ensure io

* Add carrier class

* implemented Carrier as a proper Interface subclass with container support. Here's what was done:

  1. Carrier class (flixopt/carrier.py)

  - Now inherits from Interface for serialization capabilities
  - Has transform_data() method (no-op since carriers have no time-series data)
  - Has label property for container keying
  - Maintains equality comparison with both Carrier objects and strings

  2. CarrierContainer class (flixopt/carrier.py)

  - Inherits from ContainerMixin['Carrier']
  - Provides dict-like access with nice repr and error messages
  - Uses carrier.name for keying

  3. FlowSystem updates (flixopt/flow_system.py)

  - _carriers is now a CarrierContainer instead of a plain dict
  - carriers property returns the CarrierContainer
  - add_carrier() uses the container's add() method
  - Serialization updated to include carriers in to_dataset() and restore them in from_dataset()

  4. Exports (flixopt/__init__.py)

  - Both Carrier and CarrierContainer are now exported

* Inline plotting methods to deprecate plotting.py (#508)

* Inline plotting methods to deprecate plotting.py

* Fix test

* Simplify Color Management

* ColorType is now defined in color_processing.py and imported into statistics_accessor.py.

* Fix ColorType typing

* statistics_accessor.py - Heatmap colors type safety (lines 121-148, 820-853)

  - Changed _heatmap_figure() parameter type from colors: ColorType = None to colors: str | list[str] | None = None
  - Changed heatmap() method parameter type similarly
  - Updated docstrings to clarify that dicts are not supported for heatmaps since px.imshow's color_continuous_scale only accepts colorscale names or lists

  2. statistics_accessor.py - Use configured qualitative colorscale (lines 284, 315)

  - Updated _create_stacked_bar() to use CONFIG.Plotting.default_qualitative_colorscale as the default colorscale
  - Updated _create_line() similarly
  - This ensures user-configured CONFIG.Plotting.default_qualitative_colorscale affects all bar/line plots consistently

  3. topology_accessor.py - Path type alignment (lines 219-222)

  - Added normalization of path=False to None before calling _plot_network()
  - This resolves the type mismatch where TopologyAccessor.plot() accepts bool | str | Path but _plot_network() only accepts str | Path | None

* fix usage if index name in aggregation plot

* Add to docs

* Improve carrier colors and defaults

* Update default carriers and colors

* Update config

* Update config

* Move default carriers to config.py

* Change default carrier handling

* Add color handling

* Rmeove meta_data color handling

* Add carrierst to examples

* Improve plotting acessor

* Improve _resolve_variable_names

* Improve _resolve_variable_names

* Simplify coloring and remove color accessor

* Add connected_and_transformed handling

* Improve error message in container

* Methods moved to TransformAccessor (transform_accessor.py):
    - sel() - select by label
    - isel() - select by integer index
    - resample() - resample time dimension
    - Helper methods: _dataset_sel, _dataset_isel, _dataset_resample, _resample_by_dimension_groups
  2. Solution is dropped: All transform methods return a new FlowSystem with no solution - the user must re-optimize the transformed system.
  3. Deprecation warnings: The old flow_system.sel(), flow_system.isel(), and flow_system.resample() methods now emit deprecation warnings and forward to the new
  TransformAccessor methods.
  4. Backward compatible: Existing code still works, just with deprecation warnings.

* Documentation updated

* Re-add _dataset_sel and other helper methods for proper deprectation. ALso fix new methods to be classmethods

* BUGFIX: Carrier from dataset

* Update docs

* Add notebook examples instead of raw py files for docs

* Execute on docs buildt

* Add notebooks and new config util andmore notebook related stuff

* Fix notebooks

* Fix notebooks and config.py

* docstring typo

* Update notebooks

* Add fix_sizes method and use in two stage optimization notebook

* Change notebook config

* Dont get size as float in fix sizes!

* Update notebook

* fix: fix_sizes() to handle var names properly

* Add imbalance penalty to prevent infeasibility in Fernwärme Bus

* Remove putputs from all notbeooks for git

* Update the two stage notebook

* Update notebooks with user stories

* Add new index to docs

* Update notebooks to use plotly instead of matplotlib

* fix: Use plotly express

* fix: Use plotly express

* fix: Use plotly express

* Adjust plots

* Bugfix: Add _update_scenario_metadata method

* fix: _update_scenario_metadata method

* Get all notebooks running

* Improve notebooks for more interesting results

* Fix conversion factor

* Fix conversion factor

* Update notebooks and bugfix sankey

* Add charge state and storages plots

* improve charge state plot

* Add Piecewise plots and improve Notebooks (#519)

* Build notebooks in parralel in CI

* Revert "Build notebooks in parralel in CI"

This reverts commit 0f1153c.

* Fix dependencies in docs workflow

* Use extra css

* Use extra css

* Use extra css

* Use extra css

* Fix notebook

* Add new statistics for sizes, flow_sizes and storage_sizes

* Fixed broken links in docs

* Fix mkdocs buildt noebooks on build

* Remove old example notebooks and rename folder to notebooks

* Add .storages property to FlowSystem

* fix broken links in docs

* Imrpove data extraction

* Imrpove data extraction

* Remove examples and test_examples.py
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