Skip to content

Commit

Permalink
Merge pull request #654 from klinecharts/feature/#649
Browse files Browse the repository at this point in the history
feat: support creating the same indicator multiple times #649
  • Loading branch information
liihuu authored Feb 12, 2025
2 parents 92acafd + 5de3d2c commit 820532d
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 83 deletions.
15 changes: 10 additions & 5 deletions src/Chart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ import Event from './Event'
import type DeepPartial from './common/DeepPartial'
import type { Styles } from './common/Styles'
import type BarSpace from './common/BarSpace'
import type PickRequired from './common/PickRequired'

export enum DomPosition {
Root = 'root',
Expand All @@ -82,7 +83,7 @@ export interface Chart extends Store {
applyNewData: (dataList: KLineData[], more?: boolean | Partial<LoadDataMore>) => void
updateData: (data: KLineData) => void
createIndicator: (value: string | IndicatorCreate, isStack?: boolean, paneOptions?: PaneOptions) => Nullable<string>
getIndicators: (filter?: IndicatorFilter) => Map<string, Indicator[]>
getIndicators: (filter?: IndicatorFilter) => Indicator[]
createOverlay: (value: string | OverlayCreate | Array<string | OverlayCreate>) => Nullable<string> | Array<Nullable<string>>
getOverlays: (filter?: OverlayFilter) => Map<string, Overlay[]>
setPaneOptions: (options: PaneOptions) => void
Expand Down Expand Up @@ -768,7 +769,11 @@ export default class ChartImp implements Chart {
options.height ??= PANE_DEFAULT_HEIGHT
}
this.setPaneOptions(options)
const result = this._chartStore.addIndicator(indicator, options.id, isStack ?? false)
indicator.paneId = options.id
if (!isString(indicator.id)) {
indicator.id = createId(indicator.name)
}
const result = this._chartStore.addIndicator(indicator as PickRequired<IndicatorCreate, 'id' | 'name' | 'paneId'>, isStack ?? false)
if (result) {
this.layout({
measureHeight: true,
Expand All @@ -782,11 +787,11 @@ export default class ChartImp implements Chart {
return options.id
}

overrideIndicator (override: IndicatorCreate, paneId?: string): boolean {
return this._chartStore.overrideIndicator(override, paneId)
overrideIndicator (override: IndicatorCreate): boolean {
return this._chartStore.overrideIndicator(override)
}

getIndicators (filter?: IndicatorFilter): Map<string, Indicator[]> {
getIndicators (filter?: IndicatorFilter): Indicator[] {
return this._chartStore.getIndicatorsByFilter(filter ?? {})
}

Expand Down
124 changes: 52 additions & 72 deletions src/Store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import { getStyles as getExtensionStyles } from './extension/styles/index'
import { PaneIdConstants } from './pane/types'

import type Chart from './Chart'
import type PickRequired from './common/PickRequired'

const BarSpaceLimitConstants = {
MIN: 1,
Expand Down Expand Up @@ -121,7 +122,7 @@ export interface Store {
getBarSpace: () => BarSpace
getVisibleRange: () => VisibleRange
setLoadMoreDataCallback: (callback: LoadDataCallback) => void
overrideIndicator: (override: IndicatorCreate, paneId?: string) => boolean
overrideIndicator: (override: IndicatorCreate) => boolean
removeIndicator: (filter?: IndicatorFilter) => boolean
overrideOverlay: (override: Partial<OverlayCreate>) => boolean
removeOverlay: (filter?: OverlayFilter) => boolean
Expand Down Expand Up @@ -531,11 +532,9 @@ export default class StoreImp implements Store {
if (adjustFlag) {
this._adjustVisibleRange()
this.setCrosshair(this._crosshair, { notInvalidate: true })
const filterMap = this.getIndicatorsByFilter({})
filterMap.forEach((indicators, paneId) => {
indicators.forEach(indicator => {
this._addIndicatorCalcTask(paneId, indicator, type)
})
const filterIndicators = this.getIndicatorsByFilter({})
filterIndicators.forEach(indicator => {
this._addIndicatorCalcTask(indicator, type)
})
this._chart.layout({
measureWidth: true,
Expand Down Expand Up @@ -986,9 +985,9 @@ export default class StoreImp implements Store {
}
}

private _addIndicatorCalcTask (paneId: string, indicator: IndicatorImp, loadDataType: LoadDataType): void {
private _addIndicatorCalcTask (indicator: IndicatorImp, loadDataType: LoadDataType): void {
this._taskScheduler.addTask({
id: generateTaskId(paneId, indicator.name),
id: generateTaskId(indicator.id),
handler: () => {
indicator.onDataStateChange?.({
state: IndicatorDataState.Loading,
Expand Down Expand Up @@ -1019,17 +1018,13 @@ export default class StoreImp implements Store {
})
}

addIndicator (create: IndicatorCreate, paneId: string, isStack: boolean): boolean {
const { name } = create
let paneIndicators = this._indicators.get(paneId)
if (isValid(paneIndicators)) {
if (isValid(paneIndicators.find(i => i.name === name))) {
return false
}
}
if (!isValid(paneIndicators)) {
paneIndicators = []
addIndicator (create: PickRequired<IndicatorCreate, 'id' | 'name' | 'paneId'>, isStack: boolean): boolean {
const { name, paneId } = create
const filterIndicators = this.getIndicatorsByFilter(create)
if (filterIndicators.length > 0) {
return false
}
let paneIndicators = this.getIndicatorsByPaneId(paneId)
const IndicatorClazz = getIndicatorClass(name)!
const indicator = new IndicatorClazz()

Expand All @@ -1042,50 +1037,46 @@ export default class StoreImp implements Store {
paneIndicators.push(indicator)
this._indicators.set(paneId, paneIndicators)
this._sortIndicators(paneId)
this._addIndicatorCalcTask(paneId, indicator, LoadDataType.Init)
this._addIndicatorCalcTask(indicator, LoadDataType.Init)
return true
}

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

getIndicatorsByFilter (filter: IndicatorFilter): Map<string, IndicatorImp[]> {
const find: ((indicators: IndicatorImp[], name?: string) => IndicatorImp[]) = (indicators, name) => indicators.filter(indicator => (!isValid(name) || indicator.name === name))
const { paneId, name } = filter
const map = new Map<string, IndicatorImp[]>()
getIndicatorsByFilter (filter: IndicatorFilter): IndicatorImp[] {
const { paneId, name, id } = filter
const match: ((overlay: IndicatorImp) => boolean) = indicator => {
if (isValid(id)) {
return indicator.id === id
}
return !isValid(name) || indicator.name === name
}
let indicators: IndicatorImp[] = []
if (isValid(paneId)) {
const indicators = this.getIndicatorsByPaneId(paneId)
map.set(paneId, find(indicators, name))
indicators = indicators.concat(this.getIndicatorsByPaneId(paneId).filter(match))
} else {
if (isValid(name)) {
this._indicators.forEach((indicators, paneId) => {
map.set(paneId, find(indicators, name))
})
} else {
this._indicators.forEach((indicators, paneId) => {
map.set(paneId, find(indicators))
})
}
this._indicators.forEach(paneIndicators => {
indicators = indicators.concat(paneIndicators.filter(match))
})
}
return map
return indicators
}

removeIndicator (filter: IndicatorFilter): boolean {
let removed = false
const filterMap = this.getIndicatorsByFilter(filter)
filterMap.forEach((indicators, paneId) => {
const paneIndicators = this.getIndicatorsByPaneId(paneId)
indicators.forEach(indicator => {
const index = paneIndicators.findIndex(ins => ins.name === indicator.name)
if (index > -1) {
this._taskScheduler.removeTask(generateTaskId(paneId, indicator.name))
paneIndicators.splice(index, 1)
removed = true
}
})
const filterIndicators = this.getIndicatorsByFilter(filter)
filterIndicators.forEach(indicator => {
const paneIndicators = this.getIndicatorsByPaneId(indicator.paneId)
const index = paneIndicators.findIndex(ins => ins.id === indicator.id)
if (index > -1) {
this._taskScheduler.removeTask(generateTaskId(indicator.id))
paneIndicators.splice(index, 1)
removed = true
}
if (paneIndicators.length === 0) {
this._indicators.delete(paneId)
this._indicators.delete(indicator.paneId)
}
})
return removed
Expand Down Expand Up @@ -1122,36 +1113,25 @@ export default class StoreImp implements Store {
}
}

overrideIndicator (create: IndicatorCreate, paneId?: string): boolean {
const { name } = create
let indictors = new Map<string, IndicatorImp[]>()
if (isValid(paneId)) {
const paneIndicators = this._indicators.get(paneId)
if (isValid(paneIndicators)) {
indictors.set(paneId, paneIndicators)
}
} else {
indictors = this._indicators
}
overrideIndicator (create: IndicatorCreate): boolean {
let updateFlag = false
let sortFlag = false
indictors.forEach((paneIndicators, paneId) => {
const indicator = paneIndicators.find(i => i.name === name)
if (isValid(indicator)) {
indicator.override(create)
const { calc, draw, sort } = indicator.shouldUpdateImp()
if (sort) {
sortFlag = true
}
if (calc) {
this._addIndicatorCalcTask(paneId, indicator, LoadDataType.Update)
} else {
if (draw) {
updateFlag = true
}
const filterIndicators = this.getIndicatorsByFilter(create)
filterIndicators.forEach(indicator => {
indicator.override(create)
const { calc, draw, sort } = indicator.shouldUpdateImp()
if (sort) {
sortFlag = true
}
if (calc) {
this._addIndicatorCalcTask(indicator, LoadDataType.Update)
} else {
if (draw) {
updateFlag = true
}
}
})

// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- ignore
if (sortFlag) {
this._sortIndicators()
Expand Down
21 changes: 17 additions & 4 deletions src/component/Indicator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,16 @@ export interface IndicatorOnDataStateChangeParams<D> {
export type IndicatorOnDataStateChangeCallback<D> = (params: IndicatorOnDataStateChangeParams<D>) => void

export interface Indicator<D = unknown, C = unknown, E = unknown> {
/**
* Unique id
*/
id: string

/**
* Pane id
*/
paneId: string

/**
* Indicator name
*/
Expand Down Expand Up @@ -231,10 +241,7 @@ export type IndicatorTemplate<D = unknown, C = unknown, E = unknown> = ExcludePi

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

export interface IndicatorFilter {
name?: string
paneId?: string
}
export type IndicatorFilter = Partial<Pick<Indicator, 'id' | 'paneId' | 'name'>>

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

Expand Down Expand Up @@ -307,6 +314,8 @@ export function eachFigures<D = unknown> (
}

export default class IndicatorImp<D = unknown, C = unknown, E = unknown> implements Indicator<D, C, E> {
id: string
paneId: string
name: string
shortName: string
precision = 4
Expand Down Expand Up @@ -364,6 +373,7 @@ export default class IndicatorImp<D = unknown, C = unknown, E = unknown> impleme
const { result, ...currentOthers } = this
this._prevIndicator = { ...clone(currentOthers), result }
const {
id,
name,
shortName,
precision,
Expand All @@ -372,6 +382,9 @@ export default class IndicatorImp<D = unknown, C = unknown, E = unknown> impleme
calcParams,
...others
} = indicator
if (!isString(this.id) && isString(id)) {
this.id = id
}
if (!isString(this.name)) {
this.name = name ?? ''
}
Expand Down
2 changes: 1 addition & 1 deletion src/extension/overlay/fibonacciLine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const fibonacciLine: OverlayTemplate = {
if (yAxis?.isInCandle() ?? true) {
precision = chart.getPrecision().price
} else {
const indicators = chart.getIndicators({ paneId: overlay.paneId }).get(overlay.paneId) ?? []
const indicators = chart.getIndicators({ paneId: overlay.paneId })
indicators.forEach(indicator => {
precision = Math.max(precision, indicator.precision)
})
Expand Down
2 changes: 1 addition & 1 deletion src/extension/overlay/priceLine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const priceLine: OverlayTemplate = {
if (yAxis?.isInCandle() ?? true) {
precision = chart.getPrecision().price
} else {
const indicators = chart.getIndicators({ paneId: overlay.paneId }).get(overlay.paneId) ?? []
const indicators = chart.getIndicators({ paneId: overlay.paneId })
indicators.forEach(indicator => {
precision = Math.max(precision, indicator.precision)
})
Expand Down

0 comments on commit 820532d

Please sign in to comment.