react-klinecharts-ui 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +244 -5
- package/dist/{chunk-47JXSAIT.cjs → chunk-PIFLJKYF.cjs} +47 -2
- package/dist/chunk-PIFLJKYF.cjs.map +1 -0
- package/dist/{chunk-B3NZ66YA.js → chunk-U325AHHX.js} +47 -3
- package/dist/chunk-U325AHHX.js.map +1 -0
- package/dist/extensions.cjs +9 -5
- package/dist/extensions.d.cts +18 -1
- package/dist/extensions.d.ts +18 -1
- package/dist/extensions.js +1 -1
- package/dist/index.cjs +925 -53
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +190 -2
- package/dist/index.d.ts +190 -2
- package/dist/index.js +871 -11
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-47JXSAIT.cjs.map +0 -1
- package/dist/chunk-B3NZ66YA.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { registerExtensions, TA_default } from './chunk-
|
|
2
|
-
export { TA_default as TA, abcd_default as abcd, anyWaves_default as anyWaves, arrow_default as arrow, bollTv_default as bollTv, brush_default as brush, cci_default as cci, circle_default as circle, eightWaves_default as eightWaves, elliottWave_default as elliottWave, fibRetracement_default as fibRetracement, fibonacciCircle_default as fibonacciCircle, fibonacciExtension_default as fibonacciExtension, fibonacciSegment_default as fibonacciSegment, fibonacciSpeedResistanceFan_default as fibonacciSpeedResistanceFan, fibonacciSpiral_default as fibonacciSpiral, fiveWaves_default as fiveWaves, gannBox_default as gannBox, gannFan_default as gannFan, hma_default as hma, ichimoku_default as ichimoku, indicators, longPosition_default as longPosition, maRibbon_default as maRibbon, macdTv_default as macdTv, measure_default as measure, orderLine_default as orderLine, overlays, parallelChannel_default as parallelChannel, parallelogram_default as parallelogram, pivotPoints_default as pivotPoints, ray_default as ray, rect_default as rect, registerExtensions, rsiTv_default as rsiTv, shortPosition_default as shortPosition, stochastic_default as stochastic, superTrend_default as superTrend, threeWaves_default as threeWaves, triangle_default as triangle, vwap_default as vwap, xabcd_default as xabcd } from './chunk-
|
|
1
|
+
import { registerExtensions, TA_default } from './chunk-U325AHHX.js';
|
|
2
|
+
export { TA_default as TA, abcd_default as abcd, anyWaves_default as anyWaves, arrow_default as arrow, bollTv_default as bollTv, brush_default as brush, cci_default as cci, circle_default as circle, depthOverlay_default as depthOverlay, eightWaves_default as eightWaves, elliottWave_default as elliottWave, fibRetracement_default as fibRetracement, fibonacciCircle_default as fibonacciCircle, fibonacciExtension_default as fibonacciExtension, fibonacciSegment_default as fibonacciSegment, fibonacciSpeedResistanceFan_default as fibonacciSpeedResistanceFan, fibonacciSpiral_default as fibonacciSpiral, fiveWaves_default as fiveWaves, gannBox_default as gannBox, gannFan_default as gannFan, hma_default as hma, ichimoku_default as ichimoku, indicators, longPosition_default as longPosition, maRibbon_default as maRibbon, macdTv_default as macdTv, measure_default as measure, orderLine_default as orderLine, overlays, parallelChannel_default as parallelChannel, parallelogram_default as parallelogram, pivotPoints_default as pivotPoints, ray_default as ray, rect_default as rect, registerExtensions, rsiTv_default as rsiTv, shortPosition_default as shortPosition, stochastic_default as stochastic, superTrend_default as superTrend, threeWaves_default as threeWaves, triangle_default as triangle, vwap_default as vwap, xabcd_default as xabcd } from './chunk-U325AHHX.js';
|
|
3
3
|
import { createContext, useContext, useReducer, useRef, useCallback, useEffect, useMemo, useState } from 'react';
|
|
4
4
|
import { registerOverlay, registerIndicator } from 'react-klinecharts';
|
|
5
5
|
import { jsx } from 'react/jsx-runtime';
|
|
@@ -643,9 +643,12 @@ var INDICATOR_PARAMS = {
|
|
|
643
643
|
};
|
|
644
644
|
|
|
645
645
|
// src/hooks/useIndicators.ts
|
|
646
|
+
var COLLAPSED_HEIGHT = 30;
|
|
646
647
|
function useIndicators() {
|
|
647
648
|
const { state, dispatch } = useKlinechartsUI();
|
|
648
649
|
const { undoRedoListenerRef } = useKlinechartsUIDispatch();
|
|
650
|
+
const paneHeightsRef = useRef({});
|
|
651
|
+
const collapsedPanesRef = useRef(/* @__PURE__ */ new Set());
|
|
649
652
|
const mainIndicators = useMemo(() => {
|
|
650
653
|
const activeNames = state.mainIndicators;
|
|
651
654
|
const inactive = MAIN_INDICATORS.filter((n) => !activeNames.includes(n));
|
|
@@ -815,6 +818,90 @@ function useIndicators() {
|
|
|
815
818
|
(name) => name in state.subIndicators,
|
|
816
819
|
[state.subIndicators]
|
|
817
820
|
);
|
|
821
|
+
const collapseSubIndicator = useCallback(
|
|
822
|
+
(name) => {
|
|
823
|
+
const paneId = state.subIndicators[name];
|
|
824
|
+
if (!state.chart || !paneId) return;
|
|
825
|
+
const currentSize = state.chart.getSize?.(paneId);
|
|
826
|
+
if (currentSize?.height && currentSize.height > COLLAPSED_HEIGHT) {
|
|
827
|
+
paneHeightsRef.current[paneId] = currentSize.height;
|
|
828
|
+
}
|
|
829
|
+
collapsedPanesRef.current.add(paneId);
|
|
830
|
+
state.chart.setPaneOptions?.({
|
|
831
|
+
id: paneId,
|
|
832
|
+
height: COLLAPSED_HEIGHT
|
|
833
|
+
});
|
|
834
|
+
state.chart.overrideIndicator({ name, id: `sub_${name}`, visible: false });
|
|
835
|
+
},
|
|
836
|
+
[state.chart, state.subIndicators]
|
|
837
|
+
);
|
|
838
|
+
const expandSubIndicator = useCallback(
|
|
839
|
+
(name) => {
|
|
840
|
+
const paneId = state.subIndicators[name];
|
|
841
|
+
if (!state.chart || !paneId) return;
|
|
842
|
+
const savedHeight = paneHeightsRef.current[paneId] ?? 100;
|
|
843
|
+
collapsedPanesRef.current.delete(paneId);
|
|
844
|
+
state.chart.setPaneOptions?.({
|
|
845
|
+
id: paneId,
|
|
846
|
+
height: savedHeight
|
|
847
|
+
});
|
|
848
|
+
state.chart.overrideIndicator({ name, id: `sub_${name}`, visible: true });
|
|
849
|
+
},
|
|
850
|
+
[state.chart, state.subIndicators]
|
|
851
|
+
);
|
|
852
|
+
const isSubIndicatorCollapsed = useCallback(
|
|
853
|
+
(name) => {
|
|
854
|
+
const paneId = state.subIndicators[name];
|
|
855
|
+
return paneId ? collapsedPanesRef.current.has(paneId) : false;
|
|
856
|
+
},
|
|
857
|
+
[state.subIndicators]
|
|
858
|
+
);
|
|
859
|
+
const reorderSubIndicator = useCallback(
|
|
860
|
+
(name, direction) => {
|
|
861
|
+
if (!state.chart) return;
|
|
862
|
+
const subNames = Object.keys(state.subIndicators);
|
|
863
|
+
const idx = subNames.indexOf(name);
|
|
864
|
+
if (idx === -1) return;
|
|
865
|
+
const swapIdx = direction === "up" ? idx - 1 : idx + 1;
|
|
866
|
+
if (swapIdx < 0 || swapIdx >= subNames.length) return;
|
|
867
|
+
const subStates = subNames.map((n) => {
|
|
868
|
+
const id = `sub_${n}`;
|
|
869
|
+
const indicator = state.chart.getIndicators({ id })?.[0];
|
|
870
|
+
return {
|
|
871
|
+
name: n,
|
|
872
|
+
calcParams: indicator?.calcParams,
|
|
873
|
+
visible: indicator?.visible ?? true,
|
|
874
|
+
styles: indicator?.styles,
|
|
875
|
+
paneId: state.subIndicators[n]
|
|
876
|
+
};
|
|
877
|
+
});
|
|
878
|
+
[subStates[idx], subStates[swapIdx]] = [subStates[swapIdx], subStates[idx]];
|
|
879
|
+
for (const n of subNames) {
|
|
880
|
+
state.chart.removeIndicator({ id: `sub_${n}` });
|
|
881
|
+
}
|
|
882
|
+
const newSubIndicators = {};
|
|
883
|
+
for (const sub of subStates) {
|
|
884
|
+
const id = `sub_${sub.name}`;
|
|
885
|
+
state.chart.createIndicator({
|
|
886
|
+
name: sub.name,
|
|
887
|
+
id,
|
|
888
|
+
...sub.calcParams ? { calcParams: sub.calcParams } : {},
|
|
889
|
+
visible: sub.visible
|
|
890
|
+
});
|
|
891
|
+
const paneId = state.chart.getIndicators({ id })?.[0]?.paneId ?? "";
|
|
892
|
+
newSubIndicators[sub.name] = paneId;
|
|
893
|
+
if (sub.styles) {
|
|
894
|
+
state.chart.overrideIndicator({
|
|
895
|
+
name: sub.name,
|
|
896
|
+
paneId,
|
|
897
|
+
styles: sub.styles
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
dispatch({ type: "SET_SUB_INDICATORS", indicators: newSubIndicators });
|
|
902
|
+
},
|
|
903
|
+
[state.chart, state.subIndicators, dispatch]
|
|
904
|
+
);
|
|
818
905
|
return {
|
|
819
906
|
mainIndicators,
|
|
820
907
|
subIndicators,
|
|
@@ -834,7 +921,11 @@ function useIndicators() {
|
|
|
834
921
|
updateIndicatorParams,
|
|
835
922
|
getIndicatorParams,
|
|
836
923
|
isMainIndicatorActive,
|
|
837
|
-
isSubIndicatorActive
|
|
924
|
+
isSubIndicatorActive,
|
|
925
|
+
collapseSubIndicator,
|
|
926
|
+
expandSubIndicator,
|
|
927
|
+
isSubIndicatorCollapsed,
|
|
928
|
+
reorderSubIndicator
|
|
838
929
|
};
|
|
839
930
|
}
|
|
840
931
|
|
|
@@ -933,6 +1024,17 @@ function useDrawingTools() {
|
|
|
933
1024
|
const [magnetMode, setMagnetModeState] = useState("normal");
|
|
934
1025
|
const [isLocked, setIsLocked] = useState(false);
|
|
935
1026
|
const [isVisible, setIsVisible] = useState(true);
|
|
1027
|
+
const [autoRetrigger, setAutoRetrigger] = useState(true);
|
|
1028
|
+
const activeToolRef = useRef(activeTool);
|
|
1029
|
+
activeToolRef.current = activeTool;
|
|
1030
|
+
const autoRetriggerRef = useRef(autoRetrigger);
|
|
1031
|
+
autoRetriggerRef.current = autoRetrigger;
|
|
1032
|
+
const isLockedRef = useRef(isLocked);
|
|
1033
|
+
isLockedRef.current = isLocked;
|
|
1034
|
+
const isVisibleRef = useRef(isVisible);
|
|
1035
|
+
isVisibleRef.current = isVisible;
|
|
1036
|
+
const magnetModeRef = useRef(magnetMode);
|
|
1037
|
+
magnetModeRef.current = magnetMode;
|
|
936
1038
|
const categories = useMemo(
|
|
937
1039
|
() => DRAWING_CATEGORIES.map((cat) => ({
|
|
938
1040
|
key: cat.key,
|
|
@@ -943,15 +1045,15 @@ function useDrawingTools() {
|
|
|
943
1045
|
})),
|
|
944
1046
|
[]
|
|
945
1047
|
);
|
|
946
|
-
const
|
|
1048
|
+
const createOverlayForTool = useCallback(
|
|
947
1049
|
(name) => {
|
|
948
|
-
|
|
1050
|
+
const mode = magnetModeRef.current === "strong" ? "strong_magnet" : magnetModeRef.current === "weak" ? "weak_magnet" : "normal";
|
|
949
1051
|
state.chart?.createOverlay({
|
|
950
1052
|
name,
|
|
951
1053
|
groupId: DRAWING_GROUP_ID,
|
|
952
|
-
lock:
|
|
953
|
-
visible:
|
|
954
|
-
mode
|
|
1054
|
+
lock: isLockedRef.current,
|
|
1055
|
+
visible: isVisibleRef.current,
|
|
1056
|
+
mode,
|
|
955
1057
|
onDrawEnd: (event) => {
|
|
956
1058
|
const o = event.overlay;
|
|
957
1059
|
undoRedoListenerRef.current?.({
|
|
@@ -966,10 +1068,22 @@ function useDrawingTools() {
|
|
|
966
1068
|
}
|
|
967
1069
|
}
|
|
968
1070
|
});
|
|
1071
|
+
if (autoRetriggerRef.current && activeToolRef.current === name) {
|
|
1072
|
+
requestAnimationFrame(() => {
|
|
1073
|
+
createOverlayForTool(name);
|
|
1074
|
+
});
|
|
1075
|
+
}
|
|
969
1076
|
}
|
|
970
1077
|
});
|
|
971
1078
|
},
|
|
972
|
-
[state.chart,
|
|
1079
|
+
[state.chart, undoRedoListenerRef]
|
|
1080
|
+
);
|
|
1081
|
+
const selectTool = useCallback(
|
|
1082
|
+
(name) => {
|
|
1083
|
+
setActiveTool(name);
|
|
1084
|
+
createOverlayForTool(name);
|
|
1085
|
+
},
|
|
1086
|
+
[createOverlayForTool]
|
|
973
1087
|
);
|
|
974
1088
|
const clearActiveTool = useCallback(() => {
|
|
975
1089
|
setActiveTool(null);
|
|
@@ -1040,12 +1154,14 @@ function useDrawingTools() {
|
|
|
1040
1154
|
magnetMode,
|
|
1041
1155
|
isLocked,
|
|
1042
1156
|
isVisible,
|
|
1157
|
+
autoRetrigger,
|
|
1043
1158
|
selectTool,
|
|
1044
1159
|
clearActiveTool,
|
|
1045
1160
|
setMagnetMode,
|
|
1046
1161
|
toggleLock,
|
|
1047
1162
|
toggleVisibility,
|
|
1048
|
-
removeAllDrawings
|
|
1163
|
+
removeAllDrawings,
|
|
1164
|
+
setAutoRetrigger
|
|
1049
1165
|
};
|
|
1050
1166
|
}
|
|
1051
1167
|
|
|
@@ -2244,6 +2360,750 @@ ${code}`
|
|
|
2244
2360
|
defaultScript: DEFAULT_SCRIPT
|
|
2245
2361
|
};
|
|
2246
2362
|
}
|
|
2363
|
+
function useCrosshair() {
|
|
2364
|
+
const { state } = useKlinechartsUI();
|
|
2365
|
+
const [barData, setBarData] = useState(null);
|
|
2366
|
+
const rafRef = useRef(0);
|
|
2367
|
+
const handler = useCallback(
|
|
2368
|
+
(event) => {
|
|
2369
|
+
if (rafRef.current) {
|
|
2370
|
+
cancelAnimationFrame(rafRef.current);
|
|
2371
|
+
}
|
|
2372
|
+
rafRef.current = requestAnimationFrame(() => {
|
|
2373
|
+
rafRef.current = 0;
|
|
2374
|
+
const klineData = event?.kLineData;
|
|
2375
|
+
if (!klineData) {
|
|
2376
|
+
setBarData(null);
|
|
2377
|
+
return;
|
|
2378
|
+
}
|
|
2379
|
+
const pricePrecision = state.symbol?.pricePrecision ?? 2;
|
|
2380
|
+
const open = klineData.open ?? 0;
|
|
2381
|
+
const close = klineData.close ?? 0;
|
|
2382
|
+
const change = parseFloat((close - open).toFixed(pricePrecision));
|
|
2383
|
+
const changePercent = open !== 0 ? parseFloat((change / open * 100).toFixed(2)) : 0;
|
|
2384
|
+
setBarData({
|
|
2385
|
+
open,
|
|
2386
|
+
high: klineData.high ?? 0,
|
|
2387
|
+
low: klineData.low ?? 0,
|
|
2388
|
+
close,
|
|
2389
|
+
volume: klineData.volume ?? 0,
|
|
2390
|
+
timestamp: klineData.timestamp ?? 0,
|
|
2391
|
+
change,
|
|
2392
|
+
changePercent
|
|
2393
|
+
});
|
|
2394
|
+
});
|
|
2395
|
+
},
|
|
2396
|
+
[state.symbol?.pricePrecision]
|
|
2397
|
+
);
|
|
2398
|
+
useEffect(() => {
|
|
2399
|
+
const chart = state.chart;
|
|
2400
|
+
if (!chart) return;
|
|
2401
|
+
chart.subscribeAction?.("onCrosshairChange", handler);
|
|
2402
|
+
return () => {
|
|
2403
|
+
if (rafRef.current) {
|
|
2404
|
+
cancelAnimationFrame(rafRef.current);
|
|
2405
|
+
rafRef.current = 0;
|
|
2406
|
+
}
|
|
2407
|
+
chart.unsubscribeAction?.("onCrosshairChange", handler);
|
|
2408
|
+
};
|
|
2409
|
+
}, [state.chart, handler]);
|
|
2410
|
+
return { barData };
|
|
2411
|
+
}
|
|
2412
|
+
var alertCounter = 0;
|
|
2413
|
+
function useAlerts() {
|
|
2414
|
+
const { state } = useKlinechartsUI();
|
|
2415
|
+
const [alerts, setAlerts] = useState([]);
|
|
2416
|
+
const callbackRef = useRef(null);
|
|
2417
|
+
const prevCloseRef = useRef(null);
|
|
2418
|
+
const addAlert = useCallback(
|
|
2419
|
+
(price, condition, message) => {
|
|
2420
|
+
const id = `alert_${++alertCounter}`;
|
|
2421
|
+
const alert = {
|
|
2422
|
+
id,
|
|
2423
|
+
price,
|
|
2424
|
+
condition,
|
|
2425
|
+
message,
|
|
2426
|
+
triggered: false
|
|
2427
|
+
};
|
|
2428
|
+
setAlerts((prev) => [...prev, alert]);
|
|
2429
|
+
if (state.chart) {
|
|
2430
|
+
state.chart.createOverlay({
|
|
2431
|
+
name: "horizontalStraightLine",
|
|
2432
|
+
id,
|
|
2433
|
+
groupId: "price_alerts",
|
|
2434
|
+
points: [{ value: price }],
|
|
2435
|
+
styles: {
|
|
2436
|
+
line: {
|
|
2437
|
+
style: "dashed",
|
|
2438
|
+
color: "#ff9800",
|
|
2439
|
+
size: 1
|
|
2440
|
+
}
|
|
2441
|
+
},
|
|
2442
|
+
lock: true
|
|
2443
|
+
});
|
|
2444
|
+
}
|
|
2445
|
+
return id;
|
|
2446
|
+
},
|
|
2447
|
+
[state.chart]
|
|
2448
|
+
);
|
|
2449
|
+
const removeAlert = useCallback(
|
|
2450
|
+
(id) => {
|
|
2451
|
+
setAlerts((prev) => prev.filter((a) => a.id !== id));
|
|
2452
|
+
state.chart?.removeOverlay({ id });
|
|
2453
|
+
},
|
|
2454
|
+
[state.chart]
|
|
2455
|
+
);
|
|
2456
|
+
const clearAlerts = useCallback(() => {
|
|
2457
|
+
setAlerts((prev) => {
|
|
2458
|
+
for (const alert of prev) {
|
|
2459
|
+
state.chart?.removeOverlay({ id: alert.id });
|
|
2460
|
+
}
|
|
2461
|
+
return [];
|
|
2462
|
+
});
|
|
2463
|
+
}, [state.chart]);
|
|
2464
|
+
const onAlertTriggered = useCallback(
|
|
2465
|
+
(callback) => {
|
|
2466
|
+
callbackRef.current = callback;
|
|
2467
|
+
},
|
|
2468
|
+
[]
|
|
2469
|
+
);
|
|
2470
|
+
useEffect(() => {
|
|
2471
|
+
if (!state.chart) return;
|
|
2472
|
+
const interval = setInterval(() => {
|
|
2473
|
+
const dataList = state.chart?.getDataList();
|
|
2474
|
+
if (!dataList || dataList.length === 0) return;
|
|
2475
|
+
const lastBar = dataList[dataList.length - 1];
|
|
2476
|
+
const currentClose = lastBar.close;
|
|
2477
|
+
const prevClose = prevCloseRef.current;
|
|
2478
|
+
if (prevClose === null) {
|
|
2479
|
+
prevCloseRef.current = currentClose;
|
|
2480
|
+
return;
|
|
2481
|
+
}
|
|
2482
|
+
prevCloseRef.current = currentClose;
|
|
2483
|
+
setAlerts((prev) => {
|
|
2484
|
+
let changed = false;
|
|
2485
|
+
const next = prev.map((alert) => {
|
|
2486
|
+
if (alert.triggered) return alert;
|
|
2487
|
+
let shouldTrigger = false;
|
|
2488
|
+
if (alert.condition === "crossing_up") {
|
|
2489
|
+
shouldTrigger = prevClose < alert.price && currentClose >= alert.price;
|
|
2490
|
+
} else if (alert.condition === "crossing_down") {
|
|
2491
|
+
shouldTrigger = prevClose > alert.price && currentClose <= alert.price;
|
|
2492
|
+
} else if (alert.condition === "crossing") {
|
|
2493
|
+
shouldTrigger = prevClose < alert.price && currentClose >= alert.price || prevClose > alert.price && currentClose <= alert.price;
|
|
2494
|
+
}
|
|
2495
|
+
if (shouldTrigger) {
|
|
2496
|
+
changed = true;
|
|
2497
|
+
const triggered = { ...alert, triggered: true };
|
|
2498
|
+
callbackRef.current?.(triggered);
|
|
2499
|
+
return triggered;
|
|
2500
|
+
}
|
|
2501
|
+
return alert;
|
|
2502
|
+
});
|
|
2503
|
+
return changed ? next : prev;
|
|
2504
|
+
});
|
|
2505
|
+
}, 1e3);
|
|
2506
|
+
return () => clearInterval(interval);
|
|
2507
|
+
}, [state.chart]);
|
|
2508
|
+
return {
|
|
2509
|
+
alerts,
|
|
2510
|
+
addAlert,
|
|
2511
|
+
removeAlert,
|
|
2512
|
+
clearAlerts,
|
|
2513
|
+
onAlertTriggered
|
|
2514
|
+
};
|
|
2515
|
+
}
|
|
2516
|
+
function useDataExport() {
|
|
2517
|
+
const { state } = useKlinechartsUI();
|
|
2518
|
+
const exportAll = useCallback(
|
|
2519
|
+
(format) => {
|
|
2520
|
+
if (!state.chart) return;
|
|
2521
|
+
const dataList = state.chart.getDataList();
|
|
2522
|
+
if (!dataList || dataList.length === 0) return;
|
|
2523
|
+
downloadData(dataList, format, state.symbol?.ticker);
|
|
2524
|
+
},
|
|
2525
|
+
[state.chart, state.symbol?.ticker]
|
|
2526
|
+
);
|
|
2527
|
+
const exportVisible = useCallback(
|
|
2528
|
+
(format) => {
|
|
2529
|
+
if (!state.chart) return;
|
|
2530
|
+
const dataList = state.chart.getDataList();
|
|
2531
|
+
if (!dataList || dataList.length === 0) return;
|
|
2532
|
+
const visibleRange = state.chart.getVisibleRange?.();
|
|
2533
|
+
if (!visibleRange) {
|
|
2534
|
+
downloadData(dataList, format, state.symbol?.ticker);
|
|
2535
|
+
return;
|
|
2536
|
+
}
|
|
2537
|
+
const { from, to } = visibleRange;
|
|
2538
|
+
const visibleData = dataList.slice(from, to);
|
|
2539
|
+
downloadData(visibleData, format, state.symbol?.ticker);
|
|
2540
|
+
},
|
|
2541
|
+
[state.chart, state.symbol?.ticker]
|
|
2542
|
+
);
|
|
2543
|
+
return { exportAll, exportVisible };
|
|
2544
|
+
}
|
|
2545
|
+
function formatRow(bar) {
|
|
2546
|
+
return {
|
|
2547
|
+
date: new Date(bar.timestamp).toISOString(),
|
|
2548
|
+
open: bar.open,
|
|
2549
|
+
high: bar.high,
|
|
2550
|
+
low: bar.low,
|
|
2551
|
+
close: bar.close,
|
|
2552
|
+
volume: bar.volume ?? 0
|
|
2553
|
+
};
|
|
2554
|
+
}
|
|
2555
|
+
function buildCsv(dataList) {
|
|
2556
|
+
const header = "Date,Open,High,Low,Close,Volume\n";
|
|
2557
|
+
const rows = dataList.map((bar) => {
|
|
2558
|
+
const r = formatRow(bar);
|
|
2559
|
+
return `${r.date},${r.open},${r.high},${r.low},${r.close},${r.volume}`;
|
|
2560
|
+
});
|
|
2561
|
+
return header + rows.join("\n");
|
|
2562
|
+
}
|
|
2563
|
+
function buildJson(dataList) {
|
|
2564
|
+
return JSON.stringify(dataList.map(formatRow), null, 2);
|
|
2565
|
+
}
|
|
2566
|
+
function downloadData(dataList, format, ticker) {
|
|
2567
|
+
const content = format === "csv" ? buildCsv(dataList) : buildJson(dataList);
|
|
2568
|
+
const mimeType = format === "csv" ? "text/csv" : "application/json";
|
|
2569
|
+
const blob = new Blob([content], { type: mimeType });
|
|
2570
|
+
const url = URL.createObjectURL(blob);
|
|
2571
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
2572
|
+
const prefix = ticker ?? "chart";
|
|
2573
|
+
const filename = `${prefix}_${date}.${format}`;
|
|
2574
|
+
const link = document.createElement("a");
|
|
2575
|
+
link.href = url;
|
|
2576
|
+
link.download = filename;
|
|
2577
|
+
document.body.appendChild(link);
|
|
2578
|
+
link.click();
|
|
2579
|
+
document.body.removeChild(link);
|
|
2580
|
+
URL.revokeObjectURL(url);
|
|
2581
|
+
}
|
|
2582
|
+
function useWatchlist() {
|
|
2583
|
+
const { state } = useKlinechartsUI();
|
|
2584
|
+
const { dispatch, datafeed } = useKlinechartsUIDispatch();
|
|
2585
|
+
const [items, setItems] = useState([]);
|
|
2586
|
+
const subscriptionsRef = useRef(/* @__PURE__ */ new Map());
|
|
2587
|
+
const activeSymbol = state.symbol?.ticker ?? null;
|
|
2588
|
+
const addSymbol = useCallback(
|
|
2589
|
+
(ticker) => {
|
|
2590
|
+
setItems((prev) => {
|
|
2591
|
+
if (prev.some((item) => item.ticker === ticker)) return prev;
|
|
2592
|
+
return [
|
|
2593
|
+
...prev,
|
|
2594
|
+
{ ticker, lastPrice: null, change: null, changePercent: null }
|
|
2595
|
+
];
|
|
2596
|
+
});
|
|
2597
|
+
if (subscriptionsRef.current.has(ticker)) return;
|
|
2598
|
+
const symbolInfo = { ticker };
|
|
2599
|
+
const period = state.period;
|
|
2600
|
+
subscriptionsRef.current.set(ticker, { symbolInfo, period });
|
|
2601
|
+
datafeed.subscribe(symbolInfo, period, (bar) => {
|
|
2602
|
+
setItems(
|
|
2603
|
+
(prev) => prev.map((item) => {
|
|
2604
|
+
if (item.ticker !== ticker) return item;
|
|
2605
|
+
const change = bar.close - bar.open;
|
|
2606
|
+
const changePercent = bar.open !== 0 ? change / bar.open * 100 : null;
|
|
2607
|
+
return {
|
|
2608
|
+
...item,
|
|
2609
|
+
lastPrice: bar.close,
|
|
2610
|
+
change,
|
|
2611
|
+
changePercent
|
|
2612
|
+
};
|
|
2613
|
+
})
|
|
2614
|
+
);
|
|
2615
|
+
});
|
|
2616
|
+
},
|
|
2617
|
+
[datafeed, state.period]
|
|
2618
|
+
);
|
|
2619
|
+
const removeSymbol = useCallback(
|
|
2620
|
+
(ticker) => {
|
|
2621
|
+
const sub = subscriptionsRef.current.get(ticker);
|
|
2622
|
+
if (sub) {
|
|
2623
|
+
datafeed.unsubscribe(sub.symbolInfo, sub.period);
|
|
2624
|
+
subscriptionsRef.current.delete(ticker);
|
|
2625
|
+
}
|
|
2626
|
+
setItems((prev) => prev.filter((item) => item.ticker !== ticker));
|
|
2627
|
+
},
|
|
2628
|
+
[datafeed]
|
|
2629
|
+
);
|
|
2630
|
+
const switchSymbol = useCallback(
|
|
2631
|
+
(ticker) => {
|
|
2632
|
+
dispatch({ type: "SET_SYMBOL", symbol: { ticker } });
|
|
2633
|
+
},
|
|
2634
|
+
[dispatch]
|
|
2635
|
+
);
|
|
2636
|
+
useEffect(() => {
|
|
2637
|
+
return () => {
|
|
2638
|
+
subscriptionsRef.current.forEach((sub) => {
|
|
2639
|
+
datafeed.unsubscribe(sub.symbolInfo, sub.period);
|
|
2640
|
+
});
|
|
2641
|
+
subscriptionsRef.current.clear();
|
|
2642
|
+
};
|
|
2643
|
+
}, [datafeed]);
|
|
2644
|
+
return {
|
|
2645
|
+
items,
|
|
2646
|
+
addSymbol,
|
|
2647
|
+
removeSymbol,
|
|
2648
|
+
switchSymbol,
|
|
2649
|
+
activeSymbol
|
|
2650
|
+
};
|
|
2651
|
+
}
|
|
2652
|
+
function useReplay() {
|
|
2653
|
+
const { state } = useKlinechartsUI();
|
|
2654
|
+
const [isReplaying, setIsReplaying] = useState(false);
|
|
2655
|
+
const [isPaused, setIsPaused] = useState(false);
|
|
2656
|
+
const [speed, setSpeedState] = useState(1);
|
|
2657
|
+
const [barIndex, setBarIndex] = useState(0);
|
|
2658
|
+
const [totalBars, setTotalBars] = useState(0);
|
|
2659
|
+
const intervalRef = useRef(null);
|
|
2660
|
+
const savedDataRef = useRef([]);
|
|
2661
|
+
const currentIndexRef = useRef(0);
|
|
2662
|
+
const clearInterval_ = useCallback(() => {
|
|
2663
|
+
if (intervalRef.current !== null) {
|
|
2664
|
+
clearInterval(intervalRef.current);
|
|
2665
|
+
intervalRef.current = null;
|
|
2666
|
+
}
|
|
2667
|
+
}, []);
|
|
2668
|
+
const addNextBar = useCallback(() => {
|
|
2669
|
+
if (!state.chart) return;
|
|
2670
|
+
const data = savedDataRef.current;
|
|
2671
|
+
const idx = currentIndexRef.current;
|
|
2672
|
+
if (idx >= data.length) {
|
|
2673
|
+
clearInterval_();
|
|
2674
|
+
setIsPaused(true);
|
|
2675
|
+
return;
|
|
2676
|
+
}
|
|
2677
|
+
const bar = data[idx];
|
|
2678
|
+
state.chart.updateData?.(bar);
|
|
2679
|
+
currentIndexRef.current = idx + 1;
|
|
2680
|
+
setBarIndex(idx + 1);
|
|
2681
|
+
}, [state.chart, clearInterval_]);
|
|
2682
|
+
const startInterval = useCallback(
|
|
2683
|
+
(currentSpeed) => {
|
|
2684
|
+
clearInterval_();
|
|
2685
|
+
intervalRef.current = setInterval(addNextBar, 1e3 / currentSpeed);
|
|
2686
|
+
},
|
|
2687
|
+
[addNextBar, clearInterval_]
|
|
2688
|
+
);
|
|
2689
|
+
const startReplay = useCallback(() => {
|
|
2690
|
+
if (!state.chart) return;
|
|
2691
|
+
const dataList = state.chart.getDataList();
|
|
2692
|
+
if (!dataList || dataList.length === 0) return;
|
|
2693
|
+
savedDataRef.current = [...dataList];
|
|
2694
|
+
currentIndexRef.current = 0;
|
|
2695
|
+
setTotalBars(dataList.length);
|
|
2696
|
+
setBarIndex(0);
|
|
2697
|
+
setIsReplaying(true);
|
|
2698
|
+
setIsPaused(false);
|
|
2699
|
+
state.chart.clearData?.();
|
|
2700
|
+
startInterval(speed);
|
|
2701
|
+
}, [state.chart, speed, startInterval]);
|
|
2702
|
+
const stopReplay = useCallback(() => {
|
|
2703
|
+
if (!state.chart) return;
|
|
2704
|
+
clearInterval_();
|
|
2705
|
+
const data = savedDataRef.current;
|
|
2706
|
+
if (data.length > 0) {
|
|
2707
|
+
state.chart.clearData?.();
|
|
2708
|
+
for (const bar of data) {
|
|
2709
|
+
state.chart.updateData?.(bar);
|
|
2710
|
+
}
|
|
2711
|
+
}
|
|
2712
|
+
savedDataRef.current = [];
|
|
2713
|
+
currentIndexRef.current = 0;
|
|
2714
|
+
setIsReplaying(false);
|
|
2715
|
+
setIsPaused(false);
|
|
2716
|
+
setBarIndex(0);
|
|
2717
|
+
setTotalBars(0);
|
|
2718
|
+
}, [state.chart, clearInterval_]);
|
|
2719
|
+
const togglePause = useCallback(() => {
|
|
2720
|
+
if (!isReplaying) return;
|
|
2721
|
+
if (isPaused) {
|
|
2722
|
+
startInterval(speed);
|
|
2723
|
+
setIsPaused(false);
|
|
2724
|
+
} else {
|
|
2725
|
+
clearInterval_();
|
|
2726
|
+
setIsPaused(true);
|
|
2727
|
+
}
|
|
2728
|
+
}, [isReplaying, isPaused, speed, startInterval, clearInterval_]);
|
|
2729
|
+
const stepForward = useCallback(() => {
|
|
2730
|
+
if (!isReplaying || !isPaused) return;
|
|
2731
|
+
addNextBar();
|
|
2732
|
+
}, [isReplaying, isPaused, addNextBar]);
|
|
2733
|
+
const stepBackward = useCallback(() => {
|
|
2734
|
+
if (!isReplaying || !isPaused || !state.chart) return;
|
|
2735
|
+
const idx = currentIndexRef.current;
|
|
2736
|
+
if (idx <= 1) return;
|
|
2737
|
+
const data = savedDataRef.current;
|
|
2738
|
+
state.chart.clearData?.();
|
|
2739
|
+
for (let i = 0; i < idx - 1; i++) {
|
|
2740
|
+
state.chart.updateData?.(data[i]);
|
|
2741
|
+
}
|
|
2742
|
+
currentIndexRef.current = idx - 1;
|
|
2743
|
+
setBarIndex(idx - 1);
|
|
2744
|
+
}, [isReplaying, isPaused, state.chart]);
|
|
2745
|
+
const seekTo = useCallback(
|
|
2746
|
+
(targetIndex) => {
|
|
2747
|
+
if (!isReplaying || !state.chart) return;
|
|
2748
|
+
const data = savedDataRef.current;
|
|
2749
|
+
const clamped = Math.max(0, Math.min(targetIndex, data.length));
|
|
2750
|
+
clearInterval_();
|
|
2751
|
+
setIsPaused(true);
|
|
2752
|
+
state.chart.clearData?.();
|
|
2753
|
+
for (let i = 0; i < clamped; i++) {
|
|
2754
|
+
state.chart.updateData?.(data[i]);
|
|
2755
|
+
}
|
|
2756
|
+
currentIndexRef.current = clamped;
|
|
2757
|
+
setBarIndex(clamped);
|
|
2758
|
+
},
|
|
2759
|
+
[isReplaying, state.chart, clearInterval_]
|
|
2760
|
+
);
|
|
2761
|
+
const setSpeed = useCallback(
|
|
2762
|
+
(newSpeed) => {
|
|
2763
|
+
setSpeedState(newSpeed);
|
|
2764
|
+
if (isReplaying && !isPaused) {
|
|
2765
|
+
startInterval(newSpeed);
|
|
2766
|
+
}
|
|
2767
|
+
},
|
|
2768
|
+
[isReplaying, isPaused, startInterval]
|
|
2769
|
+
);
|
|
2770
|
+
useEffect(() => {
|
|
2771
|
+
return () => {
|
|
2772
|
+
clearInterval_();
|
|
2773
|
+
};
|
|
2774
|
+
}, [clearInterval_]);
|
|
2775
|
+
return {
|
|
2776
|
+
isReplaying,
|
|
2777
|
+
isPaused,
|
|
2778
|
+
speed,
|
|
2779
|
+
barIndex,
|
|
2780
|
+
totalBars,
|
|
2781
|
+
startReplay,
|
|
2782
|
+
stopReplay,
|
|
2783
|
+
togglePause,
|
|
2784
|
+
stepForward,
|
|
2785
|
+
stepBackward,
|
|
2786
|
+
seekTo,
|
|
2787
|
+
setSpeed
|
|
2788
|
+
};
|
|
2789
|
+
}
|
|
2790
|
+
var DEFAULT_COLORS = [
|
|
2791
|
+
"#2196f3",
|
|
2792
|
+
"#ff9800",
|
|
2793
|
+
"#4caf50",
|
|
2794
|
+
"#e91e63",
|
|
2795
|
+
"#9c27b0",
|
|
2796
|
+
"#00bcd4",
|
|
2797
|
+
"#ff5722",
|
|
2798
|
+
"#8bc34a"
|
|
2799
|
+
];
|
|
2800
|
+
var colorIndex = 0;
|
|
2801
|
+
var compareCounter = 0;
|
|
2802
|
+
function useCompare() {
|
|
2803
|
+
const { state, datafeed } = useKlinechartsUI();
|
|
2804
|
+
const [symbols, setSymbols] = useState([]);
|
|
2805
|
+
const indicatorsRef = useRef(/* @__PURE__ */ new Map());
|
|
2806
|
+
const addSymbol = useCallback(
|
|
2807
|
+
async (ticker, color) => {
|
|
2808
|
+
if (!state.chart || !datafeed) return;
|
|
2809
|
+
if (indicatorsRef.current.has(ticker)) return;
|
|
2810
|
+
const assignedColor = color ?? DEFAULT_COLORS[colorIndex++ % DEFAULT_COLORS.length];
|
|
2811
|
+
const mainDataList = state.chart.getDataList();
|
|
2812
|
+
if (!mainDataList || mainDataList.length === 0) return;
|
|
2813
|
+
const from = mainDataList[0].timestamp;
|
|
2814
|
+
const to = mainDataList[mainDataList.length - 1].timestamp;
|
|
2815
|
+
const compareData = await datafeed.getHistoryKLineData(
|
|
2816
|
+
{ ticker },
|
|
2817
|
+
{ ...state.period, label: "" },
|
|
2818
|
+
from,
|
|
2819
|
+
to
|
|
2820
|
+
);
|
|
2821
|
+
if (!compareData || compareData.length === 0) return;
|
|
2822
|
+
const compareMap = /* @__PURE__ */ new Map();
|
|
2823
|
+
for (const bar of compareData) {
|
|
2824
|
+
compareMap.set(bar.timestamp, bar.close);
|
|
2825
|
+
}
|
|
2826
|
+
let basePrice = null;
|
|
2827
|
+
for (const bar of mainDataList) {
|
|
2828
|
+
const close = compareMap.get(bar.timestamp);
|
|
2829
|
+
if (close != null) {
|
|
2830
|
+
basePrice = close;
|
|
2831
|
+
break;
|
|
2832
|
+
}
|
|
2833
|
+
}
|
|
2834
|
+
if (basePrice === null || basePrice === 0) return;
|
|
2835
|
+
const bp = basePrice;
|
|
2836
|
+
const normalizedData = mainDataList.map((mainBar) => {
|
|
2837
|
+
const close = compareMap.get(mainBar.timestamp);
|
|
2838
|
+
return {
|
|
2839
|
+
pct: close != null ? (close - bp) / bp * 100 : NaN
|
|
2840
|
+
};
|
|
2841
|
+
});
|
|
2842
|
+
const indicatorName = `__cmp_${++compareCounter}_${ticker}`;
|
|
2843
|
+
registerIndicator({
|
|
2844
|
+
name: indicatorName,
|
|
2845
|
+
shortName: ticker,
|
|
2846
|
+
figures: [
|
|
2847
|
+
{
|
|
2848
|
+
key: "pct",
|
|
2849
|
+
title: `${ticker} %: `,
|
|
2850
|
+
type: "line",
|
|
2851
|
+
styles: () => ({ color: assignedColor })
|
|
2852
|
+
}
|
|
2853
|
+
],
|
|
2854
|
+
calc: () => normalizedData
|
|
2855
|
+
});
|
|
2856
|
+
const paneId = state.chart.createIndicator(
|
|
2857
|
+
{ name: indicatorName },
|
|
2858
|
+
true,
|
|
2859
|
+
{ id: "candle_pane" }
|
|
2860
|
+
);
|
|
2861
|
+
indicatorsRef.current.set(ticker, {
|
|
2862
|
+
name: indicatorName,
|
|
2863
|
+
paneId: typeof paneId === "string" ? paneId : "candle_pane"
|
|
2864
|
+
});
|
|
2865
|
+
setSymbols((prev) => {
|
|
2866
|
+
if (prev.some((s) => s.ticker === ticker)) return prev;
|
|
2867
|
+
return [
|
|
2868
|
+
...prev,
|
|
2869
|
+
{ ticker, basePrice: bp, color: assignedColor, visible: true }
|
|
2870
|
+
];
|
|
2871
|
+
});
|
|
2872
|
+
},
|
|
2873
|
+
[state.chart, state.period, datafeed]
|
|
2874
|
+
);
|
|
2875
|
+
const removeSymbol = useCallback(
|
|
2876
|
+
(ticker) => {
|
|
2877
|
+
const info = indicatorsRef.current.get(ticker);
|
|
2878
|
+
if (info && state.chart) {
|
|
2879
|
+
try {
|
|
2880
|
+
state.chart.removeIndicator({ name: info.name });
|
|
2881
|
+
} catch {
|
|
2882
|
+
}
|
|
2883
|
+
indicatorsRef.current.delete(ticker);
|
|
2884
|
+
}
|
|
2885
|
+
setSymbols((prev) => prev.filter((s) => s.ticker !== ticker));
|
|
2886
|
+
},
|
|
2887
|
+
[state.chart]
|
|
2888
|
+
);
|
|
2889
|
+
const toggleSymbol = useCallback(
|
|
2890
|
+
(ticker) => {
|
|
2891
|
+
const info = indicatorsRef.current.get(ticker);
|
|
2892
|
+
if (!info || !state.chart) return;
|
|
2893
|
+
setSymbols((prev) => {
|
|
2894
|
+
const sym = prev.find((s) => s.ticker === ticker);
|
|
2895
|
+
if (!sym) return prev;
|
|
2896
|
+
const newVisible = !sym.visible;
|
|
2897
|
+
state.chart?.overrideIndicator({
|
|
2898
|
+
name: info.name,
|
|
2899
|
+
visible: newVisible
|
|
2900
|
+
});
|
|
2901
|
+
return prev.map(
|
|
2902
|
+
(s) => s.ticker === ticker ? { ...s, visible: newVisible } : s
|
|
2903
|
+
);
|
|
2904
|
+
});
|
|
2905
|
+
},
|
|
2906
|
+
[state.chart]
|
|
2907
|
+
);
|
|
2908
|
+
const clearAll = useCallback(() => {
|
|
2909
|
+
indicatorsRef.current.forEach((info) => {
|
|
2910
|
+
try {
|
|
2911
|
+
state.chart?.removeIndicator({ name: info.name });
|
|
2912
|
+
} catch {
|
|
2913
|
+
}
|
|
2914
|
+
});
|
|
2915
|
+
indicatorsRef.current.clear();
|
|
2916
|
+
setSymbols([]);
|
|
2917
|
+
}, [state.chart]);
|
|
2918
|
+
useEffect(() => {
|
|
2919
|
+
return () => {
|
|
2920
|
+
indicatorsRef.current.forEach((info) => {
|
|
2921
|
+
try {
|
|
2922
|
+
state.chart?.removeIndicator({ name: info.name });
|
|
2923
|
+
} catch {
|
|
2924
|
+
}
|
|
2925
|
+
});
|
|
2926
|
+
indicatorsRef.current.clear();
|
|
2927
|
+
};
|
|
2928
|
+
}, [state.chart]);
|
|
2929
|
+
return { symbols, addSymbol, removeSymbol, toggleSymbol, clearAll };
|
|
2930
|
+
}
|
|
2931
|
+
var MEASURE_OVERLAY_ID = "__measure_line__";
|
|
2932
|
+
function useMeasure() {
|
|
2933
|
+
const { state } = useKlinechartsUI();
|
|
2934
|
+
const [isActive, setIsActive] = useState(false);
|
|
2935
|
+
const [fromPoint, setFromPoint] = useState(null);
|
|
2936
|
+
const [result, setResult] = useState(null);
|
|
2937
|
+
const clickCountRef = useRef(0);
|
|
2938
|
+
const computeResult = useCallback(
|
|
2939
|
+
(from, to) => {
|
|
2940
|
+
const priceDiff = to.price - from.price;
|
|
2941
|
+
const pricePercent = from.price !== 0 ? priceDiff / from.price * 100 : 0;
|
|
2942
|
+
const bars = Math.abs(to.barIndex - from.barIndex);
|
|
2943
|
+
const timeDiff = Math.abs(to.timestamp - from.timestamp);
|
|
2944
|
+
return { from, to, priceDiff, pricePercent, bars, timeDiff };
|
|
2945
|
+
},
|
|
2946
|
+
[]
|
|
2947
|
+
);
|
|
2948
|
+
const cleanup = useCallback(() => {
|
|
2949
|
+
state.chart?.removeOverlay({ id: MEASURE_OVERLAY_ID });
|
|
2950
|
+
}, [state.chart]);
|
|
2951
|
+
const startMeasure = useCallback(() => {
|
|
2952
|
+
cleanup();
|
|
2953
|
+
setIsActive(true);
|
|
2954
|
+
setFromPoint(null);
|
|
2955
|
+
setResult(null);
|
|
2956
|
+
clickCountRef.current = 0;
|
|
2957
|
+
if (!state.chart) return;
|
|
2958
|
+
state.chart.createOverlay({
|
|
2959
|
+
name: "segment",
|
|
2960
|
+
id: MEASURE_OVERLAY_ID,
|
|
2961
|
+
groupId: "measure",
|
|
2962
|
+
styles: {
|
|
2963
|
+
line: {
|
|
2964
|
+
style: "dashed",
|
|
2965
|
+
color: "#FFD600",
|
|
2966
|
+
size: 1
|
|
2967
|
+
}
|
|
2968
|
+
},
|
|
2969
|
+
lock: true,
|
|
2970
|
+
onDrawEnd: (event) => {
|
|
2971
|
+
const overlay = event?.overlay;
|
|
2972
|
+
const points = overlay?.points;
|
|
2973
|
+
if (!points || points.length < 2) return;
|
|
2974
|
+
const chart = state.chart;
|
|
2975
|
+
if (!chart) return;
|
|
2976
|
+
const dataList = chart.getDataList();
|
|
2977
|
+
if (!dataList || dataList.length === 0) return;
|
|
2978
|
+
const makePoint = (p) => {
|
|
2979
|
+
const idx = Math.max(
|
|
2980
|
+
0,
|
|
2981
|
+
Math.min(p.dataIndex ?? 0, dataList.length - 1)
|
|
2982
|
+
);
|
|
2983
|
+
const bar = dataList[idx];
|
|
2984
|
+
return {
|
|
2985
|
+
price: p.value ?? bar?.close ?? 0,
|
|
2986
|
+
timestamp: bar?.timestamp ?? 0,
|
|
2987
|
+
barIndex: idx
|
|
2988
|
+
};
|
|
2989
|
+
};
|
|
2990
|
+
const from = makePoint(points[0]);
|
|
2991
|
+
const to = makePoint(points[1]);
|
|
2992
|
+
setFromPoint(from);
|
|
2993
|
+
setResult(computeResult(from, to));
|
|
2994
|
+
setIsActive(false);
|
|
2995
|
+
}
|
|
2996
|
+
});
|
|
2997
|
+
}, [state.chart, cleanup, computeResult]);
|
|
2998
|
+
const cancelMeasure = useCallback(() => {
|
|
2999
|
+
cleanup();
|
|
3000
|
+
setIsActive(false);
|
|
3001
|
+
setFromPoint(null);
|
|
3002
|
+
clickCountRef.current = 0;
|
|
3003
|
+
}, [cleanup]);
|
|
3004
|
+
const clearResult = useCallback(() => {
|
|
3005
|
+
cleanup();
|
|
3006
|
+
setResult(null);
|
|
3007
|
+
setFromPoint(null);
|
|
3008
|
+
}, [cleanup]);
|
|
3009
|
+
useEffect(() => {
|
|
3010
|
+
return () => {
|
|
3011
|
+
state.chart?.removeOverlay({ id: MEASURE_OVERLAY_ID });
|
|
3012
|
+
};
|
|
3013
|
+
}, [state.chart]);
|
|
3014
|
+
return {
|
|
3015
|
+
isActive,
|
|
3016
|
+
startMeasure,
|
|
3017
|
+
cancelMeasure,
|
|
3018
|
+
result,
|
|
3019
|
+
clearResult,
|
|
3020
|
+
fromPoint
|
|
3021
|
+
};
|
|
3022
|
+
}
|
|
3023
|
+
var annotationCounter = 0;
|
|
3024
|
+
function useAnnotations() {
|
|
3025
|
+
const { state } = useKlinechartsUI();
|
|
3026
|
+
const [annotations, setAnnotations] = useState([]);
|
|
3027
|
+
const addAnnotation = useCallback(
|
|
3028
|
+
(text, price, timestamp, color) => {
|
|
3029
|
+
const id = `annotation_${++annotationCounter}`;
|
|
3030
|
+
const annotation = {
|
|
3031
|
+
id,
|
|
3032
|
+
text,
|
|
3033
|
+
price,
|
|
3034
|
+
timestamp,
|
|
3035
|
+
color
|
|
3036
|
+
};
|
|
3037
|
+
setAnnotations((prev) => [...prev, annotation]);
|
|
3038
|
+
if (state.chart) {
|
|
3039
|
+
state.chart.createOverlay({
|
|
3040
|
+
name: "simpleAnnotation",
|
|
3041
|
+
id,
|
|
3042
|
+
groupId: "annotations",
|
|
3043
|
+
points: [{ timestamp, value: price }],
|
|
3044
|
+
extendData: text,
|
|
3045
|
+
styles: color ? {
|
|
3046
|
+
text: {
|
|
3047
|
+
color
|
|
3048
|
+
}
|
|
3049
|
+
} : void 0,
|
|
3050
|
+
lock: true
|
|
3051
|
+
});
|
|
3052
|
+
}
|
|
3053
|
+
return id;
|
|
3054
|
+
},
|
|
3055
|
+
[state.chart]
|
|
3056
|
+
);
|
|
3057
|
+
const removeAnnotation = useCallback(
|
|
3058
|
+
(id) => {
|
|
3059
|
+
setAnnotations((prev) => prev.filter((a) => a.id !== id));
|
|
3060
|
+
state.chart?.removeOverlay({ id });
|
|
3061
|
+
},
|
|
3062
|
+
[state.chart]
|
|
3063
|
+
);
|
|
3064
|
+
const updateAnnotation = useCallback(
|
|
3065
|
+
(id, updates) => {
|
|
3066
|
+
setAnnotations(
|
|
3067
|
+
(prev) => prev.map((a) => a.id === id ? { ...a, ...updates } : a)
|
|
3068
|
+
);
|
|
3069
|
+
if (state.chart) {
|
|
3070
|
+
const overrideData = { id };
|
|
3071
|
+
if (updates.text !== void 0) {
|
|
3072
|
+
overrideData.extendData = updates.text;
|
|
3073
|
+
}
|
|
3074
|
+
if (updates.color !== void 0) {
|
|
3075
|
+
overrideData.styles = {
|
|
3076
|
+
text: {
|
|
3077
|
+
color: updates.color
|
|
3078
|
+
}
|
|
3079
|
+
};
|
|
3080
|
+
}
|
|
3081
|
+
state.chart.overrideOverlay(overrideData);
|
|
3082
|
+
}
|
|
3083
|
+
},
|
|
3084
|
+
[state.chart]
|
|
3085
|
+
);
|
|
3086
|
+
const clearAnnotations = useCallback(() => {
|
|
3087
|
+
setAnnotations((prev) => {
|
|
3088
|
+
for (const annotation of prev) {
|
|
3089
|
+
state.chart?.removeOverlay({ id: annotation.id });
|
|
3090
|
+
}
|
|
3091
|
+
return [];
|
|
3092
|
+
});
|
|
3093
|
+
}, [state.chart]);
|
|
3094
|
+
useEffect(() => {
|
|
3095
|
+
return () => {
|
|
3096
|
+
state.chart?.removeOverlay({ groupId: "annotations" });
|
|
3097
|
+
};
|
|
3098
|
+
}, [state.chart]);
|
|
3099
|
+
return {
|
|
3100
|
+
annotations,
|
|
3101
|
+
addAnnotation,
|
|
3102
|
+
removeAnnotation,
|
|
3103
|
+
updateAnnotation,
|
|
3104
|
+
clearAnnotations
|
|
3105
|
+
};
|
|
3106
|
+
}
|
|
2247
3107
|
|
|
2248
3108
|
// src/utils/createDataLoader.ts
|
|
2249
3109
|
function createDataLoader(datafeed, dispatch) {
|
|
@@ -2309,6 +3169,6 @@ function createDataLoader(datafeed, dispatch) {
|
|
|
2309
3169
|
};
|
|
2310
3170
|
}
|
|
2311
3171
|
|
|
2312
|
-
export { CANDLE_TYPES, COMPARE_RULES, DEFAULT_PERIODS, DRAWING_CATEGORIES, INDICATOR_PARAMS, KlinechartsUIDispatchContext, KlinechartsUIProvider, KlinechartsUIStateContext, MAIN_INDICATORS, PRICE_AXIS_TYPES, SUB_INDICATORS, TIMEZONES, TOOLTIP_SHOW_RULES, YAXIS_POSITIONS, createDataLoader, useDrawingTools, useFullscreen, useIndicators, useKlinechartsUI, useKlinechartsUILoading, useKlinechartsUISettings, useKlinechartsUITheme, useLayoutManager, useOrderLines, usePeriods, useScreenshot, useScriptEditor, useSymbolSearch, useTimezone, useUndoRedo };
|
|
3172
|
+
export { CANDLE_TYPES, COMPARE_RULES, DEFAULT_PERIODS, DRAWING_CATEGORIES, INDICATOR_PARAMS, KlinechartsUIDispatchContext, KlinechartsUIProvider, KlinechartsUIStateContext, MAIN_INDICATORS, PRICE_AXIS_TYPES, SUB_INDICATORS, TIMEZONES, TOOLTIP_SHOW_RULES, YAXIS_POSITIONS, createDataLoader, useAlerts, useAnnotations, useCompare, useCrosshair, useDataExport, useDrawingTools, useFullscreen, useIndicators, useKlinechartsUI, useKlinechartsUILoading, useKlinechartsUISettings, useKlinechartsUITheme, useLayoutManager, useMeasure, useOrderLines, usePeriods, useReplay, useScreenshot, useScriptEditor, useSymbolSearch, useTimezone, useUndoRedo, useWatchlist };
|
|
2313
3173
|
//# sourceMappingURL=index.js.map
|
|
2314
3174
|
//# sourceMappingURL=index.js.map
|