优化图谱渲染
parent
43eeb9788c
commit
4dcd27180d
|
|
@ -143,6 +143,20 @@ function renderGraph(payload) {
|
||||||
.map((e) => ({ ...e, sourceNode: nodeById.get(e.source), targetNode: nodeById.get(e.target) }))
|
.map((e) => ({ ...e, sourceNode: nodeById.get(e.source), targetNode: nodeById.get(e.target) }))
|
||||||
.filter((e) => e.sourceNode && e.targetNode);
|
.filter((e) => e.sourceNode && e.targetNode);
|
||||||
|
|
||||||
|
// ── adjacency map for hover highlight ──
|
||||||
|
const nodeEdges = new Map(); // nodeId -> Set of edge indices
|
||||||
|
const nodeNeighbors = new Map(); // nodeId -> Set of neighbor nodeIds
|
||||||
|
dataNodes.forEach(n => {
|
||||||
|
nodeEdges.set(n.id, new Set());
|
||||||
|
nodeNeighbors.set(n.id, new Set());
|
||||||
|
});
|
||||||
|
dataEdges.forEach((e, i) => {
|
||||||
|
nodeEdges.get(e.source).add(i);
|
||||||
|
nodeEdges.get(e.target).add(i);
|
||||||
|
nodeNeighbors.get(e.source).add(e.target);
|
||||||
|
nodeNeighbors.get(e.target).add(e.source);
|
||||||
|
});
|
||||||
|
|
||||||
const n = dataNodes.length;
|
const n = dataNodes.length;
|
||||||
const area = svgW * svgH;
|
const area = svgW * svgH;
|
||||||
const repulsionStr = Math.max(3000, area / Math.max(n, 1));
|
const repulsionStr = Math.max(3000, area / Math.max(n, 1));
|
||||||
|
|
@ -203,9 +217,45 @@ function renderGraph(payload) {
|
||||||
text.textContent = truncate(node.label, TRUNCATE_LENGTH);
|
text.textContent = truncate(node.label, TRUNCATE_LENGTH);
|
||||||
g.appendChild(text);
|
g.appendChild(text);
|
||||||
mainGroup.appendChild(g);
|
mainGroup.appendChild(g);
|
||||||
|
|
||||||
|
// ── hover highlight ──
|
||||||
|
g.addEventListener('mouseenter', () => applyFocus(node));
|
||||||
|
g.addEventListener('mouseleave', () => { if (!isDragging) clearFocus(); });
|
||||||
|
|
||||||
return g;
|
return g;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ── focus / dim helpers ──
|
||||||
|
function applyFocus(hoveredNode) {
|
||||||
|
const connectedEdges = nodeEdges.get(hoveredNode.id);
|
||||||
|
const connectedNodes = nodeNeighbors.get(hoveredNode.id);
|
||||||
|
dataNodes.forEach((n, idx) => {
|
||||||
|
const el = nodeEls[idx];
|
||||||
|
if (!el) return;
|
||||||
|
const isTarget = n.id === hoveredNode.id || (connectedNodes && connectedNodes.has(n.id));
|
||||||
|
el.classList.toggle('graph-dim', !isTarget);
|
||||||
|
el.classList.toggle('graph-focus', isTarget);
|
||||||
|
});
|
||||||
|
dataEdges.forEach((e, idx) => {
|
||||||
|
const el = edgeEls[idx];
|
||||||
|
if (!el) return;
|
||||||
|
const isConnected = connectedEdges && connectedEdges.has(idx);
|
||||||
|
el.classList.toggle('graph-dim', !isConnected);
|
||||||
|
el.classList.toggle('graph-focus', isConnected);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearFocus() {
|
||||||
|
dataNodes.forEach((n, idx) => {
|
||||||
|
const el = nodeEls[idx];
|
||||||
|
if (el) { el.classList.remove('graph-dim', 'graph-focus'); }
|
||||||
|
});
|
||||||
|
dataEdges.forEach((e, idx) => {
|
||||||
|
const el = edgeEls[idx];
|
||||||
|
if (el) { el.classList.remove('graph-dim', 'graph-focus'); }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function syncDom() {
|
function syncDom() {
|
||||||
for (let i = 0; i < dataEdges.length; i++) {
|
for (let i = 0; i < dataEdges.length; i++) {
|
||||||
const edge = dataEdges[i], el = edgeEls[i];
|
const edge = dataEdges[i], el = edgeEls[i];
|
||||||
|
|
@ -330,6 +380,7 @@ function renderGraph(payload) {
|
||||||
dragOccurred = false;
|
dragOccurred = false;
|
||||||
dragNode = dataNodes.find((n) => n.id === target.dataset.nodeId);
|
dragNode = dataNodes.find((n) => n.id === target.dataset.nodeId);
|
||||||
if (dragNode) {
|
if (dragNode) {
|
||||||
|
applyFocus(dragNode);
|
||||||
const r = graphSvg.getBoundingClientRect();
|
const r = graphSvg.getBoundingClientRect();
|
||||||
dragOffX = (e.clientX - r.left - zoomTransform.x) / zoomTransform.k - dragNode.x;
|
dragOffX = (e.clientX - r.left - zoomTransform.x) / zoomTransform.k - dragNode.x;
|
||||||
dragOffY = (e.clientY - r.top - zoomTransform.y) / zoomTransform.k - dragNode.y;
|
dragOffY = (e.clientY - r.top - zoomTransform.y) / zoomTransform.k - dragNode.y;
|
||||||
|
|
@ -367,6 +418,7 @@ function renderGraph(payload) {
|
||||||
if (el) el.style.cursor = "grab";
|
if (el) el.style.cursor = "grab";
|
||||||
dragNode.vx = 0;
|
dragNode.vx = 0;
|
||||||
dragNode.vy = 0;
|
dragNode.vy = 0;
|
||||||
|
clearFocus();
|
||||||
wakeSim();
|
wakeSim();
|
||||||
}
|
}
|
||||||
isDragging = false;
|
isDragging = false;
|
||||||
|
|
|
||||||
|
|
@ -862,6 +862,17 @@ textarea {
|
||||||
box-shadow: var(--shadow-lg);
|
box-shadow: var(--shadow-lg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.detail-modal {
|
||||||
|
border: none;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 0;
|
||||||
|
width: min(640px, 90vw);
|
||||||
|
max-height: 80vh;
|
||||||
|
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15);
|
||||||
|
inset: 0;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.detail-modal::backdrop {
|
.detail-modal::backdrop {
|
||||||
background: rgba(30, 41, 59, 0.3);
|
background: rgba(30, 41, 59, 0.3);
|
||||||
}
|
}
|
||||||
|
|
@ -1143,6 +1154,25 @@ textarea {
|
||||||
|
|
||||||
.graph-node:hover circle { opacity: 0.8; }
|
.graph-node:hover circle { opacity: 0.8; }
|
||||||
|
|
||||||
|
/* ── hover / drag dim + focus ── */
|
||||||
|
.graph-dim { opacity: 0.2 !important; transition: opacity 0.2s; }
|
||||||
|
.edge-wrap { transition: opacity 0.2s; }
|
||||||
|
|
||||||
|
.graph-focus circle {
|
||||||
|
filter: drop-shadow(0 0 8px rgba(6, 182, 212, 0.5));
|
||||||
|
}
|
||||||
|
.graph-focus text[data-type="node-label"] {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
.edge-wrap.graph-focus .graph-edge {
|
||||||
|
stroke: var(--gray-500);
|
||||||
|
stroke-width: 2.5;
|
||||||
|
}
|
||||||
|
.edge-wrap.graph-focus text[data-type="edge-label"] {
|
||||||
|
fill: var(--gray-700);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
.graph-node text {
|
.graph-node text {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue