492 lines
17 KiB
Python
492 lines
17 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
符号传承可视化系统
|
||
胡汉三千年项目可视化工具
|
||
|
||
功能:实现符号传播路径、阴阳对应关系、跨文明关联的可视化展示
|
||
"""
|
||
|
||
import json
|
||
import sqlite3
|
||
import matplotlib.pyplot as plt
|
||
import networkx as nx
|
||
from typing import Dict, List, Tuple
|
||
from datetime import datetime
|
||
import numpy as np
|
||
from enum import Enum
|
||
|
||
class VisualizationType(Enum):
|
||
"""可视化类型枚举"""
|
||
TRANSMISSION_MAP = "transmission_map" # 传播地图
|
||
TIMELINE = "timeline" # 时间线
|
||
YIN_YANG_MATRIX = "yin_yang_matrix" # 阴阳矩阵
|
||
CIVILIZATION_NETWORK = "civilization_network" # 文明网络
|
||
|
||
class SymbolVisualizer:
|
||
"""符号可视化系统"""
|
||
|
||
def __init__(self, db_path: str = "symbols.db"):
|
||
"""初始化可视化系统"""
|
||
self.db_path = db_path
|
||
self.conn = sqlite3.connect(db_path)
|
||
|
||
# 设置中文字体
|
||
plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']
|
||
plt.rcParams['axes.unicode_minus'] = False
|
||
|
||
def plot_transmission_map(self, symbol_id: str, save_path: str = None):
|
||
"""
|
||
绘制符号传播地图
|
||
|
||
Args:
|
||
symbol_id: 符号ID
|
||
save_path: 保存路径(可选)
|
||
"""
|
||
# 获取传播路径数据
|
||
cursor = self.conn.cursor()
|
||
cursor.execute("""
|
||
WITH RECURSIVE symbol_path AS (
|
||
SELECT s.symbol_id, s.origin_civilization, s.origin_period, 0 as depth
|
||
FROM symbols s
|
||
WHERE s.symbol_id = ?
|
||
|
||
UNION ALL
|
||
|
||
SELECT s.symbol_id, s.origin_civilization, s.origin_period, sp.depth + 1
|
||
FROM symbols s
|
||
JOIN cross_civilization_links l ON s.symbol_id = l.target_symbol_id
|
||
JOIN symbol_path sp ON l.source_symbol_id = sp.symbol_id
|
||
WHERE sp.depth < 10
|
||
)
|
||
SELECT * FROM symbol_path
|
||
ORDER BY depth
|
||
""", (symbol_id,))
|
||
|
||
path_data = cursor.fetchall()
|
||
|
||
if not path_data:
|
||
print("未找到传播路径数据")
|
||
return
|
||
|
||
# 创建图形
|
||
fig, ax = plt.subplots(figsize=(12, 8))
|
||
|
||
# 定义文明位置(简化版世界地图)
|
||
civilization_positions = {
|
||
"Chinese": (1, 3), # 中国
|
||
"Greek": (0, 2), # 希腊
|
||
"Roman": (-1, 1), # 罗马
|
||
"European": (-2, 0), # 欧洲
|
||
"Global": (-3, -1) # 全球
|
||
}
|
||
|
||
# 绘制传播路径
|
||
x_coords = []
|
||
y_coords = []
|
||
labels = []
|
||
|
||
for i, (sym_id, civ, period, depth) in enumerate(path_data):
|
||
if civ in civilization_positions:
|
||
x, y = civilization_positions[civ]
|
||
x_coords.append(x)
|
||
y_coords.append(y)
|
||
labels.append(f"{sym_id}\n({civ}, {period})")
|
||
|
||
# 绘制连线
|
||
ax.plot(x_coords, y_coords, 'o-', linewidth=2, markersize=8,
|
||
markerfacecolor='red', markeredgecolor='black')
|
||
|
||
# 添加标签
|
||
for i, (x, y) in enumerate(zip(x_coords, y_coords)):
|
||
ax.annotate(labels[i], (x, y), xytext=(5, 5),
|
||
textcoords='offset points', fontsize=8)
|
||
|
||
# 设置图形属性
|
||
ax.set_title(f'符号传播路径:{symbol_id}', fontsize=14, fontweight='bold')
|
||
ax.set_xlabel('文明传播方向(西→东)')
|
||
ax.set_ylabel('时间维度')
|
||
ax.grid(True, alpha=0.3)
|
||
|
||
# 调整坐标轴范围
|
||
ax.set_xlim(min(x_coords)-1, max(x_coords)+1)
|
||
ax.set_ylim(min(y_coords)-1, max(y_coords)+1)
|
||
|
||
if save_path:
|
||
plt.savefig(save_path, dpi=300, bbox_inches='tight')
|
||
print(f"传播地图已保存至:{save_path}")
|
||
|
||
plt.show()
|
||
|
||
def plot_timeline(self, symbol_family: List[str], save_path: str = None):
|
||
"""
|
||
绘制符号家族时间线
|
||
|
||
Args:
|
||
symbol_family: 符号家族ID列表
|
||
save_path: 保存路径(可选)
|
||
"""
|
||
cursor = self.conn.cursor()
|
||
|
||
# 获取符号时间数据
|
||
timeline_data = []
|
||
for symbol_id in symbol_family:
|
||
cursor.execute("""
|
||
SELECT symbol_id, symbol_form, symbol_name, origin_civilization, origin_period
|
||
FROM symbols
|
||
WHERE symbol_id = ?
|
||
""", (symbol_id,))
|
||
|
||
result = cursor.fetchone()
|
||
if result:
|
||
timeline_data.append(result)
|
||
|
||
if not timeline_data:
|
||
print("未找到符号数据")
|
||
return
|
||
|
||
# 解析时间信息(简化处理)
|
||
time_mapping = {
|
||
"Zhou Dynasty": -1000, # 周朝
|
||
"8th century BCE": -800, # 公元前8世纪
|
||
"Roman": -200, # 罗马时期
|
||
"European": 1000, # 欧洲中世纪
|
||
"Global": 1500 # 大航海时代
|
||
}
|
||
|
||
# 创建时间线图
|
||
fig, ax = plt.subplots(figsize=(14, 6))
|
||
|
||
y_positions = []
|
||
time_points = []
|
||
labels = []
|
||
|
||
for i, (sym_id, form, name, civ, period) in enumerate(timeline_data):
|
||
time_point = time_mapping.get(civ, 0)
|
||
time_points.append(time_point)
|
||
y_positions.append(i)
|
||
labels.append(f"{form} ({name})\n{civ}, {period}")
|
||
|
||
# 绘制时间线
|
||
ax.plot(time_points, y_positions, 'o-', linewidth=3, markersize=10,
|
||
markerfacecolor='blue', markeredgecolor='black')
|
||
|
||
# 添加标签
|
||
for i, (time, y, label) in enumerate(zip(time_points, y_positions, labels)):
|
||
ax.annotate(label, (time, y), xytext=(10, 0),
|
||
textcoords='offset points', fontsize=9,
|
||
bbox=dict(boxstyle="round,pad=0.3", facecolor="lightblue", alpha=0.7))
|
||
|
||
# 设置图形属性
|
||
ax.set_title('符号家族时间线', fontsize=14, fontweight='bold')
|
||
ax.set_xlabel('时间(公元前→公元后)')
|
||
ax.set_ylabel('符号序列')
|
||
ax.grid(True, alpha=0.3)
|
||
|
||
# 调整坐标轴
|
||
ax.set_xlim(min(time_points)-500, max(time_points)+500)
|
||
ax.set_ylim(-1, len(y_positions))
|
||
|
||
if save_path:
|
||
plt.savefig(save_path, dpi=300, bbox_inches='tight')
|
||
print(f"时间线图已保存至:{save_path}")
|
||
|
||
plt.show()
|
||
|
||
def plot_yin_yang_matrix(self, save_path: str = None):
|
||
"""
|
||
绘制阴阳符号矩阵
|
||
|
||
Args:
|
||
save_path: 保存路径(可选)
|
||
"""
|
||
cursor = self.conn.cursor()
|
||
|
||
# 获取所有符号的阴阳属性
|
||
cursor.execute("""
|
||
SELECT symbol_id, symbol_form, yin_yang_attribute, engraving_type, origin_civilization
|
||
FROM symbols
|
||
ORDER BY origin_civilization, yin_yang_attribute
|
||
""")
|
||
|
||
symbols_data = cursor.fetchall()
|
||
|
||
if not symbols_data:
|
||
print("未找到符号数据")
|
||
return
|
||
|
||
# 创建阴阳矩阵
|
||
civilizations = sorted(set([row[4] for row in symbols_data]))
|
||
yin_yang_types = ['yin', 'yang', 'neutral']
|
||
|
||
# 统计每个文明-阴阳组合的符号数量
|
||
matrix_data = np.zeros((len(civilizations), len(yin_yang_types)))
|
||
|
||
for row in symbols_data:
|
||
sym_id, form, yin_yang, engraving, civ = row
|
||
civ_index = civilizations.index(civ)
|
||
yin_yang_index = yin_yang_types.index(yin_yang)
|
||
matrix_data[civ_index, yin_yang_index] += 1
|
||
|
||
# 创建热力图
|
||
fig, ax = plt.subplots(figsize=(10, 8))
|
||
|
||
im = ax.imshow(matrix_data, cmap='YlOrRd', aspect='auto')
|
||
|
||
# 设置坐标轴标签
|
||
ax.set_xticks(np.arange(len(yin_yang_types)))
|
||
ax.set_yticks(np.arange(len(civilizations)))
|
||
ax.set_xticklabels(yin_yang_types)
|
||
ax.set_yticklabels(civilizations)
|
||
|
||
# 添加数值标签
|
||
for i in range(len(civilizations)):
|
||
for j in range(len(yin_yang_types)):
|
||
text = ax.text(j, i, int(matrix_data[i, j]),
|
||
ha="center", va="center", color="black", fontweight='bold')
|
||
|
||
# 设置图形属性
|
||
ax.set_title('阴阳符号分布矩阵', fontsize=14, fontweight='bold')
|
||
ax.set_xlabel('阴阳属性')
|
||
ax.set_ylabel('文明')
|
||
|
||
# 添加颜色条
|
||
cbar = ax.figure.colorbar(im, ax=ax)
|
||
cbar.ax.set_ylabel('符号数量', rotation=-90, va="bottom")
|
||
|
||
if save_path:
|
||
plt.savefig(save_path, dpi=300, bbox_inches='tight')
|
||
print(f"阴阳矩阵图已保存至:{save_path}")
|
||
|
||
plt.show()
|
||
|
||
def plot_civilization_network(self, save_path: str = None):
|
||
"""
|
||
绘制文明网络图
|
||
|
||
Args:
|
||
save_path: 保存路径(可选)
|
||
"""
|
||
cursor = self.conn.cursor()
|
||
|
||
# 获取文明关联数据
|
||
cursor.execute("""
|
||
SELECT DISTINCT s1.origin_civilization, s2.origin_civilization, COUNT(*) as link_count
|
||
FROM cross_civilization_links l
|
||
JOIN symbols s1 ON l.source_symbol_id = s1.symbol_id
|
||
JOIN symbols s2 ON l.target_symbol_id = s2.symbol_id
|
||
WHERE s1.origin_civilization != s2.origin_civilization
|
||
GROUP BY s1.origin_civilization, s2.origin_civilization
|
||
HAVING link_count > 0
|
||
""")
|
||
|
||
network_data = cursor.fetchall()
|
||
|
||
if not network_data:
|
||
print("未找到文明关联数据")
|
||
return
|
||
|
||
# 创建网络图
|
||
G = nx.Graph()
|
||
|
||
# 添加节点和边
|
||
for source_civ, target_civ, weight in network_data:
|
||
G.add_edge(source_civ, target_civ, weight=weight)
|
||
|
||
# 设置节点位置
|
||
pos = nx.spring_layout(G, k=1, iterations=50)
|
||
|
||
# 绘制网络图
|
||
fig, ax = plt.subplots(figsize=(12, 10))
|
||
|
||
# 计算节点大小(基于连接数量)
|
||
node_sizes = [G.degree(node) * 500 for node in G.nodes()]
|
||
|
||
# 绘制边
|
||
edge_widths = [G[u][v]['weight'] for u, v in G.edges()]
|
||
nx.draw_networkx_edges(G, pos, alpha=0.5, width=edge_widths,
|
||
edge_color='gray')
|
||
|
||
# 绘制节点
|
||
nx.draw_networkx_nodes(G, pos, node_size=node_sizes,
|
||
node_color='lightblue', alpha=0.9,
|
||
edgecolors='black')
|
||
|
||
# 绘制标签
|
||
nx.draw_networkx_labels(G, pos, font_size=10, font_weight='bold')
|
||
|
||
# 设置图形属性
|
||
ax.set_title('文明符号传播网络', fontsize=16, fontweight='bold')
|
||
ax.axis('off')
|
||
|
||
# 添加图例
|
||
ax.text(0.02, 0.98, '节点大小:连接数量\n边宽度:关联强度',
|
||
transform=ax.transAxes, fontsize=10,
|
||
bbox=dict(boxstyle="round,pad=0.3", facecolor="white", alpha=0.8))
|
||
|
||
if save_path:
|
||
plt.savefig(save_path, dpi=300, bbox_inches='tight')
|
||
print(f"文明网络图已保存至:{save_path}")
|
||
|
||
plt.show()
|
||
|
||
def create_comparison_chart(self, symbol_list: List[str], save_path: str = None):
|
||
"""
|
||
创建符号对比图表
|
||
|
||
Args:
|
||
symbol_list: 符号ID列表
|
||
save_path: 保存路径(可选)
|
||
"""
|
||
cursor = self.conn.cursor()
|
||
|
||
# 获取符号对比数据
|
||
comparison_data = []
|
||
for symbol_id in symbol_list:
|
||
cursor.execute("""
|
||
SELECT symbol_id, symbol_form, symbol_name, yin_yang_attribute,
|
||
engraving_type, origin_civilization, origin_period
|
||
FROM symbols
|
||
WHERE symbol_id = ?
|
||
""", (symbol_id,))
|
||
|
||
result = cursor.fetchone()
|
||
if result:
|
||
comparison_data.append(result)
|
||
|
||
if not comparison_data:
|
||
print("未找到符号数据")
|
||
return
|
||
|
||
# 创建对比图表
|
||
fig, axes = plt.subplots(2, 2, figsize=(15, 12))
|
||
fig.suptitle('符号对比分析', fontsize=16, fontweight='bold')
|
||
|
||
# 1. 阴阳属性分布
|
||
yin_yang_counts = {'yin': 0, 'yang': 0, 'neutral': 0}
|
||
for data in comparison_data:
|
||
yin_yang = data[3]
|
||
yin_yang_counts[yin_yang] += 1
|
||
|
||
axes[0, 0].pie(yin_yang_counts.values(), labels=yin_yang_counts.keys(),
|
||
autopct='%1.1f%%', startangle=90)
|
||
axes[0, 0].set_title('阴阳属性分布')
|
||
|
||
# 2. 刻法类型分布
|
||
engraving_counts = {'yin_engraving': 0, 'yang_engraving': 0, 'mixed': 0}
|
||
for data in comparison_data:
|
||
engraving = data[4]
|
||
engraving_counts[engraving] += 1
|
||
|
||
axes[0, 1].bar(engraving_counts.keys(), engraving_counts.values(),
|
||
color=['lightcoral', 'lightblue', 'lightgreen'])
|
||
axes[0, 1].set_title('刻法类型分布')
|
||
axes[0, 1].tick_params(axis='x', rotation=45)
|
||
|
||
# 3. 文明分布
|
||
civilization_counts = {}
|
||
for data in comparison_data:
|
||
civ = data[5]
|
||
civilization_counts[civ] = civilization_counts.get(civ, 0) + 1
|
||
|
||
axes[1, 0].barh(list(civilization_counts.keys()), list(civilization_counts.values()),
|
||
color='lightsteelblue')
|
||
axes[1, 0].set_title('文明分布')
|
||
|
||
# 4. 符号形态展示
|
||
symbol_forms = [data[1] for data in comparison_data]
|
||
symbol_names = [data[2] for data in comparison_data]
|
||
|
||
axes[1, 1].text(0.5, 0.5, '\n'.join([f"{form}: {name}" for form, name in zip(symbol_forms, symbol_names)]),
|
||
ha='center', va='center', transform=axes[1, 1].transAxes,
|
||
fontsize=12, bbox=dict(boxstyle="round,pad=0.5", facecolor="wheat", alpha=0.5))
|
||
axes[1, 1].set_title('符号形态列表')
|
||
axes[1, 1].axis('off')
|
||
|
||
plt.tight_layout()
|
||
|
||
if save_path:
|
||
plt.savefig(save_path, dpi=300, bbox_inches='tight')
|
||
print(f"对比图表已保存至:{save_path}")
|
||
|
||
plt.show()
|
||
|
||
def export_visualization_report(self, symbol_id: str, output_dir: str = "./reports"):
|
||
"""
|
||
导出完整的可视化报告
|
||
|
||
Args:
|
||
symbol_id: 符号ID
|
||
output_dir: 输出目录
|
||
"""
|
||
import os
|
||
os.makedirs(output_dir, exist_ok=True)
|
||
|
||
# 生成各种可视化图表
|
||
visualizations = [
|
||
("transmission_map", self.plot_transmission_map),
|
||
("yin_yang_matrix", self.plot_yin_yang_matrix),
|
||
("civilization_network", self.plot_civilization_network)
|
||
]
|
||
|
||
generated_files = []
|
||
|
||
for viz_name, viz_func in visualizations:
|
||
file_path = os.path.join(output_dir, f"{symbol_id}_{viz_name}.png")
|
||
|
||
try:
|
||
if viz_name == "transmission_map":
|
||
viz_func(symbol_id, file_path)
|
||
else:
|
||
viz_func(file_path)
|
||
|
||
generated_files.append(file_path)
|
||
print(f"生成图表:{file_path}")
|
||
|
||
except Exception as e:
|
||
print(f"生成 {viz_name} 时出错:{e}")
|
||
|
||
# 创建报告文件
|
||
report_path = os.path.join(output_dir, f"{symbol_id}_visualization_report.md")
|
||
|
||
with open(report_path, 'w', encoding='utf-8') as f:
|
||
f.write(f"# 符号可视化报告:{symbol_id}\n\n")
|
||
f.write(f"生成时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n")
|
||
|
||
for file_path in generated_files:
|
||
filename = os.path.basename(file_path)
|
||
f.write(f"## {filename.replace('_', ' ').replace('.png', '')}\n\n")
|
||
f.write(f"\n\n")
|
||
|
||
print(f"可视化报告已生成:{report_path}")
|
||
return generated_files
|
||
|
||
# 使用示例
|
||
def main():
|
||
"""主函数示例"""
|
||
# 创建可视化系统
|
||
visualizer = SymbolVisualizer()
|
||
|
||
# 示例1:绘制P符号传播地图
|
||
print("=== 绘制P符号传播地图 ===")
|
||
visualizer.plot_transmission_map("P_yin_001")
|
||
|
||
# 示例2:绘制阴阳符号矩阵
|
||
print("=== 绘制阴阳符号矩阵 ===")
|
||
visualizer.plot_yin_yang_matrix()
|
||
|
||
# 示例3:绘制文明网络图
|
||
print("=== 绘制文明网络图 ===")
|
||
visualizer.plot_civilization_network()
|
||
|
||
# 示例4:创建符号对比图表
|
||
print("=== 创建符号对比图表 ===")
|
||
symbol_list = ["P_yin_001", "T_yang_001", "Pi_yin_001", "Tau_yang_001"]
|
||
visualizer.create_comparison_chart(symbol_list)
|
||
|
||
# 示例5:导出完整可视化报告
|
||
print("=== 导出完整可视化报告 ===")
|
||
visualizer.export_visualization_report("P_yin_001")
|
||
|
||
if __name__ == "__main__":
|
||
main() |