• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

rl-institut / multi-vector-simulator / 4084543790

pending completion
4084543790

push

github

GitHub
Merge pull request #952 from rl-institut/fix/chp_component

152 of 152 new or added lines in 9 files covered. (100.0%)

5899 of 7665 relevant lines covered (76.96%)

0.77 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

41.03
/src/multi_vector_simulator/F1_plotting.py
1
r"""
2
Module F1 - Plotting
3
====================
4

5
Module F1 describes all the functions that create plots.
6

7
- creating graphs for energy flows
8
- creating bar chart for capacity
9
- creating pie chart for cost data
10
- creating network graph for the model brackets only working on Ubuntu
11
"""
12

13
import logging
1✔
14
import os
1✔
15
import textwrap
1✔
16

17
import pandas as pd
1✔
18

19
PLOTLY_INSTALLED = False
1✔
20
try:
1✔
21
    import plotly.graph_objs as go
1✔
22
    import plotly.express as px
1✔
23

24
    PLOTLY_INSTALLED = True
1✔
25
except ModuleNotFoundError:
×
26
    logging.warning(
×
27
        "You have installed the minimal configuration, if you want to output images "
28
        "please run the command <TODO>"
29
    )
30

31
import graphviz
1✔
32
import oemof
1✔
33
import oemof.solph as solph
1✔
34

35
from multi_vector_simulator.utils.constants import (
1✔
36
    PROJECT_DATA,
37
    ECONOMIC_DATA,
38
    LABEL,
39
    OUTPUT_FOLDER,
40
    PATHS_TO_PLOTS,
41
    PLOT_SANKEY,
42
    SOC,
43
)
44

45
from multi_vector_simulator.utils.constants_json_strings import (
1✔
46
    PROJECT_NAME,
47
    SCENARIO_NAME,
48
    CURR,
49
    KPI,
50
    UNIT,
51
    ENERGY_CONSUMPTION,
52
    TIMESERIES,
53
    DISPATCHABILITY,
54
    ENERGY_PRODUCTION,
55
    SIMULATION_SETTINGS,
56
    OPTIMIZED_ADD_CAP,
57
    TOTAL_FLOW,
58
    ANNUAL_TOTAL_FLOW,
59
    PEAK_FLOW,
60
    AVERAGE_FLOW,
61
    KPI_SCALAR_MATRIX,
62
    OPTIMIZED_FLOWS,
63
    DEMANDS,
64
    RESOURCES,
65
    TIME_INDEX,
66
)
67

68
from multi_vector_simulator.E1_process_results import (
1✔
69
    convert_demand_to_dataframe,
70
    convert_costs_to_dataframe,
71
)
72

73

74
def convert_plot_data_to_dataframe(plot_data_dict, data_type):
1✔
75
    """
76

77
    Parameters
78
    ----------
79
    plot_data_dict: dict
80
        timeseries for either demand or supply
81

82
    data_type: str
83
        one of DEMANDS or RESOURCES
84

85
    Returns
86
    -------
87
    df: pandas:`pandas.DataFrame<frame>`,
88
        timeseries for plotting
89
    """
90

91
    # Later, this dataframe can be passed to a function directly make the graphs with Plotly
92
    df = pd.DataFrame.from_dict(plot_data_dict[data_type], orient="columns")
×
93

94
    # Change the index of the dataframe
95
    df.reset_index(level=0, inplace=True)
×
96
    # Rename the timestamp column from 'index' to 'timestamp'
97
    df = df.rename(columns={"index": "timestamp"})
×
98
    return df
×
99

100

101
def extract_plot_data_and_title(dict_values, df_dem=None):
1✔
102
    """Dataframe used for the plots of demands and resources timeseries in the report
103

104
    Parameters
105
    ----------
106
    dict_values: dict
107
        output values of MVS
108

109
    df_dem: :class:`pandas.DataFrame<frame>`
110
        summarized demand information for each demand
111

112
    Returns
113
    -------
114
    :class:`pandas.DataFrame<frame>`
115

116
    """
117
    if df_dem is None:
×
118
        df_dem = convert_demand_to_dataframe(dict_values)
×
119

120
    # Collect the keys of various resources (PV, Wind, etc.)
121
    resources = dict_values[ENERGY_PRODUCTION].copy()
×
122
    res_keys = [k for k in resources.keys() if resources[k][DISPATCHABILITY] is False]
×
123

124
    # Gather all the keys of the various plots for later use in the graphOptions.csv
125
    dict_for_plots = {DEMANDS: {}, RESOURCES: {}}
×
126
    dict_plot_labels = {}
×
127

128
    # Add all the demands to the dict_for_plots dictionary, including the timeseries values
129
    for demand in df_dem.Demands:
×
130
        dict_for_plots[DEMANDS].update(
×
131
            {demand: dict_values[ENERGY_CONSUMPTION][demand][TIMESERIES]}
132
        )
133
        dict_plot_labels.update(
×
134
            {demand: dict_values[ENERGY_CONSUMPTION][demand][LABEL]}
135
        )
136

137
    # Add all the resources to the dict_for_plots dictionary, including the timeseries values
138
    for resource in res_keys:
×
139
        dict_for_plots[RESOURCES].update(
×
140
            {resource: dict_values[ENERGY_PRODUCTION][resource][TIMESERIES]}
141
        )
142
        dict_plot_labels.update(
×
143
            {resource: dict_values[ENERGY_PRODUCTION][resource][LABEL]}
144
        )
145

146
    return dict_for_plots, dict_plot_labels
×
147

148

149
def fixed_width_text(text, char_num=10):
1✔
150
    """Add linebreaks every char_num characters in a given text.
151

152
    Parameters
153
    ----------
154
    text: obj:'str'
155
        text to apply the linebreaks
156
    char_num: obj:'int'
157
        max number of characters in a line before a line break
158
        Default: 10
159
    Returns
160
    -------
161
    obj:'str'
162
        the text with line breaks after every char_num characters
163

164
    """
165
    # total number of characters in the text
166
    text_length = len(text)
1✔
167
    # integer number of lines of `char_num` character
168
    n_lines = int(text_length / char_num)
1✔
169
    # number of character in the last line
170
    last_line_length = text_length % char_num
1✔
171

172
    # split the text in lines of `char_num` character
173
    split_text = []
1✔
174
    for i in range(n_lines):
1✔
175
        split_text.append(text[(i * char_num) : ((i + 1) * char_num)])
1✔
176

177
    # I if the last line is not empty
178
    if n_lines > 0:
1✔
179
        if last_line_length > 0:
1✔
180
            split_text.append(text[((i + 1) * char_num) :])
1✔
181
        answer = "\n".join(split_text)
1✔
182
    else:
183
        answer = text
1✔
184
    return answer
1✔
185

186

187
class ESGraphRenderer:
1✔
188
    def __init__(
1✔
189
        self,
190
        energy_system=None,
191
        filepath="network",
192
        img_format=None,
193
        legend=True,
194
        txt_width=10,
195
        txt_fontsize=10,
196
        **kwargs,
197
    ):
198
        """Draw the energy system with Graphviz.
199

200
        Parameters
201
        ----------
202
        energy_system: `oemof.solph.network.EnergySystem`
203
            The oemof energy stystem
204

205
        filepath: str
206
            path, where the rendered result shall be saved, if an extension is provided, the format
207
            will be automatically adapted except if the `img_format` argument is provided
208
            Default: "network"
209

210
        img_format: str
211
            extension of the available image formats of graphviz (e.g "png", "svg", "pdf", ... )
212
            Default: "pdf"
213

214
        legend: bool
215
            specify, whether a legend will be added to the graph or not
216
            Default: False
217

218
        txt_width: int
219
            max number of characters in a line before a line break
220
            Default: 10
221

222
         txt_fontsize: int
223
            fontsize of the image's text (components labels)
224
            Default: 10
225

226
        Returns
227
        -------
228
        None:
229
        render the generated dot graph in the filepath
230

231
        Notes
232
        -----
233
        When new oemof-solph asset types are added to the available types in the MVS, this function needs to be updated, so that it can render the asset in the graph.
234
        """
235
        file_name, file_ext = os.path.splitext(filepath)
1✔
236

237
        self.energy_system = energy_system
1✔
238

239
        if img_format is None:
1✔
240
            if file_ext != "":
1✔
241
                img_format = file_ext.replace(".", "")
1✔
242
            else:
243
                img_format = "pdf"
×
244

245
        self.dot = graphviz.Digraph(filename=file_name, format=img_format, **kwargs)
1✔
246
        self.txt_width = txt_width
1✔
247
        self.txt_fontsize = str(txt_fontsize)
1✔
248
        self.busses = []
1✔
249

250
        if legend is True:
1✔
251
            with self.dot.subgraph(name="cluster_1") as c:
1✔
252
                # color of the legend box
253
                c.attr(color="black")
1✔
254
                # title of the legend box
255
                c.attr(label="Legends")
1✔
256
                self.add_bus(subgraph=c)
1✔
257
                self.add_sink(subgraph=c)
1✔
258
                self.add_source(subgraph=c)
1✔
259
                self.add_transformer(subgraph=c)
1✔
260
                self.add_storage(subgraph=c)
1✔
261

262
        # draw a node for each of the network's component. The shape depends on the component's type
263
        for nd in self.energy_system.nodes:
1✔
264
            if isinstance(nd, oemof.solph.network.Bus):
1✔
265
                self.add_bus(nd.label)
1✔
266
                # keep the bus reference for drawing edges later
267
                self.busses.append(nd)
1✔
268
            elif isinstance(nd, oemof.solph.network.Sink):
1✔
269
                self.add_sink(nd.label)
1✔
270
            elif isinstance(nd, oemof.solph.network.Source):
1✔
271
                self.add_source(nd.label)
1✔
272
            elif isinstance(nd, oemof.solph.network.Transformer):
1✔
273
                self.add_transformer(nd.label)
1✔
274
            elif isinstance(nd, oemof.solph.components.GenericStorage):
1✔
275
                self.add_storage(nd.label)
1✔
276
            else:
277
                logging.warning(
×
278
                    "The component {} of type {} is not implemented in the rendering "
279
                    "function of the energy model network graph drawer. It will be "
280
                    "rendered as an ellipse".format(nd.label, type(nd))
281
                )
282
                self.add_component(nd.label)
×
283

284
        # draw the edges between the nodes based on each bus inputs/outputs
285
        for bus in self.busses:
1✔
286
            for component in bus.inputs:
1✔
287
                # draw an arrow from the component to the bus
288
                self.connect(component, bus)
1✔
289
            for component in bus.outputs:
1✔
290
                # draw an arrow from the bus to the component
291
                self.connect(bus, component)
1✔
292

293
    def add_bus(self, label="Bus", subgraph=None):
1✔
294
        if subgraph is None:
1✔
295
            dot = self.dot
1✔
296
        else:
297
            dot = subgraph
1✔
298
        dot.node(
1✔
299
            label,
300
            shape="rectangle",
301
            fontsize="10",
302
            fixedsize="shape",
303
            width="4.1",
304
            height="0.3",
305
            style="filled",
306
            color="lightgrey",
307
        )
308

309
    def add_sink(self, label="Sink", subgraph=None):
1✔
310
        if subgraph is None:
1✔
311
            dot = self.dot
1✔
312
        else:
313
            dot = subgraph
1✔
314
        dot.node(
1✔
315
            fixed_width_text(label, char_num=self.txt_width),
316
            shape="trapezium",
317
            fontsize=self.txt_fontsize,
318
        )
319

320
    def add_source(self, label="Source", subgraph=None):
1✔
321
        if subgraph is None:
1✔
322
            dot = self.dot
1✔
323
        else:
324
            dot = subgraph
1✔
325
        dot.node(
1✔
326
            fixed_width_text(label, char_num=self.txt_width),
327
            shape="invtrapezium",
328
            fontsize=self.txt_fontsize,
329
        )
330

331
    def add_transformer(self, label="Transformer", subgraph=None):
1✔
332
        if subgraph is None:
1✔
333
            dot = self.dot
1✔
334
        else:
335
            dot = subgraph
1✔
336
        dot.node(
1✔
337
            fixed_width_text(label, char_num=self.txt_width),
338
            shape="rectangle",
339
            fontsize=self.txt_fontsize,
340
        )
341

342
    def add_storage(self, label="Storage", subgraph=None):
1✔
343
        if subgraph is None:
1✔
344
            dot = self.dot
1✔
345
        else:
346
            dot = subgraph
1✔
347
        dot.node(
1✔
348
            fixed_width_text(label, char_num=self.txt_width),
349
            shape="rectangle",
350
            style="rounded",
351
            fontsize=self.txt_fontsize,
352
        )
353

354
    def add_component(self, label="component", subgraph=None):
1✔
355
        if subgraph is None:
×
356
            dot = self.dot
×
357
        else:
358
            dot = subgraph
×
359
        dot.node(
×
360
            fixed_width_text(label, char_num=self.txt_width),
361
            fontsize=self.txt_fontsize,
362
        )
363

364
    def connect(self, a, b):
1✔
365
        """Draw an arrow from node a to node b
366

367
        Parameters
368
        ----------
369
        a: `oemof.solph.network.Node`
370
            An oemof node (usually a Bus or a Component)
371

372
        b: `oemof.solph.network.Node`
373
            An oemof node (usually a Bus or a Component)
374
        """
375
        if not isinstance(a, oemof.solph.network.Bus):
1✔
376
            a = fixed_width_text(a.label, char_num=self.txt_width)
1✔
377
        else:
378
            a = a.label
1✔
379
        if not isinstance(b, oemof.solph.network.Bus):
1✔
380
            b = fixed_width_text(b.label, char_num=self.txt_width)
1✔
381
        else:
382
            b = b.label
1✔
383

384
        self.dot.edge(a, b)
1✔
385

386
    def view(self, **kwargs):
1✔
387
        """Call the view method of the DiGraph instance"""
388
        self.dot.view(**kwargs)
×
389

390
    def render(self, **kwargs):
1✔
391
        """Call the render method of the DiGraph instance"""
392
        self.dot.render(**kwargs)
1✔
393

394
    def sankey(self, results):
1✔
395
        """Return a dict to a plotly sankey diagram"""
396
        busses = []
×
397

398
        labels = []
×
399
        sources = []
×
400
        targets = []
×
401
        values = []
×
402

403
        # bus_data.update({bus: solph.views.node(results_main, bus)})
404

405
        # draw a node for each of the network's component. The shape depends on the component's type
406
        for nd in self.energy_system.nodes:
×
407
            if isinstance(nd, oemof.solph.network.Bus):
×
408

409
                # keep the bus reference for drawing edges later
410
                bus = nd
×
411
                busses.append(bus)
×
412

413
                bus_label = bus.label
×
414

415
                labels.append(nd.label)
×
416

417
                flows = solph.views.node(results, bus_label)["sequences"]
×
418

419
                # draw an arrow from the component to the bus
420
                for component in bus.inputs:
×
421
                    if component.label not in labels:
×
422
                        labels.append(component.label)
×
423

424
                    sources.append(labels.index(component.label))
×
425
                    targets.append(labels.index(bus_label))
×
426

427
                    val = flows[((component.label, bus_label), "flow")].sum()
×
428
                    # if val == 0:
429
                    #     val = 1
430
                    values.append(val)
×
431

432
                for component in bus.outputs:
×
433
                    # draw an arrow from the bus to the component
434
                    if component.label not in labels:
×
435
                        labels.append(component.label)
×
436

437
                    sources.append(labels.index(bus_label))
×
438
                    targets.append(labels.index(component.label))
×
439

440
                    val = flows[((bus_label, component.label), "flow")].sum()
×
441

442
                    # if val == 0:
443
                    #     val = 1
444
                    values.append(val)
×
445

446
        fig = go.Figure(
×
447
            data=[
448
                go.Sankey(
449
                    node=dict(
450
                        pad=15,
451
                        thickness=20,
452
                        line=dict(color="black", width=0.5),
453
                        label=labels,
454
                        hovertemplate="Node has total value %{value}<extra></extra>",
455
                        color="blue",
456
                    ),
457
                    link=dict(
458
                        source=sources,  # indices correspond to labels, eg A1, A2, A2, B1, ...
459
                        target=targets,
460
                        value=values,
461
                        hovertemplate="Link from node %{source.label}<br />"
462
                        + "to node%{target.label}<br />has value %{value}"
463
                        + "<br />and data <extra></extra>",
464
                    ),
465
                )
466
            ]
467
        )
468

469
        fig.update_layout(title_text="Basic Sankey Diagram", font_size=10)
×
470
        return fig.to_dict()
×
471

472

473
def get_color(idx_line, color_list=None):
1✔
474
    """Pick a color within a color list with periodic boundary conditions
475

476
    Parameters
477
    ----------
478
    idx_line: int
479
        index of the line in a plot for which a color is required
480

481
    colors: list of str or list to tuple (hexadecimal or rbg code)
482
        list of colors
483
        Default: None
484

485
    Returns
486
    -------
487
    The color in the color list corresponding to the index modulo the color list length
488

489
    """
490
    if color_list is None:
1✔
491
        color_list = (
×
492
            "#1f77b4",
493
            "#ff7f0e",
494
            "#2ca02c",
495
            "#d62728",
496
            "#9467bd",
497
            "#8c564b",
498
            "#e377c2",
499
            "#7f7f7f",
500
            "#bcbd22",
501
            "#17becf",
502
        )
503
    n_colors = len(color_list)
1✔
504
    return color_list[idx_line % n_colors]
1✔
505

506

507
def save_plots_to_disk(
1✔
508
    fig_obj, file_name, file_path="", width=None, height=None, scale=None
509
):
510
    r"""
511
    This function saves the plots generated using the Plotly library in this module to the outputs folder.
512

513
    Parameters
514
    ----------
515
    fig_obj: instance of the classes of the Plotly go library used to generate the plots in this auto-report
516
        Figure object of the plotly plots
517

518
    file_name: str
519
        The name of the PNG image of the plot to be saved in the output folder.
520

521
    file_path: str
522
        Path where the image shall be saved
523

524
    width: int or float
525
        The width of the picture to be saved in pixels.
526
        Default: None
527

528
    height: int or float
529
        The height of the picture to be saved in pixels.
530
        Default: None
531

532
    scale: int or float
533
        The scale by which the plotly image ought to be multiplied.
534
        Default: None
535

536
    Returns
537
    -------
538
    Nothing is returned. This function call results in the plots being saved as .png images to the disk.
539
    """
540

541
    if not file_name.endswith("png"):
1✔
542
        file_name = file_name + ".png"
×
543

544
    logging.info("Saving {} under {}".format(file_name, file_path))
1✔
545

546
    file_path_out = os.path.join(file_path, file_name)
1✔
547
    with open(file_path_out, "wb") as fp:
1✔
548
        fig_obj.write_image(fp, width=width, height=height, scale=scale)
1✔
549

550

551
def get_fig_style_dict():
1✔
552
    styling_dict = dict(
×
553
        showgrid=True,
554
        gridwidth=1.5,
555
        zeroline=True,
556
        autorange=True,
557
        linewidth=1,
558
        ticks="inside",
559
        title_font=dict(size=18, color="black"),
560
    )
561
    return styling_dict
×
562

563

564
def create_plotly_line_fig(
1✔
565
    x_data,
566
    y_data,
567
    plot_title=None,
568
    x_axis_name=None,
569
    y_axis_name=None,
570
    color_for_plot="#0A2342",
571
    file_path=None,
572
):
573
    r"""
574
    Create figure for generic timeseries lineplots
575

576
    Parameters
577
    ----------
578
    x_data: list, or pandas series
579
        The list of abscissas of the data required for plotting.
580

581
    y_data: list, or pandas series, or list of lists
582
        The list of ordinates of the data required for plotting.
583

584
    plot_title: str
585
        The title of the plot generated.
586
        Default: None
587

588
    x_axis_name: str
589
        Default: None
590

591
    y_axis_name: str
592
        Default: None
593

594
    file_path: str
595
        Path where the image shall be saved if not None
596

597
    Returns
598
    -------
599
    fig :class:`plotly.graph_objs.Figure`
600
        figure object
601
    """
602
    fig = go.Figure()
×
603

604
    styling_dict = get_fig_style_dict()
×
605
    styling_dict["mirror"] = True
×
606

607
    fig.add_trace(
×
608
        go.Scatter(
609
            x=x_data,
610
            y=y_data,
611
            mode="lines",
612
            line=dict(color=color_for_plot, width=2.5),
613
        )
614
    )
615
    fig.update_layout(
×
616
        xaxis_title=x_axis_name,
617
        yaxis_title=y_axis_name,
618
        template="simple_white",
619
        xaxis=styling_dict,
620
        yaxis=styling_dict,
621
        font_family="sans-serif",
622
        title={
623
            "text": plot_title,
624
            "y": 0.90,
625
            "x": 0.5,
626
            "font_size": 23,
627
            "xanchor": "center",
628
            "yanchor": "top",
629
        },
630
    )
631

632
    name_file = "input_timeseries_" + plot_title + ".png"
×
633

634
    if file_path is not None:
×
635

636
        # Function call to save the Plotly plot to the disk
637
        save_plots_to_disk(
×
638
            fig_obj=fig,
639
            file_path=file_path,
640
            file_name=name_file,
641
            width=1200,
642
            height=600,
643
            scale=5,
644
        )
645

646
    return fig
×
647

648

649
def plot_timeseries(
1✔
650
    dict_values,
651
    data_type=DEMANDS,
652
    sector_demands=None,
653
    max_days=None,
654
    color_list=None,
655
    file_path=None,
656
):
657
    r"""Plot timeseries as line chart.
658

659
    Parameters
660
    ----------
661
    dict_values :
662
        dict Of all input and output parameters up to F0
663

664
    data_type: str
665
        one of DEMANDS or RESOURCES
666
        Default: DEMANDS
667

668
    sector_demands: str
669
        Name of the sector of the energy system
670
        Default: None
671

672
    max_days: int
673
        maximal number of days the timeseries should be displayed for
674

675
    color_list: list of str or list to tuple (hexadecimal or rbg code)
676
        list of colors
677
        Default: None
678

679
    file_path: str
680
        Path where the image shall be saved if not None
681
        Default: None
682

683
    Returns
684
    -------
685
    Dict with html DOM id for the figure as key and :class:`plotly.graph_objs.Figure` as value
686
    """
687

688
    df_dem = convert_demand_to_dataframe(
×
689
        dict_values=dict_values, sector_demands=sector_demands
690
    )
691
    dict_for_plots, dict_plot_labels = extract_plot_data_and_title(
×
692
        dict_values, df_dem=df_dem
693
    )
694

695
    df_pd = convert_plot_data_to_dataframe(dict_for_plots, data_type)
×
696

697
    list_of_keys = list(df_pd.columns)
×
698
    list_of_keys.remove("timestamp")
×
699
    plots = {}
×
700

701
    if max_days is not None:
×
702
        if df_pd["timestamp"].empty:
×
703
            logging.warning("The timeseries for {} are empty".format(data_type))
×
704
        else:
705
            if not isinstance(df_pd["timestamp"], pd.DatetimeIndex):
×
706
                dti = dict_values[SIMULATION_SETTINGS][TIME_INDEX]
×
707
                df_pd = df_pd.loc[: len(dti) - 1]
×
708
                df_pd["timestamp"] = dti
×
709
                max_date = df_pd["timestamp"][0] + pd.Timedelta(
×
710
                    "{} day".format(max_days)
711
                )
712
            df_pd = df_pd.loc[df_pd["timestamp"] < max_date]
×
713
        title_addendum = " ({} days)".format(max_days)
×
714
    else:
715
        title_addendum = ""
×
716

717
    for i, component in enumerate(list_of_keys):
×
718
        comp_id = component + "-plot"
×
719
        fig = create_plotly_line_fig(
×
720
            x_data=df_pd["timestamp"],
721
            y_data=df_pd[component],
722
            plot_title="{}{}".format(dict_plot_labels[component], title_addendum),
723
            x_axis_name="Time",
724
            y_axis_name="kW",
725
            color_for_plot=get_color(i, color_list),
726
            file_path=file_path,
727
        )
728
        if file_path is None:
×
729
            plots[comp_id] = fig
×
730

731
    return plots
×
732

733

734
def plot_sankey(dict_values):
1✔
735
    """"""
736
    fig_dict = dict_values[PATHS_TO_PLOTS].get(PLOT_SANKEY, None)
×
737
    if fig_dict is not None:
×
738
        fig = go.Figure(**fig_dict)
×
739
    else:
740
        fig = go.Figure()
×
741
    return fig
×
742

743

744
def create_plotly_barplot_fig(
1✔
745
    x_data,
746
    y_data,
747
    plot_title=None,
748
    trace_name="",
749
    legends=None,
750
    x_axis_name=None,
751
    y_axis_name=None,
752
    file_name="barplot.png",
753
    file_path=None,
754
):
755
    r"""
756
    Create figure for specific capacities barplot
757

758
    Parameters
759
    ----------
760
    x_data: list, or pandas series
761
        The list of abscissas of the data required for plotting.
762

763
    y_data: list, or pandas series, or list of lists
764
        The list of ordinates of the data required for plotting.
765

766
    plot_title: str
767
        The title of the plot generated.
768
        Default: None
769

770
    trace_name: str
771
        Sets the trace name. The trace name appear as the legend item and on hover.
772
        Default: ""
773

774
    legends: list, or pandas series
775
        The list of the text written within the bars and on hover below the trace_name
776
        Default: None
777

778
    x_axis_name: str
779
        Default: None
780

781
    y_axis_name: str
782
        Default: None
783

784
    file_name: str
785
        Name of the image file.
786
        Default: "barplot.png"
787

788
    file_path: str
789
        Path where the image shall be saved if not None
790

791
    Returns
792
    -------
793
    fig: :class:`plotly.graph_objs.Figure`
794
        figure object
795
    """
796
    fig = go.Figure()
×
797

798
    styling_dict = get_fig_style_dict()
×
799
    styling_dict["mirror"] = True
×
800

801
    opts = {}
×
802
    if legends is not None:
×
803
        opts.update(dict(text=legends, textposition="auto"))
×
804

805
    fig.add_trace(
×
806
        go.Bar(
807
            name=trace_name,
808
            x=x_data,
809
            y=y_data,
810
            marker_color=px.colors.qualitative.D3,
811
            **opts,
812
        )
813
    )
814

815
    fig.update_layout(
×
816
        xaxis_title=x_axis_name,
817
        yaxis_title=y_axis_name,
818
        template="simple_white",
819
        font_family="sans-serif",
820
        # TODO use styling dict
821
        xaxis=go.layout.XAxis(
822
            showgrid=True,
823
            gridwidth=1.5,
824
            zeroline=True,
825
            mirror=True,
826
            autorange=True,
827
            linewidth=1,
828
            ticks="inside",
829
            visible=True,
830
        ),
831
        yaxis=styling_dict,
832
        title={
833
            "text": plot_title,
834
            "y": 0.90,
835
            "x": 0.5,
836
            "font_size": 23,
837
            "xanchor": "center",
838
            "yanchor": "top",
839
        },
840
        legend_title="Components",
841
    )
842

843
    if file_path is not None:
×
844
        # Function call to save the Plotly plot to the disk
845
        save_plots_to_disk(
×
846
            fig_obj=fig,
847
            file_path=file_path,
848
            file_name=file_name,
849
            width=1200,
850
            height=600,
851
            scale=5,
852
        )
853

854
    return fig
×
855

856

857
def plot_optimized_capacities(
1✔
858
    dict_values, file_path=None,
859
):
860
    """Plot capacities as a bar chart.
861

862
    Parameters
863
    ----------
864
    dict_values :
865
        dict Of all input and output parameters up to F0
866

867
    file_path: str
868
        Path where the image shall be saved if not None
869
        Default: None
870

871
    Returns
872
    -------
873
    Dict with html DOM id for the figure as key and :class:`plotly.graph_objs.Figure` as value
874
    """
875

876
    # Add dataframe to hold all the KPIs and optimized additional capacities
877
    df_capacities = dict_values[KPI][KPI_SCALAR_MATRIX].copy(deep=True)
×
878
    df_capacities.drop(
×
879
        columns=[TOTAL_FLOW, ANNUAL_TOTAL_FLOW, PEAK_FLOW, AVERAGE_FLOW], inplace=True,
880
    )
881
    df_capacities.reset_index(drop=True, inplace=True)
×
882

883
    x_values = []
×
884
    y_values = []
×
885
    legends = []
×
886

887
    for kpi, cap, unit in zip(
×
888
        list(df_capacities[LABEL]),
889
        list(df_capacities[OPTIMIZED_ADD_CAP]),
890
        list(df_capacities[UNIT]),
891
    ):
892
        if cap > 0:
×
893
            x_values.append(kpi)
×
894
            y_values.append(cap)
×
895
            if unit == "?":
×
896
                unit = "kW"
×
897
            legends.append("{:.0f} {}".format(cap, unit))
×
898

899
    # Title to add to plot titles
900
    project_title = ": {}, {}".format(
×
901
        dict_values[PROJECT_DATA][PROJECT_NAME],
902
        dict_values[PROJECT_DATA][SCENARIO_NAME],
903
    )
904

905
    name_file = "optimal_additional_capacities"
×
906

907
    fig = create_plotly_barplot_fig(
×
908
        x_data=x_values,
909
        y_data=y_values,
910
        plot_title="Optimal additional capacities" + project_title,
911
        trace_name="capacities",
912
        legends=legends,
913
        x_axis_name="Items",
914
        y_axis_name="Capacities",
915
        file_name=name_file,
916
        file_path=file_path,
917
    )
918

919
    return {"capacities_plot": fig}
×
920

921

922
def create_plotly_flow_fig(
1✔
923
    df_plots_data,
924
    x_legend=None,
925
    y_legend=None,
926
    plot_title=None,
927
    color_list=None,
928
    file_name="flows.png",
929
    file_path=None,
930
):
931
    r"""Generate figure of an asset's flow.
932

933
    Parameters
934
    ----------
935
    df_plots_data: :class:`pandas.DataFrame<frame>`
936
        dataFrame with timeseries of the asset's energy flow
937
    x_legend: str
938
        Default: None
939

940
    y_legend: str
941
        Default: None
942

943
    plot_title: str
944
        Default: None
945

946
    color_list: list of str or list to tuple (hexadecimal or rbg code)
947
        list of colors
948
        Default: None
949

950
    file_name: str
951
        Name of the image file.
952
        Default: "flows.png"
953

954
    file_path: str
955
        Path where the image shall be saved if not None
956
        Default: None
957

958
    Returns
959
    -------
960
    fig: :class:`plotly.graph_objs.Figure`
961
        figure object
962
    """
963

964
    fig = go.Figure()
×
965
    styling_dict = get_fig_style_dict()
×
966
    styling_dict["gridwidth"] = 1.0
×
967

968
    assets_list = list(df_plots_data.columns)
×
969
    assets_list.remove("timestamp")
×
970

971
    for i, asset in enumerate(assets_list):
×
972
        fig.add_trace(
×
973
            go.Scatter(
974
                x=df_plots_data["timestamp"],
975
                y=df_plots_data[asset],
976
                mode="lines",
977
                line=dict(color=get_color(i, color_list), width=2.5),
978
                name=asset,
979
            )
980
        )
981

982
    fig.update_layout(
×
983
        xaxis_title=x_legend,
984
        yaxis_title=y_legend,
985
        font_family="sans-serif",
986
        template="simple_white",
987
        xaxis=styling_dict,
988
        yaxis=styling_dict,
989
        title={
990
            "text": plot_title,
991
            "y": 0.90,
992
            "x": 0.5,
993
            "font_size": 23,
994
            "xanchor": "center",
995
            "yanchor": "top",
996
        },
997
        legend=dict(y=0.5, traceorder="normal", font=dict(color="black"),),
998
    )
999

1000
    if file_path is not None:
×
1001
        # Function call to save the Plotly plot to the disk
1002
        save_plots_to_disk(
×
1003
            fig_obj=fig,
1004
            file_path=file_path,
1005
            file_name=file_name,
1006
            width=1200,
1007
            height=600,
1008
            scale=5,
1009
        )
1010

1011
    return fig
×
1012

1013

1014
def plot_instant_power(dict_values, file_path=None):
1✔
1015
    """Plotting timeseries of instantaneous power for each assets within the energy system
1016

1017
    Parameters
1018
    ----------
1019
    dict_values : dict
1020
        all simulation input and output data up to this point
1021

1022
    file_path: str
1023
        Path where the image shall be saved if not None
1024
        Default: None
1025

1026
    Returns
1027
    -------
1028
    multi_plots: dict
1029
       Dict with html DOM id for the figure as keys and :class:`plotly.graph_objs.Figure` as values
1030
    """
1031
    buses_list = list(dict_values[OPTIMIZED_FLOWS].keys())
×
1032
    multi_plots = {}
×
1033
    for bus in buses_list:
×
1034
        df_data = dict_values[OPTIMIZED_FLOWS][bus].copy(deep=True)
×
1035
        df_data.reset_index(level=0, inplace=True)
×
1036
        df_data = df_data.rename(columns={"index": "timestamp"})
×
1037

1038
        # In case SOC of a storage is in df_data the SOC is plotted separately and is
1039
        # removed from the df_data as the plot that shows absolute flows should not
1040
        # contain SOC in %
1041
        if any(SOC in item for item in df_data):
×
1042
            # if any(SOC in item for item in df_data):
1043
            comp_id = f"{SOC}-{bus}-plot"
×
1044
            title = (
×
1045
                bus
1046
                + " storage SOC in LES: "
1047
                + dict_values[PROJECT_DATA][PROJECT_NAME]
1048
                + ", "
1049
                + dict_values[PROJECT_DATA][SCENARIO_NAME]
1050
            )
1051
            # get columns containing SOC and plot SOC
1052
            soc_cols = [s for s in df_data.keys() if SOC in s]
×
1053
            soc_cols.extend(["timestamp"])
×
1054
            fig = create_plotly_flow_fig(
×
1055
                df_plots_data=df_data[soc_cols],
1056
                x_legend="Time",
1057
                y_legend="SOC",
1058
                plot_title=title,
1059
                file_path=file_path,
1060
                file_name=f"SOC_{bus}_power.png",
1061
            )
1062
            if file_path is None:
×
1063
                multi_plots[comp_id] = fig
×
1064

1065
            # remove SOC as it is provided in % and does not fit with flow plot
1066
            soc_cols.remove("timestamp")
×
1067
            df_data.drop(soc_cols, inplace=True, axis=1)
×
1068

1069
        # create flow plot
1070
        comp_id = f"{bus}-plot"
×
1071
        title = (
×
1072
            bus
1073
            + " power in LES: "
1074
            + dict_values[PROJECT_DATA][PROJECT_NAME]
1075
            + ", "
1076
            + dict_values[PROJECT_DATA][SCENARIO_NAME]
1077
        )
1078

1079
        fig = create_plotly_flow_fig(
×
1080
            df_plots_data=df_data,
1081
            x_legend="Time",
1082
            y_legend=bus + " in kW",
1083
            plot_title=title,
1084
            file_path=file_path,
1085
            file_name=bus + "_power.png",
1086
        )
1087
        if file_path is None:
×
1088
            multi_plots[comp_id] = fig
×
1089

1090
    return multi_plots
×
1091

1092

1093
def create_plotly_piechart_fig(
1✔
1094
    title_of_plot,
1095
    names,
1096
    values,
1097
    color_scheme=None,
1098
    file_name="costs.png",
1099
    file_path=None,
1100
):
1101
    r"""Generate figure with piechart plot.
1102

1103
    Parameters
1104
    ----------
1105
    title_of_plot: str
1106
        title of the figure
1107

1108
    names: list
1109
        List containing the labels of the slices in the pie plot.
1110

1111
    values: list
1112
        List containing the values of the labels to be plotted in the pie plot.
1113

1114
    color_scheme: instance of the px.colors class of the Plotly express library
1115
        This parameter holds the color scheme which is palette of colors (list of hex values) to be
1116
        applied to the pie plot to be created.
1117
        Default: None
1118

1119
    file_name: str
1120
        Name of the image file.
1121
        Default: "costs.png"
1122

1123
    file_path: str
1124
        Path where the image shall be saved if not None
1125
        Default: None
1126

1127
    Returns
1128
    -------
1129
    fig: :class:`plotly.graph_objs.Figure`
1130
        figure object
1131
    """
1132

1133
    if color_scheme is None:
1✔
1134
        color_scheme = px.colors.qualitative.Set1
1✔
1135

1136
    # Wrap the text of the title into next line if it exceeds the length given below
1137
    title_of_plot = textwrap.wrap(title_of_plot, width=75)
1✔
1138
    title_of_plot = "<br>".join(title_of_plot)
1✔
1139

1140
    fig = go.Figure(
1✔
1141
        go.Pie(
1142
            labels=names,
1143
            values=values,
1144
            textposition="inside",
1145
            insidetextorientation="radial",
1146
            texttemplate="%{label} <br>%{percent}",
1147
            marker=dict(colors=color_scheme),
1148
        ),
1149
    )
1150

1151
    fig.update_layout(
1✔
1152
        title={
1153
            "text": title_of_plot,
1154
            "y": 0.9,
1155
            "x": 0.5,
1156
            "font_size": 23,
1157
            "xanchor": "center",
1158
            "yanchor": "top",
1159
            "pad": {"r": 5, "l": 5, "b": 5, "t": 5},
1160
        },
1161
        font_family="sans-serif",
1162
        height=500,
1163
        width=700,
1164
        autosize=True,
1165
        legend=dict(orientation="v", y=0.5, yanchor="middle", x=0.95, xanchor="right",),
1166
        margin=dict(l=10, r=10, b=50, pad=2),
1167
        uniformtext_minsize=18,
1168
    )
1169
    fig.update_traces(hoverinfo="label+percent", textinfo="label", textfont_size=18)
1✔
1170

1171
    if file_path is not None:
1✔
1172
        # Function call to save the Plotly plot to the disk
1173
        save_plots_to_disk(
1✔
1174
            fig_obj=fig,
1175
            file_path=file_path,
1176
            file_name=file_name,
1177
            width=1200,
1178
            height=600,
1179
            scale=5,
1180
        )
1181

1182
    return fig
1✔
1183

1184

1185
def plot_piecharts_of_costs(dict_values, file_path=None):
1✔
1186
    """Plotting piecharts of different cost parameters (ie. annuity, total cost, etc...)
1187

1188
    Parameters
1189
    ----------
1190
    dict_values : dict
1191
        all simulation input and output data up to this point
1192

1193
    file_path: str
1194
        Path where the image shall be saved if not None
1195
        Default: None
1196

1197
    Returns
1198
    -------
1199
    pie_plots: dict
1200
       Dict with html DOM id for the figure as keys and :class:`plotly.graph_objs.Figure` as values
1201
    """
1202

1203
    df_pie_data = convert_costs_to_dataframe(dict_values)
×
1204

1205
    # Initialize an empty list and a dict for use later in the function
1206
    pie_plots = {}
×
1207
    pie_data_dict = {}
×
1208

1209
    # df_pie_data.reset_index(drop=True, inplace=True)
1210
    columns_list = list(df_pie_data.columns)
×
1211
    columns_list.remove(LABEL)
×
1212

1213
    # Iterate through the list of columns of the DF which are the KPIs to be plotted
1214
    for kp_indic in columns_list:
×
1215

1216
        # Assign an id for the plot
1217
        comp_id = kp_indic + "plot"
×
1218

1219
        kpi_part = ""
×
1220

1221
        # Make a copy of the DF to make various manipulations for the pie chart plotting
1222
        df_temp = df_pie_data.copy()
×
1223

1224
        # Get the total value for each KPI to use in the title of the respective pie chart
1225
        df_temp2 = df_temp.copy()
×
1226
        df_temp2.set_index(LABEL, inplace=True)
×
1227
        total_for_title = df_temp2.at["Total", kp_indic]
×
1228

1229
        # Drop the total row in the dataframe
1230
        df_temp.drop(df_temp.tail(1).index, inplace=True)
×
1231
        # Gather the data for each asset for the particular KPI, in a dict
1232
        for row_index in df_temp.index:
×
1233
            pie_data_dict[df_temp.at[row_index, LABEL]] = df_temp.at[
×
1234
                row_index, kp_indic
1235
            ]
1236

1237
        # Remove negative values (such as the feed-in sinks) from the dict
1238
        pie_data_dict = {k: v for (k, v) in pie_data_dict.items() if v > 0}
×
1239

1240
        # Get the names and values for the pie chart from the above dict
1241
        names_plot = list(pie_data_dict.keys())
×
1242
        values_plot = list(pie_data_dict.values())
×
1243

1244
        # Below loop determines the first part of the plot title, according to the kpi being plotted
1245
        if "annuity" in kp_indic:
×
1246
            kpi_part = "Annuity Costs ("
×
1247
            file_name = "annuity"
×
1248
            scheme_choosen = px.colors.qualitative.Set1
×
1249
        elif "investment" in kp_indic:
×
1250
            kpi_part = "Upfront Investment Costs ("
×
1251
            file_name = "upfront_investment_costs"
×
1252
            scheme_choosen = px.colors.diverging.BrBG
×
1253
        elif "om" in kp_indic:
×
1254
            kpi_part = "Operation and Maintenance Costs ("
×
1255
            file_name = "operation_and_maintainance_costs"
×
1256
            scheme_choosen = px.colors.sequential.RdBu
×
1257

1258
        # Title to add to plot titles
1259
        project_title = ": {}, {}".format(
×
1260
            dict_values[PROJECT_DATA][PROJECT_NAME],
1261
            dict_values[PROJECT_DATA][SCENARIO_NAME],
1262
        )
1263

1264
        # Title of the pie plot
1265
        plot_title = (
×
1266
            kpi_part
1267
            + str(round(total_for_title))
1268
            + " "
1269
            + dict_values[ECONOMIC_DATA][CURR]
1270
            + ") "
1271
            + project_title
1272
        )
1273

1274
        fig = create_plotly_piechart_fig(
×
1275
            title_of_plot=plot_title,
1276
            names=names_plot,
1277
            values=values_plot,
1278
            color_scheme=scheme_choosen,
1279
            file_name=file_name,
1280
            file_path=file_path,
1281
        )
1282

1283
        if file_path is None:
×
1284
            pie_plots[comp_id] = fig
×
1285

1286
    return pie_plots
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc