2

Abstract

As a prototype, we were able to output a 2-dimensional graph. The source code is noted below. I wanted to try outputting this graph in three dimensions, so I started messing around with the existing code, but after much research I was at a loss. So I decided to post a question on stackoverflow at the risk of being pointed out as a lack of research.

What I want to achieve

Currently, these are the three main things we want to achieve.

  • We do not want to change the UI of Tkenter as much as possible because we want to overwrite the links and make comparisons later.
  • Make the size of the node appear larger in proportion to the density of the links.
  • I want to read topology information from a text file and output a graph.

Source code in question

*Since it is redundant, the source code has been cut to some extent. Therefore, please understand that there may be some unnatural points.

  • Source code to output a 2-dimensional graph
import networkx as nx
from networkx.algorithms.centrality.betweenness_subset import edge_betweenness_centrality_subset
import matplotlib.pyplot as plt
import tkinter as tk
from tkinter import messagebox
import tkinter.filedialog as fd

# Input Window Settings
tki = tk.Tk()  # Tk class generation
tki.geometry('350x300')  # screen size
tki.title('link')  # screen title

# List check button labels
chk_txt = ['Normal network visualization']
chk_bln = {}  #Check box ON/OFF status

# Dynamically create and place check buttons
for i in range(len(chk_txt)):
    chk_bln[i] = tk.BooleanVar()
    chk = tk.Checkbutton(tki,
                         variable=chk_bln[i],
                         text=chk_txt[i]) 
    chk.place(x=50,
              y=30 + (i * 24))

path = fd.askopenfilename()

# Command to exit
def close_window():
    tki.destroy()

# button generation
btn3 = tk.Button(tki,
                 text = '  decision  ')
btn3.place(x = 100 , y = 200)

btn4 = tk.Button(tki,
                 text = 'exit',
                 command = close_window)
btn4.place(x = 150 , y = 250)

# Graph Window Settings

# Load a list of edges
G = nx.read_edgelist(path,
                     nodetype=int)
edge_size = nx.number_of_edges(G) # number of edges
node_size = nx.number_of_nodes(G) # number of nodes

# Figure Layout Determination 
pos = nx.spring_layout(G, k=0.8)
degs = dict(G.degree)

# Figure Creation
plt.figure(figsize=(15, 15))
plt.title("graph",
          fontsize = 30,
          fontweight = 'bold')

# nodes
# Determine nodes size
average_deg = sum(d for n,d in G.degree()) / G.number_of_nodes()  #Calculate the average number of hours for the entire network
sizes = [300 * deg * 2 / average_deg for node,deg in G.degree()]  #Sized to be proportional to node order

# Drawing Nodes and Labels
nx.draw_networkx_nodes(G,
                       pos,
                       node_color = 'w',
                       edgecolors = "k",
                       alpha = 0.75,
                       node_size = sizes,
                       linewidths = 2)
nx.draw_networkx_labels(G,
                        pos,
                        font_size = 10)

# Reading topology information (.txt)
with open(path, "r") as tf:
    line = tf.read().split()
ran = int(len(line) / 2)

# Graph Output
# Button click event (set to checked or unchecked)
def btn_click(bln):
    for i in range(len(chk_bln)):
        chk_bln[i].set(bln)

# How to draw for each event
def while_1(event):
    if(chk_bln[0].get() == True):
        #Normal link drawing
        nx.draw_networkx_edges(G,
                               pos,
                               edge_color = 'c')
    plt.show()
    
#Bind function to button
btn3.bind('<Button-1>',while_1)

  • Source code kneaded to output a 3D a graph
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import networkx as nx
import numpy as np
import tkinter as tk
from tkinter import messagebox
import tkinter.filedialog as fd

# Input Window Settings
#omission

# Generate edge data
with open(path, "r") as tf:
    line = tf.read().split()
ran = int(len(line) / 2)

# Create graphs from generated edge data
G = nx.read_edgelist(path,
                     nodetype=int)
edge_size = nx.number_of_edges(G) # number of edges
node_size = nx.number_of_nodes(G) # number of nodes

# spring_layout algorithm to generate 3D coordinates
pos = nx.spring_layout(G, k = 0.8, dim = 3)
degs = dict(G.degree)

# Conversion from dictionary type to array type
pos_ary = np.array([pos[n] for n in G])

# visualization
fig = plt.figure()
ax = fig.add_subplot(121, projection="3d")

#Set graph title
ax.set_title("3D graph",size=20)

# Dot the position of each node
ax.scatter(
    pos_ary[:, 0],
    pos_ary[:, 1],
    pos_ary[:, 2],
    s=50,
)

# Display labels on nodes
for n in G.nodes:
    ax.text(*pos[n], n)

# Edge display
for e in G.edges:
    node0_pos = pos[e[0]]
    node1_pos = pos[e[1]]
    xx = [node0_pos[0], node1_pos[0]]
    yy = [node0_pos[1], node1_pos[1]]
    zz = [node0_pos[2], node1_pos[2]]
    ax.plot(xx, yy, zz, c="#aaaaaa")

# Graph Output
# Button click event (set to checked or unchecked)
def btn_click(bln):
    for i in range(len(chk_bln)):
        chk_bln[i].set(bln)

# How to draw for each event
def while_1(event):
    if(chk_bln[0].get() == True):
        #Normal link drawing
        nx.draw_networkx_edges(G,
                               pos#_ary[:,2],
                               #edge_color = 'c'
                               )
    #View the resulting diagram
    plt.show()

#Bind function to button
btn3.bind('<Button-1>',while_1)

Problem/error message being encountered

Exception in Tkinter callback
Traceback (most recent call last):
  File "C:\Program Files\Python36\lib\tkinter\__init__.py", line 1705, in __call__
    return self.func(*args)
  File "C:/Program Files/Python36/3D_test2.py", line 106, in while_1
    ,edge_color = 'c'
  File "C:\Users\████\AppData\Roaming\Python\Python36\site-packages\networkx\drawing\nx_pylab.py", line 684, in draw_networkx_edges
    alpha=alpha,
  File "C:\Users\████\AppData\Roaming\Python\Python36\site-packages\matplotlib\collections.py", line 1393, in __init__
    self.set_segments(segments)
  File "C:\Users\████\AppData\Roaming\Python\Python36\site-packages\matplotlib\collections.py", line 1408, in set_segments
    self._paths = [mpath.Path(_seg) for _seg in _segments]
  File "C:\Users\████\AppData\Roaming\Python\Python36\site-packages\matplotlib\collections.py", line 1408, in <listcomp>
    self._paths = [mpath.Path(_seg) for _seg in _segments]
  File "C:\Users\████\AppData\Roaming\Python\Python36\site-packages\matplotlib\path.py", line 132, in __init__
    cbook._check_shape((None, 2), vertices=vertices)
  File "C:\Users\████\AppData\Roaming\Python\Python36\site-packages\matplotlib\cbook\__init__.py", line 2304, in _check_shape
    f"{k!r} must be {len(target_shape)}D "
ValueError: 'vertices' must be 2D with shape (M, 2). Your input has shape (2, 3).
  • Topology Information(.txt)
1 10
1 11
1 14
1 20
2 9
2 12
2 13
2 15
2 16
3 10
3 11
3 20
4 5
4 8
4 9
5 4
5 9
6 7
6 14
7 6
7 18
8 4
8 11
8 14
8 19
9 2
9 4
9 5
10 1
10 3
11 1
11 3
11 8
11 13
12 2
12 16
13 2
13 11
14 1
14 6
14 8
15 2
15 17
15 21
16 2
16 12
17 15
18 7
18 21
19 8
19 20
20 1
20 3
20 19
20 21
21 15
21 18
21 20

supplementary information

python 3.6.8

networkx 2.5.1

matplotlib 3.3.4

What we tried

If you do not enter anything in the checkbox, the output will be done. enter image description here The "edge_color = 'c'" on line 106 is commented out because the same error as above occurred. When "pos_ary[n]" was used, the following error statement was output.

>>> Exception in Tkinter callback
Traceback (most recent call last):
  File "C:\Program Files\Python36\lib\tkinter\__init__.py", line 1705, in __call__
    return self.func(*args)
  File "C:\Program Files\Python36\3D_test2.py", line 105, in while_1
    pos_ary[n]
IndexError: index 21 is out of bounds for axis 0 with size 21

In line 105, when "pos[n]" was used, another error message was generated. This is the same as "pos_ary[n-1]".。

>>> Exception in Tkinter callback
Traceback (most recent call last):
  File "C:\Program Files\Python36\lib\tkinter\__init__.py", line 1705, in __call__
    return self.func(*args)
  File "C:\Program Files\Python36\3D_test2.py", 
line 105, in while_1
    pos[n]#_ary[:,2],
  File "C:\Users\████\AppData\Roaming\Python\Python36\site-packages\networkx\drawing\nx_pylab.py", line 656, in draw_networkx_edges
    edge_pos = np.asarray([(pos[e[0]], pos[e[1]]) for e in edgelist])
  File "C:\Users\████\AppData\Roaming\Python\Python36\site-packages\networkx\drawing\nx_pylab.py", line 656, in <listcomp>
    edge_pos = np.asarray([(pos[e[0]], pos[e[1]]) for e in edgelist])
IndexError: index 10 is out of bounds for axis 0 with size 3
2
  • i don't know if this will work, but maybe consider passing your 3d axes object to networkx.draw_graph_edges. if that fails, you may have to add your own 3d fancy arrows between your node locations. Commented Nov 9, 2022 at 1:40
  • 1
    Would you consider alternatives to matplotlib? I think it is way easier to render a 3d-graph with plotly Commented Nov 9, 2022 at 9:43

1 Answer 1

3

If I understand your question correctly, the checkbox "Normal network visualization" controls whether the edges are plotted or not. Is that right? If that's what you want to do, you could use the edge drawing procedure from your code when the checkbox is on:

def while_1(event):
    if(chk_bln[0].get() == True):
        for e in G.edges:
            node0_pos = pos[e[0]]
            node1_pos = pos[e[1]]
            xx = [node0_pos[0], node1_pos[0]]
            yy = [node0_pos[1], node1_pos[1]]
            zz = [node0_pos[2], node1_pos[2]]
            ax.plot(xx, yy, zz, c="#aaaaaa")

To change the size of your nodes based on the degree of the nodes, use ax.scatter and change the size of the markers based on the degree of the node: [ax.scatter(pos_ary[i, 0],pos_ary[i, 1],pos_ary[i, 2],s=sizes[i],color='b',alpha=0.5) for i in range(len(pos_ary))]

Below is the full code. The UI stays the same and it's still possible to choose your file from the GUI:

import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import networkx as nx
import numpy as np
import tkinter as tk
from tkinter import messagebox
import tkinter.filedialog as fd

# Input Window Settings
tki = tk.Tk()  # Tk class generation
tki.geometry('350x300')  # screen size
tki.title('link')  # screen title
# List check button labels
chk_txt = ['Normal network visualization']
chk_bln = {}  #Check box ON/OFF status

# Dynamically create and place check buttons
for i in range(len(chk_txt)):
    chk_bln[i] = tk.BooleanVar()
    chk = tk.Checkbutton(tki,variable=chk_bln[i],text=chk_txt[i]) 
    chk.place(x=50,y=30 + (i * 24))
    
path = fd.askopenfilename()
# Command to exit
def close_window():
    tki.destroy()

# button generation
btn3 = tk.Button(tki,text = '  decision  ')
btn3.place(x = 100 , y = 200)
btn4 = tk.Button(tki,text = 'exit',command = close_window)
btn4.place(x = 150 , y = 250)

# Graph Window Settings
# Generate edge data
with open(path, "r") as tf:
    line = tf.read().split()
ran = int(len(line) / 2)

# Create graphs from generated edge data
G = nx.read_edgelist(path,nodetype=int)
edge_size = nx.number_of_edges(G) # number of edges
node_size = nx.number_of_nodes(G) # number of nodes

# spring_layout algorithm to generate 3D coordinates
pos = nx.spring_layout(G, k = 0.8, dim = 3)
degs = dict(G.degree)
average_deg = sum(d for n,d in G.degree()) / G.number_of_nodes()  #Calculate the average number of hours for the entire network
sizes = [100 * deg * 2 / average_deg for node,deg in G.degree()]  #Sized to be proportional to node order
# Conversion from dictionary type to array type
pos_ary = np.array([pos[n] for n in G])

# visualization
fig = plt.figure()
ax = fig.add_subplot(121, projection="3d")

#Set graph title
ax.set_title("3D graph",size=20)

# Dot the position of each node
[ax.scatter(pos_ary[i, 0],pos_ary[i, 1],pos_ary[i, 2],s=sizes[i],color='b',alpha=0.5) for i in range(len(pos_ary))]

# Display labels on nodes
for n in G.nodes:
    ax.text(*pos[n], n)

# Graph Output
# Button click event (set to checked or unchecked)
def btn_click(bln):
    for i in range(len(chk_bln)):
        chk_bln[i].set(bln)

# How to draw for each event
def while_1(event):
    if(chk_bln[0].get() == True):
        for e in G.edges:
            node0_pos = pos[e[0]]
            node1_pos = pos[e[1]]
            xx = [node0_pos[0], node1_pos[0]]
            yy = [node0_pos[1], node1_pos[1]]
            zz = [node0_pos[2], node1_pos[2]]
            ax.plot(xx, yy, zz, c="k",lw=2)
    #View the resulting diagram
    plt.show()

#Bind function to button
btn3.bind('<Button-1>',while_1)
tk.mainloop()

Here is the result without the "Normal network visualization" checkmark on:

enter image description here

And with the "Normal network visualization" checkmark on:

enter image description here

Sign up to request clarification or add additional context in comments.

1 Comment

Exactly the conclusion I was looking for! ``` [ax.scatter(pos_ary[i, 0],pos_ary[i, 1],pos_ary[i, 2],s=sizes[i],color='b',alpha=0.5) for i in range(len(pos_ary))] ``` This was very helpful.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.