diff --git a/src/component/Indicator.ts b/src/component/Indicator.ts index 75178d64f..f318d04b4 100644 --- a/src/component/Indicator.ts +++ b/src/component/Indicator.ts @@ -149,6 +149,11 @@ export interface Indicator { */ visible: boolean + /** + * Z index + */ + zLevel: number + /** * Extend data */ @@ -279,6 +284,7 @@ export default abstract class IndicatorImp implements Indicator { shouldOhlc: boolean shouldFormatBigNumber: boolean visible: boolean + zLevel: number extendData: any series: IndicatorSeries figures: Array> @@ -296,7 +302,7 @@ export default abstract class IndicatorImp implements Indicator { constructor (indicator: IndicatorTemplate) { const { name, shortName, series, calcParams, figures, precision, - shouldOhlc, shouldFormatBigNumber, visible, + shouldOhlc, shouldFormatBigNumber, visible, zLevel, minValue, maxValue, styles, extendData, regenerateFigures, createTooltipDataSource, draw } = indicator @@ -309,6 +315,7 @@ export default abstract class IndicatorImp implements Indicator { 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 ?? {}) @@ -377,14 +384,19 @@ export default abstract class IndicatorImp implements Indicator { return false } - setStyles (styles: Nullable>): boolean { - if (styles !== null) { - merge(this.styles, styles) + setZLevel (zLevel: number): boolean { + if (this.zLevel !== zLevel) { + this.zLevel = zLevel return true } return false } + setStyles (styles: Partial): boolean { + merge(this.styles, styles) + return true + } + setExtendData (extendData: any): boolean { if (this.extendData !== extendData) { this.extendData = extendData diff --git a/src/component/Overlay.ts b/src/component/Overlay.ts index fde9f82ec..83bc31f52 100644 --- a/src/component/Overlay.ts +++ b/src/component/Overlay.ts @@ -22,7 +22,7 @@ import BarSpace from '../common/BarSpace' import Precision from '../common/Precision' import { OverlayStyle } from '../common/Styles' import { MouseTouchEvent } from '../common/SyntheticEvent' -import { clone, isNumber, isValid, merge } from '../common/utils/typeChecks' +import { clone, isNumber, isString, merge } from '../common/utils/typeChecks' import TimeScaleStore from '../store/TimeScaleStore' @@ -375,7 +375,7 @@ export default abstract class OverlayImp implements Overlay { } setId (id: string): boolean { - if (!isValid(this.id)) { + if (!isString(this.id)) { this.id = id return true } @@ -383,7 +383,7 @@ export default abstract class OverlayImp implements Overlay { } setGroupId (groupId: string): boolean { - if (!isValid(this.groupId)) { + if (!isString(this.groupId)) { this.groupId = groupId return true } @@ -402,12 +402,9 @@ export default abstract class OverlayImp implements Overlay { return false } - setStyles (styles: Nullable>): boolean { - if (styles !== null) { - merge(this.styles, styles) - return true - } - return false + setStyles (styles: DeepPartial): boolean { + merge(this.styles, styles) + return true } setPoints (points: Array>): boolean { diff --git a/src/store/IndicatorStore.ts b/src/store/IndicatorStore.ts index 0dfcdc5d4..e89e3bcaf 100644 --- a/src/store/IndicatorStore.ts +++ b/src/store/IndicatorStore.ts @@ -14,7 +14,7 @@ import Nullable from '../common/Nullable' import Precision from '../common/Precision' -import { isValid } from '../common/utils/typeChecks' +import { isValid, isString, isArray, isNumber, isBoolean, isFunction } from '../common/utils/typeChecks' import ChartStore from './ChartStore' @@ -23,113 +23,133 @@ import { getIndicatorClass } from '../extension/indicator/index' export default class IndicatorStore { private readonly _chartStore: ChartStore - private readonly _instances: Map> = new Map() + private readonly _instances: Map = new Map() constructor (chartStore: ChartStore) { this._chartStore = chartStore } - private _overrideInstance (instance: IndicatorImp, indicator: Partial): boolean[] { + private _overrideInstance (instance: IndicatorImp, indicator: Partial): [boolean, boolean, boolean] { const { shortName, series, calcParams, precision, figures, minValue, maxValue, - shouldOhlc, shouldFormatBigNumber, visible, styles, extendData, + shouldOhlc, shouldFormatBigNumber, visible, zLevel, styles, extendData, regenerateFigures, createTooltipDataSource, draw, calc } = indicator let updateFlag = false - if (shortName !== undefined && instance.setShortName(shortName)) { + if (isString(shortName) && instance.setShortName(shortName)) { updateFlag = true } - if (series !== undefined && instance.setSeries(series)) { + if (isValid(series) && instance.setSeries(series)) { updateFlag = true } let calcFlag = false - if (calcParams !== undefined && instance.setCalcParams(calcParams)) { + if (isArray(calcParams) && instance.setCalcParams(calcParams)) { updateFlag = true calcFlag = true } - if (figures !== undefined && instance.setFigures(figures)) { + if (isArray(figures) && instance.setFigures(figures)) { updateFlag = true calcFlag = true } - if (minValue !== undefined && instance.setMinValue(minValue)) { + if (instance.setMinValue(minValue ?? null)) { updateFlag = true } - if (maxValue !== undefined && instance.setMinValue(maxValue)) { + if (instance.setMinValue(maxValue ?? null)) { updateFlag = true } - if (precision !== undefined && instance.setPrecision(precision)) { + if (isNumber(precision) && instance.setPrecision(precision)) { updateFlag = true } - if (shouldOhlc !== undefined && instance.setShouldOhlc(shouldOhlc)) { + if (isBoolean(shouldOhlc) && instance.setShouldOhlc(shouldOhlc)) { updateFlag = true } - if (shouldFormatBigNumber !== undefined && instance.setShouldFormatBigNumber(shouldFormatBigNumber)) { + if (isBoolean(shouldFormatBigNumber) && instance.setShouldFormatBigNumber(shouldFormatBigNumber)) { updateFlag = true } - if (visible !== undefined && instance.setVisible(visible)) { + if (isBoolean(visible) && instance.setVisible(visible)) { updateFlag = true } - if (styles !== undefined && instance.setStyles(styles)) { + let sortFlag = false + if (isNumber(zLevel) && instance.setZLevel(zLevel)) { updateFlag = true + sortFlag = true } - if (extendData !== undefined && instance.setExtendData(extendData)) { + if (isValid(styles) && instance.setStyles(styles)) { + updateFlag = true + } + if (instance.setExtendData(extendData)) { updateFlag = true calcFlag = true } - if (regenerateFigures !== undefined && instance.setRegenerateFigures(regenerateFigures)) { + if (instance.setRegenerateFigures(regenerateFigures ?? null)) { updateFlag = true } - if (createTooltipDataSource !== undefined && instance.setCreateTooltipDataSource(createTooltipDataSource)) { + if (instance.setCreateTooltipDataSource(createTooltipDataSource ?? null)) { updateFlag = true } - if (draw !== undefined && instance.setDraw(draw)) { + if (instance.setDraw(draw ?? null)) { updateFlag = true } - if (calc !== undefined) { + if (isFunction(calc)) { instance.calc = calc calcFlag = true } - return [updateFlag, calcFlag] + return [updateFlag, calcFlag, sortFlag] + } + + private _sort (paneId?: string): void { + if (isString(paneId)) { + this._instances.get(paneId)?.sort((i1, i2) => i1.zLevel - i2.zLevel) + } else { + this._instances.forEach(paneInstances => { + paneInstances.sort((i1, i2) => i1.zLevel - i2.zLevel) + }) + } } async addInstance (indicator: IndicatorCreate, paneId: string, isStack: boolean): Promise { const { name } = indicator let paneInstances = this._instances.get(paneId) - if (paneInstances?.has(name) ?? false) { - return await Promise.reject(new Error('Duplicate indicators.')) + if (isValid(paneInstances)) { + const instance = paneInstances.find(ins => ins.name === name) + if (isValid(instance)) { + return await Promise.reject(new Error('Duplicate indicators.')) + } } - if (paneInstances === undefined) { - paneInstances = new Map() - this._instances.set(paneId, paneInstances) + if (!isValid(paneInstances)) { + paneInstances = [] } const IndicatorClazz = getIndicatorClass(name) as IndicatorConstructor const instance = new IndicatorClazz() this._overrideInstance(instance, indicator) if (!isStack) { - paneInstances.clear() + paneInstances = [] } - paneInstances.set(name, instance) + paneInstances.push(instance) + this._instances.set(paneId, paneInstances) + this._sort(paneId) return await instance.calcIndicator(this._chartStore.getDataList()) } - getInstances (paneId: string): Map { - return this._instances.get(paneId) ?? new Map() + getInstances (paneId: string): IndicatorImp[] { + return this._instances.get(paneId) ?? [] } removeInstance (paneId: string, name?: string): boolean { let removed = false const paneInstances = this._instances.get(paneId) - if (paneInstances !== undefined) { - if (name !== undefined) { - if (paneInstances.has(name)) { - paneInstances.delete(name) + if (isValid(paneInstances)) { + if (isString(name)) { + const index = paneInstances.findIndex(ins => ins.name === name) + if (index > -1) { + paneInstances.splice(index, 1) removed = true } } else { - paneInstances.clear() + this._instances.set(paneId, []) removed = true } - if (paneInstances.size === 0) { + if (this._instances.get(paneId)?.length === 0) { this._instances.delete(paneId) } } @@ -142,17 +162,19 @@ export default class IndicatorStore { async calcInstance (name?: string, paneId?: string): Promise { const tasks: Array> = [] - if (name !== undefined) { - if (paneId !== undefined) { + if (isString(name)) { + if (isString(paneId)) { const paneInstances = this._instances.get(paneId) - if (paneInstances?.has(name) ?? false) { - const instance = paneInstances?.get(name) as IndicatorImp - tasks.push(instance.calcIndicator(this._chartStore.getDataList())) + if (isValid(paneInstances)) { + const instance = paneInstances.find(ins => ins.name === name) + if (isValid(instance)) { + tasks.push(instance.calcIndicator(this._chartStore.getDataList())) + } } } else { this._instances.forEach(paneInstances => { - if (paneInstances.has(name)) { - const instance = paneInstances?.get(name) as IndicatorImp + const instance = paneInstances.find(ins => ins.name === name) + if (isValid(instance)) { tasks.push(instance.calcIndicator(this._chartStore.getDataList())) } }) @@ -169,14 +191,26 @@ export default class IndicatorStore { } getInstanceByPaneId (paneId?: string, name?: string): Nullable | Nullable> | Map> { - if (paneId !== undefined) { - const paneInstances = this._instances.get(paneId) - if (name !== undefined) { - return paneInstances?.get(name) ?? null + const createMapping: ((instances: IndicatorImp[]) => Map) = (instances: IndicatorImp[]) => { + const mapping = new Map() + instances.forEach(ins => { + mapping.set(ins.name, ins) + }) + return mapping + } + + if (isString(paneId)) { + const paneInstances = this._instances.get(paneId) ?? [] + if (isString(name)) { + return paneInstances?.find(ins => ins.name === name) ?? null } - return paneInstances ?? null + return createMapping(paneInstances) } - return this._instances + const mapping = new Map>() + this._instances.forEach((instances, paneId) => { + mapping.set(paneId, createMapping(instances)) + }) + return mapping } setSeriesPrecision (precision: Precision): void { @@ -194,7 +228,7 @@ export default class IndicatorStore { async override (indicator: IndicatorCreate, paneId: Nullable): Promise<[boolean, boolean]> { const { name } = indicator - let instances: Map> = new Map() + let instances: Map = new Map() if (paneId !== null) { const paneInstances = this._instances.get(paneId) if (isValid(paneInstances)) { @@ -205,10 +239,14 @@ export default class IndicatorStore { } let onlyUpdateFlag = false const tasks: Array> = [] + let sortFlag = false instances.forEach(paneInstances => { - const instance = paneInstances.get(name) + const instance = paneInstances.find(ins => ins.name === name) if (isValid(instance)) { const overrideResult = this._overrideInstance(instance, indicator) + if (overrideResult[2]) { + sortFlag = true + } if (overrideResult[1]) { tasks.push(instance.calcIndicator(this._chartStore.getDataList())) } else { @@ -218,6 +256,9 @@ export default class IndicatorStore { } } }) + if (sortFlag) { + this._sort() + } const result = await Promise.all(tasks) return [onlyUpdateFlag, result.includes(true)] } diff --git a/src/store/OverlayStore.ts b/src/store/OverlayStore.ts index 6350da878..8faa14ea8 100644 --- a/src/store/OverlayStore.ts +++ b/src/store/OverlayStore.ts @@ -15,7 +15,7 @@ import Nullable from '../common/Nullable' import { UpdateLevel } from '../common/Updater' import { MouseTouchEvent } from '../common/SyntheticEvent' -import { isFunction, isValid, isString, isBoolean } from '../common/utils/typeChecks' +import { isFunction, isValid, isString, isBoolean, isNumber, isArray } from '../common/utils/typeChecks' import { createId } from '../common/utils/id' import OverlayImp, { OVERLAY_ID_PREFIX, OVERLAY_ACTIVE_Z_LEVEL, OverlayCreate, OverlayRemove } from '../component/Overlay' @@ -107,79 +107,51 @@ export default class OverlayStore { } = overlay let updateFlag = false let sortFlag = false - if (id !== undefined) { + if (isString(id)) { instance.setId(id) } - if (groupId !== undefined) { + if (isString(groupId)) { instance.setGroupId(groupId) } - if (points !== undefined && instance.setPoints(points)) { + if (isArray(points) && instance.setPoints(points)) { updateFlag = true } - if (styles !== undefined && instance.setStyles(styles)) { + if (isValid(styles) && instance.setStyles(styles)) { updateFlag = true } - if (lock !== undefined) { + if (isBoolean(lock)) { instance.setLock(lock) } - if (visible !== undefined && instance.setVisible(visible)) { + if (isBoolean(visible) && instance.setVisible(visible)) { updateFlag = true } - if (zLevel !== undefined && instance.setZLevel(zLevel)) { + if (isNumber(zLevel) && instance.setZLevel(zLevel)) { updateFlag = true sortFlag = true } - if (mode !== undefined) { + if (isValid(mode)) { instance.setMode(mode) } - if (modeSensitivity !== undefined) { + if (isNumber(modeSensitivity)) { instance.setModeSensitivity(modeSensitivity) } - if (extendData !== undefined && instance.setExtendData(extendData)) { + if (instance.setExtendData(extendData)) { updateFlag = true } - if (onDrawStart !== undefined) { - instance.setOnDrawStartCallback(onDrawStart) - } - if (onDrawing !== undefined) { - instance.setOnDrawingCallback(onDrawing) - } - if (onDrawEnd !== undefined) { - instance.setOnDrawEndCallback(onDrawEnd) - } - if (onClick !== undefined) { - instance.setOnClickCallback(onClick) - } - if (onDoubleClick !== undefined) { - instance.setOnDoubleClickCallback(onDoubleClick) - } - if (onRightClick !== undefined) { - instance.setOnRightClickCallback(onRightClick) - } - if (onPressedMoveStart !== undefined) { - instance.setOnPressedMoveStartCallback(onPressedMoveStart) - } - if (onPressedMoving !== undefined) { - instance.setOnPressedMovingCallback(onPressedMoving) - } - if (onPressedMoveEnd !== undefined) { - instance.setOnPressedMoveEndCallback(onPressedMoveEnd) - } - if (onMouseEnter !== undefined) { - instance.setOnMouseEnterCallback(onMouseEnter) - } - if (onMouseLeave !== undefined) { - instance.setOnMouseLeaveCallback(onMouseLeave) - } - if (onRemoved !== undefined) { - instance.setOnRemovedCallback(onRemoved) - } - if (onSelected !== undefined) { - instance.setOnSelectedCallback(onSelected) - } - if (onDeselected !== undefined) { - instance.setOnDeselectedCallback(onDeselected) - } + instance.setOnDrawStartCallback(onDrawStart ?? null) + instance.setOnDrawingCallback(onDrawing ?? null) + instance.setOnDrawEndCallback(onDrawEnd ?? null) + instance.setOnClickCallback(onClick ?? null) + instance.setOnDoubleClickCallback(onDoubleClick ?? null) + instance.setOnRightClickCallback(onRightClick ?? null) + instance.setOnPressedMoveStartCallback(onPressedMoveStart ?? null) + instance.setOnPressedMovingCallback(onPressedMoving ?? null) + instance.setOnPressedMoveEndCallback(onPressedMoveEnd ?? null) + instance.setOnMouseEnterCallback(onMouseEnter ?? null) + instance.setOnMouseLeaveCallback(onMouseLeave ?? null) + instance.setOnRemovedCallback(onRemoved ?? null) + instance.setOnSelectedCallback(onSelected ?? null) + instance.setOnDeselectedCallback(onDeselected ?? null) return [updateFlag, sortFlag] } @@ -201,10 +173,10 @@ export default class OverlayStore { private _sort (paneId?: string): void { if (isString(paneId)) { - this._instances.get(paneId)?.sort((o1, o2) => o1.zLevel - o2.zLevel).sort((o1, o2) => o1.zLevel - o2.zLevel) + this._instances.get(paneId)?.sort((o1, o2) => o1.zLevel - o2.zLevel) } else { this._instances.forEach(paneInstances => { - paneInstances.sort((o1, o2) => o1.zLevel - o2.zLevel).sort((o1, o2) => o1.zLevel - o2.zLevel) + paneInstances.sort((o1, o2) => o1.zLevel - o2.zLevel) }) } } diff --git a/src/view/CandleTooltipView.ts b/src/view/CandleTooltipView.ts index eb736bae1..46f732f56 100644 --- a/src/view/CandleTooltipView.ts +++ b/src/view/CandleTooltipView.ts @@ -181,7 +181,7 @@ export default class CandleTooltipView extends IndicatorTooltipView { private _drawRectTooltip ( ctx: CanvasRenderingContext2D, dataList: KLineData[], - indicators: Map, + indicators: Indicator[], bounding: Bounding, yAxisBounding: Bounding, crosshair: Crosshair, diff --git a/src/view/IndicatorTooltipView.ts b/src/view/IndicatorTooltipView.ts index 2d14d8832..b90f5bdf1 100644 --- a/src/view/IndicatorTooltipView.ts +++ b/src/view/IndicatorTooltipView.ts @@ -70,7 +70,7 @@ export default class IndicatorTooltipView extends View { dataList: KLineData[], crosshair: Crosshair, activeTooltipIconInfo: Nullable, - indicators: Map, + indicators: IndicatorImp[], customApi: CustomApi, formatThousands: string, bounding: Bounding, diff --git a/src/view/IndicatorView.ts b/src/view/IndicatorView.ts index 87c2baac0..134ae2db5 100644 --- a/src/view/IndicatorView.ts +++ b/src/view/IndicatorView.ts @@ -32,8 +32,7 @@ export default class IndicatorView extends CandleBarView { const yAxis = pane.getAxisComponent() if (!yAxis.isInCandle()) { const indicators = chartStore.getIndicatorStore().getInstances(pane.getId()) - for (const entries of indicators) { - const indicator = entries[1] + for (const indicator of indicators) { if (indicator.shouldOhlc && indicator.visible) { const indicatorStyles = indicator.styles const defaultStyles = chartStore.getStyles().indicator @@ -74,8 +73,14 @@ export default class IndicatorView extends CandleBarView { const visibleRange = timeScaleStore.getVisibleRange() const indicators = chartStore.getIndicatorStore().getInstances(pane.getId()) const defaultStyles = chartStore.getStyles().indicator + ctx.save() indicators.forEach(indicator => { if (indicator.visible) { + if (indicator.zLevel < 0) { + ctx.globalCompositeOperation = 'destination-over' + } else { + ctx.globalCompositeOperation = 'source-over' + } let isCover = false if (indicator.draw !== null) { ctx.save() @@ -173,5 +178,6 @@ export default class IndicatorView extends CandleBarView { } } }) + ctx.restore() } }