From 32586d3ed48fe253ccd6449d770ce0ddec8822bf Mon Sep 17 00:00:00 2001 From: TianXuan Developer Date: Sat, 25 Jul 2026 10:56:23 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E7=A7=BB=E9=99=A4Canvas=E5=83=8F?= =?UTF-8?q?=E7=B4=A0=E7=BC=93=E5=AD=98=E2=80=94=E6=AF=8F=E5=B8=A7=E5=AE=9E?= =?UTF-8?q?=E6=97=B6latLngToContainerPoint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根因: _trafficEdgeCache缓存旧像素位置,地图拖动/缩放后不更新 修复: - 完全移除像素缓存,每帧重新计算latLngToContainerPoint - Canvas为#saMap子元素,绝对定位,跟随容器 - 移除moveend缓存清除逻辑(不再需要) 效果: 地图平移/缩放时流量边自动跟随,不再是'贴片' Co-Authored-By: Claude --- .../simple_analysis/simple_analysis.html | 74 +++++++++---------- 1 file changed, 33 insertions(+), 41 deletions(-) diff --git a/simple_analysis/templates/simple_analysis/simple_analysis.html b/simple_analysis/templates/simple_analysis/simple_analysis.html index ca1be5d..ab6adc6 100644 --- a/simple_analysis/templates/simple_analysis/simple_analysis.html +++ b/simple_analysis/templates/simple_analysis/simple_analysis.html @@ -2577,9 +2577,7 @@ let _trafficPrevFrameTs = 0; let _trafficSeekTarget = null; // Canvas overlay -let _trafficCanvas = null; -let _trafficCtx = null; -let _trafficEdgeCache = {}; +let _trafficCanvasId = 'saTrafficCanvas'; function toggleTrafficPanel() { const panel = document.getElementById('saTrafficPanel'); @@ -2596,25 +2594,25 @@ function toggleTrafficPanel() { function _ensureFlowCanvas() { if (!SA.map) return null; - if (_trafficCanvas && _trafficCanvas.parentNode) return _trafficCanvas; - const mapEl = SA.map.getContainer(); - const c = document.createElement('canvas'); - c.id = 'saTrafficCanvasOverlay'; - c.style.cssText = 'position:absolute;top:0;left:0;pointer-events:none;z-index:10000;'; - mapEl.appendChild(c); + let c = document.getElementById(_trafficCanvasId); + if (!c) { + c = document.createElement('canvas'); + c.id = _trafficCanvasId; + c.style.cssText = 'position:absolute;top:0;left:0;pointer-events:none;z-index:10000;'; + SA.map.getContainer().appendChild(c); + } const s = SA.map.getSize(); - c.width = s.x; c.height = s.y; - c.style.width = s.x + 'px'; c.style.height = s.y + 'px'; - SA.map.on('resize', () => { const sz = SA.map.getSize(); c.width = sz.x; c.height = sz.y; c.style.width = sz.x + 'px'; c.style.height = sz.y + 'px'; }); - _trafficCtx = c.getContext('2d'); - _trafficCanvas = c; + if (c.width !== s.x || c.height !== s.y) { + c.width = s.x; c.height = s.y; + c.style.width = s.x + 'px'; c.style.height = s.y + 'px'; + } return c; } function _removeFlowCanvas() { cancelAnimationFrame(_trafficAnimId); _trafficAnimId = null; - if (_trafficCanvas) { _trafficCanvas.remove(); _trafficCanvas = null; _trafficCtx = null; } - _trafficEdgeCache = {}; + const c = document.getElementById(_trafficCanvasId); + if (c) c.remove(); } function tsToBJ(ts) { const d = new Date(ts * 1000); return d.toISOString().replace('T',' ').slice(0,19); } @@ -2631,7 +2629,7 @@ function loadTrafficData() { .then(d => { hideLoading(); if (d.error) { showError(d.error); return; } - _trafficEdgeCache = {}; + // Edge positions computed fresh each frame — no cache needed let ws = d.window_seconds || 10; let minTs = d.min_ts || (d.windows[0]?.ts || 0); let maxTs = d.max_ts || (d.windows[d.n_windows-1]?.ts_end || minTs + ws); @@ -2672,7 +2670,6 @@ function loadTrafficData() { document.getElementById('saTrafficSlider').value = 0; document.getElementById('saTrafficInfo').textContent = (d.windows||[]).length + ' 窗口 · ' + Object.keys(d.nodes_coords||{}).length + ' 节点 · 跨度' + ((maxTs-minTs)/60).toFixed(0) + 'min'; - SA.map.off('moveend.flowCache').on('moveend.flowCache', () => { _trafficEdgeCache = {}; if (_trafficPlaying) renderTrafficCrossfade(); }); document.getElementById('saTrafficTime').textContent = tsToBJ(_trafficCurrentTime); trafficStart(); }) @@ -2772,7 +2769,7 @@ function trafficAnimate(timestamp) { function renderTrafficCrossfade() { const c = _ensureFlowCanvas(); if (!c || !SA.map || !_trafficData) return; - const ctx = _trafficCtx; + const ctx = c.getContext('2d'); if (!ctx) return; ctx.clearRect(0, 0, c.width, c.height); @@ -2809,39 +2806,34 @@ function renderTrafficCrossfade() { edges.length + ' 边 · ' + activeNodes.size + ' 节点 · ' + tsToBJ(_trafficCurrentTime); edges.forEach(e => { - let cached = _trafficEdgeCache[e.source + '|' + e.target]; - if (!cached) { - const sc = coords[e.source], dc = coords[e.target]; - if (!sc || !dc) { _trafficEdgeCache[e.source+'|'+e.target] = null; return; } - const p1 = SA.map.latLngToContainerPoint([sc[0], sc[1]]); - const p2 = SA.map.latLngToContainerPoint([dc[0], dc[1]]); - if (!p1 || !p2) return; - cached = {p1x:p1.x, p1y:p1.y, p2x:p2.x, p2y:p2.y, midX:(p1.x+p2.x)/2, midY:(p1.y+p2.y)/2 - Math.abs(p2.x-p1.x)*0.1}; - _trafficEdgeCache[e.source+'|'+e.target] = cached; - } - if (!cached) return; + const sc = coords[e.source], dc = coords[e.target]; + if (!sc || !dc) return; + const p1 = SA.map.latLngToContainerPoint([sc[0], sc[1]]); + const p2 = SA.map.latLngToContainerPoint([dc[0], dc[1]]); + if (!p1 || !p2) return; + const midX = (p1.x + p2.x) / 2; + const midY = (p1.y + p2.y) / 2 - Math.abs(p2.x - p1.x) * 0.1; const ratio = Math.min(1, e.count / maxCount); const alpha = 0.4 + 0.6 * e.weight; - // Edge trace with source→destination color gradient - const grad = ctx.createLinearGradient(cached.p1x, cached.p1y, cached.p2x, cached.p2y); - grad.addColorStop(0, 'rgba(46,196,182,' + alpha.toFixed(2) + ')'); // src: teal - grad.addColorStop(1, 'rgba(230,57,70,' + alpha.toFixed(2) + ')'); // dst: red + // Edge trace with src→dst color gradient + const grad = ctx.createLinearGradient(p1.x, p1.y, p2.x, p2.y); + grad.addColorStop(0, 'rgba(46,196,182,' + alpha.toFixed(2) + ')'); + grad.addColorStop(1, 'rgba(230,57,70,' + alpha.toFixed(2) + ')'); ctx.beginPath(); - ctx.moveTo(cached.p1x, cached.p1y); - ctx.quadraticCurveTo(cached.midX, cached.midY, cached.p2x, cached.p2y); + ctx.moveTo(p1.x, p1.y); + ctx.quadraticCurveTo(midX, midY, p2.x, p2.y); ctx.strokeStyle = grad; ctx.lineWidth = 1.5 + 4 * ratio; ctx.stroke(); - // Particles: source(teal)→destination(red), speed ∝ traffic volume - const speedMul = 0.5 + 1.5 * ratio; // higher traffic → faster particles + // Flowing particles + const speedMul = 0.5 + 1.5 * ratio; const nP = Math.ceil(3 + 6 * ratio); for (let pi = 0; pi < nP; pi++) { const pt = ((Date.now() / 2000 * speedMul + pi / nP) % 1); - const px = (1-pt)*(1-pt)*cached.p1x + 2*(1-pt)*pt*cached.midX + pt*pt*cached.p2x; - const py = (1-pt)*(1-pt)*cached.p1y + 2*(1-pt)*pt*cached.midY + pt*pt*cached.p2y; - // src teal→dst red gradient + const px = (1-pt)*(1-pt)*p1.x + 2*(1-pt)*pt*midX + pt*pt*p2.x; + const py = (1-pt)*(1-pt)*p1.y + 2*(1-pt)*pt*midY + pt*pt*p2.y; const cr = Math.round(46 + 184*pt), cg = Math.round(196 - 139*pt), cb = Math.round(182 - 112*pt); ctx.beginPath(); ctx.arc(px, py, 2 + 3*ratio, 0, Math.PI*2);