Skip to content

Commit 4a492f4

Browse files
FBumannclaude
andauthored
Remove fxplot accessor in favor of external ploty accessor (#560)
* Remove fxplot accessor in favor of external ploty accessor * Moved stats accessor to new module name * Reuse fxstats.duration_curve * Update notebooks * Update notebooks * Remove auto * Set x='time' for most plots * Remove slot params * Improve color handling * 1. statistics_accessor.py - Removed explicit slot parameters (facet_col, facet_row, animation_frame) from all plotting methods - Added _build_color_kwargs() helper to consistently handle dict, list, and string colors - Refactored all methods to use _build_color_kwargs for consistent color handling - Updated _prepare_for_heatmap() to support reshape='auto' (default) - Slot parameters are now passed through via **plotly_kwargs 2. clustering/base.py - Removed explicit slot parameters from compare() and heatmap() methods - Parameters passed through via **plotly_kwargs 3. comparison.py - Completely refactored ComparisonStatisticsPlot: - Removed the complex generic _plot() method - Each method now has explicit parameters with proper docstrings - Uses _build_color_kwargs consistently - Changed ds.fxplot to ds.plotly - Changed stacked_bar to bar with barmode='relative' Key Design Patterns 1. Color handling: All methods use _build_color_kwargs(colors, labels) which handles: - dict → color_discrete_map - list → color_discrete_sequence - str (colorscale name) → converted to color_discrete_map via process_colors() 2. Dimension slots: Let xarray-plotly handle assignments by default, only set explicitly when needed (e.g., x='time' for time series) 3. Consistent API: All methods now have explicit parameters instead of **kwargs everywhere * ⏺ All done! Here's a summary of the changes: Changes to flixopt/comparison.py: 1. Added import: from xarray_plotly import SLOT_ORDERS 2. Added helper function: _CASE_SLOTS = frozenset(slot for slots in SLOT_ORDERS.values() for slot in slots) def _set_case_default(kwargs: dict, slot: str) -> None: """Set default slot for 'case' dimension if not already assigned elsewhere.""" if not any(kwargs.get(s) == 'case' for s in _CASE_SLOTS): kwargs.setdefault(slot, 'case') 3. Updated all 9 plotting methods with sensible defaults: | Method | Default Slot | |-------------------|--------------------------------| | balance() | facet_col='case' | | carrier_balance() | facet_col='case' | | storage() | facet_col='case' | | flows() | line_dash='case' | | charge_states() | line_dash='case' | | duration_curve() | line_dash='case' | | sizes() | color='case' + barmode='group' | | effects() | color='case' + barmode='group' | | heatmap() | facet_col='case' | The defaults are "safe" - if the user explicitly assigns 'case' to any slot, the default won't override it. * ⏺ Add sensible slot defaults for comparison plots and block unused slots Comparison plots now automatically assign the 'case' dimension to appropriate slots based on plot type: - Stacked bars (balance, carrier_balance, storage): facet_col='case' - Line charts (flows, charge_states, duration_curve): line_dash='case' - Grouped bars (sizes, effects): color='case' with barmode='group' - Heatmaps: facet_col='case' Uses _set_case_default() helper that checks if 'case' is already assigned elsewhere before setting the default, preventing conflicts. Also blocks unused slots in StatisticsPlotAccessor to prevent xarray_plotly from auto-assigning dimensions to visually unhelpful slots: - Time-series bars: pattern_shape=None - Line charts: symbol=None All defaults use setdefault() so users can override when needed. * Add sensible slot defaults for plotting methods Use setdefault() for all dimension-to-slot assignments, allowing user overrides while providing sensible defaults. Comparison plots (ComparisonStatisticsPlot): - Stacked bars: x='time', facet_col='case' - Line charts: x='time', line_dash='case' - Grouped bars: x='variable', color='case', barmode='group' - Duration curve: x='duration_pct'/'duration' - Heatmaps: facet_col='case' Statistics plots (StatisticsPlotAccessor): - Time-series bars: x='time', pattern_shape=None (blocked) - Line charts: x='time', symbol=None (blocked) - Duration curve: x='duration_pct'/'duration' - Sizes: x='variable' - Effects: x computed from 'by' parameter Uses SLOT_ORDERS from xarray_plotly to dynamically detect all valid slots when checking for 'case' dimension conflicts. All defaults use setdefault() so users can override when needed. * Updated _SLOT_DEFAULTS dict with proper plotly kwargs (no custom keys): _SLOT_DEFAULTS: dict[str, dict[str, str | None]] = { 'balance': {'x': 'time', 'color': 'variable', 'pattern_shape': None, 'facet_col': 'case'}, 'carrier_balance': {'x': 'time', 'color': 'variable', 'pattern_shape': None, 'facet_col': 'case'}, 'flows': {'x': 'time', 'color': 'variable', 'symbol': None, 'line_dash': 'case'}, 'charge_states': {'x': 'time', 'color': 'variable', 'symbol': None, 'line_dash': 'case'}, 'storage': {'x': 'time', 'color': 'variable', 'pattern_shape': None, 'facet_col': 'case'}, 'sizes': {'x': 'variable', 'color': 'case'}, 'duration_curve': {'symbol': None, 'line_dash': 'case'}, 'effects': {'color': 'case'}, 'heatmap': {'facet_col': 'case'}, } Updated _apply_slot_defaults() helper to detect if 'case' is already assigned: - If user has already set 'case' to any slot, skips the default case assignment - Otherwise applies all defaults via setdefault() (user overridable) Updated all 9 methods (balance, carrier_balance, flows, charge_states, storage, duration_curve, sizes, effects, heatmap) to use: _apply_slot_defaults(plotly_kwargs, 'method_name') * Move slot defaults inline into each comparison plot method Improves code locality by defining defaults dict directly in each method rather than in a module-level lookup table. * Build job (PRs & pushes): - Only runs fast notebooks (~1 min total) - Slow notebooks are skipped Deploy job (releases only): - Runs fast notebooks first - Then runs slow notebooks (~10 min extra) - Full documentation with all notebooks executed Files changed: - .github/workflows/docs.yaml - updated both execution steps - docs/notebooks/slow_notebooks.txt - created with the 2 slow notebooks To add/remove slow notebooks in the future, just edit slow_notebooks.txt. * Fix storage plot passing invalid kwargs to add_line_overlay Filter plotly_kwargs to only pass faceting-related kwargs (x, facet_col, facet_row, animation_frame) to add_line_overlay, avoiding TypeError from bar-chart-specific kwargs like pattern_shape. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Fix storage plot passing invalid kwargs to add_line_overlay Filter plotly_kwargs to only pass faceting-related kwargs (x, facet_col, facet_row, animation_frame) to add_line_overlay, avoiding TypeError from bar-chart-specific kwargs like pattern_shape. Applied to both statistics_accessor.py and comparison.py. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * 1. Identified slow notebooks - 08b-rolling-horizon (5 min) and 08c2-clustering-storage-modes (5 min) 2. Created slow_notebooks.txt - config file to exclude them from regular CI 3. Updated CI - fast notebooks run on every build, slow ones only on release 4. Switched to Plotly CDN - notebooks now ~98% smaller (4.8MB → 79KB each) * - Type: tuple[str, str] | Literal['auto'] | None - Docstring: "Use 'auto' to reshape to ('D', 'h') if data has a time index" * Apply slot defaults pattern to clustering and fix color handling - Add _apply_slot_defaults helper for clustering plots - Use inline defaults dict in compare() and clusters() methods - Remove explicit slot parameters (color, line_dash, facet_col) from signatures - Pass all slot assignments through **plotly_kwargs with sensible defaults - Fix color handling: use _build_color_kwargs for proper ColorType support - Apply default colorscale for heatmap when colors=None Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Add other plot to notebook * Add plotly template * Add plotly template * Fix notebook * Add docs about ploty express * More about plotly templates * Fixed. Now n is calculated explicitly from the first entry in sorted_vars rather than relying on the implicit loop variable from the last iteration. * Template System - 'flixopt' template registered on import (no side effects) - CONFIG.use_theme() to explicitly activate - Fixed .title() for colorscale names + fallback handling Color Handling Simplification - process_colors() reads CONFIG internally - Removed 8+ explicit default_colorscale=CONFIG... calls - Heatmaps use template default when colors not specified Heatmap Default - Changed reshape=None → reshape=('D', 'h') - More useful out-of-the-box daily pattern visualization Documentation - Added "Plotly Express Internals" section covering slots, colorscales, templates - Fixed markdown code block language specifiers * All fixes have been applied and verified. Here's a summary of the changes: Changes Made 1. CHANGELOG.md (line 145) - Before: #### FXPlot Accessor (#548) - After: #### Plotly Accessor (#548) 2. CHANGELOG.md (lines 169-170) - Before: Two duplicate .plotly.bar() entries (grouped/stacked) - After: Single entry: .plotly.bar() | Bar charts (use barmode='group' or 'relative' for stacked) 3. flixopt/stats_accessor.py - to_duration_curve() (lines 43-66) - Before: Used da.values (breaks dask laziness) and tuple assignment (loses attrs) - After: Uses xr.apply_ufunc() with dask='parallelized' to preserve: - Dask lazy evaluation - DataArray attributes - Dataset attributes - Variables without time dimension (unchanged) 4. docs/user-guide/recipes/plotting-custom-data.md - Added note explaining that .plotly and .fxstats accessors are automatically registered on import * 1. flixopt/stats_accessor.py (line 67-69) - Preserve non-time coordinates # Before sorted_ds = xr.Dataset(sorted_vars, attrs=self._ds.attrs) # After non_time_coords = {k: v for k, v in self._ds.coords.items() if k != 'time'} sorted_ds = xr.Dataset(sorted_vars, coords=non_time_coords, attrs=self._ds.attrs) 2. flixopt/config.py (line 820-821) - Re-register template on use_theme() # Re-register template to pick up any config changes made after import _register_flixopt_template() pio.templates.default = 'plotly_white+flixopt' Now if a user modifies CONFIG.Plotting.default_qualitative_colorscale after import but before calling use_theme(), the template will use the updated value. 3. flixopt/config.py (line 958-963) - Log warning on colorscale fallback if colorway is None: logging.getLogger(__name__).warning( f"Colorscale '{CONFIG.Plotting.default_qualitative_colorscale}' not found in " f"plotly.express.colors.qualitative, falling back to 'Plotly'. " f"Available: {[n for n in dir(colors.qualitative) if not n.startswith('_')]}" ) colorway = colors.qualitative.Plotly * ⏺ Much cleaner. The simplified implementation: - Preserves DataArray attrs via attrs=da.attrs - Preserves variable-level coords via coords={k: v for k, v in da.coords.items() if k != 'time'} - Preserves dataset-level coords and attrs separately --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 7e3a118 commit 4a492f4

22 files changed

Lines changed: 1169 additions & 1989 deletions

CHANGELOG.md

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -53,15 +53,15 @@ Until here -->
5353

5454
## [6.0.0] - Upcoming
5555

56-
**Summary**: Major release featuring a complete rewrite of the clustering/aggregation system with tsam integration, new `fxplot` plotting accessor, FlowSystem comparison tools, and removal of deprecated v5.0 classes.
56+
**Summary**: Major release featuring a complete rewrite of the clustering/aggregation system with tsam integration, new `plotly` plotting accessor, FlowSystem comparison tools, and removal of deprecated v5.0 classes.
5757

5858
!!! warning "Breaking Changes"
5959
This release removes `ClusteredOptimization` and `ClusteringParameters` which were deprecated in v5.0.0. Use `flow_system.transform.cluster()` instead. See [Migration](#migration-from-clusteredoptimization) below.
6060

6161
### Key Features
6262

6363
- **Clustering/Aggregation Rework** (#549, #552) - Complete rewrite with tsam integration, inter-cluster storage linking, and 4 storage modes
64-
- **fxplot Plotting Accessor** (#548) - Universal xarray plotting with automatic faceting
64+
- **plotly Plotting Accessor** (#548) - Universal xarray plotting with automatic faceting
6565
- **Comparison Module** (#550) - Compare multiple FlowSystems side-by-side
6666
- **Improved Notebooks** (#542, #551) - Better tutorial data and faster CI execution
6767

@@ -142,21 +142,21 @@ charge_state = fs_expanded.solution['SeasonalPit|charge_state']
142142
Use `'cyclic'` for short-term storage like batteries or hot water tanks where only daily patterns matter.
143143
Use `'independent'` for quick estimates when storage behavior isn't critical.
144144

145-
#### FXPlot Accessor (#548)
145+
#### Plotly Accessor (#548)
146146

147147
New global xarray accessors for universal plotting with automatic faceting and smart dimension handling. Works on any xarray Dataset, not just flixopt results.
148148

149149
```python
150150
import flixopt as fx # Registers accessors automatically
151151

152152
# Plot any xarray Dataset with automatic faceting
153-
dataset.fxplot.bar(x='component')
154-
dataset.fxplot.area(x='time')
155-
dataset.fxplot.heatmap(x='time', y='component')
156-
dataset.fxplot.line(x='time', facet_col='scenario')
153+
dataset.plotly.bar(x='component')
154+
dataset.plotly.area(x='time')
155+
dataset.plotly.imshow(x='time', y='component')
156+
dataset.plotly.line(x='time', facet_col='scenario')
157157

158158
# DataArray support
159-
data_array.fxplot.line()
159+
data_array.plotly.line()
160160

161161
# Statistics transformations
162162
dataset.fxstats.to_duration_curve()
@@ -166,13 +166,12 @@ dataset.fxstats.to_duration_curve()
166166

167167
| Method | Description |
168168
|--------|-------------|
169-
| `.fxplot.bar()` | Grouped bar charts |
170-
| `.fxplot.stacked_bar()` | Stacked bar charts |
171-
| `.fxplot.line()` | Line charts with faceting |
172-
| `.fxplot.area()` | Stacked area charts |
173-
| `.fxplot.heatmap()` | Heatmap visualizations |
174-
| `.fxplot.scatter()` | Scatter plots |
175-
| `.fxplot.pie()` | Pie charts with faceting |
169+
| `.plotly.bar()` | Bar charts (use `barmode='group'` or `'relative'` for stacked) |
170+
| `.plotly.line()` | Line charts with faceting |
171+
| `.plotly.area()` | Stacked area charts |
172+
| `.plotly.imshow()` | Heatmap visualizations |
173+
| `.plotly.scatter()` | Scatter plots |
174+
| `.plotly.pie()` | Pie charts with faceting |
176175
| `.fxstats.to_duration_curve()` | Transform to duration curve format |
177176

178177
**Key Features**:
@@ -195,7 +194,7 @@ comp = fx.Comparison([fs1, fs2, fs3], names=['baseline', 'low_cost', 'high_eff']
195194

196195
# Side-by-side plots (auto-facets by 'case' dimension)
197196
comp.statistics.plot.balance('Heat')
198-
comp.statistics.flow_rates.fxplot.line()
197+
comp.statistics.flow_rates.plotly.line()
199198

200199
# Access combined data with 'case' dimension
201200
comp.solution # xr.Dataset
@@ -310,7 +309,6 @@ Note: `topology.plot()` now renders a Sankey diagram. The old PyVis visualizatio
310309
- **08c2-clustering-storage-modes.ipynb** - Comparison of all 4 storage cluster modes
311310
- **08d-clustering-multiperiod.ipynb** - Clustering with periods and scenarios
312311
- **08e-clustering-internals.ipynb** - Understanding clustering internals
313-
- **fxplot_accessor_demo.ipynb** - Demo of the new fxplot accessor
314312

315313
**Improved Tutorials:**
316314

docs/notebooks/02-heat-system.ipynb

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,13 +72,13 @@
7272
"metadata": {},
7373
"outputs": [],
7474
"source": [
75-
"# Visualize the demand pattern with fxplot\n",
75+
"# Visualize the demand pattern with plotly\n",
7676
"demand_ds = xr.Dataset(\n",
7777
" {\n",
7878
" 'Heat Demand': xr.DataArray(heat_demand, dims=['time'], coords={'time': timesteps}),\n",
7979
" }\n",
8080
")\n",
81-
"demand_ds.fxplot.line(title='Office Heat Demand Profile')"
81+
"demand_ds.plotly.line(x='time', title='Office Heat Demand Profile')"
8282
]
8383
},
8484
{
@@ -98,13 +98,13 @@
9898
"metadata": {},
9999
"outputs": [],
100100
"source": [
101-
"# Visualize time-of-use gas prices with fxplot\n",
101+
"# Visualize time-of-use gas prices with plotly\n",
102102
"price_ds = xr.Dataset(\n",
103103
" {\n",
104104
" 'Gas Price': xr.DataArray(gas_price, dims=['time'], coords={'time': timesteps}),\n",
105105
" }\n",
106106
")\n",
107-
"price_ds.fxplot.line(title='Gas Price [€/kWh]')"
107+
"price_ds.plotly.line(x='time', title='Gas Price [€/kWh]')"
108108
]
109109
},
110110
{
@@ -375,7 +375,13 @@
375375
]
376376
}
377377
],
378-
"metadata": {},
378+
"metadata": {
379+
"kernelspec": {
380+
"display_name": "Python 3 (ipykernel)",
381+
"language": "python",
382+
"name": "python3"
383+
}
384+
},
379385
"nbformat": 4,
380386
"nbformat_minor": 5
381387
}

docs/notebooks/03-investment-optimization.ipynb

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,14 +100,14 @@
100100
"metadata": {},
101101
"outputs": [],
102102
"source": [
103-
"# Visualize profiles with fxplot\n",
103+
"# Visualize profiles with plotly\n",
104104
"profiles = xr.Dataset(\n",
105105
" {\n",
106106
" 'Solar Profile [kW/kW]': xr.DataArray(solar_profile, dims=['time'], coords={'time': timesteps}),\n",
107107
" 'Pool Demand [kW]': xr.DataArray(pool_demand, dims=['time'], coords={'time': timesteps}),\n",
108108
" }\n",
109109
")\n",
110-
"profiles.fxplot.line(title='Solar and Pool Profiles', height=300)"
110+
"profiles.plotly.line(x='time', title='Solar and Pool Profiles', height=300)"
111111
]
112112
},
113113
{
@@ -424,7 +424,13 @@
424424
]
425425
}
426426
],
427-
"metadata": {},
427+
"metadata": {
428+
"kernelspec": {
429+
"display_name": "Python 3 (ipykernel)",
430+
"language": "python",
431+
"name": "python3"
432+
}
433+
},
428434
"nbformat": 4,
429435
"nbformat_minor": 5
430436
}

docs/notebooks/04-operational-constraints.ipynb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,13 +85,13 @@
8585
"metadata": {},
8686
"outputs": [],
8787
"source": [
88-
"# Visualize the demand with fxplot\n",
88+
"# Visualize the demand with plotly\n",
8989
"demand_ds = xr.Dataset(\n",
9090
" {\n",
9191
" 'Steam Demand [kW]': xr.DataArray(steam_demand, dims=['time'], coords={'time': timesteps}),\n",
9292
" }\n",
9393
")\n",
94-
"demand_ds.fxplot.line(title='Factory Steam Demand')"
94+
"demand_ds.plotly.line(x='time', title='Factory Steam Demand')"
9595
]
9696
},
9797
{
@@ -250,7 +250,7 @@
250250
" }\n",
251251
")\n",
252252
"\n",
253-
"status_ds.fxplot.line(title='Main Boiler Operation', height=300)"
253+
"status_ds.plotly.line(x='time', title='Main Boiler Operation', height=300)"
254254
]
255255
},
256256
{

docs/notebooks/05-multi-carrier-system.ipynb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,15 +101,15 @@
101101
"metadata": {},
102102
"outputs": [],
103103
"source": [
104-
"# Visualize demands and prices with fxplot\n",
104+
"# Visualize demands and prices with plotly\n",
105105
"profiles = xr.Dataset(\n",
106106
" {\n",
107107
" 'Electricity Demand [kW]': xr.DataArray(electricity_demand, dims=['time'], coords={'time': timesteps}),\n",
108108
" 'Heat Demand [kW]': xr.DataArray(heat_demand, dims=['time'], coords={'time': timesteps}),\n",
109109
" 'Elec. Buy Price [EUR/kWh]': xr.DataArray(elec_buy_price, dims=['time'], coords={'time': timesteps}),\n",
110110
" }\n",
111111
")\n",
112-
"profiles.fxplot.line(title='Hospital Energy Profiles', height=300)"
112+
"profiles.plotly.line(x='time', title='Hospital Energy Profiles', height=300)"
113113
]
114114
},
115115
{

docs/notebooks/06a-time-varying-parameters.ipynb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,14 +93,14 @@
9393
"metadata": {},
9494
"outputs": [],
9595
"source": [
96-
"# Visualize input profiles with fxplot\n",
96+
"# Visualize input profiles with plotly\n",
9797
"profiles = xr.Dataset(\n",
9898
" {\n",
9999
" 'Outdoor Temp [°C]': xr.DataArray(outdoor_temp, dims=['time'], coords={'time': timesteps}),\n",
100100
" 'Heat Demand [kW]': xr.DataArray(heat_demand, dims=['time'], coords={'time': timesteps}),\n",
101101
" }\n",
102102
")\n",
103-
"profiles.fxplot.line(title='Temperature and Heat Demand Profiles', height=300)"
103+
"profiles.plotly.line(x='time', title='Temperature and Heat Demand Profiles', height=300)"
104104
]
105105
},
106106
{

docs/notebooks/07-scenarios-and-periods.ipynb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@
100100
"metadata": {},
101101
"outputs": [],
102102
"source": [
103-
"# Visualize demand scenarios with fxplot\n",
103+
"# Visualize demand scenarios with plotly\n",
104104
"demand_ds = xr.Dataset(\n",
105105
" {\n",
106106
" scenario: xr.DataArray(\n",
@@ -111,7 +111,7 @@
111111
" for scenario in scenarios\n",
112112
" }\n",
113113
")\n",
114-
"demand_ds.fxplot.line(title='Heat Demand by Scenario')"
114+
"demand_ds.plotly.line(x='time', title='Heat Demand by Scenario')"
115115
]
116116
},
117117
{

docs/notebooks/08c-clustering.ipynb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@
7777
" 'Electricity Price': flow_system.components['GridBuy'].outputs[0].effects_per_flow_hour['costs'],\n",
7878
" }\n",
7979
")\n",
80-
"input_ds.fxplot.line(facet_row='variable', title='One Month of Input Data')"
80+
"input_ds.plotly.line(x='time', facet_row='variable', title='One Month of Input Data')"
8181
]
8282
},
8383
{
@@ -461,7 +461,7 @@
461461
" }\n",
462462
")\n",
463463
"\n",
464-
"comparison_ds.fxplot.line(facet_col='variable', color='method', title='Heat Production Comparison')"
464+
"comparison_ds.plotly.line(x='time', facet_col='variable', color='method', title='Heat Production Comparison')"
465465
]
466466
},
467467
{

docs/notebooks/08e-clustering-internals.ipynb

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,27 @@
120120
"id": "9",
121121
"metadata": {},
122122
"outputs": [],
123+
"source": [
124+
"# Use a different approach of visualizing the data using normalize heatmaps\n",
125+
"ds = fs_clustered.clustering.plot.compare(data_only=True).data\n",
126+
"\n",
127+
"ds_normalized = (ds - ds.min()) / (ds.max() - ds.min())\n",
128+
"ds_normalized.to_array().plotly.imshow(\n",
129+
" x='time',\n",
130+
" animation_frame='representation',\n",
131+
" zmin=0,\n",
132+
" zmax=1,\n",
133+
" color_continuous_scale='viridis',\n",
134+
" title='Normalized Comparison',\n",
135+
")"
136+
]
137+
},
138+
{
139+
"cell_type": "code",
140+
"execution_count": null,
141+
"id": "10",
142+
"metadata": {},
143+
"outputs": [],
123144
"source": [
124145
"# Compare specific variables only\n",
125146
"fs_clustered.clustering.plot.compare(variables='HeatDemand(Q_th)|fixed_relative_profile')"
@@ -128,7 +149,7 @@
128149
{
129150
"cell_type": "code",
130151
"execution_count": null,
131-
"id": "10",
152+
"id": "11",
132153
"metadata": {},
133154
"outputs": [],
134155
"source": [
@@ -139,19 +160,19 @@
139160
{
140161
"cell_type": "code",
141162
"execution_count": null,
142-
"id": "11",
163+
"id": "12",
143164
"metadata": {},
144165
"outputs": [],
145166
"source": [
146167
"# View typical period profiles for each cluster\n",
147168
"# Each line represents a cluster's representative day\n",
148-
"fs_clustered.clustering.plot.clusters(variables='HeatDemand(Q_th)|fixed_relative_profile')"
169+
"fs_clustered.clustering.plot.clusters(variables='HeatDemand(Q_th)|fixed_relative_profile', color='cluster')"
149170
]
150171
},
151172
{
152173
"cell_type": "code",
153174
"execution_count": null,
154-
"id": "12",
175+
"id": "13",
155176
"metadata": {},
156177
"outputs": [],
157178
"source": [
@@ -161,7 +182,7 @@
161182
},
162183
{
163184
"cell_type": "markdown",
164-
"id": "13",
185+
"id": "14",
165186
"metadata": {},
166187
"source": [
167188
"## Expanding Aggregated Data\n",
@@ -173,7 +194,7 @@
173194
{
174195
"cell_type": "code",
175196
"execution_count": null,
176-
"id": "14",
197+
"id": "15",
177198
"metadata": {},
178199
"outputs": [],
179200
"source": [
@@ -192,7 +213,7 @@
192213
},
193214
{
194215
"cell_type": "markdown",
195-
"id": "15",
216+
"id": "16",
196217
"metadata": {},
197218
"source": [
198219
"## Summary\n",
@@ -236,7 +257,7 @@
236257
},
237258
{
238259
"cell_type": "markdown",
239-
"id": "16",
260+
"id": "17",
240261
"metadata": {},
241262
"source": [
242263
"## Cluster Weights\n",
@@ -252,7 +273,7 @@
252273
{
253274
"cell_type": "code",
254275
"execution_count": null,
255-
"id": "17",
276+
"id": "18",
256277
"metadata": {},
257278
"outputs": [],
258279
"source": [
@@ -262,7 +283,7 @@
262283
},
263284
{
264285
"cell_type": "markdown",
265-
"id": "18",
286+
"id": "19",
266287
"metadata": {},
267288
"source": [
268289
"## Solution Expansion\n",
@@ -273,7 +294,7 @@
273294
{
274295
"cell_type": "code",
275296
"execution_count": null,
276-
"id": "19",
297+
"id": "20",
277298
"metadata": {},
278299
"outputs": [],
279300
"source": [
@@ -287,7 +308,13 @@
287308
]
288309
}
289310
],
290-
"metadata": {},
311+
"metadata": {
312+
"kernelspec": {
313+
"display_name": "Python 3 (ipykernel)",
314+
"language": "python",
315+
"name": "python3"
316+
}
317+
},
291318
"nbformat": 4,
292319
"nbformat_minor": 5
293320
}

0 commit comments

Comments
 (0)