feat(graph+info): 重新设计文档关联板块(玻璃卡片+双向提示)与知识图谱显示(关联高亮+分组色点+克制配色)
This commit is contained in:
@@ -91,8 +91,18 @@ export function LinkGraph({ onSwitchToEditor }: { onSwitchToEditor?: () => void
|
||||
.map((e) => ({ source: idx.get(e.source)!, target: idx.get(e.target)! }));
|
||||
|
||||
// 曲线连线(贝塞尔弧):比直线更优雅、更符合"图谱"观感
|
||||
const linkSel = g.append('g').attr('stroke', 'var(--line)').attr('stroke-opacity', 0.6)
|
||||
.selectAll('path').data(links).join('path').attr('fill', 'none').attr('stroke-width', 1.1);
|
||||
// 与当前文档直接相连的节点集合——用于高亮"关联"关系
|
||||
const curPath = useStore.getState().currentPath;
|
||||
const linkedIds = new Set<string>();
|
||||
for (const e of data.edges) {
|
||||
if (e.source === curPath) linkedIds.add(e.target);
|
||||
if (e.target === curPath) linkedIds.add(e.source);
|
||||
}
|
||||
const linkSel = g.append('g')
|
||||
.selectAll('path').data(links).join('path').attr('fill', 'none')
|
||||
.attr('stroke', (d: any) => (d.source.id === curPath || d.target.id === curPath) ? 'var(--ink-2)' : 'var(--line)')
|
||||
.attr('stroke-opacity', (d: any) => (d.source.id === curPath || d.target.id === curPath) ? 0.9 : 0.5)
|
||||
.attr('stroke-width', (d: any) => (d.source.id === curPath || d.target.id === curPath) ? 2 : 1.1);
|
||||
|
||||
// 布局参数:刻意拉大间距,卡片(180 宽)之间留足空白,避免拥挤
|
||||
const sim = forceSimulation<SimNode>(nodes)
|
||||
@@ -118,31 +128,37 @@ export function LinkGraph({ onSwitchToEditor }: { onSwitchToEditor?: () => void
|
||||
const len = Math.max(1, Math.hypot(dx, dy));
|
||||
return 'M' + sx + ',' + sy + ' Q' + (mx + (nx / len) * bend * dir) + ',' + (my + (ny / len) * bend * dir) + ' ' + tx + ',' + ty;
|
||||
};
|
||||
// 当前打开的文档作为深墨焦点节点
|
||||
const curPath = useStore.getState().currentPath;
|
||||
// 卡片:深墨焦点(当前文档)+ 玻璃底卡 + 分组色点 + 关联高亮(保持每卡 1 个 rect)
|
||||
const nodeSel = g.append('g').selectAll('g').data(nodes).join('g').attr('cursor', 'pointer')
|
||||
// 阴影 filter 加在 g 层:若加在卡片 rect 上会导致该 rect 的 pointer-events 命中失效(点击穿透)
|
||||
.attr('filter', (d: any) => d.id === curPath ? 'url(#graph-card-focus)' : 'url(#graph-card-shadow)');
|
||||
|
||||
// 卡片:圆角底卡 + 标题 + 路径 + 星标(无图标,更简洁);当前文档 = 深墨焦点节点,文字居中
|
||||
nodeSel.append('rect').attr('class', 'gcard').attr('x', -CARD_W / 2).attr('y', -CARD_H / 2).attr('width', CARD_W).attr('height', CARD_H).attr('rx', 13)
|
||||
nodeSel.append('rect').attr('class', 'gcard').attr('x', -CARD_W / 2).attr('y', -CARD_H / 2).attr('width', CARD_W).attr('height', CARD_H).attr('rx', 14)
|
||||
.attr('fill', (d: any) => d.id === curPath ? 'var(--ink)' : 'var(--float)')
|
||||
.attr('stroke', (d: any) => d.id === curPath ? 'var(--ink)' : 'var(--line)')
|
||||
.attr('stroke-width', 1.2)
|
||||
.attr('stroke', (d: any) => d.id === curPath ? 'var(--ink)' : (linkedIds.has(d.id) ? 'var(--ink-2)' : 'var(--line)'))
|
||||
.attr('stroke-width', (d: any) => (d.id === curPath || linkedIds.has(d.id)) ? 1.6 : 1.1)
|
||||
.attr('pointer-events', 'all');
|
||||
// 分组色点(左上角,circle 不增加卡片 rect 计数)
|
||||
nodeSel.append('circle').attr('cx', -CARD_W / 2 + 10).attr('cy', -CARD_H / 2 + 10).attr('r', 3.4)
|
||||
.attr('fill', (d: any) => d.id === curPath ? 'var(--space)' : (d.color || 'var(--ink-3)'))
|
||||
.attr('opacity', 0.9);
|
||||
// 标题(水平居中)
|
||||
nodeSel.append('text').attr('x', 0).attr('y', -2).attr('text-anchor', 'middle')
|
||||
nodeSel.append('text').attr('x', 0).attr('y', -3).attr('text-anchor', 'middle')
|
||||
.attr('fill', (d: any) => d.id === curPath ? 'var(--space)' : 'var(--ink)')
|
||||
.attr('font-size', 13).attr('font-weight', (d: any) => d.id === curPath ? 600 : 550).attr('font-family', 'sans-serif')
|
||||
.attr('font-size', 13).attr('font-weight', (d: any) => d.id === curPath ? 650 : (linkedIds.has(d.id) ? 600 : 550)).attr('font-family', 'sans-serif')
|
||||
.text((d) => truncate(d.title, 16));
|
||||
// 路径副文本(水平居中)
|
||||
nodeSel.append('text').attr('x', 0).attr('y', 13).attr('text-anchor', 'middle')
|
||||
.attr('fill', (d: any) => d.id === curPath ? 'rgba(255,255,255,0.7)' : 'var(--ink-4)')
|
||||
nodeSel.append('text').attr('x', 0).attr('y', 14).attr('text-anchor', 'middle')
|
||||
.attr('fill', (d: any) => d.id === curPath ? 'rgba(255,255,255,0.72)' : 'var(--ink-4)')
|
||||
.attr('font-size', 10).attr('font-family', 'sans-serif')
|
||||
.text((d) => truncate(d.basePath || d.id, 18));
|
||||
// 星标(右上角)
|
||||
nodeSel.filter((d) => d.star).append('text').attr('x', CARD_W / 2 - 14).attr('y', 4)
|
||||
nodeSel.filter((d) => d.star).append('text').attr('x', CARD_W / 2 - 14).attr('y', 5)
|
||||
.attr('fill', (d: any) => d.id === curPath ? 'var(--space)' : 'var(--ink-3)').attr('font-size', 14).text('★');
|
||||
// 与当前文档关联的卡片右上角加一个小圆点提示(circle,不影响 rect 计数)
|
||||
nodeSel.filter((d: any) => linkedIds.has(d.id) && d.id !== curPath).append('circle')
|
||||
.attr('cx', CARD_W / 2 - 12).attr('cy', -CARD_H / 2 + 10).attr('r', 3)
|
||||
.attr('fill', 'var(--ink-2)').attr('opacity', 0.85);
|
||||
|
||||
// 悬停高亮(CSS,不增加 rect)
|
||||
svg.append('style').text('.gcard { transition: stroke-width .16s var(--ease), stroke .16s var(--ease); } g[data-hover] .gcard { stroke: var(--line-strong); stroke-width: 1.7; }');
|
||||
|
||||
Reference in New Issue
Block a user