Streamlit×Cytoscape.jsの話

# Streamlit の Component とは

Streamlit には Component と呼ばれる
「インタラクティブなウィジェットや、サードパーティの JavaScript ライブラリなどを簡単に統合するための仕組み」があります。

Component のギャラリーは https://streamlit.io/components にあります。

その Component の中でもっとも Star がついているのは Pygwalker (pandas の DataFrame を Tableau のような操作感でインタラクティブに探索・可視化することを実現してくれるもの)

<iframe width="560" height="315" src="https://www.youtube.com/embed/rprn79wfB9E?si=Szlqidw5GIVo4ZSF" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

ですが、この記事では Cytoscape.js というネットワーク可視化用 JavaScript ライブラリを統合している Streamlit Component についてとりとめのない話をします。

# Cytoscape.js を用いる Streamlit Component

Cytoscape.js とは？なんですが
「百聞は一見にしかず」で https://js.cytoscape.org/ にずらっとならんでいる Demos のような可視化を実現してくれる JavaScript ライブラリです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7591/38e60ab0-a6c5-3a6c-d95a-e9204b976980.png)

この Cytoscape.js を「Streamlit の中で (js ではなく) python を書くことで活用したい」と思う人は、私以外にもいるわけで、
https://streamlit.io/components?category=graphs
には すでに下記の 2 つの Streamlit Component があります。

- https://github.com/andfanilo/streamlit-cytoscapejs
- https://github.com/vivien000/st-cytoscape

何はともあれ vivien000 さんの st-cytoscape を試してみましょう。

st-cytoscape のインストールは下記の pip コマンドで行えます。
```
pip install st-cytoscape
```

st-cytoscape を試してみるための python script は下記です。
下記のコードを app.py という名前のファイルで保存してください。

```python
import streamlit as st
from st_cytoscape import cytoscape

elements = [
    {"data": {"id": "X"}, "selected": True, "selectable": False},
    {"data": {"id": "Y"}},
    {"data": {"id": "Z"}},
    {"data": {"source": "X", "target": "Y", "id": "X➞Y"}},
    {"data": {"source": "Z", "target": "Y", "id": "Z➞Y"}},
    {"data": {"source": "Z", "target": "X", "id": "Z➞X"}},
]

stylesheet = [
    {"selector": "node", "style": {"label": "data(id)", "width": 20, "height": 20}},
    {
        "selector": "edge",
        "style": {
            "width": 3,
            "curve-style": "bezier",
            "target-arrow-shape": "triangle",
        },
    },
]

selected = cytoscape(elements, stylesheet, key="graph")

st.markdown("**Selected nodes**: %s" % (", ".join(selected["nodes"])))
st.markdown("**Selected edges**: %s" % (", ".join(selected["edges"])))
```

上記の app.py は、下記のように streamlit コマンドへの入力として使えます。

```
streamlit run app.py
```

その結果、下記の Streamlit アプリを見ることができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7591/9cb49577-f9b1-0a27-f4b0-f854d93a00d3.png)

このネットワークグラフは、本記事の冒頭で述べた「インタラクティブなウィジェット」になっています。

試しに Y の丸 (以降はノードと呼びます)、Z のノード、そして X-Y-Z ノードをつなぐ矢印 (以降はエッジと呼びます) をクリックしてみましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7591/3d710872-960c-c87e-50b3-f6d6c8e131fb.png)

すると上記のようにノードやエッジが「選択されたこと」を示す赤色に変わり、下記コードに相当する出力がインタラクティブに更新されることがわかります。

```python
st.markdown("**Selected nodes**: %s" % (", ".join(selected["nodes"])))
st.markdown("**Selected edges**: %s" % (", ".join(selected["edges"])))
```

`selected` のオブジェクトは dict で、 `nodes` と `edges` という 2 つの key に、選択されたノードとエッジの id がリストの value が入っています。

# 東京の路線図データを st-cytoscape に入力してみよう

今度は先のお試しデータではなく、がっつりしたデータで st-cytoscape を使ってみましょう。

app.py を下記のように変えてください。

```python
import streamlit as st
from st_cytoscape import cytoscape

# elements = [
#     {"data": {"id": "X"}, "selected": True, "selectable": False},
#     {"data": {"id": "Y"}},
#     {"data": {"id": "Z"}},
#     {"data": {"source": "X", "target": "Y", "id": "X➞Y"}},
#     {"data": {"source": "Z", "target": "Y", "id": "Z➞Y"}},
#     {"data": {"source": "Z", "target": "X", "id": "Z➞X"}},
# ]

import requests
import json

url = "https://raw.githubusercontent.com/cytoscape/cytoscape.js/refs/heads/master/documentation/demos/tokyo-railways/tokyo-railways.json"
response = requests.get(url)
data = json.loads(response.text)
elements = data['elements']['nodes'] + data['elements']['edges']

stylesheet = [
    {"selector": "node",
      "style": {"label": "data(station_name)", "width": 10, "height": 10}},
    # {
    #     "selector": "edge",
    #     "style": {
    #         "width": 3,
    #         "curve-style": "bezier",
    #         "target-arrow-shape": "triangle",
    #     },
    # },
]


selected = cytoscape(elements, stylesheet, width="100%", height="540px", layout={"name": "preset"}, key="graph")

print(selected)
st.markdown("**Selected nodes**: %s" % (", ".join(selected["nodes"])))
st.markdown("**Selected edges**: %s" % (", ".join(selected["edges"])))
```

すると下記のようなネットワークが streamlit 中に見えると思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7591/0d965d4d-e644-61d2-f1a9-e9ba80b7e80d.png)

ズームしてみると下記みたいな感じ。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7591/18428668-6719-1938-e966-e2cc6d9691a9.png)

おっこれなら何か面白いものができるかも？と思われたのではないでしょうか？

尻切れトンボですが、最後に新たなコードの肝だけ説明します。

```py
selected = cytoscape(elements, stylesheet, width="100%", height="540px", layout={"name": "preset"}, key="graph")
```

この行の `layout={"name": "preset"}` が肝です。

このパラメーター設定によって

```py
url = "https://raw.githubusercontent.com/cytoscape/cytoscape.js/refs/heads/master/documentation/demos/tokyo-railways/tokyo-railways.json"
```

の json 中の x と y の座標がネットワークのノードのレイアウトに使われます。

```json
    }, {
      "data" : {
        "id" : "5572",
        "station_name" : "武蔵野台",
        "close_ymd" : "",
        "lon" : 139.51128899999998,
        "post" : "183-0011",
        "e_status" : 0,
        "SUID" : 5572,
        "station_g_cd" : 2400121,
        "add" : "府中市白糸台４-１８-４",
        "line_cd" : 24001,
        "selected" : false,
        "open_ymd" : "",
        "name" : "2400121",
        "pref_name" : "東京都",
        "shared_name" : "2400121",
        "lat" : 35.664159000000005,
        "y" : -356641.59,
        "x" : 1395112.8899999997
      },
```

そんなわけで、東京の実際の路線図の駅配置が再現されるわけなんですね。

# じゃあ 自分は streamlit と cytoscape.js を使って次に何しようかな？

ネットワーク可視化ってものは結構いろんな使い方ができます。

ChatGPT に 「streamlit と cytoscape.js を使った実用的なアプリケーションの例を複数挙げてください。」
と聞いてみると、みなさんの興味に合うアプリケーションが見つかるかもしれません。

ぜひ streamlit と cytoscape.js を組み合わせて、何かアプリケーションを作ってみてください！