| 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
|
|
|
|
|
| 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"
|
|
|
|
|
| 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)}
|
|
|
|
|
| if 'adj_df' not in st.session_state:
|
|
|
| st.session_state.adj_df = pd.DataFrame(
|
| np.zeros((5, 5), dtype=int),
|
| index=nodes,
|
| columns=nodes
|
| )
|
|
|
|
|
| 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"
|
| )
|
|
|
|
|
|
|
|
|
| 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
|
| elif i > j:
|
|
|
| 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
|
|
|
|
|
| col1, col2 = st.columns([1, 1.5])
|
|
|
| with col1:
|
| st.subheader("隣接行列 (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("ノードをドラッグして移動できます。スクロールで拡大縮小可能です。")
|
|
|
|
|
| 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
|
| }
|
| }
|
| """)
|
|
|
|
|
| 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("グラフの描画中にエラーが発生しました。") |