Pythonでネットワーク構造を可視化：pyvisの基本から応用まで

# **user**:

次のような実装でネットワークを描画しています。これについて、いくつか質問させてください。            

```python
net = Network(height="600px", width="100%", directed=True)

for node in G.nodes:
    color = "orange" if node in target_cols else "blue"
    net.add_node(node, label=node, color=color)
    for source, target, dat in G.edges(data=True):
        weight = dat.get("weight", 0)
        color = "gray" if weight > 0 else "red"
        thickness = abs(weight)  # 推定値の絶対値で線の強さを表現
        net.add_edge(
            source,
            target,
            value=thickness,
            label=f"{weight:.3f}",
            title=f"重み: {weight:.3f}",
            color=color,
            # font={"size": 8, "vadjust": -10},
        )

net.show_buttons(filter_=["physics"])
```

# **assistant**:

どうぞ、質問をお聞かせください。その前に、実装について簡単に補足しておくと：

* `pyvis.network.Network` を使ってネットワークを描画しています。
* ノードの色は `target_cols` に含まれているかで分けられています（オレンジと青）。
* エッジの色は重みが正なら灰色、負なら赤。
* 線の太さ（`value`）は重みの絶対値。
* エッジラベルとツールチップ（`title`）に重みの数値を表示。
* 物理シミュレーション（レイアウト調整）のボタンも表示。

さて、どんな点について質問されたいですか？

# **user**:

物理シミュレーション（レイアウト調整）を、あらかじめ自分好みの値にセットしておきたいのですが、できますか？

# **assistant**:

はい、可能です。`pyvis` の `Network` では、物理シミュレーション（force-directed layout）に関する設定をあらかじめ指定できます。これは `net.set_options()` を使って JSON 形式で渡すことで実現します。

以下は、物理設定（たとえば、バネの強さや反発力など）をカスタマイズする一例です：

```python
physics_options = """
var options = {
  "physics": {
    "enabled": true,
    "barnesHut": {
      "gravitationalConstant": -20000,
      "centralGravity": 0.3,
      "springLength": 100,
      "springConstant": 0.04,
      "damping": 0.09,
      "avoidOverlap": 1
    },
    "minVelocity": 0.75
  }
}
"""
net.set_options(physics_options)
```

`barnesHut` の部分はレイアウトアルゴリズムの種類で、他にも `forceAtlas2Based` などが選べます。

この設定を `net.show_buttons()` の前に入れておくと、ボタンを表示しつつ初期設定も適用されます。

---

必要に応じて、他のパラメータ（ノードの質量、安定化ステップなど）についても解説できますが、まずはどのようなレイアウトを目指しているか教えてもらえますか？

# **user**:

ありがとうございます。このようなエラーになったのですが、解決していただけますか？     

```
net.show_buttons(filter_=["physics"])
File "/Users/ikemen/streamlit-app/venv/lib/python3.10/site-packages/pyvis/network.py", line 959, in show_buttons
self.options.configure = Configure(enabled=True, filter_=filter_)
AttributeError: 'dict' object has no attribute 'configure'
```

# **assistant**:

ありがとうございます。このエラーは、`net.set_options()` で物理設定などのオプションをJSON形式の文字列として渡したあとに `net.show_buttons()` を呼ぶと発生する典型的なケースです。

### 原因

`pyvis` の内部で `self.options` はもともと `Options` オブジェクトとして管理されているのに、`set_options()` によってそれが **JSON文字列に上書きされてしまい**、後続の `show_buttons()` が `self.options.configure` にアクセスできずにエラーを起こしています。

---

### 解決策（2通りあります）

#### ✅ **方法1：`show_buttons()` を `set_options()` より先に呼ぶ**

```python
net.show_buttons(filter_=["physics"])

physics_options = """
var options = {
  "physics": {
    "enabled": true,
    "barnesHut": {
      "gravitationalConstant": -20000,
      "centralGravity": 0.3,
      "springLength": 100,
      "springConstant": 0.04,
      "damping": 0.09,
      "avoidOverlap": 1
    },
    "minVelocity": 0.75
  }
}
"""
net.set_options(physics_options)
```

#### ✅ **方法2：`show_buttons()` を使わずに、`set_options()` にボタンの設定を含める**

下記のように `"configure"` を `options` に含めて、物理ボタンを最初から表示させる：

```python
physics_options = """
var options = {
  "configure": {
    "enabled": true,
    "filter": ["physics"]
  },
  "physics": {
    "enabled": true,
    "barnesHut": {
      "gravitationalConstant": -20000,
      "centralGravity": 0.3,
      "springLength": 100,
      "springConstant": 0.04,
      "damping": 0.09,
      "avoidOverlap": 1
    },
    "minVelocity": 0.75
  }
}
"""
net.set_options(physics_options)
```

この方法なら `net.show_buttons()` は不要です。

---

どちらの方法でも解決しますが、設定の一元管理ができる **方法2** のほうがシンプルでおすすめです。

どちらの方法で進めたいですか？

# **user**:

ありがとうございます。「方法１」では何も出力されなくなっていしまいましたが、「方法２」で解決しました。ところで、「solver」には barnesHut, forceAtlas2Based, repulsion, hierarchicalRepulsion の４種類があるようですが、それぞれどのような手法で、どのような特徴の描画になるか、詳しく教えていただけませんか？

# **assistant**:

素晴らしい質問です。`pyvis`（= [Vis.js](https://visjs.github.io/vis-network/docs/network/physics.html) の Python ラッパー）の `physics.solver` は、ノードのレイアウトを自動で調整する「力学的配置（force-directed layout）」アルゴリズムの設定で、以下の4種類が指定可能です。

---

## 🔍 各 solver の詳細と特徴

### 1. `barnesHut`

* **方式**：N-body シミュレーションの高速化アルゴリズム。大規模なグラフで各ノード間の力（引力・斥力）を近似的に計算する。
* **特徴**：

  * ノード数が多くても比較的高速。
  * クラスタ構造が見やすくまとまる。
  * デフォルト設定。
* **向いているグラフ**：中〜大規模で自然なクラスタが存在するネットワーク。

### 2. `forceAtlas2Based`

* **方式**：Gephi の ForceAtlas2 アルゴリズムをベースにしたもの（斥力が距離に依存）。
* **特徴**：

  * クラスタをコンパクトにまとめるのが得意。
  * 距離が近いノードがより強く反発し、重なりを回避。
  * 計算負荷は `barnesHut` より高いが、レイアウトはより自然。
* **向いているグラフ**：クラスタ間の距離や局所構造を強調したいとき。

### 3. `repulsion`

* **方式**：全ノード間の斥力を正確に計算（近似なし）。バネのような引力と斥力モデル。
* **特徴**：

  * 精密な物理モデル。
  * 小規模なグラフでは非常に美しい結果。
  * 計算負荷が非常に高い（特にノード数が多いと遅い）。
* **向いているグラフ**：小規模で対称性を重視した可視化。

### 4. `hierarchicalRepulsion`

* **方式**：階層的レイアウトをベースにして、さらにノードの重なりを回避する斥力を加えたもの。
* **特徴**：

  * 縦方向に階層構造（DAG、因果構造など）を整列。
  * グラフの方向性が明示的。
  * `layout.hierarchical = true` と併用する。
* **向いているグラフ**：因果関係、ツリー構造、階層的な依存関係があるグラフ。

---

## ✅ 使い分けの目安まとめ

| solver                  | 計算速度 | ノード数に強い | 自然なクラスタ | 重なり回避 | 階層表現 | 代表的用途            |
| ----------------------- | ---- | ------- | ------- | ----- | ---- | ---------------- |
| `barnesHut`             | ◎    | ◎       | ○       | △     | ×    | デフォルト。大規模クラスタ可視化 |
| `forceAtlas2Based`      | ○    | ○       | ◎       | ○     | ×    | 局所クラスタ強調         |
| `repulsion`             | △    | ×       | ◎       | ◎     | ×    | 小規模で美しい可視化       |
| `hierarchicalRepulsion` | ○    | △       | △       | ○     | ◎    | ツリー構造や因果関係の図解    |

---

必要であれば、各 solver の具体的なパラメータ例もご紹介できます。特定の構造を強調したいネットワークがあれば、それに合った solver とチューニング方法もご提案できます。どんな描画を目指していますか？

# **user**:

ありがとうございます。hierarchicalRepulsionを利用する場合のphysics_optionsの書き方について、教えていただけますか？

# **assistant**:

こちらこそ、詳しいご質問ありがとうございます！

`hierarchicalRepulsion` を利用する場合は、**`physics.solver` に "hierarchicalRepulsion"` を指定**し、さらに **`layout.hierarchical` を有効化（`enabled: true\`）** する必要があります。

---

## ✅ `hierarchicalRepulsion` を使うための `physics_options` 例

```python
physics_options = """
var options = {
  "configure": {
    "enabled": true,
    "filter": ["physics"]
  },
  "layout": {
    "hierarchical": {
      "enabled": true,
      "direction": "UD",  // 上下（"UD"）, 左右（"LR"）などが選べる
      "sortMethod": "hubsize"  // "hubsize" または "directed"
    }
  },
  "physics": {
    "enabled": true,
    "solver": "hierarchicalRepulsion",
    "hierarchicalRepulsion": {
      "centralGravity": 0.0,
      "springLength": 100,
      "springConstant": 0.01,
      "nodeDistance": 120,
      "damping": 0.09
    },
    "minVelocity": 0.75,
    "stabilization": {
      "enabled": true,
      "iterations": 200
    }
  }
}
"""
net.set_options(physics_options)
```

---

### 🔧 調整可能な主なパラメータ

| パラメータ名                           | 説明                                            |
| -------------------------------- | --------------------------------------------- |
| `layout.hierarchical.enabled`    | 階層レイアウトの有効化                                   |
| `layout.hierarchical.direction`  | `"UD"`（上→下）、`"DU"`、`"LR"`（左→右）、`"RL"` など      |
| `layout.hierarchical.sortMethod` | `"hubsize"`：ハブノードを上に配置／`"directed"`：エッジ方向に基づく |
| `springLength`                   | ノード間のバネの長さ（広がり）                               |
| `nodeDistance`                   | 同一レベルのノードの間隔                                  |
| `damping`                        | 動きの減衰率（0〜1）                                   |
| `centralGravity`                 | 中心への引力。階層レイアウトでは通常0にする                        |
| `stabilization.iterations`       | 安定化の繰り返し回数（多いほど落ち着いたレイアウト）                    |

---

### 💡補足

このレイアウトは、**因果グラフやツリー構造**（親→子）の可視化にとても適しています。`springLength` や `nodeDistance` を調整することで、図の密度をコントロールできます。

---

必要であれば、ノードやエッジの方向も制御できます。描画したいグラフが DAG（有向非巡回グラフ）であれば、この solver は特に効果的です。

方向性付きのグラフですか？それとも単純なネットワーク可視化でしょうか？

# **user**:

ありがとうございます。こんなエラーになるのですが、解決法は分かりますでしょうか？ 

```
Traceback (most recent call last):
File "/Users/ikemen/streamlit-app/venv/lib/python3.10/site-packages/streamlit/runtime/scriptrunner/exec_code.py", line 121, in exec_func_with_error_handling
result = func()
File "/Users/ikemen/streamlit-app/venv/lib/python3.10/site-packages/streamlit/runtime/scriptrunner/script_runner.py", line 639, in code_to_exec
_mpa_v1(self._main_script_path)
File "/Users/ikemen/streamlit-app/venv/lib/python3.10/site-packages/streamlit/runtime/scriptrunner/script_runner.py", line 159, in _mpa_v1
page.run()
File "/Users/ikemen/streamlit-app/venv/lib/python3.10/site-packages/streamlit/navigation/page.py", line 297, in run
exec(code, module.**dict**)
File "/Users/ikemen/streamlit-app/pages/bayesian_network.py", line 342, in <module>
net.set_options(physics_options)
File "/Users/ikemen/streamlit-app/venv/lib/python3.10/site-packages/pyvis/network.py", line 1006, in set_options
self.options = self.options.set(options)
File "/Users/ikemen/streamlit-app/venv/lib/python3.10/site-packages/pyvis/options.py", line 224, in set
options = json.loads(options)
File "/Users/ikemen/.pyenv/versions/3.10.0/lib/python3.10/json/**init**.py", line 346, in loads
return _default_decoder.decode(s)
File "/Users/ikemen/.pyenv/versions/3.10.0/lib/python3.10/json/decoder.py", line 337, in decode
obj, end = self.raw_decode(s, idx=_w(s, 0).end())
File "/Users/ikemen/.pyenv/versions/3.10.0/lib/python3.10/json/decoder.py", line 353, in raw_decode
obj, end = self.scan_once(s, idx)
json.decoder.JSONDecodeError: Expecting property name enclosed in double quotes: line 1 column 110 (char 109)
```

# **assistant**:

ありがとうございます。エラーの原因は次の通りです：

---

## ❗️エラーの内容

```
json.decoder.JSONDecodeError: Expecting property name enclosed in double quotes
```

つまり：

> Python の `json.loads()` は **JavaScript のような `// コメント` や シングルクォート、末尾カンマ などを許容せず、完全な JSON 構文しか受け付けない** ということです。

---

## ✅ 解決策

あなたの `physics_options` に次のような「JavaScript的な記法」が含まれている可能性があります：

```js
"direction": "UD",  // 上下（"UD"）, 左右（"LR"）などが選べる
```

これは **JSON として不正（コメントは禁止）** なので、修正が必要です。

---

## ✨修正済みの `physics_options`（完全な JSON 形式）

以下のように、コメントや末尾カンマを削除してください：

```python
physics_options = """
{
  "configure": {
    "enabled": true,
    "filter": ["physics"]
  },
  "layout": {
    "hierarchical": {
      "enabled": true,
      "direction": "UD",
      "sortMethod": "hubsize"
    }
  },
  "physics": {
    "enabled": true,
    "solver": "hierarchicalRepulsion",
    "hierarchicalRepulsion": {
      "centralGravity": 0.0,
      "springLength": 100,
      "springConstant": 0.01,
      "nodeDistance": 120,
      "damping": 0.09
    },
    "minVelocity": 0.75,
    "stabilization": {
      "enabled": true,
      "iterations": 200
    }
  }
}
"""
net.set_options(physics_options)
```

---

### ✅ 注意点まとめ

| NGな記述例                | 説明               |
| --------------------- | ---------------- |
| `// コメント`             | JSONではコメントは禁止    |
| `'文字列'`               | JSONでは必ず `"` を使用 |
| 末尾カンマ（例：`"a": 1,` の後） | 最後の要素にはカンマを付けない  |

---

修正後、`net.set_options()` は問題なく動作するはずです。もう一度実行してみてください。

必要であれば、JSON検証ツール（[https://jsonlint.com](https://jsonlint.com)など）でも構文チェックが可能です。再実行して動作確認してみますか？

# **user**:

問題なく動作しました。でも、このレイアウトだと、横一列にしか並んでなくて制約がきついですね。

# **assistant**:

そうなんです、ご指摘のとおりです！

`hierarchicalRepulsion` ＋ `layout.hierarchical.enabled: true` を使うと、**ノードがきっちり階層構造（ツリー状）に整列する**一方で、

* **柔軟性がなく**
* **レイアウトが一方向に制限され**
* **自然なクラスタリングが犠牲になりやすい**

という制約があります。

---

## 🛠 柔軟なレイアウトにするには？

### ✅ 方法1：`layout.hierarchical.enabled` を `false` にして `solver: "forceAtlas2Based"` などに切り替える

```python
physics_options = """
{
  "configure": {
    "enabled": true,
    "filter": ["physics"]
  },
  "layout": {
    "hierarchical": {
      "enabled": false
    }
  },
  "physics": {
    "enabled": true,
    "solver": "forceAtlas2Based",
    "forceAtlas2Based": {
      "springLength": 100,
      "springConstant": 0.01,
      "damping": 0.8,
      "avoidOverlap": 1
    },
    "minVelocity": 0.75,
    "stabilization": {
      "enabled": true,
      "iterations": 250
    }
  }
}
"""
net.set_options(physics_options)
```

* `forceAtlas2Based` は、**構造は尊重しつつ、多少ランダム性のある自然な分布**になります。
* ノード同士が近すぎる問題には、`avoidOverlap` で対処できます。

---

### ✅ 方法2：`barnesHut` にしてクラスタ構造を保ちつつ自然に広げる

```json
"solver": "barnesHut"
```

これは Vis.js のデフォルトで、**中規模ネットワークで非常に安定**します。

---

## 🧭 まとめ：レイアウトの方向性をどうしたいか？

| レイアウトの目的             | おすすめの solver                 | layout.hierarchical |
| -------------------- | ---------------------------- | ------------------- |
| 因果関係・ツリー構造を明示したい     | hierarchicalRepulsion        | true                |
| ノードのつながりやクラスタを自然に見たい | forceAtlas2Based / barnesHut | false               |
| 小規模で綺麗な配置にしたい        | repulsion                    | false               |

---

もし、「一部階層性を保ちつつ、全体は自由に配置したい」などの要望があれば、ノードごとに座標を固定（`fixed=True`）する方法もご紹介できます。

どんな描画を目指しているか、もう少し詳しく教えていただけますか？それに合った構成を一緒に考えましょう。

# **user**:

ありがとうございます。springLengthなどのパラメータについて、どのようなパラメーターなのか、描画にどのような影響を与えるのか、詳しく教えていただけますか？

# **assistant**:

はい、`springLength` を含む物理シミュレーションのパラメータについて、以下に詳しく解説します。これらのパラメータは、**ノード間の距離感や動き方**を制御しており、**ネットワークの描画結果に大きな影響**を与えます。

---

## 🎯 主な物理パラメータの意味と描画への影響

| パラメータ名                     | 説明                           | 描画への影響                                           |
| -------------------------- | ---------------------------- | ------------------------------------------------ |
| `springLength`             | **エッジ（バネ）の自然長**。ノード間の理想的な距離。 | 値が大きいほどノード同士が「遠く」離れるように配置され、ネットワークが広がる。          |
| `springConstant`           | バネの強さ。距離が自然長とズレたときの「戻す力」。    | 値が大きいほどノード間の結合が強くなり、密集した構造になりやすい。小さいとゆるやかに配置される。 |
| `nodeDistance`             | 階層レイアウトで、同一階層にいるノード間の距離。     | 値が大きいと水平方向に広がり、低いと詰まる。`springLength`とセットで調整される。  |
| `centralGravity`           | ノードが中心に引っ張られる力（通常0〜1）。       | 値が大きいほど、全体が中心に集まってきて円形にまとまりやすくなる。                |
| `damping`                  | 動きの「減衰」率（摩擦）。0〜1。            | 高いほど動きが速く止まり、低いとゆっくりと動き続ける。描画が落ち着くまでの速度に影響。      |
| `avoidOverlap`             | ノードが重ならないようにするための係数。         | `1` 以上で有効。値が大きいほど重なりを避けて広がるが、描画に時間がかかる。          |
| `minVelocity`              | 安定したと判断する速度のしきい値。            | 小さいほど描画が長くかかり、レイアウトが滑らかになる。                      |
| `stabilization.iterations` | 安定化のための反復回数。                 | 値を上げるとより整ったレイアウトになるが、描画が遅くなる。                    |

---

## 🔍 例：`springLength` を変えて比較した場合

| springLength | 見た目の特徴                   |
| ------------ | ------------------------ |
| 50           | ノードが密集、グラフが詰まった印象        |
| 100          | 標準的な広がり                  |
| 200          | ノードが互いに強く離れ、ネットワーク全体が広がる |

---

## 🧪 チューニングのコツ

* `springLength` は **100〜200** 程度で調整すると自然なレイアウトにしやすいです。
* `springConstant` は **0.01〜0.1** 程度が多く、強くするとバネが固くなり、弱くすると全体がフワッと広がります。
* `centralGravity` を **0.2〜0.5** にすることで、**クラスタ同士が中心に寄るような自然なレイアウト**が得られます。

---

## 🎨 組み合わせ例

```json
"physics": {
  "solver": "forceAtlas2Based",
  "forceAtlas2Based": {
    "springLength": 150,
    "springConstant": 0.03,
    "avoidOverlap": 1
  },
  "centralGravity": 0.3,
  "damping": 0.8,
  "minVelocity": 0.75,
  "stabilization": {
    "enabled": true,
    "iterations": 200
  }
}
```

---

もし、可視化したいネットワークの規模（ノード数・つながりの数）や、「密集感を出したい」・「なるべく広げたい」といった要望があれば、それに合わせた推奨値もご案内できますよ。

どんな見た目を目指していますか？

# **user**:

物理パラメータ以外のパラメータってあるんでしょうか？

# **assistant**:

はい、あります！
**物理パラメータ以外にも、pyvis（＝裏で使っている [vis.js](https://visjs.org/)）では多くの描画パラメータが存在します。**

これらは主に次の4カテゴリに分類できます：

---

## 🔹 1. ノード（nodes）に関するパラメータ

ノードの見た目・ラベル・形状など：

```json
"nodes": {
  "shape": "dot",
  "size": 25,
  "font": {
    "size": 14,
    "color": "#000000"
  },
  "color": {
    "background": "#97C2FC",
    "border": "#2B7CE9",
    "highlight": {
      "background": "#D2E5FF",
      "border": "#2B7CE9"
    }
  }
}
```

| パラメータ名  | 内容                                          |
| ------- | ------------------------------------------- |
| `shape` | `"dot"`, `"box"`, `"ellipse"`, `"image"` など |
| `size`  | ノードのサイズ（数値）                                 |
| `color` | 背景・枠線・ハイライトの色                               |
| `font`  | ラベルのフォントサイズ・色など                             |

---

## 🔹 2. エッジ（edges）に関するパラメータ

矢印の形、色、太さ、ラベルの位置など：

```json
"edges": {
  "arrows": {
    "to": {
      "enabled": true,
      "type": "arrow"
    }
  },
  "color": {
    "color": "gray",
    "highlight": "red"
  },
  "font": {
    "size": 12,
    "align": "horizontal"
  },
  "smooth": {
    "enabled": true,
    "type": "dynamic"
  }
}
```

| パラメータ名   | 内容                               |
| -------- | -------------------------------- |
| `arrows` | 矢印の有無と方向（`to`, `from`, `middle`） |
| `color`  | 通常・ハイライト時の線の色                    |
| `smooth` | 線をなめらかに描くか（直線 or 曲線）             |
| `font`   | エッジラベルの見た目                       |

---

## 🔹 3. レイアウト（layout）設定

```json
"layout": {
  "randomSeed": 42,
  "improvedLayout": true
}
```

| パラメータ名           | 内容                        |
| ---------------- | ------------------------- |
| `randomSeed`     | レイアウトを固定（同じ表示に）           |
| `improvedLayout` | 配置最適化（初期の見た目が良くなる）        |
| `hierarchical`   | `enabled: true` で階層レイアウトに |

---

## 🔹 4. インタラクション（interaction）

ユーザーの操作性に関する設定：

```json
"interaction": {
  "hover": true,
  "tooltipDelay": 200,
  "navigationButtons": true,
  "keyboard": true
}
```

| パラメータ名              | 内容             |
| ------------------- | -------------- |
| `hover`             | ホバー時のハイライトを有効化 |
| `navigationButtons` | 拡大・縮小などのボタンを表示 |
| `keyboard`          | キーボード操作の有効化    |

---

## ✅ オプション全体の構造イメージ（スケルトン）

```json
{
  "nodes": { ... },
  "edges": { ... },
  "layout": { ... },
  "physics": { ... },
  "interaction": { ... },
  "configure": { ... }
}
```

---

## 📘 補足：オプションの確認方法

* [vis.js Network Options ドキュメント](https://visjs.github.io/vis-network/docs/network/)
* `pyvis` の `.set_options()` に JSON 文字列で渡せばOK
* JSON構文エラーには注意（ダブルクォート `" "` 必須、コメント不可）

---

ご希望の見た目に応じて、必要なパラメータだけ抜粋して組み合わせるのがコツです。

「こんな見た目にしたい」「ノードにアイコンや画像を使いたい」「色分けしたい」など、もし具体的な希望があれば、それに合わせてパラメータセットを提案できます！

# **user**:

ありがとうございます。描かれた図をファイルとしてアウトプットするにはどうすれば良いですか？

# **assistant**:

どういたしまして！
**描画したネットワークをファイルとして保存する方法**は、`pyvis` では簡単に行えます。以下のように、**HTMLファイルとして保存し、それをブラウザで開く or Streamlitに埋め込む**のが一般的です。

---

## ✅ 基本：HTMLファイルとして保存する方法

```python
net.save_graph("network.html")
```

このコードを実行すると、現在のネットワークの可視化が `network.html` というファイルとして保存されます。ダブルクリックすればブラウザで開けます。

---

## 🧪 Streamlitで埋め込み表示しつつ保存したい場合

Streamlitと組み合わせるときは、一時的に保存して `components.html()` で表示できます：

```python
import streamlit as st
from pyvis.network import Network
import streamlit.components.v1 as components

# ネットワーク生成
net = Network(height="600px", width="100%", directed=True)
# ノード・エッジ追加など...

# 一時HTMLに保存
path = "/tmp/network.html"
net.save_graph(path)

# Streamlit上で表示
with open(path, "r", encoding="utf-8") as f:
    html_content = f.read()
components.html(html_content, height=600, scrolling=True)
```

---

## 🗂️ 保存先や形式について

| 方法                             | 内容                                                      |
| ------------------------------ | ------------------------------------------------------- |
| `.save_graph("filename.html")` | HTMLファイルとして保存（JavaScript込みで独立実行可）                       |
| `.write_html()`                | 上と同じ（内部的には `.save_graph()` と同じ仕組み）                      |
| PNG, SVG, PDF など               | **pyvis では未サポート**（画像化には Selenium 等でスクリーンショットを取る必要があります） |

---

## 📷 補足：画像（PNGなど）として保存したい場合

これは `pyvis` だけでは直接できませんが、次のような方法があります：

### 方法1：手動でブラウザ上からスクリーンショット

→ `save_graph()` で表示 → ブラウザで開いて保存。

### 方法2：Seleniumなどで自動スクリーンショット

→ ブラウザを自動操作してPNG出力（少し準備が必要）。

ご希望があれば、自動スクリーンショットのコード例もご案内します。

---

どの形式で保存したいとお考えですか？HTMLだけで十分か、それとも画像ファイル化も必要でしょうか？

# **user**:

　ありがとうございます。インタラクティブな図をパワーポイントに貼り付ける、ようなことはできないんですよね？

# **assistant**:

とても良い質問です。
結論からいうと：

---

## ❌ **そのままでは**インタラクティブな図をPowerPointに「貼り付ける」ことはできません

PowerPointはHTMLやJavaScriptを実行できないため、
`pyvis`（＝vis.jsベース）のインタラクティブな図は**直接埋め込むことができません**。

---

## ✅ 可能な代替方法（以下のような工夫が必要です）

### 方法1：**インタラクティブな図へのリンクをPowerPointに貼る**

1. `net.save_graph("network.html")` で保存
2. HTMLファイルをクラウド（例：OneDrive, Dropbox, 自前サーバー）にアップ
3. PowerPointに「こちらをクリック」などのリンクを設置
   → クリックでブラウザが開き、インタラクティブに閲覧可能

---

### 方法2：**動画やGIFで代替**

1. ブラウザ上で図を動かす操作を画面収録（例：OBS, QuickTime, PowerPoint自体）
2. MP4またはGIFとして保存
3. PowerPointに**動画として貼り付け**

> 🎥 PowerPointでは動画やGIFの埋め込み・再生は可能です。

---

### 方法3：**静止画（PNG/SVG）で貼り付ける**

1. `network.html` を開いて、適切なタイミングでスクリーンショットを取得
2. PowerPointに画像として貼り付け

---

## 📌 補足：PowerPoint Live（Web版）との組み合わせ

* **PowerPoint Live + Webリンク**を使うと、プレゼン中にインタラクティブリンクを開いて説明もできます。
* ただし会場や参加者のネット環境に依存します。

---

## ✅ おすすめは？

| 目的                      | 方法                 |
| ----------------------- | ------------------ |
| 研究発表や教育用途で図を操作しながら説明したい | 方法1（リンク）or 方法2（動画） |
| 印刷・配布資料にしたい             | 方法3（画像）            |

---

動画・リンクの貼り方など、必要であればPowerPointの操作手順もお手伝いできます！
どの方法を試してみたいですか？

# **user**:

ありがとうございます。では、Streamlit上で、ボタンを押すとインタラクティブなHTMLがダウンロードされるようなコードにしてください。

# **assistant**:

こちらこそありがとうございます！
Streamlit上で「ボタンを押すと `pyvis` のインタラクティブなHTMLをダウンロード」できるようにするには、以下のようなコードで実現できます。

---

## ✅ サンプルコード：Streamlitでダウンロードボタン付き `pyvis` ネットワーク表示

```python
import streamlit as st
from pyvis.network import Network
import streamlit.components.v1 as components
import tempfile
import base64
import os

# グラフを構築
net = Network(height="600px", width="100%", directed=True)
net.add_node("A", label="A", color="orange")
net.add_node("B", label="B", color="blue")
net.add_edge("A", "B", value=2, title="A→B")

# 一時HTMLファイルに保存
with tempfile.NamedTemporaryFile(delete=False, suffix=".html") as tmp_file:
    path = tmp_file.name
    net.save_graph(path)

# 表示：Streamlit上で埋め込み
with open(path, "r", encoding="utf-8") as f:
    html_content = f.read()
components.html(html_content, height=600, scrolling=True)

# ダウンロードボタンを表示
with open(path, "rb") as f:
    btn = st.download_button(
        label="インタラクティブなHTMLをダウンロード",
        data=f,
        file_name="interactive_network.html",
        mime="text/html"
    )

# 後始末：ファイル削除（任意、再実行で自動消えることも多い）
os.unlink(path)
```

---

## 💡 ポイント解説

* `tempfile.NamedTemporaryFile()` を使って一時ファイルとして保存
* `st.download_button()` でHTMLファイルをダウンロード可能に
* `components.html()` でその場に埋め込み表示
* `os.unlink()` でファイルを削除（任意）

---

## 🔄 応用

* `net` の内容を動的に生成したい場合は、Streamlitのフォームやセレクタと連携可能です。
* ファイル名を動的に変えたい場合は、`file_name="graph_{datetime.now():%Y%m%d}.html"` などもOK。

---

ご希望に応じて、ネットワークの構造やUIの調整もお手伝いできます！
このコードはご希望の動作に合っていそうですか？

# **user**:

ありがとうございます。次のようなエラーが出てるのですが、解決法わかりますか？ 

```
Traceback (most recent call last):
File "/Users/ikemen/streamlit-app/venv/lib/python3.10/site-packages/streamlit/runtime/scriptrunner/exec_code.py", line 121, in exec_func_with_error_handling
result = func()
File "/Users/ikemen/streamlit-app/venv/lib/python3.10/site-packages/streamlit/runtime/scriptrunner/script_runner.py", line 639, in code_to_exec
_mpa_v1(self._main_script_path)
File "/Users/ikemen/streamlit-app/venv/lib/python3.10/site-packages/streamlit/runtime/scriptrunner/script_runner.py", line 159, in _mpa_v1
page.run()
File "/Users/ikemen/streamlit-app/venv/lib/python3.10/site-packages/streamlit/navigation/page.py", line 297, in run
exec(code, module.**dict**)
File "/Users/ikemen/streamlit-app/pages/bayesian_network.py", line 404, in <module>
net.add_edge(
File "/Users/ikemen/streamlit-app/venv/lib/python3.10/site-packages/pyvis/network.py", line 372, in add_edge
assert to in self.get_nodes(),&#x20;
AssertionError: non existent node 'X1'
```

# **assistant**:

このエラーの原因は以下の通りです：

---

### ❗️エラー内容

```
AssertionError: non existent node 'X1'
```

これは、`pyvis.Network.add_edge(from, to)` を実行したときに、
**指定されたノード（この場合 `'X1'`）がまだネットワークに追加されていない**ために起こるエラーです。

---

### ✅ 解決方法：`add_node()` で先にノードを登録しておく

たとえば以下のような順番になっていませんか？

```python
net.add_edge("A", "X1")  # ← X1 が未登録の状態でエッジ追加 → エラー
net.add_node("X1")
```

これを以下のように **ノードを先に追加するように修正**すれば解決します：

```python
net.add_node("X1")
net.add_edge("A", "X1")
```

---

### 💡 応用的な修正例（ループで処理する場合）

```python
# すべてのノードを最初に登録
for node in G.nodes:
    net.add_node(node)

# その後にエッジを登録
for source, target, dat in G.edges(data=True):
    net.add_edge(source, target)
```

もしくは、ノードが存在しなければ自動で追加するようにしてもよいですが、
`pyvis` はそれを内部で自動化してくれないため、**必ず明示的に追加が必要です**。

---

### ✅ まとめ

* `add_edge()` より前に、必ず `add_node()` で両端のノードを追加する
* 複雑な構造の場合、**ノード追加 → エッジ追加**の2段階で書くと安全です

---

どのようなネットワーク構造を使っているかにもよりますが、
上記で修正できない場合は、ノードの一覧（`G.nodes`）と、エッジの追加コードを教えていただければ、さらに具体的にサポートできます。

# **user**:

ありがとうございました。今日はこれで大丈夫です。今日の会話をブログに書き残したいのですが、タイトル案をいくつか提案していただけますか？

# **assistant**:

もちろんです！以下は、今日の会話内容（`pyvis` と `Streamlit` を使ったネットワーク描画、レイアウト調整、HTMLダウンロードなど）に基づいたブログタイトル案です：

---

### ✨ シンプル＆技術寄りなタイトル案

1. **「pyvis × Streamlitで作るインタラクティブなネットワーク描画」**
2. **「Pythonでネットワーク構造を可視化：pyvisの基本から応用まで」**
3. **「Streamlitとpyvisで動くグラフ：レイアウト調整とダウンロード機能付き」**
4. **「pyvisで始めるグラフ可視化：重み・色・ラベル・HTML出力まで」**
5. **「Streamlitアプリに組み込むpyvisネットワーク：使いこなしガイド」**

---

### 📝 ブログらしい温かみのあるタイトル案

6. **「ネットワーク描画で迷子になったけど、ChatGPTに助けてもらった話」**
7. **「Streamlit × pyvisで図が動いた！エラー解決とレイアウト調整の記録」**
8. **「インタラクティブなグラフをHTML出力してみた：pyvis活用日記」**
9. **「グラフの重みが見える！pyvisとStreamlitで可視化に挑戦」**
10. **「Pythonでネットワークを描いてみたら、思ったより奥が深かった」**

---

ご希望のトーン（技術寄り・初心者向け・日記風など）があれば、それに合わせて絞り込みや追加提案もできます！どれか気になるタイトルはありますか？

# **user**:

「Pythonでネットワーク構造を可視化：pyvisの基本から応用まで」でいきたいと思います。最後に、今回の会話のまとめを詳しく書いてください。

# **assistant**:

了解しました。それでは、ブログ記事「**Pythonでネットワーク構造を可視化：pyvisの基本から応用まで**」のために、今回の会話の内容を以下のように詳しくまとめました：

---

## 🧠 会話のまとめ：「Pythonでネットワーク構造を可視化：pyvisの基本から応用まで」

### ✅ 使用ライブラリ

* **pyvis**：ネットワーク構造をインタラクティブに可視化できるPythonライブラリ
* **Streamlit**：Python製のWebアプリを素早く作成できるライブラリ

---

### 🟦 1. 基本的なネットワーク描画

`pyvis.Network()` を使い、ノードとエッジを追加して、ネットワーク構造を描画。

```python
net = Network(height="600px", width="100%", directed=True)

# ノード追加（色分け対応）
for node in G.nodes:
    color = "orange" if node in target_cols else "blue"
    net.add_node(node, label=node, color=color)

# エッジ追加（重み・色・ラベル付き）
for source, target, dat in G.edges(data=True):
    weight = dat.get("weight", 0)
    color = "gray" if weight > 0 else "red"
    net.add_edge(source, target, label=f"{weight:.3f}", value=abs(weight), color=color)
```

---

### 🛠 2. 物理シミュレーションとレイアウトの調整

ノードの位置関係を自動で調整する**物理シミュレーション**の制御には、`set_options()` を使用。

#### 例：`hierarchicalRepulsion` を使った垂直レイアウト

```python
net.set_options("""
var options = {
  layout: {
    hierarchical: {
      direction: "UD",
      sortMethod: "hubsize"
    }
  },
  physics: {
    hierarchicalRepulsion: {
      nodeDistance: 150,
      springLength: 200,
      damping: 0.4
    },
    solver: "hierarchicalRepulsion"
  }
}
""")
```

* **springLength**：ノード間の“ばね”の長さ（長いと間隔が広がる）
* **nodeDistance**：基本的なノード間距離の制御
* **damping**：運動の収束具合（高いほど落ち着きやすい）

---

### ⚠️ よくあるエラーと解決法

* **`AttributeError: 'dict' object has no attribute 'configure'`**
  → `show_buttons()` は `set_options()` を使うと競合することがある。代わりにカスタムオプションを明示的に設定。

* **`AssertionError: non existent node`**
  → `add_edge()` で使うノードは、**必ず事前に `add_node()` で追加しておく**こと。

---

### 💾 HTMLファイル出力＆Streamlitでのダウンロード対応

#### ファイルに保存：

```python
net.save_graph("network.html")
```

#### Streamlit 上でダウンロードボタン付き：

```python
with open("network.html", "rb") as f:
    html_bytes = f.read()

st.download_button("📥 HTMLをダウンロード", data=html_bytes, file_name="network.html", mime="text/html")
```

---

### 🔍 補足：インタラクティブな図はPowerPointに直接貼れない？

* `pyvis` で生成されるのは HTML + JavaScript のインタラクティブなビジュアライゼーション
* PowerPoint に貼る場合は、**静的なスクリーンショットや SVG/PNG に変換**する必要あり
* ただし、Streamlit上からHTMLをダウンロード → ブラウザで開いて操作、という形で活用可能

---

## ✍️ 最後に

pyvis は手軽にグラフ構造を可視化できる一方で、レイアウト調整やパラメータ設定の柔軟性も高く、学習コストに対して非常に表現力のあるライブラリです。今回の会話では、基本からトラブルシューティング、Streamlit統合やエクスポート方法まで幅広くカバーできました。

---

このまとめをベースに、ブログ記事本文の構成や導入・締めのライティングもお手伝いできますので、必要があればお知らせくださいね！

# **user**:

ありがとうございました。また今度、よろしくお願いしますね！

# **assistant**:

こちらこそ、ありがとうございました！またいつでも気軽にご相談くださいね。開発や執筆、引き続き応援しています📘💻
次回もお待ちしています！