Skip to content

Commit

Permalink
Merge pull request #1238 from tradingview/external-series-primitives
Browse files Browse the repository at this point in the history
added plugins API
  • Loading branch information
SlicedSilver committed Jun 13, 2023
2 parents 1c0c73c + a996aab commit 38d6040
Show file tree
Hide file tree
Showing 70 changed files with 6,433 additions and 178 deletions.
11 changes: 11 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,13 @@ jobs:
- store_test_results:
path: test-results/

type-tests:
executor: node16-executor
steps:
- checkout-with-deps
- run: npm run tsc-verify
- run: npm run type-tests

dts-changes:
executor: node16-executor
steps:
Expand Down Expand Up @@ -334,6 +341,10 @@ workflows:
filters: *default-filters
requires:
- build
- type-tests:
filters: *default-filters
requires:
- build
- dts-changes:
filters: *merge-based-filters
requires:
Expand Down
2 changes: 1 addition & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ function getNamingConventionRules(additionalDefaultFormats = []) {
format: ['PascalCase'],
filter: {
match: true,
regex: '^(Area|Baseline|Bar|Candlestick|Histogram|Line)$',
regex: '^(Area|Baseline|Bar|Candlestick|Histogram|Line|Custom)$',
},
},
];
Expand Down
8 changes: 4 additions & 4 deletions .size-limit.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,21 @@ module.exports = [
{
name: 'CJS',
path: 'dist/lightweight-charts.production.cjs',
limit: '44.46 KB',
limit: '46.83 KB',
},
{
name: 'ESM',
path: 'dist/lightweight-charts.production.mjs',
limit: '44.38 KB',
limit: '46.74 KB',
},
{
name: 'Standalone-ESM',
path: 'dist/lightweight-charts.standalone.production.mjs',
limit: '46.08 KB',
limit: '48.45 KB',
},
{
name: 'Standalone',
path: 'dist/lightweight-charts.standalone.production.js',
limit: '46.13 KB',
limit: '48.50 KB',
},
];
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,8 @@
"prepare-release": "npm-run-all clean build:release && npm run prepare-package-json-for-release",
"prepare-package-json-for-release": "node ./scripts/clean-package-json.js",
"size-limit": "size-limit",
"verify": "npm-run-all clean -p build:prod check-markdown-links -p lint check-dts-docs tsc-verify test size-limit",
"test": "mocha tests/unittests/**/*.spec.ts"
"verify": "npm-run-all clean -p build:prod check-markdown-links -p lint check-dts-docs tsc-verify test size-limit -p type-tests",
"test": "mocha tests/unittests/**/*.spec.ts",
"type-tests": "tsc -p ./tests/type-checks/tsconfig.composite.json --noEmit"
}
}
60 changes: 51 additions & 9 deletions src/api/chart-api.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,36 @@
import { ChartWidget, MouseEventParamsImpl, MouseEventParamsImplSupplier } from '../gui/chart-widget';

import { assert, ensureDefined } from '../helpers/assertions';
import { assert, ensure, ensureDefined } from '../helpers/assertions';
import { Delegate } from '../helpers/delegate';
import { warn } from '../helpers/logger';
import { clone, DeepPartial, isBoolean, merge } from '../helpers/strict-type-checks';

import { ChartOptions, ChartOptionsInternal } from '../model/chart-model';
import { CustomData, ICustomSeriesPaneView } from '../model/icustom-series';
import { Series } from '../model/series';
import { SeriesPlotRow } from '../model/series-data';
import {
AreaSeriesPartialOptions,
BarSeriesPartialOptions,
BaselineSeriesPartialOptions,
CandlestickSeriesPartialOptions,
CustomSeriesOptions,
CustomSeriesPartialOptions,
fillUpDownCandlesticksColors,
HistogramSeriesPartialOptions,
LineSeriesPartialOptions,
precisionByMinMove,
PriceFormat,
PriceFormatBuiltIn,
SeriesOptionsMap,
SeriesPartialOptions,
SeriesPartialOptionsMap,
SeriesStyleOptionsMap,
SeriesType,
} from '../model/series-options';
import { Logical, Time } from '../model/time-data';

import { DataUpdatesConsumer, isFulfilledData, SeriesDataItemTypeMap } from './data-consumer';
import { DataUpdatesConsumer, isFulfilledData, SeriesDataItemTypeMap, WhitespaceData } from './data-consumer';
import { DataLayer, DataUpdateResponse, SeriesChanges } from './data-layer';
import { getSeriesDataCreator } from './get-series-data-creator';
import { IChartApi, MouseEventHandler, MouseEventParams } from './ichart-api';
Expand All @@ -39,6 +43,7 @@ import {
barStyleDefaults,
baselineStyleDefaults,
candlestickStyleDefaults,
customStyleDefaults,
histogramStyleDefaults,
lineStyleDefaults,
seriesOptionsDefaults,
Expand Down Expand Up @@ -171,6 +176,27 @@ export class ChartApi implements IChartApi, DataUpdatesConsumer<SeriesType> {
this._chartWidget.resize(width, height, forceRepaint);
}

public addCustomSeries<
TData extends CustomData,
TOptions extends CustomSeriesOptions,
TPartialOptions extends CustomSeriesPartialOptions = SeriesPartialOptions<TOptions>
>(
customPaneView: ICustomSeriesPaneView<TData, TOptions>,
options?: SeriesPartialOptions<TOptions>
): ISeriesApi<'Custom', TData, TOptions, TPartialOptions> {
const paneView = ensure(customPaneView);
const defaults = {
...customStyleDefaults,
...paneView.defaultOptions(),
};
return this._addSeriesImpl<'Custom', TData, TOptions, TPartialOptions>(
'Custom',
defaults,
options,
paneView
);
}

public addAreaSeries(options?: AreaSeriesPartialOptions): ISeriesApi<'Area'> {
return this._addSeriesImpl('Area', areaStyleDefaults, options);
}
Expand Down Expand Up @@ -258,17 +284,27 @@ export class ChartApi implements IChartApi, DataUpdatesConsumer<SeriesType> {
return this._chartWidget.autoSizeActive();
}

private _addSeriesImpl<TSeries extends SeriesType>(
public chartElement(): HTMLDivElement {
return this._chartWidget.element();
}

private _addSeriesImpl<
TSeries extends SeriesType,
TData extends WhitespaceData = SeriesDataItemTypeMap[TSeries],
TOptions extends SeriesOptionsMap[TSeries] = SeriesOptionsMap[TSeries],
TPartialOptions extends SeriesPartialOptionsMap[TSeries] = SeriesPartialOptionsMap[TSeries]
>(
type: TSeries,
styleDefaults: SeriesStyleOptionsMap[TSeries],
options: SeriesPartialOptionsMap[TSeries] = {}
): ISeriesApi<TSeries> {
options: SeriesPartialOptionsMap[TSeries] = {},
customPaneView?: ICustomSeriesPaneView
): ISeriesApi<TSeries, TData, TOptions, TPartialOptions> {
patchPriceFormat(options.priceFormat);

const strictOptions = merge(clone(seriesOptionsDefaults), clone(styleDefaults), options) as SeriesOptionsMap[TSeries];
const series = this._chartWidget.model().createSeries(type, strictOptions);
const series = this._chartWidget.model().createSeries(type, strictOptions, customPaneView);

const res = new SeriesApi<TSeries>(series, this, this);
const res = new SeriesApi<TSeries, TData, TOptions, TPartialOptions>(series, this, this, this);
this._seriesMap.set(res, series);
this._seriesMapReversed.set(series, res);

Expand All @@ -291,8 +327,14 @@ export class ChartApi implements IChartApi, DataUpdatesConsumer<SeriesType> {
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));
const seriesType = series.seriesType();
const data = getSeriesDataCreator(seriesType)(plotRow);
if (seriesType !== 'Custom') {
assert(isFulfilledData(data));
} else {
const customWhitespaceChecker = series.customSeriesWhitespaceCheck();
assert(!customWhitespaceChecker || customWhitespaceChecker(data) === false);
}
seriesData.set(this._mapSeriesToApi(series), data);
});

Expand Down
25 changes: 13 additions & 12 deletions src/api/data-consumer.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { isNumber, isString } from '../helpers/strict-type-checks';

import { CustomData, CustomSeriesWhitespaceData } from '../model/icustom-series';
import { Series } from '../model/series';
import { SeriesType } from '../model/series-options';
import { BusinessDay, Time, UTCTimestamp } from '../model/time-data';
Expand Down Expand Up @@ -45,17 +46,18 @@ export interface WhitespaceData {
* The time of the data.
*/
time: Time;

/**
* Additional custom values which will be ignored by the library, but
* could be used by plugins.
*/
customValues?: Record<string, unknown>;
}

/**
* A base interface for a data point of single-value series.
*/
export interface SingleValueData {
/**
* The time of the data.
*/
time: Time;

export interface SingleValueData extends WhitespaceData {
/**
* Price value of the data.
*/
Expand Down Expand Up @@ -140,12 +142,7 @@ export interface BaselineData extends SingleValueData {
/**
* Represents a bar with a {@link Time} and open, high, low, and close prices.
*/
export interface OhlcData {
/**
* The bar time.
*/
time: Time;

export interface OhlcData extends WhitespaceData {
/**
* The open price.
*/
Expand Down Expand Up @@ -235,6 +232,10 @@ export interface SeriesDataItemTypeMap {
* The types of histogram series data.
*/
Histogram: HistogramData | WhitespaceData;
/**
* The base types of an custom series data.
*/
Custom: CustomData | CustomSeriesWhitespaceData;
}

export interface DataUpdatesConsumer<TSeriesType extends SeriesType> {
Expand Down
27 changes: 9 additions & 18 deletions src/api/data-layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,17 +207,6 @@ function timeScalePointTime(mergedPointData: Map<Series, SeriesPlotRow | Whitesp
return ensureDefined(result);
}

function saveOriginalTime<TSeriesType extends SeriesType>(data: SeriesDataItemWithOriginalTime<TSeriesType>): void {
// eslint-disable-next-line @typescript-eslint/tslint/config
if (data.originalTime === undefined) {
data.originalTime = data.time as unknown as OriginalTime;
}
}

type SeriesDataItemWithOriginalTime<TSeriesType extends SeriesType> = SeriesDataItemTypeMap[TSeriesType] & {
originalTime: OriginalTime;
};

export class DataLayer {
// note that _pointDataByTimePoint and _seriesRowsBySeries shares THE SAME objects in their values between each other
// it's just different kind of maps to make usages/perf better
Expand Down Expand Up @@ -263,15 +252,16 @@ export class DataLayer {
let seriesRows: (SeriesPlotRow<TSeriesType> | WhitespacePlotRow)[] = [];

if (data.length !== 0) {
const extendedData = data as SeriesDataItemWithOriginalTime<TSeriesType>[];
extendedData.forEach((i: SeriesDataItemWithOriginalTime<TSeriesType>) => saveOriginalTime(i));
const originalTimes = data.map((d: SeriesDataItemTypeMap[TSeriesType]) => d.time as unknown as OriginalTime);

convertStringsToBusinessDays(data);

const timeConverter = ensureNotNull(selectTimeConverter(data));
const createPlotRow = getSeriesPlotRowCreator(series.seriesType());
const dataToPlotRow = series.customSeriesPlotValuesBuilder();
const customWhitespaceChecker = series.customSeriesWhitespaceCheck();

seriesRows = extendedData.map((item: SeriesDataItemWithOriginalTime<TSeriesType>) => {
seriesRows = data.map((item: SeriesDataItemTypeMap[TSeriesType], index: number) => {
const time = timeConverter(item.time);

let timePointData = this._pointDataByTimePoint.get(time.timestamp);
Expand All @@ -282,7 +272,7 @@ export class DataLayer {
isTimeScaleAffected = true;
}

const row = createPlotRow(time, timePointData.index, item, item.originalTime);
const row = createPlotRow(time, timePointData.index, item, originalTimes[index], dataToPlotRow, customWhitespaceChecker);
timePointData.mapping.set(series, row);
return row;
});
Expand Down Expand Up @@ -327,8 +317,7 @@ export class DataLayer {
}

public updateSeriesData<TSeriesType extends SeriesType>(series: Series<TSeriesType>, data: SeriesDataItemTypeMap[TSeriesType]): DataUpdateResponse {
const extendedData = data as SeriesDataItemWithOriginalTime<TSeriesType>;
saveOriginalTime(extendedData);
const originalTime = data.time as unknown as OriginalTime;
convertStringToBusinessDay(data);

const time = ensureNotNull(selectTimeConverter([data]))(data.time);
Expand All @@ -351,7 +340,9 @@ export class DataLayer {
}

const createPlotRow = getSeriesPlotRowCreator(series.seriesType());
const plotRow = createPlotRow(time, pointDataAtTime.index, data, extendedData.originalTime);
const dataToPlotRow = series.customSeriesPlotValuesBuilder();
const customWhitespaceChecker = series.customSeriesWhitespaceCheck();
const plotRow = createPlotRow(time, pointDataAtTime.index, data, originalTime, dataToPlotRow, customWhitespaceChecker);
pointDataAtTime.mapping.set(series, plotRow);

this._updateLastSeriesRow(series, plotRow);
Expand Down
13 changes: 12 additions & 1 deletion src/api/data-validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export function checkSeriesValuesType(type: SeriesType, data: readonly SeriesDat

type Checker = (item: SeriesDataItemTypeMap[SeriesType]) => void;

function getChecker(type: SeriesType): Checker {
export function getChecker(type: SeriesType): Checker {
switch (type) {
case 'Bar':
case 'Candlestick':
Expand All @@ -56,6 +56,9 @@ function getChecker(type: SeriesType): Checker {
case 'Line':
case 'Histogram':
return checkLineItem.bind(null, type);

case 'Custom':
return checkCustomItem.bind(null, type);
}
}

Expand Down Expand Up @@ -113,3 +116,11 @@ function checkLineItem(
}`
);
}

function checkCustomItem(
// type: 'Custom',
// customItem: SeriesDataItemTypeMap[typeof type]
): void {
// Nothing to check yet...
return;
}
Loading

0 comments on commit 38d6040

Please sign in to comment.