From 94adf948462720c5778093ea4225158f0e62678e Mon Sep 17 00:00:00 2001 From: Eugene Korobko Date: Thu, 23 Mar 2023 12:38:15 +0300 Subject: [PATCH 01/23] Rebase on master --- src/api/candlestick-series-api.ts | 2 +- src/api/chart-api.ts | 99 +++---- src/api/create-chart.ts | 99 ++++++- src/api/data-consumer.ts | 79 ++---- src/api/data-layer.ts | 264 +++++++---------- src/api/data-validators.ts | 25 +- src/api/get-series-data-creator.ts | 51 ++-- src/api/get-series-plot-row-creator.ts | 55 ++-- src/api/ichart-api.ts | 43 +-- src/api/iseries-api.ts | 18 +- src/api/itime-scale-api.ts | 23 +- src/api/options/chart-options-defaults.ts | 102 +++---- .../options/time-scale-options-defaults.ts | 1 + src/api/price-line-api.ts | 8 +- src/api/price-scale-api.ts | 8 +- src/api/series-api.ts | 61 ++-- src/api/time-scale-api.ts | 45 +-- src/gui/chart-widget.ts | 68 ++--- src/gui/pane-separator.ts | 10 +- src/gui/pane-widget.ts | 52 ++-- src/gui/price-axis-stub.ts | 14 +- src/gui/price-axis-widget.ts | 54 ++-- src/gui/time-axis-widget.ts | 36 +-- src/index.ts | 8 +- src/model/chart-model.ts | 124 ++++---- src/model/crosshair.ts | 54 ++-- src/model/custom-price-line.ts | 19 +- src/model/data-source.ts | 16 +- src/model/formatted-labels-cache.ts | 19 +- src/model/grid.ts | 8 +- .../default-tick-mark-formatter.ts | 5 +- .../horz-scale-behavior-time.ts | 266 ++++++++++++++++++ .../time-scale-point-weight-generator.ts | 24 +- src/model/horz-scale-behavior-time/types.ts | 130 +++++++++ src/model/idata-source.ts | 16 +- src/model/ihorz-scale-behavior.ts | 32 +++ src/model/iprice-data-source.ts | 9 +- src/model/localization-options.ts | 7 +- src/model/magnet.ts | 11 +- src/model/pane.ts | 88 +++--- src/model/plot-data.ts | 9 +- src/model/plot-list.ts | 2 +- src/model/price-data-source.ts | 8 +- src/model/price-scale.ts | 30 +- src/model/price-tick-mark-builder.ts | 6 +- src/model/series-bar-colorer.ts | 181 ++++++------ src/model/series-data.ts | 34 +-- src/model/series-markers.ts | 11 +- src/model/series.ts | 86 +++--- src/model/sort-sources.ts | 4 +- src/model/tick-marks.ts | 45 +-- src/model/time-data.ts | 94 +------ src/model/time-scale.ts | 187 +++--------- src/model/watermark.ts | 10 +- src/renderers/itime-axis-view-renderer.ts | 2 +- .../price-axis-renderer-options-provider.ts | 6 +- src/renderers/time-axis-view-renderer.ts | 2 +- src/views/pane/area-pane-view.ts | 6 +- src/views/pane/bars-pane-view-base.ts | 12 +- src/views/pane/bars-pane-view.ts | 5 +- src/views/pane/baseline-pane-view.ts | 6 +- src/views/pane/candlesticks-pane-view.ts | 4 +- src/views/pane/crosshair-marks-pane-view.ts | 11 +- src/views/pane/crosshair-pane-view.ts | 6 +- src/views/pane/custom-price-line-pane-view.ts | 7 +- src/views/pane/grid-pane-view.ts | 6 +- src/views/pane/histogram-pane-view.ts | 4 +- src/views/pane/ipane-view.ts | 2 +- src/views/pane/iupdatable-pane-view.ts | 2 +- src/views/pane/line-pane-view-base.ts | 13 +- src/views/pane/line-pane-view.ts | 4 +- src/views/pane/pane-price-axis-view.ts | 10 +- .../series-horizontal-base-line-pane-view.ts | 5 +- .../pane/series-horizontal-line-pane-view.ts | 9 +- .../series-last-price-animation-pane-view.ts | 6 +- src/views/pane/series-markers-pane-view.ts | 19 +- src/views/pane/series-pane-view-base.ts | 10 +- src/views/pane/series-price-line-pane-view.ts | 6 +- src/views/pane/watermark-pane-view.ts | 6 +- .../price-axis/crosshair-price-axis-view.ts | 12 +- .../custom-price-line-price-axis-view.ts | 9 +- src/views/price-axis/iprice-axis-view.ts | 4 +- src/views/price-axis/price-axis-view.ts | 4 +- .../price-axis/series-price-axis-view.ts | 8 +- .../time-axis/crosshair-time-axis-view.ts | 12 +- src/views/time-axis/itime-axis-view.ts | 4 +- .../default-tick-mark-formatter.spec.ts | 5 +- 87 files changed, 1634 insertions(+), 1353 deletions(-) rename src/model/{ => horz-scale-behavior-time}/default-tick-mark-formatter.ts (91%) create mode 100644 src/model/horz-scale-behavior-time/horz-scale-behavior-time.ts rename src/{api => model/horz-scale-behavior-time}/time-scale-point-weight-generator.ts (69%) create mode 100644 src/model/horz-scale-behavior-time/types.ts create mode 100644 src/model/ihorz-scale-behavior.ts diff --git a/src/api/candlestick-series-api.ts b/src/api/candlestick-series-api.ts index db44370cca..2453f91267 100644 --- a/src/api/candlestick-series-api.ts +++ b/src/api/candlestick-series-api.ts @@ -5,7 +5,7 @@ import { import { SeriesApi } from './series-api'; -export class CandlestickSeriesApi extends SeriesApi<'Candlestick'> { +export class CandlestickSeriesApi extends SeriesApi<'Candlestick', HorzScaleItem> { public override applyOptions(options: CandlestickSeriesPartialOptions): void { fillUpDownCandlesticksColors(options); super.applyOptions(options); diff --git a/src/api/chart-api.ts b/src/api/chart-api.ts index 9c638641b3..653e451d4c 100644 --- a/src/api/chart-api.ts +++ b/src/api/chart-api.ts @@ -6,6 +6,7 @@ import { warn } from '../helpers/logger'; import { clone, DeepPartial, isBoolean, merge } from '../helpers/strict-type-checks'; import { ChartOptions, ChartOptionsInternal } from '../model/chart-model'; +import { IHorzScaleBehavior } from '../model/ihorz-scale-behavior'; import { Series } from '../model/series'; import { SeriesPlotRow } from '../model/series-data'; import { @@ -24,7 +25,7 @@ import { SeriesStyleOptionsMap, SeriesType, } from '../model/series-options'; -import { Logical, Time } from '../model/time-data'; +import { Logical } from '../model/time-data'; import { DataUpdatesConsumer, isFulfilledData, SeriesDataItemTypeMap } from './data-consumer'; import { DataLayer, DataUpdateResponse, SeriesChanges } from './data-layer'; @@ -57,7 +58,7 @@ function patchPriceFormat(priceFormat?: DeepPartial): void { } } -function migrateHandleScaleScrollOptions(options: DeepPartial): void { +function migrateHandleScaleScrollOptions(options: DeepPartial>): void { if (isBoolean(options.handleScale)) { const handleScale = options.handleScale; options.handleScale = { @@ -99,34 +100,38 @@ function migrateHandleScaleScrollOptions(options: DeepPartial): vo } } -function toInternalOptions(options: DeepPartial): DeepPartial { +function toInternalOptions(options: DeepPartial>): DeepPartial> { migrateHandleScaleScrollOptions(options); - return options as DeepPartial; + return options as DeepPartial>; } -export type IPriceScaleApiProvider = Pick; +export type IPriceScaleApiProvider = Pick, 'priceScale'>; -export class ChartApi implements IChartApi, DataUpdatesConsumer { - private _chartWidget: ChartWidget; - private _dataLayer: DataLayer = new DataLayer(); - private readonly _seriesMap: Map, Series> = new Map(); - private readonly _seriesMapReversed: Map> = new Map(); +export class ChartApi implements IChartApi, DataUpdatesConsumer { + private _chartWidget: ChartWidget; + private _dataLayer: DataLayer; + private readonly _seriesMap: Map, Series> = new Map(); + private readonly _seriesMapReversed: Map, SeriesApi> = new Map(); - private readonly _clickedDelegate: Delegate = new Delegate(); - private readonly _crosshairMovedDelegate: Delegate = new Delegate(); + private readonly _clickedDelegate: Delegate> = new Delegate(); + private readonly _crosshairMovedDelegate: Delegate> = new Delegate(); - private readonly _timeScaleApi: TimeScaleApi; + private readonly _timeScaleApi: TimeScaleApi; - public constructor(container: HTMLElement, options?: DeepPartial) { + private readonly _horzScaleBehavior: IHorzScaleBehavior; + + public constructor(container: HTMLElement, horzScaleBehavior: IHorzScaleBehavior, options?: DeepPartial>) { + this._dataLayer = new DataLayer(horzScaleBehavior); const internalOptions = (options === undefined) ? - clone(chartOptionsDefaults) : - merge(clone(chartOptionsDefaults), toInternalOptions(options)) as ChartOptionsInternal; + clone(chartOptionsDefaults()) : + merge(clone(chartOptionsDefaults()), toInternalOptions(options)) as ChartOptionsInternal; - this._chartWidget = new ChartWidget(container, internalOptions); + this._horzScaleBehavior = horzScaleBehavior; + this._chartWidget = new ChartWidget(container, internalOptions, horzScaleBehavior); this._chartWidget.clicked().subscribe( - (paramSupplier: MouseEventParamsImplSupplier) => { + (paramSupplier: MouseEventParamsImplSupplier) => { if (this._clickedDelegate.hasListeners()) { this._clickedDelegate.fire(this._convertMouseParams(paramSupplier())); } @@ -134,7 +139,7 @@ export class ChartApi implements IChartApi, DataUpdatesConsumer { this ); this._chartWidget.crosshairMoved().subscribe( - (paramSupplier: MouseEventParamsImplSupplier) => { + (paramSupplier: MouseEventParamsImplSupplier) => { if (this._crosshairMovedDelegate.hasListeners()) { this._crosshairMovedDelegate.fire(this._convertMouseParams(paramSupplier())); } @@ -143,7 +148,7 @@ export class ChartApi implements IChartApi, DataUpdatesConsumer { ); const model = this._chartWidget.model(); - this._timeScaleApi = new TimeScaleApi(model, this._chartWidget.timeAxisWidget()); + this._timeScaleApi = new TimeScaleApi(model, this._chartWidget.timeAxisWidget(), this._horzScaleBehavior); } public remove(): void { @@ -171,33 +176,33 @@ export class ChartApi implements IChartApi, DataUpdatesConsumer { this._chartWidget.resize(width, height, forceRepaint); } - public addAreaSeries(options?: AreaSeriesPartialOptions): ISeriesApi<'Area'> { + public addAreaSeries(options?: AreaSeriesPartialOptions): ISeriesApi<'Area', HorzScaleItem> { return this._addSeriesImpl('Area', areaStyleDefaults, options); } - public addBaselineSeries(options?: BaselineSeriesPartialOptions): ISeriesApi<'Baseline'> { + public addBaselineSeries(options?: BaselineSeriesPartialOptions): ISeriesApi<'Baseline', HorzScaleItem> { return this._addSeriesImpl('Baseline', baselineStyleDefaults, options); } - public addBarSeries(options?: BarSeriesPartialOptions): ISeriesApi<'Bar'> { + public addBarSeries(options?: BarSeriesPartialOptions): ISeriesApi<'Bar', HorzScaleItem> { return this._addSeriesImpl('Bar', barStyleDefaults, options); } - public addCandlestickSeries(options: CandlestickSeriesPartialOptions = {}): ISeriesApi<'Candlestick'> { + public addCandlestickSeries(options: CandlestickSeriesPartialOptions = {}): ISeriesApi<'Candlestick', HorzScaleItem> { fillUpDownCandlesticksColors(options); return this._addSeriesImpl('Candlestick', candlestickStyleDefaults, options); } - public addHistogramSeries(options?: HistogramSeriesPartialOptions): ISeriesApi<'Histogram'> { + public addHistogramSeries(options?: HistogramSeriesPartialOptions): ISeriesApi<'Histogram', HorzScaleItem> { return this._addSeriesImpl('Histogram', histogramStyleDefaults, options); } - public addLineSeries(options?: LineSeriesPartialOptions): ISeriesApi<'Line'> { + public addLineSeries(options?: LineSeriesPartialOptions): ISeriesApi<'Line', HorzScaleItem> { return this._addSeriesImpl('Line', lineStyleDefaults, options); } - public removeSeries(seriesApi: SeriesApi): void { + public removeSeries(seriesApi: SeriesApi): void { const series = ensureDefined(this._seriesMap.get(seriesApi)); const update = this._dataLayer.removeSeries(series); @@ -210,27 +215,27 @@ export class ChartApi implements IChartApi, DataUpdatesConsumer { this._seriesMapReversed.delete(series); } - public applyNewData(series: Series, data: SeriesDataItemTypeMap[TSeriesType][]): void { + public applyNewData(series: Series, data: SeriesDataItemTypeMap[TSeriesType][]): void { this._sendUpdateToChart(this._dataLayer.setSeriesData(series, data)); } - public updateData(series: Series, data: SeriesDataItemTypeMap[TSeriesType]): void { + public updateData(series: Series, data: SeriesDataItemTypeMap[TSeriesType]): void { this._sendUpdateToChart(this._dataLayer.updateSeriesData(series, data)); } - public subscribeClick(handler: MouseEventHandler): void { + public subscribeClick(handler: MouseEventHandler): void { this._clickedDelegate.subscribe(handler); } - public unsubscribeClick(handler: MouseEventHandler): void { + public unsubscribeClick(handler: MouseEventHandler): void { this._clickedDelegate.unsubscribe(handler); } - public subscribeCrosshairMove(handler: MouseEventHandler): void { + public subscribeCrosshairMove(handler: MouseEventHandler): void { this._crosshairMovedDelegate.subscribe(handler); } - public unsubscribeCrosshairMove(handler: MouseEventHandler): void { + public unsubscribeCrosshairMove(handler: MouseEventHandler): void { this._crosshairMovedDelegate.unsubscribe(handler); } @@ -238,16 +243,16 @@ export class ChartApi implements IChartApi, DataUpdatesConsumer { return new PriceScaleApi(this._chartWidget, priceScaleId); } - public timeScale(): ITimeScaleApi { + public timeScale(): ITimeScaleApi { return this._timeScaleApi; } - public applyOptions(options: DeepPartial): void { + public applyOptions(options: DeepPartial>): void { this._chartWidget.applyOptions(toInternalOptions(options)); } - public options(): Readonly { - return this._chartWidget.options() as Readonly; + public options(): Readonly> { + return this._chartWidget.options() as Readonly>; } public takeScreenshot(): HTMLCanvasElement { @@ -262,36 +267,36 @@ export class ChartApi implements IChartApi, DataUpdatesConsumer { type: TSeries, styleDefaults: SeriesStyleOptionsMap[TSeries], options: SeriesPartialOptionsMap[TSeries] = {} - ): ISeriesApi { + ): ISeriesApi { patchPriceFormat(options.priceFormat); const strictOptions = merge(clone(seriesOptionsDefaults), clone(styleDefaults), options) as SeriesOptionsMap[TSeries]; const series = this._chartWidget.model().createSeries(type, strictOptions); - const res = new SeriesApi(series, this, this); + const res = new SeriesApi(series, this, this, this._horzScaleBehavior); this._seriesMap.set(res, series); this._seriesMapReversed.set(series, res); return res; } - private _sendUpdateToChart(update: DataUpdateResponse): void { + private _sendUpdateToChart(update: DataUpdateResponse): void { const model = this._chartWidget.model(); model.updateTimeScale(update.timeScale.baseIndex, update.timeScale.points, update.timeScale.firstChangedPointIndex); - update.series.forEach((value: SeriesChanges, series: Series) => series.setData(value.data, value.info)); + update.series.forEach((value: SeriesChanges, series: Series) => series.setData(value.data, value.info)); model.recalculateAllPanes(); } - private _mapSeriesToApi(series: Series): ISeriesApi { + private _mapSeriesToApi(series: Series): ISeriesApi { return ensureDefined(this._seriesMapReversed.get(series)); } - private _convertMouseParams(param: MouseEventParamsImpl): MouseEventParams { - const seriesData: MouseEventParams['seriesData'] = new Map(); - param.seriesData.forEach((plotRow: SeriesPlotRow, series: Series) => { - const data = getSeriesDataCreator(series.seriesType())(plotRow); + private _convertMouseParams(param: MouseEventParamsImpl): MouseEventParams { + const seriesData: MouseEventParams['seriesData'] = new Map(); + param.seriesData.forEach((plotRow: SeriesPlotRow, series: Series) => { + const data = getSeriesDataCreator(series.seriesType())(plotRow); assert(isFulfilledData(data)); seriesData.set(this._mapSeriesToApi(series), data); }); @@ -299,7 +304,7 @@ export class ChartApi implements IChartApi, DataUpdatesConsumer { const hoveredSeries = param.hoveredSeries === undefined ? undefined : this._mapSeriesToApi(param.hoveredSeries); return { - time: param.time as Time | undefined, + time: param.time, logical: param.index as Logical | undefined, point: param.point, hoveredSeries, diff --git a/src/api/create-chart.ts b/src/api/create-chart.ts index 9a91dd27a3..c6722f1f4e 100644 --- a/src/api/create-chart.ts +++ b/src/api/create-chart.ts @@ -1,9 +1,19 @@ import { assert } from '../helpers/assertions'; -import { DeepPartial, isString } from '../helpers/strict-type-checks'; +import { Nominal } from '../helpers/nominal'; +import { DeepPartial, isString, merge } from '../helpers/strict-type-checks'; import { ChartOptions } from '../model/chart-model'; +import { HorzScaleBehaviorTime } from '../model/horz-scale-behavior-time/horz-scale-behavior-time'; +import { Time } from '../model/horz-scale-behavior-time/types'; +import { DataItem, HorzScaleItemConverterToInternalObj, IHorzScaleBehavior, InternalHorzScaleItem, InternalHorzScaleItemKey } from '../model/ihorz-scale-behavior'; +import { LocalizationOptions } from '../model/localization-options'; +import { SeriesType } from '../model/series-options'; +import { TickMark } from '../model/tick-marks'; +import { TickMarkWeightValue, TimeScalePoint } from '../model/time-data'; +import { markWithGreaterWeight, TimeMark } from '../model/time-scale'; import { ChartApi } from './chart-api'; +import { SeriesDataItemTypeMap } from './data-consumer'; import { IChartApi } from './ichart-api'; /** @@ -13,7 +23,7 @@ import { IChartApi } from './ichart-api'; * @param options - Any subset of options to be applied at start. * @returns An interface to the created chart */ -export function createChart(container: string | HTMLElement, options?: DeepPartial): IChartApi { +export function createChartEx(container: string | HTMLElement, horzScaleBehavior: IHorzScaleBehavior, options?: DeepPartial>): IChartApi { let htmlElement: HTMLElement; if (isString(container)) { const element = document.getElementById(container); @@ -23,5 +33,88 @@ export function createChart(container: string | HTMLElement, options?: DeepParti htmlElement = container; } - return new ChartApi(htmlElement, options); + const res = new ChartApi(htmlElement, horzScaleBehavior, options); + horzScaleBehavior.setOptions(res.options()); + return res; +} + +export function createChart(container: string | HTMLElement, options?: DeepPartial>): IChartApi