Exemple #1
0
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
    )
Exemple #2
0
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
Exemple #4
0
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)
Exemple #5
0
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
Exemple #6
0
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
Exemple #7
0
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
Exemple #8
0
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