|
27 | 27 | import pandas as pd |
28 | 28 | import plotly.graph_objects as go |
29 | 29 | import xarray as xr |
30 | | -from xarray_plotly.figures import update_traces |
| 30 | +from xarray_plotly.figures import add_secondary_y, update_traces |
31 | 31 |
|
32 | 32 | from .color_processing import ColorType, hex_to_rgba, process_colors |
33 | 33 | from .config import CONFIG |
@@ -298,102 +298,6 @@ def _filter_small_variables(ds: xr.Dataset, threshold: float | None) -> xr.Datas |
298 | 298 | return ds[keep] if keep else ds |
299 | 299 |
|
300 | 300 |
|
301 | | -def add_line_overlay( |
302 | | - fig: go.Figure, |
303 | | - da: xr.DataArray, |
304 | | - *, |
305 | | - x: str | None = None, |
306 | | - facet_col: str | None = None, |
307 | | - facet_row: str | None = None, |
308 | | - animation_frame: str | None = None, |
309 | | - color: str | None = None, |
310 | | - line_color: str = 'black', |
311 | | - name: str | None = None, |
312 | | - secondary_y: bool = False, |
313 | | - y_title: str | None = None, |
314 | | - showlegend: bool = True, |
315 | | -) -> None: |
316 | | - """Add line traces on top of existing figure, optionally on secondary y-axis. |
317 | | -
|
318 | | - This function creates line traces from a DataArray and adds them to an existing |
319 | | - figure. When using secondary_y=True, it correctly handles faceted figures by |
320 | | - creating matching secondary axes for each primary axis. |
321 | | -
|
322 | | - Args: |
323 | | - fig: Plotly figure to add traces to. |
324 | | - da: DataArray to plot as lines. |
325 | | - x: Dimension to use for x-axis. If None, auto-detects 'time' or first dim. |
326 | | - facet_col: Dimension for column facets (must match primary figure). |
327 | | - facet_row: Dimension for row facets (must match primary figure). |
328 | | - animation_frame: Dimension for animation slider (must match primary figure). |
329 | | - color: Dimension to color by (creates multiple lines). |
330 | | - line_color: Color for lines when color is None. |
331 | | - name: Legend name for the traces. |
332 | | - secondary_y: If True, plot on secondary y-axis. |
333 | | - y_title: Title for the y-axis (secondary if secondary_y=True). |
334 | | - showlegend: Whether to show legend entries. |
335 | | - """ |
336 | | - if da.size == 0: |
337 | | - return |
338 | | - |
339 | | - # Auto-detect x dimension if not specified |
340 | | - if x is None: |
341 | | - x = 'time' if 'time' in da.dims else da.dims[0] |
342 | | - |
343 | | - # Build kwargs for line plot, only passing facet params if specified |
344 | | - line_kwargs: dict[str, Any] = {'x': x} |
345 | | - if color is not None: |
346 | | - line_kwargs['color'] = color |
347 | | - if facet_col is not None: |
348 | | - line_kwargs['facet_col'] = facet_col |
349 | | - if facet_row is not None: |
350 | | - line_kwargs['facet_row'] = facet_row |
351 | | - if animation_frame is not None: |
352 | | - line_kwargs['animation_frame'] = animation_frame |
353 | | - |
354 | | - # Create line figure with same facets |
355 | | - line_fig = da.plotly.line(**line_kwargs) |
356 | | - |
357 | | - if secondary_y: |
358 | | - # Get the primary y-axes from the bar figure to create matching secondary axes |
359 | | - primary_yaxes = [key for key in fig.layout if key.startswith('yaxis')] |
360 | | - |
361 | | - # For each primary y-axis, create a secondary y-axis. |
362 | | - # Secondary axis numbering strategy: |
363 | | - # - Primary axes are named 'yaxis', 'yaxis2', 'yaxis3', etc. |
364 | | - # - We use +100 offset (yaxis101, yaxis102, ...) to avoid conflicts |
365 | | - # - Each secondary axis 'overlays' its corresponding primary axis |
366 | | - for i, primary_key in enumerate(sorted(primary_yaxes, key=lambda x: int(x[5:]) if x[5:] else 0)): |
367 | | - primary_num = primary_key[5:] if primary_key[5:] else '1' |
368 | | - secondary_num = int(primary_num) + 100 |
369 | | - secondary_key = f'yaxis{secondary_num}' |
370 | | - secondary_anchor = f'x{primary_num}' if primary_num != '1' else 'x' |
371 | | - |
372 | | - fig.layout[secondary_key] = dict( |
373 | | - overlaying=f'y{primary_num}' if primary_num != '1' else 'y', |
374 | | - side='right', |
375 | | - showgrid=False, |
376 | | - title=y_title if i == len(primary_yaxes) - 1 else None, |
377 | | - anchor=secondary_anchor, |
378 | | - ) |
379 | | - |
380 | | - # Add line traces with correct axis assignments |
381 | | - for i, trace in enumerate(line_fig.data): |
382 | | - if name is not None: |
383 | | - trace.name = name |
384 | | - if color is None: |
385 | | - trace.line = dict(color=line_color, width=2) |
386 | | - |
387 | | - if secondary_y: |
388 | | - primary_num = i + 1 if i > 0 else 1 |
389 | | - trace.yaxis = f'y{primary_num + 100}' |
390 | | - |
391 | | - trace.showlegend = showlegend and (i == 0) |
392 | | - if name is not None: |
393 | | - trace.legendgroup = name |
394 | | - fig.add_trace(trace) |
395 | | - |
396 | | - |
397 | 301 | def _filter_by_carrier(ds: xr.Dataset, carrier: str | list[str] | None) -> xr.Dataset: |
398 | 302 | """Filter dataset variables by carrier attribute. |
399 | 303 |
|
@@ -2489,19 +2393,19 @@ def storage( |
2489 | 2393 | _apply_unified_hover(fig, unit=unit_label) |
2490 | 2394 |
|
2491 | 2395 | # Add charge state as line on secondary y-axis |
2492 | | - # Only pass faceting kwargs that add_line_overlay accepts |
2493 | | - overlay_kwargs = { |
| 2396 | + # Build line figure with same faceting kwargs |
| 2397 | + line_kwargs = { |
2494 | 2398 | k: v for k, v in plotly_kwargs.items() if k in ('x', 'facet_col', 'facet_row', 'animation_frame') |
2495 | 2399 | } |
2496 | | - add_line_overlay( |
2497 | | - fig, |
2498 | | - charge_da, |
2499 | | - line_color=charge_state_color, |
2500 | | - name='charge_state', |
2501 | | - secondary_y=True, |
2502 | | - y_title='Charge State', |
2503 | | - **overlay_kwargs, |
2504 | | - ) |
| 2400 | + line_fig = charge_da.plotly.line(color=None, **line_kwargs) |
| 2401 | + # Style the line traces |
| 2402 | + for trace in line_fig.data: |
| 2403 | + trace.name = 'charge_state' |
| 2404 | + trace.line = dict(color=charge_state_color, width=2) |
| 2405 | + trace.showlegend = trace == line_fig.data[0] # Only first trace in legend |
| 2406 | + trace.legendgroup = 'charge_state' |
| 2407 | + # Combine using xarray_plotly's add_secondary_y which handles facets correctly |
| 2408 | + fig = add_secondary_y(fig, line_fig, secondary_y_title='Charge State') |
2505 | 2409 |
|
2506 | 2410 | if show is None: |
2507 | 2411 | show = CONFIG.Plotting.default_show |
|
0 commit comments