I am trying to create an interactive plot using netgraph and networkx.
I would like the plot to allow movement of the nodes and as the nodes move, the edges and edge_labels will also dynamically update.
Moving the nodes was addressed by the author of netgraph here . Now when I make a simpler plot and try to label an edge, the label stays static, and is sometimes not even on an edge.
It seems like handling edge_positions similar to node_positions on the last two lines should at least address the label not moving. Why the label isn't anchored to a particular edge is still puzzling. Does anyone know if the desired effect is achievable?
Here is a snip before moving anything:
Here is a snip after moving the bottom-right node to the top left:
Here is my current code:
import matplotlib.pyplot as plt
import networkx as nx
import netgraph # pip install netgraph
#Graph creation:
G=nx.Graph(type="")
for i in range(6):
G.add_node(i,shape="o")
#Changing shape for two nodes
G.nodes[1]['shape'] = "v"
G.nodes[5]['shape'] = "v"
#Add edges
G.add_edge(1,2)
G.add_edge(4,5)
G.add_edge(0,4)
G.add_edge(2,3)
G.add_edge(2,4)
labs={(1,2):"1 to 2"}
nx.draw_networkx_edge_labels(G, pos=nx.spring_layout(G),edge_labels=labs)
#Get node shapes
node_shapes = nx.get_node_attributes(G,"shape")
# Create an interactive plot.
# NOTE: you must retain a reference to the object instance!
# Otherwise the whole thing will be garbage collected after the initial draw
# and you won't be able to move the plot elements around.
pos = nx.layout.spring_layout(G)
######## drag nodes around #########
# To access the new node positions:
plot_instance = netgraph.InteractiveGraph(G, node_shape=node_shapes, node_positions=pos, edge_positions=pos)
node_positions = plot_instance.node_positions
edge_positions = plot_instance.edge_positions
To have it as formal answer, you need to add to the InteractiveGraph object the information that you want to draw (and move) the edge labels, i.e. the following
netgraph.InteractiveGraph(G,
node_shape=node_shapes,
node_positions=pos,
edge_positions=pos,
edge_labels=labs)
(With emphasis on the last parameter)
As you already, noticed then you don't need the call of nx.draw_networkx_edge_labels.
Related
I want to visualize a graph in Pyvis which its nodes has labels. I am completely able to visualize it in Pyvis but my problem is about the ways of visualizing it. The graph displayed in Pyvis is not clear and edges are messed up. Is there any way to visualize the graph more clear?
The image below shows the graph.
For example in the graph, node 15 is displayed well. I want other nodes to be displayed in a clear way that the connections can be displayed more clearly
Update:
This is the code i use for drawing graph using Pyvis:
def showGraph(FileName, labelList):
Txtfile = open("./results.txt")
G = nx.read_weighted_edgelist(Txtfile)
Txtfile.close()
palette = (sns.color_palette("Pastel1", n_colors=len(set(labelList.values()))))
palette = palette.as_hex()
colorDict = {}
counter = 0
for i in palette:
colorDict[counter] = i
counter += 1
N = Network(height='100%', width='100%', directed=False, notebook=False)
for n in G.nodes:
N.add_node(n, color=(colorDict[labelList[n]]), size=5)
for e in G.edges.data():
N.add_edge(e[0], e[1], title=str(e[2]), value=e[2]['weight'])
N.show('result.html')
results.txt is my edge list file and labelList holds label of each node. Labels are numerical. For example label of node 48 is 5, it can be anything. I use labels to give different colors to nodes.
The NetworkX circular layouts tend to make individual nodes and the connections between them easier to see, so you could try that as long as you don't want nodes to move (without dragging) after you've drawn them.
Before creating your pyvis network, run the following on your NetworkX graph to create a dictionary that will be keyed by node and have (x, y) positions as values. You might need to mess around with the scale parameter a bit to see what works best for you.
pos = nx.circular_layout(G, scale = 1000)
You can then add x and y values from pos to your pyvis network when you add each node. Adding physics = False keeps the nodes in one place unless you click and drag them around.
for n in G.nodes:
N.add_node(n,
color=(colorDict[labelList[n]]),
size=5,
x = pos[n][0],
y = pos[n][1],
physics = False)
I'm not sure how the edge weights will play into things, so you should probably also add physics = False to the add_edge parameters to ensure that nothing will move.
Since I didn't have your original data, I just generated a random graph with 10 nodes and this was the result in pyvis.
I have gotten sensor location data from Highway England. I want to add these sensor locations to OSM multidigraph. How to do that?
import numpy as np
import pandas as pd
import networkx as nx
from shapely.geometry import Point, Polygon, LineString
import geopandas as gpd
import osmnx as ox
Graph data is
graph = ox.graph.graph_from_bbox(52.2, 51.85, -.6, -0.9, network_type='drive', simplify=False)
I want to add sensor = Point(-0.6116768, 51.8508765) on the edge nearest to it. Nearest edges to this sensor is n_edge = osmnx.distance.nearest_edges(graph, -0.6116768, 51.8508765, return_dist=False). Now, I need to bend this n_edge such that it passes through the given sensor point.
I found a way to solve this issue by creating a new node in graph, graph.add_node('sensor25', y= 51.8508765, x= -0.6116768, street_count = 2) then graph.add_edges_from([(n_edge[0], 'sensor25'), ('sensor25', n_edge[1)]). However, the node created by me (sensor25) is not identical to other nodes. How to make this node similar to existing nodes?
I have went through following questions
add attribute to node
add new node to existing edge in networkx
add random nodes on edges manually.
I'm not 100% certain what you need, what I understand: You want to add new edges with attributes: speed_limit, length, street number one way, copied from the edge you delete?
I assume that some of these attributes can be copied 1:1, like one way, while others will have to be recalulated. For simplicity, let's assume we have a function d(a, b) that takes (graph) nodes a and b, extracts their position, and calculates the air distance between them. Define other functions as required.
Then you could e.g. define the new edge like this:
# Get from/to id of closest edge
f, t = osmnx.distance.nearest_edges(graph, -0.6116768, 51.8508765, return_dist=False)[0]
c = 'sensor25' # Id of new node, c as in 'center'
edge_attrs = g[f][t] # Copy edge attributes
g.remove_edge(f, t) # Remove edge from graph
graph.add_node(c, y= 51.8508765, x= -0.6116768, street_count = 2)
# Add new edges, recalculating atttributes as required
g.add_edge(f, c, **{**edge_attrs, 'length': d(f, c)})
g.add_edge(c, t, **{**edge_attrs, 'length': d(c, t)})
Hope the syntax is clear, otherwise ask. It copies edge_attrs 1:1, except for attributes you specify after, like lenght. Probably you will have to define multiple functions like d, that also calculate the geometry etc.
The code isn't tested.
I want to visualise some points on a graph, the points move along the link, but they are not nodes. Currently I have added some point location, but can not display them on the figure.
This is the code
# -- coding: utf-8 --
import networkx as nx
import matplotlib.pyplot as plt
import itertools
import math
#from mesa.space import NetworkGrid
#from mesa import Agent, Model
#from mesa.time import RandomActivation
#from mesa.datacollection import DataCollector
#from mesa.space import NetworkGrid
#%%Build a graph
G=nx.Graph()
G.add_node("GPs")
G.add_node("AcuteCares")
G.add_node("Waitlists")
G.add_node("newPatients")
G.add_node("Preventabledeaths")
G.add_node("ReviewPatients")
G.add_node("DeathPools")
G.add_node("DNAPool1s")
G.add_node("DNAPool2s")
G.add_node("UntreatedPopulations")
G.add_node("SAPops")
labeldict = {}
labeldict["GPs"] = "GP"
labeldict["AcuteCares"] = "Acute Care"
labeldict["Waitlists"] = "Waitlist"
labeldict["newPatients"] = "New Patients"
labeldict["Preventabledeaths"] = "Preventable Deaths"
labeldict["ReviewPatients"] = "Review Patients"
labeldict["DeathPools"] = "Natural Deaths"
labeldict["DNAPool1s"] = "First DNA"
labeldict["DNAPool2s"] = "Second DNA"
labeldict["UntreatedPopulations"] = "Untreated Population"
labeldict["SAPops"] = "General Population"
G.node["Preventabledeaths"]['pos']=(0,6)
G.node["ReviewPatients"]['pos']=(-3,5)
G.node["UntreatedPopulations"]['pos']=(3,5)
G.node["DNAPool2s"]['pos']=(-3,3)
G.node["Waitlists"]['pos']=(3,3)
G.node["AcuteCares"]['pos']=(-5,0)
G.node["DNAPool1s"]['pos']=(5,0)
G.node["GPs"]['pos']=(-3,-5)
G.node["DeathPools"]['pos']=(3,-5)
G.node["SAPops"]['pos']=(-3,-3)
G.node["newPatients"]['pos']=(3,-3)
edges=itertools.permutations(G.nodes(),2)
G.add_edges_from(edges)
pos=nx.get_node_attributes(G,'pos')
nx.draw(G,pos,labels=labeldict, with_labels = True)
plt.show()
#grid = NetworkGrid(G)
# %%
def arclen(edge):
"""
calculate the length of an edge. The format of edge is like: ('UntreatedPopulations', 'SAPops')
"""
dist_edge = math.sqrt((G.node[edge[0]]['pos'][0] - G.node[edge[1]]['pos'][0])**2 + (G.node[edge[0]]['pos'][1] - G.node[edge[1]]['pos'][1])**2)
return dist_edge
def patientcor(speed,step,edge):
"""get the coordinate of point along the edge, speed is the moving speed per step,
time is the number of steps, edge is the specific edge
"""
x=G.node[edge[0]]['pos'][0] + speed*step/arclen(edge) *(G.node[edge[1]]['pos'][0] -G.node[edge[0]]['pos'][0])
y=G.node[edge[0]]['pos'][1] + speed*step/arclen(edge) *(G.node[edge[1]]['pos'][1] -G.node[edge[0]]['pos'][1])
return (x,y)
#%% Visualise the graph, set the speed at 0.2, time is 0,1,2
edge=("SAPops","GPs")
for t in range(3):
pos[t]=patientcor(0.2, t,edge) #add the location of point on the link per step to the dict
nx.draw(G,pos, labels=labeldict,with_labels = True) #visualise pos dict along with the graph, but the additional points other than nodes do not appear on the figure
plt.show()
The graph figure only displays the nodes, but not the points that move along the edges:
The nx.draw command will only plot those nodes that are in the graph. If your dictionary pos provides locations of other points, it will silently ignore them. I believe this is the appropriate behavior and I can think of lots of times where my coding would be much more difficult if it would also plot other points that appeared in my pos dictionary.
For what you want, simply create a new list of the points you want to plot (or in your example it looks like just a single point). Then use matplotlib's scatter command.
#stuff skipped here
edge=("SAPops","GPs")
for t in range(3):
mypoint = patientcor(0.2, t,edge)
nx.draw(G,pos, labels=labeldict,with_labels = True) #visualise pos dict along with the graph, but the additional points other than nodes do not appear on the figure
plt.scatter([mypoint[0]], [mypoint[1]])
plt.show()
You'll probably want to play with the node sizes and specific locations of these points.
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.
I wanna draw something like this :
The closest thing to this I could find was NetworkX Edge Colormap:
http://networkx.github.io/documentation/latest/examples/drawing/edge_colormap.html
and here is the source code:
#!/usr/bin/env python
"""
Draw a graph with matplotlib, color edges.
You must have matplotlib>=87.7 for this to work.
"""
__author__ = """Aric Hagberg (hagberg#lanl.gov)"""
try:
import matplotlib.pyplot as plt
except:
raise
import networkx as nx
G=nx.star_graph(20)
pos=nx.spring_layout(G)
colors=range(20)
nx.draw(G,pos,node_color='#A0CBE2',edge_color=colors,width=4,edge_cmap=plt.cm.Blues,with_labels=False)
plt.savefig("edge_colormap.png") # save as png
plt.show() # display
After playing around with their source code, I can't figure out how to hardcode distance of the edge circles from the centre. Right now its random.
Also how do I label the edge circles and their distance from the centre?
I know for position comes from pos=nx.spring_layout(G). So I looked at the spring_layout attribute and found that position can be specified by using a pos variable which is a dictionary with nodes as keys and values as a list. (https://networkx.github.io/documentation/latest/reference/generated/networkx.drawing.layout.spring_layout.html)
But even when I do the following result is random edges :
ap = {'uniwide':[55,34,1],'eduram':[34],'uniwide_webauth':[20,55,39],'uniwide_guest':[55,34],'tele9751_lab':[100],'HomeSDN':[100],'TP-LINK':[39]}
pos=nx.spring_layout(G,pos=ap)
You can set the node positions explicitly with the pos dictionary.
For example
import networkx as nx
import matplotlib.pyplot as plt
G = nx.Graph()
G.add_edge('center',1)
G.add_edge('center',2)
G.add_edge('center',3)
G.add_edge('center',4)
pos = {'center':(0,0),
1:(1,0),
2:(0,1),
3:(-1,0),
4:(0,-1)
}
nx.draw(G, pos=pos, with_labels=True)
plt.show()
I'm trying to be as helpful as I can. I wouldn't try to keep them static. You'll want to add and remove things, and the algorithm's automatic placement is something you don't want to lose. According to the docs, you should probably tweak k. It looks like n is 20, so multiply k times some factor to increase the distance.
n = 20
nx.spring_layout(G, k=(1.0/pow(n, .5))) # what it currently is
should maybe be this:
nx.spring_layout(G, k=(1.0/pow(n, .5))*1.5) # play around with this factor