@363045841yyt/klinechart 0.8.4 → 0.8.6
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 +6 -1
- package/dist/components/BaseModal.vue.d.ts +54 -0
- package/dist/components/BaseModal.vue.d.ts.map +1 -0
- package/dist/components/BatchStockDialog.vue.d.ts +13 -0
- package/dist/components/BatchStockDialog.vue.d.ts.map +1 -0
- package/dist/components/ChartSettingsDialog.vue.d.ts.map +1 -1
- package/dist/components/ColorPresetPanel.vue.d.ts +4 -1
- package/dist/components/ColorPresetPanel.vue.d.ts.map +1 -1
- package/dist/components/CompareSymbolSelector.vue.d.ts.map +1 -1
- package/dist/components/DrawingStyleToolbar.vue.d.ts.map +1 -1
- package/dist/components/Dropdown.vue.d.ts.map +1 -1
- package/dist/components/ExportProgressDialog.vue.d.ts +15 -0
- package/dist/components/ExportProgressDialog.vue.d.ts.map +1 -0
- package/dist/components/IndicatorParams.vue.d.ts.map +1 -1
- package/dist/components/IndicatorSelector.vue.d.ts.map +1 -1
- package/dist/components/KLineChart.vue.d.ts +5 -9
- package/dist/components/KLineChart.vue.d.ts.map +1 -1
- package/dist/components/LeftToolbar.vue.d.ts.map +1 -1
- package/dist/components/RangeSelectionExport.vue.d.ts +23 -0
- package/dist/components/RangeSelectionExport.vue.d.ts.map +1 -0
- package/dist/components/SymbolSelector.vue.d.ts.map +1 -1
- package/dist/components/TopToolbar.vue.d.ts.map +1 -1
- package/dist/components/common/CanvasToolbar.vue.d.ts +14 -0
- package/dist/components/common/CanvasToolbar.vue.d.ts.map +1 -0
- package/dist/components/common/CanvasToolbarStack.vue.d.ts +14 -0
- package/dist/components/common/CanvasToolbarStack.vue.d.ts.map +1 -0
- package/dist/composables/chart/useChartTheme.d.ts +329 -0
- package/dist/composables/chart/useChartTheme.d.ts.map +1 -0
- package/dist/composables/chart/useDrawingManager.d.ts +86 -0
- package/dist/composables/chart/useDrawingManager.d.ts.map +1 -0
- package/dist/composables/chart/useIndicatorManager.d.ts +38 -0
- package/dist/composables/chart/useIndicatorManager.d.ts.map +1 -0
- package/dist/composables/chart/useRangeSelection.d.ts +66 -0
- package/dist/composables/chart/useRangeSelection.d.ts.map +1 -0
- package/dist/composables/useTeleportedPopup.d.ts +8 -0
- package/dist/composables/useTeleportedPopup.d.ts.map +1 -0
- package/dist/index.cjs +9 -2
- package/dist/index.css +1 -1
- package/dist/index.js +2149 -1409
- package/dist/tools/calcRangeOverlayPixel.d.ts +15 -0
- package/dist/tools/calcRangeOverlayPixel.d.ts.map +1 -0
- package/dist/tools/getKLineIndexByTimestamp.d.ts +4 -0
- package/dist/tools/getKLineIndexByTimestamp.d.ts.map +1 -0
- package/dist/web-component.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/BaseModal.vue +292 -0
- package/src/components/BatchStockDialog.vue +128 -0
- package/src/components/ChartSettingsDialog.vue +248 -405
- package/src/components/ColorPresetPanel.vue +58 -106
- package/src/components/CompareSymbolSelector.vue +37 -10
- package/src/components/DrawingStyleToolbar.vue +33 -72
- package/src/components/Dropdown.vue +42 -19
- package/src/components/ExportProgressDialog.vue +118 -0
- package/src/components/IndicatorParams.vue +194 -321
- package/src/components/IndicatorSelector.vue +188 -405
- package/src/components/KLineChart.vue +228 -403
- package/src/components/LeftToolbar.vue +3 -2
- package/src/components/RangeSelectionExport.vue +117 -0
- package/src/components/SymbolSelector.vue +37 -10
- package/src/components/TopToolbar.vue +55 -2
- package/src/components/common/CanvasToolbar.vue +70 -0
- package/src/components/common/CanvasToolbarStack.vue +32 -0
- package/src/composables/chart/useChartTheme.ts +86 -0
- package/src/composables/chart/useDrawingManager.ts +67 -0
- package/src/composables/chart/useIndicatorManager.ts +307 -0
- package/src/composables/chart/useRangeSelection.ts +424 -0
- package/src/composables/useTeleportedPopup.ts +46 -0
- package/src/tools/calcRangeOverlayPixel.ts +28 -0
- package/src/tools/getKLineIndexByTimestamp.ts +40 -0
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Manages indicator state for both main-pane and sub-pane indicators.
|
|
3
|
+
* Provides pane layout construction, default param resolution,
|
|
4
|
+
* indicator toggle/update/reorder logic, and bridges signal subscriptions
|
|
5
|
+
* (ctrl.indicators, ctrl.subPanes) to Vue reactive refs.
|
|
6
|
+
*/
|
|
7
|
+
import { ref, computed, type Ref } from 'vue'
|
|
8
|
+
import type {
|
|
9
|
+
ChartController,
|
|
10
|
+
PaneSpec,
|
|
11
|
+
IndicatorInstance,
|
|
12
|
+
SubIndicatorType,
|
|
13
|
+
} from '@363045841yyt/klinechart-core/controllers'
|
|
14
|
+
import { getRegisteredIndicatorDefinition } from '@363045841yyt/klinechart-core/indicators'
|
|
15
|
+
import type { SemanticChartConfig } from '@363045841yyt/klinechart-core/semantic'
|
|
16
|
+
|
|
17
|
+
interface SubPaneSlot {
|
|
18
|
+
id: string
|
|
19
|
+
indicatorId: SubIndicatorType
|
|
20
|
+
params: Record<string, unknown>
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function useIndicatorManager(
|
|
24
|
+
ctrl: Ref<ChartController | null>,
|
|
25
|
+
paneRatiosRef: Ref<Record<string, number>>,
|
|
26
|
+
) {
|
|
27
|
+
const maxSubPanes = 4
|
|
28
|
+
|
|
29
|
+
const mainActiveIndicators = ref<string[]>([])
|
|
30
|
+
|
|
31
|
+
const subPanes = ref<SubPaneSlot[]>([])
|
|
32
|
+
|
|
33
|
+
const subActiveIndicators = computed(() => {
|
|
34
|
+
const ids: string[] = []
|
|
35
|
+
const seen = new Set<string>()
|
|
36
|
+
for (const pane of subPanes.value) {
|
|
37
|
+
if (!seen.has(pane.indicatorId)) {
|
|
38
|
+
seen.add(pane.indicatorId)
|
|
39
|
+
ids.push(pane.indicatorId)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return ids
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
const activeIndicators = computed(() => [
|
|
46
|
+
...mainActiveIndicators.value,
|
|
47
|
+
...subActiveIndicators.value,
|
|
48
|
+
])
|
|
49
|
+
|
|
50
|
+
const indicatorParams = ref<Record<string, Record<string, unknown>>>({})
|
|
51
|
+
|
|
52
|
+
function buildPaneLayoutIntent(): PaneSpec[] {
|
|
53
|
+
const mainRatio = paneRatiosRef.value['main'] ?? 3
|
|
54
|
+
return subPanes.value.length === 0
|
|
55
|
+
? [{ id: 'main', ratio: mainRatio, visible: true, role: 'price' }]
|
|
56
|
+
: [
|
|
57
|
+
{ id: 'main', ratio: mainRatio, visible: true, role: 'price' },
|
|
58
|
+
...subPanes.value.map((pane) => ({
|
|
59
|
+
id: pane.id,
|
|
60
|
+
ratio: paneRatiosRef.value[pane.id] ?? 1,
|
|
61
|
+
visible: true,
|
|
62
|
+
role: 'indicator' as const,
|
|
63
|
+
})),
|
|
64
|
+
]
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function getDefaultParams(
|
|
68
|
+
indicatorId: SubIndicatorType,
|
|
69
|
+
): Record<string, number | boolean | string> {
|
|
70
|
+
if (indicatorId === 'VOLUME') return {}
|
|
71
|
+
const meta = getRegisteredIndicatorDefinition(indicatorId)
|
|
72
|
+
if (meta?.runtime?.defaultConfig) {
|
|
73
|
+
return { ...meta.runtime.defaultConfig } as Record<string, number | boolean | string>
|
|
74
|
+
}
|
|
75
|
+
return {}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function isSubPaneIndicator(id: string): boolean {
|
|
79
|
+
if (id === 'VOLUME') return true
|
|
80
|
+
const def = getRegisteredIndicatorDefinition(id)
|
|
81
|
+
return !!def && def.category !== 'main'
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function addSubPane(
|
|
85
|
+
indicatorId: SubIndicatorType = 'VOLUME',
|
|
86
|
+
params?: Record<string, number | boolean | string>,
|
|
87
|
+
): boolean {
|
|
88
|
+
if (subPanes.value.length >= maxSubPanes) {
|
|
89
|
+
return false
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const mergedParams = params ?? getDefaultParams(indicatorId)
|
|
93
|
+
|
|
94
|
+
const paneId = ctrl.value?.addIndicator(indicatorId, 'sub', mergedParams)
|
|
95
|
+
if (!paneId) return false
|
|
96
|
+
return true
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function removeSubPane(paneId: string): void {
|
|
100
|
+
ctrl.value?.removeIndicator(paneId)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function clearAllSubPanes(): void {
|
|
104
|
+
for (const pane of subPanes.value) {
|
|
105
|
+
ctrl.value?.removeIndicator(pane.id)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function initIndicatorsFromConfig(semanticConfig?: SemanticChartConfig): void {
|
|
110
|
+
const config = semanticConfig
|
|
111
|
+
const c = ctrl.value
|
|
112
|
+
if (!config || !c) return
|
|
113
|
+
|
|
114
|
+
const mainIndicators = config.indicators?.main
|
|
115
|
+
if (mainIndicators) {
|
|
116
|
+
for (const indicator of mainIndicators) {
|
|
117
|
+
if (indicator.enabled) {
|
|
118
|
+
c.addIndicator(
|
|
119
|
+
indicator.type,
|
|
120
|
+
'main',
|
|
121
|
+
indicator.params as Record<string, number | boolean | string>,
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function switchSubIndicator(paneId: string, newIndicatorId: SubIndicatorType): void {
|
|
129
|
+
const nextParams = getDefaultParams(newIndicatorId)
|
|
130
|
+
ctrl.value?.replaceSubPaneIndicator(paneId, newIndicatorId, nextParams)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function handleIndicatorToggle(indicatorId: string, active: boolean) {
|
|
134
|
+
const c = ctrl.value
|
|
135
|
+
if (!c) return
|
|
136
|
+
|
|
137
|
+
const def = getRegisteredIndicatorDefinition(indicatorId)
|
|
138
|
+
const isMain = def && (def.category === 'main' || def.allowMainPane)
|
|
139
|
+
if (isMain) {
|
|
140
|
+
const existingIndicator = mainActiveIndicators.value.find((id) => id === indicatorId)
|
|
141
|
+
if (active && !existingIndicator) {
|
|
142
|
+
c.addIndicator(indicatorId, 'main', indicatorParams.value[indicatorId])
|
|
143
|
+
} else if (!active && existingIndicator) {
|
|
144
|
+
c.removeIndicator(indicatorId.toUpperCase())
|
|
145
|
+
}
|
|
146
|
+
return
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (isSubPaneIndicator(indicatorId)) {
|
|
150
|
+
if (active) {
|
|
151
|
+
const existingPane = subPanes.value.find((p) => p.indicatorId === indicatorId)
|
|
152
|
+
if (existingPane) return
|
|
153
|
+
if (subPanes.value.length >= maxSubPanes) return
|
|
154
|
+
|
|
155
|
+
const paneId = c.addIndicator(indicatorId, 'sub', indicatorParams.value[indicatorId])
|
|
156
|
+
if (!paneId && subPanes.value.length > 0) {
|
|
157
|
+
const lastPane = subPanes.value[subPanes.value.length - 1]
|
|
158
|
+
switchSubIndicator(lastPane.id, indicatorId as SubIndicatorType)
|
|
159
|
+
}
|
|
160
|
+
} else {
|
|
161
|
+
const panesToRemove = subPanes.value.filter((p) => p.indicatorId === indicatorId)
|
|
162
|
+
panesToRemove.forEach((pane) => {
|
|
163
|
+
c.removeIndicator(pane.id)
|
|
164
|
+
})
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function handleUpdateParams(indicatorId: string, params: Record<string, unknown>) {
|
|
170
|
+
if (
|
|
171
|
+
indicatorId === 'MA' ||
|
|
172
|
+
indicatorId === 'BOLL' ||
|
|
173
|
+
indicatorId === 'EXPMA' ||
|
|
174
|
+
indicatorId === 'ENE'
|
|
175
|
+
) {
|
|
176
|
+
ctrl.value?.updateIndicatorParams(indicatorId, params)
|
|
177
|
+
return
|
|
178
|
+
}
|
|
179
|
+
if (isSubPaneIndicator(indicatorId)) {
|
|
180
|
+
subPanes.value
|
|
181
|
+
.filter((p) => p.indicatorId === indicatorId)
|
|
182
|
+
.forEach((pane) => {
|
|
183
|
+
ctrl.value?.updateIndicatorParams(pane.id, params)
|
|
184
|
+
})
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function handleReorderSubIndicators(orderedIndicatorIds: string[]) {
|
|
189
|
+
if (!orderedIndicatorIds.length || subPanes.value.length <= 1) return
|
|
190
|
+
|
|
191
|
+
const validOrder = orderedIndicatorIds.filter((id): id is SubIndicatorType =>
|
|
192
|
+
isSubPaneIndicator(id),
|
|
193
|
+
)
|
|
194
|
+
if (!validOrder.length) return
|
|
195
|
+
|
|
196
|
+
const paneByIndicator = new Map(subPanes.value.map((pane) => [pane.indicatorId, pane] as const))
|
|
197
|
+
const nextSubPanes: SubPaneSlot[] = []
|
|
198
|
+
|
|
199
|
+
for (const indicatorId of validOrder) {
|
|
200
|
+
const pane = paneByIndicator.get(indicatorId)
|
|
201
|
+
if (pane) {
|
|
202
|
+
nextSubPanes.push(pane)
|
|
203
|
+
paneByIndicator.delete(indicatorId)
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (nextSubPanes.length === 0) return
|
|
208
|
+
|
|
209
|
+
for (const pane of subPanes.value) {
|
|
210
|
+
if (paneByIndicator.has(pane.indicatorId)) {
|
|
211
|
+
nextSubPanes.push(pane)
|
|
212
|
+
paneByIndicator.delete(pane.indicatorId)
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const currentSubIds = subPanes.value.map((p) => p.id)
|
|
217
|
+
const nextSubIds = nextSubPanes.map((p) => p.id)
|
|
218
|
+
if (currentSubIds.join('|') === nextSubIds.join('|')) return
|
|
219
|
+
|
|
220
|
+
subPanes.value = nextSubPanes
|
|
221
|
+
|
|
222
|
+
const c = ctrl.value
|
|
223
|
+
if (!c) return
|
|
224
|
+
c.updatePaneLayout(buildPaneLayoutIntent())
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function setupIndicatorSubscriptions(chartCtrl: ChartController): () => void {
|
|
228
|
+
const unsubIndicators = chartCtrl.indicators.subscribe(() => {
|
|
229
|
+
const instances = chartCtrl.indicators.peek()
|
|
230
|
+
|
|
231
|
+
const mains = instances
|
|
232
|
+
.filter((i): i is IndicatorInstance & { role: 'main' } => i.role === 'main')
|
|
233
|
+
.map((i) => i.definitionId)
|
|
234
|
+
mainActiveIndicators.value = mains
|
|
235
|
+
|
|
236
|
+
const nextParams = { ...indicatorParams.value }
|
|
237
|
+
for (const inst of instances) {
|
|
238
|
+
if (inst.role === 'main' && inst.params && Object.keys(inst.params).length > 0) {
|
|
239
|
+
nextParams[inst.definitionId] = { ...inst.params }
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
chartCtrl.updateRendererConfig('mainIndicatorLegend', {
|
|
244
|
+
indicators: {
|
|
245
|
+
MA: { enabled: mains.includes('MA'), params: nextParams['MA'] || {} },
|
|
246
|
+
BOLL: { enabled: mains.includes('BOLL'), params: nextParams['BOLL'] || {} },
|
|
247
|
+
EXPMA: { enabled: mains.includes('EXPMA'), params: nextParams['EXPMA'] || {} },
|
|
248
|
+
ENE: { enabled: mains.includes('ENE'), params: nextParams['ENE'] || {} },
|
|
249
|
+
},
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
indicatorParams.value = nextParams
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
const unsubSubPanes = chartCtrl.subPanes.subscribe(() => {
|
|
256
|
+
const subPaneInfos = chartCtrl.subPanes.peek()
|
|
257
|
+
const signalIds = new Set(subPaneInfos.map((sp) => sp.paneId))
|
|
258
|
+
|
|
259
|
+
const merged = subPanes.value.filter((p) => signalIds.has(p.id))
|
|
260
|
+
const existingIds = new Set(merged.map((p) => p.id))
|
|
261
|
+
for (const sp of subPaneInfos) {
|
|
262
|
+
if (!existingIds.has(sp.paneId)) {
|
|
263
|
+
merged.push({
|
|
264
|
+
id: sp.paneId,
|
|
265
|
+
indicatorId: sp.indicatorId as SubIndicatorType,
|
|
266
|
+
params: sp.params,
|
|
267
|
+
})
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
subPanes.value = merged
|
|
271
|
+
|
|
272
|
+
const nextParams = { ...indicatorParams.value }
|
|
273
|
+
for (const sp of subPaneInfos) {
|
|
274
|
+
if (sp.params && Object.keys(sp.params).length > 0) {
|
|
275
|
+
nextParams[sp.indicatorId] = { ...sp.params }
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
indicatorParams.value = nextParams
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
return () => {
|
|
282
|
+
unsubIndicators()
|
|
283
|
+
unsubSubPanes()
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return {
|
|
288
|
+
mainActiveIndicators,
|
|
289
|
+
subActiveIndicators,
|
|
290
|
+
activeIndicators,
|
|
291
|
+
indicatorParams,
|
|
292
|
+
subPanes,
|
|
293
|
+
maxSubPanes,
|
|
294
|
+
buildPaneLayoutIntent,
|
|
295
|
+
getDefaultParams,
|
|
296
|
+
isSubPaneIndicator,
|
|
297
|
+
addSubPane,
|
|
298
|
+
removeSubPane,
|
|
299
|
+
clearAllSubPanes,
|
|
300
|
+
initIndicatorsFromConfig,
|
|
301
|
+
switchSubIndicator,
|
|
302
|
+
handleIndicatorToggle,
|
|
303
|
+
handleUpdateParams,
|
|
304
|
+
handleReorderSubIndicators,
|
|
305
|
+
setupIndicatorSubscriptions,
|
|
306
|
+
}
|
|
307
|
+
}
|