diff --git a/pandapower/plotting/collections.py b/pandapower/plotting/collections.py index ed6d0b8e4..334302c36 100644 --- a/pandapower/plotting/collections.py +++ b/pandapower/plotting/collections.py @@ -27,7 +27,7 @@ class TextPath: # so that the test does not fail from pandapower.auxiliary import soft_dependency_error from pandapower.plotting.patch_makers import load_patches, node_patches, gen_patches, \ - sgen_patches, ext_grid_patches, trafo_patches, storage_patches + sgen_patches, ext_grid_patches, trafo_patches, storage_patches, vsc_patches from pandapower.plotting.plotting_toolbox import _rotate_dim2, coords_from_node_geodata, \ position_on_busbar, get_index_array @@ -938,6 +938,148 @@ def create_trafo3w_collection(net, trafo3ws=None, picker=False, infofunc=None, c return lc, pc +def create_vsc_collection(net, vscs=None, picker=False, size=None, infofunc=None, cmap=None, + norm=None, z=None, clim=None, cbar_title="VSC power", + plot_colormap=True, bus_geodata=None, bus_dc_geodata=None, **kwargs): + """ + Creates a matplotlib line collection of pandapower transformers. + + Input: + **net** (pandapowerNet) - The pandapower network + + OPTIONAL: + **vscs** (list, None) - The VSC indices for which the collections are created. + If None, all VSCs in the grid are considered. + + **picker** (bool, False) - picker argument passed to the patch collection + + **size** (int, None) - size of VSC symbol squares. Should be >0 and + < 0.35*bus_distance + + **infofunc** (function, None) - infofunction for the patch element + + **kwargs** - keyword arguments are passed to the patch function + + OUTPUT: + **lc** - line collection + + **pc** - patch collection + """ + if not MATPLOTLIB_INSTALLED: + soft_dependency_error(str(sys._getframe().f_code.co_name)+"()", "matplotlib") + + vscs = get_index_array(vscs, net.vsc.index) + + if bus_geodata is None: + bus_geodata = net["bus_geodata"] + if bus_dc_geodata is None: + bus_dc_geodata = net["bus_dc_geodata"] + + in_geodata = (net.vsc.bus.loc[vscs].isin(bus_geodata.index) & + net.vsc.bus_dc.loc[vscs].isin(bus_dc_geodata.index)) + vscs = vscs[in_geodata] + vsc_table = net.vsc.loc[vscs] + + coords, vscs_with_geo = coords_from_node_geodata( + vscs, vsc_table.bus.values, vsc_table.bus_dc.values, bus_geodata, "vsc", node_geodata_to=bus_dc_geodata) + + if len(vscs_with_geo) == 0: + return None + + colors = kwargs.pop("color", "k") + linewidths = kwargs.pop("linewidths", 2.) + linewidths = kwargs.pop("linewidth", linewidths) + linewidths = kwargs.pop("lw", linewidths) + if cmap is not None: + if z is None: + z = net.res_vsc.p_mw + colors = [cmap(norm(z.at[idx])) for idx in vscs_with_geo] + + infos = [infofunc(i) for i in range(len(vscs_with_geo))] if infofunc is not None else [] + + lc, pc = _create_complex_branch_collection( + coords, vsc_patches, size, infos, patch_facecolor="none", patch_edgecolor=colors, + line_color=colors, picker=picker, linewidths=linewidths, **kwargs) + + if cmap is not None: + z_duplicated = np.repeat(z.values, 2) + add_cmap_to_collection(lc, cmap, norm, z_duplicated, cbar_title, plot_colormap, clim) + return lc, pc + + +def create_vsc_connection_collection(net, vscs=None, bus_geodata=None, bus_dc_geodata=None, infofunc=None, + cmap=None, clim=None, norm=None, z=None, + cbar_title="Transformer Loading", picker=False, **kwargs): + """ + Creates a matplotlib line collection of pandapower VSCs. + + Input: + **net** (pandapowerNet) - The pandapower network + + OPTIONAL: + **vscs** (list, None) - The VSC indices for which the collections are created. + If None, all VSCs in the network are considered. + + **bus_geodata** (DataFrame, None) - coordinates of AC buses to use for plotting + If None, net["bus_geodata"] is used + + **bus_dc_geodata** (DataFrame, None) - coordinates of DC buses to use for plotting + If None, net["bus_dc_geodata"] is used + + **infofunc** (function, None) - infofunction for the patch element + + **cmap** - colormap for the patch colors + + **clim** (tuple of floats, None) - setting the norm limits for image scaling + + **norm** (matplotlib norm object, None) - matplotlib norm object + + **z** (array, None) - array of values for colormap. Used in case of given + cmap. If None net.res_vsc.p_mw is used. + + **cbar_title** (str, "VSC active power [MW]") - colormap bar title in case of given cmap + + **picker** (bool, False) - picker argument passed to the line collection + + **kwargs - keyword arguments are passed to the patch function + + OUTPUT: + **lc** - line collection + """ + if not MATPLOTLIB_INSTALLED: + soft_dependency_error(str(sys._getframe().f_code.co_name)+"()", "matplotlib") + + vscs = get_index_array(vscs, net.vsc.index) + + if bus_geodata is None: + bus_geodata = net["bus_geodata"] + + if bus_dc_geodata is None: + bus_dc_geodata = net["bus_dc_geodata"] + + in_geodata = (net.vsc.bus.loc[vscs].isin(bus_geodata.index) & + net.vsc.bus_dc.loc[vscs].isin(bus_dc_geodata.index)) + vscs = vscs[in_geodata] + vsc_table = net.vsc.loc[vscs] + + ac_geo = list(zip(bus_geodata.loc[vsc_table["bus"], "x"].values, + bus_geodata.loc[vsc_table["bus_dc"], "y"].values)) + dc_geo = list(zip(bus_dc_geodata.loc[vsc_table["bus_dc"], "x"].values, + bus_dc_geodata.loc[vsc_table["bus_dc"], "y"].values)) + vg = list(zip(ac_geo, dc_geo)) + + info = [infofunc(v) for v in vscs] if infofunc is not None else [] + + lc = _create_line2d_collection(vg, vscs, info, picker=picker, **kwargs) + + if cmap is not None: + if z is None: + z = net.res_vsc.p_mw.loc[vscs] + add_cmap_to_collection(lc, cmap, norm, z, cbar_title, True, clim) + + return lc + + def create_busbar_collection(net, buses=None, infofunc=None, cmap=None, norm=None, picker=False, z=None, cbar_title="Bus Voltage [p.u.]", clim=None, **kwargs): """ diff --git a/pandapower/plotting/patch_makers.py b/pandapower/plotting/patch_makers.py index e880e3b24..a041b3782 100644 --- a/pandapower/plotting/patch_makers.py +++ b/pandapower/plotting/patch_makers.py @@ -407,3 +407,49 @@ def trafo_patches(coords, size, **kwargs): lines.append([p1, lp1]) lines.append([p2, lp2]) return lines, circles, {"patch_edgecolor", "patch_facecolor"} + + +def vsc_patches(coords, size, **kwargs): + """ + Creates a list of patches and line coordinates representing VSCs each connecting an AC and a DC + node. + + :param coords: list of connecting node coordinates (usually should be \ + `[((x11, y11), (x12, y12)), ((x21, y21), (x22, y22)), ...]`) + :type coords: (N, (2, 2)) shaped iterable + :param size: size of the VSC patches + :type size: float + :param kwargs: additional keyword arguments (might contain parameters "patch_edgecolor" and\ + "patch_facecolor") + :type kwargs: + :return: Return values are: \ + - lines (list) - list of coordinates for lines connecting nodes and VSC patches\ + - squares (list of Rectangle) - list containing the VSC patches (squares) + """ + if not MATPLOTLIB_INSTALLED: + soft_dependency_error(str(sys._getframe().f_code.co_name)+"()", "matplotlib") + edgecolor = kwargs.get("patch_edgecolor", "w") + facecolor = kwargs.get("patch_facecolor", "w") + edgecolors = get_color_list(edgecolor, len(coords)) + facecolors = get_color_list(facecolor, len(coords)) + linewidths = kwargs.get("linewidths", 2.) + linewidths = get_linewidth_list(linewidths, len(coords), name_entries="vscs") + squares, lines = list(), list() + for i, (p1, p2) in enumerate(coords): + p1 = np.array(p1) + p2 = np.array(p2) + if np.all(p1 == p2): + continue + d = np.sqrt(np.sum((p1 - p2) ** 2)) # distance + if size is None: + size_this = np.sqrt(d) / 3 + else: + size_this = size + xy1 = p1 - np.array([size_this, size_this]) + xy2 = p2 - np.array([size_this, size_this]) + squares.append(Rectangle(xy1, size_this * 2, size_this * 2, fc=facecolors[i], ec=edgecolors[i], + lw=linewidths[i], hatch="+++")) + squares.append(Rectangle(xy2, size_this * 2, size_this * 2, fc=facecolors[i], ec=edgecolors[i], + lw=linewidths[i], hatch="---")) + lines.append([p1, p2]) + return lines, squares, {"patch_edgecolor", "patch_facecolor"} diff --git a/pandapower/plotting/plotting_toolbox.py b/pandapower/plotting/plotting_toolbox.py index 4fa31e790..d78b4356b 100644 --- a/pandapower/plotting/plotting_toolbox.py +++ b/pandapower/plotting/plotting_toolbox.py @@ -124,7 +124,7 @@ def get_index_array(indices, net_table_indices): def coords_from_node_geodata(element_indices, from_nodes, to_nodes, node_geodata, table_name, - node_name="Bus", ignore_zero_length=True): + node_name="Bus", ignore_zero_length=True, node_geodata_to=None): """ Auxiliary function to get the node coordinates for a number of branches with respective from and to nodes. The branch elements for which there is no geodata available are not included in @@ -145,18 +145,22 @@ def coords_from_node_geodata(element_indices, from_nodes, to_nodes, node_geodata :param ignore_zero_length: States if branches should be left out, if their length is zero, i.e.\ from_node_coords = to_node_coords :type ignore_zero_length: bool, default True + :param node_geodata_to: Dataframe containing x and y coordinates of the "to" nodes (optional, default node_geodata) + :type node_geodata_to: pd.DataFrame :return: Return values are:\ - coords (list) - list of branch coordinates of shape (N, (2, 2))\ - elements_with_geo (set) - the indices of branch elements for which coordinates wer found\ in the node geodata table """ + if node_geodata_to is None: + node_geodata_to = node_geodata have_geo = np.isin(from_nodes, node_geodata.index.values) \ - & np.isin(to_nodes, node_geodata.index.values) + & np.isin(to_nodes, node_geodata_to.index.values) elements_with_geo = np.array(element_indices)[have_geo] fb_with_geo, tb_with_geo = from_nodes[have_geo], to_nodes[have_geo] coords = [[(x_from, y_from), (x_to, y_to)] for x_from, y_from, x_to, y_to in np.concatenate([node_geodata.loc[fb_with_geo, ["x", "y"]].values, - node_geodata.loc[tb_with_geo, ["x", "y"]].values], axis=1) + node_geodata_to.loc[tb_with_geo, ["x", "y"]].values], axis=1) if not ignore_zero_length or not (x_from == x_to and y_from == y_to)] elements_without_geo = set(element_indices) - set(elements_with_geo) if len(elements_without_geo) > 0: diff --git a/pandapower/plotting/simple_plot.py b/pandapower/plotting/simple_plot.py index a04fc0c2b..51488209a 100644 --- a/pandapower/plotting/simple_plot.py +++ b/pandapower/plotting/simple_plot.py @@ -13,8 +13,9 @@ from pandapower.plotting.plotting_toolbox import get_collection_sizes from pandapower.plotting.collections import create_bus_collection, create_line_collection, \ create_trafo_collection, create_trafo3w_collection, \ - create_line_switch_collection, draw_collections, create_bus_bus_switch_collection, create_ext_grid_collection, create_sgen_collection, \ - create_gen_collection, create_load_collection, create_dcline_collection + create_line_switch_collection, draw_collections, create_bus_bus_switch_collection, create_ext_grid_collection, \ + create_sgen_collection, \ + create_gen_collection, create_load_collection, create_dcline_collection, create_vsc_collection from pandapower.plotting.generic_geodata import create_generic_coordinates try: @@ -30,7 +31,7 @@ def simple_plot(net, respect_switches=False, line_width=1.0, bus_size=1.0, ext_g switch_size=2.0, switch_distance=1.0, plot_line_switches=False, scale_size=True, bus_color='b', line_color='grey', dcline_color='c', trafo_color='k', ext_grid_color='y', switch_color='k', library='igraph', show_plot=True, ax=None, - bus_dc_size=1.0, bus_dc_color="m", line_dc_color="c", vsc_size=2.0, vsc_color="c"): + bus_dc_size=1.0, bus_dc_color="m", line_dc_color="c", vsc_size=4.0, vsc_color="orange"): """ Plots a pandapower network as simple as possible. If no geodata is available, artificial geodata is generated. For advanced plotting see the tutorial @@ -149,8 +150,7 @@ def simple_plot(net, respect_switches=False, line_width=1.0, bus_size=1.0, ext_g collections.append(bc_dc) # create VSC collection if len(net.vsc) > 0: - vsc_ac = create_bus_collection(net, net.vsc.bus.values, size=vsc_size, color=vsc_color, - patch_type="rect", zorder=10) + vsc_ac = create_vsc_collection(net, net.vsc.index, size=vsc_size, color=vsc_color, zorder=12) collections.append(vsc_ac) # create line_dc collections if len(net.line_dc) > 0: