def map_lmp(s_grid, lmp, us_states_dat=None, lmp_min=20, lmp_max=45, is_website=False): """Plots average LMP by color coding buses :param powersimdata.input.grid.Grid s_grid: scenario grid :param pandas.DataFrame lmp: locational marginal prices calculated for the scenario :param dict us_states_dat: dictionary of state border lats/lons. If None, get from :func:`postreise.plot.plot_states.get_state_borders`. :param inf/float lmp_min: minimum LMP to clamp plot range to. :param inf/float lmp_max: maximum LMP to clamp plot range to. :param bool is_website: changes text/legend formatting to look better on the website :return: (*bokeh.models.layout.Row*) bokeh map visual in row layout """ if us_states_dat is None: us_states_dat = get_state_borders() bus = project_bus(s_grid.bus) bus_segments = _construct_bus_data(bus, lmp, lmp_min, lmp_max) return _construct_shadowprice_visuals( bus_segments, us_states_dat, lmp_min, lmp_max, is_website )
def map_upgrades(scenario1, scenario2, **plot_kwargs): """Plot capacity differences for branches & DC lines between two scenarios. :param powersimdata.scenario.scenario.Scenario scenario1: first scenario. :param powersimdata.scenario.scenario.Scenario scenario2: second scenario. :param \\*\\*plot_kwargs: collected keyword arguments to be passed to _map_upgrades. :return: (*bokeh.plotting.figure.Figure*) -- Bokeh map plot of color-coded upgrades. """ if not {scenario1.info["interconnect"], scenario2.info["interconnect"] } == {"USA"}: raise ValueError("Scenarios to compare must be USA") grid1 = scenario1.state.get_grid() grid2 = scenario2.state.get_grid() branch_merge = get_branch_differences(grid1.branch, grid2.branch) dc_merge = get_dcline_differences(grid1.dcline, grid2.dcline, grid1.bus) state_shapes = get_state_borders() # Since we hardcode to USA only, we know that the first 9 DC Lines are really B2Bs b2b_indices = list(range(9)) map_plot = _map_upgrades(branch_merge, dc_merge, state_shapes, b2b_indices, **plot_kwargs) return map_plot
def map_plant_capacity(scenario, us_states_dat=None, size_factor=1): """Makes map of renewables from change table 'new plants'. Size/area indicates capacity. :param powersimdata.scenario.scenario.Scenario scenario: scenario instance. :param dict us_states_dat: dictionary of state border lats/lons. If None, get from :func:`postreise.plot.plot_states.get_state_borders`. :param float/int size_factor: scale size of glyphs. """ _check_scenario_is_in_analyze_state(scenario) # prepare data from the change table to select new plants ct = scenario.state.get_ct() # check that there are new plants, check scenario is in analyze state if "new_plant" not in ct.keys(): raise ValueError( "There are no new plants added in the selected scenario. Please choose a different scenario." ) if us_states_dat is None: us_states_dat = get_state_borders() newplant = pd.DataFrame(ct["new_plant"]) newplant = newplant.set_index("bus_id") newplant = newplant[newplant.Pmax > 0] # merge with bus info to get coordinates gridscen = scenario.state.get_grid() bus_of_interest = gridscen.bus.loc[list(set(newplant.index))] bus_capacity = bus_of_interest.merge(newplant, right_index=True, left_index=True) bus_map = project_bus(bus_capacity) bus_map1 = bus_map.loc[bus_map.Pmax > 1] rar_df = bus_map1.loc[(bus_map1.type_y == "solar") | (bus_map1.type_y == "wind")] # group by coordinates rar_df = rar_df.groupby(["lat", "lon"]).agg({ "Pmax": "sum", "x": "mean", "y": "mean" }) a, b = project_borders(us_states_dat) rar_source = ColumnDataSource({ "x": rar_df["x"], "y": rar_df["y"], "capacity": (rar_df["Pmax"] * size_factor)**0.5, "capacitymw": rar_df["Pmax"], }) tools: str = "pan,wheel_zoom,reset,save" p = figure( tools=tools, x_axis_location=None, y_axis_location=None, plot_width=800, plot_height=800, output_backend="webgl", sizing_mode="stretch_both", match_aspect=True, ) # for legend, plot behind tiles p.circle( -8.1e6, 5.2e6, fill_color=be_green, color=be_green, alpha=0.5, size=50, legend_label="Renewable Capacity Added", ) p.add_tile(get_provider(Vendors.CARTODBPOSITRON_RETINA)) # state borders p.patches(a, b, fill_alpha=0.0, line_color="gray", line_width=2) # capacity circles circle = p.circle( "x", "y", fill_color=be_green, color=be_green, alpha=0.8, size="capacity", source=rar_source, ) p.legend.label_text_font_size = "12pt" p.legend.location = "bottom_right" hover = HoverTool( tooltips=[ ("Capacity (MW)", "@capacitymw"), ], renderers=[circle], ) p.add_tools(hover) return p
def map_carbon_emission_bar( bus_info_and_emission, scenario_name, color_coal="black", color_ng="purple", us_states_dat=None, size_factor=1.0, ): """Makes map of carbon emissions, color code by fuel type. Size/area indicates emissions. :param dict us_states_dat: dictionary of state border lats/lons. If None, get from :func:`postreise.plot.plot_states.get_state_borders`. :param pandas.DataFrame bus_info_and_emission: info and emission of buses by :func:`combine_bus_info_and_emission`. :param str scenario_name: name of scenario for labeling. :param str color_coal: color assigned for coal :param str color_ng: color assigned for natural gas :param float size_factor: height scale for bars """ if us_states_dat is None: us_states_dat = get_state_borders() bus_map = project_bus(bus_info_and_emission) bus_map = group_zone(bus_map) a, b = project_borders(us_states_dat) # plotting adjustment constants ha = 85000 size_factor = size_factor * 0.02 bus_source = ColumnDataSource({ "x": bus_map["x"], "x2": bus_map["x"] + ha * 2, "y": bus_map["y"], "coaly": bus_map["y"] + bus_map["coal"] * size_factor, "ngy": bus_map["y"] + bus_map["ng"] * size_factor, }) # Set up figure tools: str = "pan,wheel_zoom,reset,hover,save" p = figure( title=scenario_name, tools=tools, x_axis_location=None, y_axis_location=None, plot_width=800, plot_height=800, ) p_legend = figure( x_axis_location=None, y_axis_location=None, toolbar_location=None, plot_width=200, plot_height=200, y_range=(0, 2), x_range=(0, 2), ) p.add_tile(get_provider(Vendors.CARTODBPOSITRON_RETINA)) # state borders p.patches(a, b, fill_alpha=0.0, line_color="black", line_width=2) # emissions bars width = 150000.0 alpha = 0.7 p.vbar( x="x2", bottom="y", top="coaly", width=width, color=color_coal, source=bus_source, alpha=alpha, ) p.vbar( x="x", bottom="y", top="ngy", width=width, color=color_ng, source=bus_source, alpha=alpha, ) bus_map = pd.DataFrame(bus_map) # citation fields cit_x = [] cit_y = [] cit_text = [] # constants for adjustments of text labels va = 1000 m = 1000000 # x values are offset so vbars are side by side. cit_x.append([i - ha * 1.5 for i in bus_map.x.values.tolist()]) cit_x.append([i + ha for i in bus_map.x.values.tolist()]) cit_y.append( np.add( [i + va * 2 for i in bus_map.y.values.tolist()], [i * size_factor for i in bus_map.ng.values.tolist()], )) cit_y.append( np.add( [i + va * 2 for i in bus_map.y.values.tolist()], [i * size_factor for i in bus_map.coal.values.tolist()], )) cit_text.append( [round(num, 1) for num in [i / m for i in bus_map.ng.values.tolist()]]) cit_text.append([ round(num, 1) for num in [i / m for i in bus_map.coal.values.tolist()] ]) for j in range(0, len(cit_x)): for i in range(0, len(cit_x[j])): citation = Label( x=cit_x[j][i], y=cit_y[j][i], x_units="data", y_units="data", text_font_size="20pt", text=str(cit_text[j][i]), render_mode="css", border_line_color="black", border_line_alpha=0, background_fill_color="white", background_fill_alpha=0.8, ) p.add_layout(citation) # custom legend p_legend.square(1, 1, fill_color="white", color="white", size=600) p_legend.square(1, 1, fill_color="white", color="black", size=400) p_legend.square(0.4, 0.8, fill_color="black", color="black", size=40, alpha=0.7) p_legend.square(0.4, 0.2, fill_color="purple", color="purple", size=40, alpha=0.7) source = ColumnDataSource(data=dict( x=[0.7, 0.7, 0.05], y=[0.6, 0.1, 1.5], text=["Coal", "Natural Gas", "Emissions (Million Tons)"], )) labels = LabelSet( x="x", y="y", text="text", source=source, level="glyph", x_offset=0, y_offset=0, render_mode="css", text_font_size="20pt", ) p_legend.add_layout(labels) return row(p_legend, p)
def map_carbon_emission( bus_info_and_emission, color_coal="black", color_ng=be_purple, label_coal=u"Coal: CO\u2082", label_ng=u"Natural gas: CO\u2082", us_states_dat=None, size_factor=1, web=True, agg=True, type1="natural gas", type2="coal", ): """Makes map of carbon emissions, color code by fuel type. Size/area indicates emissions. :param pandas.DataFrame bus_info_and_emission: info and emission of buses as returned by :func:`combine_bus_info_and_emission`. :param str color_ng: color assigned for ng, default to BE purple :param str color_coal: color associated with coal, default to black/gray :param str label_coal: label for legend associated with coal. :param str label_ng: label for legend associated with ng. :param dict us_states_dat: dictionary of state border lats/lons. If None, get from :func:`postreise.plot.plot_states.get_state_borders`. :param float size_factor: scaling factor for size of emissions circles glyphs :param boolean web: if true, optimizes figure for web-based presentation :param boolean agg: if true, aggregates points by lat lon within a given radius :param str type1: label for hover over tool tips, first color/type (usual choices: natural gas or increase if making a diff map) :param str type2: label for hover over tool tips, second color/type (usual choices: coal or decrease if making diff map) """ # us states borders, prepare data if us_states_dat is None: us_states_dat = get_state_borders() a, b = project_borders(us_states_dat) # prepare data frame for emissions data bus_map = _prepare_busmap(bus_info_and_emission, color_ng, color_coal, agg=agg, type1=type1, type2=type2) bus_map = bus_map.sort_values(by=["color"]) bus_source = ColumnDataSource({ "x": bus_map["x"], "y": bus_map["y"], "size": (bus_map["amount"] / 10000 * size_factor)**0.5 + 2, "radius": (bus_map["amount"] * 1000 * size_factor)**0.5 + 10, "tons": bus_map["amount"], "color": bus_map["color"], "type": bus_map["type"], }) # Set up figure tools: str = "pan,wheel_zoom,reset,save" p = figure( tools=tools, x_axis_location=None, y_axis_location=None, plot_width=800, plot_height=800, output_backend="webgl", sizing_mode="stretch_both", match_aspect=True, ) p_legend = figure( x_axis_location=None, y_axis_location=None, toolbar_location=None, plot_width=200, plot_height=400, y_range=(0, 4), x_range=(0, 2), output_backend="webgl", ) # circle glyphs that exist only for the web legend, # these are plotted behind the tiles, not visible p.circle( -8.1e6, 5.2e6, fill_color=color_coal, color=color_coal, alpha=0.5, size=50, legend_label=label_coal, ) p.circle( -8.1e6, 5.2e6, fill_color=color_ng, color=color_ng, alpha=0.5, size=50, legend_label=label_ng, ) p.add_tile(get_provider(Vendors.CARTODBPOSITRON_RETINA)) # state borders p.patches(a, b, fill_alpha=0.0, line_color="gray", line_width=2) # emissions circles, web version if web: circle = p.circle( "x", "y", fill_color="color", color="color", alpha=0.25, radius="radius", source=bus_source, ) else: p.circle( "x", "y", fill_color="color", color="color", alpha=0.25, size="size", source=bus_source, ) # legend: white square background and bars per color code p_legend.square(1, [1, 3], fill_color="white", color="white", size=300) p_legend.square(1, [1, 3], fill_color="white", color="black", size=(2000000 / 100)**0.5) p_legend.circle( 1, y=np.repeat([1, 3], 3), fill_color=np.repeat([color_coal, color_ng], 3), color=np.repeat([color_coal, color_ng], 3), alpha=0.25, size=[ (10000000 / 10000 * size_factor)**0.5 + 2, (5000000 / 10000 * size_factor)**0.5 + 2, (1000000 / 10000 * size_factor)**0.5 + 2, ] * 2, ) source = ColumnDataSource(data=dict( x=[1, 1, 1, 0.3, 1, 1, 1, 0.3], y=[0.9, 1.1, 1.3, 1.55, 2.9, 3.1, 3.3, 3.55], text=["1M", "5M", "10M", label_coal, "1M", "5M", "10M", label_ng], )) labels = LabelSet( x="x", y="y", text="text", source=source, level="glyph", x_offset=0, y_offset=0, render_mode="css", ) p_legend.add_layout(labels) if web: p.legend.location = "bottom_right" p.legend.label_text_font_size = "12pt" hover = HoverTool( tooltips=[ ("Type", "@type"), (u"Tons CO\u2082", "@tons"), ], renderers=[circle], ) p.add_tools(hover) return_p = p else: p.legend.visible = False return_p = row(p_legend, p) return return_p
def map_risk_bind( risk_or_bind, congestion_stats, branch, us_states_dat=None, vmin=None, vmax=None, is_website=False, ): """Makes map showing risk or binding incidents on US states map. :param str risk_or_bind: specify plotting "risk" or "bind" :param pandas.DataFrame congestion_stats: data frame as returned by :func:`postreise.analyze.transmission.utilization.generate_cong_stats`. :param pandas.DataFrame branch: branch data frame. :param dict us_states_dat: dictionary of state border lats/lons. If None, get from :func:`postreise.plot.plot_states.get_state_borders`. :param int/float vmin: minimum value for color range. If None, use data minimum. :param int/float vmax: maximum value for color range. If None, use data maximum. :param bool is_website: changes text/legend formatting to look better on the website :return: -- map of lines with risk and bind incidents color coded """ if us_states_dat is None: us_states_dat = get_state_borders() if risk_or_bind == "risk": risk_or_bind_units = "Risk (MWH)" if risk_or_bind == "bind": risk_or_bind_units = "Binding incidents" # projection steps for mapping branch_congestion = pd.concat( [branch.loc[congestion_stats.index], congestion_stats], axis=1) branch_map_all = project_branch(branch) multi_line_source_all = ColumnDataSource({ "xs": branch_map_all[["from_x", "to_x"]].values.tolist(), "ys": branch_map_all[["from_y", "to_y"]].values.tolist(), "cap": branch_map_all["rateA"] / 2000 + 0.2, }) a, b = project_borders(us_states_dat) tools: str = "pan,wheel_zoom,reset,save" branch_congestion = branch_congestion[branch_congestion[risk_or_bind] > 0] branch_congestion.sort_values(by=[risk_or_bind]) branch_map = project_branch(branch_congestion) min_val = branch_congestion[risk_or_bind].min() if vmin is None else vmin max_val = branch_congestion[risk_or_bind].max() if vmax is None else vmax mapper = linear_cmap(field_name=risk_or_bind, palette=traffic_palette, low=min_val, high=max_val) color_bar = ColorBar( color_mapper=mapper["transform"], width=385 if is_website else 500, height=5, location=(0, 0), title=risk_or_bind_units, orientation="horizontal", padding=5, ) multi_line_source = ColumnDataSource({ "xs": branch_map[["from_x", "to_x"]].values.tolist(), "ys": branch_map[["from_y", "to_y"]].values.tolist(), risk_or_bind: branch_map[risk_or_bind], "value": branch_map[risk_or_bind].round(), "cap": branch_map["capacity"] / 1000 + 2, "capacity": branch_map.rateA.round(), }) # Set up figure p = figure( tools=tools, x_axis_location=None, y_axis_location=None, plot_width=800, plot_height=800, output_backend="webgl", sizing_mode="stretch_both", match_aspect=True, ) p.add_layout(color_bar, "center") p.add_tile(get_provider(Vendors.CARTODBPOSITRON)) p.patches(a, b, fill_alpha=0.0, line_color="gray", line_width=1) p.multi_line( "xs", "ys", color="gray", line_width="cap", source=multi_line_source_all, alpha=0.5, ) lines = p.multi_line("xs", "ys", color=mapper, line_width="cap", source=multi_line_source) hover = HoverTool( tooltips=[ ("Capacity MW", "@capacity"), (risk_or_bind_units, "@value"), ], renderers=[lines], ) p.add_tools(hover) return p
def map_utilization(utilization_df, branch, us_states_dat=None, vmin=None, vmax=None, is_website=False): """Makes map showing utilization. Utilization input can either be medians only, or can be normalized utilization dataframe :param pandas.DataFrame utilization_df: utilization returned by :func:`postreise.analyze.transmission.utilization.get_utilization` :param pandas.DataFrame branch: branch data frame. :param dict us_states_dat: dictionary of state border lats/lons. If None, get from :func:`postreise.plot.plot_states.get_state_borders`. :param int/float vmin: minimum value for color range. If None, use data minimum. :param int/float vmax: maximum value for color range. If None, use data maximum. :param bool is_website: changes text/legend formatting to look better on the website :return: -- map of lines with median utilization color coded """ if us_states_dat is None: us_states_dat = get_state_borders() branch_mask = branch.rateA != 0 median_util = utilization_df[branch.loc[branch_mask].index].median() branch_utilization = pd.concat( [branch.loc[branch_mask], median_util.rename("median_utilization")], axis=1) lines = branch_utilization.loc[( branch_utilization["branch_device_type"] == "Line")] min_val = lines["median_utilization"].min() if vmin is None else vmin max_val = lines["median_utilization"].max() if vmax is None else vmax mapper1 = linear_cmap( field_name="median_utilization", palette=traffic_palette, low=min_val, high=max_val, ) color_bar = ColorBar( color_mapper=mapper1["transform"], width=385 if is_website else 500, height=5, location=(0, 0), title="median utilization", orientation="horizontal", padding=5, ) branch_map = project_branch(branch_utilization) branch_map = branch_map.sort_values(by=["median_utilization"]) branch_map = branch_map[~branch_map.isin([np.nan, np.inf, -np.inf]).any(1)] # state borders a, b = project_borders(us_states_dat) multi_line_source = ColumnDataSource({ "xs": branch_map[["from_x", "to_x"]].values.tolist(), "ys": branch_map[["from_y", "to_y"]].values.tolist(), "median_utilization": branch_map.median_utilization, "cap": branch_map.rateA / 2000 + 0.2, "util": branch_map.median_utilization.round(2), "capacity": branch_map.rateA.round(), }) # Set up figure tools: str = "pan,wheel_zoom,reset,save" p = figure( tools=tools, x_axis_location=None, y_axis_location=None, plot_width=800, plot_height=800, output_backend="webgl", sizing_mode="stretch_both", match_aspect=True, ) p.add_layout(color_bar, "center") p.add_tile(get_provider(Vendors.CARTODBPOSITRON)) p.patches(a, b, fill_alpha=0.0, line_color="gray", line_width=2) lines = p.multi_line("xs", "ys", color=mapper1, line_width="cap", source=multi_line_source) hover = HoverTool( tooltips=[ ("Capacity MW", "@capacity"), ("Utilization", "@util{f0.00}"), ], renderers=[lines], ) p.add_tools(hover) return p
def map_interconnections( grid, state_counts, hover_choice, hvdc_width=1, us_states_dat=None ): """Maps transmission lines color coded by interconnection. :param powersimdata.input.grid.Grid grid: grid object. :param pandas.DataFrame state_counts: state names and node counts, created by :func:`count_nodes_per_state`. :param str hover_choice: "nodes" for state_counts nodes per state, otherwise HVDC capacity in hover over tool tips for hvdc lines only. :param float hvdc_width: adjust width of HVDC lines on map. :param dict us_states_dat: dictionary of state border lats/lons. If None, get from :func:`postreise.plot.plot_states.get_state_borders`. :return: (*bokeh.plotting.figure*) -- map of transmission lines. """ if us_states_dat is None: us_states_dat = get_state_borders() # projection steps for mapping branch = grid.branch branch_bus = grid.bus branch_map = project_branch(branch) branch_map["point1"] = list(zip(branch_map.to_lat, branch_map.to_lon)) branch_map["point2"] = list(zip(branch_map.from_lat, branch_map.from_lon)) branch_map["dist"] = branch_map.apply( lambda row: distance.haversine(row["point1"], row["point2"]), axis=1 ) # speed rendering on website by removing very short branches branch_map = branch_map.loc[branch_map.dist > 5] branch_west = branch_map.loc[branch_map.interconnect == "Western"] branch_east = branch_map.loc[branch_map.interconnect == "Eastern"] branch_tx = branch_map.loc[branch_map.interconnect == "Texas"] branch_mdc = grid.dcline branch_mdc["from_lon"] = branch_bus.loc[branch_mdc.from_bus_id, "lon"].values branch_mdc["from_lat"] = branch_bus.loc[branch_mdc.from_bus_id, "lat"].values branch_mdc["to_lon"] = branch_bus.loc[branch_mdc.to_bus_id, "lon"].values branch_mdc["to_lat"] = branch_bus.loc[branch_mdc.to_bus_id, "lat"].values branch_mdc = project_branch(branch_mdc) # back to backs are index 0-8, treat separately branch_mdc1 = branch_mdc.iloc[ 9:, ] b2b = branch_mdc.iloc[ 0:9, ] branch_mdc_leg = branch_mdc branch_mdc_leg.loc[0:8, ["to_x"]] = np.nan branch_mdc_leg["to_x"] = branch_mdc_leg["to_x"].fillna(branch_mdc_leg["from_x"]) branch_mdc_leg.loc[0:8, ["to_y"]] = np.nan branch_mdc_leg["to_y"] = branch_mdc_leg["to_y"].fillna(branch_mdc_leg["from_y"]) # pseudolines for legend to show hvdc and back to back, plot UNDER map multi_line_source6 = ColumnDataSource( { "xs": branch_mdc_leg[["from_x", "to_x"]].values.tolist(), "ys": branch_mdc_leg[["from_y", "to_y"]].values.tolist(), "capacity": branch_mdc_leg.Pmax.astype(float) * 0.00023 + 0.2, "cap": branch_mdc_leg.Pmax.astype(float), } ) # state borders a, b = project_borders(us_states_dat, state_list=list(state_counts["state"])) # transmission data sources line_width_const = 0.000225 multi_line_source = ColumnDataSource( { "xs": branch_west[["from_x", "to_x"]].values.tolist(), "ys": branch_west[["from_y", "to_y"]].values.tolist(), "capacity": branch_west.rateA * line_width_const + 0.1, } ) multi_line_source2 = ColumnDataSource( { "xs": branch_east[["from_x", "to_x"]].values.tolist(), "ys": branch_east[["from_y", "to_y"]].values.tolist(), "capacity": branch_east.rateA * line_width_const + 0.1, } ) multi_line_source3 = ColumnDataSource( { "xs": branch_tx[["from_x", "to_x"]].values.tolist(), "ys": branch_tx[["from_y", "to_y"]].values.tolist(), "capacity": branch_tx.rateA * line_width_const + 0.1, } ) # hvdc multi_line_source4 = ColumnDataSource( { "xs": branch_mdc1[["from_x", "to_x"]].values.tolist(), "ys": branch_mdc1[["from_y", "to_y"]].values.tolist(), "capacity": branch_mdc1.Pmax.astype(float) * line_width_const * hvdc_width + 0.1, "cap": branch_mdc1.Pmax.astype(float), } ) # pseudolines for ac multi_line_source5 = ColumnDataSource( { "xs": b2b[["from_x", "to_x"]].values.tolist(), "ys": b2b[["from_y", "to_y"]].values.tolist(), "capacity": b2b.Pmax.astype(float) * 0.00023 + 0.2, "cap": b2b.Pmax.astype(float), "col": ( "#006ff9", "#006ff9", "#006ff9", "#006ff9", "#006ff9", "#006ff9", "#006ff9", "#8B36FF", "#8B36FF", ), } ) # lower 48 states, patches source = ColumnDataSource( dict( xs=a, ys=b, col=["gray" for i in range(48)], col2=["gray" for i in range(48)], label=list(state_counts["count"]), state_name=list(state_counts["state"]), ) ) # Set up figure tools: str = "pan, wheel_zoom, reset, save" p = figure( tools=tools, x_axis_location=None, y_axis_location=None, plot_width=800, plot_height=800, output_backend="webgl", sizing_mode="stretch_both", match_aspect=True, ) # for legend, hidden lines leg_clr = ["#006ff9", "#8B36FF", "#01D4ED", "#FF2370"] leg_lab = ["Western", "Eastern", "Texas", "HVDC"] leg_xs = [-1.084288e07] * 4 leg_ys = [4.639031e06] * 4 for (colr, leg, x, y) in zip(leg_clr, leg_lab, leg_xs, leg_ys): p.line(x, y, color=colr, width=5, legend=leg) # pseudo lines for hover tips lines = p.multi_line( "xs", "ys", color="black", line_width="capacity", source=multi_line_source6 ) # background tiles p.add_tile(get_provider(Vendors.CARTODBPOSITRON)) # state borders patch = p.patches("xs", "ys", fill_alpha=0.0, line_color="col", source=source) # branches source_list = [multi_line_source, multi_line_source2, multi_line_source3] for (colr, source) in zip(leg_clr[0:3], source_list): p.multi_line("xs", "ys", color=colr, line_width="capacity", source=source) p.multi_line( "xs", "ys", color="#FF2370", line_width="capacity", source=multi_line_source4 ) # pseudo ac p.multi_line( "xs", "ys", color="col", line_width="capacity", source=multi_line_source5 ) # triangles for b2b p.scatter( x=b2b.from_x, y=b2b.from_y, color="#FF2370", marker="triangle", size=b2b.Pmax / 50 + 5, legend="Back-to-Back", ) # legend formatting p.legend.location = "bottom_right" p.legend.label_text_font_size = "12pt" if hover_choice == "nodes": hover = HoverTool( tooltips=[ ("State", "@state_name"), ("Nodes", "@label"), ], renderers=[patch], ) else: hover = HoverTool( tooltips=[ ("HVDC capacity MW", "@cap"), ], renderers=[lines], ) p.add_tools(hover) return p