import streamlit as st import pandas as pd import numpy as np import networkx as nx from pyvis.network import Network import streamlit.components.v1 as components import matplotlib.font_manager as fm import os # --- 1. フォント設定 (長期メモリのデフォルト要件) --- FONT_PATH = "NotoSansJP-Regular.ttf" if os.path.exists(FONT_PATH): fm.fontManager.addfont(FONT_PATH) font_name = fm.FontProperties(fname=FONT_PATH).get_name() else: font_name = "sans-serif" # --- 2. アプリ設定 --- st.set_page_config(page_title="Interactive SNA Tool", layout="wide") st.title("SNA:インタラクティブ隣接行列可視化") nodes = ['A', 'B', 'C', 'D', 'E'] colors = ["#FF4B4B", "#1C83E1", "#00C04A", "#FFD700", "#800080"] # 美しい色分け color_map = {node: color for node, color in zip(nodes, colors)} # --- 3. セッション状態の管理 --- if 'adj_df' not in st.session_state: # 初期状態:全て0の5x5行列 st.session_state.adj_df = pd.DataFrame( np.zeros((5, 5), dtype=int), index=nodes, columns=nodes ) # --- 4. サイドバー:入力インターフェース --- st.sidebar.header("1. 隣接行列の入力") st.sidebar.info("上半三角(対角線より右上)のセルのみ編集可能です。") # データエディタの表示 # ユーザーがどこを触っても、プログラム側で「上半三角以外を無効化・同期」する edited_df = st.sidebar.data_editor( st.session_state.adj_df, column_config={ col: st.column_config.SelectboxColumn( options=[0, 1], required=True, width="small" ) for col in nodes }, hide_index=False, key="matrix_editor" ) # 【重要】上半三角のみを有効にするロジック # 前回の値と比較して、上半三角(i < j)以外の変更をリセットし、 # 同時に下半三角を同期(対称化)させる new_matrix = edited_df.values.copy() old_matrix = st.session_state.adj_df.values for i in range(5): for j in range(5): if i == j: new_matrix[i, j] = 0 # 対角成分は0固定 elif i > j: # 下半三角は上半三角(j, i)の値をコピー new_matrix[i, j] = new_matrix[j, i] final_df = pd.DataFrame(new_matrix, index=nodes, columns=nodes) st.session_state.adj_df = final_df # --- 5. メインコンテンツ:表示 --- col1, col2 = st.columns([1, 1.5]) with col1: st.subheader("隣接行列 (LaTeX表示)") # LaTeX形式への変換 matrix_values = final_df.values latex_str = r"A = \begin{pmatrix} " rows = [] for row in matrix_values: rows.append(" & ".join(map(str, row))) latex_str += " \\\\ ".join(rows) latex_str += r" \end{pmatrix}" st.latex(latex_str) st.write("---") st.markdown(f""" **現在の状態:** - エッジ数: `{int(np.sum(matrix_values)/2)}` - 無向グラフ(対称行列) """) with col2: st.subheader("インタラクティブ・ネットワーク図") st.caption("ノードをドラッグして移動できます。スクロールで拡大縮小可能です。") # Pyvisを用いたグラフ作成 net = Network(height="500px", width="100%", bgcolor="#ffffff", font_color="#333333", directed=False) # ノード追加 for i, node in enumerate(nodes): net.add_node(node, label=node, color=colors[i], size=25, font={'size': 18, 'face': font_name}) # エッジ追加 for i in range(5): for j in range(i + 1, 5): if matrix_values[i, j] == 1: net.add_edge(nodes[i], nodes[j], color="#888888", width=2) # レイアウト設定(物理シミュレーション) net.toggle_physics(True) net.set_options(""" var options = { "physics": { "barnesHut": { "gravitationalConstant": -2000, "centralGravity": 0.3, "springLength": 150 }, "minVelocity": 0.75 } } """) # HTMLとして保存してStreamlitで表示 try: path = '/tmp' if os.name != 'nt' else '.' file_path = f"{path}/nx.html" net.save_graph(file_path) with open(file_path, 'r', encoding='utf-8') as f: html_data = f.read() components.html(html_data, height=550) except Exception as e: st.error("グラフの描画中にエラーが発生しました。")