From 79c66474311772bba26dec2bb4033431b43f90d5 Mon Sep 17 00:00:00 2001 From: hyowons Date: Tue, 9 Jun 2026 12:41:50 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=88=9C=EC=9E=90=EC=82=B0=20=EC=B0=A8?= =?UTF-8?q?=ED=8A=B8=20=EC=98=A4=EB=8A=98=20=ED=8F=AC=EC=9D=B8=ED=8A=B8?= =?UTF-8?q?=EB=8F=84=201=EC=B4=88=20=EC=8B=A4=EC=8B=9C=EA=B0=84=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99(=EB=9D=BC=EB=B2=A8=E2=86=94=EC=A0=90=20?= =?UTF-8?q?=EC=9D=BC=EC=B9=98)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 장중 차트의 오늘 값은 foot 라벨만 1초 라이브였고 점 위치는 페치 때만 갱신돼 둘이 어긋나 보였다. WS 틱마다 서버와 동일 스케일로 y를 재계산해 last-dot·live-halo·신고가 마커·라이브 점선 끝점·마지막 pt-dot을 함께 이동. 렌더 범위 밖이면 차트 가장자리로 클램프(다음 페치가 스케일 재계산). - net-chart-last에 스케일 노출(data-rt-nw-ylo/span/top/ih/x) - 라이브 점선에 live-seg 클래스로 baseline 점선과 구분 - rt-nw 페인트에 점·선분 이동 로직 추가 Co-Authored-By: Claude Opus 4.8 --- agents/stock/workspace/scripts/behive_web.py | 25 ++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/agents/stock/workspace/scripts/behive_web.py b/agents/stock/workspace/scripts/behive_web.py index d39a767..ab17347 100644 --- a/agents/stock/workspace/scripts/behive_web.py +++ b/agents/stock/workspace/scripts/behive_web.py @@ -3660,7 +3660,7 @@ def _render_net_worth_chart(series: list[tuple[str, int]], current_net: int | No if solid_d: svg_parts.append(f'') if dashed_d: - svg_parts.append(f'') + svg_parts.append(f'') if point_dots: svg_parts.append(''.join(point_dots)) if has_live: @@ -3729,6 +3729,7 @@ def _render_net_worth_chart(series: list[tuple[str, int]], current_net: int | No '' f'{html.escape(last_label_v)}' '' @@ -9076,13 +9077,33 @@ window.openPinModal = openPinModal; if(td) td.innerHTML=''+sgn(T.daypl)+'원'+(T.dpct!==null?' ('+(T.dpct>=0?'+':'')+T.dpct.toFixed(2)+'%)':'')+''; } }); - // 순자산 차트 오늘(장중) 포인트 값 라벨 실시간 — 점 위치(SVG)는 새로고침 시, 값 라벨만 라이브. + // 순자산 차트 오늘(장중) 포인트 — 값 라벨 + SVG 점·마지막 선분을 1초마다 함께 이동(라벨↔점 불일치 제거). document.querySelectorAll('.net-chart-last[data-rt-nw-owner="'+owner+'"][data-rt-nw-live="1"]').forEach(function(el){ var basenet=parseFloat(el.getAttribute('data-rt-nw-basenet')||'0'); var baseval=parseFloat(el.getAttribute('data-rt-nw-baseval')||'0'); var v=Math.round(baseval+(T.net-basenet)); // 순자산 변동분 = 손익 변동분(baseline·cf는 세션 중 고정) var s=(el.getAttribute('data-rt-nw-pnl')==='1') ? ((v>=0?'+':'−')+Math.abs(v).toLocaleString()+'원') : (v.toLocaleString()+'원'); el.textContent=s+' (장중)'; + // 점 위치도 같은 값으로 재계산 — 서버 렌더와 동일 스케일 y = top + (1 − (v−ylo)/span)·ih. + var ylo=parseFloat(el.getAttribute('data-rt-nw-ylo')); + var span=parseFloat(el.getAttribute('data-rt-nw-span')); + var top=parseFloat(el.getAttribute('data-rt-nw-top')); + var ih=parseFloat(el.getAttribute('data-rt-nw-ih')); + var lastx=parseFloat(el.getAttribute('data-rt-nw-x')); + if([ylo,span,top,ih,lastx].some(isNaN)) return; + var chart=el.closest('.net-chart'); if(!chart) return; + var svg=chart.querySelector('svg.net-chart-svg'); if(!svg) return; + var ny=top+(1-(v-ylo)/span)*ih; + ny=Math.max(top, Math.min(top+ih, ny)); // 렌더 당시 범위 밖이면 차트 가장자리로 클램프(다음 페치가 스케일 재계산) + var nys=ny.toFixed(2); + ['.last-dot','.live-halo','.ath-ring'].forEach(function(sel){ + var c=svg.querySelector(sel); if(c) c.setAttribute('cy', nys); + }); + var al=svg.querySelector('.ath-label'); if(al) al.setAttribute('y', Math.max(ny-14, top+12).toFixed(2)); + var seg=svg.querySelector('.line.dashed.live-seg'); // 마지막(확정점→라이브) 점선 끝점 y 교체 + if(seg){ var d=seg.getAttribute('d')||''; seg.setAttribute('d', d.replace(/L\s*([\d.]+),[\d.]+\s*$/, 'L $1,'+nys)); } + var dots=svg.querySelectorAll('.pt-dot'); // 라이브 점과 같은 x의 pt-dot도 이동 + if(dots.length){ var ld=dots[dots.length-1]; if(Math.abs(parseFloat(ld.getAttribute('cx'))-lastx)<0.5) ld.setAttribute('cy', nys); } }); } // KPI 총합 — owner 탭 scope의 consolidated pane 행만 합산(중복합산 방지). 시세 미수신 종목은 data-rt-px(서버값) fallback.