From 7a88453ad792fb46384ad429ebd722c380d742c5 Mon Sep 17 00:00:00 2001 From: Denis Travin Date: Wed, 11 Oct 2023 16:17:33 +0500 Subject: [PATCH 1/2] feat!: fixed crossTool, last candle plain Y axis label drawers --- src/chart/bootstrap.ts | 1 + .../cross_tool/cross-tool.component.ts | 18 ++- .../types/cross-and-labels.drawer.ts | 3 + .../y_axis/label-color.functions.ts | 29 +++- .../data-series-y-axis-labels.provider.ts | 75 ++++++----- .../last-candle-labels.provider.ts | 125 ++++++++++++------ 6 files changed, 171 insertions(+), 80 deletions(-) diff --git a/src/chart/bootstrap.ts b/src/chart/bootstrap.ts index eda68ddc..a204cb1e 100755 --- a/src/chart/bootstrap.ts +++ b/src/chart/bootstrap.ts @@ -510,6 +510,7 @@ export default class ChartBootstrap { paneManager, this.crossEventProducer, this.hoverProducer, + this.backgroundCanvasModel, ); this.chartComponents.push(this.crossToolComponent); diff --git a/src/chart/components/cross_tool/cross-tool.component.ts b/src/chart/components/cross_tool/cross-tool.component.ts index 626b1192..5e09a655 100644 --- a/src/chart/components/cross_tool/cross-tool.component.ts +++ b/src/chart/components/cross_tool/cross-tool.component.ts @@ -34,6 +34,7 @@ export class CrossToolComponent extends ChartBaseElement { private paneManager: PaneManager, crossEventProducer: CrossEventProducerComponent, hoverProducer: HoverProducerComponent, + private backgroundCanvasModel: CanvasModel, ) { super(); this.model = new CrossToolModel( @@ -63,11 +64,24 @@ export class CrossToolComponent extends ChartBaseElement { private registerDefaultDrawerTypes() { this.registerCrossToolTypeDrawer( 'cross-and-labels', - new CrossAndLabelsDrawerType(this.config, this.canvasBoundsContainer, this.paneManager, () => true), + new CrossAndLabelsDrawerType( + this.config, + this.canvasBoundsContainer, + this.paneManager, + this.backgroundCanvasModel, + () => true, + ), ); this.registerCrossToolTypeDrawer( 'just-labels', - new CrossAndLabelsDrawerType(this.config, this.canvasBoundsContainer, this.paneManager, () => true, true), + new CrossAndLabelsDrawerType( + this.config, + this.canvasBoundsContainer, + this.paneManager, + this.backgroundCanvasModel, + () => true, + true, + ), ); this.registerCrossToolTypeDrawer('none', new NoneDrawerType()); } diff --git a/src/chart/components/cross_tool/types/cross-and-labels.drawer.ts b/src/chart/components/cross_tool/types/cross-and-labels.drawer.ts index a207e7ea..15913049 100644 --- a/src/chart/components/cross_tool/types/cross-and-labels.drawer.ts +++ b/src/chart/components/cross_tool/types/cross-and-labels.drawer.ts @@ -5,6 +5,7 @@ */ import { CanvasBoundsContainer, CanvasElement } from '../../../canvas/canvas-bounds-container'; import { FullChartConfig } from '../../../chart.config'; +import { CanvasModel } from '../../../model/canvas.model'; import { avoidAntialiasing, drawRoundedRect } from '../../../utils/canvas/canvas-drawing-functions.utils'; import { PaneManager } from '../../pane/pane-manager.component'; import { priceLabelDrawersMap } from '../../y_axis/price_labels/price-label.drawer'; @@ -21,6 +22,7 @@ export class CrossAndLabelsDrawerType implements CrossToolTypeDrawer { private config: FullChartConfig, private canvasBoundsContainer: CanvasBoundsContainer, private paneManager: PaneManager, + private backgroundCanvasModel: CanvasModel, private crossDrawPredicate: () => boolean = () => true, private noLines?: boolean, ) {} @@ -161,6 +163,7 @@ export class CrossAndLabelsDrawerType implements CrossToolTypeDrawer { extent.yAxis.state, this.config.colors.yAxis, true, + this.backgroundCanvasModel.ctx, ); } } diff --git a/src/chart/components/y_axis/label-color.functions.ts b/src/chart/components/y_axis/label-color.functions.ts index 35f894d0..053a22d5 100644 --- a/src/chart/components/y_axis/label-color.functions.ts +++ b/src/chart/components/y_axis/label-color.functions.ts @@ -4,10 +4,37 @@ * If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import { PriceMovement } from '../../model/candle-series.model'; -import { FullChartColors, YAxisLabelsColors } from '../../chart.config'; +import { FullChartColors, YAxisConfig, YAxisLabelsColors } from '../../chart.config'; +import { getLabelTextColorByBackgroundColor } from '../../utils/canvas/canvas-text-functions.utils'; export const DEFAULT_LABEL_COLOR = '#FF00FF'; +export function getPlainLabelTextColor( + colorsConfig: FullChartColors, + textColor: string, + inversedTextColor: string, + yAxisState: YAxisConfig, +) { + // `plain` label is transparent, so to calculate text color + // we need to go down to draw hierarchy + // the next is YAxis bg color + const yAxisBGColor = colorsConfig.yAxis.backgroundColor; + + // if yAxis bg color is transparent, then we need to check chart area background color + const plainChartBGColor = colorsConfig.chartAreaTheme.backgroundColor; + // when chart area bg color is gradient, then we need to check which side yAxis is drawn + // because color on the right side and left side is different + const leftChartBGColor = colorsConfig.chartAreaTheme.backgroundGradientTopColor; + const rightChartBGColor = colorsConfig.chartAreaTheme.backgroundGradientBottomColor; + const gradientChartBGColor = yAxisState.align === 'left' ? leftChartBGColor : rightChartBGColor; + const chartBGColor = + colorsConfig.chartAreaTheme.backgroundMode === 'gradient' ? gradientChartBGColor : plainChartBGColor; + + const bgColor = yAxisBGColor === 'transparent' ? chartBGColor : yAxisBGColor; + + return getLabelTextColorByBackgroundColor(bgColor, textColor, inversedTextColor); +} + export const getPrimaryLabelTextColor = (lastPriceMovement: PriceMovement, colors: YAxisLabelsColors): string => { if (lastPriceMovement === 'down') { return colors.lastPrice.textNegative; diff --git a/src/chart/components/y_axis/price_labels/data-series-y-axis-labels.provider.ts b/src/chart/components/y_axis/price_labels/data-series-y-axis-labels.provider.ts index c2773cbd..c4a1fc67 100644 --- a/src/chart/components/y_axis/price_labels/data-series-y-axis-labels.provider.ts +++ b/src/chart/components/y_axis/price_labels/data-series-y-axis-labels.provider.ts @@ -16,7 +16,7 @@ export class DataSeriesYAxisLabelsProvider implements YAxisLabelsProvider { private series: DataSeriesModel, private config: DataSeriesConfig, public yAxisBoundsProvider: BoundsProvider, - public axisState?: YAxisConfig, + public axisState: YAxisConfig, ) {} /** @@ -28,42 +28,47 @@ export class DataSeriesYAxisLabelsProvider implements YAxisLabelsProvider { */ public getUnorderedLabels(): LabelGroup[] { const visible = this.config.visible; - if (visible) { - const getLastPointForLabel = - this.config.labelLastValue === 'series' - ? this.series.getLastDataSeriesPoint - : this.series.getLastVisualSeriesPoint; - const bounds = this.yAxisBoundsProvider(); - const mode = this.config.labelMode; - const appearanceType = this.config.labelAppearanceType; + if (!visible) { + return []; + } + + const getLastPointForLabel = + this.config.labelLastValue === 'series' + ? this.series.getLastDataSeriesPoint + : this.series.getLastVisualSeriesPoint; + const bounds = this.yAxisBoundsProvider(); + const mode = this.config.labelMode; + const appearanceType = this.config.labelAppearanceType; - const lastPoint = getLastPointForLabel(); - if (lastPoint !== undefined) { - const lastPointY = this.series.view.toY(lastPoint.close); - if (isFinite(lastPointY)) { - const label = this.series.valueFormatter(lastPoint.close); - const drawConfig = this.getLabelDrawConfig(); + const lastPoint = getLastPointForLabel(); + if (lastPoint === undefined) { + return []; + } - return [ - { - labels: [ - { - y: lastPointY, - description: this.series.name, - mode, - labelType: appearanceType, - labelText: label, - ...drawConfig, - }, - ], - axisState: this.axisState, - bounds, - }, - ]; - } - } + const lastPointY = this.series.view.toY(lastPoint.close); + if (!isFinite(lastPointY)) { + return []; } - return []; + + const label = this.series.valueFormatter(lastPoint.close); + const drawConfig = this.getLabelDrawConfig(); + + return [ + { + labels: [ + { + y: lastPointY, + description: this.series.name, + mode, + labelType: appearanceType, + labelText: label, + ...drawConfig, + }, + ], + axisState: this.axisState, + bounds, + }, + ]; } /** @@ -76,8 +81,10 @@ export class DataSeriesYAxisLabelsProvider implements YAxisLabelsProvider { private getLabelDrawConfig(): YAxisLabelDrawConfig { const config = this.series.config; const paintConfig = config.paintConfig[0] ?? DEFAULT_DATA_SERIES_PAINT_CONFIG; + const bgColor = paintConfig.color; const textColor = getLabelTextColorByBackgroundColor(bgColor, 'white', 'black'); + return { textColor, bgColor, diff --git a/src/chart/components/y_axis/price_labels/last-candle-labels.provider.ts b/src/chart/components/y_axis/price_labels/last-candle-labels.provider.ts index cbd244c7..076cb2d8 100644 --- a/src/chart/components/y_axis/price_labels/last-candle-labels.provider.ts +++ b/src/chart/components/y_axis/price_labels/last-candle-labels.provider.ts @@ -7,7 +7,7 @@ import { FullChartConfig, YAxisConfig } from '../../../chart.config'; import { CandleSeriesModel } from '../../../model/candle-series.model'; import { DataSeriesType } from '../../../model/data-series.config'; import { ChartModel, LastCandleLabelHandler } from '../../chart/chart.model'; -import { getPrimaryLabelTextColor } from '../label-color.functions'; +import { getPlainLabelTextColor, getPrimaryLabelTextColor } from '../label-color.functions'; import { YAxisLabelDrawConfig } from '../y-axis-labels.drawer'; import { LabelGroup, VisualYAxisLabel, YAxisLabelsProvider } from './y-axis-labels.model'; import { lastOf } from '../../../utils/array.utils'; @@ -17,7 +17,7 @@ import { LabelColorResolver } from '../y-axis.component'; export class LastCandleLabelsProvider implements YAxisLabelsProvider { constructor( private chartModel: ChartModel, - private fullConfig: FullChartConfig, + private chartConfig: FullChartConfig, private yAxisConfig: YAxisConfig, private lastCandleLabelsByChartType: Partial>, private resolveLabelColorFn: (chartType: DataSeriesType) => LabelColorResolver, @@ -30,45 +30,49 @@ export class LastCandleLabelsProvider implements YAxisLabelsProvider { public getUnorderedLabels(): LabelGroup[] { const collectedLabels: LabelGroup[] = []; const visible = this.yAxisConfig.labels.settings.lastPrice.mode !== 'none'; - if (visible) { - // main candle series - const yAxisVisualLabel = this.getYAxisVisualLabel(this.chartModel.mainCandleSeries); - const mainCandleSeriesVisualLabel: VisualYAxisLabel | null = yAxisVisualLabel + + if (!visible) { + return collectedLabels; + } + + // main candle series + const yAxisVisualLabel = this.getYAxisVisualLabel(this.chartModel.mainCandleSeries); + const mainCandleSeriesVisualLabel: VisualYAxisLabel | null = yAxisVisualLabel + ? { + ...yAxisVisualLabel, + ...this.getLabelDrawConfig(this.chartModel.mainCandleSeries, true), + } + : yAxisVisualLabel; + if (mainCandleSeriesVisualLabel) { + const mainCandleSeriesLabels: LabelGroup = { labels: [mainCandleSeriesVisualLabel] }; + + const handler = this.lastCandleLabelsByChartType[this.chartConfig.components.chart.type]; + handler?.(mainCandleSeriesLabels, this.chartModel.mainCandleSeries); + collectedLabels.push(mainCandleSeriesLabels); + } + + // compare candle series + this.chartModel.candleSeries.forEach((series, index) => { + if (index === 0) { + return; + } + const yAxisVisualLabel = this.getYAxisVisualLabel(series); + const secondarySeriesVisualLabel: VisualYAxisLabel | null = yAxisVisualLabel ? { ...yAxisVisualLabel, - ...this.getLabelDrawConfig(this.chartModel.mainCandleSeries, true), + ...this.getLabelDrawConfig(series, false), } : yAxisVisualLabel; - if (mainCandleSeriesVisualLabel) { - const mainCandleSeriesLabels: LabelGroup = { labels: [mainCandleSeriesVisualLabel] }; - - const handler = this.lastCandleLabelsByChartType[this.fullConfig.components.chart.type]; - handler?.(mainCandleSeriesLabels, this.chartModel.mainCandleSeries); - collectedLabels.push(mainCandleSeriesLabels); + if (secondarySeriesVisualLabel) { + const secondarySeriesLabel: LabelGroup = { + labels: [secondarySeriesVisualLabel], + }; + const handler = this.lastCandleLabelsByChartType[series.config.type]; + handler?.(secondarySeriesLabel, this.chartModel.mainCandleSeries); + collectedLabels.push(secondarySeriesLabel); } + }); - // compare candle series - this.chartModel.candleSeries.forEach((series, index) => { - if (index === 0) { - return; - } - const yAxisVisualLabel = this.getYAxisVisualLabel(series); - const secondarySeriesVisualLabel: VisualYAxisLabel | null = yAxisVisualLabel - ? { - ...yAxisVisualLabel, - ...this.getLabelDrawConfig(series, false), - } - : yAxisVisualLabel; - if (secondarySeriesVisualLabel) { - const secondarySeriesLabel: LabelGroup = { - labels: [secondarySeriesVisualLabel], - }; - const handler = this.lastCandleLabelsByChartType[series.config.type]; - handler?.(secondarySeriesLabel, this.chartModel.mainCandleSeries); - collectedLabels.push(secondarySeriesLabel); - } - }); - } return collectedLabels; } @@ -100,22 +104,57 @@ export class LastCandleLabelsProvider implements YAxisLabelsProvider { /** * Returns the configuration object for drawing the label of the Y-axis. * @param {CandleSeriesModel} series - The series model for which the label configuration is to be returned. - * @param {boolean} primary - A boolean value indicating whether the label is primary or not. + * @param {boolean} primary - A boolean value indicating whether the label is for the main series or not. * @returns {YAxisLabelDrawConfig} - The configuration object for drawing the label of the Y-axis. */ private getLabelDrawConfig(series: CandleSeriesModel, primary: boolean): YAxisLabelDrawConfig { + const appearanceType = this.yAxisConfig.labels.settings.lastPrice.type; + const colors = series.colors.labels; + const { rectLabelTextColor = 'white', rectLabelInvertedTextColor = 'black' } = this.chartConfig.colors.yAxis; + const getLabelBoxColor = this.resolveLabelColorFn(series.config.type); - const { rectLabelTextColor, rectLabelInvertedTextColor } = this.chartModel.config.colors.yAxis; - let boxColor = '#FFFFFF'; - let textColor = '#FFFFFF'; - if (colors) { - boxColor = getLabelBoxColor(series.lastPriceMovement, series.colors); - textColor = primary ? getPrimaryLabelTextColor(series.lastPriceMovement, colors) : rectLabelTextColor; + + if (!colors) { + return { + bgColor: '#FFFFFF', + textColor: getLabelTextColorByBackgroundColor('#FFFFFF', '#000000', rectLabelInvertedTextColor), + rounded: true, + }; } + + const boxColor = getLabelBoxColor(series.lastPriceMovement, series.colors); + + // if the label is for the main candle series + if (primary) { + const textColor = getPrimaryLabelTextColor(series.lastPriceMovement, colors); + return { + bgColor: boxColor, + textColor: + appearanceType === 'plain' + ? getPlainLabelTextColor( + this.chartConfig.colors, + textColor, + rectLabelInvertedTextColor, + this.yAxisConfig, + ) + : getLabelTextColorByBackgroundColor(boxColor, textColor, rectLabelInvertedTextColor), + rounded: true, + }; + } + + // if the label is for the secondary candle series return { bgColor: boxColor, - textColor: getLabelTextColorByBackgroundColor(boxColor, textColor, rectLabelInvertedTextColor), + textColor: + appearanceType === 'plain' + ? getPlainLabelTextColor( + this.chartConfig.colors, + rectLabelTextColor, + rectLabelInvertedTextColor, + this.yAxisConfig, + ) + : getLabelTextColorByBackgroundColor(boxColor, rectLabelTextColor, rectLabelInvertedTextColor), rounded: true, }; } From e94f38ad7f0249f06afe4ce2dc33a62a43e194e4 Mon Sep 17 00:00:00 2001 From: Denis Travin Date: Wed, 11 Oct 2023 17:09:49 +0500 Subject: [PATCH 2/2] chore: typo in arg name --- src/chart/components/y_axis/label-color.functions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/chart/components/y_axis/label-color.functions.ts b/src/chart/components/y_axis/label-color.functions.ts index 053a22d5..bd4285d5 100644 --- a/src/chart/components/y_axis/label-color.functions.ts +++ b/src/chart/components/y_axis/label-color.functions.ts @@ -12,7 +12,7 @@ export const DEFAULT_LABEL_COLOR = '#FF00FF'; export function getPlainLabelTextColor( colorsConfig: FullChartColors, textColor: string, - inversedTextColor: string, + invertedTextColor: string, yAxisState: YAxisConfig, ) { // `plain` label is transparent, so to calculate text color @@ -32,7 +32,7 @@ export function getPlainLabelTextColor( const bgColor = yAxisBGColor === 'transparent' ? chartBGColor : yAxisBGColor; - return getLabelTextColorByBackgroundColor(bgColor, textColor, inversedTextColor); + return getLabelTextColorByBackgroundColor(bgColor, textColor, invertedTextColor); } export const getPrimaryLabelTextColor = (lastPriceMovement: PriceMovement, colors: YAxisLabelsColors): string => {