fix: 边线点击优化 — 双层线结构 + hover 高亮

- 每条边由底层粗透明线(12px)和上层可见线组成
- 粗透明线捕获点击,命中区域扩大10倍以上
- 线宽 2-6px(按通信频次),不透明度 0.45-0.80
- hover 时高亮:加粗+亮红+不透明
- tooltip 提示点击查看详情
- highlightNodeEdges 同步使用双层线

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
TianXuan Developer
2026-07-23 23:20:26 +08:00
parent 2f7173637a
commit 0f84497c82
@@ -1561,21 +1561,35 @@ function highlightNodeEdges() {
const src = ipCoord[e.source];
const dst = ipCoord[e.target];
if (!src || !dst) return;
const w = Math.min((e.comm_count || 1) / 3 + 1, 5);
const poly = L.polyline([src, dst], {
color: '#ff3333', weight: w, opacity: 0.75,
interactive: true, bubblingMouseEvents: true,
const w = Math.max(2, Math.min((e.comm_count || 1) / 3 + 1, 5));
// 底层粗透明线用于点击捕获
const hitPoly = L.polyline([src, dst], {
color: '#ff3333', weight: 12, opacity: 0,
interactive: true, bubblingMouseEvents: false,
});
poly.bindTooltip(
// 上层细线用于展示
const visPoly = L.polyline([src, dst], {
color: '#ff3333', weight: w, opacity: 0.8,
interactive: false, bubblingMouseEvents: true,
});
const pair = L.layerGroup([hitPoly, visPoly]);
pair.bindTooltip(
`${escapeHtml(e.source)}${escapeHtml(e.target)}<br>` +
`${e.comm_count} 次 · ${(e.total_bytes/1024).toFixed(1)} KB`,
{sticky: true}
`${e.comm_count} 次 · ${(e.total_bytes/1024).toFixed(1)} KB` +
`<br><span style="color:#aaa;">🖱 点击查看详情</span>`,
{sticky: true, direction: 'top'}
);
poly.on('click', function(ev) {
pair.on('click', function(ev) {
L.DomEvent.stopPropagation(ev);
loadEdgeDetail(e);
});
polys.push(poly);
pair.on('mouseover', function() {
visPoly.setStyle({ color: '#e60000', weight: Math.min(w + 2, 8), opacity: 1 });
});
pair.on('mouseout', function() {
visPoly.setStyle({ color: '#ff3333', weight: w, opacity: 0.8 });
});
polys.push(pair);
});
if (polys.length) {
@@ -1594,7 +1608,16 @@ function highlightEdge(idx) {
if (!_edgeLayer) return;
const layers = _edgeLayer.getLayers();
layers.forEach((l, i) => {
l.setStyle({color: i === idx ? '#ff0000' : '#ff4444', weight: i === idx ? 4 : (l.options?.weight || 1), opacity: i === idx ? 1 : 0.5});
// Each entry is a layerGroup containing [hitPoly, visPoly]
const children = l.getLayers ? l.getLayers() : [];
const visPoly = children.length >= 2 ? children[1] : children[0];
if (visPoly && visPoly.setStyle) {
visPoly.setStyle({
color: i === idx ? '#e60000' : '#ff6666',
weight: i === idx ? 6 : 2,
opacity: i === idx ? 1 : 0.3
});
}
});
}
@@ -1635,21 +1658,44 @@ function renderEdgesOnMap() {
if (shown >= 300) return;
shown++;
const weight = Math.min((e.comm_count || 1) / maxCount * 3 + 0.5, 4);
const poly = L.polyline([srcCoord, dstCoord], {
color: '#ff6b6b', weight: weight, opacity: 0.35,
interactive: true, bubblingMouseEvents: true,
// 线宽:最小2px,最大6px,按频次占比缩放
const ratio = (e.comm_count || 1) / maxCount;
const weight = Math.max(2, Math.min(ratio * 5 + 1.5, 6));
const opacity = 0.45 + ratio * 0.35; // 0.45 ~ 0.80
// 底层的粗透明线 —— 用于点击捕获(不可见但可交互)
const hitPoly = L.polyline([srcCoord, dstCoord], {
color: '#ff6b6b', weight: 12, opacity: 0,
interactive: true, bubblingMouseEvents: false,
});
poly.bindTooltip(
// 上层的细线 —— 用于展示
const visPoly = L.polyline([srcCoord, dstCoord], {
color: '#ff6b6b', weight: weight, opacity: opacity,
interactive: false, bubblingMouseEvents: true,
});
// 将两条线绑定为一个组,共享 tooltip 和 click
const pair = L.layerGroup([hitPoly, visPoly]);
pair.bindTooltip(
`<b>${escapeHtml(e.source)}${escapeHtml(e.target)}</b><br>` +
`${e.comm_count} 次 · ${e.total_bytes ? (e.total_bytes/1024).toFixed(1)+'KB' : ''}`,
{sticky: true}
`${e.comm_count} 次 · ${e.total_bytes ? (e.total_bytes/1024).toFixed(1)+'KB' : ''}` +
`<br><span style="color:#aaa;">🖱 点击查看详情</span>`,
{sticky: true, direction: 'top'}
);
poly.on('click', function(ev) {
pair.on('click', function(ev) {
L.DomEvent.stopPropagation(ev);
loadEdgeDetail(e);
});
edgePolys.push(poly);
// Hover 高亮
pair.on('mouseover', function() {
visPoly.setStyle({ color: '#e63946', weight: Math.min(weight + 2, 8), opacity: 0.95 });
});
pair.on('mouseout', function() {
visPoly.setStyle({ color: '#ff6b6b', weight: weight, opacity: opacity });
});
edgePolys.push(pair);
});
if (edgePolys.length) _edgeLayer = L.layerGroup(edgePolys).addTo(SA.map);