react-klinecharts-ui 0.3.0 → 0.4.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 +33 -2
- package/dist/index.cjs +196 -74
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +43 -4
- package/dist/index.d.ts +43 -4
- package/dist/index.js +196 -74
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -206,6 +206,7 @@ interface KlinechartsUIState {
|
|
|
206
206
|
periods: TerminalPeriod[]; // List of available timeframes
|
|
207
207
|
mainIndicators: string[]; // Active main chart indicators
|
|
208
208
|
subIndicators: Record<string, string>; // Active sub-indicators: { name → paneId }
|
|
209
|
+
indicatorAxes: Record<string, string>; // Custom Y-axis bindings: { indicatorId → yAxisId }
|
|
209
210
|
styles: DeepPartial<Styles> | undefined; // Custom klinecharts styles
|
|
210
211
|
screenshotUrl: string | null; // URL of the last screenshot
|
|
211
212
|
}
|
|
@@ -225,6 +226,7 @@ type KlinechartsUIAction =
|
|
|
225
226
|
| { type: "SET_LOADING"; isLoading: boolean }
|
|
226
227
|
| { type: "SET_MAIN_INDICATORS"; indicators: string[] }
|
|
227
228
|
| { type: "SET_SUB_INDICATORS"; indicators: Record<string, string> }
|
|
229
|
+
| { type: "SET_INDICATOR_AXES"; axes: Record<string, string> }
|
|
228
230
|
| { type: "SET_STYLES"; styles: DeepPartial<Styles> | undefined }
|
|
229
231
|
| { type: "SET_LOCALE"; locale: string }
|
|
230
232
|
| { type: "SET_SCREENSHOT_URL"; url: string | null };
|
|
@@ -459,6 +461,9 @@ const {
|
|
|
459
461
|
getIndicatorParams,
|
|
460
462
|
isMainIndicatorActive,
|
|
461
463
|
isSubIndicatorActive,
|
|
464
|
+
indicatorAxes,
|
|
465
|
+
getIndicatorAxis,
|
|
466
|
+
bindIndicatorToNewAxis,
|
|
462
467
|
} = useIndicators();
|
|
463
468
|
```
|
|
464
469
|
|
|
@@ -484,9 +489,9 @@ interface IndicatorInfo {
|
|
|
484
489
|
|
|
485
490
|
| Method | Description |
|
|
486
491
|
| --------------------------------------------- | ------------------------------------------------------------- |
|
|
487
|
-
| `addMainIndicator(name)`
|
|
492
|
+
| `addMainIndicator(name, options?)` | Creates the indicator on `candle_pane` with id `main_${name}`. Pass `{ yAxis }` to bind it to a secondary axis (see below) |
|
|
488
493
|
| `removeMainIndicator(name)` | Removes the indicator and updates state |
|
|
489
|
-
| `addSubIndicator(name)`
|
|
494
|
+
| `addSubIndicator(name, options?)` | Creates the indicator on a new sub-pane with id `sub_${name}`. Also accepts `{ yAxis }` |
|
|
490
495
|
| `removeSubIndicator(name)` | Removes the sub-indicator and its pane |
|
|
491
496
|
| `toggleMainIndicator(name)` | `add` if inactive, `remove` if active |
|
|
492
497
|
| `toggleSubIndicator(name)` | Same for sub-indicators |
|
|
@@ -497,6 +502,32 @@ interface IndicatorInfo {
|
|
|
497
502
|
| `getIndicatorParams(name)` | Returns `[{ label, defaultValue }]` or `[]` if no parameters |
|
|
498
503
|
| `isMainIndicatorActive(name)` | Quick active check |
|
|
499
504
|
| `isSubIndicatorActive(name)` | Quick active check |
|
|
505
|
+
| `getIndicatorAxis(name, isMain)` | Returns the custom `yAxisId` an indicator is bound to, or `undefined` for the default axis |
|
|
506
|
+
| `bindIndicatorToNewAxis(name, isMain, yAxis?)`| Rebinds an existing indicator to a different Y-axis (omit `yAxis` to return it to the default axis) |
|
|
507
|
+
|
|
508
|
+
#### Secondary Y-axis binding (multiple Y-axes)
|
|
509
|
+
|
|
510
|
+
klinecharts v10 supports several independent Y-axes on one pane, so an indicator whose value range differs sharply from price (RSI 0–100, volume, …) can get its own scale instead of distorting the shared price axis or being forced into a separate sub-pane.
|
|
511
|
+
|
|
512
|
+
```typescript
|
|
513
|
+
// Add RSI on the main pane, on its own left axis (price scale stays intact)
|
|
514
|
+
addMainIndicator("RSI", { yAxis: { id: "rsi_axis", position: "left" } });
|
|
515
|
+
|
|
516
|
+
// Later: move it back to the shared price axis
|
|
517
|
+
bindIndicatorToNewAxis("RSI", true);
|
|
518
|
+
|
|
519
|
+
// Or move an indicator already on the chart onto a dedicated right axis
|
|
520
|
+
bindIndicatorToNewAxis("VOL", true, { id: "vol_axis", position: "right" });
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
`yAxis` is a klinecharts `YAxisOverride`; the key field is **`id`** — indicators sharing the same `id` share one axis. Useful extras: `position` (`"left" | "right"`), `name` (scale type: `"normal" | "percentage" | "log"`), `inside`, and `needWidget` (set `false` for an invisible axis — own scale, no labels).
|
|
524
|
+
|
|
525
|
+
This binding is a **persistent property, not a one-shot action**. It is tracked in provider state (`indicatorAxes`, keyed by indicator id) and preserved across:
|
|
526
|
+
|
|
527
|
+
- **undo/redo** — toggling an indicator off and back on (`useUndoRedo`) restores it on the same axis, not the price axis;
|
|
528
|
+
- **layout presets** — `useLayoutManager` save/load reproduces each indicator's axis 1:1.
|
|
529
|
+
|
|
530
|
+
> Note: `bindIndicatorToNewAxis` removes and recreates the indicator (v10 `overrideIndicator` cannot rebind an axis), preserving calc params, styles and visibility. The rebind action itself is not pushed onto the undo stack.
|
|
500
531
|
|
|
501
532
|
#### Available indicators
|
|
502
533
|
|
package/dist/index.cjs
CHANGED
|
@@ -58,6 +58,8 @@ function reducer(state, action) {
|
|
|
58
58
|
return { ...state, mainIndicators: action.indicators };
|
|
59
59
|
case "SET_SUB_INDICATORS":
|
|
60
60
|
return { ...state, subIndicators: action.indicators };
|
|
61
|
+
case "SET_INDICATOR_AXES":
|
|
62
|
+
return { ...state, indicatorAxes: action.axes };
|
|
61
63
|
case "SET_STYLES":
|
|
62
64
|
return { ...state, styles: action.styles };
|
|
63
65
|
case "SET_LOCALE":
|
|
@@ -120,6 +122,7 @@ function KlinechartsUIProvider({
|
|
|
120
122
|
(acc, name) => ({ ...acc, [name]: "" }),
|
|
121
123
|
{}
|
|
122
124
|
),
|
|
125
|
+
indicatorAxes: {},
|
|
123
126
|
styles: opts.styles,
|
|
124
127
|
screenshotUrl: null
|
|
125
128
|
})
|
|
@@ -667,62 +670,95 @@ function useIndicators() {
|
|
|
667
670
|
];
|
|
668
671
|
}, [state.subIndicators]);
|
|
669
672
|
const addMainIndicator = react.useCallback(
|
|
670
|
-
(name) => {
|
|
673
|
+
(name, options) => {
|
|
671
674
|
if (state.mainIndicators.includes(name)) return;
|
|
675
|
+
const yAxis = options?.yAxis;
|
|
672
676
|
state.chart?.createIndicator(
|
|
673
|
-
{ name, id: `main_${name}
|
|
674
|
-
true,
|
|
675
|
-
{ id: "candle_pane" }
|
|
677
|
+
{ name, id: `main_${name}` },
|
|
678
|
+
{ isStack: true, pane: { id: "candle_pane" }, yAxis }
|
|
676
679
|
);
|
|
677
680
|
const newIndicators = [...state.mainIndicators, name];
|
|
678
681
|
dispatch({ type: "SET_MAIN_INDICATORS", indicators: newIndicators });
|
|
682
|
+
if (yAxis?.id) {
|
|
683
|
+
dispatch({
|
|
684
|
+
type: "SET_INDICATOR_AXES",
|
|
685
|
+
axes: { ...state.indicatorAxes, [`main_${name}`]: yAxis.id }
|
|
686
|
+
});
|
|
687
|
+
}
|
|
679
688
|
undoRedoListenerRef.current?.({
|
|
680
689
|
type: "indicator_toggled",
|
|
681
|
-
data: {
|
|
690
|
+
data: {
|
|
691
|
+
name,
|
|
692
|
+
wasActive: false,
|
|
693
|
+
isMain: true,
|
|
694
|
+
paneId: "candle_pane",
|
|
695
|
+
yAxisId: yAxis?.id
|
|
696
|
+
}
|
|
682
697
|
});
|
|
683
698
|
},
|
|
684
|
-
[state.chart, state.mainIndicators, dispatch, undoRedoListenerRef]
|
|
699
|
+
[state.chart, state.mainIndicators, state.indicatorAxes, dispatch, undoRedoListenerRef]
|
|
685
700
|
);
|
|
686
701
|
const removeMainIndicator = react.useCallback(
|
|
687
702
|
(name) => {
|
|
688
|
-
|
|
703
|
+
const id = `main_${name}`;
|
|
704
|
+
const yAxisId = state.indicatorAxes[id];
|
|
705
|
+
state.chart?.removeIndicator({ id });
|
|
689
706
|
const newIndicators = state.mainIndicators.filter((n) => n !== name);
|
|
690
707
|
dispatch({ type: "SET_MAIN_INDICATORS", indicators: newIndicators });
|
|
708
|
+
if (yAxisId) {
|
|
709
|
+
const nextAxes = { ...state.indicatorAxes };
|
|
710
|
+
delete nextAxes[id];
|
|
711
|
+
dispatch({ type: "SET_INDICATOR_AXES", axes: nextAxes });
|
|
712
|
+
}
|
|
691
713
|
undoRedoListenerRef.current?.({
|
|
692
714
|
type: "indicator_toggled",
|
|
693
|
-
data: { name, wasActive: true, isMain: true, paneId: "candle_pane" }
|
|
715
|
+
data: { name, wasActive: true, isMain: true, paneId: "candle_pane", yAxisId }
|
|
694
716
|
});
|
|
695
717
|
},
|
|
696
|
-
[state.chart, state.mainIndicators, dispatch, undoRedoListenerRef]
|
|
718
|
+
[state.chart, state.mainIndicators, state.indicatorAxes, dispatch, undoRedoListenerRef]
|
|
697
719
|
);
|
|
698
720
|
const addSubIndicator = react.useCallback(
|
|
699
|
-
(name) => {
|
|
721
|
+
(name, options) => {
|
|
700
722
|
if (name in state.subIndicators) return;
|
|
701
723
|
const id = `sub_${name}`;
|
|
702
|
-
|
|
724
|
+
const yAxis = options?.yAxis;
|
|
725
|
+
state.chart?.createIndicator({ name, id }, { yAxis });
|
|
703
726
|
const paneId = state.chart?.getIndicators({ id })?.[0]?.paneId ?? "";
|
|
704
727
|
const newIndicators = { ...state.subIndicators, [name]: paneId };
|
|
705
728
|
dispatch({ type: "SET_SUB_INDICATORS", indicators: newIndicators });
|
|
729
|
+
if (yAxis?.id) {
|
|
730
|
+
dispatch({
|
|
731
|
+
type: "SET_INDICATOR_AXES",
|
|
732
|
+
axes: { ...state.indicatorAxes, [id]: yAxis.id }
|
|
733
|
+
});
|
|
734
|
+
}
|
|
706
735
|
undoRedoListenerRef.current?.({
|
|
707
736
|
type: "indicator_toggled",
|
|
708
|
-
data: { name, wasActive: false, isMain: false, paneId }
|
|
737
|
+
data: { name, wasActive: false, isMain: false, paneId, yAxisId: yAxis?.id }
|
|
709
738
|
});
|
|
710
739
|
},
|
|
711
|
-
[state.chart, state.subIndicators, dispatch, undoRedoListenerRef]
|
|
740
|
+
[state.chart, state.subIndicators, state.indicatorAxes, dispatch, undoRedoListenerRef]
|
|
712
741
|
);
|
|
713
742
|
const removeSubIndicator = react.useCallback(
|
|
714
743
|
(name) => {
|
|
744
|
+
const id = `sub_${name}`;
|
|
715
745
|
const paneId = state.subIndicators[name] ?? "";
|
|
716
|
-
state.
|
|
746
|
+
const yAxisId = state.indicatorAxes[id];
|
|
747
|
+
state.chart?.removeIndicator({ id });
|
|
717
748
|
const newIndicators = { ...state.subIndicators };
|
|
718
749
|
delete newIndicators[name];
|
|
719
750
|
dispatch({ type: "SET_SUB_INDICATORS", indicators: newIndicators });
|
|
751
|
+
if (yAxisId) {
|
|
752
|
+
const nextAxes = { ...state.indicatorAxes };
|
|
753
|
+
delete nextAxes[id];
|
|
754
|
+
dispatch({ type: "SET_INDICATOR_AXES", axes: nextAxes });
|
|
755
|
+
}
|
|
720
756
|
undoRedoListenerRef.current?.({
|
|
721
757
|
type: "indicator_toggled",
|
|
722
|
-
data: { name, wasActive: true, isMain: false, paneId }
|
|
758
|
+
data: { name, wasActive: true, isMain: false, paneId, yAxisId }
|
|
723
759
|
});
|
|
724
760
|
},
|
|
725
|
-
[state.chart, state.subIndicators, dispatch, undoRedoListenerRef]
|
|
761
|
+
[state.chart, state.subIndicators, state.indicatorAxes, dispatch, undoRedoListenerRef]
|
|
726
762
|
);
|
|
727
763
|
const toggleMainIndicator = react.useCallback(
|
|
728
764
|
(name) => {
|
|
@@ -752,8 +788,10 @@ function useIndicators() {
|
|
|
752
788
|
[state.chart]
|
|
753
789
|
);
|
|
754
790
|
const updateIndicatorParams = react.useCallback(
|
|
755
|
-
|
|
756
|
-
|
|
791
|
+
// `paneId` is kept in the signature for API stability; klinecharts v10
|
|
792
|
+
// `overrideIndicator` no longer accepts it and targets by name/id instead.
|
|
793
|
+
(name, _paneId, params) => {
|
|
794
|
+
state.chart?.overrideIndicator({ name, calcParams: params });
|
|
757
795
|
},
|
|
758
796
|
[state.chart]
|
|
759
797
|
);
|
|
@@ -774,11 +812,9 @@ function useIndicators() {
|
|
|
774
812
|
{
|
|
775
813
|
name,
|
|
776
814
|
id: `main_${name}`,
|
|
777
|
-
paneId: "candle_pane",
|
|
778
815
|
...prevCalcParams ? { calcParams: prevCalcParams } : {}
|
|
779
816
|
},
|
|
780
|
-
true,
|
|
781
|
-
{ id: "candle_pane" }
|
|
817
|
+
{ isStack: true, pane: { id: "candle_pane" } }
|
|
782
818
|
);
|
|
783
819
|
const newSub = { ...state.subIndicators };
|
|
784
820
|
delete newSub[name];
|
|
@@ -894,7 +930,7 @@ function useIndicators() {
|
|
|
894
930
|
if (sub.styles) {
|
|
895
931
|
state.chart.overrideIndicator({
|
|
896
932
|
name: sub.name,
|
|
897
|
-
|
|
933
|
+
id,
|
|
898
934
|
styles: sub.styles
|
|
899
935
|
});
|
|
900
936
|
}
|
|
@@ -903,6 +939,44 @@ function useIndicators() {
|
|
|
903
939
|
},
|
|
904
940
|
[state.chart, state.subIndicators, dispatch]
|
|
905
941
|
);
|
|
942
|
+
const getIndicatorAxis = react.useCallback(
|
|
943
|
+
(name, isMain) => state.indicatorAxes[isMain ? `main_${name}` : `sub_${name}`],
|
|
944
|
+
[state.indicatorAxes]
|
|
945
|
+
);
|
|
946
|
+
const bindIndicatorToNewAxis = react.useCallback(
|
|
947
|
+
(name, isMain, yAxis) => {
|
|
948
|
+
if (!state.chart) return;
|
|
949
|
+
const id = isMain ? `main_${name}` : `sub_${name}`;
|
|
950
|
+
const current = state.chart.getIndicators({ id })?.[0];
|
|
951
|
+
if (!current) return;
|
|
952
|
+
const calcParams = current.calcParams;
|
|
953
|
+
const styles = current.styles;
|
|
954
|
+
const visible = current.visible ?? true;
|
|
955
|
+
const paneId = isMain ? "candle_pane" : current.paneId ?? state.subIndicators[name] ?? "";
|
|
956
|
+
state.chart.removeIndicator({ id });
|
|
957
|
+
state.chart.createIndicator(
|
|
958
|
+
{
|
|
959
|
+
name,
|
|
960
|
+
id,
|
|
961
|
+
...calcParams ? { calcParams } : {},
|
|
962
|
+
visible
|
|
963
|
+
},
|
|
964
|
+
{
|
|
965
|
+
isStack: isMain,
|
|
966
|
+
...paneId ? { pane: { id: paneId } } : {},
|
|
967
|
+
yAxis
|
|
968
|
+
}
|
|
969
|
+
);
|
|
970
|
+
if (styles) {
|
|
971
|
+
state.chart.overrideIndicator({ name, id, styles });
|
|
972
|
+
}
|
|
973
|
+
const nextAxes = { ...state.indicatorAxes };
|
|
974
|
+
if (yAxis?.id) nextAxes[id] = yAxis.id;
|
|
975
|
+
else delete nextAxes[id];
|
|
976
|
+
dispatch({ type: "SET_INDICATOR_AXES", axes: nextAxes });
|
|
977
|
+
},
|
|
978
|
+
[state.chart, state.indicatorAxes, state.subIndicators, dispatch]
|
|
979
|
+
);
|
|
906
980
|
return {
|
|
907
981
|
mainIndicators,
|
|
908
982
|
subIndicators,
|
|
@@ -926,7 +1000,10 @@ function useIndicators() {
|
|
|
926
1000
|
collapseSubIndicator,
|
|
927
1001
|
expandSubIndicator,
|
|
928
1002
|
isSubIndicatorCollapsed,
|
|
929
|
-
reorderSubIndicator
|
|
1003
|
+
reorderSubIndicator,
|
|
1004
|
+
indicatorAxes: state.indicatorAxes,
|
|
1005
|
+
getIndicatorAxis,
|
|
1006
|
+
bindIndicatorToNewAxis
|
|
930
1007
|
};
|
|
931
1008
|
}
|
|
932
1009
|
|
|
@@ -1236,20 +1313,23 @@ function useKlinechartsUISettings() {
|
|
|
1236
1313
|
() => TOOLTIP_SHOW_RULES.map((r) => ({ key: r, localeKey: r })),
|
|
1237
1314
|
[]
|
|
1238
1315
|
);
|
|
1316
|
+
const applyPaneAxis = react.useCallback(
|
|
1317
|
+
(axis) => {
|
|
1318
|
+
state.chart?.overrideYAxis?.({ paneId: "candle_pane", ...axis });
|
|
1319
|
+
},
|
|
1320
|
+
[state.chart]
|
|
1321
|
+
);
|
|
1239
1322
|
const hasAppliedInitial = react.useRef(false);
|
|
1240
1323
|
react.useEffect(() => {
|
|
1241
1324
|
if (!state.chart || hasAppliedInitial.current) return;
|
|
1242
1325
|
hasAppliedInitial.current = true;
|
|
1243
|
-
const
|
|
1244
|
-
if (
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
...settings.yAxisPosition !== "right" && { position: settings.yAxisPosition },
|
|
1251
|
-
...settings.yAxisInside && { inside: true }
|
|
1252
|
-
}
|
|
1326
|
+
const needsAxisOverride = settings.reverseCoordinate || settings.priceAxisType !== "normal" || settings.yAxisPosition !== "right" || settings.yAxisInside;
|
|
1327
|
+
if (needsAxisOverride) {
|
|
1328
|
+
applyPaneAxis({
|
|
1329
|
+
...settings.priceAxisType !== "normal" && { name: settings.priceAxisType },
|
|
1330
|
+
...settings.reverseCoordinate && { reverse: true },
|
|
1331
|
+
...settings.yAxisPosition !== "right" && { position: settings.yAxisPosition },
|
|
1332
|
+
...settings.yAxisInside && { inside: true }
|
|
1253
1333
|
});
|
|
1254
1334
|
}
|
|
1255
1335
|
if (settings.showIndicatorLastValue) {
|
|
@@ -1272,12 +1352,6 @@ function useKlinechartsUISettings() {
|
|
|
1272
1352
|
},
|
|
1273
1353
|
[state.chart]
|
|
1274
1354
|
);
|
|
1275
|
-
const applyPaneAxis = react.useCallback(
|
|
1276
|
-
(axis) => {
|
|
1277
|
-
state.chart?.setPaneOptions({ id: "candle_pane", axis });
|
|
1278
|
-
},
|
|
1279
|
-
[state.chart]
|
|
1280
|
-
);
|
|
1281
1355
|
const setCandleType = react.useCallback(
|
|
1282
1356
|
(type) => {
|
|
1283
1357
|
applyStyle("candle.type", type);
|
|
@@ -1425,11 +1499,8 @@ function useKlinechartsUISettings() {
|
|
|
1425
1499
|
const resetToDefaults = react.useCallback(() => {
|
|
1426
1500
|
setSettings(defaultSettings);
|
|
1427
1501
|
state.chart?.setStyles(state.theme);
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
axis: { name: "normal", reverse: false, position: "right", inside: false }
|
|
1431
|
-
});
|
|
1432
|
-
}, [state.chart, state.theme]);
|
|
1502
|
+
applyPaneAxis({ name: "normal", reverse: false, position: "right", inside: false });
|
|
1503
|
+
}, [state.chart, state.theme, applyPaneAxis]);
|
|
1433
1504
|
return {
|
|
1434
1505
|
...settings,
|
|
1435
1506
|
candleTypes,
|
|
@@ -1686,21 +1757,28 @@ function useUndoRedo() {
|
|
|
1686
1757
|
break;
|
|
1687
1758
|
}
|
|
1688
1759
|
case "indicator_toggled": {
|
|
1689
|
-
const { name, wasActive, isMain, paneId } = action.data;
|
|
1760
|
+
const { name, wasActive, isMain, paneId, yAxisId } = action.data;
|
|
1761
|
+
const id = isMain ? `main_${name}` : `sub_${name}`;
|
|
1690
1762
|
if (wasActive) {
|
|
1691
1763
|
if (isMain) {
|
|
1692
1764
|
state.chart.createIndicator(
|
|
1693
|
-
{ name, id
|
|
1694
|
-
|
|
1695
|
-
|
|
1765
|
+
{ name, id },
|
|
1766
|
+
{
|
|
1767
|
+
isStack: true,
|
|
1768
|
+
pane: { id: "candle_pane" },
|
|
1769
|
+
...yAxisId ? { yAxis: { id: yAxisId } } : {}
|
|
1770
|
+
}
|
|
1696
1771
|
);
|
|
1697
1772
|
dispatch({
|
|
1698
1773
|
type: "SET_MAIN_INDICATORS",
|
|
1699
1774
|
indicators: [...state.mainIndicators, name]
|
|
1700
1775
|
});
|
|
1701
1776
|
} else {
|
|
1702
|
-
state.chart.createIndicator(
|
|
1703
|
-
|
|
1777
|
+
state.chart.createIndicator(
|
|
1778
|
+
{ name, id },
|
|
1779
|
+
yAxisId ? { yAxis: { id: yAxisId } } : void 0
|
|
1780
|
+
);
|
|
1781
|
+
const newPaneId = state.chart.getIndicators({ id })?.[0]?.paneId ?? "";
|
|
1704
1782
|
dispatch({
|
|
1705
1783
|
type: "SET_SUB_INDICATORS",
|
|
1706
1784
|
indicators: {
|
|
@@ -1709,9 +1787,15 @@ function useUndoRedo() {
|
|
|
1709
1787
|
}
|
|
1710
1788
|
});
|
|
1711
1789
|
}
|
|
1790
|
+
if (yAxisId) {
|
|
1791
|
+
dispatch({
|
|
1792
|
+
type: "SET_INDICATOR_AXES",
|
|
1793
|
+
axes: { ...state.indicatorAxes, [id]: yAxisId }
|
|
1794
|
+
});
|
|
1795
|
+
}
|
|
1712
1796
|
} else {
|
|
1797
|
+
state.chart.removeIndicator({ id });
|
|
1713
1798
|
if (isMain) {
|
|
1714
|
-
state.chart.removeIndicator({ id: `main_${name}` });
|
|
1715
1799
|
dispatch({
|
|
1716
1800
|
type: "SET_MAIN_INDICATORS",
|
|
1717
1801
|
indicators: state.mainIndicators.filter(
|
|
@@ -1719,7 +1803,6 @@ function useUndoRedo() {
|
|
|
1719
1803
|
)
|
|
1720
1804
|
});
|
|
1721
1805
|
} else {
|
|
1722
|
-
state.chart.removeIndicator({ id: `sub_${name}` });
|
|
1723
1806
|
const newSub = { ...state.subIndicators };
|
|
1724
1807
|
delete newSub[name];
|
|
1725
1808
|
dispatch({
|
|
@@ -1727,6 +1810,11 @@ function useUndoRedo() {
|
|
|
1727
1810
|
indicators: newSub
|
|
1728
1811
|
});
|
|
1729
1812
|
}
|
|
1813
|
+
if (state.indicatorAxes[id]) {
|
|
1814
|
+
const nextAxes = { ...state.indicatorAxes };
|
|
1815
|
+
delete nextAxes[id];
|
|
1816
|
+
dispatch({ type: "SET_INDICATOR_AXES", axes: nextAxes });
|
|
1817
|
+
}
|
|
1730
1818
|
}
|
|
1731
1819
|
setRedoStack((prev) => [
|
|
1732
1820
|
...prev,
|
|
@@ -1736,7 +1824,8 @@ function useUndoRedo() {
|
|
|
1736
1824
|
name,
|
|
1737
1825
|
wasActive: !wasActive,
|
|
1738
1826
|
isMain,
|
|
1739
|
-
paneId
|
|
1827
|
+
paneId,
|
|
1828
|
+
yAxisId
|
|
1740
1829
|
}
|
|
1741
1830
|
}
|
|
1742
1831
|
]);
|
|
@@ -1746,7 +1835,7 @@ function useUndoRedo() {
|
|
|
1746
1835
|
} finally {
|
|
1747
1836
|
isProcessingRef.current = false;
|
|
1748
1837
|
}
|
|
1749
|
-
}, [undoStack, state.chart, state.mainIndicators, state.subIndicators, dispatch]);
|
|
1838
|
+
}, [undoStack, state.chart, state.mainIndicators, state.subIndicators, state.indicatorAxes, dispatch]);
|
|
1750
1839
|
const redo = react.useCallback(() => {
|
|
1751
1840
|
if (redoStack.length === 0 || !state.chart || isProcessingRef.current)
|
|
1752
1841
|
return;
|
|
@@ -1798,21 +1887,28 @@ function useUndoRedo() {
|
|
|
1798
1887
|
break;
|
|
1799
1888
|
}
|
|
1800
1889
|
case "indicator_toggled": {
|
|
1801
|
-
const { name, wasActive, isMain } = action.data;
|
|
1890
|
+
const { name, wasActive, isMain, paneId, yAxisId } = action.data;
|
|
1891
|
+
const id = isMain ? `main_${name}` : `sub_${name}`;
|
|
1802
1892
|
if (wasActive) {
|
|
1803
1893
|
if (isMain) {
|
|
1804
1894
|
state.chart.createIndicator(
|
|
1805
|
-
{ name, id
|
|
1806
|
-
|
|
1807
|
-
|
|
1895
|
+
{ name, id },
|
|
1896
|
+
{
|
|
1897
|
+
isStack: true,
|
|
1898
|
+
pane: { id: "candle_pane" },
|
|
1899
|
+
...yAxisId ? { yAxis: { id: yAxisId } } : {}
|
|
1900
|
+
}
|
|
1808
1901
|
);
|
|
1809
1902
|
dispatch({
|
|
1810
1903
|
type: "SET_MAIN_INDICATORS",
|
|
1811
1904
|
indicators: [...state.mainIndicators, name]
|
|
1812
1905
|
});
|
|
1813
1906
|
} else {
|
|
1814
|
-
state.chart.createIndicator(
|
|
1815
|
-
|
|
1907
|
+
state.chart.createIndicator(
|
|
1908
|
+
{ name, id },
|
|
1909
|
+
yAxisId ? { yAxis: { id: yAxisId } } : void 0
|
|
1910
|
+
);
|
|
1911
|
+
const newPaneId = state.chart.getIndicators({ id })?.[0]?.paneId ?? "";
|
|
1816
1912
|
dispatch({
|
|
1817
1913
|
type: "SET_SUB_INDICATORS",
|
|
1818
1914
|
indicators: {
|
|
@@ -1821,9 +1917,15 @@ function useUndoRedo() {
|
|
|
1821
1917
|
}
|
|
1822
1918
|
});
|
|
1823
1919
|
}
|
|
1920
|
+
if (yAxisId) {
|
|
1921
|
+
dispatch({
|
|
1922
|
+
type: "SET_INDICATOR_AXES",
|
|
1923
|
+
axes: { ...state.indicatorAxes, [id]: yAxisId }
|
|
1924
|
+
});
|
|
1925
|
+
}
|
|
1824
1926
|
} else {
|
|
1927
|
+
state.chart.removeIndicator({ id });
|
|
1825
1928
|
if (isMain) {
|
|
1826
|
-
state.chart.removeIndicator({ id: `main_${name}` });
|
|
1827
1929
|
dispatch({
|
|
1828
1930
|
type: "SET_MAIN_INDICATORS",
|
|
1829
1931
|
indicators: state.mainIndicators.filter(
|
|
@@ -1831,7 +1933,6 @@ function useUndoRedo() {
|
|
|
1831
1933
|
)
|
|
1832
1934
|
});
|
|
1833
1935
|
} else {
|
|
1834
|
-
state.chart.removeIndicator({ id: `sub_${name}` });
|
|
1835
1936
|
const newSub = { ...state.subIndicators };
|
|
1836
1937
|
delete newSub[name];
|
|
1837
1938
|
dispatch({
|
|
@@ -1839,6 +1940,11 @@ function useUndoRedo() {
|
|
|
1839
1940
|
indicators: newSub
|
|
1840
1941
|
});
|
|
1841
1942
|
}
|
|
1943
|
+
if (state.indicatorAxes[id]) {
|
|
1944
|
+
const nextAxes = { ...state.indicatorAxes };
|
|
1945
|
+
delete nextAxes[id];
|
|
1946
|
+
dispatch({ type: "SET_INDICATOR_AXES", axes: nextAxes });
|
|
1947
|
+
}
|
|
1842
1948
|
}
|
|
1843
1949
|
setUndoStack((prev) => [
|
|
1844
1950
|
...prev,
|
|
@@ -1847,7 +1953,9 @@ function useUndoRedo() {
|
|
|
1847
1953
|
data: {
|
|
1848
1954
|
name,
|
|
1849
1955
|
wasActive: !wasActive,
|
|
1850
|
-
isMain
|
|
1956
|
+
isMain,
|
|
1957
|
+
paneId,
|
|
1958
|
+
yAxisId
|
|
1851
1959
|
}
|
|
1852
1960
|
}
|
|
1853
1961
|
]);
|
|
@@ -1857,7 +1965,7 @@ function useUndoRedo() {
|
|
|
1857
1965
|
} finally {
|
|
1858
1966
|
isProcessingRef.current = false;
|
|
1859
1967
|
}
|
|
1860
|
-
}, [redoStack, state.chart, state.mainIndicators, state.subIndicators, dispatch]);
|
|
1968
|
+
}, [redoStack, state.chart, state.mainIndicators, state.subIndicators, state.indicatorAxes, dispatch]);
|
|
1861
1969
|
react.useEffect(() => {
|
|
1862
1970
|
const handleKeyDown = (e) => {
|
|
1863
1971
|
const isCtrlOrMeta = e.ctrlKey || e.metaKey;
|
|
@@ -1931,12 +2039,14 @@ function useLayoutManager() {
|
|
|
1931
2039
|
if (indicatorMap) {
|
|
1932
2040
|
indicatorMap.forEach((paneIndicators, paneId) => {
|
|
1933
2041
|
paneIndicators.forEach((indicator) => {
|
|
2042
|
+
const yAxisId = state.indicatorAxes[indicator.id];
|
|
1934
2043
|
indicators2.push({
|
|
1935
2044
|
paneId,
|
|
1936
2045
|
name: indicator.name,
|
|
1937
2046
|
calcParams: indicator.calcParams,
|
|
1938
2047
|
visible: indicator.visible,
|
|
1939
|
-
...indicator.styles ? { styles: indicator.styles } : {}
|
|
2048
|
+
...indicator.styles ? { styles: indicator.styles } : {},
|
|
2049
|
+
...yAxisId ? { yAxisId } : {}
|
|
1940
2050
|
});
|
|
1941
2051
|
});
|
|
1942
2052
|
});
|
|
@@ -1964,7 +2074,7 @@ function useLayoutManager() {
|
|
|
1964
2074
|
indicators: indicators2,
|
|
1965
2075
|
drawings
|
|
1966
2076
|
};
|
|
1967
|
-
}, [state.chart, state.symbol, state.period]);
|
|
2077
|
+
}, [state.chart, state.symbol, state.period, state.indicatorAxes]);
|
|
1968
2078
|
const saveLayout = react.useCallback(
|
|
1969
2079
|
(name) => {
|
|
1970
2080
|
const chartState = serializeState();
|
|
@@ -2012,25 +2122,35 @@ function useLayoutManager() {
|
|
|
2012
2122
|
}
|
|
2013
2123
|
const newMainIndicators = [];
|
|
2014
2124
|
const newSubIndicators = {};
|
|
2125
|
+
const restoredAxes = {};
|
|
2015
2126
|
if (chartState.indicators) {
|
|
2016
2127
|
for (const ind of chartState.indicators) {
|
|
2128
|
+
const isMain = ind.paneId === "candle_pane";
|
|
2129
|
+
const id2 = isMain ? `main_${ind.name}` : `sub_${ind.name}`;
|
|
2017
2130
|
chart.createIndicator(
|
|
2018
2131
|
{
|
|
2019
2132
|
name: ind.name,
|
|
2133
|
+
id: id2,
|
|
2020
2134
|
calcParams: ind.calcParams,
|
|
2021
2135
|
visible: ind.visible
|
|
2022
2136
|
},
|
|
2023
|
-
|
|
2024
|
-
|
|
2137
|
+
{
|
|
2138
|
+
isStack: ind.paneId !== "candle_pane",
|
|
2139
|
+
pane: { id: ind.paneId },
|
|
2140
|
+
...ind.yAxisId ? { yAxis: { id: ind.yAxisId } } : {}
|
|
2141
|
+
}
|
|
2025
2142
|
);
|
|
2026
2143
|
if (ind.styles) {
|
|
2027
2144
|
chart.overrideIndicator({
|
|
2028
2145
|
name: ind.name,
|
|
2029
|
-
|
|
2146
|
+
id: id2,
|
|
2030
2147
|
styles: ind.styles
|
|
2031
2148
|
});
|
|
2032
2149
|
}
|
|
2033
|
-
if (ind.
|
|
2150
|
+
if (ind.yAxisId) {
|
|
2151
|
+
restoredAxes[id2] = ind.yAxisId;
|
|
2152
|
+
}
|
|
2153
|
+
if (isMain) {
|
|
2034
2154
|
newMainIndicators.push(ind.name);
|
|
2035
2155
|
} else {
|
|
2036
2156
|
newSubIndicators[ind.name] = ind.paneId;
|
|
@@ -2045,6 +2165,10 @@ function useLayoutManager() {
|
|
|
2045
2165
|
type: "SET_SUB_INDICATORS",
|
|
2046
2166
|
indicators: newSubIndicators
|
|
2047
2167
|
});
|
|
2168
|
+
dispatch({
|
|
2169
|
+
type: "SET_INDICATOR_AXES",
|
|
2170
|
+
axes: restoredAxes
|
|
2171
|
+
});
|
|
2048
2172
|
if (chartState.drawings) {
|
|
2049
2173
|
for (const drawing of chartState.drawings) {
|
|
2050
2174
|
chart.createOverlay({
|
|
@@ -2270,8 +2394,7 @@ ${code}`
|
|
|
2270
2394
|
if (placement === "main") {
|
|
2271
2395
|
paneId = chart.createIndicator(
|
|
2272
2396
|
{ name: indicatorName },
|
|
2273
|
-
true,
|
|
2274
|
-
{ id: "candle_pane" }
|
|
2397
|
+
{ isStack: true, pane: { id: "candle_pane" } }
|
|
2275
2398
|
);
|
|
2276
2399
|
} else {
|
|
2277
2400
|
paneId = chart.createIndicator({ name: indicatorName });
|
|
@@ -2856,8 +2979,7 @@ function useCompare() {
|
|
|
2856
2979
|
});
|
|
2857
2980
|
const paneId = state.chart.createIndicator(
|
|
2858
2981
|
{ name: indicatorName },
|
|
2859
|
-
true,
|
|
2860
|
-
{ id: "candle_pane" }
|
|
2982
|
+
{ isStack: true, pane: { id: "candle_pane" } }
|
|
2861
2983
|
);
|
|
2862
2984
|
indicatorsRef.current.set(ticker, {
|
|
2863
2985
|
name: indicatorName,
|