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:
@@ -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 });
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user