Пример #1
0
def create_plot(df, title, carbon_unit, cost_unit, ylimit=None):
    """

    :param df:
    :param title: string, plot title
    :param carbon_unit: string, the unit of carbon emissions used in the
    database/model, e.g. "tCO2"
    :param cost_unit: string, the unit of cost used in the database/model,
    e.g. "USD"
    :param ylimit: float/int, upper limit of y-axis; optional
    :return:
    """

    if df.empty:
        return figure()

    # Set up data source
    source = ColumnDataSource(data=df)

    # Determine column types for plotting, legend and colors
    # Order of stacked_cols will define order of stacked areas in chart
    x_col = "period"
    line_col = "carbon_cap"
    stacked_cols = ["in_zone_project_emissions", "import_emissions_degen"]

    # Stacked Area Colors
    colors = ['#666666', "#999999"]

    # Set up the figure
    plot = figure(
        plot_width=800,
        plot_height=500,
        tools=["pan", "reset", "zoom_in", "zoom_out", "save", "help"],
        title=title,
        x_range=df[x_col]
        # sizing_mode="scale_both"
    )

    # Add stacked bar chart to plot
    bar_renderers = plot.vbar_stack(
        stackers=stacked_cols,
        x=x_col,
        source=source,
        color=colors,
        width=0.5,
    )

    # Add Carbon Cap target line chart to plot
    target_renderer = plot.circle(x=x_col,
                                  y=line_col,
                                  source=source,
                                  size=20,
                                  color="black",
                                  fill_alpha=0.2,
                                  line_width=2)

    # Create legend items
    legend_items = [("Project Emissions", [bar_renderers[0]]),
                    ("Import Emissions", [bar_renderers[1]]),
                    ("Carbon Target", [target_renderer])]

    # Add Legend
    legend = Legend(items=legend_items)
    plot.add_layout(legend, 'right')
    plot.legend[0].items.reverse()  # Reverse legend to match stacked order
    plot.legend.click_policy = 'hide'  # Add interactivity to the legend
    # Note: Doesn't rescale the graph down, simply hides the area
    # Note2: There's currently no way to auto-size legend based on graph size(?)
    # except for maybe changing font size automatically?
    show_hide_legend(plot=plot)  # Hide legend on double click

    # Format Axes (labels, number formatting, range, etc.)
    plot.xaxis.axis_label = "Period"
    plot.yaxis.axis_label = "Emissions ({})".format(carbon_unit)
    plot.yaxis.formatter = NumeralTickFormatter(format="0,0")
    plot.y_range.end = ylimit  # will be ignored if ylimit is None

    # Add delivered RPS HoverTool
    r_delivered = bar_renderers[0]  # renderer for delivered RPS
    hover = HoverTool(tooltips=[
        ("Period", "@period"),
        ("Project Emissions",
         "@%s{0,0} %s (@fraction_of_project_emissions{0%%})" %
         (stacked_cols[0], carbon_unit)),
    ],
                      renderers=[r_delivered],
                      toggleable=False)
    plot.add_tools(hover)

    # Add curtailed RPS HoverTool
    r_curtailed = bar_renderers[1]  # renderer for curtailed RPS
    hover = HoverTool(tooltips=[
        ("Period", "@period"),
        ("Import Emissions",
         "@%s{0,0} %s (@fraction_of_import_emissions{0%%})" %
         (stacked_cols[1], carbon_unit)),
    ],
                      renderers=[r_curtailed],
                      toggleable=False)
    plot.add_tools(hover)

    # Add RPS Target HoverTool
    hover = HoverTool(tooltips=[
        ("Period", "@period"),
        ("Carbon Target", "@%s{0,0} %s" % (line_col, carbon_unit)),
        ("Marginal Cost", "@carbon_cap_marginal_cost_per_emission{0,0} %s/%s" %
         (cost_unit, carbon_unit))
    ],
                      renderers=[target_renderer],
                      toggleable=False)
    plot.add_tools(hover)

    return plot
Пример #2
0
def create_plot(df, title, power_unit, tech_colors={}, tech_plotting_order={},
                ylimit=None):
    """

    :param df:
    :param title: string, plot title
    :param power_unit: string, the unit of power used in the database/model
    :param tech_colors: optional dict that maps technologies to colors.
        Technologies without a specified color map will use a default palette
    :param tech_plotting_order: optional dict that maps technologies to their
        plotting order in the stacked bar/area chart.
    :param ylimit: float/int, upper limit of y-axis; optional
    :return:
    """

    # Re-arrange df according to plotting order
    for col in df.columns:
        if col not in tech_plotting_order:
            tech_plotting_order[col] = max(tech_plotting_order.values()) + 1
    df = df.reindex(sorted(df.columns, key=lambda x: tech_plotting_order[x]),
                    axis=1)

    # Set up data source
    source = ColumnDataSource(data=df)

    # Determine column types for plotting, legend and colors
    # Order of stacked_cols will define order of stacked areas in chart
    all_cols = list(df.columns)
    x_col = "x"
    # TODO: remove hard-coding?
    line_cols = ["Load", "Exports", "Storage_Charging"]
    stacked_cols = [c for c in all_cols if c not in line_cols + [x_col]]

    # Set up color scheme. Use cividis palette for unspecified colors
    unspecified_columns = [c for c in stacked_cols
                           if c not in tech_colors.keys()]
    unspecified_tech_colors = dict(zip(unspecified_columns,
                                        cividis(len(unspecified_columns))))
    colors = []
    for tech in stacked_cols:
        if tech in tech_colors:
            colors.append(tech_colors[tech])
        else:
            colors.append(unspecified_tech_colors[tech])

    # Set up the figure
    plot = figure(
        plot_width=800, plot_height=500,
        tools=["pan", "reset", "zoom_in", "zoom_out", "save", "help"],
        title=title,
        # sizing_mode="scale_both"
    )

    # Add stacked area chart to plot
    area_renderers = plot.vbar_stack(
        stackers=stacked_cols,
        x=x_col,
        source=source,
        color=colors,
        width=1,
    )
    # Note: can easily change vbar_stack to varea_stack by replacing the plot
    # function and removing the width argument. However, hovertools don't work
    # with varea_stack.

    # Add load line chart to plot
    load_renderer = plot.line(
        x=df[x_col],
        y=df[line_cols[0]],
        line_color="black",
        line_width=2,
        name="Load"
    )

    # Keep track of legend items and load renderers
    legend_items = [(x, [area_renderers[i]]) for i, x in enumerate(stacked_cols)
                    if df[x].mean() > 0] + [("Load", [load_renderer])]
    load_renderers = [load_renderer]

    # Add 'Load + ...' lines
    if line_cols[1] not in df.columns:
        inactive_exports = True
    else:
        inactive_exports = (df[line_cols[1]] == 0).all()
    inactive_storage = (df[line_cols[2]] == 0).all()

    if inactive_exports:
        line_cols = [line_cols[0], line_cols[2]]
    else:
        # Add export line to plot
        label = "Load + Exports"
        exports_renderer = plot.line(
            x=df[x_col],
            y=df[line_cols[0:2]].sum(axis=1),
            line_color="black",
            line_width=2,
            line_dash="dashed",
            name=label
        )
        legend_items.append((label, [exports_renderer]))
        load_renderers.append(exports_renderer)

    if not inactive_storage:
        # Add storage line to plot
        label = legend_items[-1][0] + " + Storage Charging"
        stor_renderer = plot.line(
            x=df[x_col],
            y=df[line_cols].sum(axis=1),
            line_color="black",
            line_width=2,
            line_dash="dotted",
            name=label
        )
        legend_items.append((label, [stor_renderer]))
        load_renderers.append(stor_renderer)

    # Add Legend
    legend = Legend(items=legend_items)
    plot.add_layout(legend, 'right')
    plot.legend[0].items.reverse()  # Reverse legend to match stacked order
    plot.legend.click_policy = 'hide'  # Add interactivity to the legend
    # Note: Doesn't rescale the graph down, simply hides the area
    # Note2: There's currently no way to auto-size legend based on graph size(?)
    # except for maybe changing font size automatically?
    show_hide_legend(plot=plot)  # Hide legend on double click

    # Format Axes (labels, number formatting, range, etc.)
    plot.xaxis.axis_label = "Hour Ending"
    plot.yaxis.axis_label = "Dispatch ({})".format(power_unit)
    plot.yaxis.formatter = NumeralTickFormatter(format="0,0")
    plot.y_range.end = ylimit  # will be ignored if ylimit is None

    # Add HoverTools for stacked bars/areas
    for r in area_renderers:
        power_source = r.name
        hover = HoverTool(
            tooltips=[
                ("Hour Ending", "@x"),
                ("Source", power_source),
                ("Dispatch", "@%s{0,0} %s" % (power_source, power_unit))
            ],
            renderers=[r],
            toggleable=False)
        plot.add_tools(hover)

    # Add HoverTools for load lines
    for r in load_renderers:
        load_type = r.name
        hover = HoverTool(
            tooltips=[
                ("Hour Ending", "@x"),
                (load_type, "@y{0,0} %s" % power_unit),
            ],
            renderers=[r],
            toggleable=False)
        plot.add_tools(hover)

    return plot
Пример #3
0
def create_plot(df, title, tech_colors={}):
    """

    :param df:
    :param title: string, plot title
    :param tech_colors: optional dict that maps technologies to colors.
        Technologies without a specified color will use a default palette
    :return:
    """
    # TODO: handle empty dataframe (will give bokeh warning)

    technologies = df["technology"].unique()

    # Create a map between factor (technology) and color.
    techs_wo_colors = [t for t in technologies if t not in tech_colors.keys()]
    default_cmap = dict(zip(techs_wo_colors, cividis(len(techs_wo_colors))))
    colormap = {}
    for t in technologies:
        if t in tech_colors:
            colormap[t] = tech_colors[t]
        else:
            colormap[t] = default_cmap[t]

    # Set up the figure
    plot = figure(
        plot_width=800,
        plot_height=500,
        tools=["pan", "reset", "zoom_in", "zoom_out", "save", "help"],
        title=title,
        # sizing_mode="scale_both"
    )

    # Add scattered cap factors to plot. Do this one tech at a time so as to
    # allow interactivity such as hiding cap factors by tech by clicking
    # on legend
    renderers = []
    for tech in technologies:
        sub_df = df[df["technology"] == tech]
        source = ColumnDataSource(data=sub_df)

        r = plot.circle(
            x="period",
            y="cap_factor",
            # legend=tech,
            source=source,
            line_color=colormap[tech],
            fill_color=colormap[tech],
            size=12,
            alpha=0.4)
        renderers.append(r)

    # Keep track of legend items
    legend_items = [(y, [renderers[i]]) for i, y in enumerate(technologies)]

    # Add Legend
    legend = Legend(items=legend_items)
    plot.add_layout(legend, "right")
    plot.legend.click_policy = "hide"  # Add interactivity to the legend
    plot.legend.title = "Technology"
    # # Note: Doesn't rescale the graph down, simply hides the area
    # # Note2: There's currently no way to auto-size legend based on graph size(?)
    # # except for maybe changing font size automatically?
    show_hide_legend(plot=plot)  # Hide legend on double click

    # Format Axes (labels, number formatting, range, etc.)
    plot.xaxis.axis_label = "Period"
    plot.yaxis.axis_label = "Capacity Factor (%)"
    plot.xaxis[0].ticker = df["period"].unique()
    plot.yaxis.formatter = NumeralTickFormatter(format="0%")

    for r in renderers:
        hover = HoverTool(tooltips=[("Project", "@project"),
                                    ("Technology", "@technology"),
                                    ("Period", "@period"),
                                    ("Capacity Factor", "@cap_factor{0%}")],
                          renderers=[r],
                          toggleable=False)
        plot.add_tools(hover)

    # Alternative, more succinct approach that uses factor_cmap and plots all
    # circles at once (but less legend interactivity and customizable)
    #
    # colors = factor_cmap(
    #     field_name='technology',
    #     palette=cividis,
    #     factors=df["technology"].unique()
    # )
    #
    # r = plot.circle(
    #     x="period",
    #     y="cap_factor",
    #     legend="technology",
    #     source=source,
    #     line_color=colors,
    #     fill_color=colors,
    #     size=12,
    #     alpha=0.4
    # )
    #
    # Add HoverTools
    # hover = HoverTool(
    #     tooltips=[
    #         ("Project", "@project"),
    #         ("Technology", "@technology"),
    #         ("Period", "@period"),
    #         ("Capacity Factor", "@cap_factor{0%}")
    #     ],
    #     renderers=[r],
    #     toggleable=False)
    # plot.add_tools(hover)

    return plot
def create_plot(df, title, power_unit, ylimit=None):
    """

    :param df:
    :param title: string, plot title
    :param power_unit: string, the unit of power used in the database/model
    :param ylimit: float/int, upper limit of y-axis; optional
    :return:
    """

    # Set up data source
    source = ColumnDataSource(data=df)

    # Determine column types for plotting, legend and colors
    x_col = "hour_on_period"
    power_col = "Power"
    commitment_col = "Committed Capacity"
    stable_level_col = "Minimum Output"
    all_reserves = [
        "bottom_reserves",
        "Regulation Down",
        "Load Following Down",
        "Load Following Up",
        "Regulation Up",
        "Frequency Response",
        "Spinning Reserves",
    ]
    active_reserves = list(
        df[all_reserves].columns[df[all_reserves].notna().all()
                                 & df[all_reserves].mean() > 0])

    # Setup the reserve colors
    colors = grey(len(active_reserves) +
                  2)[1:-1]  # skip the white/black colors
    alphas = [0] + [1] * (len(active_reserves) - 1) if active_reserves else []

    # Set up the figure
    plot = figure(
        plot_width=800,
        plot_height=500,
        tools=["pan", "reset", "zoom_in", "zoom_out", "save", "help"],
        title=title,
    )

    # Add reserve area renderers
    area_renderers = plot.vbar_stack(
        stackers=active_reserves,
        x=x_col,
        source=source,
        fill_color=colors,
        fill_alpha=alphas,
        line_color=colors,
        line_alpha=alphas,
        width=1,
    )

    # Add operations to plot
    power_renderer = plot.step(
        name="Power",
        source=source,
        x=x_col,
        y=power_col,
        color="black",
        mode="center",
    )
    commitment_renderer = plot.step(
        name="Committed Capacity",
        source=source,
        x=x_col,
        y=commitment_col,
        color="black",
        line_dash="dashed",
        mode="center",
    )
    stable_level_renderer = plot.step(
        name="Minimum Output",
        source=source,
        x=x_col,
        y=stable_level_col,
        color="black",
        line_dash="dotted",
        mode="center",
    )

    # Add legend items
    legend_items = [
        (commitment_renderer.name, [commitment_renderer]),
        (power_renderer.name, [power_renderer]),
        (stable_level_renderer.name, [stable_level_renderer]),
    ] + list(reversed([(r.name, [r]) for r in area_renderers[1:]]))

    # Add Legend
    legend = Legend(items=legend_items)
    plot.add_layout(legend, "right")
    plot.legend.click_policy = "hide"  # Add interactivity to the legend
    # Note: Doesn't rescale the graph down, simply hides the area
    # Note2: There's currently no way to auto-size legend based on graph size(?)
    # except for maybe changing font size automatically?
    show_hide_legend(plot=plot)  # Hide legend on double click

    # Format Axes (labels, number formatting, range, etc.)
    plot.xaxis.axis_label = "Hour"
    plot.yaxis.axis_label = power_unit
    plot.yaxis.formatter = NumeralTickFormatter(format="0,0")
    plot.y_range.end = ylimit  # will be ignored if ylimit is None

    # Add HoverTools
    # Note: stepped lines or varea charts not yet supported (lines/bars OK)
    # Note: skip bottom renderer for vbars/areas since it's just a helper
    hover_renderers = area_renderers[1:] + [
        commitment_renderer,
        power_renderer,
        stable_level_renderer,
    ]
    for r in hover_renderers:
        tooltips = [("Hour", "@%s" % x_col),
                    (r.name, "@$name{0,0} %s" % power_unit)]
        if r.name == "Power":
            tooltips.append(("% of Committed", "@power_pct_of_committed{0%}"))
        elif r.name == "Minimum Output":
            tooltips.append(
                ("% of Committed", "@min_stable_level_pct_of_committed{0%}"))
        hover = HoverTool(tooltips=tooltips, renderers=[r], toggleable=False)
        plot.add_tools(hover)

    return plot