

### 前提

試作として2次元のグラフを出力することができました。ソースコードを記しておきます。  
このグラフを3次元で出力してみたいと考え、既存のコードをいじくりまわしていましたが、いろいろ調査してみましたが手詰まりとなってしまいました。  
そこで調査不足と指摘されることを覚悟の上でstackoverflowに質問を投稿するに至りました。

### 実現したいこと

現在、主に実現したいことはこれら3つです。

  * 後にリンクを上書きして比較を行いたいため、TkenterのUIはできる限り変更したくない。
  * リンクの密集具合に比例してノードの大きさをより大きく見せる。
  * テキストファイルのトポロジー情報を読み込んでグラフを出力したい。

### 該当のソースコード

※冗長になるため、ある程度ソースコードを削っています。そのため、どこかしら不自然な点があるかもしれませんがご了承ください。

  * 2次元のグラフを出力するソースコード

[code]

    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
    
    # 入力ウインドウの設定
    tki = tk.Tk()  # Tkクラス生成
    tki.geometry('350x300')  # 画面サイズ
    tki.title('2次元リンク')  # 画面タイトル
    
    # チェックボタンのラベルをリスト化する
    chk_txt = ['通常ネットワークの可視化']
    chk_bln = {}  #チェックボックスON/OFFの状態
    
    # チェックボタンを動的に作成して配置
    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()
    
    # 終了するためのコマンド
    def close_window():
        tki.destroy()
    
    # ボタン作成
    btn3 = tk.Button(tki,
                     text = '　　　　決定　　　　')
    btn3.place(x = 100 , y = 200)
    
    btn4 = tk.Button(tki,
                     text = '終了',
                     command = close_window)
    btn4.place(x = 150 , y = 250)
    
    # グラフウインドウの設定
    # タイトルで漢字が使えるようフォントを設定
    plt.rcParams['font.family'] = 'Meiryo'
    
    # エッジのリストを読み込む
    G = nx.read_edgelist(path,
                         nodetype=int)
    edge_size = nx.number_of_edges(G) # リンク数
    node_size = nx.number_of_nodes(G) # ノード数
    
    # 図のレイアウト決定 
    pos = nx.spring_layout(G, k=0.8)
    degs = dict(G.degree)
    
    # 図の作成
    plt.figure(figsize=(15, 15))
    plt.title("グラフ",
              fontsize = 30,
              fontweight = 'bold')
    
    # ノード
    # ノードの大きさを決定
    average_deg = sum(d for n,d in G.degree()) / G.number_of_nodes()  #ネットワーク全体の時数の平均値を計算
    sizes = [300 * deg * 2 / average_deg for node,deg in G.degree()]  #ノードの次数に比例するようにサイズを設定
    
    # ノード・ラベルの描画
    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)
    
    # トポロジー情報(.txt)の読み込み
    with open(path, "r") as tf:
        line = tf.read().split()
    ran = int(len(line) / 2)
    
    # グラフの出力
    # ボタンクリックイベント(チェック有無をセット)
    def btn_click(bln):
        for i in range(len(chk_bln)):
            chk_bln[i].set(bln)
    
    # 各イベントごとの描画方法
    def while_1(event):
        if(chk_bln[0].get() == True):
            #通常リンク描画
            nx.draw_networkx_edges(G,
                                   pos,
                                   edge_color = 'c')
        plt.show()
        
    #ボタンに関数をbind
    btn3.bind('<Button-1>',while_1)
    
[/code]

  * 3次元のグラフを出力するためにこねくり回したソースコード

[code]

    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
    
    # 入力ウインドウの設定
    #省略
    
    #タイトルで漢字が使えるようフォントを設定
    plt.rcParams['font.family'] = 'Meiryo'
    
    # エッジデータを生成
    with open(path, "r") as tf:
        line = tf.read().split()
    ran = int(len(line) / 2)
    
    # 生成したエッジデータからグラフ作成
    G = nx.read_edgelist(path,
                         nodetype=int)
    edge_size = nx.number_of_edges(G) # リンク数
    node_size = nx.number_of_nodes(G) # ノード数
    
    # spring_layout アルゴリズムで、3次元の座標を生成する
    pos = nx.spring_layout(G, k = 0.8, dim = 3)
    degs = dict(G.degree)
    
    # 辞書型から配列型に変換
    pos_ary = np.array([pos[n] for n in G])
    
    # ここから可視化
    fig = plt.figure()
    ax = fig.add_subplot(121, projection="3d")
    
    #グラフタイトルを設定
    ax.set_title("3Dでグラフを可視化",size=20)
    
    # 各ノードの位置に点を打つ
    ax.scatter(
        pos_ary[:, 0],
        pos_ary[:, 1],
        pos_ary[:, 2],
        s=50,
    )
    
    # ノードにラベルを表示する
    for n in G.nodes:
        ax.text(*pos[n], n)
    
    # エッジの表示
    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")
    
    # 8_グラフの出力
    # 8.1_ボタンクリックイベント(チェック有無をセット)
    def btn_click(bln):
        for i in range(len(chk_bln)):
            chk_bln[i].set(bln)
    
    # 8.2_各イベントごとの描画方法
    def while_1(event):
        if(chk_bln[0].get() == True):
            #通常リンク描画
            nx.draw_networkx_edges(G,
                                   pos#_ary[:,2],
                                   #edge_color = 'c'
                                   )
        #出来上がった図を表示
        plt.show()
    
    #ボタンに関数をbind
    btn3.bind('<Button-1>',while_1)
    
[/code]

### 発生している問題・エラーメッセージ

※個人情報があるため、その部分は伏せさせていただきます。

[code]

    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).
    
[/code]

  * トポロジー情報

[code]

    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
    
[/code]

### 試したこと

チェックボックスに何も入力しなければ出力は行われます。  
[![ノード数は20個](https://i.stack.imgur.com/mSxwI.png)](https://i.stack.imgur.com/mSxwI.png)  
106行目の「edge_color = 'c'」をコメントアウトしていますが、これは上記と同様のエラーが発生したためです。  
「pos_ary[n]」と表記した場合は以下のようなエラー文が出力されました。

[code]

    >>> 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
    
[/code]

105行目において「pos[n]」と表記したところ、これまた別のエラー文が出ました。これは「pos_ary[n-1]」と表記したものと同様です。

[code]

    >>> 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
    
[/code]

### 補足情報

python 3.6.8  
networkx 2.5.1  
matplotlib 3.3.4


CC BY-SA 4.0
2022-11-08T01:26:47.177
92074
2022-11-15T01:37:23.960
2022-11-15T01:37:23.960
55268
55268
question
python python3 matplotlib networkx
NetworkxとMatplotlibを用いて3次元のグラフを出力したい


自身が質問していたstackoverflowの英語サイトに回答が付きましたので、共有させていただきます。[I want to output a 3D
graph using Networkx and
Matplotlib](https://stackoverflow.com/questions/74368905/i-want-to-
output-a-3d-graph-using-networkx-and-
matplotlib?noredirect=1#comment131296593_74368905)

# ソースコード

[code]

    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()
    
[/code]

# 出力結果

  * チェックボックスにチェックを入れない場合  
[![リンクなし](https://i.stack.imgur.com/CgJAl.png)](https://i.stack.imgur.com/CgJAl.png)

  * チェックボックスにチェックを入れた場合  
[![リンクあり](https://i.stack.imgur.com/0UeYr.png)](https://i.stack.imgur.com/0UeYr.png)


CC BY-SA 4.0
2022-11-11T05:03:40.677
92148
2022-11-11T05:03:40.677
55268
92074
answer