Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
120 changes: 6 additions & 114 deletions flixopt/statistics_accessor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.

Expand All @@ -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'<b>%{{fullData.name}}</b>: %{{y:.{decimals}f}}{unit_suffix}<extra></extra>'

# 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')
Expand Down Expand Up @@ -1650,13 +1548,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,
)
Comment on lines 1551 to 1555
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 | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, let's find the fast_bar function definition
find . -type f -name "*.py" | head -20

Repository: flixOpt/flixopt

Length of output: 174


🏁 Script executed:

# Search for fast_bar definition in the codebase
rg -n "def fast_bar" --type py

Repository: flixOpt/flixopt

Length of output: 41


🏁 Script executed:

# Check the context of the lines mentioned in the review
sed -n '1545,1560p' flixopt/statistics_accessor.py

Repository: flixOpt/flixopt

Length of output: 596


🏁 Script executed:

# Also check the other locations mentioned
sed -n '1670,1685p' flixopt/statistics_accessor.py
sed -n '2275,2290p' flixopt/statistics_accessor.py

Repository: flixOpt/flixopt

Length of output: 1359


🏁 Script executed:

# Search for previous implementation using area and _style_area_as_bar
rg -n "_style_area_as_bar|\.area\(" flixopt/statistics_accessor.py -B 3 -A 3

Repository: flixOpt/flixopt

Length of output: 41


🏁 Script executed:

# Check where fast_bar comes from - look for imports
rg -n "fast_bar|import.*plotly" flixopt/statistics_accessor.py | head -30

Repository: flixOpt/flixopt

Length of output: 217


🏁 Script executed:

# Try to find fast_bar in xarray-plotly or plotly packages
rg -n "def fast_bar" . --type py 2>/dev/null | head -20

Repository: flixOpt/flixopt

Length of output: 41


🌐 Web query:

xarray-plotly fast_bar function parameters documentation

💡 Result:

I couldn’t find any official function named fast_bar in xarray or Plotly docs. Xarray’s plotting API wraps Matplotlib (DataArray.plot and friends) and Plotly’s bar function is plotly.express.bar (see its full parameter list). If you meant a third‑party helper, there’s an independent xarray_plotly project that wraps plotly.express for xarray (GitHub). Sources:

  • xarray plotting documentation (DataArray.plot / plotting guide). [1]
  • plotly.express.bar parameter reference. [2]
  • xarray_plotly (third‑party accessor on GitHub). [3]

If you want, tell me which behavior/parameters you need and I’ll map those to plotly.express.bar arguments or inspect the xarray_plotly code.


🏁 Script executed:

# Check if there's a plotly accessor module in the repo
find . -type f -name "*plotly*" | grep -v __pycache__

Repository: flixOpt/flixopt

Length of output: 95


🏁 Script executed:

# Look for the plotly accessor and its methods
fd -e py "plotly" | head -20

Repository: flixOpt/flixopt

Length of output: 41


🏁 Script executed:

# Search for xarray accessor registration or plotly accessor definition
rg -n "@.*accessor|register_.*accessor" . --type py

Repository: flixOpt/flixopt

Length of output: 129


🏁 Script executed:

# Look for plotly accessor import or definition
rg -n "plotly" flixopt/*.py | grep -i "import\|from\|accessor"

Repository: flixOpt/flixopt

Length of output: 8584


🏁 Script executed:

# Check requirements files for xarray_plotly dependency
find . -type f \( -name "requirements*.txt" -o -name "setup.py" -o -name "setup.cfg" -o -name "pyproject.toml" \) -exec grep -l "xarray" {} \;

Repository: flixOpt/flixopt

Length of output: 75


🏁 Script executed:

# Check if there's a local plotly accessor module or definition
find . -type f -name "*.py" -exec grep -l "plotly.*accessor\|fast_bar" {} \;

Repository: flixOpt/flixopt

Length of output: 91


🏁 Script executed:

# Look for the specific accessor setup or where plotly methods might be defined
rg -n "class.*Plotly|def fast_bar" . --type py

Repository: flixOpt/flixopt

Length of output: 41


🏁 Script executed:

# Check pyproject.toml for xarray_plotly version
cat pyproject.toml | grep -A 5 -B 5 "xarray_plotly"

Repository: flixOpt/flixopt

Length of output: 339


🌐 Web query:

xarray_plotly fast_bar function source code github

💡 Result:

I couldn't find a public GitHub function named fast_bar in an xarray_plotly repository. Do you have a link, package name, or example (repo owner) I should search for?


🌐 Web query:

xarray-plotly fast_bar barmode line_shape parameter

💡 Result:

Short answer: xarray's Plotly-based bar plotting ultimately calls Plotly Express' bar routine, so you can pass barmode (e.g. "group", "overlay", "relative"/"stack") via kwargs to control stacking/grouping. The line_shape parameter is for line plots (px.line / line-type traces) — it does not apply to bar/fast_bar.

Sources:

  • px.bar docs (barmode parameter). [1]
  • px.line / line_shape docs (line_shape applies to lines, not bars). [2]
  • Plotly Express overview (PX is the backend used by high-level calls and accepts xarray inputs). [3]

References:
[1] plotly.express.bar documentation.
[2] plotly.express.line / line_shape documentation.
[3] Plotly Express overview (PX accepts xarray/numpy/pandas inputs).


🌐 Web query:

xarray_plotly pypi package repository github

💡 Result:

  • PyPI / package: xarray-plotly (install via pip as xarray-plotly). [1]
  • Source / repository: GitHub — FBumann/xarray_plotly. [2]

Sources:
[1] piwheels / xarray-plotly (package listing).
[2] Plotly community post linking the GitHub repo (FBumann/xarray_plotly).


🌐 Web query:

plotly express bar line_shape parameter invalid

💡 Result:

Cause: px.bar creates Bar traces (no line_shape) and Plotly Express may auto-create Scatter/Scattergl traces when drawing lines; the line_shape option applies to Scatter (plotly.graph_objects.Scatter) and only 'scatter' (SVG) supports 'spline' — Scattergl does not. See the px/Scatter docs and community discussion. [1][2]

Fixes:

  • If you want a curved/step line, add a separate Scatter trace (go.Scatter) and set its line.shape (or mode/line dict) instead of passing line_shape to px.bar. Example: add_traces(go.Scatter(x=..., y=..., mode='lines', line=dict(shape='spline'))). [1]
  • If using px.line or px.scatter and you need 'spline' with many points, force SVG rendering so spline is allowed: set render_mode='svg' (default may switch to 'scattergl' for performance). [1][2]
  • For a horizontal/constant line on a bar chart, simpler: fig.add_hline(y=...) or fig.add_shape(...). [3]

Sources:

  1. Plotly Express / Scatter doc (line_shape options). [1]
  2. Plotly community thread explaining Scattergl lacks 'spline' and render_mode='svg' workaround. [2]
  3. StackOverflow examples for adding lines to bar charts (add_hline / go.Scatter). [3]

🏁 Script executed:

# Check if xarray_plotly is vendored or can be searched in pip
python3 << 'EOF'
import subprocess
result = subprocess.run(['pip', 'show', 'xarray_plotly'], capture_output=True, text=True)
print(result.stdout)
print(result.stderr)
EOF

Repository: flixOpt/flixopt

Length of output: 145


🌐 Web query:

"xarray_plotly" "fast_bar" documentation api

💡 Result:

Do you want the xarray_plotly fast_bar docs/usage (link + examples) or the API reference (function signature and parameters)? Which one should I fetch?


🌐 Web query:

site:github.com FBumann xarray_plotly fast_bar

💡 Result:

I found the GitHub repository "FBumann/xarray_plotly" (interactive Plotly Express plotting for xarray) — latest release v0.0.10 (Jan 22, 2026) and docs. [1][2]

I did not find any public GitHub results for "fast_bar" in that repo or tied to FBumann.

Sources:

  • FBumann/xarray_plotly GitHub repo. [1]
  • Plotly community post announcing the package. [2]

🌐 Web query:

xarray_plotly fast_bar method signature parameters source

💡 Result:

Do you mean the fast_bar method in the xarray-plotly package (the xarray_plotly accessor)? I can fetch the exact method signature and source — confirm and I’ll look it up.


🌐 Web query:

FBumann xarray_plotly figures.py fast_bar source code

💡 Result:

Do you want me to (a) fetch and show the contents of figures.py (fast_bar) from FBumann/xarray_plotly, or (b) provide the repository link and instructions to view it yourself?


🌐 Web query:

xarray_plotly plotly accessor bar method line_shape

💡 Result:

Short answer: plotly.express.bar (and the underlying Bar trace) does not accept a line_shape argument. line_shape is a parameter for line/area/line-like Plotly Express functions (px.line, px.area, etc.) and accepts 'linear', 'spline', 'hv', 'vh', 'hvh', or 'vhv'. See the px.bar signature (no line_shape) and the px docs describing line_shape. [1][2]

Sources:

  • px.bar signature (no line_shape). [1]
  • px docs showing line_shape and allowed values for line/area-like functions. [2]

🏁 Script executed:

# Try to check the xarray_plotly package directly via pip
python3 << 'EOF'
try:
    import xarray_plotly
    import inspect
    # Try to find fast_bar
    if hasattr(xarray_plotly, 'fast_bar'):
        print("Found fast_bar in xarray_plotly")
        print(inspect.signature(xarray_plotly.fast_bar))
    else:
        print("fast_bar not found in xarray_plotly module directly")
        print("Available attributes:", dir(xarray_plotly))
except Exception as e:
    print(f"Error: {e}")
    print("Trying to import and check xarray_plotly.figures...")
    try:
        from xarray_plotly import figures
        print("xarray_plotly.figures available")
        print("Available in figures:", [x for x in dir(figures) if not x.startswith('_')])
    except Exception as e2:
        print(f"Error with figures: {e2}")
EOF

Repository: flixOpt/flixopt

Length of output: 201


🌐 Web query:

xarray_plotly wraps plotly.express bar function documentation

💡 Result:

Yes — the xarray_plotly package provides a plotting accessor that wraps most plotly.express functions (including px.bar). See the package announcement on the Plotly community and the Plotly Express px.bar reference for the underlying API it wraps. [1][2]

Sources:

  • Package announcement / GitHub link (xarray_plotly). [1]
  • plotly.express.bar documentation. [2]

Remove line_shape='hv' from fast_bar calls — not supported for bar charts.

The fast_bar method wraps plotly.express.bar, which does not accept the line_shape parameter. This parameter is only valid for line and area plots. Passing it to a bar chart will be silently ignored or cause an error.

Stacking behavior for mixed positive/negative values via barmode='relative' is already supported and will work correctly through **plotly_kwargs.

Remove line_shape='hv' from all three locations: 1551–1556, 1675–1680, and 2279–2283.

🤖 Prompt for AI Agents
In `@flixopt/statistics_accessor.py` around lines 1551 - 1556, The
ds.plotly.fast_bar calls are passing an unsupported parameter line_shape='hv';
remove the line_shape argument from each ds.plotly.fast_bar invocation (the ones
that assign to fig) so the call becomes fig = ds.plotly.fast_bar(title=...,
**color_kwargs, **plotly_kwargs), relying on plotly_kwargs (e.g., barmode) for
stacking/behavior instead.

_style_area_as_bar(fig)
_apply_unified_hover(fig, unit=unit_label)

if show is None:
Expand Down Expand Up @@ -1775,13 +1671,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:
Expand Down Expand Up @@ -2380,13 +2274,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
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down