|
|
<!DOCTYPE html>
|
|
|
<html>
|
|
|
<head>
|
|
|
<meta charset="UTF-8">
|
|
|
<title>知识图谱可视化(接口调用修复版)</title>
|
|
|
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
|
|
|
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/extension/graph.min.js"></script>
|
|
|
<style>
|
|
|
.container { width: 1200px; margin: 20px auto; }
|
|
|
#param-form {
|
|
|
margin-bottom: 20px;
|
|
|
padding: 15px;
|
|
|
border: 1px solid #eee;
|
|
|
border-radius: 4px;
|
|
|
}
|
|
|
.form-group {
|
|
|
margin-bottom: 12px;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
gap: 10px;
|
|
|
}
|
|
|
.form-group label {
|
|
|
width: 120px;
|
|
|
font-weight: bold;
|
|
|
}
|
|
|
.form-group input {
|
|
|
flex: 1;
|
|
|
padding: 6px 8px;
|
|
|
border: 1px solid #ddd;
|
|
|
border-radius: 3px;
|
|
|
}
|
|
|
.form-group input[type="number"] {
|
|
|
width: 100px;
|
|
|
}
|
|
|
#note-ids-input {
|
|
|
display: flex;
|
|
|
gap: 5px;
|
|
|
align-items: center;
|
|
|
}
|
|
|
#note-ids-input input {
|
|
|
flex: 1;
|
|
|
}
|
|
|
button {
|
|
|
padding: 8px 16px;
|
|
|
background: #4285f4;
|
|
|
color: white;
|
|
|
border: none;
|
|
|
border-radius: 4px;
|
|
|
cursor: pointer;
|
|
|
}
|
|
|
button:hover {
|
|
|
background: #3367d6;
|
|
|
}
|
|
|
#graph-container {
|
|
|
width: 100%;
|
|
|
height: 700px;
|
|
|
border: 1px solid #eee;
|
|
|
}
|
|
|
#node-info {
|
|
|
margin-top: 10px;
|
|
|
padding: 10px;
|
|
|
border: 1px solid #eee;
|
|
|
border-radius: 4px;
|
|
|
}
|
|
|
.required-mark {
|
|
|
color: red;
|
|
|
}
|
|
|
.error-message {
|
|
|
color: #dc3545;
|
|
|
}
|
|
|
.loading-message {
|
|
|
color: #6c757d;
|
|
|
}
|
|
|
</style>
|
|
|
</head>
|
|
|
<body>
|
|
|
<div class="container">
|
|
|
<form id="param-form">
|
|
|
<div class="form-group">
|
|
|
<label>标签<label class="required-mark">*</label>:</label>
|
|
|
<input type="text" id="label" placeholder="输入标签(支持通配符*)" required value="*">
|
|
|
</div>
|
|
|
<div class="form-group">
|
|
|
<label>最大深度:</label>
|
|
|
<input type="number" id="max_depth" value="3" min="1">
|
|
|
</div>
|
|
|
<div class="form-group">
|
|
|
<label>最大节点数:</label>
|
|
|
<input type="number" id="max_nodes" value="100" min="1">
|
|
|
</div>
|
|
|
<div class="form-group">
|
|
|
<label>文档ID<label class="required-mark">*</label>:</label>
|
|
|
<input type="text" id="note_ids" placeholder="多个用逗号分隔(如:马克思主义原理.txt)" required value="马克思主义原理.txt">
|
|
|
</div>
|
|
|
<button type="submit">加载知识图谱</button>
|
|
|
</form>
|
|
|
|
|
|
<div id="graph-container"></div>
|
|
|
<div id="node-info">请点击按钮调用接口</div>
|
|
|
</div>
|
|
|
|
|
|
<script>
|
|
|
document.getElementById('param-form').addEventListener('submit', function(e) {
|
|
|
e.preventDefault();
|
|
|
const nodeInfo = document.getElementById('node-info');
|
|
|
nodeInfo.className = 'loading-message';
|
|
|
nodeInfo.innerHTML = '正在加载...';
|
|
|
|
|
|
// 1. 收集参数(不手动编码)
|
|
|
const params = {
|
|
|
label: document.getElementById('label').value.trim(),
|
|
|
max_depth: document.getElementById('max_depth').value.trim() || 3,
|
|
|
max_nodes: document.getElementById('max_nodes').value.trim() || 100,
|
|
|
note_ids: document.getElementById('note_ids').value.trim().split(',').map(id => id.trim()).filter(Boolean)
|
|
|
};
|
|
|
|
|
|
if (!params.label || params.note_ids.length === 0) {
|
|
|
nodeInfo.className = 'error-message';
|
|
|
nodeInfo.innerHTML = '请填写必填参数';
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 2. 构建URL(关键:不手动编码,让URLSearchParams自动处理)
|
|
|
const baseUrl = 'http://localhost:9621/graphs/note';
|
|
|
const urlParams = new URLSearchParams();
|
|
|
|
|
|
// 直接传原始值,URLSearchParams会自动编码一次(正确格式)
|
|
|
urlParams.append('label', params.label);
|
|
|
urlParams.append('max_depth', params.max_depth);
|
|
|
urlParams.append('max_nodes', params.max_nodes);
|
|
|
params.note_ids.forEach(id => urlParams.append('note_ids', id));
|
|
|
|
|
|
const fullUrl = `${baseUrl}?${urlParams.toString()}`;
|
|
|
console.log('调用接口:', fullUrl); // 此时日志中的note_ids应为正确的一次编码格式
|
|
|
|
|
|
// 3. 发起请求
|
|
|
fetch(fullUrl, {
|
|
|
method: 'GET',
|
|
|
mode: 'cors',
|
|
|
headers: { 'Accept': 'application/json' }
|
|
|
})
|
|
|
.then(response => {
|
|
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
|
return response.json();
|
|
|
})
|
|
|
.then(kgData => {
|
|
|
if (!kgData.nodes || !kgData.edges) throw new Error('数据格式错误');
|
|
|
|
|
|
// 4. 处理节点连接数
|
|
|
const nodeLinkCount = {};
|
|
|
kgData.nodes.forEach(node => nodeLinkCount[node.id] = 0);
|
|
|
kgData.edges.forEach(edge => {
|
|
|
nodeLinkCount[edge.source]++;
|
|
|
nodeLinkCount[edge.target]++;
|
|
|
});
|
|
|
|
|
|
// 5. 转换为ECharts格式
|
|
|
const echartsNodes = kgData.nodes.map(node => ({
|
|
|
id: node.id,
|
|
|
name: node.labels[0],
|
|
|
category: node.properties.entity_type,
|
|
|
properties: node.properties,
|
|
|
symbolSize: 20 + (nodeLinkCount[node.id] * 3)
|
|
|
}));
|
|
|
const echartsLinks = kgData.edges.map(edge => ({
|
|
|
source: edge.source,
|
|
|
target: edge.target,
|
|
|
properties: edge.properties
|
|
|
}));
|
|
|
const categories = Array.from(new Set(echartsNodes.map(n => n.category))).map(type => ({ name: type }));
|
|
|
|
|
|
// 6. 渲染图表
|
|
|
const chart = echarts.init(document.getElementById('graph-container'));
|
|
|
chart.setOption({
|
|
|
tooltip: { formatter: '{b}' },
|
|
|
legend: { data: categories.map(c => c.name), bottom: 10 },
|
|
|
series: [{
|
|
|
type: 'graph',
|
|
|
layout: 'force',
|
|
|
roam: true,
|
|
|
draggable: true,
|
|
|
force: { repulsion: 300, edgeLength: 100, gravity: 0.1, iterations: 50 },
|
|
|
label: { show: true, fontSize: 12 },
|
|
|
categories: categories,
|
|
|
data: echartsNodes,
|
|
|
links: echartsLinks,
|
|
|
emphasis: { focus: 'adjacency', lineStyle: { width: 5 } }
|
|
|
}]
|
|
|
});
|
|
|
|
|
|
// 7. 点击事件
|
|
|
chart.on('click', (params) => {
|
|
|
// 保持原有点击逻辑不变
|
|
|
if (params.dataType === 'node') {
|
|
|
const p = params.data.properties;
|
|
|
nodeInfo.innerHTML = `<strong>${params.name}</strong> | 类型:${p.entity_type} | 连接数:${nodeLinkCount[params.data.id]}<br>描述:${p.description?.slice(0, 500)}...`;
|
|
|
} else if (params.dataType === 'edge') {
|
|
|
const p = params.data.properties;
|
|
|
const sourceNode = echartsNodes.find(n => n.id === params.data.source);
|
|
|
const targetNode = echartsNodes.find(n => n.id === params.data.target);
|
|
|
nodeInfo.innerHTML = `<strong>边信息</strong> | ${sourceNode?.name} → ${targetNode?.name}<br>描述:${p.description}`;
|
|
|
}
|
|
|
});
|
|
|
|
|
|
window.addEventListener('resize', () => chart.resize());
|
|
|
nodeInfo.className = '';
|
|
|
nodeInfo.innerHTML = '加载完成 | 点击节点/边查看详情';
|
|
|
})
|
|
|
.catch(error => {
|
|
|
console.error('错误:', error);
|
|
|
nodeInfo.className = 'error-message';
|
|
|
nodeInfo.innerHTML = `加载失败:${error.message}`;
|
|
|
});
|
|
|
});
|
|
|
</script>
|
|
|
</body>
|
|
|
</html> |