|
|
@@ -23,6 +23,8 @@ class KnowledgeMindmapGraph {
|
|
|
this.clickTimer = null;
|
|
|
this.clickDelay = 220;
|
|
|
this.focusListener = null;
|
|
|
+ this.showEdges = options.showEdges ?? true;
|
|
|
+ this.showRelationEdges = options.showRelationEdges ?? true;
|
|
|
|
|
|
this.setMasteryData(options.masteryData || {});
|
|
|
}
|
|
|
@@ -63,7 +65,7 @@ class KnowledgeMindmapGraph {
|
|
|
this.logMasteryCoverage();
|
|
|
this.stats = {
|
|
|
nodes: this.countNodes(this.treeData),
|
|
|
- extraEdges: this.relationEdges.length,
|
|
|
+ extraEdges: this.showRelationEdges ? this.relationEdges.length : 0,
|
|
|
};
|
|
|
}
|
|
|
|
|
|
@@ -186,16 +188,11 @@ class KnowledgeMindmapGraph {
|
|
|
normalizeEdges(rawEdges) {
|
|
|
const seen = new Set();
|
|
|
const normalized = [];
|
|
|
- const neutral = {
|
|
|
- stroke: '#cbd5e1',
|
|
|
- lineWidth: 2,
|
|
|
- lineDash: [6, 6],
|
|
|
- };
|
|
|
const styleMap = {
|
|
|
- prerequisite: { ...neutral, label: '前置' },
|
|
|
- successor: { ...neutral, label: '后继' },
|
|
|
- crosslink: { ...neutral, label: '跨联' },
|
|
|
- sibling: { ...neutral, label: '同级' },
|
|
|
+ prerequisite: { stroke: '#8C8986FF', lineDash: [8, 6], lineWidth: 1, label: '前置' },
|
|
|
+ successor: { stroke: '#8C8986FF', lineDash: [8, 6], lineWidth: 1, label: '后继' },
|
|
|
+ crosslink: { stroke: '#8c8a89', lineDash: [8, 6], lineWidth: 1, label: '跨联' },
|
|
|
+ sibling: { stroke: '#8C8986FF', lineDash: [8, 6], lineWidth: 1, label: '同级' },
|
|
|
};
|
|
|
|
|
|
(rawEdges || []).forEach((edge, index) => {
|
|
|
@@ -208,7 +205,10 @@ class KnowledgeMindmapGraph {
|
|
|
const category = edge.type || 'successor';
|
|
|
const renderType =
|
|
|
category === 'successor' ? 'cubic-horizontal' : 'quadratic';
|
|
|
- const baseStyle = styleMap[category] || neutral;
|
|
|
+ const baseStyle = styleMap[category] || {
|
|
|
+ stroke: '#cbd5e1',
|
|
|
+ lineWidth: 2.5,
|
|
|
+ };
|
|
|
const label = baseStyle.label || edge.label || category;
|
|
|
const arrowStroke = baseStyle.stroke || '#cbd5e1';
|
|
|
const style = {
|
|
|
@@ -271,10 +271,16 @@ class KnowledgeMindmapGraph {
|
|
|
},
|
|
|
defaultEdge: {
|
|
|
type: 'cubic-horizontal',
|
|
|
- style: {
|
|
|
- stroke: '#cbd5e1',
|
|
|
- lineWidth: 2,
|
|
|
- },
|
|
|
+ style: this.showEdges
|
|
|
+ ? {
|
|
|
+ stroke: '#cbd5e1',
|
|
|
+ lineWidth: 2,
|
|
|
+ }
|
|
|
+ : {
|
|
|
+ stroke: 'transparent',
|
|
|
+ lineWidth: 0,
|
|
|
+ opacity: 0,
|
|
|
+ },
|
|
|
},
|
|
|
nodeStateStyles: {
|
|
|
hover: { shadowColor: '#38bdf8', shadowBlur: 24 },
|
|
|
@@ -310,19 +316,23 @@ class KnowledgeMindmapGraph {
|
|
|
window.focusMindmapNode = (id) => this.focusNodeById(id);
|
|
|
|
|
|
// 边提示
|
|
|
- this.bindEdgeTooltip();
|
|
|
+ if (this.showEdges) {
|
|
|
+ this.bindEdgeTooltip();
|
|
|
+ }
|
|
|
|
|
|
this.applyNodeStates();
|
|
|
- this.startEdgeFlows();
|
|
|
+ if (this.showEdges) {
|
|
|
+ this.startEdgeFlows();
|
|
|
+ }
|
|
|
this.focusOnLowestMastery();
|
|
|
this.repaintNodes();
|
|
|
+ if (!this.showEdges) {
|
|
|
+ this.hideAllEdges();
|
|
|
+ }
|
|
|
|
|
|
// 折叠/展开或重新布局后重新挂载关联线
|
|
|
this.graph.on('afterlayout', () => {
|
|
|
this.redrawRelationEdges();
|
|
|
- this.repaintNodes();
|
|
|
- this.applyNodeStates();
|
|
|
- this.graph.paint();
|
|
|
});
|
|
|
|
|
|
this.setupFocusListener();
|
|
|
@@ -355,7 +365,13 @@ class KnowledgeMindmapGraph {
|
|
|
}
|
|
|
|
|
|
drawRelationEdges() {
|
|
|
- if (!this.graph || !this.relationEdges.length) return;
|
|
|
+ if (
|
|
|
+ !this.graph ||
|
|
|
+ !this.relationEdges.length ||
|
|
|
+ !this.showRelationEdges ||
|
|
|
+ !this.showEdges
|
|
|
+ )
|
|
|
+ return;
|
|
|
this.relationEdges.forEach((edge) => {
|
|
|
// 尝试将隐藏节点映射到可见的父节点,避免关联线丢失
|
|
|
let sourceId = edge.source;
|
|
|
@@ -393,15 +409,7 @@ class KnowledgeMindmapGraph {
|
|
|
if (!resolved || !this.graph.findById(resolved)) return;
|
|
|
targetId = resolved;
|
|
|
}
|
|
|
- const added = this.graph.addItem('edge', { ...edge, source: sourceId, target: targetId });
|
|
|
- const shape = added?.getKeyShape?.();
|
|
|
- if (shape) {
|
|
|
- shape.attr({
|
|
|
- lineDash: [6, 6],
|
|
|
- stroke: '#cbd5e1',
|
|
|
- lineWidth: 2,
|
|
|
- });
|
|
|
- }
|
|
|
+ this.graph.addItem('edge', { ...edge, source: sourceId, target: targetId });
|
|
|
});
|
|
|
}
|
|
|
|
|
|
@@ -426,7 +434,7 @@ class KnowledgeMindmapGraph {
|
|
|
}
|
|
|
|
|
|
startEdgeFlows() {
|
|
|
- if (!this.graph) return;
|
|
|
+ if (!this.graph || !this.showEdges) return;
|
|
|
const nodeMap = new Map(
|
|
|
this.graph
|
|
|
.getNodes()
|
|
|
@@ -497,15 +505,15 @@ class KnowledgeMindmapGraph {
|
|
|
bindEvents() {
|
|
|
if (!this.graph) return;
|
|
|
|
|
|
- this.graph.on('node:mouseenter', (evt) => {
|
|
|
+ this.graph.on('node:mouseover', (evt) => {
|
|
|
const item = evt.item;
|
|
|
- if (!item || item.getModel().locked) return;
|
|
|
+ // if (!item || item.getModel().locked) return;
|
|
|
this.graph.setItemState(item, 'hover', true);
|
|
|
this.highlightNeighbors(item.getModel().id);
|
|
|
this.showTooltip(evt, item.getModel());
|
|
|
});
|
|
|
|
|
|
- this.graph.on('node:mouseleave', (evt) => {
|
|
|
+ this.graph.on('node:mouseout', (evt) => {
|
|
|
const item = evt.item;
|
|
|
if (!item) return;
|
|
|
this.graph.setItemState(item, 'hover', false);
|
|
|
@@ -513,10 +521,6 @@ class KnowledgeMindmapGraph {
|
|
|
this.hideTooltip();
|
|
|
});
|
|
|
|
|
|
- this.graph.on('canvas:mouseleave', () => {
|
|
|
- this.hideTooltip();
|
|
|
- });
|
|
|
-
|
|
|
this.graph.on('node:click', (evt) => this.handleNodeClick(evt));
|
|
|
|
|
|
// 双击用于折叠/展开
|
|
|
@@ -532,9 +536,6 @@ class KnowledgeMindmapGraph {
|
|
|
this.graph.updateItem(evt.item, { collapsed: nextState });
|
|
|
this.graph.layout?.();
|
|
|
this.redrawRelationEdges();
|
|
|
- this.repaintNodes();
|
|
|
- this.applyNodeStates?.();
|
|
|
- this.graph.paint();
|
|
|
}
|
|
|
});
|
|
|
|
|
|
@@ -555,6 +556,7 @@ class KnowledgeMindmapGraph {
|
|
|
}
|
|
|
|
|
|
highlightNeighbors(nodeId) {
|
|
|
+ if (!this.showEdges) return;
|
|
|
const connected = new Set([nodeId]);
|
|
|
this.graph.getEdges().forEach((edge) => {
|
|
|
const model = edge.getModel();
|
|
|
@@ -580,6 +582,7 @@ class KnowledgeMindmapGraph {
|
|
|
}
|
|
|
|
|
|
clearNeighborHighlight() {
|
|
|
+ if (!this.showEdges) return;
|
|
|
this.graph.getEdges().forEach((edge) => {
|
|
|
this.graph.clearItemStates(edge, ['hover', 'crosshover']);
|
|
|
});
|
|
|
@@ -685,13 +688,13 @@ class KnowledgeMindmapGraph {
|
|
|
}
|
|
|
|
|
|
focusOnLowestMastery() {
|
|
|
- if (!this.graph) return null;
|
|
|
+ if (!this.graph) return;
|
|
|
|
|
|
const entries = Object.entries(this.masteryData || {}).filter(
|
|
|
([, value]) =>
|
|
|
value && typeof value.mastery_level === 'number'
|
|
|
);
|
|
|
- if (!entries.length) return null;
|
|
|
+ if (!entries.length) return;
|
|
|
|
|
|
let targetId = null;
|
|
|
let minLevel = Infinity;
|
|
|
@@ -703,7 +706,7 @@ class KnowledgeMindmapGraph {
|
|
|
}
|
|
|
});
|
|
|
|
|
|
- if (!targetId) return null;
|
|
|
+ if (!targetId) return;
|
|
|
|
|
|
this.graph.getNodes().forEach((node) => {
|
|
|
this.graph.clearItemStates(node);
|
|
|
@@ -711,14 +714,13 @@ class KnowledgeMindmapGraph {
|
|
|
|
|
|
const item = this.graph.findById(targetId);
|
|
|
if (item) {
|
|
|
+ this.graph.zoom(2);
|
|
|
this.graph.focusItem(item, true, {
|
|
|
easing: 'easeCubic',
|
|
|
duration: 500,
|
|
|
});
|
|
|
this.graph.setItemState(item, 'selected', true);
|
|
|
- return item;
|
|
|
}
|
|
|
- return null;
|
|
|
}
|
|
|
|
|
|
refreshGraph() {
|
|
|
@@ -748,30 +750,18 @@ class KnowledgeMindmapGraph {
|
|
|
this.clearRelationEdges();
|
|
|
this.drawRelationEdges();
|
|
|
this.applyNodeStates();
|
|
|
- this.startEdgeFlows();
|
|
|
- const focused = this.focusOnLowestMastery();
|
|
|
- this.zoomAfterFocus(focused);
|
|
|
+ if (!this.showEdges) {
|
|
|
+ this.hideAllEdges();
|
|
|
+ } else {
|
|
|
+ this.startEdgeFlows();
|
|
|
+ }
|
|
|
+ this.focusOnLowestMastery();
|
|
|
this.graph.paint();
|
|
|
this.setupFocusListener();
|
|
|
}
|
|
|
|
|
|
- zoomAfterFocus(focusedItem = null) {
|
|
|
- if (!this.graph) return;
|
|
|
- const hasMastery = Object.keys(this.masteryData || {}).length > 0;
|
|
|
- if (!hasMastery) return;
|
|
|
- const model = focusedItem?.getModel?.() || null;
|
|
|
- const center = model?.x && model?.y
|
|
|
- ? { x: model.x, y: model.y }
|
|
|
- : {
|
|
|
- x: this.graph.get('width') / 2,
|
|
|
- y: this.graph.get('height') / 2,
|
|
|
- };
|
|
|
- const targetZoom = Math.min(2.0, Math.max(1.4, this.graph.getZoom() * 1.35));
|
|
|
- this.graph.zoomTo(targetZoom, center);
|
|
|
- }
|
|
|
-
|
|
|
bindEdgeTooltip() {
|
|
|
- if (!this.graph) return;
|
|
|
+ if (!this.graph || !this.showEdges) return;
|
|
|
const tooltipEl = document.createElement('div');
|
|
|
tooltipEl.style.position = 'fixed';
|
|
|
tooltipEl.style.pointerEvents = 'none';
|
|
|
@@ -811,10 +801,10 @@ class KnowledgeMindmapGraph {
|
|
|
});
|
|
|
|
|
|
this.graph.on('edge:mouseleave', () => hide());
|
|
|
- this.graph.on('canvas:mouseleave', () => hide());
|
|
|
}
|
|
|
|
|
|
redrawRelationEdges() {
|
|
|
+ if (!this.showRelationEdges || !this.showEdges) return;
|
|
|
this.clearRelationEdges();
|
|
|
this.drawRelationEdges();
|
|
|
}
|
|
|
@@ -832,7 +822,7 @@ class KnowledgeMindmapGraph {
|
|
|
this.clickTimer = setTimeout(() => {
|
|
|
const model = evt.item?.getModel();
|
|
|
if (!model) return;
|
|
|
- if (model.locked) return;
|
|
|
+ // if (model.locked) return;
|
|
|
|
|
|
this.graph.getNodes().forEach((node) => {
|
|
|
this.graph.clearItemStates(node);
|
|
|
@@ -1097,6 +1087,13 @@ class KnowledgeMindmapGraph {
|
|
|
);
|
|
|
}
|
|
|
|
|
|
+ hideAllEdges() {
|
|
|
+ if (!this.graph) return;
|
|
|
+ this.graph.getEdges().forEach((edge) => {
|
|
|
+ this.graph.hideItem(edge);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
}
|
|
|
|
|
|
// 定义KnowledgeMindmapGraph类,确保G6已加载
|