Skip to content

Commit

Permalink
refactor: refactor indicator module,add shouldUpdate callback.
Browse files Browse the repository at this point in the history
  • Loading branch information
liihuu committed Jun 7, 2024
1 parent 039f425 commit b2688b9
Show file tree
Hide file tree
Showing 11 changed files with 137 additions and 280 deletions.
5 changes: 3 additions & 2 deletions src/Chart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,8 @@ export default class ChartImp implements Chart {
const id = pane.getId()
const paneIndicatorData = {}
const indicators = this._chartStore.getIndicatorStore().getInstances(id)
indicators.forEach(indicator => {
indicators.forEach(proxy => {
const indicator = proxy.getIndicator()
const result = indicator.result
paneIndicatorData[indicator.name] = result[crosshair.dataIndex ?? result.length - 1]
})
Expand Down Expand Up @@ -711,7 +712,7 @@ export default class ChartImp implements Chart {

createIndicator (value: string | IndicatorCreate, isStack?: boolean, paneOptions?: Nullable<PaneOptions>, callback?: () => void): Nullable<string> {
const indicator = isString(value) ? { name: value } : value
if (getIndicatorClass(indicator.name) === null) {
if (getIndicatorClass(indicator.name as string) === null) {
logWarn('createIndicator', 'value', 'indicator not supported, you may need to use registerIndicator to add one!!!')
return null
}
Expand Down
4 changes: 3 additions & 1 deletion src/common/ExcludePickPartial.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
* limitations under the License.
*/

type ExcludePickPartial<T, K extends keyof T> = Partial<Omit<T, K>> & Pick<T, K>
import type PickRequired from './PickRequired'

type ExcludePickPartial<T, K extends keyof T> = PickRequired<Partial<T>, K>

export default ExcludePickPartial
253 changes: 82 additions & 171 deletions src/component/Indicator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ import type VisibleRange from '../common/VisibleRange'
import type BarSpace from '../common/BarSpace'
import type Crosshair from '../common/Crosshair'
import { type IndicatorStyle, type IndicatorPolygonStyle, type SmoothLineStyle, type RectStyle, type TextStyle, type TooltipIconStyle, type LineStyle, type LineType, type PolygonType, type TooltipLegend } from '../common/Styles'
import { isNumber, isValid, merge, clone, isArray, isBoolean } from '../common/utils/typeChecks'

import { type XAxis } from './XAxis'
import { type YAxis } from './YAxis'

import { formatValue } from '../common/utils/format'
import { isValid, merge, clone } from '../common/utils/typeChecks'

import { type ArcAttrs } from '../extension/figure/arc'
import { type RectAttrs } from '../extension/figure/rect'
Expand Down Expand Up @@ -79,6 +79,8 @@ export interface IndicatorFigure<D = any> {
styles?: IndicatorFigureStylesCallback<D>
}

export type IndicatorShouldUpdateReturn = boolean | { calc: boolean, draw: boolean }

export type IndicatorRegenerateFiguresCallback<D = any> = (calcParams: any[]) => Array<IndicatorFigure<D>>

export interface IndicatorTooltipData {
Expand Down Expand Up @@ -187,6 +189,11 @@ export interface Indicator<D = any> {
*/
styles: Nullable<Partial<IndicatorStyle>>

/**
* Should update, should calc or draw
*/
shouldUpdate: (prev: Indicator<D>, current: Indicator<D>) => IndicatorShouldUpdateReturn

/**
* Indicator calculation
*/
Expand All @@ -211,6 +218,11 @@ export interface Indicator<D = any> {
* Calculation result
*/
result: D[]

/**
* Others
*/
[key: string]: any
}

export type IndicatorTemplate<D = any> = ExcludePickPartial<Omit<Indicator<D>, 'result'>, 'name' | 'calc'>
Expand Down Expand Up @@ -284,204 +296,103 @@ export function eachFigures<D> (
})
}

export default abstract class IndicatorImp<D = any> implements Indicator<D> {
name: string
shortName: string
precision: number
calcParams: any[]
shouldOhlc: boolean
shouldFormatBigNumber: boolean
visible: boolean
zLevel: number
extendData: any
series: IndicatorSeries
figures: Array<IndicatorFigure<D>>
minValue: Nullable<number>
maxValue: Nullable<number>
styles: Nullable<Partial<IndicatorStyle>>
regenerateFigures: Nullable<IndicatorRegenerateFiguresCallback<D>>
createTooltipDataSource: Nullable<IndicatorCreateTooltipDataSourceCallback>
draw: Nullable<IndicatorDrawCallback<D>>

result: D[] = []

private _precisionFlag: boolean = false

constructor (indicator: IndicatorTemplate) {
const {
name, shortName, series, calcParams, figures, precision,
shouldOhlc, shouldFormatBigNumber, visible, zLevel,
minValue, maxValue, styles, extendData,
regenerateFigures, createTooltipDataSource, draw
} = indicator
this.name = name
this.shortName = shortName ?? name
this.series = series ?? IndicatorSeries.Normal
this.precision = precision ?? 4
this.calcParams = calcParams ?? []
this.figures = figures ?? []
this.shouldOhlc = shouldOhlc ?? false
this.shouldFormatBigNumber = shouldFormatBigNumber ?? false
this.visible = visible ?? true
this.zLevel = zLevel ?? 0
this.minValue = minValue ?? null
this.maxValue = maxValue ?? null
this.styles = clone(styles ?? {})
this.extendData = extendData
this.regenerateFigures = regenerateFigures ?? null
this.createTooltipDataSource = createTooltipDataSource ?? null
this.draw = draw ?? null
}

setShortName (shortName: string): boolean {
if (this.shortName !== shortName) {
this.shortName = shortName
return true
}
return false
}

setSeries (series: IndicatorSeries): boolean {
if (this.series !== series) {
this.series = series
return true
}
return false
}

setPrecision (precision: number, flag?: boolean): boolean {
const f = flag ?? false
const optimalPrecision = Math.floor(precision)
if (optimalPrecision !== this.precision && precision >= 0 && (!f || (f && !this._precisionFlag))) {
this.precision = optimalPrecision
if (!f) {
this._precisionFlag = true
}
return true
}
return false
}

setCalcParams (params: any[]): boolean {
this.calcParams = params
this.figures = this.regenerateFigures?.(params) ?? this.figures
return true
export default class IndicatorImp<D = any> {
private _prevIndicator: Indicator<D>
private readonly _indicator: Indicator<D> = {
name: '',
shortName: '',
precision: 4,
calcParams: [],
shouldOhlc: false,
shouldFormatBigNumber: false,
visible: true,
zLevel: 0,
extendData: null,
series: IndicatorSeries.Normal,
figures: [],
minValue: null,
maxValue: null,
styles: {},
regenerateFigures: null,
createTooltipDataSource: null,
shouldUpdate: (prev, current) => {
const calc = JSON.stringify(prev.calcParams) !== JSON.stringify(current.calcParams) ||
prev.figures !== current.figures ||
prev.calc !== current.calc
const draw = calc ||
prev.shortName !== current.shortName ||
prev.series !== current.series ||
prev.minValue !== current.minValue ||
prev.maxValue !== current.maxValue ||
prev.precision !== current.precision ||
prev.shouldOhlc !== current.shouldOhlc ||
prev.shouldFormatBigNumber !== current.shouldFormatBigNumber ||
prev.visible !== current.visible ||
prev.zLevel !== current.zLevel ||
prev.extendData !== current.extendData ||
prev.regenerateFigures !== current.regenerateFigures ||
prev.createTooltipDataSource !== current.createTooltipDataSource ||
prev.draw !== current.draw

return { calc, draw }
},
calc: () => [],
draw: null,
result: []
}

setShouldOhlc (shouldOhlc: boolean): boolean {
if (this.shouldOhlc !== shouldOhlc) {
this.shouldOhlc = shouldOhlc
return true
}
return false
}
private _lockSeriesPrecision: boolean = false

setShouldFormatBigNumber (shouldFormatBigNumber: boolean): boolean {
if (this.shouldFormatBigNumber !== shouldFormatBigNumber) {
this.shouldFormatBigNumber = shouldFormatBigNumber
return true
constructor (indicator: IndicatorTemplate<D>) {
this.override(indicator)
this._indicator.shortName ??= this._indicator.name
if (isArray(indicator.figures)) {
this._indicator.figures = indicator.figures
}
return false
}

setVisible (visible: boolean): boolean {
if (this.visible !== visible) {
this.visible = visible
return true
}
return false
getIndicator (): Indicator<D> {
return this._indicator
}

setZLevel (zLevel: number): boolean {
if (this.zLevel !== zLevel) {
this.zLevel = zLevel
return true
override (indicator: IndicatorCreate<D>): void {
this._prevIndicator = clone(this._indicator)
merge(this._indicator, indicator)
if (isNumber(indicator.precision)) {
this._lockSeriesPrecision = true
}
return false
}

setStyles (styles: Partial<IndicatorStyle>): boolean {
merge(this.styles, styles)
return true
}

setExtendData (extendData: any): boolean {
if (this.extendData !== extendData) {
this.extendData = extendData
return true
setSeriesPrecision (precision: number): void {
if (!this._lockSeriesPrecision) {
this._indicator.precision = precision
}
return false
}

setFigures (figures: IndicatorFigure[]): boolean {
if (this.figures !== figures) {
this.figures = figures
return true
shouldUpdate (): ({ calc: boolean, draw: boolean, sort: boolean }) {
const sort = this._prevIndicator.zLevel !== this._indicator.zLevel
const result = this._indicator.shouldUpdate(this._prevIndicator, this._indicator)
if (isBoolean(result)) {
return { calc: result, draw: result, sort }
}
return false
return { ...result, sort }
}

setMinValue (value: Nullable<number>): boolean {
if (this.minValue !== value) {
this.minValue = value
return true
}
return false
}

setMaxValue (value: Nullable<number>): boolean {
if (this.maxValue !== value) {
this.maxValue = value
return true
}
return false
}

setRegenerateFigures (callback: Nullable<IndicatorRegenerateFiguresCallback>): boolean {
if (this.regenerateFigures !== callback) {
this.regenerateFigures = callback
return true
}
return false
}

setCreateTooltipDataSource (callback: Nullable<IndicatorCreateTooltipDataSourceCallback>): boolean {
if (this.createTooltipDataSource !== callback) {
this.createTooltipDataSource = callback
return true
}
return false
}

setDraw (callback: Nullable<IndicatorDrawCallback>): boolean {
if (this.draw !== callback) {
this.draw = callback
return true
}
return false
}

async calcIndicator (dataList: KLineData[]): Promise<boolean> {
async calc (dataList: KLineData[]): Promise<boolean> {
try {
const result = await this.calc(dataList, this)
this.result = result
const result = await this._indicator.calc(dataList, this._indicator)
this._indicator.result = result
return true
} catch (e) {
return false
}
}

abstract calc (dataList: KLineData[], indicator: Indicator<D>): D[] | Promise<D[]>

static extend<D> (template: IndicatorTemplate): IndicatorConstructor<D> {
static extend<D> (template: IndicatorTemplate<D>): IndicatorConstructor<D> {
class Custom extends IndicatorImp<D> {
constructor () {
super(template)
}

calc (dataList: KLineData[], indicator: Indicator<D>): D[] | Promise<D[]> {
return template.calc(dataList, indicator)
}
}
return Custom
}
Expand Down
17 changes: 10 additions & 7 deletions src/component/YAxis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ export default abstract class YAxisImp extends AxisImp implements YAxis {
let specifyMax = Number.MIN_SAFE_INTEGER
let indicatorPrecision = Number.MAX_SAFE_INTEGER
const indicators = chartStore.getIndicatorStore().getInstances(parent.getId())
indicators.forEach(indicator => {
indicators.forEach(proxy => {
const indicator = proxy.getIndicator()
if (!shouldOhlc) {
shouldOhlc = indicator.shouldOhlc ?? false
}
Expand Down Expand Up @@ -262,10 +263,11 @@ export default abstract class YAxisImp extends AxisImp implements YAxis {
if (this.isInCandle()) {
precision = chartStore.getPrecision().price
} else {
indicators.forEach(tech => {
precision = Math.max(precision, tech.precision)
indicators.forEach(proxy => {
const indicator = proxy.getIndicator()
precision = Math.max(precision, indicator.precision)
if (!shouldFormatBigNumber) {
shouldFormatBigNumber = tech.shouldFormatBigNumber
shouldFormatBigNumber = indicator.shouldFormatBigNumber
}
})
}
Expand Down Expand Up @@ -342,10 +344,11 @@ export default abstract class YAxisImp extends AxisImp implements YAxis {
const indicators = chartStore.getIndicatorStore().getInstances(pane.getId())
let techPrecision = 0
let shouldFormatBigNumber = false
indicators.forEach(tech => {
techPrecision = Math.max(tech.precision, techPrecision)
indicators.forEach(proxy => {
const indicator = proxy.getIndicator()
techPrecision = Math.max(indicator.precision, techPrecision)
if (!shouldFormatBigNumber) {
shouldFormatBigNumber = tech.shouldFormatBigNumber
shouldFormatBigNumber = indicator.shouldFormatBigNumber
}
})
let precision = 2
Expand Down
Loading

0 comments on commit b2688b9

Please sign in to comment.