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
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
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