How can I draw a graph with it's communities using python networkx like this image :
image url
The documentation for networkx.draw_networkx_nodes and networkx.draw_networkx_edges explains how to set the node and edge colors. The patches bounding the communities can be made by finding the positions of the nodes for each community and then drawing a patch (e.g. matplotlib.patches.Circle) that contains all positions (and then some).
The hard bit is the graph layout / setting the node positions.
AFAIK, there is no routine in networkx to achieve the desired graph layout "out of the box". What you want to do is the following:
Position the communities with respect to each other: create a new, weighted graph, where each node corresponds to a community, and the weights correspond to the number of edges between communities. Get a decent layout with your favourite graph layout algorithm (e.g.spring_layout).
Position the nodes within each community: for each community, create a new graph. Find a layout for the subgraph.
Combine node positions in 1) and 3). E.g. scale community positions calculated in 1) by a factor of 10; add those values to the positions of all nodes (as computed in 2)) within that community.
I have been wanting to implement this for a while. I might do it later today or over the weekend.
EDIT:
Voila. Now you just need to draw your favourite patch around (behind) the nodes.
import numpy as np
import matplotlib.pyplot as plt
import networkx as nx
def community_layout(g, partition):
"""
Compute the layout for a modular graph.
Arguments:
----------
g -- networkx.Graph or networkx.DiGraph instance
graph to plot
partition -- dict mapping int node -> int community
graph partitions
Returns:
--------
pos -- dict mapping int node -> (float x, float y)
node positions
"""
pos_communities = _position_communities(g, partition, scale=3.)
pos_nodes = _position_nodes(g, partition, scale=1.)
# combine positions
pos = dict()
for node in g.nodes():
pos[node] = pos_communities[node] + pos_nodes[node]
return pos
def _position_communities(g, partition, **kwargs):
# create a weighted graph, in which each node corresponds to a community,
# and each edge weight to the number of edges between communities
between_community_edges = _find_between_community_edges(g, partition)
communities = set(partition.values())
hypergraph = nx.DiGraph()
hypergraph.add_nodes_from(communities)
for (ci, cj), edges in between_community_edges.items():
hypergraph.add_edge(ci, cj, weight=len(edges))
# find layout for communities
pos_communities = nx.spring_layout(hypergraph, **kwargs)
# set node positions to position of community
pos = dict()
for node, community in partition.items():
pos[node] = pos_communities[community]
return pos
def _find_between_community_edges(g, partition):
edges = dict()
for (ni, nj) in g.edges():
ci = partition[ni]
cj = partition[nj]
if ci != cj:
try:
edges[(ci, cj)] += [(ni, nj)]
except KeyError:
edges[(ci, cj)] = [(ni, nj)]
return edges
def _position_nodes(g, partition, **kwargs):
"""
Positions nodes within communities.
"""
communities = dict()
for node, community in partition.items():
try:
communities[community] += [node]
except KeyError:
communities[community] = [node]
pos = dict()
for ci, nodes in communities.items():
subgraph = g.subgraph(nodes)
pos_subgraph = nx.spring_layout(subgraph, **kwargs)
pos.update(pos_subgraph)
return pos
def test():
# to install networkx 2.0 compatible version of python-louvain use:
# pip install -U git+https://github.com/taynaud/python-louvain.git#networkx2
from community import community_louvain
g = nx.karate_club_graph()
partition = community_louvain.best_partition(g)
pos = community_layout(g, partition)
nx.draw(g, pos, node_color=list(partition.values())); plt.show()
return
Addendum
Although the general idea is sound, my old implementation above has a few issues. Most importantly, the implementation doesn't work very well for unevenly sized communities. Specifically, _position_communities gives each community the same amount of real estate on the canvas. If some of the communities are much larger than others, these communities end up being compressed into the same amount of space as the small communities. Obviously, this does not reflect the structure of the graph very well.
I have written a library for visualizing networks, which is called netgraph. It includes an improved version of the community layout routine outlined above, which also considers the sizes of the communities when arranging them. It is fully compatible with networkx and igraph Graph objects, so it should be easy and fast to make great looking graphs (at least that is the idea).
import matplotlib.pyplot as plt
import networkx as nx
# installation easiest via pip:
# pip install netgraph
from netgraph import Graph
# create a modular graph
partition_sizes = [10, 20, 30, 40]
g = nx.random_partition_graph(partition_sizes, 0.5, 0.1)
# since we created the graph, we know the best partition:
node_to_community = dict()
node = 0
for community_id, size in enumerate(partition_sizes):
for _ in range(size):
node_to_community[node] = community_id
node += 1
# # alternatively, we can infer the best partition using Louvain:
# from community import community_louvain
# node_to_community = community_louvain.best_partition(g)
community_to_color = {
0 : 'tab:blue',
1 : 'tab:orange',
2 : 'tab:green',
3 : 'tab:red',
}
node_color = {node: community_to_color[community_id] for node, community_id in node_to_community.items()}
Graph(g,
node_color=node_color, node_edge_width=0, edge_alpha=0.1,
node_layout='community', node_layout_kwargs=dict(node_to_community=node_to_community),
edge_layout='bundled', edge_layout_kwargs=dict(k=2000),
)
plt.show()
Related
I am trying graph-tool by Tiago Peixoto to build a graph (either directed or undirected) from a given weighted adjacency matrix with a block structure. So far, unsuccessfully. My question partly overlaps with this thread on SO, which, however, remains without a clear solution.
Suppose I have a function that generates my block matrix of weights J, which is in the form:
Each block Jij is some random binary matrix with entries drawn from a given distribution. The scalars s and g respectively denote weights for connections within diagonal blocks (i.e. when i = j) and blocks off the diagonal (i.e. i ≠ j).
I build my graph in graph_tool as follows:
import graph_tool.all as gt
directed = False # True if we want the graph to be directed
J = generate_adj_bmatrix(...,s=0.1,g=0.01,directed=directed) # Some function to generate the weighted adjacency matrix (here the matrix will be symmetric since we want the graph undirected)
# Define graph
G = gt.Graph(directed=directed)
indexes = J.nonzero()
G.add_edge_list(np.transpose(indexes))
# Add weight information
G.ep['weight'] = G.new_ep("double", vals=J[indexes])
I can also add, if I want, some VertexProperty to my G graph to whose block my nodes belong. But how do I include this information in the code whereby I can build the circular graph? The code reads (pasted here from graph-tool docs):
state = gt.minimize_blockmodel_dl(G) # or should I consider instead state = gt.minimize_nested_blockmodel_dl(G)?
gt.draw_hierarchy(state)
t = gt.get_hierarchy_tree(state)[0]
tpos = pos = gt.radial_tree_layout(t, t.vertex(t.num_vertices() - 1), weighted=True)
cts = gt.get_hierarchy_control_points(G, t, tpos)
pos = G.own_property(tpos)
b = state.levels[0].b
shape = b.copy()
shape.a %= 14 # Have not yet figured out what I need it for
gt.graph_draw(G, pos=pos, vertex_fill_color=b, vertex_shape=shape,
edge_control_points=cts,edge_color=[0, 0, 0, 0.3], vertex_anchor=0)
Noteworthy is that the above code currently hangs seemingly too long. The minimize_blockmodel_dl(G) line appears to engage in an endless loop. Ideally, I should not sample my graph for clusters, since this information could already be provided as a property to the vertexes, based on my knowledge of the block structure of J. At the same time, minimize_blockmodel_dl(G) seems necessary in order to access the edge bundling option, doesn't it?
Here is the solution I came up with.
def visualize_network(J,N_sizes):
"""
Visualize a network from weighted block adjacency matrix in a circular layout with FEB.
Input arguments:
-- J : Weighted adjacency matrix (in block-matrix form, but can be any, as far as it is square).
-- N_sizes : {<block1_label>: size; <block2_label>: size,...} such that node indexes of block n follow immediately those of block n-1.
"""
import numpy as np
import matplotlib.colors as mcolors
import graph_tool.all as gt
# Generate the graph
G = gt.Graph(directed=True) # In my case, network edges are oriented
eindexes = J.nonzero()
G.add_edge_list(np.transpose(eindexes))
# Add weight information
weight = G.new_ep("double", vals = J[eindexes])
# Assign color to each vertex based on the block it belongs to
colors = {'B1' : 'k',
'B2' : 'r',
'B3' : 'g',
'B4' : 'b'}
regs = np.asarray(list(N_sizes.keys()))
rindexes = np.cumsum(list(N_sizes.values()))
iidd = regs[np.searchsorted(rindexes,np.arange(np.shape(J)[0]))]
region_id = G.new_vp("string",vals=iidd)
vcolors = [colors[id] for id in iidd]
vertex_color = G.new_vp("string",vals=vcolors)
# Assigns edge colors by out-node.
eid = regs[np.searchsorted(rindexes,np.arange(np.shape(J)[0]))]
ecolors = [mcolors.to_hex(c) for c in regs[np.searchsorted(rindexes,eindexes[0]]]
edge_color = G.new_ep("string",vals=ecolors)
# Construct a graph in a circular layout with FEB
G = gt.GraphView(G, vfilt=gt.label_largest_component(G))
state = gt.minimize_nested_blockmodel_dl(G)
t = gt.get_hierarchy_tree(state)[0]
tpos = gt.radial_tree_layout(t, t.vertex(t.num_vertices() - 1, use_index=False), weighted=True)
cts = gt.get_hierarchy_control_points(G, t, tpos)
pos = G.own_property(tpos)
gt.graph_draw(G,
pos = pos,
vertex_fill_color = vertex_color,
edge_control_points = cts,
edge_color = edge_color,
vertex_anchor = 0)
Additional documentation on the circular layout and this way of building the graph can be found at this graph-tool doc page.
I am trying to triangulate a large amount of massive data using Delaunay to scipy.spatial for triangulation and networkx to get the node adjacency relations. My code works very well on small data sets but when I try to introduce volumes of about 2 miollion points I always get the following error:
raise NetworkXError(f"The node {n} is not in the graph.") from e
NetworkXError: The node 1 is not in the graph.
It seems like my graph store the first node and nothing more. When I did my research I found that networkx is well adapted to massive data
Here is my code :
import numpy as np
import networkx as nx
import scipy.spatial
points = np.genfromtxt('las1.xyz', delimiter = ';')
xy= points[:,0:2]
z= points[:,2]
delTri = scipy.spatial.Delaunay(xy)
edges = set()
for n in range(delTri.nsimplex):
edge = sorted([delTri.vertices[n,0], delTri.vertices[n,1]])
edges.add((edge[0], edge[1]))
edge = sorted([delTri.vertices[n,0], delTri.vertices[n,2]])
edges.add((edge[0], edge[1]))
edge = sorted([delTri.vertices[n,1], delTri.vertices[n,2]])
edges.add((edge[0], edge[1]))
pts_neigh = {}
graph = nx.Graph(list(edges))
for i in range(len(xy)):
pts_neigh[i] = list(graph.neighbors(i))
I still get the edges list from my networkx graph but it seems like it fails at the level of constructing the nodes.
I will be so grateful for your help.
Although it's possible to instantiate graph with specific data, the syntax can be a bit complex. An easier option is to explicitly add edges from a list:
graph = nx.Graph()
graph.add_edges_from(list(edges))
I am looking for a way to automatically define neighbourhoods in cities as polygons on a graph.
My definition of a neighbourhood has two parts:
A block: An area inclosed between a number of streets, where the number of streets (edges) and intersections (nodes) is a minimum of three (a triangle).
A neighbourhood: For any given block, all the blocks directly adjacent to that block and the block itself.
See this illustration for an example:
E.g. B4 is block defined by 7 nodes and 6 edges connecting them. As most of the examples here, the other blocks are defined by 4 nodes and 4 edges connecting them. Also, the neighbourhood of B1 includes B2 (and vice versa) while B2 also includes B3.
I am using osmnx to get street data from OSM.
Using osmnx and networkx, how can I traverse a graph to find the nodes and edges that define each block?
For each block, how can I find the adjacent blocks?
I am working myself towards a piece of code that takes a graph and a pair of coordinates (latitude, longitude) as input, identifies the relevant block and returns the polygon for that block and the neighbourhood as defined above.
Here is the code used to make the map:
import osmnx as ox
import networkx as nx
import matplotlib.pyplot as plt
G = ox.graph_from_address('Nørrebrogade 20, Copenhagen Municipality',
network_type='all',
distance=500)
and my attempt at finding cliques with different number of nodes and degrees.
def plot_cliques(graph, number_of_nodes, degree):
ug = ox.save_load.get_undirected(graph)
cliques = nx.find_cliques(ug)
cliques_nodes = [clq for clq in cliques if len(clq) >= number_of_nodes]
print("{} cliques with more than {} nodes.".format(len(cliques_nodes), number_of_nodes))
nodes = set(n for clq in cliques_nodes for n in clq)
h = ug.subgraph(nodes)
deg = nx.degree(h)
nodes_degree = [n for n in nodes if deg[n] >= degree]
k = h.subgraph(nodes_degree)
nx.draw(k, node_size=5)
Theory that might be relevant:
Enumerating All Cycles in an Undirected Graph
Finding city blocks using the graph is surprisingly non-trivial.
Basically, this amounts to finding the smallest set of smallest rings (SSSR), which is an NP-complete problem.
A review of this problem (and related problems) can be found here.
On SO, there is one description of an algorithm to solve it here.
As far as I can tell, there is no corresponding implementation in networkx (or in python for that matter).
I tried this approach briefly and then abandoned it -- my brain is not up to scratch for that kind of work today.
That being said, I will award a bounty to anybody that might visit this page at a later date and post a tested implementation of an algorithm that finds the SSSR in python.
I have instead pursued a different approach, leveraging the fact that the graph is guaranteed to be planar.
Briefly, instead of treating this as a graph problem, we treat this as an image segmentation problem.
First, we find all connected regions in the image. We then determine the contour around each region,
transform the contours in image coordinates back to longitudes and latitudes.
Given the following imports and function definitions:
#!/usr/bin/env python
# coding: utf-8
"""
Find house blocks in osmnx graphs.
"""
import numpy as np
import osmnx as ox
import networkx as nx
import matplotlib.pyplot as plt
from matplotlib.path import Path
from matplotlib.patches import PathPatch
from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
from skimage.measure import label, find_contours, points_in_poly
from skimage.color import label2rgb
ox.config(log_console=True, use_cache=True)
def k_core(G, k):
H = nx.Graph(G, as_view=True)
H.remove_edges_from(nx.selfloop_edges(H))
core_nodes = nx.k_core(H, k)
H = H.subgraph(core_nodes)
return G.subgraph(core_nodes)
def plot2img(fig):
# remove margins
fig.subplots_adjust(left=0, bottom=0, right=1, top=1, wspace=0, hspace=0)
# convert to image
# https://stackoverflow.com/a/35362787/2912349
# https://stackoverflow.com/a/54334430/2912349
canvas = FigureCanvas(fig)
canvas.draw()
img_as_string, (width, height) = canvas.print_to_buffer()
as_rgba = np.fromstring(img_as_string, dtype='uint8').reshape((height, width, 4))
return as_rgba[:,:,:3]
Load the data. Do cache the imports, if testing this repeatedly -- otherwise your account can get banned.
Speaking from experience here.
G = ox.graph_from_address('Nørrebrogade 20, Copenhagen Municipality',
network_type='all', distance=500)
G_projected = ox.project_graph(G)
ox.save_graphml(G_projected, filename='network.graphml')
# G = ox.load_graphml('network.graphml')
Prune nodes and edges that cannot be part of a cycle.
This step is not strictly necessary but results in nicer contours.
H = k_core(G, 2)
fig1, ax1 = ox.plot_graph(H, node_size=0, edge_color='k', edge_linewidth=1)
Convert plot to image and find connected regions:
img = plot2img(fig1)
label_image = label(img > 128)
image_label_overlay = label2rgb(label_image[:,:,0], image=img[:,:,0])
fig, ax = plt.subplots(1,1)
ax.imshow(image_label_overlay)
For each labelled region, find the contour and convert the contour pixel coordinates back to data coordinates.
# using a large region here as an example;
# however we could also loop over all unique labels, i.e.
# for ii in np.unique(labels.ravel()):
ii = np.argsort(np.bincount(label_image.ravel()))[-5]
mask = (label_image[:,:,0] == ii)
contours = find_contours(mask.astype(np.float), 0.5)
# Select the largest contiguous contour
contour = sorted(contours, key=lambda x: len(x))[-1]
# display the image and plot the contour;
# this allows us to transform the contour coordinates back to the original data cordinates
fig2, ax2 = plt.subplots()
ax2.imshow(mask, interpolation='nearest', cmap='gray')
ax2.autoscale(enable=False)
ax2.step(contour.T[1], contour.T[0], linewidth=2, c='r')
plt.close(fig2)
# first column indexes rows in images, second column indexes columns;
# therefor we need to swap contour array to get xy values
contour = np.fliplr(contour)
pixel_to_data = ax2.transData + ax2.transAxes.inverted() + ax1.transAxes + ax1.transData.inverted()
transformed_contour = pixel_to_data.transform(contour)
transformed_contour_path = Path(transformed_contour, closed=True)
patch = PathPatch(transformed_contour_path, facecolor='red')
ax1.add_patch(patch)
Determine all points in the original graph that fall inside (or on) the contour.
x = G.nodes.data('x')
y = G.nodes.data('y')
xy = np.array([(x[node], y[node]) for node in G.nodes])
eps = (xy.max(axis=0) - xy.min(axis=0)).mean() / 100
is_inside = transformed_contour_path.contains_points(xy, radius=-eps)
nodes_inside_block = [node for node, flag in zip(G.nodes, is_inside) if flag]
node_size = [50 if node in nodes_inside_block else 0 for node in G.nodes]
node_color = ['r' if node in nodes_inside_block else 'k' for node in G.nodes]
fig3, ax3 = ox.plot_graph(G, node_color=node_color, node_size=node_size)
Figuring out if two blocks are neighbors is pretty easy. Just check if they share a node:
if set(nodes_inside_block_1) & set(nodes_inside_block_2): # empty set evaluates to False
print("Blocks are neighbors.")
I'm not completely sure that cycle_basis will give you the neighborhoods you seek, but if it does, it's a simple thing to get the neighborhood graph from it:
import osmnx as ox
import networkx as nx
import matplotlib.pyplot as plt
G = ox.graph_from_address('Nørrebrogade 20, Copenhagen Municipality',
network_type='all',
distance=500)
H = nx.Graph(G) # make a simple undirected graph from G
cycles = nx.cycles.cycle_basis(H) # I think a cycle basis should get all the neighborhoods, except
# we'll need to filter the cycles that are too small.
cycles = [set(cycle) for cycle in cycles if len(cycle) > 2] # Turn the lists into sets for next loop.
# We can create a new graph where the nodes are neighborhoods and two neighborhoods are connected if
# they are adjacent:
I = nx.Graph()
for i, n in enumerate(cycles):
for j, m in enumerate(cycles[i + 1:], start=i + 1):
if not n.isdisjoint(m):
I.add_edge(i, j)
I appreciate this question is a little bit old now but I have an alternative approach that is relatively straighforward - it does require stepping away from networkx for a moment though.
Creating the blocks
Get the projected graph:
import osmnx as ox
import geopandas as gpd
from shapely.ops import polygonize
G = ox.graph_from_address('Nørrebrogade 20, Copenhagen Municipality',
network_type='all',
dist=500)
G_projected = ox.project_graph(G)
Convert the graph to undirected - this removes duplicate edges that would cause the subsequent polygonization to fail:
G_undirected = G_projected.to_undirected()
Extract just the edges into a GeoPandas GeoDataFrame:
G_edges_as_gdf = ox.graph_to_gdfs(G_undirected, nodes=False, edges=True)
Use polygonize from shapely.ops on the edges to create the block faces and then use these as the geometry in a new GeoDataFrame:
block_faces = list(polygonize(G_edges_as_gdf['geometry']))
blocks = gpd.GeoDataFrame(geometry=block_faces)
Plot the result:
ax = G_edges_as_gdf.plot(figsize=(10,10), color='red', zorder=0)
blocks.plot(ax=ax, facecolor='gainsboro', edgecolor='k', linewidth=2, alpha=0.5, zorder=1)
blocks created from line fragments by shapely.ops.polygonize()
Finding neighbors
PySAL does a great job of this with its spatial weights see https://pysal.org/notebooks/lib/libpysal/weights.html for further information. libpysal can be installed from conda-forge.
Here we use Rook weights to identify blocks that share an edge as in the original question. Queen weights would also include those that only share a node (i.e. meet at a street junction)
from libpysal.weights import Rook # Queen, KNN also available
w_rook = Rook.from_dataframe(blocks)
The spatial weights are just for the neighbours so we need to append the original block (index number 18 here just as an example):
block = 18
neighbors = w_rook.neighbors[block]
neighbors.append(block)
neighbors
Then we can plot using neighbors as a filter:
ax = blocks.plot(figsize=(10,10), facecolor='gainsboro', edgecolor='black')
blocks[blocks.index.isin(neighbors)].plot(ax=ax, color='red', alpha=0.5)
neighboring blocks highlighted on top of all blocks
Notes
This doesn't resolve the sliver polygons caused by multiple road centre lines and it can be challenging to resolve ambiguities caused by pedestrianised streets and footpaths - presumably a pedestrianised street is a legitimate division between blocks but a footpath may not be.
From memory I have in the past needed to fragment the LineStrings created from the edges further before polygonizing them - this didn't seem to be necessary with this example.
I have left out the final step of linking the new geometries back to the Node IDs with some kind of spatial join etc.
I don't have a code, but I guess that once i'm on the sidewalk, if I keep turning to the right at each corner, I will cycle through the edges of my block. I don't know the libraries so I'll just talk algo here.
from your point, go north until you reach a street
turn right as much as you can and walk on the street
on the next corner, find all the steets, chose the one that makes the smallest angle with your street counting from the right.
walk on that street.
turn right, etc.
It's actually an algorithm to use to exit a maze : keep your right hand on the wall and walk. It doesn't work in case of loops in the maze, you just loop around. But it gives a solution to your problem.
This is an implementation of Hashemi Emad's idea. It works well as long as the starting position is chosen such that there exists a way to step counterclockwise in a tight circle. For some edges, in particular around the outside of the map, this is not possible. I don't have an idea how to select good starting positions, or how to filter solutions -- but maybe somebody else has one.
Working example (starting with edge (1204573687, 4555480822)):
Example, where this approach does not work (starting with edge (1286684278, 5818325197)):
Code
#!/usr/bin/env python
# coding: utf-8
"""
Find house blocks in osmnx graphs.
"""
import numpy as np
import networkx as nx
import osmnx as ox
import matplotlib.pyplot as plt; plt.ion()
from matplotlib.path import Path
from matplotlib.patches import PathPatch
ox.config(log_console=True, use_cache=True)
def k_core(G, k):
H = nx.Graph(G, as_view=True)
H.remove_edges_from(nx.selfloop_edges(H))
core_nodes = nx.k_core(H, k)
H = H.subgraph(core_nodes)
return G.subgraph(core_nodes)
def get_vector(G, n1, n2):
dx = np.diff([G.nodes.data()[n]['x'] for n in (n1, n2)])
dy = np.diff([G.nodes.data()[n]['y'] for n in (n1, n2)])
return np.array([dx, dy])
def angle_between(v1, v2):
# https://stackoverflow.com/a/31735642/2912349
ang1 = np.arctan2(*v1[::-1])
ang2 = np.arctan2(*v2[::-1])
return (ang1 - ang2) % (2 * np.pi)
def step_counterclockwise(G, edge, path):
start, stop = edge
v1 = get_vector(G, stop, start)
neighbors = set(G.neighbors(stop))
candidates = list(set(neighbors) - set([start]))
if not candidates:
raise Exception("Ran into a dead end!")
else:
angles = np.zeros_like(candidates, dtype=float)
for ii, neighbor in enumerate(candidates):
v2 = get_vector(G, stop, neighbor)
angles[ii] = angle_between(v1, v2)
next_node = candidates[np.argmin(angles)]
if next_node in path:
# next_node might not be the same as the first node in path;
# therefor, we backtrack until we end back at next_node
closed_path = [next_node]
for node in path[::-1]:
closed_path.append(node)
if node == next_node:
break
return closed_path[::-1] # reverse to have counterclockwise path
else:
path.append(next_node)
return step_counterclockwise(G, (stop, next_node), path)
def get_city_block_patch(G, boundary_nodes, *args, **kwargs):
xy = []
for node in boundary_nodes:
x = G.nodes.data()[node]['x']
y = G.nodes.data()[node]['y']
xy.append((x, y))
path = Path(xy, closed=True)
return PathPatch(path, *args, **kwargs)
if __name__ == '__main__':
# --------------------------------------------------------------------------------
# load data
# # DO CACHE RESULTS -- otherwise you can get banned for repeatedly querying the same address
# G = ox.graph_from_address('Nørrebrogade 20, Copenhagen Municipality',
# network_type='all', distance=500)
# G_projected = ox.project_graph(G)
# ox.save_graphml(G_projected, filename='network.graphml')
G = ox.load_graphml('network.graphml')
# --------------------------------------------------------------------------------
# prune nodes and edges that should/can not be part of a cycle;
# this also reduces the chance of running into a dead end when stepping counterclockwise
H = k_core(G, 2)
# --------------------------------------------------------------------------------
# pick an edge and step counterclockwise until you complete a circle
# random edge
total_edges = len(H.edges)
idx = np.random.choice(total_edges)
start, stop, _ = list(H.edges)[idx]
# good edge
# start, stop = 1204573687, 4555480822
# bad edge
# start, stop = 1286684278, 5818325197
steps = step_counterclockwise(H, (start, stop), [start, stop])
# --------------------------------------------------------------------------------
# plot
patch = get_city_block_patch(G, steps, facecolor='red', edgecolor='red', zorder=-1)
node_size = [100 if node in steps else 20 for node in G.nodes]
node_color = ['crimson' if node in steps else 'black' for node in G.nodes]
fig1, ax1 = ox.plot_graph(G, node_size=node_size, node_color=node_color, edge_color='k', edge_linewidth=1)
ax1.add_patch(patch)
fig1.savefig('city_block.png')
I am implementing Bellman ford's algorithm from GeeksForGeeks in Python. I want to generate the Graph(The Diagramatic form and not in dictionary type-which is easy) using some library like pyplot or networkx or something similar. I want the graph UI to contain the nodes,edges and the respective cost.
from collections import defaultdict
#Class to represent a graph
class Graph:
def __init__(self,vertices):
self.V= vertices #No. of vertices
self.graph = [] # default dictionary to store graph
# function to add an edge to graph
def addEdge(self,u,v,w):
self.graph.append([u, v, w])
# utility function used to print the solution
def printArr(self, dist):
print("Vertex Distance from Source")
for i in range(self.V):
print("%d \t\t %d" % (i, dist[i]))
# The main function that finds shortest distances from src to
# all other vertices using Bellman-Ford algorithm. The function
# also detects negative weight cycle
def BellmanFord(self, src):
# Step 1: Initialize distances from src to all other vertices
# as INFINITE
dist = [float("Inf")] * self.V
dist[src] = 0
# Step 2: Relax all edges |V| - 1 times. A simple shortest
# path from src to any other vertex can have at-most |V| - 1
# edges
for i in range(self.V - 1):
# Update dist value and parent index of the adjacent vertices of
# the picked vertex. Consider only those vertices which are still in
# queue
for u, v, w in self.graph:
if dist[u] != float("Inf") and dist[u] + w < dist[v]:
dist[v] = dist[u] + w
# Step 3: check for negative-weight cycles. The above step
# guarantees shortest distances if graph doesn't contain
# negative weight cycle. If we get a shorter path, then there
# is a cycle.
for u, v, w in self.graph:
if dist[u] != float("Inf") and dist[u] + w < dist[v]:
print "Graph contains negative weight cycle"
return
# print all distance
self.printArr(dist)
g = Graph(5)
g.addEdge(0, 1, -1)
g.addEdge(0, 2, 4)
g.addEdge(1, 2, 3)
g.addEdge(1, 3, 2)
g.addEdge(1, 4, 2)
g.addEdge(3, 2, 5)
g.addEdge(3, 1, 1)
g.addEdge(4, 3, -3)
The graph that I want either in terminal or in separate file is(based on above code):
The link of documentation by ekiim is highly useful. This is the code that I did for plotting graph:
import networkx as nx
import matplotlib.pyplot as plt
G=nx.DiGraph()
G.add_node(0),G.add_node(1),G.add_node(2),G.add_node(3),G.add_node(4)
G.add_edge(0, 1),G.add_edge(1, 2),G.add_edge(0, 2),G.add_edge(1, 4),G.add_edge(1, 3),G.add_edge(3, 2),G.add_edge(3,1),G.add_edge(4,3)
nx.draw(G, with_labels=True, font_weight='bold')
plt.show()
This code prints the directed graph without cost. I tried printing with cost but the output was highly distorted with costs jumbled up. Some costs were written in blank spaces while only one or two were present on the edges. Hence if someone knows to implement that it would be highly useful.
If you check this tutorial for networkx, you'll see how easy, is to create a directed graph, plus, plotting it.
Pretty much, It's the same for a directed or simple graph, (API wise), and the plotting, is also simple enough and uses Matplotlib to generate it.
You could make a Tk app, that allows you to input, manually the nodes, and Edges, and store them in ListBoxes, and plot a graph, in function of that, this won't be drag and drop, but still, it helps you visualize the graph on the fly.
and this Matplotlib tutorial, will give you the idea how to embed it in a TK app.
I have a large graph object with many nodes that I am trying to graph. Due to the large number of nodes, many are being drawn one over another. This in itself is not a problem. However, a small percentage of nodes have node attributes which dictate their colour.
Ideally I would be able to draw the graph in such a way that nodes with this property are drawn last, on top of the other nodes, so that it is possible to see their distribution across the graph.
The code I have so far used to generate the graph is shown below:
import networkx as nx
import matplotlib.pyplot as plt
import numpy as np
import os
import pickle
from pathlib import Path
def openFileAtPath(filePath):
print('Opening file at: ' + filePath)
with open(filePath, 'rb') as input:
file = pickle.load(input)
return file
# Pre manipulation path
g = openFileAtPath('../initialGraphs/wordNetadj_dictionary1.11.pkl')
# Post manipulation path
# g = openFileAtPath('../manipulatedGraphs/wordNetadj_dictionary1.11.pkl')
print('Fetching SO scores')
scores = list()
for node in g.nodes:
scores.append(g.node[node]['weight'])
print('Drawing network')
nx.draw(g,
with_labels=False,
cmap=plt.get_cmap('RdBu'),
node_color=scores,
node_size=40,
font_size=8)
plt.show()
And currently the output is as shown:
This graph object itself has taken a relatively long time to generate and is computationally intensive, so ideally I wouldn't have to remake the graph from scratch.
However, I am fairly sure that the graph is drawn in the same order that the nodes were added to the graph object. I have searched for a way of changing the order that the nodes are stored within the graph object, but given directional graphs actually have an order, my searches always end up with answers showing me how to reverse the direction of a graph.
So, is there a way to dictate the order in which nodes are drawn, or alternatively, change the order that nodes are stored inside some graph object.
Potentially worthy of a second question, but the edges are also blocked out by the large number of nodes. Is there a way to draw the edges above the nodes behind them?
Piggybacking off Paul Brodersen's answer, if you want different nodes to be in the foreground and background, I think you should do the following:
For all nodes that belong in the same layer, draw the subgraph corresponding to the nodes, and set the , as follows:
pos = {...} # some dictionary of node positions, required for the function below
H = G.subgraph(nbunch)
collection = nx.draw_networkx_nodes(H, pos)
collection.set_zorder(zorder)
Do this for every group of nodes that belong in the same level. It's tedious, but it will do the trick. Here is a toy example that I created based on looking up this question as part of my own research
import matplotlib as mpl
mpl.use('agg')
import pylab
import networkx as nx
G = nx.Graph()
G.add_path([1, 2, 3, 4])
pos = {1 : (0, 0), 2 : (0.5, 0), 3 : (1, 0), 4 : (1.5, 0)}
for node in G.nodes():
H = G.subgraph([node])
collection = nx.draw_networkx_nodes(H, pos)
collection.set_zorder(node)
pylab.plot([0, 2], [0, 0], zorder=2.5)
pylab.savefig('nodes_zorder.pdf', format='pdf')
pylab.close()
This makes a graph, and then puts the each node at a successively higher level going from left to right, so the leftmost node is farthest in the background and the rightmost node is farthest in the foreground. It then draws a straight line whose zorder is 2. As a result, it comes in front of the two left nodes, and behind the two right nodes. Here is the result.
draw is a wrapper around draw_networkx_nodes and draw_networkx_edges.
Unlike draw, the two functions return their respective artists ( PathCollection and LineCollection, IIRC). These are your standard matplotlib artists, and as as such their relative draw order can be controlled via their zorder attribute.