优化图谱渲染
parent
43eeb9788c
commit
4dcd27180d
|
|
@ -143,6 +143,20 @@ function renderGraph(payload) {
|
|||
.map((e) => ({ ...e, sourceNode: nodeById.get(e.source), targetNode: nodeById.get(e.target) }))
|
||||
.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 area = svgW * svgH;
|
||||
const repulsionStr = Math.max(3000, area / Math.max(n, 1));
|
||||
|
|
@ -203,9 +217,45 @@ function renderGraph(payload) {
|
|||
text.textContent = truncate(node.label, TRUNCATE_LENGTH);
|
||||
g.appendChild(text);
|
||||
mainGroup.appendChild(g);
|
||||
|
||||
// ── hover highlight ──
|
||||
g.addEventListener('mouseenter', () => applyFocus(node));
|
||||
g.addEventListener('mouseleave', () => { if (!isDragging) clearFocus(); });
|
||||
|
||||
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() {
|
||||
for (let i = 0; i < dataEdges.length; i++) {
|
||||
const edge = dataEdges[i], el = edgeEls[i];
|
||||
|
|
@ -330,6 +380,7 @@ function renderGraph(payload) {
|
|||
dragOccurred = false;
|
||||
dragNode = dataNodes.find((n) => n.id === target.dataset.nodeId);
|
||||
if (dragNode) {
|
||||
applyFocus(dragNode);
|
||||
const r = graphSvg.getBoundingClientRect();
|
||||
dragOffX = (e.clientX - r.left - zoomTransform.x) / zoomTransform.k - dragNode.x;
|
||||
dragOffY = (e.clientY - r.top - zoomTransform.y) / zoomTransform.k - dragNode.y;
|
||||
|
|
@ -367,6 +418,7 @@ function renderGraph(payload) {
|
|||
if (el) el.style.cursor = "grab";
|
||||
dragNode.vx = 0;
|
||||
dragNode.vy = 0;
|
||||
clearFocus();
|
||||
wakeSim();
|
||||
}
|
||||
isDragging = false;
|
||||
|
|
|
|||
|
|
@ -862,6 +862,17 @@ textarea {
|
|||
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 {
|
||||
background: rgba(30, 41, 59, 0.3);
|
||||
}
|
||||
|
|
@ -1143,6 +1154,25 @@ textarea {
|
|||
|
||||
.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 {
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
|
|
|
|||
Loading…
Reference in New Issue