feat(visualizer): improve chart interactions

- Rename Drift → Trade in UI\n- Dynamic price precision for low prices\n- Fib retracement: select + move in X+Y\n- Ctrl+wheel vertical zoom, Ctrl+drag vertical pan\n- Auto Scale toggle for price scale
This commit is contained in:
u1
2026-01-06 17:46:33 +01:00
parent e20a1f5198
commit 6107c4e0ef
5 changed files with 345 additions and 32 deletions

View File

@@ -31,9 +31,12 @@ export default function ChartPanel({
const [fibStart, setFibStart] = useState<FibAnchor | null>(null);
const [fib, setFib] = useState<FibRetracement | null>(null);
const [fibDraft, setFibDraft] = useState<FibRetracement | null>(null);
const [fibMove, setFibMove] = useState<{ start: FibAnchor; origin: FibRetracement } | null>(null);
const [priceAutoScale, setPriceAutoScale] = useState(true);
const chartApiRef = useRef<IChartApi | null>(null);
const activeToolRef = useRef(activeTool);
const fibStartRef = useRef<FibAnchor | null>(fibStart);
const fibMoveRef = useRef<{ start: FibAnchor; origin: FibRetracement } | null>(fibMove);
const pendingMoveRef = useRef<FibAnchor | null>(null);
const rafRef = useRef<number | null>(null);
@@ -57,6 +60,7 @@ export default function ChartPanel({
useEffect(() => {
activeToolRef.current = activeTool;
if (activeTool === 'fib-retracement') setFibMove(null);
if (activeTool !== 'fib-retracement') {
setFibStart(null);
setFibDraft(null);
@@ -67,9 +71,20 @@ export default function ChartPanel({
fibStartRef.current = fibStart;
}, [fibStart]);
useEffect(() => {
fibMoveRef.current = fibMove;
}, [fibMove]);
useEffect(() => {
const onKeyDown = (e: KeyboardEvent) => {
if (e.key !== 'Escape') return;
if (fibMoveRef.current) {
setFibMove(null);
setFibDraft(null);
return;
}
if (activeToolRef.current !== 'fib-retracement') return;
setFibStart(null);
setFibDraft(null);
@@ -108,6 +123,8 @@ export default function ChartPanel({
onTimeframeChange={onTimeframeChange}
showIndicators={showIndicators}
onToggleIndicators={onToggleIndicators}
priceAutoScale={priceAutoScale}
onTogglePriceAutoScale={() => setPriceAutoScale((v) => !v)}
seriesLabel={seriesLabel}
isFullscreen={isFullscreen}
onToggleFullscreen={() => setIsFullscreen((v) => !v)}
@@ -126,6 +143,7 @@ export default function ChartPanel({
setFib(null);
setFibStart(null);
setFibDraft(null);
setFibMove(null);
}}
/>
<div className="chartCard__chart">
@@ -137,35 +155,68 @@ export default function ChartPanel({
bb20={indicators.bb20}
showIndicators={showIndicators}
fib={fibDraft ?? fib}
fibSelected={fibMove != null}
priceAutoScale={priceAutoScale}
onReady={({ chart }) => {
chartApiRef.current = chart;
}}
onChartClick={(p) => {
if (activeTool !== 'fib-retracement') return;
if (!fibStartRef.current) {
fibStartRef.current = p;
setFibStart(p);
setFibDraft({ a: p, b: p });
if (activeTool === 'fib-retracement') {
if (!fibStartRef.current) {
fibStartRef.current = p;
setFibStart(p);
setFibDraft({ a: p, b: p });
return;
}
setFib({ a: fibStartRef.current, b: p });
setFibStart(null);
fibStartRef.current = null;
setFibDraft(null);
setActiveTool('cursor');
return;
}
setFib({ a: fibStartRef.current, b: p });
setFibStart(null);
fibStartRef.current = null;
setFibDraft(null);
setActiveTool('cursor');
const move = fibMoveRef.current;
if (move) {
const deltaLogical = p.logical - move.start.logical;
const deltaPrice = p.price - move.start.price;
setFib({
a: { logical: move.origin.a.logical + deltaLogical, price: move.origin.a.price + deltaPrice },
b: { logical: move.origin.b.logical + deltaLogical, price: move.origin.b.price + deltaPrice },
});
setFibMove(null);
setFibDraft(null);
return;
}
if (p.target === 'fib' && fib) {
setFibMove({ start: p, origin: fib });
setFibDraft(fib);
}
}}
onChartCrosshairMove={(p) => {
if (activeToolRef.current !== 'fib-retracement') return;
const start = fibStartRef.current;
if (!start) return;
pendingMoveRef.current = p;
if (rafRef.current != null) return;
rafRef.current = window.requestAnimationFrame(() => {
rafRef.current = null;
const move = pendingMoveRef.current;
const pointer = pendingMoveRef.current;
if (!pointer) return;
const move = fibMoveRef.current;
if (move) {
const deltaLogical = pointer.logical - move.start.logical;
const deltaPrice = pointer.price - move.start.price;
setFibDraft({
a: { logical: move.origin.a.logical + deltaLogical, price: move.origin.a.price + deltaPrice },
b: { logical: move.origin.b.logical + deltaLogical, price: move.origin.b.price + deltaPrice },
});
return;
}
if (activeToolRef.current !== 'fib-retracement') return;
const start2 = fibStartRef.current;
if (!move || !start2) return;
setFibDraft({ a: start2, b: move });
if (!start2) return;
setFibDraft({ a: start2, b: pointer });
});
}}
/>