Skip to content

Commit 45841af

Browse files
authored
Merge pull request #1956 from tpunt/plugin-api-improvements
Plugin API improvements
2 parents 2051c30 + c0cd4c9 commit 45841af

File tree

10 files changed

+154
-16
lines changed

10 files changed

+154
-16
lines changed

src/api/iseries-api.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { BarPrice } from '../model/bar';
44
import { Coordinate } from '../model/coordinate';
55
import { SeriesDataItemTypeMap } from '../model/data-consumer';
66
import { Time } from '../model/horz-scale-behavior-time/types';
7+
import { LastValueDataResult } from '../model/iseries';
78
import { MismatchDirection } from '../model/plot-list';
89
import { CreatePriceLineOptions } from '../model/price-line-options';
910
import {
@@ -303,6 +304,22 @@ export interface ISeriesApi<
303304
*/
304305
seriesType(): TSeriesType;
305306

307+
/**
308+
* Return the last value data of the series.
309+
*
310+
* @param globalLast - If false, get the last value in the current visible range. Otherwise, fetch the absolute last value
311+
* @returns The last value data of the series.
312+
* @example
313+
* ```js
314+
* const lineSeries = chart.addSeries(LineSeries);
315+
* console.log(lineSeries.lastValueData(true)); // { noData: false, price: 24.11, color: '#000000' }
316+
*
317+
* const candlestickSeries = chart.addCandlestickSeries();
318+
* console.log(candlestickSeries.lastValueData(false)); // { noData: false, price: 145.72, color: '#000000' }
319+
* ```
320+
*/
321+
lastValueData(globalLast: boolean): LastValueDataResult;
322+
306323
/**
307324
* Attaches additional drawing primitive to the series
308325
*

src/api/series-api.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { CustomPriceLine } from '../model/custom-price-line';
1111
import { DataUpdatesConsumer, SeriesDataItemTypeMap, WhitespaceData } from '../model/data-consumer';
1212
import { checkItemsAreOrdered, checkPriceLineOptions, checkSeriesValuesType } from '../model/data-validators';
1313
import { IHorzScaleBehavior } from '../model/ihorz-scale-behavior';
14+
import { LastValueDataResult } from '../model/iseries';
1415
import { ISeriesPrimitiveBase } from '../model/iseries-primitive';
1516
import { Pane } from '../model/pane';
1617
import { MismatchDirection } from '../model/plot-list';
@@ -229,6 +230,22 @@ export class SeriesApi<
229230
return this._series.seriesType();
230231
}
231232

233+
public lastValueData(globalLast: boolean): LastValueDataResult {
234+
const result = this._series.lastValueData(globalLast);
235+
236+
if (result.noData) {
237+
return {
238+
noData: true,
239+
};
240+
}
241+
242+
return {
243+
noData: false,
244+
price: result.price,
245+
color: result.color,
246+
};
247+
}
248+
232249
public attachPrimitive(primitive: ISeriesPrimitive<HorzScaleItem>): void {
233250
// at this point we cast the generic to unknown because we
234251
// don't want the model to know the types of the API (◑_◑)

src/model/ipane-primitive.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { CanvasRenderingTarget2D } from 'fancy-canvas';
22

3+
import { LineStyle } from '../renderers/draw-line';
4+
35
/**
46
* This interface represents rendering some element on the canvas
57
*/
@@ -8,18 +10,20 @@ export interface IPrimitivePaneRenderer {
810
* Method to draw main content of the element
911
*
1012
* @param target - canvas context to draw on, refer to FancyCanvas library for more details about this class
13+
* @param utils - exposes drawing utilities (such as setLineStyle) from the library to plugins
1114
*
1215
*/
13-
draw(target: CanvasRenderingTarget2D): void;
16+
draw(target: CanvasRenderingTarget2D, utils?: DrawingUtils): void;
1417

1518
/**
1619
* Optional method to draw the background.
1720
* Some elements could implement this method to draw on the background of the chart.
1821
* Usually this is some kind of watermarks or time areas highlighting.
1922
*
2023
* @param target - canvas context to draw on, refer FancyCanvas library for more details about this class
24+
* @param utils - exposes drawing utilities (such as setLineStyle) from the library to plugins
2125
*/
22-
drawBackground?(target: CanvasRenderingTarget2D): void;
26+
drawBackground?(target: CanvasRenderingTarget2D, utils?: DrawingUtils): void;
2327
}
2428

2529
/**
@@ -141,3 +145,16 @@ export interface IPanePrimitiveBase<TPaneAttachedParameters = unknown> {
141145
*/
142146
hitTest?(x: number, y: number): PrimitiveHoveredItem | null;
143147
}
148+
149+
/**
150+
* Helper drawing utilities exposed by the library to a Primitive (a.k.a plugin).
151+
*/
152+
export interface DrawingUtils {
153+
/**
154+
* Drawing utility to change the line style on the canvas context to one of the
155+
* built-in line styles.
156+
* @param ctx - 2D rendering context for the target canvas.
157+
* @param lineStyle - Built-in {@link LineStyle} to set on the canvas context.
158+
*/
159+
readonly setLineStyle: (ctx: CanvasRenderingContext2D, lineStyle: LineStyle) => void;
160+
}

src/model/iseries.ts

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ import { SeriesPlotList } from './series-data';
77
import { SeriesOptionsMap, SeriesType } from './series-options';
88
import { TimePointIndex } from './time-data';
99

10-
export interface LastValueDataResultWithoutData {
10+
export interface LastValueDataInternalResultWithoutData {
1111
noData: true;
1212
}
13-
export interface LastValueDataResultWithData {
13+
export interface LastValueDataInternalResultWithData {
1414
noData: false;
1515

1616
price: number;
@@ -22,7 +22,34 @@ export interface LastValueDataResultWithData {
2222
index: TimePointIndex;
2323
}
2424

25-
export type LastValueDataResult = LastValueDataResultWithoutData | LastValueDataResultWithData;
25+
export type LastValueDataInternalResult = LastValueDataInternalResultWithoutData | LastValueDataInternalResultWithData;
26+
27+
/** Represents last value data result of a series for plugins when there is no data */
28+
export interface LastValueDataResultWithoutData {
29+
/**
30+
* Indicates if the series has data.
31+
*/
32+
noData: true;
33+
}
34+
35+
/** Represents last value data result of a series for plugins when there is data */
36+
export interface LastValueDataResultWithData {
37+
/**
38+
* Indicates if the series has data.
39+
*/
40+
noData: false;
41+
/**
42+
* The last price of the series.
43+
*/
44+
price: number;
45+
/**
46+
* The color of the last value.
47+
*/
48+
color: string;
49+
}
50+
51+
/** Represents last value data result of a series for plugins */
52+
export type LastValueDataResult = LastValueDataResultWithData | LastValueDataResultWithoutData;
2653

2754
export interface MarkerData {
2855
price: BarPrice;
@@ -48,7 +75,7 @@ export interface ISeries<T extends SeriesType> extends IPriceDataSource {
4875
options(): Readonly<SeriesOptionsMap[T]>;
4976
title(): string;
5077
priceScale(): PriceScale;
51-
lastValueData(globalLast: boolean): LastValueDataResult;
78+
lastValueData(globalLast: boolean): LastValueDataInternalResult;
5279
barColorer(): ISeriesBarColorer<T>;
5380
markerDataAtIndex(index: TimePointIndex): MarkerData | null;
5481
dataAt(time: TimePointIndex): SeriesDataAtTypeMap[SeriesType] | null;

src/model/pane-primitive-wrapper.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
import {
1515
ISeriesPrimitiveBase,
1616
} from './iseries-primitive';
17+
import { drawingUtils } from './primitive-drawing-utils';
1718

1819
class PrimitiveRendererWrapper implements IPaneRenderer {
1920
private readonly _baseRenderer: IPrimitivePaneRenderer;
@@ -23,11 +24,11 @@ class PrimitiveRendererWrapper implements IPaneRenderer {
2324
}
2425

2526
public draw(target: CanvasRenderingTarget2D, isHovered: boolean, hitTestData?: unknown): void {
26-
this._baseRenderer.draw(target);
27+
this._baseRenderer.draw(target, drawingUtils);
2728
}
2829

2930
public drawBackground?(target: CanvasRenderingTarget2D, isHovered: boolean, hitTestData?: unknown): void {
30-
this._baseRenderer.drawBackground?.(target);
31+
this._baseRenderer.drawBackground?.(target, drawingUtils);
3132
}
3233
}
3334

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { setLineStyle } from '../renderers/draw-line';
2+
3+
import { DrawingUtils } from './ipane-primitive';
4+
5+
export const drawingUtils: DrawingUtils = {
6+
setLineStyle: setLineStyle,
7+
};

src/model/series-primitive-wrapper.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
} from './iseries-primitive';
1616
import { PrimitiveWrapper } from './pane-primitive-wrapper';
1717
import { PriceScale } from './price-scale';
18+
import { drawingUtils } from './primitive-drawing-utils';
1819
import { Series } from './series';
1920
import { AutoscaleInfo, SeriesType } from './series-options';
2021
import { Logical, TimePointIndex } from './time-data';
@@ -28,11 +29,11 @@ class SeriesPrimitiveRendererWrapper implements IPaneRenderer {
2829
}
2930

3031
public draw(target: CanvasRenderingTarget2D, isHovered: boolean, hitTestData?: unknown): void {
31-
this._baseRenderer.draw(target);
32+
this._baseRenderer.draw(target, drawingUtils);
3233
}
3334

3435
public drawBackground?(target: CanvasRenderingTarget2D, isHovered: boolean, hitTestData?: unknown): void {
35-
this._baseRenderer.drawBackground?.(target);
36+
this._baseRenderer.drawBackground?.(target, drawingUtils);
3637
}
3738
}
3839

src/model/series.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import { isDefaultPriceScale } from './default-price-scale';
2626
import { CustomData, CustomSeriesWhitespaceData, ICustomSeriesPaneView, WhitespaceCheck } from './icustom-series';
2727
import { PrimitiveHoveredItem, PrimitivePaneViewZOrder } from './ipane-primitive';
2828
import { FirstValue } from './iprice-data-source';
29-
import { ISeries, LastValueDataResult, LastValueDataResultWithoutData, MarkerData, SeriesDataAtTypeMap } from './iseries';
29+
import { ISeries, LastValueDataInternalResult, LastValueDataInternalResultWithoutData, MarkerData, SeriesDataAtTypeMap } from './iseries';
3030
import { ISeriesPrimitiveBase } from './iseries-primitive';
3131
import { Pane } from './pane';
3232
import { PlotRowValueIndex } from './plot-data';
@@ -140,8 +140,8 @@ export class Series<T extends SeriesType> extends PriceDataSource implements IDe
140140
return this._options.priceLineColor || lastBarColor;
141141
}
142142

143-
public lastValueData(globalLast: boolean): LastValueDataResult {
144-
const noDataRes: LastValueDataResultWithoutData = { noData: true };
143+
public lastValueData(globalLast: boolean): LastValueDataInternalResult {
144+
const noDataRes: LastValueDataInternalResultWithoutData = { noData: true };
145145

146146
const priceScale = this.priceScale();
147147

src/views/price-axis/series-price-axis-view.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ISeries, LastValueDataResultWithData } from '../../model/iseries';
1+
import { ISeries, LastValueDataInternalResultWithData } from '../../model/iseries';
22
import { PriceAxisLastValueMode, SeriesType } from '../../model/series-options';
33
import { PriceAxisViewRendererCommonData, PriceAxisViewRendererData } from '../../renderers/iprice-axis-view-renderer';
44

@@ -62,7 +62,7 @@ export class SeriesPriceAxisView extends PriceAxisView {
6262
}
6363

6464
protected _paneText(
65-
lastValue: LastValueDataResultWithData,
65+
lastValue: LastValueDataInternalResultWithData,
6666
showSeriesLastValue: boolean,
6767
showSymbolLabel: boolean,
6868
showPriceAndPercentage: boolean
@@ -83,7 +83,7 @@ export class SeriesPriceAxisView extends PriceAxisView {
8383
return result.trim();
8484
}
8585

86-
protected _axisText(lastValueData: LastValueDataResultWithData, showSeriesLastValue: boolean, showPriceAndPercentage: boolean): string {
86+
protected _axisText(lastValueData: LastValueDataInternalResultWithData, showSeriesLastValue: boolean, showPriceAndPercentage: boolean): string {
8787
if (!showSeriesLastValue) {
8888
return '';
8989
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
function runTestCase(container) {
2+
const chart = window.chart = LightweightCharts.createChart(container, { layout: { attributionLogo: false } });
3+
4+
const lineSeries = chart.addSeries(LightweightCharts.LineSeries);
5+
6+
lineSeries.setData([
7+
{ time: '1990-04-24', value: 0 },
8+
{ time: '1990-04-25', value: 1 },
9+
{ time: '1990-04-26', value: 2 },
10+
{ time: '1990-04-27', value: 3 },
11+
{ time: '1990-04-28', value: 4, color: 'green' },
12+
{ time: '1990-04-29', value: 5, color: 'red' },
13+
]);
14+
15+
chart.timeScale().fitContent();
16+
chart.timeScale().scrollToPosition(-1);
17+
18+
return new Promise(resolve => {
19+
requestAnimationFrame(() => {
20+
const lastValueInView = lineSeries.lastValueData(false);
21+
console.assert(
22+
lastValueInView.noData === false,
23+
'expected to find data for last value in view'
24+
);
25+
console.assert(
26+
lastValueInView.price === 4,
27+
`expected to find price 4 as last value in view, but found ${lastValueInView.price}`
28+
);
29+
console.assert(
30+
lastValueInView.color === 'green',
31+
`expected to find color green as last value in view, but found ${lastValueInView.color}`
32+
);
33+
34+
const lastValueGlobal = lineSeries.lastValueData(true);
35+
console.assert(
36+
lastValueGlobal.noData === false,
37+
'expected to find data for last value globally'
38+
);
39+
console.assert(
40+
lastValueGlobal.price === 5,
41+
`expected to find price 5 as last value globally, but found ${lastValueGlobal.price}`
42+
);
43+
console.assert(
44+
lastValueGlobal.color === 'red',
45+
`expected to find color red as last value globally, but found ${lastValueGlobal.color}`
46+
);
47+
48+
resolve();
49+
});
50+
});
51+
}

0 commit comments

Comments
 (0)