From 1574a57ca6c049983f6036f68fef8cccf3f9c120 Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Fri, 16 Jan 2026 12:14:47 +0100
Subject: [PATCH 1/7] _style_area_as_bar() helper: - Classifies traces by
analyzing y-values: positive, negative, mixed, zero - Sets
stackgroup='positive' or stackgroup='negative' for proper separate stacking
- Mixed values shown as dashed lines (no fill) - Opaque fills, no line
borders, hv line shape
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Performance:
┌────────────────────────┬───────┐
│ Method │ Time │
├────────────────────────┼───────┤
│ .plotly.bar() + update │ 0.14s │
├────────────────────────┼───────┤
│ .plotly.area() + style │ 0.10s │
├────────────────────────┼───────┤
│ Speedup │ ~1.4x │
└────────────────────────┴───────┘
---
flixopt/statistics_accessor.py | 101 +++++++++++++++++++++++++++++----
1 file changed, 91 insertions(+), 10 deletions(-)
diff --git a/flixopt/statistics_accessor.py b/flixopt/statistics_accessor.py
index 90ad875b7..cd9d4710c 100644
--- a/flixopt/statistics_accessor.py
+++ b/flixopt/statistics_accessor.py
@@ -145,6 +145,87 @@ def _reshape_time_for_heatmap(
return result.transpose('timestep', 'timeframe', *other_dims)
+def _style_area_as_bar(fig: go.Figure) -> None:
+ """Style area chart traces to look like bar charts with proper pos/neg stacking.
+
+ Iterates over all traces in fig.data and fig.frames (for animations),
+ setting stepped line shape, removing line borders, making fills opaque,
+ and assigning stackgroups based on whether values are positive or negative.
+
+ Handles faceting + animation combinations by building color and classification
+ maps from trace names in the base figure.
+
+ Args:
+ fig: Plotly Figure with area chart traces.
+ """
+ import plotly.express as px
+
+ default_colors = px.colors.qualitative.Plotly
+
+ # Build color map and classify traces from base figure
+ # trace.name -> color, trace.name -> 'positive'|'negative'|'mixed'|'zero'
+ color_map: dict[str, str] = {}
+ class_map: dict[str, str] = {}
+
+ for i, trace in enumerate(fig.data):
+ # Get color
+ if hasattr(trace, 'line') and trace.line and trace.line.color:
+ color_map[trace.name] = trace.line.color
+ else:
+ color_map[trace.name] = default_colors[i % len(default_colors)]
+
+ # Classify based on y values
+ y_vals = trace.y
+ if y_vals is None or len(y_vals) == 0:
+ class_map[trace.name] = 'zero'
+ else:
+ y_arr = np.asarray(y_vals)
+ y_clean = y_arr[np.abs(y_arr) > 1e-9]
+ if len(y_clean) == 0:
+ class_map[trace.name] = 'zero'
+ else:
+ has_pos = np.any(y_clean > 0)
+ has_neg = np.any(y_clean < 0)
+ if has_pos and has_neg:
+ class_map[trace.name] = 'mixed'
+ elif has_neg:
+ class_map[trace.name] = 'negative'
+ else:
+ class_map[trace.name] = 'positive'
+
+ def style_trace(trace: go.Scatter) -> None:
+ """Apply bar-like styling to a single trace."""
+ # Look up color by trace name
+ color = color_map.get(trace.name, default_colors[0])
+
+ # Look up classification
+ cls = class_map.get(trace.name, 'positive')
+
+ # Set stackgroup based on classification (positive and negative stack separately)
+ if cls in ('positive', 'negative'):
+ trace.stackgroup = cls
+ trace.fillcolor = color
+ trace.line = dict(width=0, color=color, shape='hv')
+ elif cls == 'mixed':
+ # Mixed: show as dashed line, no stacking
+ trace.stackgroup = None
+ trace.fill = None
+ trace.line = dict(width=2, color=color, shape='hv', dash='dash')
+ else: # zero
+ trace.stackgroup = None
+ trace.fill = None
+ trace.line = dict(width=0, color=color, shape='hv')
+
+ # Style main traces
+ for trace in fig.data:
+ style_trace(trace)
+
+ # Style animation frame traces
+ for frame in getattr(fig, 'frames', []) or []:
+ for trace in frame.data:
+ style_trace(trace)
+
+
# --- Helper functions ---
@@ -1529,13 +1610,13 @@ def balance(
unit_label = ds[first_var].attrs.get('unit', '')
_apply_slot_defaults(plotly_kwargs, 'balance')
- fig = ds.plotly.bar(
+ fig = ds.plotly.area(
title=f'{node} [{unit_label}]' if unit_label else node,
+ line_shape='hv',
**color_kwargs,
**plotly_kwargs,
)
- fig.update_layout(barmode='relative', bargap=0, bargroupgap=0)
- fig.update_traces(marker_line_width=0)
+ _style_area_as_bar(fig)
if show is None:
show = CONFIG.Plotting.default_show
@@ -1653,13 +1734,13 @@ def carrier_balance(
unit_label = ds[first_var].attrs.get('unit', '')
_apply_slot_defaults(plotly_kwargs, 'carrier_balance')
- fig = ds.plotly.bar(
+ fig = ds.plotly.area(
title=f'{carrier.capitalize()} Balance [{unit_label}]' if unit_label else f'{carrier.capitalize()} Balance',
+ line_shape='hv',
**color_kwargs,
**plotly_kwargs,
)
- fig.update_layout(barmode='relative', bargap=0, bargroupgap=0)
- fig.update_traces(marker_line_width=0)
+ _style_area_as_bar(fig)
if show is None:
show = CONFIG.Plotting.default_show
@@ -2249,15 +2330,15 @@ def storage(
else:
color_kwargs = _build_color_kwargs(colors, flow_labels)
- # Create stacked bar chart for flows
+ # Create stacked area chart for flows (styled as bar)
_apply_slot_defaults(plotly_kwargs, 'storage')
- fig = flow_ds.plotly.bar(
+ fig = flow_ds.plotly.area(
title=f'{storage} Operation ({unit})',
+ line_shape='hv',
**color_kwargs,
**plotly_kwargs,
)
- fig.update_layout(barmode='relative', bargap=0, bargroupgap=0)
- fig.update_traces(marker_line_width=0)
+ _style_area_as_bar(fig)
# Add charge state as line on secondary y-axis
# Only pass faceting kwargs that add_line_overlay accepts
From d1497f127aeeb8b907d9c9187ce3a0cb66d145f5 Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Fri, 16 Jan 2026 12:30:40 +0100
Subject: [PATCH 2/7] New Helper Functions
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
# Iterate over all traces (main + animation frames)
def _iter_all_traces(fig: go.Figure):
yield from fig.data
for frame in getattr(fig, 'frames', []) or []:
yield from frame.data
# Apply unified hover styling (works with any plot type)
def _apply_unified_hover(fig: go.Figure, unit: str = '', decimals: int = 1):
# Sets: name: value unit
# + hovermode='x unified' + spike lines
Updated Methods
┌───────────────────┬──────────────────────────────────────────────┐
│ Method │ Changes │
├───────────────────┼──────────────────────────────────────────────┤
│ balance() │ + _apply_unified_hover(fig, unit=unit_label) │
├───────────────────┼──────────────────────────────────────────────┤
│ carrier_balance() │ + _apply_unified_hover(fig, unit=unit_label) │
├───────────────────┼──────────────────────────────────────────────┤
│ storage() │ + _apply_unified_hover(fig, unit=unit_label) │
└───────────────────┴──────────────────────────────────────────────┘
Result
- Hover format: Solar: 45.3 kW
- Hovermode: x unified (single tooltip for all traces)
- Spikes: Gray vertical line at cursor
---
flixopt/statistics_accessor.py | 64 ++++++++++++++++++++++++++++++----
1 file changed, 57 insertions(+), 7 deletions(-)
diff --git a/flixopt/statistics_accessor.py b/flixopt/statistics_accessor.py
index cd9d4710c..cfa0f9f68 100644
--- a/flixopt/statistics_accessor.py
+++ b/flixopt/statistics_accessor.py
@@ -145,6 +145,23 @@ def _reshape_time_for_heatmap(
return result.transpose('timestep', 'timeframe', *other_dims)
+def _iter_all_traces(fig: go.Figure):
+ """Iterate over all traces in a figure, including animation frames.
+
+ Yields traces from fig.data first, then from each frame in fig.frames.
+ Useful for applying styling to all traces including those in animations.
+
+ Args:
+ fig: Plotly Figure.
+
+ Yields:
+ Each trace object from the figure.
+ """
+ yield from fig.data
+ for frame in getattr(fig, 'frames', []) or []:
+ yield from frame.data
+
+
def _style_area_as_bar(fig: go.Figure) -> None:
"""Style area chart traces to look like bar charts with proper pos/neg stacking.
@@ -216,14 +233,38 @@ def style_trace(trace: go.Scatter) -> None:
trace.fill = None
trace.line = dict(width=0, color=color, shape='hv')
- # Style main traces
- for trace in fig.data:
+ # Style all traces (main + animation frames)
+ for trace in _iter_all_traces(fig):
style_trace(trace)
- # Style animation frame traces
- for frame in getattr(fig, 'frames', []) or []:
- for trace in frame.data:
- style_trace(trace)
+
+def _apply_unified_hover(fig: go.Figure, unit: str = '', decimals: int = 1) -> None:
+ """Apply unified hover mode with clean formatting to any Plotly figure.
+
+ Sets up 'x unified' hovermode with spike lines and formats hover labels
+ as 'name: value unit'.
+
+ Works with any plot type (area, bar, line, scatter).
+
+ Args:
+ fig: Plotly Figure to style.
+ unit: Unit string to append (e.g., 'kW', 'MWh'). Empty for no unit.
+ decimals: Number of decimal places for values.
+ """
+ unit_suffix = f' {unit}' if unit else ''
+ hover_template = f'%{{fullData.name}}: %{{y:.{decimals}f}}{unit_suffix}'
+
+ # Apply to all traces (main + animation frames)
+ for trace in _iter_all_traces(fig):
+ trace.hovertemplate = hover_template
+
+ # Layout settings for unified hover
+ fig.update_layout(
+ hovermode='x unified',
+ xaxis_showspikes=True,
+ xaxis_spikecolor='gray',
+ xaxis_spikethickness=1,
+ )
# --- Helper functions ---
@@ -1617,6 +1658,7 @@ def balance(
**plotly_kwargs,
)
_style_area_as_bar(fig)
+ _apply_unified_hover(fig, unit=unit_label)
if show is None:
show = CONFIG.Plotting.default_show
@@ -1741,6 +1783,7 @@ def carrier_balance(
**plotly_kwargs,
)
_style_area_as_bar(fig)
+ _apply_unified_hover(fig, unit=unit_label)
if show is None:
show = CONFIG.Plotting.default_show
@@ -2330,15 +2373,22 @@ def storage(
else:
color_kwargs = _build_color_kwargs(colors, flow_labels)
+ # Get unit label from flow data
+ unit_label = ''
+ if flow_ds.data_vars:
+ first_var = next(iter(flow_ds.data_vars))
+ unit_label = flow_ds[first_var].attrs.get('unit', '')
+
# Create stacked area chart for flows (styled as bar)
_apply_slot_defaults(plotly_kwargs, 'storage')
fig = flow_ds.plotly.area(
- title=f'{storage} Operation ({unit})',
+ title=f'{storage} Operation [{unit_label}]' if unit_label else f'{storage} Operation',
line_shape='hv',
**color_kwargs,
**plotly_kwargs,
)
_style_area_as_bar(fig)
+ _apply_unified_hover(fig, unit=unit_label)
# Add charge state as line on secondary y-axis
# Only pass faceting kwargs that add_line_overlay accepts
From 93e30cc7bd6d418035d63c7e9f782f86eca94970 Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Wed, 21 Jan 2026 16:22:57 +0100
Subject: [PATCH 3/7] 1. _style_area_as_bar (lines 183-221): The class_map is
now built by aggregating sign info across all traces returned by
_iter_all_traces(fig), including animation frames. The color_map is still
derived from fig.data. The implementation uses a sign_flags dictionary to
incrementally update has_pos/has_neg flags for each trace.name, then
computes class_map from those aggregated flags. 2. _apply_unified_hover
(lines 271-274): Replaced the fig.update_layout(xaxis_showspikes=..., ...)
with a single fig.update_xaxes(showspikes=True, spikecolor='gray',
spikethickness=1) call so spike settings apply to all x-axes (xaxis, xaxis2,
xaxis3, ...) in faceted plots.
---
flixopt/statistics_accessor.py | 58 +++++++++++++++++++---------------
1 file changed, 32 insertions(+), 26 deletions(-)
diff --git a/flixopt/statistics_accessor.py b/flixopt/statistics_accessor.py
index 90448fd51..11f80de4e 100644
--- a/flixopt/statistics_accessor.py
+++ b/flixopt/statistics_accessor.py
@@ -180,36 +180,45 @@ def _style_area_as_bar(fig: go.Figure) -> None:
default_colors = px.colors.qualitative.Plotly
- # Build color map and classify traces from base figure
- # trace.name -> color, trace.name -> 'positive'|'negative'|'mixed'|'zero'
+ # Build color map from base figure traces
+ # trace.name -> color
color_map: dict[str, str] = {}
- class_map: dict[str, str] = {}
-
for i, trace in enumerate(fig.data):
- # Get color
if hasattr(trace, 'line') and trace.line and trace.line.color:
color_map[trace.name] = trace.line.color
else:
color_map[trace.name] = default_colors[i % len(default_colors)]
- # Classify based on y values
+ # Classify traces by aggregating sign info across ALL traces (including animation frames)
+ # trace.name -> 'positive'|'negative'|'mixed'|'zero'
+ class_map: dict[str, str] = {}
+ sign_flags: dict[str, dict[str, bool]] = {} # trace.name -> {'has_pos': bool, 'has_neg': bool}
+
+ for trace in _iter_all_traces(fig):
+ if trace.name not in sign_flags:
+ sign_flags[trace.name] = {'has_pos': False, 'has_neg': False}
+
y_vals = trace.y
- if y_vals is None or len(y_vals) == 0:
- class_map[trace.name] = 'zero'
- else:
+ if y_vals is not None and len(y_vals) > 0:
y_arr = np.asarray(y_vals)
y_clean = y_arr[np.abs(y_arr) > 1e-9]
- if len(y_clean) == 0:
- class_map[trace.name] = 'zero'
- else:
- has_pos = np.any(y_clean > 0)
- has_neg = np.any(y_clean < 0)
- if has_pos and has_neg:
- class_map[trace.name] = 'mixed'
- elif has_neg:
- class_map[trace.name] = 'negative'
- else:
- class_map[trace.name] = 'positive'
+ if len(y_clean) > 0:
+ if np.any(y_clean > 0):
+ sign_flags[trace.name]['has_pos'] = True
+ if np.any(y_clean < 0):
+ sign_flags[trace.name]['has_neg'] = True
+
+ # Compute class_map from aggregated sign flags
+ for name, flags in sign_flags.items():
+ has_pos, has_neg = flags['has_pos'], flags['has_neg']
+ if has_pos and has_neg:
+ class_map[name] = 'mixed'
+ elif has_neg:
+ class_map[name] = 'negative'
+ elif has_pos:
+ class_map[name] = 'positive'
+ else:
+ class_map[name] = 'zero'
def style_trace(trace: go.Scatter) -> None:
"""Apply bar-like styling to a single trace."""
@@ -260,12 +269,9 @@ def _apply_unified_hover(fig: go.Figure, unit: str = '', decimals: int = 1) -> N
trace.hovertemplate = hover_template
# Layout settings for unified hover
- fig.update_layout(
- hovermode='x unified',
- xaxis_showspikes=True,
- xaxis_spikecolor='gray',
- xaxis_spikethickness=1,
- )
+ fig.update_layout(hovermode='x unified')
+ # Apply spike settings to all x-axes (for faceted plots with xaxis, xaxis2, xaxis3, etc.)
+ fig.update_xaxes(showspikes=True, spikecolor='gray', spikethickness=1)
# --- Helper functions ---
From 30e4ba7602a8a9906ea9ac5e726d0538bfe8f72a Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Thu, 22 Jan 2026 15:17:17 +0100
Subject: [PATCH 4/7] Update plotting deps
---
pyproject.toml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pyproject.toml b/pyproject.toml
index 0ebb15d99..ac39fd48a 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -47,7 +47,7 @@ dependencies = [
# Visualization
"matplotlib >= 3.5.2, < 4",
"plotly >= 5.15.0, < 7",
- "xarray_plotly >= 0.0.3, < 1",
+ "xarray_plotly >= 0.0.10, < 1",
]
[project.optional-dependencies]
From 0d04aa8f726294c24f5b4abb6cc70c516a5b7045 Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Thu, 22 Jan 2026 16:03:41 +0100
Subject: [PATCH 5/7] Update plotting deps and use xarray-plotly for fast_bar
---
flixopt/statistics_accessor.py | 120 ++-------------------------------
1 file changed, 6 insertions(+), 114 deletions(-)
diff --git a/flixopt/statistics_accessor.py b/flixopt/statistics_accessor.py
index 11f80de4e..0fcdd6a6d 100644
--- a/flixopt/statistics_accessor.py
+++ b/flixopt/statistics_accessor.py
@@ -27,6 +27,7 @@
import pandas as pd
import plotly.graph_objects as go
import xarray as xr
+from xarray_plotly.figures import update_traces
from .color_processing import ColorType, hex_to_rgba, process_colors
from .config import CONFIG
@@ -146,108 +147,6 @@ def _reshape_time_for_heatmap(
return result.transpose('timestep', 'timeframe', *other_dims)
-def _iter_all_traces(fig: go.Figure):
- """Iterate over all traces in a figure, including animation frames.
-
- Yields traces from fig.data first, then from each frame in fig.frames.
- Useful for applying styling to all traces including those in animations.
-
- Args:
- fig: Plotly Figure.
-
- Yields:
- Each trace object from the figure.
- """
- yield from fig.data
- for frame in getattr(fig, 'frames', []) or []:
- yield from frame.data
-
-
-def _style_area_as_bar(fig: go.Figure) -> None:
- """Style area chart traces to look like bar charts with proper pos/neg stacking.
-
- Iterates over all traces in fig.data and fig.frames (for animations),
- setting stepped line shape, removing line borders, making fills opaque,
- and assigning stackgroups based on whether values are positive or negative.
-
- Handles faceting + animation combinations by building color and classification
- maps from trace names in the base figure.
-
- Args:
- fig: Plotly Figure with area chart traces.
- """
- import plotly.express as px
-
- default_colors = px.colors.qualitative.Plotly
-
- # Build color map from base figure traces
- # trace.name -> color
- color_map: dict[str, str] = {}
- for i, trace in enumerate(fig.data):
- if hasattr(trace, 'line') and trace.line and trace.line.color:
- color_map[trace.name] = trace.line.color
- else:
- color_map[trace.name] = default_colors[i % len(default_colors)]
-
- # Classify traces by aggregating sign info across ALL traces (including animation frames)
- # trace.name -> 'positive'|'negative'|'mixed'|'zero'
- class_map: dict[str, str] = {}
- sign_flags: dict[str, dict[str, bool]] = {} # trace.name -> {'has_pos': bool, 'has_neg': bool}
-
- for trace in _iter_all_traces(fig):
- if trace.name not in sign_flags:
- sign_flags[trace.name] = {'has_pos': False, 'has_neg': False}
-
- y_vals = trace.y
- if y_vals is not None and len(y_vals) > 0:
- y_arr = np.asarray(y_vals)
- y_clean = y_arr[np.abs(y_arr) > 1e-9]
- if len(y_clean) > 0:
- if np.any(y_clean > 0):
- sign_flags[trace.name]['has_pos'] = True
- if np.any(y_clean < 0):
- sign_flags[trace.name]['has_neg'] = True
-
- # Compute class_map from aggregated sign flags
- for name, flags in sign_flags.items():
- has_pos, has_neg = flags['has_pos'], flags['has_neg']
- if has_pos and has_neg:
- class_map[name] = 'mixed'
- elif has_neg:
- class_map[name] = 'negative'
- elif has_pos:
- class_map[name] = 'positive'
- else:
- class_map[name] = 'zero'
-
- def style_trace(trace: go.Scatter) -> None:
- """Apply bar-like styling to a single trace."""
- # Look up color by trace name
- color = color_map.get(trace.name, default_colors[0])
-
- # Look up classification
- cls = class_map.get(trace.name, 'positive')
-
- # Set stackgroup based on classification (positive and negative stack separately)
- if cls in ('positive', 'negative'):
- trace.stackgroup = cls
- trace.fillcolor = color
- trace.line = dict(width=0, color=color, shape='hv')
- elif cls == 'mixed':
- # Mixed: show as dashed line, no stacking
- trace.stackgroup = None
- trace.fill = None
- trace.line = dict(width=2, color=color, shape='hv', dash='dash')
- else: # zero
- trace.stackgroup = None
- trace.fill = None
- trace.line = dict(width=0, color=color, shape='hv')
-
- # Style all traces (main + animation frames)
- for trace in _iter_all_traces(fig):
- style_trace(trace)
-
-
def _apply_unified_hover(fig: go.Figure, unit: str = '', decimals: int = 1) -> None:
"""Apply unified hover mode with clean formatting to any Plotly figure.
@@ -264,9 +163,8 @@ def _apply_unified_hover(fig: go.Figure, unit: str = '', decimals: int = 1) -> N
unit_suffix = f' {unit}' if unit else ''
hover_template = f'%{{fullData.name}}: %{{y:.{decimals}f}}{unit_suffix}'
- # Apply to all traces (main + animation frames)
- for trace in _iter_all_traces(fig):
- trace.hovertemplate = hover_template
+ # Apply to all traces (main + animation frames) using xarray_plotly helper
+ update_traces(fig, hovertemplate=hover_template)
# Layout settings for unified hover
fig.update_layout(hovermode='x unified')
@@ -1648,13 +1546,11 @@ def balance(
unit_label = ds[first_var].attrs.get('unit', '')
_apply_slot_defaults(plotly_kwargs, 'balance')
- fig = ds.plotly.area(
+ fig = ds.plotly.fast_bar(
title=f'{node} [{unit_label}]' if unit_label else node,
- line_shape='hv',
**color_kwargs,
**plotly_kwargs,
)
- _style_area_as_bar(fig)
_apply_unified_hover(fig, unit=unit_label)
if show is None:
@@ -1773,13 +1669,11 @@ def carrier_balance(
unit_label = ds[first_var].attrs.get('unit', '')
_apply_slot_defaults(plotly_kwargs, 'carrier_balance')
- fig = ds.plotly.area(
+ fig = ds.plotly.fast_bar(
title=f'{carrier.capitalize()} Balance [{unit_label}]' if unit_label else f'{carrier.capitalize()} Balance',
- line_shape='hv',
**color_kwargs,
**plotly_kwargs,
)
- _style_area_as_bar(fig)
_apply_unified_hover(fig, unit=unit_label)
if show is None:
@@ -2378,13 +2272,11 @@ def storage(
# Create stacked area chart for flows (styled as bar)
_apply_slot_defaults(plotly_kwargs, 'storage')
- fig = flow_ds.plotly.area(
+ fig = flow_ds.plotly.fast_bar(
title=f'{storage} Operation [{unit_label}]' if unit_label else f'{storage} Operation',
- line_shape='hv',
**color_kwargs,
**plotly_kwargs,
)
- _style_area_as_bar(fig)
_apply_unified_hover(fig, unit=unit_label)
# Add charge state as line on secondary y-axis
From 6fbf6b2638d72fa9d599778e135ffa33e446dea2 Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Thu, 22 Jan 2026 17:30:10 +0100
Subject: [PATCH 6/7] Removed both redundant line_shape='hv' parameters -
fast_bar() handles that internally
---
flixopt/statistics_accessor.py | 2 --
1 file changed, 2 deletions(-)
diff --git a/flixopt/statistics_accessor.py b/flixopt/statistics_accessor.py
index 536e55b30..55259b0ba 100644
--- a/flixopt/statistics_accessor.py
+++ b/flixopt/statistics_accessor.py
@@ -1550,7 +1550,6 @@ def balance(
_apply_slot_defaults(plotly_kwargs, 'balance')
fig = ds.plotly.fast_bar(
title=f'{node} [{unit_label}]' if unit_label else node,
- line_shape='hv',
**color_kwargs,
**plotly_kwargs,
)
@@ -1674,7 +1673,6 @@ def carrier_balance(
_apply_slot_defaults(plotly_kwargs, 'carrier_balance')
fig = ds.plotly.fast_bar(
title=f'{carrier.capitalize()} Balance [{unit_label}]' if unit_label else f'{carrier.capitalize()} Balance',
- line_shape='hv',
**color_kwargs,
**plotly_kwargs,
)
From e91d937eaf16d9cd425151157c0e93d0732dd71c Mon Sep 17 00:00:00 2001
From: FBumann <117816358+FBumann@users.noreply.github.com>
Date: Thu, 22 Jan 2026 17:34:09 +0100
Subject: [PATCH 7/7] Update CHANGELOG.md
---
CHANGELOG.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 01165c249..4f8f8e128 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -301,6 +301,7 @@ fs.transform.cluster(
- `FlowSystem.weights` returns `dict[str, xr.DataArray]` (unit weights instead of `1.0` float fallback)
- `FlowSystemDimensions` type now includes `'cluster'`
+- `statistics.plot.balance()`, `carrier_balance()`, and `storage()` now use `xarray_plotly.fast_bar()` internally (styled stacked areas for better performance)
### 🗑️ Deprecated