|
|
@@ -19,6 +19,7 @@ class KnowledgeMindmapGraph {
|
|
|
{ prerequisite: 'P04', target: 'P05', threshold: 0.6 },
|
|
|
{ prerequisite: 'P05', target: 'P06', threshold: 0.6 },
|
|
|
];
|
|
|
+ this.parentMap = {};
|
|
|
|
|
|
this.setMasteryData(options.masteryData || {});
|
|
|
}
|
|
|
@@ -50,6 +51,7 @@ class KnowledgeMindmapGraph {
|
|
|
|
|
|
this.masteryCache = {};
|
|
|
this.treeData = this.transformNode(this.rawTree);
|
|
|
+ this.buildParentMap(this.treeData);
|
|
|
this.relationEdges = this.normalizeEdges(rawEdges);
|
|
|
const flatIds = [];
|
|
|
this.collectIds(this.treeData, flatIds);
|
|
|
@@ -78,7 +80,7 @@ class KnowledgeMindmapGraph {
|
|
|
|
|
|
const model = {
|
|
|
id,
|
|
|
- label,
|
|
|
+ label: `${id} · ${label}`,
|
|
|
depth,
|
|
|
locked: false,
|
|
|
collapsed: depth > 0 && (node.children || []).length > 0, // 默认折叠所有有子节点的节点(除根节点)
|
|
|
@@ -182,10 +184,10 @@ class KnowledgeMindmapGraph {
|
|
|
const seen = new Set();
|
|
|
const normalized = [];
|
|
|
const styleMap = {
|
|
|
- prerequisite: { stroke: '#60a5fa', lineDash: [10, 8], lineWidth: 3 },
|
|
|
- successor: { stroke: '#7dd3fc', lineWidth: 3 },
|
|
|
- crosslink: { stroke: '#fb923c', lineDash: [8, 6], lineWidth: 2.5 },
|
|
|
- sibling: { stroke: '#94a3b8', lineDash: [6, 6], lineWidth: 2.5 },
|
|
|
+ prerequisite: { stroke: '#60a5fa', lineDash: [10, 8], lineWidth: 3, label: '前置' },
|
|
|
+ successor: { stroke: '#7dd3fc', lineWidth: 3, label: '后继' },
|
|
|
+ crosslink: { stroke: '#fb923c', lineDash: [8, 6], lineWidth: 2.5, label: '跨联' },
|
|
|
+ sibling: { stroke: '#94a3b8', lineDash: [6, 6], lineWidth: 2.5, label: '同级' },
|
|
|
};
|
|
|
|
|
|
(rawEdges || []).forEach((edge, index) => {
|
|
|
@@ -198,10 +200,21 @@ class KnowledgeMindmapGraph {
|
|
|
const category = edge.type || 'successor';
|
|
|
const renderType =
|
|
|
category === 'successor' ? 'cubic-horizontal' : 'quadratic';
|
|
|
- const style = styleMap[category] || {
|
|
|
+ const baseStyle = styleMap[category] || {
|
|
|
stroke: '#cbd5e1',
|
|
|
lineWidth: 2.5,
|
|
|
};
|
|
|
+ const label = baseStyle.label || edge.label || category;
|
|
|
+ const arrowStroke = baseStyle.stroke || '#cbd5e1';
|
|
|
+ const style = {
|
|
|
+ ...baseStyle,
|
|
|
+ endArrow: {
|
|
|
+ path: 'M 0,0 L 8,4 L 0,8 z',
|
|
|
+ fill: arrowStroke,
|
|
|
+ d: 8,
|
|
|
+ },
|
|
|
+ startArrow: false,
|
|
|
+ };
|
|
|
|
|
|
normalized.push({
|
|
|
id: `rel-${index}`,
|
|
|
@@ -212,7 +225,8 @@ class KnowledgeMindmapGraph {
|
|
|
style: {
|
|
|
...style,
|
|
|
},
|
|
|
- label: category,
|
|
|
+ label,
|
|
|
+ comment: edge.comment || edge.note || '',
|
|
|
});
|
|
|
});
|
|
|
|
|
|
@@ -298,11 +312,18 @@ class KnowledgeMindmapGraph {
|
|
|
window.KnowledgeMindmapGraphInstance = this;
|
|
|
window.KnowledgeMindmapG6Graph = this.graph;
|
|
|
|
|
|
- this.drawRelationEdges();
|
|
|
+ // 边提示
|
|
|
+ this.bindEdgeTooltip();
|
|
|
+
|
|
|
this.applyNodeStates();
|
|
|
this.startEdgeFlows();
|
|
|
this.focusOnLowestMastery();
|
|
|
this.repaintNodes();
|
|
|
+
|
|
|
+ // 折叠/展开或重新布局后重新挂载关联线
|
|
|
+ this.graph.on('afterlayout', () => {
|
|
|
+ this.redrawRelationEdges();
|
|
|
+ });
|
|
|
}
|
|
|
|
|
|
setCollapsedState(nodeData, depth = 0) {
|
|
|
@@ -334,12 +355,43 @@ class KnowledgeMindmapGraph {
|
|
|
drawRelationEdges() {
|
|
|
if (!this.graph || !this.relationEdges.length) return;
|
|
|
this.relationEdges.forEach((edge) => {
|
|
|
- // 跳过缺失节点的边,避免 G6 报错导致后续渲染异常
|
|
|
- if (!this.graph.findById(edge.source) || !this.graph.findById(edge.target)) {
|
|
|
- console.warn('[mindmap] 跳过无效关联边', edge.id || edge.source + '-' + edge.target);
|
|
|
+ // 尝试将隐藏节点映射到可见的父节点,避免关联线丢失
|
|
|
+ let sourceId = edge.source;
|
|
|
+ let targetId = edge.target;
|
|
|
+ const resolveVisible = (id) => {
|
|
|
+ let cur = id;
|
|
|
+ while (cur) {
|
|
|
+ if (this.graph.findById(cur)) return cur;
|
|
|
+ cur = this.parentMap[cur];
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ };
|
|
|
+ if (!this.graph.findById(sourceId)) {
|
|
|
+ const fallback = resolveVisible(sourceId);
|
|
|
+ if (fallback) sourceId = fallback;
|
|
|
+ }
|
|
|
+ if (!this.graph.findById(targetId)) {
|
|
|
+ const fallback = resolveVisible(targetId);
|
|
|
+ if (fallback) targetId = fallback;
|
|
|
+ }
|
|
|
+ const sourceVisible = this.graph.findById(sourceId);
|
|
|
+ const targetVisible = this.graph.findById(targetId);
|
|
|
+ // 若两端都找不到可见节点,直接跳过
|
|
|
+ if (!sourceVisible && !targetVisible) {
|
|
|
return;
|
|
|
}
|
|
|
- this.graph.addItem('edge', edge);
|
|
|
+ // 若只有一端可见,则将不可见端提升到其最近的可见父节点(若仍不可见则跳过)
|
|
|
+ if (!sourceVisible) {
|
|
|
+ const resolved = resolveVisible(edge.source);
|
|
|
+ if (!resolved || !this.graph.findById(resolved)) return;
|
|
|
+ sourceId = resolved;
|
|
|
+ }
|
|
|
+ if (!targetVisible) {
|
|
|
+ const resolved = resolveVisible(edge.target);
|
|
|
+ if (!resolved || !this.graph.findById(resolved)) return;
|
|
|
+ targetId = resolved;
|
|
|
+ }
|
|
|
+ this.graph.addItem('edge', { ...edge, source: sourceId, target: targetId });
|
|
|
});
|
|
|
}
|
|
|
|
|
|
@@ -646,6 +698,7 @@ class KnowledgeMindmapGraph {
|
|
|
|
|
|
this.masteryCache = {};
|
|
|
this.treeData = this.transformNode(this.rawTree);
|
|
|
+ this.buildParentMap(this.treeData);
|
|
|
const flatIds = [];
|
|
|
this.collectIds(this.treeData, flatIds);
|
|
|
this.nodeIdSet = new Set(flatIds);
|
|
|
@@ -672,6 +725,54 @@ class KnowledgeMindmapGraph {
|
|
|
this.graph.paint();
|
|
|
}
|
|
|
|
|
|
+ bindEdgeTooltip() {
|
|
|
+ if (!this.graph) return;
|
|
|
+ const tooltipEl = document.createElement('div');
|
|
|
+ tooltipEl.style.position = 'fixed';
|
|
|
+ tooltipEl.style.pointerEvents = 'none';
|
|
|
+ tooltipEl.style.zIndex = '9999';
|
|
|
+ tooltipEl.style.display = 'none';
|
|
|
+ tooltipEl.style.background = 'rgba(15,23,42,0.95)';
|
|
|
+ tooltipEl.style.color = '#e2e8f0';
|
|
|
+ tooltipEl.style.padding = '8px 10px';
|
|
|
+ tooltipEl.style.borderRadius = '8px';
|
|
|
+ tooltipEl.style.boxShadow = '0 10px 30px rgba(0,0,0,0.18)';
|
|
|
+ tooltipEl.style.fontSize = '12px';
|
|
|
+ tooltipEl.style.lineHeight = '1.4';
|
|
|
+ document.body.appendChild(tooltipEl);
|
|
|
+
|
|
|
+ const show = (html, x, y) => {
|
|
|
+ tooltipEl.innerHTML = html;
|
|
|
+ tooltipEl.style.left = `${x + 12}px`;
|
|
|
+ tooltipEl.style.top = `${y + 12}px`;
|
|
|
+ tooltipEl.style.display = 'block';
|
|
|
+ };
|
|
|
+ const hide = () => {
|
|
|
+ tooltipEl.style.display = 'none';
|
|
|
+ };
|
|
|
+
|
|
|
+ const buildHtml = (model) => {
|
|
|
+ return `
|
|
|
+ <div style="font-weight:700;font-size:13px;margin-bottom:4px;">${model.label || '关联'}</div>
|
|
|
+ <div style="font-size:12px;">${model.source || ''} → ${model.target || ''}</div>
|
|
|
+ ${model.comment ? `<div style="font-size:11px;color:#cbd5e1;margin-top:4px;white-space:pre-line;">${model.comment}</div>` : ''}
|
|
|
+ `;
|
|
|
+ };
|
|
|
+
|
|
|
+ this.graph.on('edge:mouseenter', (evt) => {
|
|
|
+ const model = evt?.item?.getModel?.() || {};
|
|
|
+ const { clientX, clientY } = evt;
|
|
|
+ show(buildHtml(model), clientX, clientY);
|
|
|
+ });
|
|
|
+
|
|
|
+ this.graph.on('edge:mouseleave', () => hide());
|
|
|
+ }
|
|
|
+
|
|
|
+ redrawRelationEdges() {
|
|
|
+ this.clearRelationEdges();
|
|
|
+ this.drawRelationEdges();
|
|
|
+ }
|
|
|
+
|
|
|
forceCollapseNodes() {
|
|
|
if (!this.graph) return;
|
|
|
}
|
|
|
@@ -877,6 +978,14 @@ class KnowledgeMindmapGraph {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ buildParentMap(node, parentId = null) {
|
|
|
+ if (!node) return;
|
|
|
+ this.parentMap[node.id] = parentId;
|
|
|
+ (node.children || []).forEach((child) =>
|
|
|
+ this.buildParentMap(child, node.id)
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
}
|
|
|
|
|
|
// 定义KnowledgeMindmapGraph类,确保G6已加载
|