Skip to content

Commit 820532d

Browse files
authored
Merge pull request #654 from klinecharts/feature/#649
feat: support creating the same indicator multiple times #649
2 parents 92acafd + 5de3d2c commit 820532d

File tree

5 files changed

+81
-83
lines changed

5 files changed

+81
-83
lines changed

src/Chart.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ import Event from './Event'
5858
import type DeepPartial from './common/DeepPartial'
5959
import type { Styles } from './common/Styles'
6060
import type BarSpace from './common/BarSpace'
61+
import type PickRequired from './common/PickRequired'
6162

6263
export enum DomPosition {
6364
Root = 'root',
@@ -82,7 +83,7 @@ export interface Chart extends Store {
8283
applyNewData: (dataList: KLineData[], more?: boolean | Partial<LoadDataMore>) => void
8384
updateData: (data: KLineData) => void
8485
createIndicator: (value: string | IndicatorCreate, isStack?: boolean, paneOptions?: PaneOptions) => Nullable<string>
85-
getIndicators: (filter?: IndicatorFilter) => Map<string, Indicator[]>
86+
getIndicators: (filter?: IndicatorFilter) => Indicator[]
8687
createOverlay: (value: string | OverlayCreate | Array<string | OverlayCreate>) => Nullable<string> | Array<Nullable<string>>
8788
getOverlays: (filter?: OverlayFilter) => Map<string, Overlay[]>
8889
setPaneOptions: (options: PaneOptions) => void
@@ -768,7 +769,11 @@ export default class ChartImp implements Chart {
768769
options.height ??= PANE_DEFAULT_HEIGHT
769770
}
770771
this.setPaneOptions(options)
771-
const result = this._chartStore.addIndicator(indicator, options.id, isStack ?? false)
772+
indicator.paneId = options.id
773+
if (!isString(indicator.id)) {
774+
indicator.id = createId(indicator.name)
775+
}
776+
const result = this._chartStore.addIndicator(indicator as PickRequired<IndicatorCreate, 'id' | 'name' | 'paneId'>, isStack ?? false)
772777
if (result) {
773778
this.layout({
774779
measureHeight: true,
@@ -782,11 +787,11 @@ export default class ChartImp implements Chart {
782787
return options.id
783788
}
784789

785-
overrideIndicator (override: IndicatorCreate, paneId?: string): boolean {
786-
return this._chartStore.overrideIndicator(override, paneId)
790+
overrideIndicator (override: IndicatorCreate): boolean {
791+
return this._chartStore.overrideIndicator(override)
787792
}
788793

789-
getIndicators (filter?: IndicatorFilter): Map<string, Indicator[]> {
794+
getIndicators (filter?: IndicatorFilter): Indicator[] {
790795
return this._chartStore.getIndicatorsByFilter(filter ?? {})
791796
}
792797

src/Store.ts

Lines changed: 52 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import { getStyles as getExtensionStyles } from './extension/styles/index'
5252
import { PaneIdConstants } from './pane/types'
5353

5454
import type Chart from './Chart'
55+
import type PickRequired from './common/PickRequired'
5556

5657
const BarSpaceLimitConstants = {
5758
MIN: 1,
@@ -121,7 +122,7 @@ export interface Store {
121122
getBarSpace: () => BarSpace
122123
getVisibleRange: () => VisibleRange
123124
setLoadMoreDataCallback: (callback: LoadDataCallback) => void
124-
overrideIndicator: (override: IndicatorCreate, paneId?: string) => boolean
125+
overrideIndicator: (override: IndicatorCreate) => boolean
125126
removeIndicator: (filter?: IndicatorFilter) => boolean
126127
overrideOverlay: (override: Partial<OverlayCreate>) => boolean
127128
removeOverlay: (filter?: OverlayFilter) => boolean
@@ -531,11 +532,9 @@ export default class StoreImp implements Store {
531532
if (adjustFlag) {
532533
this._adjustVisibleRange()
533534
this.setCrosshair(this._crosshair, { notInvalidate: true })
534-
const filterMap = this.getIndicatorsByFilter({})
535-
filterMap.forEach((indicators, paneId) => {
536-
indicators.forEach(indicator => {
537-
this._addIndicatorCalcTask(paneId, indicator, type)
538-
})
535+
const filterIndicators = this.getIndicatorsByFilter({})
536+
filterIndicators.forEach(indicator => {
537+
this._addIndicatorCalcTask(indicator, type)
539538
})
540539
this._chart.layout({
541540
measureWidth: true,
@@ -986,9 +985,9 @@ export default class StoreImp implements Store {
986985
}
987986
}
988987

989-
private _addIndicatorCalcTask (paneId: string, indicator: IndicatorImp, loadDataType: LoadDataType): void {
988+
private _addIndicatorCalcTask (indicator: IndicatorImp, loadDataType: LoadDataType): void {
990989
this._taskScheduler.addTask({
991-
id: generateTaskId(paneId, indicator.name),
990+
id: generateTaskId(indicator.id),
992991
handler: () => {
993992
indicator.onDataStateChange?.({
994993
state: IndicatorDataState.Loading,
@@ -1019,17 +1018,13 @@ export default class StoreImp implements Store {
10191018
})
10201019
}
10211020

1022-
addIndicator (create: IndicatorCreate, paneId: string, isStack: boolean): boolean {
1023-
const { name } = create
1024-
let paneIndicators = this._indicators.get(paneId)
1025-
if (isValid(paneIndicators)) {
1026-
if (isValid(paneIndicators.find(i => i.name === name))) {
1027-
return false
1028-
}
1029-
}
1030-
if (!isValid(paneIndicators)) {
1031-
paneIndicators = []
1021+
addIndicator (create: PickRequired<IndicatorCreate, 'id' | 'name' | 'paneId'>, isStack: boolean): boolean {
1022+
const { name, paneId } = create
1023+
const filterIndicators = this.getIndicatorsByFilter(create)
1024+
if (filterIndicators.length > 0) {
1025+
return false
10321026
}
1027+
let paneIndicators = this.getIndicatorsByPaneId(paneId)
10331028
const IndicatorClazz = getIndicatorClass(name)!
10341029
const indicator = new IndicatorClazz()
10351030

@@ -1042,50 +1037,46 @@ export default class StoreImp implements Store {
10421037
paneIndicators.push(indicator)
10431038
this._indicators.set(paneId, paneIndicators)
10441039
this._sortIndicators(paneId)
1045-
this._addIndicatorCalcTask(paneId, indicator, LoadDataType.Init)
1040+
this._addIndicatorCalcTask(indicator, LoadDataType.Init)
10461041
return true
10471042
}
10481043

10491044
getIndicatorsByPaneId (paneId: string): IndicatorImp[] {
10501045
return this._indicators.get(paneId) ?? []
10511046
}
10521047

1053-
getIndicatorsByFilter (filter: IndicatorFilter): Map<string, IndicatorImp[]> {
1054-
const find: ((indicators: IndicatorImp[], name?: string) => IndicatorImp[]) = (indicators, name) => indicators.filter(indicator => (!isValid(name) || indicator.name === name))
1055-
const { paneId, name } = filter
1056-
const map = new Map<string, IndicatorImp[]>()
1048+
getIndicatorsByFilter (filter: IndicatorFilter): IndicatorImp[] {
1049+
const { paneId, name, id } = filter
1050+
const match: ((overlay: IndicatorImp) => boolean) = indicator => {
1051+
if (isValid(id)) {
1052+
return indicator.id === id
1053+
}
1054+
return !isValid(name) || indicator.name === name
1055+
}
1056+
let indicators: IndicatorImp[] = []
10571057
if (isValid(paneId)) {
1058-
const indicators = this.getIndicatorsByPaneId(paneId)
1059-
map.set(paneId, find(indicators, name))
1058+
indicators = indicators.concat(this.getIndicatorsByPaneId(paneId).filter(match))
10601059
} else {
1061-
if (isValid(name)) {
1062-
this._indicators.forEach((indicators, paneId) => {
1063-
map.set(paneId, find(indicators, name))
1064-
})
1065-
} else {
1066-
this._indicators.forEach((indicators, paneId) => {
1067-
map.set(paneId, find(indicators))
1068-
})
1069-
}
1060+
this._indicators.forEach(paneIndicators => {
1061+
indicators = indicators.concat(paneIndicators.filter(match))
1062+
})
10701063
}
1071-
return map
1064+
return indicators
10721065
}
10731066

10741067
removeIndicator (filter: IndicatorFilter): boolean {
10751068
let removed = false
1076-
const filterMap = this.getIndicatorsByFilter(filter)
1077-
filterMap.forEach((indicators, paneId) => {
1078-
const paneIndicators = this.getIndicatorsByPaneId(paneId)
1079-
indicators.forEach(indicator => {
1080-
const index = paneIndicators.findIndex(ins => ins.name === indicator.name)
1081-
if (index > -1) {
1082-
this._taskScheduler.removeTask(generateTaskId(paneId, indicator.name))
1083-
paneIndicators.splice(index, 1)
1084-
removed = true
1085-
}
1086-
})
1069+
const filterIndicators = this.getIndicatorsByFilter(filter)
1070+
filterIndicators.forEach(indicator => {
1071+
const paneIndicators = this.getIndicatorsByPaneId(indicator.paneId)
1072+
const index = paneIndicators.findIndex(ins => ins.id === indicator.id)
1073+
if (index > -1) {
1074+
this._taskScheduler.removeTask(generateTaskId(indicator.id))
1075+
paneIndicators.splice(index, 1)
1076+
removed = true
1077+
}
10871078
if (paneIndicators.length === 0) {
1088-
this._indicators.delete(paneId)
1079+
this._indicators.delete(indicator.paneId)
10891080
}
10901081
})
10911082
return removed
@@ -1122,36 +1113,25 @@ export default class StoreImp implements Store {
11221113
}
11231114
}
11241115

1125-
overrideIndicator (create: IndicatorCreate, paneId?: string): boolean {
1126-
const { name } = create
1127-
let indictors = new Map<string, IndicatorImp[]>()
1128-
if (isValid(paneId)) {
1129-
const paneIndicators = this._indicators.get(paneId)
1130-
if (isValid(paneIndicators)) {
1131-
indictors.set(paneId, paneIndicators)
1132-
}
1133-
} else {
1134-
indictors = this._indicators
1135-
}
1116+
overrideIndicator (create: IndicatorCreate): boolean {
11361117
let updateFlag = false
11371118
let sortFlag = false
1138-
indictors.forEach((paneIndicators, paneId) => {
1139-
const indicator = paneIndicators.find(i => i.name === name)
1140-
if (isValid(indicator)) {
1141-
indicator.override(create)
1142-
const { calc, draw, sort } = indicator.shouldUpdateImp()
1143-
if (sort) {
1144-
sortFlag = true
1145-
}
1146-
if (calc) {
1147-
this._addIndicatorCalcTask(paneId, indicator, LoadDataType.Update)
1148-
} else {
1149-
if (draw) {
1150-
updateFlag = true
1151-
}
1119+
const filterIndicators = this.getIndicatorsByFilter(create)
1120+
filterIndicators.forEach(indicator => {
1121+
indicator.override(create)
1122+
const { calc, draw, sort } = indicator.shouldUpdateImp()
1123+
if (sort) {
1124+
sortFlag = true
1125+
}
1126+
if (calc) {
1127+
this._addIndicatorCalcTask(indicator, LoadDataType.Update)
1128+
} else {
1129+
if (draw) {
1130+
updateFlag = true
11521131
}
11531132
}
11541133
})
1134+
11551135
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- ignore
11561136
if (sortFlag) {
11571137
this._sortIndicators()

src/component/Indicator.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,16 @@ export interface IndicatorOnDataStateChangeParams<D> {
121121
export type IndicatorOnDataStateChangeCallback<D> = (params: IndicatorOnDataStateChangeParams<D>) => void
122122

123123
export interface Indicator<D = unknown, C = unknown, E = unknown> {
124+
/**
125+
* Unique id
126+
*/
127+
id: string
128+
129+
/**
130+
* Pane id
131+
*/
132+
paneId: string
133+
124134
/**
125135
* Indicator name
126136
*/
@@ -231,10 +241,7 @@ export type IndicatorTemplate<D = unknown, C = unknown, E = unknown> = ExcludePi
231241

232242
export type IndicatorCreate<D = unknown, C = unknown, E = unknown> = ExcludePickPartial<Omit<Indicator<D, C, E>, 'result'>, 'name'>
233243

234-
export interface IndicatorFilter {
235-
name?: string
236-
paneId?: string
237-
}
244+
export type IndicatorFilter = Partial<Pick<Indicator, 'id' | 'paneId' | 'name'>>
238245

239246
export type IndicatorConstructor<D = unknown, C = unknown, E = unknown> = new () => IndicatorImp<D, C, E>
240247

@@ -307,6 +314,8 @@ export function eachFigures<D = unknown> (
307314
}
308315

309316
export default class IndicatorImp<D = unknown, C = unknown, E = unknown> implements Indicator<D, C, E> {
317+
id: string
318+
paneId: string
310319
name: string
311320
shortName: string
312321
precision = 4
@@ -364,6 +373,7 @@ export default class IndicatorImp<D = unknown, C = unknown, E = unknown> impleme
364373
const { result, ...currentOthers } = this
365374
this._prevIndicator = { ...clone(currentOthers), result }
366375
const {
376+
id,
367377
name,
368378
shortName,
369379
precision,
@@ -372,6 +382,9 @@ export default class IndicatorImp<D = unknown, C = unknown, E = unknown> impleme
372382
calcParams,
373383
...others
374384
} = indicator
385+
if (!isString(this.id) && isString(id)) {
386+
this.id = id
387+
}
375388
if (!isString(this.name)) {
376389
this.name = name ?? ''
377390
}

src/extension/overlay/fibonacciLine.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ const fibonacciLine: OverlayTemplate = {
3333
if (yAxis?.isInCandle() ?? true) {
3434
precision = chart.getPrecision().price
3535
} else {
36-
const indicators = chart.getIndicators({ paneId: overlay.paneId }).get(overlay.paneId) ?? []
36+
const indicators = chart.getIndicators({ paneId: overlay.paneId })
3737
indicators.forEach(indicator => {
3838
precision = Math.max(precision, indicator.precision)
3939
})

src/extension/overlay/priceLine.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ const priceLine: OverlayTemplate = {
2525
if (yAxis?.isInCandle() ?? true) {
2626
precision = chart.getPrecision().price
2727
} else {
28-
const indicators = chart.getIndicators({ paneId: overlay.paneId }).get(overlay.paneId) ?? []
28+
const indicators = chart.getIndicators({ paneId: overlay.paneId })
2929
indicators.forEach(indicator => {
3030
precision = Math.max(precision, indicator.precision)
3131
})

0 commit comments

Comments
 (0)