@@ -652,22 +652,17 @@ def plot_heatmap(
652652 """
653653 dataarray = self .solution [variable_name ]
654654
655- dataarray , suffix_parts = _apply_indexer_to_data (dataarray , indexer )
656-
657- # Create name
658- suffix = '--' + '-' .join (suffix_parts ) if suffix_parts else ''
659- name = variable_name if not suffix_parts else f'{ variable_name } --{ '-' .join (suffix_parts )} ' if suffix else variable_name
660-
661655 return plot_heatmap (
662656 dataarray = dataarray ,
663- name = name ,
657+ name = variable_name ,
664658 folder = self .folder ,
665659 heatmap_timeframes = heatmap_timeframes ,
666660 heatmap_timesteps_per_frame = heatmap_timesteps_per_frame ,
667661 color_map = color_map ,
668662 save = save ,
669663 show = show ,
670664 engine = engine ,
665+ indexer = indexer ,
671666 )
672667
673668 def plot_network (
@@ -848,20 +843,19 @@ def plot_node_balance(
848843 show: Whether to show the plot or not.
849844 colors: The colors to use for the plot. See `flixopt.plotting.ColorType` for options.
850845 engine: The engine to use for plotting. Can be either 'plotly' or 'matplotlib'.
851- scenario: The scenario to plot. Defaults to the first scenario. Has no effect without scenarios present
846+ indexer: Optional selection dict, e.g., {'scenario': 'base', 'year': 2024}.
847+ If None, uses first value for each dimension (except time).
852848 mode: The mode to use for the dataset. Can be 'flow_rate' or 'flow_hours'.
853849 - 'flow_rate': Returns the flow_rates of the Node.
854850 - 'flow_hours': Returns the flow_hours of the Node. [flow_hours(t) = flow_rate(t) * dt(t)]. Renames suffixes to |flow_hours.
855851 drop_suffix: Whether to drop the suffix from the variable names.
856852 """
857853 ds = self .node_balance (with_last_timestep = True , mode = mode , drop_suffix = drop_suffix )
858854
859- title = f'{ self .label } (flow rates)' if mode == 'flow_rate' else f'{ self .label } (flow hours)'
855+ ds , suffix_parts = _apply_indexer_to_data (ds , indexer )
856+ suffix = '--' + '-' .join (suffix_parts ) if suffix_parts else ''
860857
861- if 'scenario' in ds .indexes :
862- chosen_scenario = scenario or self ._calculation_results .scenarios [0 ]
863- ds = ds .sel (scenario = chosen_scenario ).drop_vars ('scenario' )
864- title = f'{ title } - { chosen_scenario } '
858+ title = f'{ self .label } (flow rates){ suffix } ' if mode == 'flow_rate' else f'{ self .label } (flow hours){ suffix } '
865859
866860 if engine == 'plotly' :
867861 figure_like = plotting .with_plotly (
@@ -911,8 +905,8 @@ def plot_node_balance_pie(
911905 save: Whether to save the figure.
912906 show: Whether to show the figure.
913907 engine: Plotting engine to use. Only 'plotly' is implemented atm.
914- scenario: If scenarios are present: The scenario to plot. If None, the first scenario is used .
915- drop_suffix: Whether to drop the suffix from the variable names .
908+ indexer: Optional selection dict, e.g., {' scenario': 'base', 'year': 2024} .
909+ If None, uses first value for each dimension .
916910 """
917911 inputs = sanitize_dataset (
918912 ds = self .solution [self .inputs ] * self ._calculation_results .hours_per_timestep ,
@@ -928,16 +922,15 @@ def plot_node_balance_pie(
928922 zero_small_values = True ,
929923 drop_suffix = '|' ,
930924 )
931- inputs = inputs .sum ('time' )
932- outputs = outputs .sum ('time' )
933925
934- title = f'{ self .label } (total flow hours)'
926+ inputs , suffix_parts = _apply_indexer_to_data (inputs , indexer )
927+ outputs , suffix_parts = _apply_indexer_to_data (outputs , indexer )
928+ suffix = '--' + '-' .join (suffix_parts ) if suffix_parts else ''
935929
936- if 'scenario' in inputs .indexes :
937- chosen_scenario = scenario or self ._calculation_results .scenarios [0 ]
938- inputs = inputs .sel (scenario = chosen_scenario ).drop_vars ('scenario' )
939- outputs = outputs .sel (scenario = chosen_scenario ).drop_vars ('scenario' )
940- title = f'{ title } - { chosen_scenario } '
930+ title = f'{ self .label } (total flow hours){ suffix } '
931+
932+ inputs = inputs .sum ('time' )
933+ outputs = outputs .sum ('time' )
941934
942935 if engine == 'plotly' :
943936 figure_like = plotting .dual_pie_with_plotly (
@@ -1060,7 +1053,8 @@ def plot_charge_state(
10601053 colors: The c
10611054 engine: Plotting engine to use. Only 'plotly' is implemented atm.
10621055 style: The plotting mode for the flow_rate
1063- scenario: The scenario to plot. Defaults to the first scenario. Has no effect without scenarios present
1056+ indexer: Optional selection dict, e.g., {'scenario': 'base', 'year': 2024}.
1057+ If None, uses first value for each dimension.
10641058
10651059 Raises:
10661060 ValueError: If the Component is not a Storage.
@@ -1071,18 +1065,18 @@ def plot_charge_state(
10711065 ds = self .node_balance (with_last_timestep = True )
10721066 charge_state = self .charge_state
10731067
1074- scenario_suffix = ''
1075- if 'scenario' in ds . indexes :
1076- chosen_scenario = scenario or self . _calculation_results . scenarios [ 0 ]
1077- ds = ds . sel ( scenario = chosen_scenario ). drop_vars ( 'scenario' )
1078- charge_state = charge_state . sel ( scenario = chosen_scenario ). drop_vars ( 'scenario' )
1079- scenario_suffix = f'-- { chosen_scenario } '
1068+ ds , suffix_parts = _apply_indexer_to_data ( ds , indexer )
1069+ charge_state , suffix_parts = _apply_indexer_to_data ( charge_state , indexer )
1070+ suffix = '--' + '-' . join ( suffix_parts ) if suffix_parts else ''
1071+
1072+ title = f'Operation Balance of { self . label } { suffix } '
1073+
10801074 if engine == 'plotly' :
10811075 fig = plotting .with_plotly (
10821076 ds .to_dataframe (),
10831077 colors = colors ,
10841078 style = style ,
1085- title = f'Operation Balance of { self . label } { scenario_suffix } ' ,
1079+ title = title ,
10861080 )
10871081
10881082 # TODO: Use colors for charge state?
@@ -1098,7 +1092,7 @@ def plot_charge_state(
10981092 ds .to_dataframe (),
10991093 colors = colors ,
11001094 style = style ,
1101- title = f'Operation Balance of { self . label } { scenario_suffix } ' ,
1095+ title = title ,
11021096 )
11031097
11041098 charge_state = charge_state .to_dataframe ()
@@ -1108,7 +1102,7 @@ def plot_charge_state(
11081102
11091103 return plotting .export_figure (
11101104 fig ,
1111- default_path = self ._calculation_results .folder / f' { self . label } (charge state) { scenario_suffix } ' ,
1105+ default_path = self ._calculation_results .folder / title ,
11121106 default_filetype = '.html' ,
11131107 user_path = None if isinstance (save , bool ) else pathlib .Path (save ),
11141108 show = show ,
@@ -1331,6 +1325,7 @@ def plot_heatmap(
13311325 save : Union [bool , pathlib .Path ] = False ,
13321326 show : bool = True ,
13331327 engine : plotting .PlottingEngine = 'plotly' ,
1328+ indexer : Optional [Dict [str , Any ]] = None ,
13341329):
13351330 """
13361331 Plots a heatmap of the solution of a variable.
@@ -1346,6 +1341,10 @@ def plot_heatmap(
13461341 show: Whether to show the plot or not.
13471342 engine: The engine to use for plotting. Can be either 'plotly' or 'matplotlib'.
13481343 """
1344+ dataarray , suffix_parts = _apply_indexer_to_data (dataarray , indexer , drop = True )
1345+ suffix = '--' + '-' .join (suffix_parts ) if suffix_parts else ''
1346+ name = name if not suffix_parts else name + suffix
1347+
13491348 heatmap_data = plotting .heat_map_data_from_df (
13501349 dataarray .to_dataframe (name ), heatmap_timeframes , heatmap_timesteps_per_frame , 'ffill'
13511350 )
@@ -1609,7 +1608,7 @@ def apply_filter(array, coord_name: str, coord_values: Union[Any, List[Any]]):
16091608 return da
16101609
16111610
1612- def _apply_indexer_to_data (data : xr .DataArray , indexer : Optional [Dict [str , Any ]] = None ):
1611+ def _apply_indexer_to_data (data : Union [ xr .DataArray , xr . Dataset ], indexer : Optional [Dict [str , Any ]] = None , drop = False ):
16131612 """
16141613 Apply indexer selection or auto-select first values for non-time dimensions.
16151614
@@ -1618,23 +1617,23 @@ def _apply_indexer_to_data(data: xr.DataArray, indexer: Optional[Dict[str, Any]]
16181617 indexer: Optional selection dict
16191618
16201619 Returns:
1621- Tuple of (selected_data, suffix_parts_list )
1620+ Tuple of (selected_data, selection_string )
16221621 """
1623- suffix_parts = []
1622+ selection_string = []
16241623
16251624 if indexer is not None :
16261625 # User provided indexer
1627- data = data .sel (indexer )
1628- suffix_parts .extend (f"{ v } [{ k } ]" for k , v in indexer .items ())
1626+ data = data .sel (indexer , drop = drop )
1627+ selection_string .extend (f"{ v } [{ k } ]" for k , v in indexer .items ())
16291628 else :
16301629 # Auto-select first value for each dimension except 'time'
16311630 selection = {}
16321631 for dim in data .dims :
16331632 if dim != 'time' and dim in data .coords :
16341633 first_value = data .coords [dim ].values [0 ]
16351634 selection [dim ] = first_value
1636- suffix_parts .append (f"{ first_value } [{ dim } ]" )
1635+ selection_string .append (f"{ first_value } [{ dim } ]" )
16371636 if selection :
1638- data = data .sel (selection )
1637+ data = data .sel (selection , drop = drop )
16391638
1640- return data , suffix_parts
1639+ return data , selection_string
0 commit comments