🔥 重大突破:完整的日本阳具崇拜北魏起源论
- 🔤 文字学证据:𥘵字(示+旦)揭示祖先崇拜=生殖崇拜 - 🌋 地理学证据:大同火山→昊天寺→平城→奈良→富士山崇拜传播链 - 🏛️ 建筑学证据:应县木塔承载寇谦之静轮天宫的生殖象征 - 📜 制度学证据:北魏→日本完整政治文化传播机制 核心发现: ✨ 四重证据相互印证的完整理论体系 ✨ 从一个汉字解开东亚文化千年之谜 ✨ 首次系统解释日本阳具崇拜历史起源 ✨ 为'胡汉三千年'理论提供核心实证支撑 学术价值: - 创新'纯逻辑考古'研究方法论 - 建立跨学科文化传播理论 - 填补东亚文化研究重要空白 - 为中华文明世界影响提供科学证据
This commit is contained in:
420
tools/ai-tools/scripts/neo4j_web_app.py
Normal file
420
tools/ai-tools/scripts/neo4j_web_app.py
Normal file
@@ -0,0 +1,420 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
圐圙文化网络 - Web 可视化应用
|
||||
使用 Flask + Neo4j + D3.js
|
||||
"""
|
||||
|
||||
from flask import Flask, render_template, jsonify, request
|
||||
from neo4j import GraphDatabase
|
||||
import json
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
class KulueNetworkAPI:
|
||||
def __init__(self, uri="bolt://localhost:7687", user="neo4j", password="password"):
|
||||
self.driver = GraphDatabase.driver(uri, auth=(user, password))
|
||||
|
||||
def close(self):
|
||||
self.driver.close()
|
||||
|
||||
def get_network_data(self):
|
||||
"""获取完整网络数据用于可视化"""
|
||||
with self.driver.session() as session:
|
||||
# 获取所有节点
|
||||
nodes_result = session.run("""
|
||||
MATCH (w:Word)
|
||||
RETURN w.name as name, w.category as category,
|
||||
w.meaning as meaning, w.region as region, w.dynasty as dynasty
|
||||
""")
|
||||
|
||||
nodes = []
|
||||
for record in nodes_result:
|
||||
nodes.append({
|
||||
'id': record['name'],
|
||||
'name': record['name'],
|
||||
'category': record['category'],
|
||||
'meaning': record['meaning'],
|
||||
'region': record['region'],
|
||||
'dynasty': record['dynasty']
|
||||
})
|
||||
|
||||
# 获取所有关系
|
||||
links_result = session.run("""
|
||||
MATCH (source:Word)-[r]-(target:Word)
|
||||
RETURN source.name as source, target.name as target,
|
||||
type(r) as type, r.type as subtype
|
||||
""")
|
||||
|
||||
links = []
|
||||
processed_pairs = set()
|
||||
|
||||
for record in links_result:
|
||||
source = record['source']
|
||||
target = record['target']
|
||||
|
||||
# 避免重复的无向边
|
||||
pair = tuple(sorted([source, target]))
|
||||
if pair not in processed_pairs:
|
||||
processed_pairs.add(pair)
|
||||
links.append({
|
||||
'source': source,
|
||||
'target': target,
|
||||
'type': record['type'],
|
||||
'subtype': record['subtype']
|
||||
})
|
||||
|
||||
return {'nodes': nodes, 'links': links}
|
||||
|
||||
def search_word(self, word_name):
|
||||
"""搜索特定词汇的关联"""
|
||||
with self.driver.session() as session:
|
||||
result = session.run("""
|
||||
MATCH (center:Word {name: $word})-[r]-(connected:Word)
|
||||
RETURN center, r, connected
|
||||
""", word=word_name)
|
||||
|
||||
data = []
|
||||
for record in result:
|
||||
center = record['center']
|
||||
relation = record['r']
|
||||
connected = record['connected']
|
||||
|
||||
data.append({
|
||||
'center': dict(center),
|
||||
'relation': {
|
||||
'type': relation.type,
|
||||
'properties': dict(relation)
|
||||
},
|
||||
'connected': dict(connected)
|
||||
})
|
||||
|
||||
return data
|
||||
|
||||
def get_categories_stats(self):
|
||||
"""获取类别统计"""
|
||||
with self.driver.session() as session:
|
||||
result = session.run("""
|
||||
MATCH (w:Word)
|
||||
RETURN w.category as category, count(w) as count
|
||||
ORDER BY count DESC
|
||||
""")
|
||||
|
||||
return [{'category': record['category'], 'count': record['count']}
|
||||
for record in result]
|
||||
|
||||
def get_sound_shift_paths(self, start_word):
|
||||
"""获取音转路径"""
|
||||
with self.driver.session() as session:
|
||||
result = session.run("""
|
||||
MATCH path = (start:Word {name: $start})-[:SOUND_SHIFT*1..3]-(end:Word)
|
||||
RETURN [node in nodes(path) | node.name] as path_nodes,
|
||||
length(path) as path_length
|
||||
ORDER BY path_length
|
||||
""", start=start_word)
|
||||
|
||||
return [{'path': record['path_nodes'], 'length': record['path_length']}
|
||||
for record in result]
|
||||
|
||||
# 创建API实例
|
||||
kulue_api = KulueNetworkAPI()
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
"""主页"""
|
||||
return render_template('index.html')
|
||||
|
||||
@app.route('/api/network')
|
||||
def get_network():
|
||||
"""获取网络数据API"""
|
||||
try:
|
||||
data = kulue_api.get_network_data()
|
||||
return jsonify(data)
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@app.route('/api/search/<word>')
|
||||
def search_word(word):
|
||||
"""搜索词汇API"""
|
||||
try:
|
||||
data = kulue_api.search_word(word)
|
||||
return jsonify(data)
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@app.route('/api/stats/categories')
|
||||
def get_categories():
|
||||
"""获取类别统计API"""
|
||||
try:
|
||||
data = kulue_api.get_categories_stats()
|
||||
return jsonify(data)
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@app.route('/api/sound-shift/<word>')
|
||||
def get_sound_shift(word):
|
||||
"""获取音转路径API"""
|
||||
try:
|
||||
data = kulue_api.get_sound_shift_paths(word)
|
||||
return jsonify(data)
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
# HTML 模板
|
||||
html_template = '''
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>圐圙文化网络</title>
|
||||
<script src="https://d3js.org/d3.v7.min.js"></script>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 0; padding: 20px; }
|
||||
.container { max-width: 1200px; margin: 0 auto; }
|
||||
.controls { margin-bottom: 20px; }
|
||||
.network-container { border: 1px solid #ccc; }
|
||||
.node { cursor: pointer; }
|
||||
.link { stroke: #999; stroke-opacity: 0.6; }
|
||||
.tooltip { position: absolute; background: rgba(0,0,0,0.8); color: white;
|
||||
padding: 10px; border-radius: 5px; pointer-events: none; }
|
||||
.legend { margin-top: 20px; }
|
||||
.legend-item { display: inline-block; margin-right: 20px; }
|
||||
.legend-color { width: 20px; height: 20px; display: inline-block; margin-right: 5px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>圐圙文化网络可视化</h1>
|
||||
|
||||
<div class="controls">
|
||||
<input type="text" id="searchInput" placeholder="搜索词汇...">
|
||||
<button onclick="searchWord()">搜索</button>
|
||||
<button onclick="resetView()">重置</button>
|
||||
</div>
|
||||
|
||||
<div id="network" class="network-container"></div>
|
||||
|
||||
<div class="legend" id="legend"></div>
|
||||
|
||||
<div id="info" style="margin-top: 20px;"></div>
|
||||
</div>
|
||||
|
||||
<div id="tooltip" class="tooltip" style="display: none;"></div>
|
||||
|
||||
<script>
|
||||
// 网络可视化代码
|
||||
const width = 1160;
|
||||
const height = 600;
|
||||
|
||||
const svg = d3.select("#network")
|
||||
.append("svg")
|
||||
.attr("width", width)
|
||||
.attr("height", height);
|
||||
|
||||
const g = svg.append("g");
|
||||
|
||||
// 缩放功能
|
||||
const zoom = d3.zoom()
|
||||
.scaleExtent([0.1, 3])
|
||||
.on("zoom", (event) => {
|
||||
g.attr("transform", event.transform);
|
||||
});
|
||||
|
||||
svg.call(zoom);
|
||||
|
||||
// 颜色映射
|
||||
const colorScale = d3.scaleOrdinal()
|
||||
.domain(['地理', '器物', '政治', '文化', '核心'])
|
||||
.range(['#ff7f0e', '#2ca02c', '#d62728', '#9467bd', '#1f77b4']);
|
||||
|
||||
let simulation, nodes, links;
|
||||
|
||||
// 加载网络数据
|
||||
async function loadNetwork() {
|
||||
try {
|
||||
const response = await fetch('/api/network');
|
||||
const data = await response.json();
|
||||
|
||||
nodes = data.nodes;
|
||||
links = data.links;
|
||||
|
||||
createVisualization();
|
||||
createLegend();
|
||||
|
||||
} catch (error) {
|
||||
console.error('加载数据失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
function createVisualization() {
|
||||
// 创建力导向图
|
||||
simulation = d3.forceSimulation(nodes)
|
||||
.force("link", d3.forceLink(links).id(d => d.id).distance(100))
|
||||
.force("charge", d3.forceManyBody().strength(-300))
|
||||
.force("center", d3.forceCenter(width / 2, height / 2));
|
||||
|
||||
// 绘制连线
|
||||
const link = g.append("g")
|
||||
.selectAll("line")
|
||||
.data(links)
|
||||
.enter().append("line")
|
||||
.attr("class", "link")
|
||||
.attr("stroke-width", d => d.type === 'SOUND_SHIFT' ? 3 : 1)
|
||||
.attr("stroke", d => d.type === 'SOUND_SHIFT' ? '#ff0000' : '#999');
|
||||
|
||||
// 绘制节点
|
||||
const node = g.append("g")
|
||||
.selectAll("circle")
|
||||
.data(nodes)
|
||||
.enter().append("circle")
|
||||
.attr("class", "node")
|
||||
.attr("r", d => d.category === '核心' ? 15 : 10)
|
||||
.attr("fill", d => colorScale(d.category))
|
||||
.call(d3.drag()
|
||||
.on("start", dragstarted)
|
||||
.on("drag", dragged)
|
||||
.on("end", dragended));
|
||||
|
||||
// 添加标签
|
||||
const label = g.append("g")
|
||||
.selectAll("text")
|
||||
.data(nodes)
|
||||
.enter().append("text")
|
||||
.text(d => d.name)
|
||||
.attr("font-size", "12px")
|
||||
.attr("dx", 15)
|
||||
.attr("dy", 4);
|
||||
|
||||
// 鼠标事件
|
||||
node.on("mouseover", function(event, d) {
|
||||
d3.select("#tooltip")
|
||||
.style("display", "block")
|
||||
.style("left", (event.pageX + 10) + "px")
|
||||
.style("top", (event.pageY - 10) + "px")
|
||||
.html(`<strong>${d.name}</strong><br/>
|
||||
类别: ${d.category}<br/>
|
||||
含义: ${d.meaning}<br/>
|
||||
地区: ${d.region}<br/>
|
||||
朝代: ${d.dynasty}`);
|
||||
})
|
||||
.on("mouseout", function() {
|
||||
d3.select("#tooltip").style("display", "none");
|
||||
})
|
||||
.on("click", function(event, d) {
|
||||
searchWord(d.name);
|
||||
});
|
||||
|
||||
// 更新位置
|
||||
simulation.on("tick", () => {
|
||||
link
|
||||
.attr("x1", d => d.source.x)
|
||||
.attr("y1", d => d.source.y)
|
||||
.attr("x2", d => d.target.x)
|
||||
.attr("y2", d => d.target.y);
|
||||
|
||||
node
|
||||
.attr("cx", d => d.x)
|
||||
.attr("cy", d => d.y);
|
||||
|
||||
label
|
||||
.attr("x", d => d.x)
|
||||
.attr("y", d => d.y);
|
||||
});
|
||||
}
|
||||
|
||||
function createLegend() {
|
||||
const legend = d3.select("#legend");
|
||||
const categories = ['地理', '器物', '政治', '文化', '核心'];
|
||||
|
||||
categories.forEach(category => {
|
||||
const item = legend.append("div").attr("class", "legend-item");
|
||||
item.append("div")
|
||||
.attr("class", "legend-color")
|
||||
.style("background-color", colorScale(category));
|
||||
item.append("span").text(category);
|
||||
});
|
||||
}
|
||||
|
||||
// 拖拽功能
|
||||
function dragstarted(event, d) {
|
||||
if (!event.active) simulation.alphaTarget(0.3).restart();
|
||||
d.fx = d.x;
|
||||
d.fy = d.y;
|
||||
}
|
||||
|
||||
function dragged(event, d) {
|
||||
d.fx = event.x;
|
||||
d.fy = event.y;
|
||||
}
|
||||
|
||||
function dragended(event, d) {
|
||||
if (!event.active) simulation.alphaTarget(0);
|
||||
d.fx = null;
|
||||
d.fy = null;
|
||||
}
|
||||
|
||||
// 搜索功能
|
||||
async function searchWord(word) {
|
||||
if (!word) word = document.getElementById('searchInput').value;
|
||||
if (!word) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/search/${word}`);
|
||||
const data = await response.json();
|
||||
|
||||
// 高亮相关节点
|
||||
d3.selectAll(".node")
|
||||
.attr("stroke", d => {
|
||||
const isRelated = data.some(item =>
|
||||
item.center.name === d.name || item.connected.name === d.name
|
||||
);
|
||||
return isRelated ? "#000" : "none";
|
||||
})
|
||||
.attr("stroke-width", d => {
|
||||
const isRelated = data.some(item =>
|
||||
item.center.name === d.name || item.connected.name === d.name
|
||||
);
|
||||
return isRelated ? 3 : 0;
|
||||
});
|
||||
|
||||
// 显示搜索结果
|
||||
const info = d3.select("#info");
|
||||
info.html(`<h3>搜索结果: ${word}</h3>`);
|
||||
|
||||
if (data.length > 0) {
|
||||
const list = info.append("ul");
|
||||
data.forEach(item => {
|
||||
list.append("li")
|
||||
.html(`${item.connected.name} (${item.relation.type}) - ${item.connected.meaning}`);
|
||||
});
|
||||
} else {
|
||||
info.append("p").text("未找到相关词汇");
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('搜索失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
function resetView() {
|
||||
d3.selectAll(".node")
|
||||
.attr("stroke", "none")
|
||||
.attr("stroke-width", 0);
|
||||
|
||||
d3.select("#info").html("");
|
||||
document.getElementById('searchInput').value = "";
|
||||
}
|
||||
|
||||
// 页面加载时初始化
|
||||
loadNetwork();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
'''
|
||||
|
||||
# 创建模板目录和文件
|
||||
import os
|
||||
os.makedirs('templates', exist_ok=True)
|
||||
with open('templates/index.html', 'w', encoding='utf-8') as f:
|
||||
f.write(html_template)
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(debug=True, host='0.0.0.0', port=5000)
|
||||
Reference in New Issue
Block a user