From 5f89e78fe12e612ee775fa0b258e41b7e8aa9a43 Mon Sep 17 00:00:00 2001 From: Andrii Ovcharenko Date: Mon, 18 Nov 2024 11:35:54 +0100 Subject: [PATCH 1/9] add relative gradient option to the baseline series --- src/model/series-options.ts | 8 +++- src/model/series/baseline-pane-view.ts | 32 +++++++++++--- src/model/series/baseline-series.ts | 2 +- src/renderers/area-renderer.ts | 3 +- src/renderers/baseline-renderer-area.ts | 5 ++- src/renderers/baseline-renderer-line.ts | 5 ++- src/renderers/gradient-style-cache.ts | 27 ++++++++---- .../series/baseline-relative-gradient.js | 43 +++++++++++++++++++ 8 files changed, 104 insertions(+), 21 deletions(-) create mode 100644 tests/e2e/graphics/test-cases/series/baseline-relative-gradient.js diff --git a/src/model/series-options.ts b/src/model/series-options.ts index ea3fa9c211..09f75e6251 100644 --- a/src/model/series-options.ts +++ b/src/model/series-options.ts @@ -375,7 +375,13 @@ export interface BaselineStyleOptions { * @defaultValue `{ type: 'price', price: 0 }` */ baseValue: BaseValueType; - + /** + * Gradient is relative to the base value and the currently visible range. + * If it is false, the gradient is relative to the top and bottom of the chart. + * + * @defaultValue `false` + */ + relativeGradient: boolean; /** * The first color of the top area. * diff --git a/src/model/series/baseline-pane-view.ts b/src/model/series/baseline-pane-view.ts index b8b92ade17..4432e26969 100644 --- a/src/model/series/baseline-pane-view.ts +++ b/src/model/series/baseline-pane-view.ts @@ -33,34 +33,52 @@ export class SeriesBaselinePaneView extends LinePaneViewBase<'Baseline', Baselin } const options = this._series.options(); - const baseLevelCoordinate = this._series.priceScale().priceToCoordinate(options.baseValue.price, firstValue.value); const barWidth = this._model.timeScale().barSpacing(); + if (this._itemsVisibleRange === null || this._items.length === 0) { + return; + } + let topCoordinate; + let bottomCoordinate; + + if (options.relativeGradient) { + topCoordinate = this._items[this._itemsVisibleRange.from].y; + bottomCoordinate = this._items[this._itemsVisibleRange.from].y; + + for (let i = this._itemsVisibleRange.from; i < this._itemsVisibleRange.to; i++) { + const item = this._items[i]; + if (item.y < topCoordinate) { + topCoordinate = item.y; + } + if (item.y > bottomCoordinate) { + bottomCoordinate = item.y; + } + } + } + this._baselineAreaRenderer.setData({ items: this._items, - lineWidth: options.lineWidth, lineStyle: options.lineStyle, lineType: options.lineType, - baseLevelCoordinate, + topCoordinate, + bottomCoordinate, invertFilledArea: false, - visibleRange: this._itemsVisibleRange, barWidth, }); this._baselineLineRenderer.setData({ items: this._items, - lineWidth: options.lineWidth, lineStyle: options.lineStyle, lineType: options.lineVisible ? options.lineType : undefined, pointMarkersRadius: options.pointMarkersVisible ? (options.pointMarkersRadius || options.lineWidth / 2 + 2) : undefined, - baseLevelCoordinate, - + topCoordinate, + bottomCoordinate, visibleRange: this._itemsVisibleRange, barWidth, }); diff --git a/src/model/series/baseline-series.ts b/src/model/series/baseline-series.ts index 430e5cbf78..1b0dc05e14 100644 --- a/src/model/series/baseline-series.ts +++ b/src/model/series/baseline-series.ts @@ -12,7 +12,7 @@ export const baselineStyleDefaults: BaselineStyleOptions = { type: 'price', price: 0, }, - + relativeGradient: false, topFillColor1: 'rgba(38, 166, 154, 0.28)', topFillColor2: 'rgba(38, 166, 154, 0.05)', topLineColor: 'rgba(38, 166, 154, 1)', diff --git a/src/renderers/area-renderer.ts b/src/renderers/area-renderer.ts index 62f82eeda4..69e10c1975 100644 --- a/src/renderers/area-renderer.ts +++ b/src/renderers/area-renderer.ts @@ -21,7 +21,8 @@ export class PaneRendererArea extends PaneRendererAreaBase topColor2: '', bottomColor1: '', bottomColor2: item.bottomColor, - bottom: renderingScope.bitmapSize.height as Coordinate, + topCoordinate: 0 as Coordinate, + bottomCoordinate: renderingScope.bitmapSize.height as Coordinate, } ); } diff --git a/src/renderers/baseline-renderer-area.ts b/src/renderers/baseline-renderer-area.ts index 369bbc8ca0..84ca398123 100644 --- a/src/renderers/baseline-renderer-area.ts +++ b/src/renderers/baseline-renderer-area.ts @@ -8,6 +8,8 @@ import { GradientStyleCache } from './gradient-style-cache'; export type BaselineFillItem = AreaFillItemBase & BaselineFillColorerStyle; export interface PaneRendererBaselineData extends PaneRendererAreaDataBase { + topCoordinate?: Coordinate; + bottomCoordinate?: Coordinate; } export class PaneRendererBaselineArea extends PaneRendererAreaBase { private readonly _fillCache: GradientStyleCache = new GradientStyleCache(); @@ -23,8 +25,9 @@ export class PaneRendererBaselineArea extends PaneRendererAreaBase { baseLevelCoordinate: Coordinate; + topCoordinate?: Coordinate; + bottomCoordinate?: Coordinate; } export class PaneRendererBaselineLine extends PaneRendererLineBase { @@ -25,8 +27,9 @@ export class PaneRendererBaselineLine extends PaneRendererLineBase 4) { + target.topFillColor1 = 'red'; + target.topFillColor2 = 'rgba(255, 0, 0, 0)'; + } + + if ((i % 10) > 6) { + target.bottomFillColor1 = 'yellow'; + target.bottomFillColor2 = 'rgba(255, 255, 0, 0)'; + } + + if ((i % 10) > 5) { + target.topLineColor = 'blue'; + target.bottomLineColor = 'green'; + } +} + +function generateData() { + const res = []; + const time = new Date(Date.UTC(2018, 0, 1, 0, 0, 0, 0)); + for (let i = 0; i < 500; ++i) { + const item = { + time: time.getTime() / 1000, + }; + time.setUTCDate(time.getUTCDate() + 1); + + generateBar(i, item); + res.push(item); + } + return res; +} + +function runTestCase(container) { + const chart = window.chart = LightweightCharts.createChart(container, { layout: { attributionLogo: false } }); + + const mainSeries = chart.addSeries(LightweightCharts.BaselineSeries, { baseValue: { type: 'price', price: 88 }, relativeGradient: true }); + + mainSeries.setData(generateData()); +} From 178d9a2271ec3f07108142713c0c7f8eb9e40bf3 Mon Sep 17 00:00:00 2001 From: Andrii Ovcharenko Date: Mon, 18 Nov 2024 13:01:00 +0100 Subject: [PATCH 2/9] update size-limit --- .size-limit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.size-limit.js b/.size-limit.js index 29f97caba3..239e6da1f8 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -79,7 +79,7 @@ export default [ path: 'dist/lightweight-charts.production.mjs', import: '{ BaselineSeries }', ignore: ['fancy-canvas'], - limit: '3.2 KB', + limit: '3.3 KB', brotli: true, }, { From b5d78213a54e2cd6b64beb432c7d3fb25a7a9765 Mon Sep 17 00:00:00 2001 From: Andrii Ovcharenko Date: Thu, 21 Nov 2024 10:54:59 +0100 Subject: [PATCH 3/9] update Area series --- .size-limit.js | 4 ++-- src/model/series-options.ts | 8 +++++++ src/model/series/area-pane-view.ts | 15 ++++++++++++ src/model/series/area-series.ts | 1 + src/renderers/area-renderer.ts | 3 ++- .../series/area-relative-gradient-inverted.js | 24 +++++++++++++++++++ .../series/area-relative-gradient.js | 23 ++++++++++++++++++ 7 files changed, 75 insertions(+), 3 deletions(-) create mode 100644 tests/e2e/graphics/test-cases/series/area-relative-gradient-inverted.js create mode 100644 tests/e2e/graphics/test-cases/series/area-relative-gradient.js diff --git a/.size-limit.js b/.size-limit.js index 239e6da1f8..1cb59e3cfc 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -79,7 +79,7 @@ export default [ path: 'dist/lightweight-charts.production.mjs', import: '{ BaselineSeries }', ignore: ['fancy-canvas'], - limit: '3.3 KB', + limit: '4.00 KB', brotli: true, }, { @@ -87,7 +87,7 @@ export default [ path: 'dist/lightweight-charts.production.mjs', import: '{ AreaSeries }', ignore: ['fancy-canvas'], - limit: '3.2 KB', + limit: '4.00 KB', brotli: true, }, { diff --git a/src/model/series-options.ts b/src/model/series-options.ts index 09f75e6251..ccb809a413 100644 --- a/src/model/series-options.ts +++ b/src/model/series-options.ts @@ -251,6 +251,14 @@ export interface AreaStyleOptions { */ bottomColor: string; + /** + * Gradient is relative to the base value and the currently visible range. + * If it is false, the gradient is relative to the top and bottom of the chart. + * + * @defaultValue `false` + */ + relativeGradient: boolean; + /** * Invert the filled area. Fills the area above the line if set to true. * diff --git a/src/model/series/area-pane-view.ts b/src/model/series/area-pane-view.ts index 8a4e8a8552..edeacd9483 100644 --- a/src/model/series/area-pane-view.ts +++ b/src/model/series/area-pane-view.ts @@ -28,13 +28,28 @@ export class SeriesAreaPaneView extends LinePaneViewBase<'Area', AreaFillItem & protected _prepareRendererData(): void { const options = this._series.options(); + if (this._itemsVisibleRange === null || this._items.length === 0) { + return; + } + let topCoordinate; + if (options.relativeGradient) { + topCoordinate = this._items[this._itemsVisibleRange.from].y; + + for (let i = this._itemsVisibleRange.from; i < this._itemsVisibleRange.to; i++) { + const item = this._items[i]; + if (item.y < topCoordinate) { + topCoordinate = item.y; + } + } + } this._areaRenderer.setData({ lineType: options.lineType, items: this._items, lineStyle: options.lineStyle, lineWidth: options.lineWidth, baseLevelCoordinate: null, + topCoordinate, invertFilledArea: options.invertFilledArea, visibleRange: this._itemsVisibleRange, barWidth: this._model.timeScale().barSpacing(), diff --git a/src/model/series/area-series.ts b/src/model/series/area-series.ts index 7cf8d96587..f6dd72005e 100644 --- a/src/model/series/area-series.ts +++ b/src/model/series/area-series.ts @@ -11,6 +11,7 @@ export const areaStyleDefaults: AreaStyleOptions = { topColor: 'rgba( 46, 220, 135, 0.4)', bottomColor: 'rgba( 40, 221, 100, 0)', invertFilledArea: false, + relativeGradient: false, lineColor: '#33D778', lineStyle: LineStyle.Solid, lineWidth: 3, diff --git a/src/renderers/area-renderer.ts b/src/renderers/area-renderer.ts index 69e10c1975..8382963d71 100644 --- a/src/renderers/area-renderer.ts +++ b/src/renderers/area-renderer.ts @@ -8,6 +8,7 @@ import { GradientStyleCache } from './gradient-style-cache'; export type AreaFillItem = AreaFillItemBase & AreaFillColorerStyle; export interface PaneRendererAreaData extends PaneRendererAreaDataBase { + topCoordinate?: Coordinate; } export class PaneRendererArea extends PaneRendererAreaBase { @@ -21,7 +22,7 @@ export class PaneRendererArea extends PaneRendererAreaBase topColor2: '', bottomColor1: '', bottomColor2: item.bottomColor, - topCoordinate: 0 as Coordinate, + topCoordinate: this._data?.topCoordinate ?? 0 as Coordinate, bottomCoordinate: renderingScope.bitmapSize.height as Coordinate, } ); diff --git a/tests/e2e/graphics/test-cases/series/area-relative-gradient-inverted.js b/tests/e2e/graphics/test-cases/series/area-relative-gradient-inverted.js new file mode 100644 index 0000000000..65916c1f01 --- /dev/null +++ b/tests/e2e/graphics/test-cases/series/area-relative-gradient-inverted.js @@ -0,0 +1,24 @@ +function generateData() { + const res = []; + const time = new Date(Date.UTC(2018, 0, 1, 0, 0, 0, 0)); + for (let i = 0; i < 500; ++i) { + res.push({ + time: time.getTime() / 1000, + value: i, + }); + + time.setUTCDate(time.getUTCDate() + 1); + } + return res; +} + +function runTestCase(container) { + const chart = window.chart = LightweightCharts.createChart(container, { layout: { attributionLogo: false } }); + + const mainSeries = chart.addSeries(LightweightCharts.AreaSeries, { + relativeGradient: true, + invertFilledArea: true, + }); + + mainSeries.setData(generateData()); +} diff --git a/tests/e2e/graphics/test-cases/series/area-relative-gradient.js b/tests/e2e/graphics/test-cases/series/area-relative-gradient.js new file mode 100644 index 0000000000..1c7e01e026 --- /dev/null +++ b/tests/e2e/graphics/test-cases/series/area-relative-gradient.js @@ -0,0 +1,23 @@ +function generateData() { + const res = []; + const time = new Date(Date.UTC(2018, 0, 1, 0, 0, 0, 0)); + for (let i = 0; i < 500; ++i) { + res.push({ + time: time.getTime() / 1000, + value: i, + }); + + time.setUTCDate(time.getUTCDate() + 1); + } + return res; +} + +function runTestCase(container) { + const chart = window.chart = LightweightCharts.createChart(container, { layout: { attributionLogo: false } }); + + const mainSeries = chart.addSeries(LightweightCharts.AreaSeries, { + relativeGradient: true, + }); + + mainSeries.setData(generateData()); +} From cc8e61ae477f06710bfad6ae642fa6f111f38001 Mon Sep 17 00:00:00 2001 From: Andrii Ovcharenko Date: Wed, 27 Nov 2024 11:10:58 +0100 Subject: [PATCH 4/9] fix area gradient --- src/renderers/gradient-style-cache.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/renderers/gradient-style-cache.ts b/src/renderers/gradient-style-cache.ts index e3216550c7..cbc8d27e33 100644 --- a/src/renderers/gradient-style-cache.ts +++ b/src/renderers/gradient-style-cache.ts @@ -18,6 +18,7 @@ export class GradientStyleCache { private _params?: GradientCacheParams; private _cachedValue?: CanvasGradient; + // eslint-disable-next-line complexity public get(scope: BitmapCoordinatesRenderingScope, params: GradientCacheParams): CanvasGradient { const cachedParams = this._params; const { @@ -37,9 +38,10 @@ export class GradientStyleCache { cachedParams.bottomCoordinate !== bottomCoordinate ) { const { verticalPixelRatio } = scope; - const top = (topCoordinate) * verticalPixelRatio; - const bottom = (bottomCoordinate) * verticalPixelRatio; - const baseline = (baseLevelCoordinate ?? 0) * verticalPixelRatio; + const multiplier = baseLevelCoordinate || topCoordinate > 0 ? verticalPixelRatio : 1; + const top = topCoordinate * multiplier; + const bottom = bottomCoordinate === scope.bitmapSize.height ? bottomCoordinate : bottomCoordinate * multiplier; + const baseline = (baseLevelCoordinate ?? 0) * multiplier; const gradient = scope.context.createLinearGradient(0, top, 0, bottom); gradient.addColorStop(0, topColor1); From 0e758a1725d13e642c0e6520286530eb1e11bca8 Mon Sep 17 00:00:00 2001 From: Andrii Ovcharenko Date: Thu, 28 Nov 2024 10:51:17 +0100 Subject: [PATCH 5/9] change pixelmatch threshold --- tests/e2e/graphics/helpers/compare-screenshots.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/graphics/helpers/compare-screenshots.ts b/tests/e2e/graphics/helpers/compare-screenshots.ts index c23d3f5f3e..39dec3b422 100644 --- a/tests/e2e/graphics/helpers/compare-screenshots.ts +++ b/tests/e2e/graphics/helpers/compare-screenshots.ts @@ -24,7 +24,7 @@ export function compareScreenshots(leftImg: PNG, rightImg: PNG): CompareResult { leftImg.data, rightImg.data, diffImg.data, leftImg.width, leftImg.height, - { threshold: 0.1 } + { threshold: 0.01 } ); return { diffPixelsCount, diffImg }; From dd085e4861d2cb0418c9f3d1deb7c3e2b0719f46 Mon Sep 17 00:00:00 2001 From: Andrii Ovcharenko Date: Thu, 28 Nov 2024 11:03:12 +0100 Subject: [PATCH 6/9] pin deps version --- website/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/package.json b/website/package.json index 422560d5e6..9f95cc4477 100644 --- a/website/package.json +++ b/website/package.json @@ -35,6 +35,6 @@ "vue": "^3.4.29" }, "devDependencies": { - "typedoc-plugin-frontmatter": "^1.0.0" + "typedoc-plugin-frontmatter": "1.0.0" } } From 04897cb3d9d36294b65c3e2bd9681c33ede7f86f Mon Sep 17 00:00:00 2001 From: Andrii Ovcharenko Date: Thu, 28 Nov 2024 11:18:14 +0100 Subject: [PATCH 7/9] revert threshold --- tests/e2e/graphics/helpers/compare-screenshots.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/graphics/helpers/compare-screenshots.ts b/tests/e2e/graphics/helpers/compare-screenshots.ts index 39dec3b422..c23d3f5f3e 100644 --- a/tests/e2e/graphics/helpers/compare-screenshots.ts +++ b/tests/e2e/graphics/helpers/compare-screenshots.ts @@ -24,7 +24,7 @@ export function compareScreenshots(leftImg: PNG, rightImg: PNG): CompareResult { leftImg.data, rightImg.data, diffImg.data, leftImg.width, leftImg.height, - { threshold: 0.01 } + { threshold: 0.1 } ); return { diffPixelsCount, diffImg }; From ac48b79fa667baaa233f2afc349e3c0846add08c Mon Sep 17 00:00:00 2001 From: Andrii Ovcharenko Date: Thu, 28 Nov 2024 11:23:31 +0100 Subject: [PATCH 8/9] change threshold --- tests/e2e/graphics/helpers/compare-screenshots.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/graphics/helpers/compare-screenshots.ts b/tests/e2e/graphics/helpers/compare-screenshots.ts index c23d3f5f3e..bb803e9ba9 100644 --- a/tests/e2e/graphics/helpers/compare-screenshots.ts +++ b/tests/e2e/graphics/helpers/compare-screenshots.ts @@ -24,7 +24,7 @@ export function compareScreenshots(leftImg: PNG, rightImg: PNG): CompareResult { leftImg.data, rightImg.data, diffImg.data, leftImg.width, leftImg.height, - { threshold: 0.1 } + { threshold: 0.03 } ); return { diffPixelsCount, diffImg }; From 9ad5a6501d7e6332e7fd1a20719569aac60d7bb9 Mon Sep 17 00:00:00 2001 From: Andrii Ovcharenko Date: Thu, 28 Nov 2024 11:33:19 +0100 Subject: [PATCH 9/9] change threshold --- tests/e2e/graphics/helpers/compare-screenshots.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/graphics/helpers/compare-screenshots.ts b/tests/e2e/graphics/helpers/compare-screenshots.ts index bb803e9ba9..c23d3f5f3e 100644 --- a/tests/e2e/graphics/helpers/compare-screenshots.ts +++ b/tests/e2e/graphics/helpers/compare-screenshots.ts @@ -24,7 +24,7 @@ export function compareScreenshots(leftImg: PNG, rightImg: PNG): CompareResult { leftImg.data, rightImg.data, diffImg.data, leftImg.width, leftImg.height, - { threshold: 0.03 } + { threshold: 0.1 } ); return { diffPixelsCount, diffImg };