Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Yield Curve and Options Chart Types, Up/Down Markers Plugin #1674

Merged
merged 26 commits into from
Dec 9, 2024
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
2b4cad3
historicalUpdate for series.update
SlicedSilver Jul 31, 2024
a346d3d
add ignoreWhitespaceIndices option to time scale
SlicedSilver Jul 31, 2024
8689e91
add yield chart type
SlicedSilver Aug 1, 2024
1be5dc8
add up down markers primitive plugin
SlicedSilver Aug 4, 2024
a8bc394
add options chart type
SlicedSilver Aug 4, 2024
e164b1a
add chart types documentation page
SlicedSilver Aug 4, 2024
fb56e1e
change docs pages with examples to mdx
SlicedSilver Aug 4, 2024
7b3ea0b
fix chart code blocks for new version of docusaurus
SlicedSilver Aug 4, 2024
26042b9
add yield-curve-with-update-markers demo
SlicedSilver Aug 4, 2024
1695a1d
Merge branch 'v5-candidate' into curve-charts
SlicedSilver Aug 13, 2024
ca35c77
update types for up down markers plugin
SlicedSilver Aug 13, 2024
b866007
add back missing size-limit checks after branch merge
SlicedSilver Aug 13, 2024
0056e15
fix coverage tests
SlicedSilver Aug 13, 2024
05d7823
change name for SupportedSeriesTypes type
SlicedSilver Aug 13, 2024
566748e
Update historical-data-updates graphics test case
SlicedSilver Aug 13, 2024
327c3c8
Merge branch 'v5-candidate' into curve-charts
SlicedSilver Nov 11, 2024
a982d7e
fix broken documentation links
SlicedSilver Nov 11, 2024
03ead85
fix ignoreWhitespaces option after recent changes to v5
SlicedSilver Nov 13, 2024
7c5edd1
fix link within watermark tutorial article
SlicedSilver Nov 13, 2024
8e6c307
Merge branch 'v5-candidate' into curve-charts
SlicedSilver Dec 9, 2024
135dfe7
Update yield curves code for tree-shaken series
SlicedSilver Dec 9, 2024
4ebcc11
Add horzBehaviour getter on the Chart Api
SlicedSilver Dec 9, 2024
8b2a7fd
Add tip for yield curve spacing
SlicedSilver Dec 9, 2024
ae602f7
Update e2e tests and framework examples for tree-shaken series
SlicedSilver Dec 9, 2024
22c1ba6
Move up down markers into a wrapper
SlicedSilver Dec 9, 2024
56b063b
Fix docs link for yield curve example page
SlicedSilver Dec 9, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 26 additions & 2 deletions .size-limit.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export default [
{
name: 'ESM',
path: 'dist/lightweight-charts.production.mjs',
limit: '45.00 KB',
limit: '47.00 KB',
import: '*',
ignore: ['fancy-canvas'],
brotli: true,
Expand All @@ -30,7 +30,23 @@ export default [
brotli: true,
},
{
name: 'ESM Standalone',
name: 'ESM createYieldCurveChart',
path: 'dist/lightweight-charts.production.mjs',
limit: '45.00 KB',
import: '{ createYieldCurveChart }',
ignore: ['fancy-canvas'],
brotli: true,
},
{
name: 'ESM createOptionsChart',
path: 'dist/lightweight-charts.production.mjs',
limit: '45.00 KB',
import: '{ createOptionsChart }',
ignore: ['fancy-canvas'],
brotli: true,
},
{
name: 'Standalone-ESM',
path: 'dist/lightweight-charts.standalone.production.mjs',
limit: '50.00 KB',
import: '*',
Expand Down Expand Up @@ -58,4 +74,12 @@ export default [
limit: '2.00 KB',
brotli: true,
},
{
name: 'Plugin: UpDownMarkersPrimitive',
path: 'dist/lightweight-charts.production.mjs',
limit: '2.50 KB',
import: '{ UpDownMarkersPrimitive }',
ignore: ['fancy-canvas'],
brotli: true,
},
];
7 changes: 4 additions & 3 deletions src/api/chart-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ function toInternalOptions<HorzScaleItem>(options: DeepPartial<ChartOptionsImpl<
export type IPriceScaleApiProvider<HorzScaleItem> = Pick<IChartApiBase<HorzScaleItem>, 'priceScale'>;

export class ChartApi<HorzScaleItem> implements IChartApiBase<HorzScaleItem>, DataUpdatesConsumer<SeriesType, HorzScaleItem> {
protected readonly _horzScaleBehavior: IHorzScaleBehavior<HorzScaleItem>;

private _chartWidget: ChartWidget<HorzScaleItem>;
private _dataLayer: DataLayer<HorzScaleItem>;
private readonly _seriesMap: Map<SeriesApi<SeriesType, HorzScaleItem>, Series<SeriesType>> = new Map();
Expand All @@ -128,7 +130,6 @@ export class ChartApi<HorzScaleItem> implements IChartApiBase<HorzScaleItem>, Da

private readonly _timeScaleApi: TimeScaleApi<HorzScaleItem>;
private readonly _panes: WeakMap<Pane, PaneApi<HorzScaleItem>> = new WeakMap();
private readonly _horzScaleBehavior: IHorzScaleBehavior<HorzScaleItem>;

public constructor(container: HTMLElement, horzScaleBehavior: IHorzScaleBehavior<HorzScaleItem>, options?: DeepPartial<ChartOptionsImpl<HorzScaleItem>>) {
this._dataLayer = new DataLayer<HorzScaleItem>(horzScaleBehavior);
Expand Down Expand Up @@ -261,8 +262,8 @@ export class ChartApi<HorzScaleItem> implements IChartApiBase<HorzScaleItem>, Da
this._sendUpdateToChart(this._dataLayer.setSeriesData(series, data));
}

public updateData<TSeriesType extends SeriesType>(series: Series<TSeriesType>, data: SeriesDataItemTypeMap<HorzScaleItem>[TSeriesType]): void {
this._sendUpdateToChart(this._dataLayer.updateSeriesData(series, data));
public updateData<TSeriesType extends SeriesType>(series: Series<TSeriesType>, data: SeriesDataItemTypeMap<HorzScaleItem>[TSeriesType], historicalUpdate: boolean): void {
this._sendUpdateToChart(this._dataLayer.updateSeriesData(series, data, historicalUpdate));
}

public subscribeClick(handler: MouseEventHandler<HorzScaleItem>): void {
Expand Down
19 changes: 10 additions & 9 deletions src/api/create-chart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@ import { IHorzScaleBehavior } from '../model/ihorz-scale-behavior';
import { ChartApi } from './chart-api';
import { IChartApiBase } from './ichart-api';

export function fetchHtmlElement(container: string | HTMLElement): HTMLElement {
if (isString(container)) {
const element = document.getElementById(container);
assert(element !== null, `Cannot find element in DOM with id=${container}`);
return element;
}
return container;
}

/**
* This function is the main entry point of the Lightweight Charting Library. If you are using time values
* for the horizontal scale then it is recommended that you rather use the {@link createChart} function.
Expand All @@ -26,15 +35,7 @@ export function createChartEx<HorzScaleItem, THorzScaleBehavior extends IHorzSca
horzScaleBehavior: THorzScaleBehavior,
options?: DeepPartial<ReturnType<THorzScaleBehavior['options']>>
): IChartApiBase<HorzScaleItem> {
let htmlElement: HTMLElement;
if (isString(container)) {
const element = document.getElementById(container);
assert(element !== null, `Cannot find element in DOM with id=${container}`);
htmlElement = element;
} else {
htmlElement = container;
}

const htmlElement = fetchHtmlElement(container);
const res = new ChartApi<HorzScaleItem>(htmlElement, horzScaleBehavior, options);
horzScaleBehavior.setOptions(res.options());
return res;
Expand Down
25 changes: 25 additions & 0 deletions src/api/create-options-chart.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { DeepPartial } from '../helpers/strict-type-checks';

import { HorzScaleBehaviorPrice } from '../model/horz-scale-behavior-price/horz-scale-behaviour-price';
import { PriceChartOptions } from '../model/horz-scale-behavior-price/options';

import { createChartEx } from './create-chart';
import { IChartApiBase } from './ichart-api';

/**
* Creates an 'options' chart with price values on the horizontal scale.
*
* This function is used to create a specialized chart type where the horizontal scale
* represents price values instead of time. It's particularly useful for visualizing
* option chains, price distributions, or any data where price is the primary x-axis metric.
*
* @param container - The DOM element or its id where the chart will be rendered.
* @param options - Optional configuration options for the price chart.
* @returns An instance of IChartApiBase configured for price-based horizontal scaling.
*/
export function createOptionsChart(
container: string | HTMLElement,
options?: DeepPartial<PriceChartOptions>
): IChartApiBase<number> {
return createChartEx(container, new HorzScaleBehaviorPrice(), options);
}
31 changes: 31 additions & 0 deletions src/api/create-yield-curve-chart.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { DeepPartial } from '../helpers/strict-type-checks';

import {
YieldCurveChartOptions,
} from '../model/yield-curve-horz-scale-behavior/yield-curve-chart-options';

import { fetchHtmlElement } from './create-chart';
import { IYieldCurveChartApi } from './iyield-chart-api';
import { YieldChartApi } from './yield-chart-api';

/**
* Creates a yield curve chart with the specified options.
*
* A yield curve chart differs from the default chart type
* in the following ways:
* - Horizontal scale is linearly spaced, and defined in monthly
* time duration units
* - Whitespace is ignored for the crosshair and grid lines
*
* @param container - ID of HTML element or element itself
* @param options - The yield chart options.
* @returns An interface to the created chart
*/
export function createYieldCurveChart(
container: string | HTMLElement,
options?: DeepPartial<YieldCurveChartOptions>
): IYieldCurveChartApi {
const htmlElement = fetchHtmlElement(container);
const chartApi = new YieldChartApi(htmlElement, options);
return chartApi;
}
4 changes: 3 additions & 1 deletion src/api/iseries-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@ export interface ISeriesApi<
*
* @param bar - A single data item to be added. Time of the new item must be greater or equal to the latest existing time point.
* If the new item's time is equal to the last existing item's time, then the existing item is replaced with the new one.
* @param historicalUpdate - If true, allows updating an existing data point that is not the latest bar. Default is false.
* Updating older data using `historicalUpdate` will be slower than updating the most recent data point.
* @example Updating line series data
* ```js
* lineSeries.update({
Expand All @@ -176,7 +178,7 @@ export interface ISeriesApi<
* });
* ```
*/
update(bar: TData): void;
update(bar: TData, historicalUpdate?: boolean): void;

/**
* Returns a bar data by provided logical index.
Expand Down
5 changes: 5 additions & 0 deletions src/api/iseries-primitive-api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Time } from '../model/horz-scale-behavior-time/types';
import { IHorzScaleBehavior } from '../model/ihorz-scale-behavior';
import { ISeriesPrimitiveBase } from '../model/iseries-primitive';
import { SeriesOptionsMap, SeriesType } from '../model/series-options';

Expand All @@ -25,6 +26,10 @@ export interface SeriesAttachedParameter<
* Request an update (redraw the chart)
*/
requestUpdate: () => void;
/**
* Horizontal Scale Behaviour for the chart.
*/
horzScaleBehavior: IHorzScaleBehavior<HorzScaleItem>;
}

/**
Expand Down
6 changes: 6 additions & 0 deletions src/api/iyield-chart-api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { IChartApiBase } from './ichart-api';

/**
* The main interface of a single yield curve chart.
*/
export interface IYieldCurveChartApi extends IChartApiBase<number> {}
1 change: 1 addition & 0 deletions src/api/options/time-scale-options-defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ export const timeScaleOptionsDefaults: HorzScaleOptions = {
uniformDistribution: false,
minimumHeight: 0,
allowBoldLabels: true,
ignoreWhitespaceIndices: false,
};
7 changes: 7 additions & 0 deletions src/api/options/yield-curve-chart-options-defaults.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { YieldCurveOptions } from '../../model/yield-curve-horz-scale-behavior/yield-curve-chart-options';

export const yieldChartOptionsDefaults: YieldCurveOptions = {
baseResolution: 1,
minimumTimeRange: 120,
startTimeRange: 0,
};
5 changes: 3 additions & 2 deletions src/api/series-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,10 +155,10 @@ export class SeriesApi<
this._onDataChanged('full');
}

public update(bar: TData): void {
public update(bar: TData, historicalUpdate: boolean = false): void {
checkSeriesValuesType(this._series.seriesType(), [bar]);

this._dataUpdatesConsumer.updateData(this._series, bar);
this._dataUpdatesConsumer.updateData(this._series, bar, historicalUpdate);
this._onDataChanged('update');
}

Expand Down Expand Up @@ -239,6 +239,7 @@ export class SeriesApi<
chart: this._chartApi,
series: this,
requestUpdate: () => this._series.model().fullUpdate(),
horzScaleBehavior: this._horzScaleBehavior,
});
}
}
Expand Down
128 changes: 128 additions & 0 deletions src/api/yield-chart-api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { DeepPartial, merge } from '../helpers/strict-type-checks';

import { AreaData, LineData, WhitespaceData } from '../model/data-consumer';
import {
AreaSeriesOptions,
AreaStyleOptions,
LineSeriesOptions,
LineStyleOptions,
SeriesOptionsCommon,
} from '../model/series-options';
import { YieldCurveChartOptions, YieldCurveOptions } from '../model/yield-curve-horz-scale-behavior/yield-curve-chart-options';
import { YieldCurveHorzScaleBehavior } from '../model/yield-curve-horz-scale-behavior/yield-curve-horz-scale-behavior';

import { ChartApi } from './chart-api';
import { ISeriesApi } from './iseries-api';
import { IYieldCurveChartApi } from './iyield-chart-api';
import { yieldChartOptionsDefaults } from './options/yield-curve-chart-options-defaults';

interface WhitespaceState {
start: number;
end: number;
resolution: number;
}

function generateWhitespaceData({
start,
end,
resolution,
}: WhitespaceState): WhitespaceData<number>[] {
return Array.from(
{ length: Math.floor((end - start) / resolution) + 1 },
// eslint-disable-next-line quote-props
(item: unknown, i: number) => ({ 'time': start + i * resolution })
);
}

function buildWhitespaceState(
options: YieldCurveOptions,
lastIndex: number
): WhitespaceState {
return {
start: Math.max(0, options.startTimeRange),
end: Math.max(0, options.minimumTimeRange, lastIndex || 0),
resolution: Math.max(1, options.baseResolution),
};
}

const generateWhitespaceHash = ({
start,
end,
resolution,
}: WhitespaceState): string => `${start}~${end}~${resolution}`;

const defaultOptions: DeepPartial<YieldCurveChartOptions> = {
yieldCurve: yieldChartOptionsDefaults,
// and add sensible default options for yield charts which
// are different from the usual defaults.
timeScale: {
ignoreWhitespaceIndices: true,
},
leftPriceScale: {
visible: true,
},
rightPriceScale: {
visible: false,
},
localization: {
priceFormatter: (value: number): string => {
return value.toFixed(3) + '%';
},
},
};

const lineStyleDefaultOptionOverrides: DeepPartial<LineStyleOptions & SeriesOptionsCommon & AreaStyleOptions> = {
lastValueVisible: false,
priceLineVisible: false,
};

export class YieldChartApi extends ChartApi<number> implements IYieldCurveChartApi {
public constructor(container: HTMLElement, options?: DeepPartial<YieldCurveChartOptions>) {
const fullOptions = merge(
defaultOptions,
options || {}
) as YieldCurveChartOptions;
const horzBehaviour = new YieldCurveHorzScaleBehavior();
super(container, horzBehaviour, fullOptions);
horzBehaviour.setOptions(this.options() as YieldCurveChartOptions);
this._initWhitespaceSeries();
}

public override addLineSeries(options?: DeepPartial<LineStyleOptions & SeriesOptionsCommon> | undefined, paneIndex?: number | undefined): ISeriesApi<'Line', number, WhitespaceData<number> | LineData<number>, LineSeriesOptions, DeepPartial<LineStyleOptions & SeriesOptionsCommon>> {
const optionOverrides = {
...lineStyleDefaultOptionOverrides,
...options,
};
return super.addLineSeries(optionOverrides, paneIndex);
}

public override addAreaSeries(options?: DeepPartial<AreaStyleOptions & SeriesOptionsCommon> | undefined, paneIndex?: number | undefined): ISeriesApi<'Area', number, AreaData<number> | WhitespaceData<number>, AreaSeriesOptions, DeepPartial<AreaStyleOptions & SeriesOptionsCommon>> {
const optionOverrides = {
...lineStyleDefaultOptionOverrides,
...options,
};
return super.addAreaSeries(optionOverrides, paneIndex);
}

private _initWhitespaceSeries(): void {
const horzBehaviour = this._horzScaleBehavior as YieldCurveHorzScaleBehavior;
const whiteSpaceSeries = this.addLineSeries();

let currentWhitespaceHash: string;
function updateWhitespace(lastIndex: number): void {
const newWhitespaceState = buildWhitespaceState(
horzBehaviour.options().yieldCurve,
lastIndex
);
const newWhitespaceHash = generateWhitespaceHash(newWhitespaceState);

if (newWhitespaceHash !== currentWhitespaceHash) {
currentWhitespaceHash = newWhitespaceHash;
whiteSpaceSeries.setData(generateWhitespaceData(newWhitespaceState));
}
}

updateWhitespace(0);
horzBehaviour.whitespaceInvalidated().subscribe(updateWhitespace);
}
}
4 changes: 4 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ export const customSeriesDefaultOptions: CustomSeriesOptions = {
};

export { createChart, createChartEx, defaultHorzScaleBehavior } from './api/create-chart';
export { createYieldCurveChart } from './api/create-yield-curve-chart';
export { createOptionsChart } from './api/create-options-chart';

export { UpDownMarkersPrimitive } from './plugins/up-down-markers-plugin/primitive';

/*
Plugins
Expand Down
Loading
Loading