接下来为大家带来的是一个功能完善的AI图形管理器,专为AI应用设计,支持节点管理、关系可视化和数据分析功能 并且能够用于实际应用 添加功能
目录:1. 功能说明
这个AI图形管理器提供了一套完整的图形管理和分析工具,专为AI应用设计:
主要功能
1. 节点管理**:
- 添加/删除/编辑节点
- 支持节点类型(输入层、隐藏层、输出层等)
- 自定义节点权重和属性
2. 边管理**:
- 添加/删除边
- 支持边权重
- 可视化连接关系
3. 图形操作**:
- 随机生成AI模型结构
- 清空图形
- 图形分析(连通性、节点度、权重分布等)
4. 文件操作:
- 保存/加载图形(JSON格式)
- 导出高质量图像(PNG/PDF)
5. 可视化:
-基于NetworkX和Matplotlib的交互式可视化
- 按节点类型自动着色
- 显示节点标签和权重
以下为使用场景
1. 神经网络可视化:展示神经网络结构,包括输入层、隐藏层和输出层
2. 知识图谱管理:创建和管理AI领域的知识图谱
3. 决策树表示:可视化AI决策过程
4. :数据流分析:展示数据在AI系统中的流动和处理过程
技术特点
- 使用NetworkX作为图数据结构后端
- 基于Matplotlib的可视化展示
- Tkinter GUI界面,跨平台运行
- JSON格式的持久化存储
- 完整的异常处理和用户反馈
使用方法
1. 运行程序后,您可以通过"添加节点"按钮创建新节点
2. 使用"添加边"按钮连接节点
3. 随机生成功能可以快速创建测试图形
4. 使用分析功能获取图形的统计信息
5. 保存/加载功能允许您持久化存储图形
代码展示:
import tkinter as tk
from tkinter import ttk, messagebox, filedialog, simpledialog
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import networkx as nx
import numpy as np
import random
import json
import uuid
class AIGraphManager:
def __init__(self, root):
self.root = root
self.root.title("AI图形管理器 v1.0")
self.root.geometry("1200x800")
self.root.configure(bg="#2c3e50")
# 创建图形数据结构
self.graph = nx.DiGraph()
self.selected_node = None
self.node_colors = {}
# 设置样式
self.style = ttk.Style()
self.style.theme_use("clam")
self.style.configure("TFrame", background="#34495e")
self.style.configure("TButton", background="#3498db", foreground="white", font=("Arial", 10))
self.style.configure("TLabel", background="#34495e", foreground="#ecf0f1", font=("Arial", 10))
self.style.configure("Treeview", background="#ecf0f1", fieldbackground="#ecf0f1", font=("Arial", 9))
self.style.configure("Treeview.Heading", background="#3498db", foreground="white", font=("Arial", 10, "bold"))
# 创建主框架
self.main_frame = ttk.Frame(root)
self.main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# 创建左侧控制面板
self.control_frame = ttk.LabelFrame(self.main_frame, text="图形控制", padding=(10, 5))
self.control_frame.pack(side=tk.LEFT, fill=tk.Y, padx=(0, 10))
# 创建右侧图形显示区域
self.graph_frame = ttk.LabelFrame(self.main_frame, text="图形可视化", padding=(10, 5))
self.graph_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True)
# 设置图形显示区域
self.fig = plt.Figure(figsize=(8, 6), dpi=100)
self.canvas = FigureCanvasTkAgg(self.fig, master=self.graph_frame)
self.canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
# 初始化图形
self.init_graph()
# 创建控制面板组件
self.create_control_panel()
# 创建底部状态栏
self.status_bar = ttk.Label(root, text="就绪 | 节点数: 0 | 边数: 0", relief=tk.SUNKEN, anchor=tk.W)
self.status_bar.pack(side=tk.BOTTOM, fill=tk.X)
# 更新状态
self.update_status()
def create_control_panel(self):
# 节点管理
node_frame = ttk.LabelFrame(self.control_frame, text="节点管理", padding=(5, 5))
node_frame.pack(fill=tk.X, pady=5)
ttk.Button(node_frame, text="添加节点", command=self.add_node).pack(fill=tk.X, pady=2)
ttk.Button(node_frame, text="删除节点", command=self.delete_node).pack(fill=tk.X, pady=2)
ttk.Button(node_frame, text="编辑节点", command=self.edit_node).pack(fill=tk.X, pady=2)
# 边管理
edge_frame = ttk.LabelFrame(self.control_frame, text="边管理", padding=(5, 5))
edge_frame.pack(fill=tk.X, pady=5)
ttk.Button(edge_frame, text="添加边", command=self.add_edge).pack(fill=tk.X, pady=2)
ttk.Button(edge_frame, text="删除边", command=self.delete_edge).pack(fill=tk.X, pady=2)
# 图形操作
graph_frame = ttk.LabelFrame(self.control_frame, text="图形操作", padding=(5, 5))
graph_frame.pack(fill=tk.X, pady=5)
ttk.Button(graph_frame, text="随机生成", command=self.generate_random_graph).pack(fill=tk.X, pady=2)
ttk.Button(graph_frame, text="清空图形", command=self.clear_graph).pack(fill=tk.X, pady=2)
ttk.Button(graph_frame, text="分析图形", command=self.analyze_graph).pack(fill=tk.X, pady=2)
# 文件操作
file_frame = ttk.LabelFrame(self.control_frame, text="文件操作", padding=(5, 5))
file_frame.pack(fill=tk.X, pady=5)
ttk.Button(file_frame, text="保存图形", command=self.save_graph).pack(fill=tk.X, pady=2)
ttk.Button(file_frame, text="加载图形", command=self.load_graph).pack(fill=tk.X, pady=2)
ttk.Button(file_frame, text="导出图像", command=self.export_image).pack(fill=tk.X, pady=2)
# 节点列表
list_frame = ttk.LabelFrame(self.control_frame, text="节点列表", padding=(5, 5))
list_frame.pack(fill=tk.BOTH, expand=True, pady=5)
# 创建树形视图
columns = ("id", "label", "type", "weight")
self.node_tree = ttk.Treeview(list_frame, columns=columns, show="headings", height=15)
# 设置列属性
self.node_tree.column("id", width=80, anchor=tk.W)
self.node_tree.column("label", width=120, anchor=tk.W)
self.node_tree.column("type", width=80, anchor=tk.W)
self.node_tree.column("weight", width=60, anchor=tk.CENTER)
# 设置列标题
self.node_tree.heading("id", text="ID", anchor=tk.W)
self.node_tree.heading("label", text="标签", anchor=tk.W)
self.node_tree.heading("type", text="类型", anchor=tk.W)
self.node_tree.heading("weight", text="权重", anchor=tk.CENTER)
# 添加滚动条
scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=self.node_tree.yview)
self.node_tree.configure(yscroll=scrollbar.set)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.node_tree.pack(fill=tk.BOTH, expand=True)
# 绑定选择事件
self.node_tree.bind("<<TreeviewSelect>>", self.on_node_select)
def init_graph(self):
self.fig.clf()
self.ax = self.fig.add_subplot(111)
self.ax.set_title("AI图形可视化", fontsize=14)
self.ax.set_facecolor("#ecf0f1")
self.ax.axis('off')
self.canvas.draw()
def update_graph(self):
self.fig.clf()
self.ax = self.fig.add_subplot(111)
self.ax.set_title("AI图形可视化", fontsize=14)
self.ax.set_facecolor("#ecf0f1")
if len(self.graph.nodes) == 0:
self.ax.text(0.5, 0.5, "没有可显示的图形数据\n请添加节点或加载图形",
ha='center', va='center', fontsize=12, color='gray')
self.canvas.draw()
return
# 生成布局
pos = nx.spring_layout(self.graph, seed=42)
# 为不同类型的节点设置不同的颜色
node_colors = [self.node_colors.get(self.graph.nodes[node]['type'], '#3498db')
for node in self.graph.nodes]
# 绘制节点
nx.draw_networkx_nodes(self.graph, pos, node_size=700,
node_color=node_colors, alpha=0.9, ax=self.ax)
# 绘制边
nx.draw_networkx_edges(self.graph, pos, width=1.5,
edge_color='#7f8c8d', alpha=0.7, ax=self.ax)
# 绘制节点标签
node_labels = {node: f"{data['label']}\n({data['type']})"
for node, data in self.graph.nodes(data=True)}
nx.draw_networkx_labels(self.graph, pos, labels=node_labels,
font_size=9, font_color='white', font_weight='bold', ax=self.ax)
# 绘制边标签
edge_labels = {(u, v): f"{data.get('weight', '')}"
for u, v, data in self.graph.edges(data=True)}
nx.draw_networkx_edge_labels(self.graph, pos, edge_labels=edge_labels,
font_size=8, font_color='#2c3e50', ax=self.ax)
self.canvas.draw()
self.update_node_list()
self.update_status()
def update_node_list(self):
# 清空当前列表
for item in self.node_tree.get_children():
self.node_tree.delete(item)
# 添加节点到列表
for node, data in self.graph.nodes(data=True):
self.node_tree.insert("", "end", values=(
node[:8] + "...",
data.get('label', 'N/A'),
data.get('type', '未知'),
data.get('weight', 0)
))
def update_status(self):
node_count = len(self.graph.nodes)
edge_count = len(self.graph.edges)
self.status_bar.config(text=f"就绪 | 节点数: {node_count} | 边数: {edge_count}")
def add_node(self):
dialog = tk.Toplevel(self.root)
dialog.title("添加节点")
dialog.geometry("300x250")
dialog.resizable(False, False)
dialog.transient(self.root)
dialog.grab_set()
ttk.Label(dialog, text="节点标签:").pack(pady=(10, 0))
label_entry = ttk.Entry(dialog, width=30)
label_entry.pack(pady=5)
label_entry.focus_set()
ttk.Label(dialog, text="节点类型:").pack()
node_type = ttk.Combobox(dialog, values=["输入层", "隐藏层", "输出层", "数据", "处理", "决策"])
node_type.pack(pady=5)
node_type.set("隐藏层")
ttk.Label(dialog, text="初始权重:").pack()
weight_entry = ttk.Entry(dialog, width=30)
weight_entry.pack(pady=5)
weight_entry.insert(0, str(round(random.uniform(0.1, 1.0), 2)))
ttk.Label(dialog, text="其他属性:").pack()
attr_entry = ttk.Entry(dialog, width=30)
attr_entry.pack(pady=5)
attr_entry.insert(0, "属性=值")
def on_ok():
label = label_entry.get()
n_type = node_type.get()
weight = weight_entry.get()
attr = attr_entry.get()
if not label:
messagebox.showerror("错误", "节点标签不能为空!")
return
try:
weight_val = float(weight) if weight else 0.0
except ValueError:
messagebox.showerror("错误", "权重必须是数字!")
return
# 创建唯一ID
node_id = str(uuid.uuid4())
# 添加节点到图形
attributes = {"label": label, "type": n_type, "weight": weight_val}
# 解析额外属性
if attr and "=" in attr:
key, value = attr.split("=", 1)
attributes[key.strip()] = value.strip()
self.graph.add_node(node_id, **attributes)
# 为节点类型分配颜色
if n_type not in self.node_colors:
colors = ['#e74c3c', '#2ecc71', '#9b59b6', '#f1c40f', '#1abc9c', '#d35400']
self.node_colors[n_type] = colors[len(self.node_colors) % len(colors)]
self.update_graph()
dialog.destroy()
ttk.Button(dialog, text="确定", command=on_ok).pack(side=tk.LEFT, padx=20, pady=10)
ttk.Button(dialog, text="取消", command=dialog.destroy).pack(side=tk.RIGHT, padx=20, pady=10)
def delete_node(self):
if not self.selected_node:
messagebox.showinfo("信息", "请先选择一个节点")
return
if messagebox.askyesno("确认", "确定要删除选中的节点吗?"):
# 查找完整的节点ID
full_id = None
for node in self.graph.nodes:
if node.startswith(self.selected_node):
full_id = node
break
if full_id:
self.graph.remove_node(full_id)
self.selected_node = None
self.update_graph()
def edit_node(self):
if not self.selected_node:
messagebox.showinfo("信息", "请先选择一个节点")
return
# 查找完整的节点ID和节点数据
full_id = None
node_data = {}
for node in self.graph.nodes:
if node.startswith(self.selected_node):
full_id = node
node_data = self.graph.nodes[node]
break
if not full_id:
return
dialog = tk.Toplevel(self.root)
dialog.title("编辑节点")
dialog.geometry("300x250")
dialog.resizable(False, False)
dialog.transient(self.root)
dialog.grab_set()
ttk.Label(dialog, text="节点标签:").pack(pady=(10, 0))
label_entry = ttk.Entry(dialog, width=30)
label_entry.pack(pady=5)
label_entry.insert(0, node_data.get('label', ''))
label_entry.focus_set()
ttk.Label(dialog, text="节点类型:").pack()
node_type = ttk.Combobox(dialog, values=["输入层", "隐藏层", "输出层", "数据", "处理", "决策"])
node_type.pack(pady=5)
node_type.set(node_data.get('type', '隐藏层'))
ttk.Label(dialog, text="节点权重:").pack()
weight_entry = ttk.Entry(dialog, width=30)
weight_entry.pack(pady=5)
weight_entry.insert(0, str(node_data.get('weight', 0)))
ttk.Label(dialog, text="其他属性:").pack()
attr_entry = ttk.Entry(dialog, width=30)
attr_entry.pack(pady=5)
# 显示额外属性(除了label、type、weight)
extra_attrs = [f"{k}={v}" for k, v in node_data.items()
if k not in ['label', 'type', 'weight']]
if extra_attrs:
attr_entry.insert(0, "; ".join(extra_attrs))
def on_ok():
label = label_entry.get()
n_type = node_type.get()
weight = weight_entry.get()
attr = attr_entry.get()
if not label:
messagebox.showerror("错误", "节点标签不能为空!")
return
try:
weight_val = float(weight) if weight else 0.0
except ValueError:
messagebox.showerror("错误", "权重必须是数字!")
return
# 更新节点属性
self.graph.nodes[full_id]['label'] = label
self.graph.nodes[full_id]['type'] = n_type
self.graph.nodes[full_id]['weight'] = weight_val
# 更新节点类型颜色
if n_type not in self.node_colors:
colors = ['#e74c3c', '#2ecc71', '#9b59b6', '#f1c40f', '#1abc9c', '#d35400']
self.node_colors[n_type] = colors[len(self.node_colors) % len(colors)]
# 解析额外属性
if attr:
for pair in attr.split(';'):
if '=' in pair:
key, value = pair.split('=', 1)
key = key.strip()
value = value.strip()
if key and key not in ['label', 'type', 'weight']:
self.graph.nodes[full_id][key] = value
self.update_graph()
dialog.destroy()
ttk.Button(dialog, text="确定", command=on_ok).pack(side=tk.LEFT, padx=20, pady=10)
ttk.Button(dialog, text="取消", command=dialog.destroy).pack(side=tk.RIGHT, padx=20, pady=10)
def add_edge(self):
if len(self.graph.nodes) < 2:
messagebox.showinfo("信息", "至少需要两个节点才能添加边")
return
dialog = tk.Toplevel(self.root)
dialog.title("添加边")
dialog.geometry("300x200")
dialog.resizable(False, False)
dialog.transient(self.root)
dialog.grab_set()
ttk.Label(dialog, text="起点节点:").pack(pady=(10, 0))
from_node = ttk.Combobox(dialog, values=[self.graph.nodes[n]['label'] for n in self.graph.nodes])
from_node.pack(pady=5)
ttk.Label(dialog, text="终点节点:").pack()
to_node = ttk.Combobox(dialog, values=[self.graph.nodes[n]['label'] for n in self.graph.nodes])
to_node.pack(pady=5)
ttk.Label(dialog, text="权重 (可选):").pack()
weight_entry = ttk.Entry(dialog, width=30)
weight_entry.pack(pady=5)
def on_ok():
from_label = from_node.get()
to_label = to_node.get()
weight = weight_entry.get()
if not from_label or not to_label:
messagebox.showerror("错误", "必须选择起点和终点节点!")
return
# 查找节点ID
from_id = None
to_id = None
for node, data in self.graph.nodes(data=True):
if data['label'] == from_label:
from_id = node
if data['label'] == to_label:
to_id = node
if not from_id or not to_id:
messagebox.showerror("错误", "无法找到节点!")
return
if from_id == to_id:
messagebox.showerror("错误", "不能连接节点到自身!")
return
if self.graph.has_edge(from_id, to_id):
messagebox.showerror("错误", "这两个节点之间已经存在边!")
return
# 添加边
try:
weight_val = float(weight) if weight else 1.0
self.graph.add_edge(from_id, to_id, weight=weight_val)
except ValueError:
messagebox.showerror("错误", "权重必须是数字!")
return
self.update_graph()
dialog.destroy()
ttk.Button(dialog, text="确定", command=on_ok).pack(side=tk.LEFT, padx=20, pady=10)
ttk.Button(dialog, text="取消", command=dialog.destroy).pack(side=tk.RIGHT, padx=20, pady=10)
def delete_edge(self):
if len(self.graph.edges) == 0:
messagebox.showinfo("信息", "图形中没有边")
return
dialog = tk.Toplevel(self.root)
dialog.title("删除边")
dialog.geometry("300x150")
dialog.resizable(False, False)
dialog.transient(self.root)
dialog.grab_set()
ttk.Label(dialog, text="起点节点:").pack(pady=(10, 0))
from_node = ttk.Combobox(dialog, values=[self.graph.nodes[n]['label'] for n in self.graph.nodes])
from_node.pack(pady=5)
ttk.Label(dialog, text="终点节点:").pack()
to_node = ttk.Combobox(dialog, values=[self.graph.nodes[n]['label'] for n in self.graph.nodes])
to_node.pack(pady=5)
def on_ok():
from_label = from_node.get()
to_label = to_node.get()
if not from_label or not to_label:
messagebox.showerror("错误", "必须选择起点和终点节点!")
return
# 查找节点ID
from_id = None
to_id = None
for node, data in self.graph.nodes(data=True):
if data['label'] == from_label:
from_id = node
if data['label'] == to_label:
to_id = node
if not from_id or not to_id:
messagebox.showerror("错误", "无法找到节点!")
return
if not self.graph.has_edge(from_id, to_id):
messagebox.showerror("错误", "这两个节点之间没有边!")
return
# 删除边
self.graph.remove_edge(from_id, to_id)
self.update_graph()
dialog.destroy()
ttk.Button(dialog, text="确定", command=on_ok).pack(side=tk.LEFT, padx=20, pady=10)
ttk.Button(dialog, text="取消", command=dialog.destroy).pack(side=tk.RIGHT, padx=20, pady=10)
def generate_random_graph(self):
# 清空现有图形
self.graph.clear()
self.node_colors = {}
# 生成5-10个节点
num_nodes = random.randint(5, 10)
node_types = ["输入层", "隐藏层", "输出层", "数据", "处理", "决策"]
for i in range(num_nodes):
node_id = str(uuid.uuid4())
node_type = random.choice(node_types)
weight = round(random.uniform(0.1, 1.0), 2)
self.graph.add_node(node_id,
label=f"节点{i+1}",
type=node_type,
weight=weight)
# 为节点类型分配颜色
if node_type not in self.node_colors:
colors = ['#e74c3c', '#2ecc71', '#9b59b6', '#f1c40f', '#1abc9c', '#d35400']
self.node_colors[node_type] = colors[len(self.node_colors) % len(colors)]
# 添加边(连接节点)
nodes = list(self.graph.nodes)
for i in range(len(nodes)):
# 每个节点连接到1-3个其他节点
num_edges = random.randint(1, min(3, len(nodes)-1)
targets = random.sample(nodes, num_edges)
for target in targets:
if nodes[i] != target and not self.graph.has_edge(nodes[i], target):
weight = round(random.uniform(0.1, 1.0), 2)
self.graph.add_edge(nodes[i], target, weight=weight)
self.update_graph()
messagebox.showinfo("信息", f"已生成随机图形: {num_nodes}个节点, {len(self.graph.edges)}条边")
def clear_graph(self):
if messagebox.askyesno("确认", "确定要清空整个图形吗?"):
self.graph.clear()
self.node_colors = {}
self.selected_node = None
self.update_graph()
def analyze_graph(self):
if len(self.graph.nodes) == 0:
messagebox.showinfo("信息", "图形为空,无法进行分析")
return
# 创建分析结果窗口
result_window = tk.Toplevel(self.root)
result_window.title("图形分析结果")
result_window.geometry("600x400")
result_window.transient(self.root)
# 创建文本区域显示结果
text_area = tk.Text(result_window, wrap=tk.WORD, font=("Courier New", 10))
scrollbar = ttk.Scrollbar(result_window, command=text_area.yview)
text_area.configure(yscrollcommand=scrollbar.set)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
text_area.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# 添加分析结果
text_area.insert(tk.END, "="*50 + "\n")
text_area.insert(tk.END, "图形分析报告\n")
text_area.insert(tk.END, "="*50 + "\n\n")
# 基本信息
text_area.insert(tk.END, f"节点总数: {len(self.graph.nodes)}\n")
text_area.insert(tk.END, f"边总数: {len(self.graph.edges)}\n\n")
# 节点类型统计
type_count = {}
for node, data in self.graph.nodes(data=True):
node_type = data.get('type', '未知')
type_count[node_type] = type_count.get(node_type, 0) + 1
text_area.insert(tk.END, "节点类型分布:\n")
for t, count in type_count.items():
text_area.insert(tk.END, f" {t}: {count}个节点\n")
text_area.insert(tk.END, "\n")
# 平均度
degrees = [deg for _, deg in self.graph.degree()]
avg_degree = sum(degrees) / len(degrees) if degrees else 0
text_area.insert(tk.END, f"平均节点度: {avg_degree:.2f}\n\n")
# 连通性分析
if nx.is_weakly_connected(self.graph):
text_area.insert(tk.END, "图形是弱连通的\n")
else:
text_area.insert(tk.END, "图形不是弱连通的\n")
text_area.insert(tk.END, f"连通分量数: {nx.number_weakly_connected_components(self.graph)}\n")
# 权重分析
weights = [data['weight'] for _, data in self.graph.nodes(data=True)]
min_weight = min(weights) if weights else 0
max_weight = max(weights) if weights else 0
avg_weight = sum(weights) / len(weights) if weights else 0
text_area.insert(tk.END, "\n节点权重分析:\n")
text_area.insert(tk.END, f" 最小权重: {min_weight:.2f}\n")
text_area.insert(tk.END, f" 最大权重: {max_weight:.2f}\n")
text_area.insert(tk.END, f" 平均权重: {avg_weight:.2f}\n")
# 边权重分析
if len(self.graph.edges) > 0:
edge_weights = [data['weight'] for _, _, data in self.graph.edges(data=True)]
min_edge = min(edge_weights) if edge_weights else 0
max_edge = max(edge_weights) if edge_weights else 0
avg_edge = sum(edge_weights) / len(edge_weights) if edge_weights else 0
text_area.insert(tk.END, "\n边权重分析:\n")
text_area.insert(tk.END, f" 最小权重: {min_edge:.2f}\n")
text_area.insert(tk.END, f" 最大权重: {max_edge:.2f}\n")
text_area.insert(tk.END, f" 平均权重: {avg_edge:.2f}\n")
text_area.configure(state=tk.DISABLED)
def save_graph(self):
if len(self.graph.nodes) == 0:
messagebox.showinfo("信息", "没有数据可保存")
return
file_path = filedialog.asksaveasfilename(
defaultextension=".json",
filetypes=[("JSON 文件", "*.json"), ("所有文件", "*.*")]
)
if not file_path:
return
try:
# 转换图形数据为可序列化的格式
graph_data = {
"nodes": [],
"edges": []
}
# 添加节点
for node, data in self.graph.nodes(data=True):
node_data = {"id": node}
node_data.update(data)
graph_data["nodes"].append(node_data)
# 添加边
for u, v, data in self.graph.edges(data=True):
edge_data = {
"source": u,
"target": v
}
edge_data.update(data)
graph_data["edges"].append(edge_data)
# 保存节点颜色
graph_data["node_colors"] = self.node_colors
# 写入文件
with open(file_path, 'w') as f:
json.dump(graph_data, f, indent=2)
messagebox.showinfo("成功", f"图形已保存到: {file_path}")
except Exception as e:
messagebox.showerror("错误", f"保存文件时出错: {str(e)}")
def load_graph(self):
file_path = filedialog.askopenfilename(
filetypes=[("JSON 文件", "*.json"), ("所有文件", "*.*")]
)
if not file_path:
return
try:
with open(file_path, 'r') as f:
graph_data = json.load(f)
# 清空现有图形
self.graph.clear()
self.node_colors = {}
# 添加节点
for node_data in graph_data.get("nodes", []):
node_id = node_data.pop("id")
self.graph.add_node(node_id, **node_data)
# 恢复节点颜色
node_type = node_data.get('type')
if node_type and node_type in graph_data.get("node_colors", {}):
self.node_colors[node_type] = graph_data["node_colors"][node_type]
# 添加边
for edge_data in graph_data.get("edges", []):
source = edge_data.pop("source")
target = edge_data.pop("target")
self.graph.add_edge(source, target, **edge_data)
self.update_graph()
messagebox.showinfo("成功", f"已加载图形: {len(self.graph.nodes)}个节点, {len(self.graph.edges)}条边")
except Exception as e:
messagebox.showerror("错误", f"加载文件时出错: {str(e)}")
def export_image(self):
if len(self.graph.nodes) == 0:
messagebox.showinfo("信息", "没有数据可导出")
return
file_path = filedialog.asksaveasfilename(
defaultextension=".png",
filetypes=[("PNG 图像", "*.png"), ("PDF 文件", "*.pdf"), ("所有文件", "*.*")]
)
if not file_path:
return
try:
# 创建一个新图形用于导出
export_fig = plt.Figure(figsize=(10, 8), dpi=150)
ax = export_fig.add_subplot(111)
# 生成布局
pos = nx.spring_layout(self.graph, seed=42)
# 为不同类型的节点设置不同的颜色
node_colors = [self.node_colors.get(self.graph.nodes[node]['type'], '#3498db')
for node in self.graph.nodes]
# 绘制节点
nx.draw_networkx_nodes(self.graph, pos, node_size=800,
node_color=node_colors, alpha=0.9, ax=ax)
# 绘制边
nx.draw_networkx_edges(self.graph, pos, width=1.8,
edge_color='#7f8c8d', alpha=0.7, ax=ax)
# 绘制节点标签
node_labels = {node: f"{data['label']}\n({data['type']})"
for node, data in self.graph.nodes(data=True)}
nx.draw_networkx_labels(self.graph, pos, labels=node_labels,
font_size=10, font_color='white', font_weight='bold', ax=ax)
# 绘制边标签
edge_labels = {(u, v): f"{data.get('weight', '')}"
for u, v, data in self.graph.edges(data=True)}
nx.draw_networkx_edge_labels(self.graph, pos, edge_labels=edge_labels,
font_size=9, font_color='#2c3e50', ax=ax)
ax.set_title("AI图形可视化", fontsize=16)
ax.axis('off')
# 保存图像
export_fig.savefig(file_path, bbox_inches='tight')
messagebox.showinfo("成功", f"图像已导出到: {file_path}")
except Exception as e:
messagebox.showerror("错误", f"导出图像时出错: {str(e)}")
def on_node_select(self, event):
selected_item = self.node_tree.selection()
if selected_item:
item = self.node_tree.item(selected_item[0])
self.selected_node = item['values'][0] # 存储截断的ID
def run(self):
self.root.mainloop()
if __name__ == "__main__":
root = tk.Tk()
app = AIGraphManager(root)
app.run()