def draw_networkx_edges(G, pos, edgelist=None, width=1.0, edge_color='k', style='solid', alpha=1.0, edge_cmap=None, edge_vmin=None, edge_vmax=None, ax=None, arrows=True, label=None, **kwds): """Draw the edges of the graph G. This draws only the edges of the graph G. Parameters ---------- G : graph A networkx graph pos : dictionary A dictionary with nodes as keys and positions as values. Positions should be sequences of length 2. edgelist : collection of edge tuples Draw only specified edges(default=G.edges()) width : float, or array of floats Line width of edges (default=1.0) edge_color : color string, or array of floats Edge color. Can be a single color format string (default='r'), or a sequence of colors with the same length as edgelist. If numeric values are specified they will be mapped to colors using the edge_cmap and edge_vmin,edge_vmax parameters. style : string Edge line style (default='solid') (solid|dashed|dotted,dashdot) alpha : float The edge transparency (default=1.0) edge_ cmap : Matplotlib colormap Colormap for mapping intensities of edges (default=None) edge_vmin,edge_vmax : floats Minimum and maximum for edge colormap scaling (default=None) ax : Matplotlib Axes object, optional Draw the graph in the specified Matplotlib axes. arrows : bool, optional (default=True) For directed graphs, if True draw arrowheads. label : [None| string] Label for legend Returns ------- matplotlib.collection.LineCollection `LineCollection` of the edges Notes ----- For directed graphs, "arrows" (actually just thicker stubs) are drawn at the head end. Arrows can be turned off with keyword arrows=False. Yes, it is ugly but drawing proper arrows with Matplotlib this way is tricky. Examples -------- >>> G=nx.dodecahedral_graph() >>> edges=nx.draw_networkx_edges(G,pos=nx.spring_layout(G)) Also see the NetworkX drawing examples at http://networkx.github.io/documentation/latest/gallery.html See Also -------- draw() draw_networkx() draw_networkx_nodes() draw_networkx_labels() draw_networkx_edge_labels() """ try: import matplotlib import matplotlib.pyplot as plt import matplotlib.cbook as cb from matplotlib.colors import colorConverter, Colormap from matplotlib.collections import LineCollection import numpy except ImportError: raise ImportError("Matplotlib required for draw()") except RuntimeError: print("Matplotlib unable to open display") raise if ax is None: ax = plt.gca() if edgelist is None: edgelist = list(G.edges()) if not edgelist or len(edgelist) == 0: # no edges! return None # set edge positions edge_pos = numpy.asarray([(pos[e[0]], pos[e[1]]) for e in edgelist]) if not cb.iterable(width): lw = (width,) else: lw = width if not cb.is_string_like(edge_color) \ and cb.iterable(edge_color) \ and len(edge_color) == len(edge_pos): if numpy.alltrue([cb.is_string_like(c) for c in edge_color]): # (should check ALL elements) # list of color letters such as ['k','r','k',...] edge_colors = tuple([colorConverter.to_rgba(c, alpha) for c in edge_color]) elif numpy.alltrue([not cb.is_string_like(c) for c in edge_color]): # If color specs are given as (rgb) or (rgba) tuples, we're OK if numpy.alltrue([cb.iterable(c) and len(c) in (3, 4) for c in edge_color]): edge_colors = tuple(edge_color) else: # numbers (which are going to be mapped with a colormap) edge_colors = None else: raise ValueError('edge_color must consist of either color names or numbers') else: if cb.is_string_like(edge_color) or len(edge_color) == 1: edge_colors = (colorConverter.to_rgba(edge_color, alpha), ) else: raise ValueError('edge_color must be a single color or list of exactly m colors where m is the number or edges') edge_collection = LineCollection(edge_pos, colors=edge_colors, linewidths=lw, antialiaseds=(1,), linestyle=style, transOffset = ax.transData, ) edge_collection.set_zorder(1) # edges go behind nodes edge_collection.set_label(label) ax.add_collection(edge_collection) # Note: there was a bug in mpl regarding the handling of alpha values for # each line in a LineCollection. It was fixed in matplotlib in r7184 and # r7189 (June 6 2009). We should then not set the alpha value globally, # since the user can instead provide per-edge alphas now. Only set it # globally if provided as a scalar. if cb.is_numlike(alpha): edge_collection.set_alpha(alpha) if edge_colors is None: if edge_cmap is not None: assert(isinstance(edge_cmap, Colormap)) edge_collection.set_array(numpy.asarray(edge_color)) edge_collection.set_cmap(edge_cmap) if edge_vmin is not None or edge_vmax is not None: edge_collection.set_clim(edge_vmin, edge_vmax) else: edge_collection.autoscale() arrow_collection = None if G.is_directed() and arrows: # a directed graph hack # draw thick line segments at head end of edge # waiting for someone else to implement arrows that will work arrow_colors = edge_colors a_pos = [] p = 1.0-0.25 # make head segment 25 percent of edge length for src, dst in edge_pos: x1, y1 = src x2, y2 = dst dx = x2-x1 # x offset dy = y2-y1 # y offset d = numpy.sqrt(float(dx**2 + dy**2)) # length of edge if d == 0: # source and target at same position continue if dx == 0: # vertical edge xa = x2 ya = dy*p+y1 if dy == 0: # horizontal edge ya = y2 xa = dx*p+x1 else: theta = numpy.arctan2(dy, dx) xa = p*d*numpy.cos(theta)+x1 ya = p*d*numpy.sin(theta)+y1 a_pos.append(((xa, ya), (x2, y2))) arrow_collection = LineCollection(a_pos, colors=arrow_colors, linewidths=[4*ww for ww in lw], antialiaseds=(1,), transOffset = ax.transData, ) arrow_collection.set_zorder(1) # edges go behind nodes arrow_collection.set_label(label) ax.add_collection(arrow_collection) # update view minx = numpy.amin(numpy.ravel(edge_pos[:, :, 0])) maxx = numpy.amax(numpy.ravel(edge_pos[:, :, 0])) miny = numpy.amin(numpy.ravel(edge_pos[:, :, 1])) maxy = numpy.amax(numpy.ravel(edge_pos[:, :, 1])) w = maxx-minx h = maxy-miny padx, pady = 0.05*w, 0.05*h corners = (minx-padx, miny-pady), (maxx+padx, maxy+pady) ax.update_datalim(corners) ax.autoscale_view() # if arrow_collection: return edge_collection
def plot(network, margin=0.05, ax=None, geomap=True, projection=None, bus_colors='b', line_colors='g', bus_sizes=10, line_widths=2, title="", line_cmap=None, bus_cmap=None, boundaries=None, geometry=False, branch_components=['Line', 'Link'], jitter=None, basemap=None): """ Plot the network buses and lines using matplotlib and Basemap. Parameters ---------- margin : float Margin at the sides as proportion of distance between max/min x,y ax : matplotlib ax, defaults to plt.gca() Axis to which to plot the network geomap: bool/str, default True Switch to use Basemap or Cartopy (depends on what is installed). If string is passed, it will be used as a resolution argument. For Basemap users 'c' (crude), 'l' (low), 'i' (intermediate), 'h' (high), 'f' (full) are valid resolutions options. For Cartopy users '10m', '50m', '110m' are valid resolutions options. projection: cartopy.crs.Projection, defaults to None Define the projection of your geomap, only valid if cartopy is installed. If None (default) is passed the projection for cartropy is set to cartopy.crs.PlateCarree bus_colors : dict/pandas.Series Colors for the buses, defaults to "b" bus_sizes : dict/pandas.Series Sizes of bus points, defaults to 10 line_colors : dict/pandas.Series Colors for the lines, defaults to "g" for Lines and "cyan" for Links. Colors for branches other than Lines can be specified using a pandas Series with a MultiIndex. line_widths : dict/pandas.Series Widths of lines, defaults to 2. Widths for branches other than Lines can be specified using a pandas Series with a MultiIndex. title : string Graph title line_cmap : plt.cm.ColorMap/str|dict If line_colors are floats, this color map will assign the colors. Use a dict to specify colormaps for more than one branch type. bus_cmap : plt.cm.ColorMap/str If bus_colors are floats, this color map will assign the colors boundaries : list of four floats Boundaries of the plot in format [x1,x2,y1,y2] branch_components : list of str Branch components to be plotted, defaults to Line and Link. jitter : None|float Amount of random noise to add to bus positions to distinguish overlapping buses Returns ------- bus_collection, branch_collection1, ... : tuple of Collections Collections for buses and branches. """ defaults_for_branches = { 'Link': dict(color="cyan", width=2), 'Line': dict(color="b", width=2), 'Transformer': dict(color='green', width=2) } if not plt_present: logger.error("Matplotlib is not present, so plotting won't work.") return if basemap is not None: logger.warning("argument `basemap` is deprecated, " "use `geomap` instead.") geomap = basemap if cartopy_present and geomap: if projection is None: projection = get_projection_from_crs(network.srid) if ax is None: ax = plt.gca(projection=projection) else: assert isinstance(ax, cartopy.mpl.geoaxes.GeoAxesSubplot), ( 'The passed axis is not a GeoAxesSubplot. You can ' 'create one with: \nimport cartopy.crs as ccrs \n' 'fig, ax = plt.subplots(' 'subplot_kw={"projection":ccrs.PlateCarree()})') elif ax is None: ax = plt.gca() x, y = network.buses["x"], network.buses["y"] if jitter is not None: x = x + np.random.uniform(low=-jitter, high=jitter, size=len(x)) y = y + np.random.uniform(low=-jitter, high=jitter, size=len(y)) if geomap: transform = draw_map(network, x, y, ax, boundaries, margin, geomap) else: transform = ax.transData if isinstance(bus_sizes, pd.Series) and isinstance(bus_sizes.index, pd.MultiIndex): # We are drawing pies to show all the different shares assert len(bus_sizes.index.levels[0].difference(network.buses.index)) == 0, \ "The first MultiIndex level of bus_sizes must contain buses" assert (isinstance(bus_colors, dict) and set(bus_colors).issuperset(bus_sizes.index.levels[1])), \ "bus_colors must be a dictionary defining a color for each element " \ "in the second MultiIndex level of bus_sizes" bus_sizes = bus_sizes.sort_index(level=0, sort_remaining=False)\ * projected_area_factor(ax, network.srid)**2 patches = [] for b_i in bus_sizes.index.levels[0]: s = bus_sizes.loc[b_i] radius = s.sum()**0.5 if radius == 0.0: ratios = s else: ratios = s / s.sum() start = 0.25 for i, ratio in ratios.iteritems(): patches.append( Wedge((x.at[b_i], y.at[b_i]), radius, 360 * start, 360 * (start + ratio), facecolor=bus_colors[i])) start += ratio bus_collection = PatchCollection(patches, match_original=True, transform=transform) ax.add_collection(bus_collection) else: c = pd.Series(bus_colors, index=network.buses.index) s = pd.Series(bus_sizes, index=network.buses.index, dtype="float").fillna(10) bus_collection = ax.scatter(x, y, c=c, s=s, cmap=bus_cmap, edgecolor='face', transform=transform) def as_branch_series(ser): if isinstance(ser, dict) and set(ser).issubset(branch_components): return pd.Series(ser) elif isinstance(ser, pd.Series): if isinstance(ser.index, pd.MultiIndex): return ser index = ser.index ser = ser.values else: index = network.lines.index return pd.Series(ser, index=pd.MultiIndex(levels=(["Line"], index), codes=(np.zeros(len(index)), np.arange(len(index))))) line_colors = as_branch_series(line_colors) line_widths = as_branch_series(line_widths) if not isinstance(line_cmap, dict): line_cmap = {'Line': line_cmap} branch_collections = [] for c in network.iterate_components(branch_components): l_defaults = defaults_for_branches[c.name] l_widths = line_widths.get(c.name, l_defaults['width']) l_nums = None l_colors = line_colors.get(c.name, l_defaults['color']) if isinstance(l_colors, pd.Series): if issubclass(l_colors.dtype.type, np.number): l_nums = l_colors l_colors = None else: l_colors.fillna(l_defaults['color'], inplace=True) if not geometry: segments = (np.asarray( ((c.df.bus0.map(x), c.df.bus0.map(y)), (c.df.bus1.map(x), c.df.bus1.map(y)))).transpose(2, 0, 1)) else: from shapely.wkt import loads from shapely.geometry import LineString linestrings = c.df.geometry.map(loads) assert all(isinstance(ls, LineString) for ls in linestrings), ( "The WKT-encoded geometry in the 'geometry' column must be " "composed of LineStrings") segments = np.asarray(list(linestrings.map(np.asarray))) l_collection = LineCollection(segments, linewidths=l_widths, antialiaseds=(1, ), colors=l_colors, transOffset=ax.transData, transform=transform) if l_nums is not None: l_collection.set_array(np.asarray(l_nums)) l_collection.set_cmap(line_cmap.get(c.name, None)) l_collection.autoscale() ax.add_collection(l_collection) l_collection.set_zorder(1) branch_collections.append(l_collection) bus_collection.set_zorder(2) ax.update_datalim(compute_bbox_with_margins(margin, x, y)) ax.autoscale_view() if geomap: if cartopy_present: ax.outline_patch.set_visible(False) ax.axis('off') ax.set_title(title) return (bus_collection, ) + tuple(branch_collections)
def plot(network, margin=0.05, ax=None, basemap=True, bus_colors='b', line_colors='g', bus_sizes=10, line_widths=2, title="", line_cmap=None, bus_cmap=None, boundaries=None, geometry=False, branch_components=['Line', 'Link'], jitter=None): """ Plot the network buses and lines using matplotlib and Basemap. Parameters ---------- margin : float Margin at the sides as proportion of distance between max/min x,y ax : matplotlib ax, defaults to plt.gca() Axis to which to plot the network basemap : bool, default True Switch to use Basemap bus_colors : dict/pandas.Series Colors for the buses, defaults to "b" bus_sizes : dict/pandas.Series Sizes of bus points, defaults to 10 line_colors : dict/pandas.Series Colors for the lines, defaults to "g" for Lines and "cyan" for Links. Colors for branches other than Lines can be specified using a pandas Series with a MultiIndex. line_widths : dict/pandas.Series Widths of lines, defaults to 2. Widths for branches other than Lines can be specified using a pandas Series with a MultiIndex. title : string Graph title line_cmap : plt.cm.ColorMap/str|dict If line_colors are floats, this color map will assign the colors. Use a dict to specify colormaps for more than one branch type. bus_cmap : plt.cm.ColorMap/str If bus_colors are floats, this color map will assign the colors boundaries : list of four floats Boundaries of the plot in format [x1,x2,y1,y2] branch_components : list of str Branch components to be plotted, defaults to Line and Link. jitter : None|float Amount of random noise to add to bus positions to distinguish overlapping buses Returns ------- bus_collection, branch_collection1, ... : tuple of Collections Collections for buses and branches. """ defaults_for_branches = { 'Link': dict(color="cyan", width=2), 'Line': dict(color="b", width=2), 'Transformer': dict(color='green', width=2) } if not plt_present: logger.error("Matplotlib is not present, so plotting won't work.") return if ax is None: ax = plt.gca() def compute_bbox_with_margins(margin, x, y): #set margins pos = np.asarray((x, y)) minxy, maxxy = pos.min(axis=1), pos.max(axis=1) xy1 = minxy - margin * (maxxy - minxy) xy2 = maxxy + margin * (maxxy - minxy) return tuple(xy1), tuple(xy2) x = network.buses["x"] y = network.buses["y"] if jitter is not None: x = x + np.random.uniform(low=-jitter, high=jitter, size=len(x)) y = y + np.random.uniform(low=-jitter, high=jitter, size=len(y)) if basemap and basemap_present: if boundaries is None: (x1, y1), (x2, y2) = compute_bbox_with_margins(margin, x, y) else: x1, x2, y1, y2 = boundaries bmap = Basemap(resolution='l', epsg=network.srid, llcrnrlat=y1, urcrnrlat=y2, llcrnrlon=x1, urcrnrlon=x2, ax=ax) bmap.drawcountries() bmap.drawcoastlines() x, y = bmap(x.values, y.values) x = pd.Series(x, network.buses.index) y = pd.Series(y, network.buses.index) if isinstance(bus_sizes, pd.Series) and isinstance(bus_sizes.index, pd.MultiIndex): # We are drawing pies to show all the different shares assert len(bus_sizes.index.levels[0].difference(network.buses.index)) == 0, \ "The first MultiIndex level of bus_sizes must contain buses" assert isinstance(bus_colors, dict) and set(bus_colors).issuperset(bus_sizes.index.levels[1]), \ "bus_colors must be a dictionary defining a color for each element " \ "in the second MultiIndex level of bus_sizes" bus_sizes = bus_sizes.sort_index(level=0, sort_remaining=False) patches = [] for b_i in bus_sizes.index.levels[0]: s = bus_sizes.loc[b_i] radius = s.sum()**0.5 ratios = s / s.sum() start = 0.25 for i, ratio in ratios.iteritems(): patches.append( Wedge((x.at[b_i], y.at[b_i]), radius, 360 * start, 360 * (start + ratio), facecolor=bus_colors[i])) start += ratio bus_collection = PatchCollection(patches, match_original=True) ax.add_collection(bus_collection) else: c = pd.Series(bus_colors, index=network.buses.index) if c.dtype == np.dtype('O'): c.fillna("b", inplace=True) c = list(c.values) s = pd.Series(bus_sizes, index=network.buses.index, dtype="float").fillna(10) bus_collection = ax.scatter(x, y, c=c, s=s, cmap=bus_cmap) def as_branch_series(ser): if isinstance(ser, dict) and set(ser).issubset(branch_components): return pd.Series(ser) elif isinstance(ser, pd.Series): if isinstance(ser.index, pd.MultiIndex): return ser index = ser.index ser = ser.values else: index = network.lines.index return pd.Series(ser, index=pd.MultiIndex(levels=(["Line"], index), labels=(np.zeros(len(index)), np.arange(len(index))))) line_colors = as_branch_series(line_colors) line_widths = as_branch_series(line_widths) if not isinstance(line_cmap, dict): line_cmap = {'Line': line_cmap} branch_collections = [] for c in network.iterate_components(branch_components): l_defaults = defaults_for_branches[c.name] l_widths = line_widths.get(c.name, l_defaults['width']) l_nums = None l_colors = line_colors.get(c.name, l_defaults['color']) if isinstance(l_colors, pd.Series): if issubclass(l_colors.dtype.type, np.number): l_nums = l_colors l_colors = None else: l_colors.fillna(l_defaults['color'], inplace=True) if not geometry: segments = (np.asarray( ((c.df.bus0.map(x), c.df.bus0.map(y)), (c.df.bus1.map(x), c.df.bus1.map(y)))).transpose(2, 0, 1)) else: from shapely.wkt import loads from shapely.geometry import LineString linestrings = c.df.geometry.map(loads) assert all(isinstance(ls, LineString) for ls in linestrings), \ "The WKT-encoded geometry in the 'geometry' column must be composed of LineStrings" segments = np.asarray(list(linestrings.map(np.asarray))) if basemap and basemap_present: segments = np.transpose( bmap(*np.transpose(segments, (2, 0, 1))), (1, 2, 0)) l_collection = LineCollection(segments, linewidths=l_widths, antialiaseds=(1, ), colors=l_colors, transOffset=ax.transData) if l_nums is not None: l_collection.set_array(np.asarray(l_nums)) l_collection.set_cmap(line_cmap.get(c.name, None)) l_collection.autoscale() ax.add_collection(l_collection) l_collection.set_zorder(1) branch_collections.append(l_collection) bus_collection.set_zorder(2) ax.update_datalim(compute_bbox_with_margins(margin, x, y)) ax.autoscale_view() ax.set_title(title) return (bus_collection, ) + tuple(branch_collections)
def plot(n, margin=None, ax=None, geomap=True, projection=None, bus_colors='cadetblue', bus_alpha=1, bus_sizes=2e-2, bus_cmap=None, line_colors='rosybrown', link_colors='darkseagreen', transformer_colors='orange', line_widths=1.5, link_widths=1.5, transformer_widths=1.5, line_cmap=None, link_cmap=None, transformer_cmap=None, flow=None, branch_components=None, layouter=None, title="", boundaries=None, geometry=False, jitter=None, color_geomap=None): """ Plot the network buses and lines using matplotlib and cartopy. Parameters ---------- margin : float Margin at the sides as proportion of distance between max/min x,y ax : matplotlib ax, defaults to plt.gca() Axis to which to plot the network geomap: bool/str, default True Switch to use Cartopy and draw geographical features. If string is passed, it will be used as a resolution argument, valid options are '10m', '50m' and '110m'. projection: cartopy.crs.Projection, defaults to None Define the projection of your geomap, only valid if cartopy is installed. If None (default) is passed the projection for cartopy is set to cartopy.crs.PlateCarree bus_colors : dict/pandas.Series Colors for the buses, defaults to "cadetblue". If bus_sizes is a pandas.Series with a Multiindex, bus_colors defaults to the n.carriers['color'] column. bus_alpha : float Adds alpha channel to buses, defaults to 1. bus_sizes : dict/pandas.Series Sizes of bus points, defaults to 1e-2. If a multiindexed Series is passed, the function will draw pies for each bus (first index level) with segments of different color (second index level). Such a Series is ob- tained by e.g. n.generators.groupby(['bus', 'carrier']).p_nom.sum() bus_cmap : plt.cm.ColorMap/str If bus_colors are floats, this color map will assign the colors line_colors : str/pandas.Series Colors for the lines, defaults to 'rosybrown'. link_colors : str/pandas.Series Colors for the links, defaults to 'darkseagreen'. transfomer_colors : str/pandas.Series Colors for the transfomer, defaults to 'orange'. line_widths : dict/pandas.Series Widths of lines, defaults to 1.5 link_widths : dict/pandas.Series Widths of links, defaults to 1.5 transformer_widths : dict/pandas.Series Widths of transformer, defaults to 1.5 line_cmap : plt.cm.ColorMap/str|dict If line_colors are floats, this color map will assign the colors. link_cmap : plt.cm.ColorMap/str|dict If link_colors are floats, this color map will assign the colors. transformer_cmap : plt.cm.ColorMap/str|dict If transformer_colors are floats, this color map will assign the colors. flow : snapshot/pandas.Series/function/string Flow to be displayed in the plot, defaults to None. If an element of n.snapshots is given, the flow at this timestamp will be displayed. If an aggregation function is given, is will be applied to the total network flow via pandas.DataFrame.agg (accepts also function names). Otherwise flows can be specified by passing a pandas Series with MultiIndex including all necessary branch components. Use the line_widths argument to additionally adjust the size of the flow arrows. layouter : networkx.drawing.layout function, default None Layouting function from `networkx <https://networkx.github.io/>`_ which overrules coordinates given in ``n.buses[['x','y']]``. See `list <https://networkx.github.io/documentation/stable/reference/drawing.html#module-networkx.drawing.layout>`_ of available options. title : string Graph title boundaries : list of four floats Boundaries of the plot in format [x1,x2,y1,y2] branch_components : list of str Branch components to be plotted, defaults to Line and Link. jitter : None|float Amount of random noise to add to bus positions to distinguish overlapping buses color_geomap : dict or bool Specify colors to paint land and sea areas in. If True, it defaults to `{'ocean': 'lightblue', 'land': 'whitesmoke'}`. If no dictionary is provided, colors are white. If False, no geographical features are plotted. Returns ------- bus_collection, branch_collection1, ... : tuple of Collections Collections for buses and branches. """ x, y = _get_coordinates(n, layouter=layouter) if boundaries is None and margin: boundaries = sum(zip(*compute_bbox_with_margins(margin, x, y)), ()) if geomap and not cartopy_present: logger.warning("Cartopy needs to be installed to use `geomap=True`.") geomap = False if geomap: transform = get_projection_from_crs(n.srid) if projection is None: projection = transform else: assert isinstance(projection, cartopy.crs.Projection), ( 'The passed projection is not a cartopy.crs.Projection') if ax is None: ax = plt.gca(projection=projection) else: assert isinstance(ax, cartopy.mpl.geoaxes.GeoAxesSubplot), ( 'The passed axis is not a GeoAxesSubplot. You can ' 'create one with: \nimport cartopy.crs as ccrs \n' 'fig, ax = plt.subplots(' 'subplot_kw={"projection":ccrs.PlateCarree()})') x, y, z = ax.projection.transform_points(transform, x.values, y.values).T x, y = pd.Series(x, n.buses.index), pd.Series(y, n.buses.index) if color_geomap is not False: draw_map_cartopy(ax, geomap, color_geomap) if boundaries is not None: ax.set_extent(boundaries, crs=transform) elif ax is None: ax = plt.gca() if not geomap and boundaries: ax.axis(boundaries) ax.set_aspect('equal') ax.axis('off') ax.set_title(title) # Plot buses: if jitter is not None: x = x + np.random.uniform(low=-jitter, high=jitter, size=len(x)) y = y + np.random.uniform(low=-jitter, high=jitter, size=len(y)) if isinstance(bus_sizes, pd.Series) and isinstance(bus_sizes.index, pd.MultiIndex): # We are drawing pies to show all the different shares assert len(bus_sizes.index.levels[0].difference(n.buses.index)) == 0, \ "The first MultiIndex level of bus_sizes must contain buses" if isinstance(bus_colors, dict): bus_colors = pd.Series(bus_colors) # case bus_colors isn't a series or dict: look in n.carriers for existent colors if not isinstance(bus_colors, pd.Series): bus_colors = n.carriers.color.dropna() assert bus_sizes.index.levels[1].isin(bus_colors.index).all(), ( "Colors not defined for all elements in the second MultiIndex " "level of bus_sizes, please make sure that all the elements are " "included in bus_colors or in n.carriers.color") bus_sizes = bus_sizes.sort_index(level=0, sort_remaining=False) if geomap: bus_sizes = bus_sizes * projected_area_factor(ax, n.srid)**2 patches = [] for b_i in bus_sizes.index.levels[0]: s = bus_sizes.loc[b_i] radius = s.sum()**0.5 if radius == 0.0: ratios = s else: ratios = s/s.sum() start = 0.25 for i, ratio in ratios.iteritems(): patches.append(Wedge((x.at[b_i], y.at[b_i]), radius, 360*start, 360*(start+ratio), facecolor=bus_colors[i], alpha=bus_alpha)) start += ratio bus_collection = PatchCollection(patches, match_original=True, zorder=5) ax.add_collection(bus_collection) else: c = pd.Series(bus_colors, index=n.buses.index) s = pd.Series(bus_sizes, index=n.buses.index, dtype="float") if geomap: s = s * projected_area_factor(ax, n.srid)**2 if bus_cmap is not None and c.dtype is np.dtype('float'): if isinstance(bus_cmap, str): bus_cmap = plt.cm.get_cmap(bus_cmap) norm = plt.Normalize(vmin=c.min(), vmax=c.max()) c = c.apply(lambda cval: bus_cmap(norm(cval))) patches = [] for b_i in s.index: radius = s.at[b_i]**0.5 patches.append(Circle((x.at[b_i], y.at[b_i]), radius, facecolor=c.at[b_i], alpha=bus_alpha)) bus_collection = PatchCollection(patches, match_original=True, zorder=5) ax.add_collection(bus_collection) # Plot branches: if isinstance(line_widths, pd.Series): if isinstance(line_widths.index, pd.MultiIndex): raise TypeError("Index of argument 'line_widths' is a Multiindex, " "this is not support since pypsa v0.17. " "Set differing widths with arguments 'line_widths', " "'link_widths' and 'transformer_widths'.") if isinstance(line_colors, pd.Series): if isinstance(line_colors.index, pd.MultiIndex): raise TypeError("Index of argument 'line_colors' is a Multiindex, " "this is not support since pypsa v0.17. " "Set differing colors with arguments 'line_colors', " "'link_colors' and 'transformer_colors'.") if branch_components is None: branch_components = n.branch_components branch_colors = {'Line': line_colors, 'Link': link_colors, 'Transformer': transformer_colors} branch_widths = {'Line': line_widths, 'Link': link_widths, 'Transformer': transformer_widths} branch_cmap = {'Line': line_cmap, 'Link': link_cmap, 'Transformer': transformer_cmap} branch_collections = [] arrow_collections = [] if flow is not None: rough_scale = sum(len(n.df(c)) for c in branch_components) + 100 flow = _flow_ds_from_arg(flow, n, branch_components) / rough_scale for c in n.iterate_components(branch_components): b_widths = as_branch_series(branch_widths[c.name], 'width', c.name, n) b_colors = as_branch_series(branch_colors[c.name], 'color', c.name, n) b_nums = None b_cmap = branch_cmap[c.name] b_flow = flow.get(c.name, None) if flow is not None else None if issubclass(b_colors.dtype.type, np.number): b_nums = b_colors b_colors = None if not geometry: segments = (np.asarray(((c.df.bus0.map(x), c.df.bus0.map(y)), (c.df.bus1.map(x), c.df.bus1.map(y)))) .transpose(2, 0, 1)) else: from shapely.wkt import loads from shapely.geometry import LineString linestrings = c.df.geometry[lambda ds: ds != ''].map(loads) assert all(isinstance(ls, LineString) for ls in linestrings), ( "The WKT-encoded geometry in the 'geometry' column must be " "composed of LineStrings") segments = np.asarray(list(linestrings.map(np.asarray))) if b_flow is not None: coords = pd.DataFrame({'x1': c.df.bus0.map(x), 'y1': c.df.bus0.map(y), 'x2': c.df.bus1.map(x), 'y2': c.df.bus1.map(y)}) b_flow = b_flow.mul(b_widths[b_flow.index], fill_value=0) # update the line width, allows to set line widths separately from flows b_widths.update((5 * b_flow.abs()).pipe(np.sqrt)) area_factor = projected_area_factor(ax, n.srid) f_collection = directed_flow(coords, b_flow, b_colors, area_factor, b_cmap) if b_nums is not None: f_collection.set_array(np.asarray(b_nums)) f_collection.set_cmap(b_cmap) f_collection.autoscale() arrow_collections.append(f_collection) ax.add_collection(f_collection) b_collection = LineCollection(segments, linewidths=b_widths, antialiaseds=(1,), colors=b_colors, transOffset=ax.transData) if b_nums is not None: b_collection.set_array(np.asarray(b_nums)) b_collection.set_cmap(b_cmap) b_collection.autoscale() ax.add_collection(b_collection) b_collection.set_zorder(3) branch_collections.append(b_collection) if boundaries is None: ax.autoscale() return (bus_collection,) + tuple(branch_collections) + tuple(arrow_collections)
def draw_networkx_edges(G, pos, edgelist=None, width=1.0, edge_color='k', style='solid', alpha=None, edge_cmap=None, edge_vmin=None, edge_vmax=None, ax=None, arrows=True, **kwds): """Draw the edges of the graph G This draws only the edges of the graph G. pos is a dictionary keyed by vertex with a two-tuple of x-y positions as the value. See networkx.layout for functions that compute node positions. edgelist is an optional list of the edges in G to be drawn. If provided, only the edges in edgelist will be drawn. edgecolor can be a list of matplotlib color letters such as 'k' or 'b' that lists the color of each edge; the list must be ordered in the same way as the edge list. Alternatively, this list can contain numbers and those number are mapped to a color scale using the color map edge_cmap. Finally, it can also be a list of (r,g,b) or (r,g,b,a) tuples, in which case these will be used directly to color the edges. If the latter mode is used, you should not provide a value for alpha, as it would be applied globally to all lines. For directed graphs, "arrows" (actually just thicker stubs) are drawn at the head end. Arrows can be turned off with keyword arrows=False. See draw_networkx for the list of other optional parameters. """ try: import matplotlib import matplotlib.pylab as pylab import numpy as np from matplotlib.colors import colorConverter,Colormap from matplotlib.collections import LineCollection except ImportError: raise ImportError("Matplotlib required for draw()") except RuntimeError: pass # unable to open display if ax is None: ax=pylab.gca() if edgelist is None: edgelist=G.edges() if not edgelist or len(edgelist)==0: # no edges! return None # set edge positions edge_pos=np.asarray([(pos[e[0]],pos[e[1]]) for e in edgelist]) if not cb.iterable(width): lw = (width,) else: lw = width if not cb.is_string_like(edge_color) \ and cb.iterable(edge_color) \ and len(edge_color)==len(edge_pos): if np.alltrue([cb.is_string_like(c) for c in edge_color]): # (should check ALL elements) # list of color letters such as ['k','r','k',...] edge_colors = tuple([colorConverter.to_rgba(c,alpha) for c in edge_color]) elif np.alltrue([not cb.is_string_like(c) for c in edge_color]): # If color specs are given as (rgb) or (rgba) tuples, we're OK if np.alltrue([cb.iterable(c) and len(c) in (3,4) for c in edge_color]): edge_colors = tuple(edge_color) alpha=None else: # numbers (which are going to be mapped with a colormap) edge_colors = None else: raise ValueError('edge_color must consist of either color names or numbers') else: if len(edge_color)==1: edge_colors = ( colorConverter.to_rgba(edge_color, alpha), ) else: raise ValueError('edge_color must be a single color or list of exactly m colors where m is the number or edges') edge_collection = LineCollection(edge_pos, colors = edge_colors, linewidths = lw, antialiaseds = (1,), linestyle = style, transOffset = ax.transData, ) # Note: there was a bug in mpl regarding the handling of alpha values for # each line in a LineCollection. It was fixed in matplotlib in r7184 and # r7189 (June 6 2009). We should then not set the alpha value globally, # since the user can instead provide per-edge alphas now. Only set it # globally if provided as a scalar. if cb.is_numlike(alpha): edge_collection.set_alpha(alpha) # need 0.87.7 or greater for edge colormaps. No checks done, this will # just not work with an older mpl if edge_colors is None: if edge_cmap is not None: assert(isinstance(edge_cmap, Colormap)) edge_collection.set_array(np.asarray(edge_color)) edge_collection.set_cmap(edge_cmap) if edge_vmin is not None or edge_vmax is not None: edge_collection.set_clim(edge_vmin, edge_vmax) else: edge_collection.autoscale() pylab.sci(edge_collection) arrow_collection=None if G.is_directed() and arrows: # a directed graph hack # draw thick line segments at head end of edge # waiting for someone else to implement arrows that will work arrow_colors = ( colorConverter.to_rgba('k', alpha), ) a_pos=[] p=1.0-0.25 # make head segment 25 percent of edge length for src,dst in edge_pos: x1,y1=src x2,y2=dst dx=x2-x1 # x offset dy=y2-y1 # y offset d=np.sqrt(float(dx**2+dy**2)) # length of edge if d==0: # source and target at same position continue if dx==0: # vertical edge xa=x2 ya=dy*p+y1 if dy==0: # horizontal edge ya=y2 xa=dx*p+x1 else: theta=np.arctan2(dy,dx) xa=p*d*np.cos(theta)+x1 ya=p*d*np.sin(theta)+y1 a_pos.append(((xa,ya),(x2,y2))) arrow_collection = LineCollection(a_pos, colors = arrow_colors, linewidths = [4*ww for ww in lw], antialiaseds = (1,), transOffset = ax.transData, ) # update view minx = np.amin(np.ravel(edge_pos[:,:,0])) maxx = np.amax(np.ravel(edge_pos[:,:,0])) miny = np.amin(np.ravel(edge_pos[:,:,1])) maxy = np.amax(np.ravel(edge_pos[:,:,1])) w = maxx-minx h = maxy-miny padx, pady = 0.05*w, 0.05*h corners = (minx-padx, miny-pady), (maxx+padx, maxy+pady) ax.update_datalim( corners) ax.autoscale_view() edge_collection.set_zorder(1) # edges go behind nodes ax.add_collection(edge_collection) if arrow_collection: arrow_collection.set_zorder(1) # edges go behind nodes ax.add_collection(arrow_collection) return edge_collection
def plot(network, margin=0.05, ax=None, basemap=True, bus_colors='b', line_colors='g', bus_sizes=10, line_widths=2, title="", line_cmap=None, bus_cmap=None, boundaries=None, geometry=False, branch_components=['Line', 'Link'], jitter=None): """ Plot the network buses and lines using matplotlib and Basemap. Parameters ---------- margin : float Margin at the sides as proportion of distance between max/min x,y ax : matplotlib ax, defaults to plt.gca() Axis to which to plot the network basemap : bool, default True Switch to use Basemap bus_colors : dict/pandas.Series Colors for the buses, defaults to "b" bus_sizes : dict/pandas.Series Sizes of bus points, defaults to 10 line_colors : dict/pandas.Series Colors for the lines, defaults to "g" for Lines and "cyan" for Links. Colors for branches other than Lines can be specified using a pandas Series with a MultiIndex. line_widths : dict/pandas.Series Widths of lines, defaults to 2. Widths for branches other than Lines can be specified using a pandas Series with a MultiIndex. title : string Graph title line_cmap : plt.cm.ColorMap/str|dict If line_colors are floats, this color map will assign the colors. Use a dict to specify colormaps for more than one branch type. bus_cmap : plt.cm.ColorMap/str If bus_colors are floats, this color map will assign the colors boundaries : list of four floats Boundaries of the plot in format [x1,x2,y1,y2] branch_components : list of str Branch components to be plotted, defaults to Line and Link. jitter : None|float Amount of random noise to add to bus positions to distinguish overlapping buses Returns ------- bus_collection, branch_collection1, ... : tuple of Collections Collections for buses and branches. """ defaults_for_branches = { 'Link': dict(color="cyan", width=2), 'Line': dict(color="b", width=2), 'Transformer': dict(color='green', width=2) } if not plt_present: logger.error("Matplotlib is not present, so plotting won't work.") return if ax is None: ax = plt.gca() def compute_bbox_with_margins(margin, x, y): #set margins pos = np.asarray((x, y)) minxy, maxxy = pos.min(axis=1), pos.max(axis=1) xy1 = minxy - margin*(maxxy - minxy) xy2 = maxxy + margin*(maxxy - minxy) return tuple(xy1), tuple(xy2) x = network.buses["x"] y = network.buses["y"] if jitter is not None: x = x + np.random.uniform(low=-jitter, high=jitter, size=len(x)) y = y + np.random.uniform(low=-jitter, high=jitter, size=len(y)) if basemap and basemap_present: if boundaries is None: (x1, y1), (x2, y2) = compute_bbox_with_margins(margin, x, y) else: x1, x2, y1, y2 = boundaries bmap = Basemap(resolution='l', epsg=network.srid, llcrnrlat=y1, urcrnrlat=y2, llcrnrlon=x1, urcrnrlon=x2, ax=ax) bmap.drawcountries() bmap.drawcoastlines() x, y = bmap(x.values, y.values) x = pd.Series(x, network.buses.index) y = pd.Series(y, network.buses.index) if isinstance(bus_sizes, pd.Series) and isinstance(bus_sizes.index, pd.MultiIndex): # We are drawing pies to show all the different shares assert len(network.buses.index.difference(bus_sizes.index.levels[0])) == 0, \ "The first MultiIndex level of bus_sizes must contain buses" assert isinstance(bus_colors, dict) and set(bus_colors).issuperset(bus_sizes.index.levels[1]), \ "bus_colors must be a dictionary defining a color for each element " \ "in the second MultiIndex level of bus_sizes" bus_sizes = bus_sizes.sort_index(level=0, sort_remaining=False) patches = [] for b_i in bus_sizes.index.levels[0]: s = bus_sizes.loc[b_i] radius = s.sum()**0.5 ratios = s/s.sum() start = 0.25 for i, ratio in ratios.iteritems(): patches.append(Wedge((x.at[b_i], y.at[b_i]), radius, 360*start, 360*(start+ratio), facecolor=bus_colors[i])) start += ratio bus_collection = PatchCollection(patches, match_original=True) ax.add_collection(bus_collection) else: c = pd.Series(bus_colors, index=network.buses.index) if c.dtype == np.dtype('O'): c.fillna("b", inplace=True) c = list(c.values) s = pd.Series(bus_sizes, index=network.buses.index, dtype="float").fillna(10) bus_collection = ax.scatter(x, y, c=c, s=s, cmap=bus_cmap) def as_branch_series(ser): if isinstance(ser, dict) and set(ser).issubset(branch_components): return pd.Series(ser) elif isinstance(ser, pd.Series): if isinstance(ser.index, pd.MultiIndex): return ser index = ser.index ser = ser.values else: index = network.lines.index return pd.Series(ser, index=pd.MultiIndex(levels=(["Line"], index), labels=(np.zeros(len(index)), np.arange(len(index))))) line_colors = as_branch_series(line_colors) line_widths = as_branch_series(line_widths) if not isinstance(line_cmap, dict): line_cmap = {'Line': line_cmap} branch_collections = [] for c in network.iterate_components(branch_components): l_defaults = defaults_for_branches[c.name] l_widths = line_widths.get(c.name, l_defaults['width']) l_nums = None l_colors = line_colors.get(c.name, l_defaults['color']) if isinstance(l_colors, pd.Series): if issubclass(l_colors.dtype.type, np.number): l_nums = l_colors l_colors = None else: l_colors.fillna(l_defaults['color'], inplace=True) if not geometry: segments = (np.asarray(((c.df.bus0.map(x), c.df.bus0.map(y)), (c.df.bus1.map(x), c.df.bus1.map(y)))) .transpose(2, 0, 1)) else: from shapely.wkt import loads from shapely.geometry import LineString linestrings = c.df.geometry.map(loads) assert all(isinstance(ls, LineString) for ls in linestrings), \ "The WKT-encoded geometry in the 'geometry' column must be composed of LineStrings" segments = np.asarray(list(linestrings.map(np.asarray))) if basemap and basemap_present: segments = np.transpose(bmap(*np.transpose(segments, (2, 0, 1))), (1, 2, 0)) l_collection = LineCollection(segments, linewidths=l_widths, antialiaseds=(1,), colors=l_colors, transOffset=ax.transData) if l_nums is not None: l_collection.set_array(np.asarray(l_nums)) l_collection.set_cmap(line_cmap.get(c.name, None)) l_collection.autoscale() ax.add_collection(l_collection) l_collection.set_zorder(1) branch_collections.append(l_collection) bus_collection.set_zorder(2) ax.update_datalim(compute_bbox_with_margins(margin, x, y)) ax.autoscale_view() ax.set_title(title) return (bus_collection,) + tuple(branch_collections)
def draw_networkx_edges(G, pos, edgelist=None, width=1.0, edge_color='k', style='solid', alpha=None, edge_cmap=None, edge_vmin=None, edge_vmax=None, ax=None, arrows=True, **kwds): """Draw the edges of the graph G This draws only the edges of the graph G. pos is a dictionary keyed by vertex with a two-tuple of x-y positions as the value. See networkx.layout for functions that compute node positions. edgelist is an optional list of the edges in G to be drawn. If provided, only the edges in edgelist will be drawn. edgecolor can be a list of matplotlib color letters such as 'k' or 'b' that lists the color of each edge; the list must be ordered in the same way as the edge list. Alternatively, this list can contain numbers and those number are mapped to a color scale using the color map edge_cmap. Finally, it can also be a list of (r,g,b) or (r,g,b,a) tuples, in which case these will be used directly to color the edges. If the latter mode is used, you should not provide a value for alpha, as it would be applied globally to all lines. For directed graphs, "arrows" (actually just thicker stubs) are drawn at the head end. Arrows can be turned off with keyword arrows=False. See draw_networkx for the list of other optional parameters. """ try: import matplotlib import matplotlib.pylab as pylab import matplotlib.cbook as cb from matplotlib.colors import colorConverter,Colormap from matplotlib.collections import LineCollection import numpy except ImportError: raise ImportError, "Matplotlib required for draw()" except RuntimeError: pass # unable to open display if ax is None: ax=pylab.gca() if edgelist is None: edgelist=G.edges() if not edgelist or len(edgelist)==0: # no edges! return None # set edge positions edge_pos=numpy.asarray([(pos[e[0]],pos[e[1]]) for e in edgelist]) if not cb.iterable(width): lw = (width,) else: lw = width if not cb.is_string_like(edge_color) \ and cb.iterable(edge_color) \ and len(edge_color)==len(edge_pos): if numpy.alltrue([cb.is_string_like(c) for c in edge_color]): # (should check ALL elements) # list of color letters such as ['k','r','k',...] edge_colors = tuple([colorConverter.to_rgba(c,alpha) for c in edge_color]) elif numpy.alltrue([not cb.is_string_like(c) for c in edge_color]): # If color specs are given as (rgb) or (rgba) tuples, we're OK if numpy.alltrue([cb.iterable(c) and len(c) in (3,4) for c in edge_color]): edge_colors = tuple(edge_color) else: # numbers (which are going to be mapped with a colormap) edge_colors = None else: raise ValueError('edge_color must consist of either color names or numbers') else: if len(edge_color)==1: edge_colors = ( colorConverter.to_rgba(edge_color, alpha), ) else: raise ValueError('edge_color must be a single color or list of exactly m colors where m is the number or edges') edge_collection = LineCollection(edge_pos, colors = edge_colors, linewidths = lw, antialiaseds = (1,), linestyle = style, transOffset = ax.transData, ) # Note: there was a bug in mpl regarding the handling of alpha values for # each line in a LineCollection. It was fixed in matplotlib in r7184 and # r7189 (June 6 2009). We should then not set the alpha value globally, # since the user can instead provide per-edge alphas now. Only set it # globally if provided as a scalar. if cb.is_numlike(alpha): edge_collection.set_alpha(alpha) # need 0.87.7 or greater for edge colormaps mpl_version=matplotlib.__version__ if mpl_version.endswith('svn'): mpl_version=matplotlib.__version__[0:-3] if mpl_version.endswith('pre'): mpl_version=matplotlib.__version__[0:-3] if map(int,mpl_version.split('.'))>=[0,87,7]: if edge_colors is None: if edge_cmap is not None: assert(isinstance(edge_cmap, Colormap)) edge_collection.set_array(numpy.asarray(edge_color)) edge_collection.set_cmap(edge_cmap) if edge_vmin is not None or edge_vmax is not None: edge_collection.set_clim(edge_vmin, edge_vmax) else: edge_collection.autoscale() pylab.sci(edge_collection) # else: # sys.stderr.write(\ # """matplotlib version >= 0.87.7 required for colormapped edges. # (version %s detected)."""%matplotlib.__version__) # raise UserWarning(\ # """matplotlib version >= 0.87.7 required for colormapped edges. # (version %s detected)."""%matplotlib.__version__) arrow_collection=None if G.is_directed() and arrows: # a directed graph hack # draw thick line segments at head end of edge # waiting for someone else to implement arrows that will work arrow_colors = ( colorConverter.to_rgba('k', alpha), ) a_pos=[] p=1.0-0.25 # make head segment 25 percent of edge length for src,dst in edge_pos: x1,y1=src x2,y2=dst dx=x2-x1 # x offset dy=y2-y1 # y offset d=numpy.sqrt(float(dx**2+dy**2)) # length of edge if d==0: # source and target at same position continue if dx==0: # vertical edge xa=x2 ya=dy*p+y1 if dy==0: # horizontal edge ya=y2 xa=dx*p+x1 else: theta=numpy.arctan2(dy,dx) xa=p*d*numpy.cos(theta)+x1 ya=p*d*numpy.sin(theta)+y1 a_pos.append(((xa,ya),(x2,y2))) arrow_collection = LineCollection(a_pos, colors = arrow_colors, linewidths = [4*ww for ww in lw], antialiaseds = (1,), transOffset = ax.transData, ) # update view minx = numpy.amin(numpy.ravel(edge_pos[:,:,0])) maxx = numpy.amax(numpy.ravel(edge_pos[:,:,0])) miny = numpy.amin(numpy.ravel(edge_pos[:,:,1])) maxy = numpy.amax(numpy.ravel(edge_pos[:,:,1])) w = maxx-minx h = maxy-miny padx, pady = 0.05*w, 0.05*h corners = (minx-padx, miny-pady), (maxx+padx, maxy+pady) ax.update_datalim( corners) ax.autoscale_view() edge_collection.set_zorder(1) # edges go behind nodes ax.add_collection(edge_collection) if arrow_collection: arrow_collection.set_zorder(1) # edges go behind nodes ax.add_collection(arrow_collection) return edge_collection
def draw_animation_edges(G, pos, edgelist=None, width=1.0, edge_color='k', style='solid', alpha=1.0, edge_cmap=None, edge_vmin=None, edge_vmax=None, ax=None, arrows=True, label=None, **kwds): try: import matplotlib import matplotlib.pyplot as plt import matplotlib.cbook as cb from matplotlib.colors import colorConverter, Colormap from matplotlib.collections import LineCollection import numpy except ImportError: raise ImportError("Matplotlib required for draw()") except RuntimeError: print("Matplotlib unable to open display") raise if ax is None: ax = plt.gca() if edgelist is None: edgelist = list(G.edges()) if not edgelist or len(edgelist) == 0: # no edges! return None # set edge positions box_pos = numpy.asarray([(pos[e[0]], pos[e[1]]) for e in edgelist]) p = 0.25 edge_pos = [] for edge in edgelist: src, dst = np.array(pos[edge[0]]), np.array(pos[edge[1]]) s = dst - src # src = src + p * s # Box at beginning # dst = src + (1-p) * s # Box at the end dst = src # No edge at all edge_pos.append((src, dst)) edge_pos = numpy.asarray(edge_pos) if not cb.iterable(width): lw = (width, ) else: lw = width if not cb.is_scalar_or_string(edge_color) \ and cb.iterable(edge_color) \ and len(edge_color) == len(edge_pos): if numpy.alltrue([cb.is_scalar_or_string(c) for c in edge_color]): # (should check ALL elements) # list of color letters such as ['k','r','k',...] edge_colors = tuple( [colorConverter.to_rgba(c, alpha) for c in edge_color]) elif numpy.alltrue( [not cb.is_scalar_or_string(c) for c in edge_color]): # If color specs are given as (rgb) or (rgba) tuples, we're OK if numpy.alltrue( [cb.iterable(c) and len(c) in (3, 4) for c in edge_color]): edge_colors = tuple(edge_color) else: # numbers (which are going to be mapped with a colormap) edge_colors = None else: raise ValueError( 'edge_color must consist of either color names or numbers') else: if cb.is_scalar_or_string(edge_color) or len(edge_color) == 1: edge_colors = (colorConverter.to_rgba(edge_color, alpha), ) else: raise ValueError( 'edge_color must be a single color or list of exactly m colors where m is the number or edges' ) ''' modEdgeColors = list(edge_colors) modEdgeColors = tuple(modEdgeColors + [colorConverter.to_rgba('w', alpha) for c in edge_color]) #print(modEdgeColors) edge_collection = LineCollection(np.asarray(list(edge_pos)*2), colors=modEdgeColors, linewidths=[6]*len(list(edge_colors))+[4]*len(list(edge_colors)), antialiaseds=(1,), linestyle=style, transOffset=ax.transData, ) ''' edge_collection = LineCollection( edge_pos, colors=edge_colors, linewidths=6, antialiaseds=(1, ), linestyle=style, transOffset=ax.transData, ) edge_collection.set_zorder(1) # edges go behind nodes edge_collection.set_label(label) ax.add_collection(edge_collection) tube_collection = LineCollection( edge_pos, colors=tuple([ colorConverter.to_rgba('lightgrey', alpha) for c in edge_color ]), linewidths=4, antialiaseds=(1, ), linestyle=style, transOffset=ax.transData, ) tube_collection.set_zorder(1) # edges go behind nodes tube_collection.set_label(label) ax.add_collection(tube_collection) # Note: there was a bug in mpl regarding the handling of alpha values for # each line in a LineCollection. It was fixed in matplotlib in r7184 and # r7189 (June 6 2009). We should then not set the alpha value globally, # since the user can instead provide per-edge alphas now. Only set it # globally if provided as a scalar. if cb.is_numlike(alpha): edge_collection.set_alpha(alpha) if edge_colors is None: if edge_cmap is not None: assert (isinstance(edge_cmap, Colormap)) edge_collection.set_array(numpy.asarray(edge_color)) edge_collection.set_cmap(edge_cmap) if edge_vmin is not None or edge_vmax is not None: edge_collection.set_clim(edge_vmin, edge_vmax) else: edge_collection.autoscale() box_collection = Utilities.get_boxes(edge_colors=edge_colors, edge_pos=box_pos) box_collection.set_zorder(1) # edges go behind nodes box_collection.set_label(label) ax.add_collection(box_collection) arrow_collection = Utilities.get_arrows_on_edges( edge_colors=edge_colors, edge_pos=box_pos) arrow_collection.set_zorder(0) if arrows: # Visualize them only if wanted ax.add_collection(arrow_collection) return edge_collection, box_collection, tube_collection, arrow_collection
def plot(network, margin=0.05, ax=None, basemap=True, bus_colors='b', line_colors='g', bus_sizes=10, line_widths=2, title="", line_cmap=None, bus_cmap=None, boundaries=None, geometry=False, branch_types=['Line', 'TransportLink', 'Link']): """ Plot the network buses and lines using matplotlib and Basemap. Parameters ---------- margin : float Margin at the sides as proportion of distance between max/min x,y ax : matplotlib ax, defaults to plt.gca() Axis to which to plot the network basemap : bool, default True Switch to use Basemap bus_colors : dict/pandas.Series Colors for the buses, defaults to "b" bus_sizes : dict/pandas.Series Sizes of bus points, defaults to 10 line_colors : dict/pandas.Series Colors for the lines, defaults to "g" for Lines and "cyan" for TransportLinks and Links. Colors for branches other than Lines can be specified using a pandas Series with a MultiIndex. line_widths : dict/pandas.Series Widths of lines, defaults to 2. Widths for branches other than Lines can be specified using a pandas Series with a MultiIndex. title : string Graph title line_cmap : plt.cm.ColorMap/str|dict If line_colors are floats, this color map will assign the colors. Use a dict to specify colormaps for more than one branch type. bus_cmap : plt.cm.ColorMap/str If bus_colors are floats, this color map will assign the colors boundaries : list of four floats Boundaries of the plot in format [x1,x2,y1,y2] branch_types : list of str or pypsa.component Branch types to be plotted, defaults to Line, TransportLink and Link. Returns ------- bus_collection, branch_collection1, ... : tuple of Collections Collections for buses and branches. """ defaults_for_branches = { 'TransportLink': dict(color="cyan", width=2), 'Link': dict(color="cyan", width=2), 'Line': dict(color="b", width=2) } if not plt_present: print("Matplotlib is not present, so plotting won't work.") return if ax is None: ax = plt.gca() def compute_bbox_with_margins(margin, x, y): #set margins pos = np.asarray((x, y)) minxy, maxxy = pos.min(axis=1), pos.max(axis=1) xy1 = minxy - margin*(maxxy - minxy) xy2 = maxxy + margin*(maxxy - minxy) return tuple(xy1), tuple(xy2) x = network.buses["x"] y = network.buses["y"] if basemap and basemap_present: if boundaries is None: (x1, y1), (x2, y2) = compute_bbox_with_margins(margin, x, y) else: x1, x2, y1, y2 = boundaries bmap = Basemap(resolution='l', epsg=network.srid, llcrnrlat=y1, urcrnrlat=y2, llcrnrlon=x1, urcrnrlon=x2, ax=ax) bmap.drawcountries() bmap.drawcoastlines() x, y = bmap(x.values, y.values) x = pd.Series(x, network.buses.index) y = pd.Series(y, network.buses.index) c = pd.Series(bus_colors, index=network.buses.index) if c.dtype == np.dtype('O'): c.fillna("b", inplace=True) s = pd.Series(bus_sizes, index=network.buses.index, dtype="float").fillna(10) bus_collection = ax.scatter(x, y, c=c, s=s, cmap=bus_cmap) def as_branch_series(ser): if isinstance(ser, pd.Series): if isinstance(ser.index, pd.MultiIndex): return ser index = ser.index ser = ser.values else: index = network.lines.index return pd.Series(ser, index=pd.MultiIndex(levels=(["Line"], index), labels=(np.zeros(len(index)), np.arange(len(index))))) line_colors = as_branch_series(line_colors) line_widths = as_branch_series(line_widths) if not isinstance(line_cmap, dict): line_cmap = {'Line': line_cmap} branch_collections = [] for t in network.iterate_components(branch_types): l_defaults = defaults_for_branches[t.name] l_widths = line_widths.get(t.name, l_defaults['width']) l_nums = None if t.name in line_colors: l_colors = line_colors[t.name] if issubclass(l_colors.dtype.type, np.number): l_nums = l_colors l_colors = None else: l_colors.fillna(l_defaults['color'], inplace=True) else: l_colors = l_defaults['color'] if not geometry: segments = (np.asarray(((t.df.bus0.map(x), t.df.bus0.map(y)), (t.df.bus1.map(x), t.df.bus1.map(y)))) .transpose(2, 0, 1)) else: from shapely.wkt import loads from shapely.geometry import LineString linestrings = t.df.geometry.map(loads) assert all(isinstance(ls, LineString) for ls in linestrings), \ "The WKT-encoded geometry in the 'geometry' column must be composed of LineStrings" segments = np.asarray(list(linestrings.map(np.asarray))) if basemap and basemap_present: segments = np.transpose(bmap(*np.transpose(segments, (2, 0, 1))), (1, 2, 0)) l_collection = LineCollection(segments, linewidths=l_widths, antialiaseds=(1,), colors=l_colors, transOffset=ax.transData) if l_nums is not None: l_collection.set_array(np.asarray(l_nums)) l_collection.set_cmap(line_cmap.get(t.name, None)) l_collection.autoscale() ax.add_collection(l_collection) l_collection.set_zorder(1) branch_collections.append(l_collection) bus_collection.set_zorder(2) ax.update_datalim(compute_bbox_with_margins(margin, x, y)) ax.autoscale_view() ax.set_title(title) return (bus_collection,) + tuple(branch_collections)
def draw_networkx_edges(G, pos, edgelist=None, width=1.0, edge_color='k', style='solid', alpha=1.0, edge_cmap=None, edge_vmin=None, edge_vmax=None, ax=None, arrows=True, **kwds): """Draw the edges of the graph G This draws only the edges of the graph G. pos is a dictionary keyed by vertex with a two-tuple of x-y positions as the value. See networkx_v099.layout for functions that compute node positions. edgelist is an optional list of the edges in G to be drawn. If provided, only the edges in edgelist will be drawn. edgecolor can be a list of matplotlib color letters such as 'k' or 'b' that lists the color of each edge; the list must be ordered in the same way as the edge list. Alternatively, this list can contain numbers and those number are mapped to a color scale using the color map edge_cmap. For directed graphs, "arrows" (actually just thicker stubs) are drawn at the head end. Arrows can be turned off with keyword arrows=False. See draw_networkx_v099 for the list of other optional parameters. """ if ax is None: ax=matplotlib.pylab.gca() if edgelist is None: edgelist=G.edges() if not edgelist or len(edgelist)==0: # no edges! return None # set edge positions edge_pos=asarray([(pos[e[0]],pos[e[1]]) for e in edgelist]) if not cb.iterable(width): lw = (width,) else: lw = width if not cb.is_string_like(edge_color) \ and cb.iterable(edge_color) \ and len(edge_color)==len(edge_pos): if matplotlib.numerix.alltrue([cb.is_string_like(c) for c in edge_color]): # (should check ALL elements) # list of color letters such as ['k','r','k',...] edge_colors = tuple([colorConverter.to_rgba(c,alpha) for c in edge_color]) elif matplotlib.numerix.alltrue([not cb.is_string_like(c) for c in edge_color]): # numbers (which are going to be mapped with a colormap) edge_colors = None else: raise ValueError('edge_color must consist of either color names or numbers') else: if len(edge_color)==1: edge_colors = ( colorConverter.to_rgba(edge_color, alpha), ) else: raise ValueError('edge_color must be a single color or list of exactly m colors where m is the number or edges') edge_collection = LineCollection(edge_pos, colors = edge_colors, linewidths = lw, antialiaseds = (1,), linestyle = style, transOffset = ax.transData, ) edge_collection.set_alpha(alpha) # need 0.87.7 or greater for edge colormaps mpl_version=matplotlib.__version__ if mpl_version.endswith('svn'): mpl_version=matplotlib.__version__[0:-3] if mpl_version.endswith('pre'): mpl_version=matplotlib.__version__[0:-3] if map(int,mpl_version.split('.'))>=[0,87,7]: if edge_colors is None: if edge_cmap is not None: assert(isinstance(edge_cmap, Colormap)) edge_collection.set_array(asarray(edge_color)) edge_collection.set_cmap(edge_cmap) if edge_vmin is not None or edge_vmax is not None: edge_collection.set_clim(edge_vmin, edge_vmax) else: edge_collection.autoscale() matplotlib.pylab.sci(edge_collection) # else: # sys.stderr.write(\ # """matplotlib version >= 0.87.7 required for colormapped edges. # (version %s detected)."""%matplotlib.__version__) # raise UserWarning(\ # """matplotlib version >= 0.87.7 required for colormapped edges. # (version %s detected)."""%matplotlib.__version__) arrow_collection=None if G.directed and arrows: # a directed graph hack # draw thick line segments at head end of edge # waiting for someone else to implement arrows that will work arrow_colors = ( colorConverter.to_rgba('k', alpha), ) a_pos=[] p=1.0-0.25 # make head segment 25 percent of edge length for src,dst in edge_pos: x1,y1=src x2,y2=dst dx=x2-x1 # x offset dy=y2-y1 # y offset d=sqrt(float(dx**2+dy**2)) # length of edge if d==0: # source and target at same position continue if dx==0: # vertical edge xa=x2 ya=dy*p+y1 if dy==0: # horizontal edge ya=y2 xa=dx*p+x1 else: theta=arctan2(dy,dx) xa=p*d*cos(theta)+x1 ya=p*d*sin(theta)+y1 a_pos.append(((xa,ya),(x2,y2))) arrow_collection = LineCollection(a_pos, colors = arrow_colors, linewidths = [4*ww for ww in lw], antialiaseds = (1,), transOffset = ax.transData, ) # update view minx = amin(ravel(edge_pos[:,:,0])) maxx = amax(ravel(edge_pos[:,:,0])) miny = amin(ravel(edge_pos[:,:,1])) maxy = amax(ravel(edge_pos[:,:,1])) w = maxx-minx h = maxy-miny padx, pady = 0.05*w, 0.05*h corners = (minx-padx, miny-pady), (maxx+padx, maxy+pady) ax.update_datalim( corners) ax.autoscale_view() edge_collection.set_zorder(1) # edges go behind nodes ax.add_collection(edge_collection) if arrow_collection: arrow_collection.set_zorder(1) # edges go behind nodes ax.add_collection(arrow_collection) return edge_collection
def draw_networkx_edges(G, pos, edgelist=None, width=1.0, edge_color='k', style='solid', alpha=1.0, arrowstyle='-|>', arrowsize=10, edge_cmap=None, edge_vmin=None, edge_vmax=None, ax=None, arrows=True, label=None, node_size=300, nodelist=None, node_shape="o", **kwds): """Draw the edges of the graph G. This draws only the edges of the graph G. Parameters ---------- G : graph A networkx graph pos : dictionary A dictionary with nodes as keys and positions as values. Positions should be sequences of length 2. edgelist : collection of edge tuples Draw only specified edges(default=G.edges()) width : float, or array of floats Line width of edges (default=1.0) edge_color : color string, or array of floats Edge color. Can be a single color format string (default='r'), or a sequence of colors with the same length as edgelist. If numeric values are specified they will be mapped to colors using the edge_cmap and edge_vmin,edge_vmax parameters. style : string Edge line style (default='solid') (solid|dashed|dotted,dashdot) alpha : float The edge transparency (default=1.0) edge_ cmap : Matplotlib colormap Colormap for mapping intensities of edges (default=None) edge_vmin,edge_vmax : floats Minimum and maximum for edge colormap scaling (default=None) ax : Matplotlib Axes object, optional Draw the graph in the specified Matplotlib axes. arrows : bool, optional (default=True) For directed graphs, if True draw arrowheads. Note: Arrows will be the same color as edges. arrowstyle : str, optional (default='-|>') For directed graphs, choose the style of the arrow heads. See :py:class: `matplotlib.patches.ArrowStyle` for more options. arrowsize : int, optional (default=10) For directed graphs, choose the size of the arrow head head's length and width. See :py:class: `matplotlib.patches.FancyArrowPatch` for attribute `mutation_scale` for more info. label : [None| string] Label for legend Returns ------- matplotlib.collection.LineCollection `LineCollection` of the edges list of matplotlib.patches.FancyArrowPatch `FancyArrowPatch` instances of the directed edges Depending whether the drawing includes arrows or not. Notes ----- For directed graphs, arrows are drawn at the head end. Arrows can be turned off with keyword arrows=False. Be sure to include `node_size' as a keyword argument; arrows are drawn considering the size of nodes. Examples -------- >>> G = nx.dodecahedral_graph() >>> edges = nx.draw_networkx_edges(G, pos=nx.spring_layout(G)) >>> G = nx.DiGraph() >>> G.add_edges_from([(1, 2), (1, 3), (2, 3)]) >>> arcs = nx.draw_networkx_edges(G, pos=nx.spring_layout(G)) >>> alphas = [0.3, 0.4, 0.5] >>> for i, arc in enumerate(arcs): # change alpha values of arcs ... arc.set_alpha(alphas[i]) Also see the NetworkX drawing examples at https://networkx.github.io/documentation/latest/auto_examples/index.html See Also -------- draw() draw_networkx() draw_networkx_nodes() draw_networkx_labels() draw_networkx_edge_labels() """ try: import matplotlib import matplotlib.pyplot as plt import matplotlib.cbook as cb from matplotlib.colors import colorConverter, Colormap, Normalize from matplotlib.collections import LineCollection from matplotlib.patches import FancyArrowPatch import numpy as np except ImportError: raise ImportError("Matplotlib required for draw()") except RuntimeError: print("Matplotlib unable to open display") raise if ax is None: ax = plt.gca() if edgelist is None: edgelist = list(G.edges()) if not edgelist or len(edgelist) == 0: # no edges! return None if nodelist is None: nodelist = list(G.nodes()) # set edge positions edge_pos = np.asarray([(pos[e[0]], pos[e[1]]) for e in edgelist]) if not cb.iterable(width): lw = (width,) else: lw = width if not is_string_like(edge_color) \ and cb.iterable(edge_color) \ and len(edge_color) == len(edge_pos): if np.alltrue([is_string_like(c) for c in edge_color]): # (should check ALL elements) # list of color letters such as ['k','r','k',...] edge_colors = tuple([colorConverter.to_rgba(c, alpha) for c in edge_color]) elif np.alltrue([not is_string_like(c) for c in edge_color]): # If color specs are given as (rgb) or (rgba) tuples, we're OK if np.alltrue([cb.iterable(c) and len(c) in (3, 4) for c in edge_color]): edge_colors = tuple(edge_color) else: # numbers (which are going to be mapped with a colormap) edge_colors = None else: raise ValueError('edge_color must contain color names or numbers') else: if is_string_like(edge_color) or len(edge_color) == 1: edge_colors = (colorConverter.to_rgba(edge_color, alpha), ) else: msg = 'edge_color must be a color or list of one color per edge' raise ValueError(msg) if (not G.is_directed() or not arrows): edge_collection = LineCollection(edge_pos, colors=edge_colors, linewidths=lw, antialiaseds=(1,), linestyle=style, transOffset=ax.transData, ) edge_collection.set_zorder(1) # edges go behind nodes edge_collection.set_label(label) ax.add_collection(edge_collection) # Note: there was a bug in mpl regarding the handling of alpha values # for each line in a LineCollection. It was fixed in matplotlib by # r7184 and r7189 (June 6 2009). We should then not set the alpha # value globally, since the user can instead provide per-edge alphas # now. Only set it globally if provided as a scalar. if cb.is_numlike(alpha): edge_collection.set_alpha(alpha) if edge_colors is None: if edge_cmap is not None: assert(isinstance(edge_cmap, Colormap)) edge_collection.set_array(np.asarray(edge_color)) edge_collection.set_cmap(edge_cmap) if edge_vmin is not None or edge_vmax is not None: edge_collection.set_clim(edge_vmin, edge_vmax) else: edge_collection.autoscale() return edge_collection arrow_collection = None if G.is_directed() and arrows: # Note: Waiting for someone to implement arrow to intersection with # marker. Meanwhile, this works well for polygons with more than 4 # sides and circle. def to_marker_edge(marker_size, marker): if marker in "s^>v<d": # `large` markers need extra space return np.sqrt(2 * marker_size) / 2 else: return np.sqrt(marker_size) / 2 # Draw arrows with `matplotlib.patches.FancyarrowPatch` arrow_collection = [] mutation_scale = arrowsize # scale factor of arrow head arrow_colors = edge_colors if arrow_colors is None: if edge_cmap is not None: assert(isinstance(edge_cmap, Colormap)) else: edge_cmap = plt.get_cmap() # default matplotlib colormap if edge_vmin is None: edge_vmin = min(edge_color) if edge_vmax is None: edge_vmax = max(edge_color) color_normal = Normalize(vmin=edge_vmin, vmax=edge_vmax) for i, (src, dst) in enumerate(edge_pos): x1, y1 = src x2, y2 = dst arrow_color = None line_width = None shrink_source = 0 # space from source to tail shrink_target = 0 # space from head to target if cb.iterable(node_size): # many node sizes src_node, dst_node = edgelist[i] index_node = nodelist.index(dst_node) marker_size = node_size[index_node] shrink_target = to_marker_edge(marker_size, node_shape) else: shrink_target = to_marker_edge(node_size, node_shape) if arrow_colors is None: arrow_color = edge_cmap(color_normal(edge_color[i])) elif len(arrow_colors) > 1: arrow_color = arrow_colors[i] else: arrow_color = arrow_colors[0] if len(lw) > 1: line_width = lw[i] else: line_width = lw[0] arrow = FancyArrowPatch((x1, y1), (x2, y2), arrowstyle=arrowstyle, shrinkA=shrink_source, shrinkB=shrink_target, mutation_scale=mutation_scale, color=arrow_color, linewidth=line_width, zorder=1) # arrows go behind nodes # There seems to be a bug in matplotlib to make collections of # FancyArrowPatch instances. Until fixed, the patches are added # individually to the axes instance. arrow_collection.append(arrow) ax.add_patch(arrow) # update view minx = np.amin(np.ravel(edge_pos[:, :, 0])) maxx = np.amax(np.ravel(edge_pos[:, :, 0])) miny = np.amin(np.ravel(edge_pos[:, :, 1])) maxy = np.amax(np.ravel(edge_pos[:, :, 1])) w = maxx - minx h = maxy - miny padx, pady = 0.05 * w, 0.05 * h corners = (minx - padx, miny - pady), (maxx + padx, maxy + pady) ax.update_datalim(corners) ax.autoscale_view() return arrow_collection
def plot(n, margin=0.05, ax=None, geomap=True, projection=None, bus_colors='b', bus_alpha=1, line_colors={ 'Line': 'g', 'Link': 'cyan' }, bus_sizes=1e-2, line_widths={ 'Line': 2, 'Link': 2 }, flow=None, layouter=None, title="", line_cmap=None, bus_cmap=None, boundaries=None, geometry=False, branch_components=['Line', 'Link'], jitter=None, color_geomap=None): """ Plot the network buses and lines using matplotlib and cartopy. Parameters ---------- margin : float Margin at the sides as proportion of distance between max/min x,y ax : matplotlib ax, defaults to plt.gca() Axis to which to plot the network geomap: bool/str, default True Switch to use Cartopy and draw geographical features. If string is passed, it will be used as a resolution argument, valid options are '10m', '50m' and '110m'. projection: cartopy.crs.Projection, defaults to None Define the projection of your geomap, only valid if cartopy is installed. If None (default) is passed the projection for cartopy is set to cartopy.crs.PlateCarree bus_colors : dict/pandas.Series Colors for the buses, defaults to "b". If bus_sizes is a pandas.Series with a Multiindex, bus_colors defaults to the n.carriers['color'] column. bus_sizes : dict/pandas.Series Sizes of bus points, defaults to 1e-2. If a multiindexed Series is passed, the function will draw pies for each bus (first index level) with segments of different color (second index level). Such a Series is ob- tained by e.g. n.generators.groupby(['bus', 'carrier']).p_nom.sum() bus_alpha : float Adds alpha channel to buses, defaults to 1. line_colors : dict/pandas.Series Colors for the lines, defaults to "g" for Lines and "cyan" for Links. Colors for branches other than Lines can be specified using a pandas Series with a MultiIndex. line_widths : dict/pandas.Series Widths of lines, defaults to 2. Widths for branches other than Lines can be specified using a pandas Series with a MultiIndex. flow : snapshot/pandas.Series/function/string Flow to be displayed in the plot, defaults to None. If an element of n.snapshots is given, the flow at this timestamp will be displayed. If an aggregation function is given, is will be applied to the total network flow via pandas.DataFrame.agg (accepts also function names). Otherwise flows can be specified by passing a pandas Series with MultiIndex including all necessary branch components. Use the line_widths argument to additionally adjust the size of the flow arrows. layouter : networkx.drawing.layout function, default None Layouting function from `networkx <https://networkx.github.io/>`_ which overrules coordinates given in ``n.buses[['x','y']]``. See `list <https://networkx.github.io/documentation/stable/reference/drawing.html#module-networkx.drawing.layout>`_ of available options. title : string Graph title line_cmap : plt.cm.ColorMap/str|dict If line_colors are floats, this color map will assign the colors. Use a dict to specify colormaps for more than one branch type. bus_cmap : plt.cm.ColorMap/str If bus_colors are floats, this color map will assign the colors boundaries : list of four floats Boundaries of the plot in format [x1,x2,y1,y2] branch_components : list of str Branch components to be plotted, defaults to Line and Link. jitter : None|float Amount of random noise to add to bus positions to distinguish overlapping buses color_geomap : dict or bool Specify colors to paint land and sea areas in. If True, it defaults to `{'ocean': 'lightblue', 'land': 'whitesmoke'}`. If no dictionary is provided, colors are white. Returns ------- bus_collection, branch_collection1, ... : tuple of Collections Collections for buses and branches. """ defaults_for_branches = pd.Series({ 'Link': dict(color="cyan", width=2), 'Line': dict(color="b", width=2), 'Transformer': dict(color='green', width=2) }).rename_axis('component') x, y = _get_coordinates(n, layouter=layouter) if geomap: if not cartopy_present: logger.warning( "Cartopy needs to be installed to use `geomap=True`.") geomap = False if projection is None: projection = get_projection_from_crs(n.srid) if ax is None: ax = plt.gca(projection=projection) else: assert isinstance(ax, cartopy.mpl.geoaxes.GeoAxesSubplot), ( 'The passed axis is not a GeoAxesSubplot. You can ' 'create one with: \nimport cartopy.crs as ccrs \n' 'fig, ax = plt.subplots(' 'subplot_kw={"projection":ccrs.PlateCarree()})') transform = draw_map_cartopy(n, x, y, ax, boundaries, margin, geomap, color_geomap) x, y, z = ax.projection.transform_points(transform, x.values, y.values).T x, y = pd.Series(x, n.buses.index), pd.Series(y, n.buses.index) elif ax is None: ax = plt.gca() if jitter is not None: x = x + np.random.uniform(low=-jitter, high=jitter, size=len(x)) y = y + np.random.uniform(low=-jitter, high=jitter, size=len(y)) if isinstance(bus_sizes, pd.Series) and isinstance(bus_sizes.index, pd.MultiIndex): # We are drawing pies to show all the different shares assert len(bus_sizes.index.levels[0].difference(n.buses.index)) == 0, \ "The first MultiIndex level of bus_sizes must contain buses" if isinstance(bus_colors, dict): bus_colors = pd.Series(bus_colors) # case bus_colors isn't a series or dict: look in n.carriers for existent colors if not isinstance(bus_colors, pd.Series): bus_colors = n.carriers.color.dropna() assert bus_sizes.index.levels[1].isin(bus_colors.index).all(), ( "Colors not defined for all elements in the second MultiIndex " "level of bus_sizes, please make sure that all the elements are " "included in bus_colors or in n.carriers.color") bus_sizes = bus_sizes.sort_index(level=0, sort_remaining=False) if geomap: bus_sizes *= projected_area_factor(ax, n.srid)**2 patches = [] for b_i in bus_sizes.index.levels[0]: s = bus_sizes.loc[b_i] radius = s.sum()**0.5 if radius == 0.0: ratios = s else: ratios = s / s.sum() start = 0.25 for i, ratio in ratios.iteritems(): patches.append( Wedge((x.at[b_i], y.at[b_i]), radius, 360 * start, 360 * (start + ratio), facecolor=bus_colors[i], alpha=bus_alpha)) start += ratio bus_collection = PatchCollection(patches, match_original=True) ax.add_collection(bus_collection) else: c = pd.Series(bus_colors, index=n.buses.index) s = pd.Series(bus_sizes, index=n.buses.index, dtype="float") if geomap: s *= projected_area_factor(ax, n.srid)**2 if bus_cmap is not None and c.dtype is np.dtype('float'): if isinstance(bus_cmap, str): bus_cmap = plt.cm.get_cmap(bus_cmap) norm = plt.Normalize(vmin=c.min(), vmax=c.max()) c = c.apply(lambda cval: bus_cmap(norm(cval))) patches = [] for b_i in s.index: radius = s.at[b_i]**0.5 patches.append( Circle((x.at[b_i], y.at[b_i]), radius, facecolor=c.at[b_i], alpha=bus_alpha)) bus_collection = PatchCollection(patches, match_original=True) ax.add_collection(bus_collection) def as_branch_series(ser): # ensure that this function always return a multiindexed series if isinstance(ser, dict) and set(ser).issubset(branch_components): return pd.concat( { c.name: pd.Series(s, index=c.df.index) for c, s in zip(n.iterate_components(ser.keys()), ser.values()) }, names=['component', 'name']) elif isinstance(ser, pd.Series) and isinstance(ser.index, pd.MultiIndex): return ser.rename_axis(index=['component', 'name']) else: ser = pd.Series(ser, n.lines.index) return pd.concat([ser], axis=0, keys=['Line'], names=['component', 'name']).fillna(0) line_colors = as_branch_series(line_colors) line_widths = as_branch_series(line_widths) if not isinstance(line_cmap, dict): line_cmap = {'Line': line_cmap} branch_collections = [] if flow is not None: flow = (_flow_ds_from_arg( flow, n, branch_components).pipe(as_branch_series).div( sum( len(t.df) for t in n.iterate_components(branch_components)) + 100)) flow = flow.mul(line_widths[flow.index], fill_value=1) # update the line width, allows to set line widths separately from flows line_widths.update((5 * flow.abs()).pipe(np.sqrt)) arrows = directed_flow(n, flow, x=x, y=y, ax=ax, geomap=geomap, branch_colors=line_colors, branch_comps=branch_components, cmap=line_cmap['Line']) branch_collections.append(arrows) for c in n.iterate_components(branch_components): l_defaults = defaults_for_branches[c.name] l_widths = line_widths.get(c.name, l_defaults['width']) l_nums = None l_colors = line_colors.get(c.name, l_defaults['color']) if isinstance(l_colors, pd.Series): if issubclass(l_colors.dtype.type, np.number): l_nums = l_colors l_colors = None else: l_colors.fillna(l_defaults['color'], inplace=True) if not geometry: segments = (np.asarray( ((c.df.bus0.map(x), c.df.bus0.map(y)), (c.df.bus1.map(x), c.df.bus1.map(y)))).transpose(2, 0, 1)) else: from shapely.wkt import loads from shapely.geometry import LineString linestrings = c.df.geometry[lambda ds: ds != ''].map(loads) assert all(isinstance(ls, LineString) for ls in linestrings), ( "The WKT-encoded geometry in the 'geometry' column must be " "composed of LineStrings") segments = np.asarray(list(linestrings.map(np.asarray))) l_collection = LineCollection(segments, linewidths=l_widths, antialiaseds=(1, ), colors=l_colors, transOffset=ax.transData) if l_nums is not None: l_collection.set_array(np.asarray(l_nums)) l_collection.set_cmap(line_cmap.get(c.name, None)) l_collection.autoscale() ax.add_collection(l_collection) l_collection.set_zorder(3) branch_collections.append(l_collection) bus_collection.set_zorder(4) ax.update_datalim(compute_bbox_with_margins(margin, x, y)) ax.autoscale_view() if geomap: ax.outline_patch.set_visible(False) ax.axis('off') else: ax.set_aspect('equal') ax.set_title(title) return (bus_collection, ) + tuple(branch_collections)
def plot(network, margin=0.05, ax=None, basemap=True, bus_colors='b', line_colors='g', bus_sizes=10, line_widths=2, title="", line_cmap=None, bus_cmap=None, boundaries=None, geometry=False, branch_types=['Line', 'Link']): """ Plot the network buses and lines using matplotlib and Basemap. Parameters ---------- margin : float Margin at the sides as proportion of distance between max/min x,y ax : matplotlib ax, defaults to plt.gca() Axis to which to plot the network basemap : bool, default True Switch to use Basemap bus_colors : dict/pandas.Series Colors for the buses, defaults to "b" bus_sizes : dict/pandas.Series Sizes of bus points, defaults to 10 line_colors : dict/pandas.Series Colors for the lines, defaults to "g" for Lines and "cyan" for Links. Colors for branches other than Lines can be specified using a pandas Series with a MultiIndex. line_widths : dict/pandas.Series Widths of lines, defaults to 2. Widths for branches other than Lines can be specified using a pandas Series with a MultiIndex. title : string Graph title line_cmap : plt.cm.ColorMap/str|dict If line_colors are floats, this color map will assign the colors. Use a dict to specify colormaps for more than one branch type. bus_cmap : plt.cm.ColorMap/str If bus_colors are floats, this color map will assign the colors boundaries : list of four floats Boundaries of the plot in format [x1,x2,y1,y2] branch_types : list of str or pypsa.component Branch types to be plotted, defaults to Line and Link. Returns ------- bus_collection, branch_collection1, ... : tuple of Collections Collections for buses and branches. """ defaults_for_branches = { 'Link': dict(color="cyan", width=2), 'Line': dict(color="b", width=2) } if not plt_present: logger.error("Matplotlib is not present, so plotting won't work.") return if ax is None: ax = plt.gca() def compute_bbox_with_margins(margin, x, y): #set margins pos = np.asarray((x, y)) minxy, maxxy = pos.min(axis=1), pos.max(axis=1) xy1 = minxy - margin * (maxxy - minxy) xy2 = maxxy + margin * (maxxy - minxy) return tuple(xy1), tuple(xy2) x = network.buses["x"] y = network.buses["y"] if basemap and basemap_present: if boundaries is None: (x1, y1), (x2, y2) = compute_bbox_with_margins(margin, x, y) else: x1, x2, y1, y2 = boundaries bmap = Basemap(resolution='l', epsg=network.srid, llcrnrlat=y1, urcrnrlat=y2, llcrnrlon=x1, urcrnrlon=x2, ax=ax) bmap.drawcountries() bmap.drawcoastlines() x, y = bmap(x.values, y.values) x = pd.Series(x, network.buses.index) y = pd.Series(y, network.buses.index) c = pd.Series(bus_colors, index=network.buses.index) if c.dtype == np.dtype('O'): c.fillna("b", inplace=True) c = list(c.values) s = pd.Series(bus_sizes, index=network.buses.index, dtype="float").fillna(10) bus_collection = ax.scatter(x, y, c=c, s=s, cmap=bus_cmap) def as_branch_series(ser): if isinstance(ser, pd.Series): if isinstance(ser.index, pd.MultiIndex): return ser index = ser.index ser = ser.values else: index = network.lines.index return pd.Series(ser, index=pd.MultiIndex(levels=(["Line"], index), labels=(np.zeros(len(index)), np.arange(len(index))))) line_colors = as_branch_series(line_colors) line_widths = as_branch_series(line_widths) if not isinstance(line_cmap, dict): line_cmap = {'Line': line_cmap} branch_collections = [] for t in network.iterate_components(branch_types): l_defaults = defaults_for_branches[t.name] l_widths = line_widths.get(t.name, l_defaults['width']) l_nums = None if t.name in line_colors: l_colors = line_colors[t.name] if issubclass(l_colors.dtype.type, np.number): l_nums = l_colors l_colors = None else: l_colors.fillna(l_defaults['color'], inplace=True) else: l_colors = l_defaults['color'] if not geometry: segments = (np.asarray( ((t.df.bus0.map(x), t.df.bus0.map(y)), (t.df.bus1.map(x), t.df.bus1.map(y)))).transpose(2, 0, 1)) else: from shapely.wkt import loads from shapely.geometry import LineString linestrings = t.df.geometry.map(loads) assert all(isinstance(ls, LineString) for ls in linestrings), \ "The WKT-encoded geometry in the 'geometry' column must be composed of LineStrings" segments = np.asarray(list(linestrings.map(np.asarray))) if basemap and basemap_present: segments = np.transpose( bmap(*np.transpose(segments, (2, 0, 1))), (1, 2, 0)) l_collection = LineCollection(segments, linewidths=l_widths, antialiaseds=(1, ), colors=l_colors, transOffset=ax.transData) if l_nums is not None: l_collection.set_array(np.asarray(l_nums)) l_collection.set_cmap(line_cmap.get(t.name, None)) l_collection.autoscale() ax.add_collection(l_collection) l_collection.set_zorder(1) branch_collections.append(l_collection) bus_collection.set_zorder(2) ax.update_datalim(compute_bbox_with_margins(margin, x, y)) ax.autoscale_view() ax.set_title(title) return (bus_collection, ) + tuple(branch_collections)
def plot(network, margin=0.05, ax=None, geomap=True, projection=None, bus_colors='b', line_colors={'Line':'g', 'Link':'cyan'}, bus_sizes=10, line_widths={'Line':2, 'Link':2}, flow=None, title="", line_cmap=None, bus_cmap=None, boundaries=None, geometry=False, branch_components=['Line', 'Link'], jitter=None, basemap=None, basemap_parameters=None, color_geomap=None): """ Plot the network buses and lines using matplotlib and Basemap. Parameters ---------- margin : float Margin at the sides as proportion of distance between max/min x,y ax : matplotlib ax, defaults to plt.gca() Axis to which to plot the network geomap: bool/str, default True Switch to use Basemap or Cartopy (depends on what is installed). If string is passed, it will be used as a resolution argument. For Basemap users 'c' (crude), 'l' (low), 'i' (intermediate), 'h' (high), 'f' (full) are valid resolutions options. For Cartopy users '10m', '50m', '110m' are valid resolutions options. projection: cartopy.crs.Projection, defaults to None Define the projection of your geomap, only valid if cartopy is installed. If None (default) is passed the projection for cartopy is set to cartopy.crs.PlateCarree bus_colors : dict/pandas.Series Colors for the buses, defaults to "b" bus_sizes : dict/pandas.Series Sizes of bus points, defaults to 10 line_colors : dict/pandas.Series Colors for the lines, defaults to "g" for Lines and "cyan" for Links. Colors for branches other than Lines can be specified using a pandas Series with a MultiIndex. line_widths : dict/pandas.Series Widths of lines, defaults to 2. Widths for branches other than Lines can be specified using a pandas Series with a MultiIndex. flow : snapshot/pandas.Series/function/string Flow to be displayed in the plot, defaults to None. If an element of network.snapshots is given, the flow at this timestamp will be displayed. If an aggregation function is given, is will be applied to the total network flow via pandas.DataFrame.agg (accepts also function names). Otherwise flows can be specified by passing a pandas Series with MultiIndex including all necessary branch components. Use the line_widths argument to additionally adjust the size of the flow arrows. title : string Graph title line_cmap : plt.cm.ColorMap/str|dict If line_colors are floats, this color map will assign the colors. Use a dict to specify colormaps for more than one branch type. bus_cmap : plt.cm.ColorMap/str If bus_colors are floats, this color map will assign the colors boundaries : list of four floats Boundaries of the plot in format [x1,x2,y1,y2] branch_components : list of str Branch components to be plotted, defaults to Line and Link. jitter : None|float Amount of random noise to add to bus positions to distinguish overlapping buses basemap_parameters : dict Specify a dict with additional constructor parameters for the Basemap. Will disable Cartopy. Use this feature to set a custom projection. (e.g. `{'projection': 'tmerc', 'lon_0':10.0, 'lat_0':50.0}`) color_geomap : dict or bool Specify colors to paint land and sea areas in. If True, it defaults to `{'ocean': 'lightblue', 'land': 'whitesmoke'}`. If no dictionary is provided, colors are white. Returns ------- bus_collection, branch_collection1, ... : tuple of Collections Collections for buses and branches. """ defaults_for_branches = pd.Series({ 'Link': dict(color="cyan", width=2), 'Line': dict(color="b", width=2), 'Transformer': dict(color='green', width=2) }).rename_axis('component') if not plt_present: logger.error("Matplotlib is not present, so plotting won't work.") return if basemap is not None: logger.warning("argument `basemap` is deprecated, " "use `geomap` instead.") geomap = basemap if geomap: if not (cartopy_present or basemap_present): # Not suggesting Basemap since it is being deprecated logger.warning("Cartopy needs to be installed to use `geomap=True`.") geomap = False # Use cartopy by default, fall back on basemap use_basemap = False use_cartopy = cartopy_present if not use_cartopy: use_basemap = basemap_present # If the user specifies basemap parameters, they prefer # basemap over cartopy. # (This means that you can force the use of basemap by # setting `basemap_parameters={}`) if basemap_present: if basemap_parameters is not None: logger.warning("Basemap is being deprecated, consider " "switching to Cartopy.") use_basemap = True use_cartopy = False if use_cartopy: if projection is None: projection = get_projection_from_crs(network.srid) if ax is None: ax = plt.gca(projection=projection) else: assert isinstance(ax, cartopy.mpl.geoaxes.GeoAxesSubplot), ( 'The passed axis is not a GeoAxesSubplot. You can ' 'create one with: \nimport cartopy.crs as ccrs \n' 'fig, ax = plt.subplots(' 'subplot_kw={"projection":ccrs.PlateCarree()})') elif ax is None: ax = plt.gca() x, y = network.buses["x"], network.buses["y"] axis_transform = ax.transData if geomap: if use_cartopy: axis_transform = draw_map_cartopy(network, x, y, ax, boundaries, margin, geomap, color_geomap) new_coords = pd.DataFrame( ax.projection.transform_points(axis_transform, x.values, y.values), index=network.buses.index, columns=['x', 'y', 'z']) x, y = new_coords['x'], new_coords['y'] elif use_basemap: basemap_transform = draw_map_basemap(network, x, y, ax, boundaries, margin, geomap, basemap_parameters, color_geomap) # A non-standard projection might be used; the easiest way to # support this is to tranform the bus coordinates. x, y = basemap_transform(x.values, y.values) x = pd.Series(x, network.buses.index) y = pd.Series(y, network.buses.index) if jitter is not None: x = x + np.random.uniform(low=-jitter, high=jitter, size=len(x)) y = y + np.random.uniform(low=-jitter, high=jitter, size=len(y)) if isinstance(bus_sizes, pd.Series) and isinstance(bus_sizes.index, pd.MultiIndex): # We are drawing pies to show all the different shares assert len(bus_sizes.index.levels[0].difference(network.buses.index)) == 0, \ "The first MultiIndex level of bus_sizes must contain buses" assert (isinstance(bus_colors, dict) and set(bus_colors).issuperset(bus_sizes.index.levels[1])), \ "bus_colors must be a dictionary defining a color for each element " \ "in the second MultiIndex level of bus_sizes" bus_sizes = bus_sizes.sort_index(level=0, sort_remaining=False) if geomap: bus_sizes *= projected_area_factor(ax, network.srid)**2 patches = [] for b_i in bus_sizes.index.levels[0]: s = bus_sizes.loc[b_i] radius = s.sum()**0.5 if radius == 0.0: ratios = s else: ratios = s/s.sum() start = 0.25 for i, ratio in ratios.iteritems(): patches.append(Wedge((x.at[b_i], y.at[b_i]), radius, 360*start, 360*(start+ratio), facecolor=bus_colors[i])) start += ratio bus_collection = PatchCollection(patches, match_original=True) ax.add_collection(bus_collection) else: c = pd.Series(bus_colors, index=network.buses.index) s = pd.Series(bus_sizes, index=network.buses.index, dtype="float").fillna(10) bus_collection = ax.scatter(x, y, c=c, s=s, cmap=bus_cmap, edgecolor='face') def as_branch_series(ser): # ensure that this function always return a multiindexed series if isinstance(ser, dict) and set(ser).issubset(branch_components): return pd.concat( {c.name: pd.Series(s, index=c.df.index) for c, s in zip(network.iterate_components(ser.keys()), ser.values())}, names=['component', 'name']) elif isinstance(ser, pd.Series) and isinstance(ser.index, pd.MultiIndex): return ser.rename_axis(index=['component', 'name']) else: ser = pd.Series(ser, network.lines.index) return pd.concat([ser], axis=0, keys=['Line'], names=['component', 'name']).fillna(0) line_colors = as_branch_series(line_colors) line_widths = as_branch_series(line_widths) if not isinstance(line_cmap, dict): line_cmap = {'Line': line_cmap} branch_collections = [] if flow is not None: flow = (_flow_ds_from_arg(flow, network, branch_components) .pipe(as_branch_series) .div(sum(len(t.df) for t in network.iterate_components(branch_components)) + 100)) flow = flow.mul(line_widths[flow.index], fill_value=1) # update the line width, allows to set line widths separately from flows line_widths.update((5 * flow.abs()).pipe(np.sqrt)) arrows = directed_flow(network, flow, x=x, y=y, ax=ax, geomap=geomap, branch_colors=line_colors, branch_comps=branch_components, cmap=line_cmap['Line']) branch_collections.append(arrows) for c in network.iterate_components(branch_components): l_defaults = defaults_for_branches[c.name] l_widths = line_widths.get(c.name, l_defaults['width']) l_nums = None l_colors = line_colors.get(c.name, l_defaults['color']) if isinstance(l_colors, pd.Series): if issubclass(l_colors.dtype.type, np.number): l_nums = l_colors l_colors = None else: l_colors.fillna(l_defaults['color'], inplace=True) if not geometry: segments = (np.asarray(((c.df.bus0.map(x), c.df.bus0.map(y)), (c.df.bus1.map(x), c.df.bus1.map(y)))) .transpose(2, 0, 1)) else: from shapely.wkt import loads from shapely.geometry import LineString linestrings = c.df.geometry[lambda ds: ds != ''].map(loads) assert all(isinstance(ls, LineString) for ls in linestrings), ( "The WKT-encoded geometry in the 'geometry' column must be " "composed of LineStrings") segments = np.asarray(list(linestrings.map(np.asarray))) l_collection = LineCollection(segments, linewidths=l_widths, antialiaseds=(1,), colors=l_colors, transOffset=ax.transData) if l_nums is not None: l_collection.set_array(np.asarray(l_nums)) l_collection.set_cmap(line_cmap.get(c.name, None)) l_collection.autoscale() ax.add_collection(l_collection) l_collection.set_zorder(3) branch_collections.append(l_collection) bus_collection.set_zorder(4) ax.update_datalim(compute_bbox_with_margins(margin, x, y)) ax.autoscale_view() if geomap: if use_cartopy: ax.outline_patch.set_visible(False) ax.axis('off') ax.set_title(title) return (bus_collection,) + tuple(branch_collections)
def draw_networkx_edges(G, pos, edgelist=None, width=1.0, edge_color='k', style='solid', alpha=1.0, edge_cmap=None, edge_vmin=None, edge_vmax=None, ax=None, arrows=True, label=None, **kwds): """Draw the edges of the graph G. This draws only the edges of the graph G. Parameters ---------- G : graph A networkx graph pos : dictionary A dictionary with nodes as keys and positions as values. Positions should be sequences of length 2. edgelist : collection of edge tuples Draw only specified edges(default=G.edges()) width : float, or array of floats Line width of edges (default=1.0) edge_color : color string, or array of floats Edge color. Can be a single color format string (default='r'), or a sequence of colors with the same length as edgelist. If numeric values are specified they will be mapped to colors using the edge_cmap and edge_vmin,edge_vmax parameters. style : string Edge line style (default='solid') (solid|dashed|dotted,dashdot) alpha : float The edge transparency (default=1.0) edge_ cmap : Matplotlib colormap Colormap for mapping intensities of edges (default=None) edge_vmin,edge_vmax : floats Minimum and maximum for edge colormap scaling (default=None) ax : Matplotlib Axes object, optional Draw the graph in the specified Matplotlib axes. arrows : bool, optional (default=True) For directed graphs, if True draw arrowheads. label : [None| string] Label for legend Returns ------- matplotlib.collection.LineCollection `LineCollection` of the edges Notes ----- For directed graphs, "arrows" (actually just thicker stubs) are drawn at the head end. Arrows can be turned off with keyword arrows=False. Yes, it is ugly but drawing proper arrows with Matplotlib this way is tricky. Examples -------- >>> G = nx.dodecahedral_graph() >>> edges = nx.draw_networkx_edges(G, pos=nx.spring_layout(G)) Also see the NetworkX drawing examples at https://networkx.github.io/documentation/latest/auto_examples/index.html See Also -------- draw() draw_networkx() draw_networkx_nodes() draw_networkx_labels() draw_networkx_edge_labels() """ try: import matplotlib import matplotlib.pyplot as plt import matplotlib.cbook as cb from matplotlib.colors import colorConverter, Colormap from matplotlib.collections import LineCollection import numpy as np except ImportError: raise ImportError("Matplotlib required for draw()") except RuntimeError: print("Matplotlib unable to open display") raise if ax is None: ax = plt.gca() if edgelist is None: edgelist = list(G.edges()) if not edgelist or len(edgelist) == 0: # no edges! return None # set edge positions edge_pos = np.asarray([(pos[e[0]], pos[e[1]]) for e in edgelist]) if not cb.iterable(width): lw = (width,) else: lw = width if not cb.is_string_like(edge_color) \ and cb.iterable(edge_color) \ and len(edge_color) == len(edge_pos): if np.alltrue([cb.is_string_like(c) for c in edge_color]): # (should check ALL elements) # list of color letters such as ['k','r','k',...] edge_colors = tuple([colorConverter.to_rgba(c, alpha) for c in edge_color]) elif np.alltrue([not cb.is_string_like(c) for c in edge_color]): # If color specs are given as (rgb) or (rgba) tuples, we're OK if np.alltrue([cb.iterable(c) and len(c) in (3, 4) for c in edge_color]): edge_colors = tuple(edge_color) else: # numbers (which are going to be mapped with a colormap) edge_colors = None else: raise ValueError('edge_color must consist of either color names or numbers') else: if cb.is_string_like(edge_color) or len(edge_color) == 1: edge_colors = (colorConverter.to_rgba(edge_color, alpha), ) else: raise ValueError( 'edge_color must be a single color or list of exactly m colors where m is the number or edges') edge_collection = LineCollection(edge_pos, colors=edge_colors, linewidths=lw, antialiaseds=(1,), linestyle=style, transOffset=ax.transData, ) edge_collection.set_zorder(1) # edges go behind nodes edge_collection.set_label(label) ax.add_collection(edge_collection) # Note: there was a bug in mpl regarding the handling of alpha values for # each line in a LineCollection. It was fixed in matplotlib in r7184 and # r7189 (June 6 2009). We should then not set the alpha value globally, # since the user can instead provide per-edge alphas now. Only set it # globally if provided as a scalar. if cb.is_numlike(alpha): edge_collection.set_alpha(alpha) if edge_colors is None: if edge_cmap is not None: assert(isinstance(edge_cmap, Colormap)) edge_collection.set_array(np.asarray(edge_color)) edge_collection.set_cmap(edge_cmap) if edge_vmin is not None or edge_vmax is not None: edge_collection.set_clim(edge_vmin, edge_vmax) else: edge_collection.autoscale() arrow_collection = None if G.is_directed() and arrows: # a directed graph hack # draw thick line segments at head end of edge # waiting for someone else to implement arrows that will work arrow_colors = edge_colors a_pos = [] p = 1.0 - 0.25 # make head segment 25 percent of edge length for src, dst in edge_pos: x1, y1 = src x2, y2 = dst dx = x2 - x1 # x offset dy = y2 - y1 # y offset d = np.sqrt(float(dx**2 + dy**2)) # length of edge if d == 0: # source and target at same position continue if dx == 0: # vertical edge xa = x2 ya = dy * p + y1 if dy == 0: # horizontal edge ya = y2 xa = dx * p + x1 else: theta = np.arctan2(dy, dx) xa = p * d * np.cos(theta) + x1 ya = p * d * np.sin(theta) + y1 a_pos.append(((xa, ya), (x2, y2))) arrow_collection = LineCollection(a_pos, colors=arrow_colors, linewidths=[4 * ww for ww in lw], antialiaseds=(1,), transOffset=ax.transData, ) arrow_collection.set_zorder(1) # edges go behind nodes arrow_collection.set_label(label) ax.add_collection(arrow_collection) # update view minx = np.amin(np.ravel(edge_pos[:, :, 0])) maxx = np.amax(np.ravel(edge_pos[:, :, 0])) miny = np.amin(np.ravel(edge_pos[:, :, 1])) maxy = np.amax(np.ravel(edge_pos[:, :, 1])) w = maxx - minx h = maxy - miny padx, pady = 0.05 * w, 0.05 * h corners = (minx - padx, miny - pady), (maxx + padx, maxy + pady) ax.update_datalim(corners) ax.autoscale_view() # if arrow_collection: return edge_collection
def draw_edges(G, pos, ax, edgelist=None, width=1.0, width_adjuster=50, edge_color='k', style='solid', alpha=None, edge_cmap=None, edge_vmin=None, edge_vmax=None, traversal_weight=1.0, edge_delengthify=0.15, arrows=True, label=None, zorder=1, **kwds): """ Code cleaned-up version of networkx.draw_networkx_edges New args: width_adjuster - the line width is generated from the weight if present, use this adjuster to thicken the lines (multiply) """ if edgelist is None: edgelist = G.edges() if not edgelist or len(edgelist) == 0: # no edges! return None # set edge positions edge_pos = [(pos[e[0]], pos[e[1]]) for e in edgelist] new_ep = [] for e in edge_pos: x, y = e[0] dx, dy = e[1] # Get edge length elx = (dx - x) * edge_delengthify ely = (dy - y) * edge_delengthify x += elx y += ely dx -= elx dy -= ely new_ep.append(((x, y), (dx, dy))) edge_pos = numpy.asarray(new_ep) if not cb.iterable(width): #print [G.get_edge_data(n[0], n[1])['weight'] for n in edgelist] # see if I can find an edge attribute: if 'weight' in G.get_edge_data(edgelist[0][0], edgelist[0][1]): # Test an edge lw = [ 0.5 + ((G.get_edge_data(n[0], n[1])['weight'] - traversal_weight) * width_adjuster) for n in edgelist ] else: lw = (width, ) else: lw = width if not is_string_like(edge_color) and cb.iterable(edge_color) and len( edge_color) == len(edge_pos): if numpy.alltrue([cb.is_string_like(c) for c in edge_color]): # (should check ALL elements) # list of color letters such as ['k','r','k',...] edge_colors = tuple( [colorConverter.to_rgba(c, alpha) for c in edge_color]) elif numpy.alltrue([not cb.is_string_like(c) for c in edge_color]): # If color specs are given as (rgb) or (rgba) tuples, we're OK if numpy.alltrue( [cb.iterable(c) and len(c) in (3, 4) for c in edge_color]): edge_colors = tuple(edge_color) else: # numbers (which are going to be mapped with a colormap) edge_colors = None else: raise ValueError( 'edge_color must consist of either color names or numbers') else: if is_string_like(edge_color) or len(edge_color) == 1: edge_colors = (colorConverter.to_rgba(edge_color, alpha), ) else: raise ValueError( 'edge_color must be a single color or list of exactly m colors where m is the number or edges' ) edge_collection = LineCollection(edge_pos, colors=edge_colors, linewidths=lw, antialiaseds=(1, ), linestyle=style, transOffset=ax.transData, zorder=zorder) edge_collection.set_label(label) ax.add_collection(edge_collection) if cb.is_numlike(alpha): edge_collection.set_alpha(alpha) if edge_colors is None: if edge_cmap is not None: assert (isinstance(edge_cmap, Colormap)) edge_collection.set_array(numpy.asarray(edge_color)) edge_collection.set_cmap(edge_cmap) if edge_vmin is not None or edge_vmax is not None: edge_collection.set_clim(edge_vmin, edge_vmax) else: edge_collection.autoscale() # update view ''' minx = numpy.amin(numpy.ravel(edge_pos[:,:,0])) maxx = numpy.amax(numpy.ravel(edge_pos[:,:,0])) miny = numpy.amin(numpy.ravel(edge_pos[:,:,1])) maxy = numpy.amax(numpy.ravel(edge_pos[:,:,1])) w = maxx-minx h = maxy-miny padx, pady = 0.05*w, 0.05*h corners = (minx-padx, miny-pady), (maxx+padx, maxy+pady) ax.update_datalim(corners) ax.autoscale_view() ''' return (edge_collection)
def draw_networkx_edges(G, pos, edgelist=None, width=1.0, edge_color='k', style='solid', alpha=1.0, arrowstyle='-|>', arrowsize=10, edge_cmap=None, edge_vmin=None, edge_vmax=None, ax=None, arrows=True, label=None, node_size=300, nodelist=None, node_shape="o", connectionstyle=None, **kwds): """Draw the edges of the graph G. This draws only the edges of the graph G. Parameters ---------- G : graph A networkx graph pos : dictionary A dictionary with nodes as keys and positions as values. Positions should be sequences of length 2. edgelist : collection of edge tuples Draw only specified edges(default=G.edges()) width : float, or array of floats Line width of edges (default=1.0) edge_color : color string, or array of floats Edge color. Can be a single color format string (default='r'), or a sequence of colors with the same length as edgelist. If numeric values are specified they will be mapped to colors using the edge_cmap and edge_vmin,edge_vmax parameters. style : string Edge line style (default='solid') (solid|dashed|dotted,dashdot) alpha : float The edge transparency (default=1.0) edge_ cmap : Matplotlib colormap Colormap for mapping intensities of edges (default=None) edge_vmin,edge_vmax : floats Minimum and maximum for edge colormap scaling (default=None) ax : Matplotlib Axes object, optional Draw the graph in the specified Matplotlib axes. arrows : bool, optional (default=True) For directed graphs, if True draw arrowheads. Note: Arrows will be the same color as edges. arrowstyle : str, optional (default='-|>') For directed graphs, choose the style of the arrow heads. See :py:class: `matplotlib.patches.ArrowStyle` for more options. arrowsize : int, optional (default=10) For directed graphs, choose the size of the arrow head head's length and width. See :py:class: `matplotlib.patches.FancyArrowPatch` for attribute `mutation_scale` for more info. connectionstyle : str, optional (default=None) Pass the connectionstyle parameter to create curved arc of rounding radius rad. For example, connectionstyle='arc3,rad=0.2'. See :py:class: `matplotlib.patches.ConnectionStyle` and :py:class: `matplotlib.patches.FancyArrowPatch` for more info. label : [None| string] Label for legend Returns ------- matplotlib.collection.LineCollection `LineCollection` of the edges list of matplotlib.patches.FancyArrowPatch `FancyArrowPatch` instances of the directed edges Depending whether the drawing includes arrows or not. Notes ----- For directed graphs, arrows are drawn at the head end. Arrows can be turned off with keyword arrows=False. Be sure to include `node_size` as a keyword argument; arrows are drawn considering the size of nodes. Examples -------- >>> G = nx.dodecahedral_graph() >>> edges = nx.draw_networkx_edges(G, pos=nx.spring_layout(G)) >>> G = nx.DiGraph() >>> G.add_edges_from([(1, 2), (1, 3), (2, 3)]) >>> arcs = nx.draw_networkx_edges(G, pos=nx.spring_layout(G)) >>> alphas = [0.3, 0.4, 0.5] >>> for i, arc in enumerate(arcs): # change alpha values of arcs ... arc.set_alpha(alphas[i]) Also see the NetworkX drawing examples at https://networkx.github.io/documentation/latest/auto_examples/index.html See Also -------- draw() draw_networkx() draw_networkx_nodes() draw_networkx_labels() draw_networkx_edge_labels() """ try: import matplotlib import matplotlib.pyplot as plt import matplotlib.cbook as cb from matplotlib.colors import colorConverter, Colormap, Normalize from matplotlib.collections import LineCollection from matplotlib.patches import FancyArrowPatch import numpy as np except ImportError: raise ImportError("Matplotlib required for draw()") except RuntimeError: print("Matplotlib unable to open display") raise if ax is None: ax = plt.gca() if edgelist is None: edgelist = list(G.edges()) if not edgelist or len(edgelist) == 0: # no edges! return None if nodelist is None: nodelist = list(G.nodes()) # set edge positions edge_pos = np.asarray([(pos[e[0]], pos[e[1]]) for e in edgelist]) if not cb.iterable(width): lw = (width,) else: lw = width if not is_string_like(edge_color) \ and cb.iterable(edge_color) \ and len(edge_color) == len(edge_pos): if np.alltrue([is_string_like(c) for c in edge_color]): # (should check ALL elements) # list of color letters such as ['k','r','k',...] edge_colors = tuple([colorConverter.to_rgba(c, alpha) for c in edge_color]) elif np.alltrue([not is_string_like(c) for c in edge_color]): # If color specs are given as (rgb) or (rgba) tuples, we're OK if np.alltrue([cb.iterable(c) and len(c) in (3, 4) for c in edge_color]): edge_colors = tuple(edge_color) else: # numbers (which are going to be mapped with a colormap) edge_colors = None else: raise ValueError('edge_color must contain color names or numbers') else: if is_string_like(edge_color) or len(edge_color) == 1: edge_colors = (colorConverter.to_rgba(edge_color, alpha), ) else: msg = 'edge_color must be a color or list of one color per edge' raise ValueError(msg) if (not G.is_directed() or not arrows): edge_collection = LineCollection(edge_pos, colors=edge_colors, linewidths=lw, antialiaseds=(1,), linestyle=style, transOffset=ax.transData, ) edge_collection.set_zorder(1) # edges go behind nodes edge_collection.set_label(label) ax.add_collection(edge_collection) # Note: there was a bug in mpl regarding the handling of alpha values # for each line in a LineCollection. It was fixed in matplotlib by # r7184 and r7189 (June 6 2009). We should then not set the alpha # value globally, since the user can instead provide per-edge alphas # now. Only set it globally if provided as a scalar. if isinstance(alpha, Number): edge_collection.set_alpha(alpha) if edge_colors is None: if edge_cmap is not None: assert(isinstance(edge_cmap, Colormap)) edge_collection.set_array(np.asarray(edge_color)) edge_collection.set_cmap(edge_cmap) if edge_vmin is not None or edge_vmax is not None: edge_collection.set_clim(edge_vmin, edge_vmax) else: edge_collection.autoscale() return edge_collection arrow_collection = None if G.is_directed() and arrows: # Note: Waiting for someone to implement arrow to intersection with # marker. Meanwhile, this works well for polygons with more than 4 # sides and circle. def to_marker_edge(marker_size, marker): if marker in "s^>v<d": # `large` markers need extra space return np.sqrt(2 * marker_size) / 2 else: return np.sqrt(marker_size) / 2 # Draw arrows with `matplotlib.patches.FancyarrowPatch` arrow_collection = [] mutation_scale = arrowsize # scale factor of arrow head arrow_colors = edge_colors if arrow_colors is None: if edge_cmap is not None: assert(isinstance(edge_cmap, Colormap)) else: edge_cmap = plt.get_cmap() # default matplotlib colormap if edge_vmin is None: edge_vmin = min(edge_color) if edge_vmax is None: edge_vmax = max(edge_color) color_normal = Normalize(vmin=edge_vmin, vmax=edge_vmax) for i, (src, dst) in enumerate(edge_pos): x1, y1 = src x2, y2 = dst arrow_color = None line_width = None shrink_source = 0 # space from source to tail shrink_target = 0 # space from head to target if cb.iterable(node_size): # many node sizes src_node, dst_node = edgelist[i][:2] index_node = nodelist.index(dst_node) marker_size = node_size[index_node] shrink_target = to_marker_edge(marker_size, node_shape) else: shrink_target = to_marker_edge(node_size, node_shape) if arrow_colors is None: arrow_color = edge_cmap(color_normal(edge_color[i])) elif len(arrow_colors) > 1: arrow_color = arrow_colors[i] else: arrow_color = arrow_colors[0] if len(lw) > 1: line_width = lw[i] else: line_width = lw[0] arrow = FancyArrowPatch((x1, y1), (x2, y2), arrowstyle=arrowstyle, shrinkA=shrink_source, shrinkB=shrink_target, mutation_scale=mutation_scale, color=arrow_color, linewidth=line_width, connectionstyle=connectionstyle, zorder=1) # arrows go behind nodes # There seems to be a bug in matplotlib to make collections of # FancyArrowPatch instances. Until fixed, the patches are added # individually to the axes instance. arrow_collection.append(arrow) ax.add_patch(arrow) # update view minx = np.amin(np.ravel(edge_pos[:, :, 0])) maxx = np.amax(np.ravel(edge_pos[:, :, 0])) miny = np.amin(np.ravel(edge_pos[:, :, 1])) maxy = np.amax(np.ravel(edge_pos[:, :, 1])) w = maxx - minx h = maxy - miny padx, pady = 0.05 * w, 0.05 * h corners = (minx - padx, miny - pady), (maxx + padx, maxy + pady) ax.update_datalim(corners) ax.autoscale_view() plt.tick_params( axis='both', which='both', bottom=False, left=False, labelbottom=False, labelleft=False) return arrow_collection
def draw_networkx_edges(G, pos, edgelist=None, width=1.0, edge_color='k', style='solid', alpha=1.0, edge_cmap=None, edge_vmin=None, edge_vmax=None, ax=None, arrows=True, arrowstyle='thick', label=None, **kwds): try: import matplotlib import matplotlib.pyplot as plt import matplotlib.cbook as cb import matplotlib.patches as patches from matplotlib.colors import colorConverter, Colormap from matplotlib.collections import LineCollection from matplotlib.path import Path import numpy except ImportError: raise ImportError("Matplotlib required for draw()") except RuntimeError: print("Matplotlib unable to open display") raise # print "drawing_edges" if ax is None: ax = plt.gca() if edgelist is None: edgelist = G.edges() if not edgelist or len(edgelist) == 0: # no edges! return None # set edge positions edge_pos = numpy.asarray([(pos[e[0]], pos[e[1]]) for e in edgelist]) # for e in edge_pos: # print e if not cb.iterable(width): lw = (width, ) else: lw = width if not cb.is_string_like(edge_color) \ and cb.iterable(edge_color) \ and len(edge_color) == len(edge_pos): if numpy.alltrue([cb.is_string_like(c) for c in edge_color]): # (should check ALL elements) # list of color letters such as ['k','r','k',...] edge_colors = tuple( [colorConverter.to_rgba(c, alpha) for c in edge_color]) elif numpy.alltrue([not cb.is_string_like(c) for c in edge_color]): # If color specs are given as (rgb) or (rgba) tuples, we're OK if numpy.alltrue( [cb.iterable(c) and len(c) in (3, 4) for c in edge_color]): edge_colors = tuple(edge_color) else: # numbers (which are going to be mapped with a colormap) edge_colors = None else: raise ValueError( 'edge_color must consist of either color names or numbers') else: if cb.is_string_like(edge_color) or len(edge_color) == 1: edge_colors = (colorConverter.to_rgba(edge_color, alpha), ) else: raise ValueError( 'edge_color must be a single color or list of exactly m colors where m is the number or edges' ) edge_collection = LineCollection( edge_pos, colors=edge_colors, linewidths=lw, antialiaseds=(1, ), linestyle=style, transOffset=ax.transData, ) # print type(edge_collection) edge_collection.set_zorder(1) # edges go behind nodes edge_collection.set_label(label) ax.add_collection(edge_collection) # Note: there was a bug in mpl regarding the handling of alpha values for # each line in a LineCollection. It was fixed in matplotlib in r7184 and # r7189 (June 6 2009). We should then not set the alpha value globally, # since the user can instead provide per-edge alphas now. Only set it # globally if provided as a scalar. if cb.is_numlike(alpha): edge_collection.set_alpha(alpha) if edge_colors is None: if edge_cmap is not None: assert (isinstance(edge_cmap, Colormap)) edge_collection.set_array(numpy.asarray(edge_color)) edge_collection.set_cmap(edge_cmap) if edge_vmin is not None or edge_vmax is not None: edge_collection.set_clim(edge_vmin, edge_vmax) else: edge_collection.autoscale() arrow_collection = None if G.is_directed() and arrows: # a directed graph hack-fix # draws arrows at each # waiting for someone else to implement arrows that will work arrow_colors = edge_colors a_pos = [] p = .1 # make arrows 10% of total length angle = 2.7 #angle for arrows for src, dst in edge_pos: x1, y1 = src x2, y2 = dst dx = x2 - x1 # x offset dy = y2 - y1 # y offset d = numpy.sqrt(float(dx**2 + dy**2)) # length of edge theta = numpy.arctan2(dy, dx) if d == 0: # source and target at same position continue if dx == 0: # vertical edge xa = x2 ya = dy + y1 if dy == 0: # horizontal edge ya = y2 xa = dx + x1 else: # xa = p*d*numpy.cos(theta)+x1 # ya = p*d*numpy.sin(theta)+y1 #corrects the endpoints to better draw x2 -= .04 * numpy.cos(theta) y2 -= .04 * numpy.sin(theta) lx1 = p * d * numpy.cos(theta + angle) + (x2) lx2 = p * d * numpy.cos(theta - angle) + (x2) ly1 = p * d * numpy.sin(theta + angle) + (y2) ly2 = p * d * numpy.sin(theta - angle) + (y2) a_pos.append(((lx1, ly1), (x2, y2))) a_pos.append(((lx2, ly2), (x2, y2))) arrow_collection = LineCollection( a_pos, colors=arrow_colors, linewidths=[1 * ww for ww in lw], antialiaseds=(1, ), transOffset=ax.transData, ) arrow_collection.set_zorder(1) # edges go behind nodes arrow_collection.set_label(label) # print type(ax) ax.add_collection(arrow_collection) #drawing self loops d = 1 c = 0.0707 selfedges = [] verts = [ (0.1 * d - 0.1 * d, 0.0), # P0 (c * d - 0.1 * d, c * d), # P0 (0.0 - 0.1 * d, 0.1 * d), # P0 (-c * d - 0.1 * d, c * d), # P0 (-0.1 * d - 0.1 * d, 0.0), # P0 (-c * d - 0.1 * d, -c * d), # P0 (0.0 - 0.1 * d, -0.1 * d), # P0 (c * d - 0.1 * d, -c * d), # P0 (0.1 * d - 0.1 * d, 0.0) ] # print verts codes = [ Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.LINETO, ] for e in edge_pos: if (numpy.array_equal(e[0], e[1])): nodes = verts[:] for i in range(len(nodes)): nodes[i] += e[0] # print nodes path = Path(nodes, codes) patch = patches.PathPatch(path, color=None, facecolor=None, edgecolor=edge_colors[0], fill=False, lw=4) ax.add_patch(patch) # update view minx = numpy.amin(numpy.ravel(edge_pos[:, :, 0])) maxx = numpy.amax(numpy.ravel(edge_pos[:, :, 0])) miny = numpy.amin(numpy.ravel(edge_pos[:, :, 1])) maxy = numpy.amax(numpy.ravel(edge_pos[:, :, 1])) w = maxx - minx h = maxy - miny padx, pady = 0.05 * w, 0.05 * h corners = (minx - padx, miny - pady), (maxx + padx, maxy + pady) # print ax ax.update_datalim(corners) ax.autoscale_view() # if arrow_collection: return edge_collection