From a2bc2caa31b4da52a981dfce24621c1004f9c48f Mon Sep 17 00:00:00 2001 From: Sergey Vlasov Date: Thu, 30 Nov 2023 12:56:41 +0200 Subject: [PATCH 1/2] Low label doesn't show up --- .../components/high_low/high-low.drawer.ts | 10 +++- .../y_axis/price_labels/price-label.drawer.ts | 52 +++++++++---------- .../components/y_axis/y-axis-labels.drawer.ts | 45 +++++++++------- .../canvas/canvas-text-functions.utils.ts | 12 +++-- 4 files changed, 70 insertions(+), 49 deletions(-) diff --git a/src/chart/components/high_low/high-low.drawer.ts b/src/chart/components/high_low/high-low.drawer.ts index 9eded9ec..ef7e5620 100644 --- a/src/chart/components/high_low/high-low.drawer.ts +++ b/src/chart/components/high_low/high-low.drawer.ts @@ -9,6 +9,7 @@ import { FullChartConfig } from '../../chart.config'; import { ChartModel } from '../chart/chart.model'; import { CanvasModel } from '../../model/canvas.model'; import { getTextLineHeight } from '../../utils/canvas/canvas-text-functions.utils'; +import { clipToBounds } from '../../drawers/data-series.drawer'; type MarkerType = 'high' | 'low'; @@ -41,7 +42,10 @@ export class HighLowDrawer implements Drawer { ctx.font = this.config.components.highLow.font; this.drawMarkerLabel(ctx, finalHighIdx, high, 'high'); this.drawMarkerLabel(ctx, finalLowIdx, low, 'low'); + const chartBounds = this.canvasBoundsContainer.getBounds('PANE_CHART'); ctx.restore(); + // We need clip here so lowLabel won't overlap other panes + clipToBounds(ctx, chartBounds); } } @@ -55,7 +59,9 @@ export class HighLowDrawer implements Drawer { */ private drawMarkerLabel(ctx: CanvasRenderingContext2D, candleIdx: number, yValue: number, type: MarkerType): void { const y = this.getMarkerY(ctx, yValue, type === 'low'); - if (!this.checkMarkerInBounds(y)) { + const fontSize = getTextLineHeight(ctx, false); + const yForBoundsTrack = type === 'low' ? y - fontSize : y; + if (!this.checkMarkerInBounds(yForBoundsTrack)) { return; } const text = this.getMarkerText(yValue, type); @@ -89,7 +95,7 @@ export class HighLowDrawer implements Drawer { private getMarkerY(ctx: CanvasRenderingContext2D, yValue: number, offset: boolean = false): number { const y = this.chartModel.toY(yValue); if (offset) { - const fontSize = getTextLineHeight(ctx); + const fontSize = getTextLineHeight(ctx, false); return y + fontSize; } return y; diff --git a/src/chart/components/y_axis/price_labels/price-label.drawer.ts b/src/chart/components/y_axis/price_labels/price-label.drawer.ts index 0c2fadb1..4ceebf61 100644 --- a/src/chart/components/y_axis/price_labels/price-label.drawer.ts +++ b/src/chart/components/y_axis/price_labels/price-label.drawer.ts @@ -9,14 +9,14 @@ import { FullChartColors, getFontFromConfig, YAxisAlign, - YAxisLabelAppearanceType, + YAxisLabelAppearanceType, YAxisLabelMode, } from '../../../chart.config'; import { redrawBackgroundArea } from '../../../drawers/chart-background.drawer'; import { Bounds } from '../../../model/bounds.model'; import { avoidAntialiasing, drawLine } from '../../../utils/canvas/canvas-drawing-functions.utils'; import { calculateSymbolHeight, calculateTextWidth } from '../../../utils/canvas/canvas-font-measure-tool.utils'; import { floor } from '../../../utils/math.utils'; -import { drawBadgeLabel, drawPlainLabel, drawRectLabel } from '../y-axis-labels.drawer'; +import { drawBadgeLabel, drawPlainLabel, drawRectLabel, checkLabelInBoundaries } from '../y-axis-labels.drawer'; import { VisualYAxisLabel, YAxisVisualLabelType } from './y-axis-labels.model'; type LabelDrawer = typeof drawBadgeLabel | typeof drawRectLabel | typeof drawPlainLabel; @@ -96,22 +96,30 @@ export function drawLabel( showLine && avoidAntialiasing(ctx, () => drawLine(ctx, lineXStart, lineY, lineXEnd, lineY, 1)); const _drawLabel = () => drawLabel(ctx, bounds, text, centralY, visualLabel, config, colors, false, backgroundCtx); - switch (mode) { - case 'line': - _drawLine(); - _drawDescription(); - break; - case 'line-label': - _drawLine(); - _drawDescription(); - _drawLabel(); - break; - case 'label': - _drawDescription(); - _drawLabel(); - break; - case 'none': - break; + const drawLineLabel = () => { + _drawLine(); + _drawDescription(); + } + + const drawLineLabelLabel = () => { + _drawLine(); + _drawLabel(); + _drawDescription(); + } + + const drawLabelLabel = () => { + _drawDescription(); + _drawLabel(); + } + + const labelDrawerByMode: Record, () => void> = { + 'line': drawLineLabel, + 'line-label': drawLineLabelLabel, + 'label': drawLabelLabel, + } + + if (mode !== 'none' && checkLabelInBoundaries(centralY, bounds, labelBoxHeight)) { + labelDrawerByMode[mode](); } ctx.restore(); @@ -143,14 +151,6 @@ function drawDescription( const labelBoxBottom = centralY + fontHeight / 2 + paddingBottom; const labelBoxHeight = labelBoxBottom - labelBoxY; - // do not draw, if description is out of bounds - if ( - centralY < labelBounds.y + labelBoxHeight / 2 || - centralY > labelBounds.y + labelBounds.height - labelBoxHeight / 2 - ) { - return; - } - ctx.save(); // overlay rect diff --git a/src/chart/components/y_axis/y-axis-labels.drawer.ts b/src/chart/components/y_axis/y-axis-labels.drawer.ts index 1ce95d9f..c9e6490f 100644 --- a/src/chart/components/y_axis/y-axis-labels.drawer.ts +++ b/src/chart/components/y_axis/y-axis-labels.drawer.ts @@ -43,8 +43,9 @@ export const DEFAULT_PRICE_LABEL_PADDING = 4; * @param text - text to draw * @param centralY - y * @param config - label styles config - * @param align * @param yAxisState + * @param yAxisColors + * @param checkBoundaries */ export function drawBadgeLabel( ctx: CanvasRenderingContext2D, @@ -54,7 +55,7 @@ export function drawBadgeLabel( config: YAxisLabelDrawConfig, yAxisState: YAxisConfig, yAxisColors: FullChartColors['yAxis'], - drawOutside: boolean = false, + checkBoundaries: boolean = true, ): void { const align = yAxisState.align; const textFont = config.textFont ?? getFontFromConfig(yAxisState); @@ -71,12 +72,10 @@ export function drawBadgeLabel( const labelBoxHeight = labelBoxBottomY - labelBoxTopY; // do not draw, if label is out of bounds - if ( - (centralY < bounds.y + labelBoxHeight / 2 || centralY > bounds.y + bounds.height - labelBoxHeight / 2) && - !drawOutside - ) { + if (checkBoundaries && !checkLabelInBoundaries(centralY, bounds, labelBoxHeight)) { return; } + ctx.save(); ctx.fillStyle = bgColor; ctx.strokeStyle = bgColor; @@ -119,8 +118,9 @@ export function drawBadgeLabel( * @param text - text to draw * @param centralY - y * @param config - label styles config - * @param align * @param yAxisState + * @param yAxisColors + * @param checkBoundaries */ export function drawRectLabel( ctx: CanvasRenderingContext2D, @@ -130,7 +130,7 @@ export function drawRectLabel( config: YAxisLabelDrawConfig, yAxisState: YAxisConfig, yAxisColors: FullChartColors['yAxis'], - drawOutside: boolean = false, + checkBoundaries: boolean = true, ) { const align = yAxisState.align; const textFont = config.textFont ?? getFontFromConfig(yAxisState); @@ -150,12 +150,10 @@ export function drawRectLabel( const rounded = config.rounded ?? yAxisState.typeConfig.rectangle?.rounded; // do not draw, if label is out of bounds - if ( - (centralY < bounds.y + labelBoxHeight / 2 || centralY > bounds.y + bounds.height - labelBoxHeight / 2) && - !drawOutside - ) { + if (checkBoundaries && !checkLabelInBoundaries(centralY, bounds, labelBoxHeight)) { return; } + ctx.save(); ctx.fillStyle = bgColor; ctx.strokeStyle = bgColor; @@ -188,8 +186,10 @@ export function drawRectLabel( * @param text - text to draw * @param centralY - y * @param config - label styles config - * @param align * @param yAxisState + * @param yAxisColors + * @param checkBoundaries + * @param backgroundCtx */ export function drawPlainLabel( ctx: CanvasRenderingContext2D, @@ -199,7 +199,7 @@ export function drawPlainLabel( config: YAxisLabelDrawConfig, yAxisState: YAxisConfig, yAxisColors: FullChartColors['yAxis'], - drawOutside: boolean = false, + checkBoundaries: boolean = true, backgroundCtx?: CanvasRenderingContext2D, ) { const align = yAxisState.align; @@ -219,12 +219,10 @@ export function drawPlainLabel( const labelBoxHeight = labelBoxBottomY - labelBoxTopY; // do not draw, if label is out of bounds - if ( - (centralY < bounds.y + labelBoxHeight / 2 || centralY > bounds.y + bounds.height - labelBoxHeight / 2) && - !drawOutside - ) { + if (checkBoundaries && !checkLabelInBoundaries(centralY, bounds, labelBoxHeight)) { return; } + ctx.save(); ctx.fillStyle = bgColor; ctx.strokeStyle = bgColor; @@ -261,3 +259,14 @@ export function getLabelYOffset( const fontHeight = calculateSymbolHeight(font, ctx); return fontHeight / 2 + paddingTop; } + +/** + * Checks if label fits in chart scale boundaries + * @param centralY + * @param bounds + * @param labelBoxHeight + * returns true if label fits + */ +export function checkLabelInBoundaries(centralY: number, bounds: Bounds, labelBoxHeight: number) { + return !(centralY < bounds.y + labelBoxHeight / 2 || centralY > bounds.y + bounds.height - labelBoxHeight / 2); +} \ No newline at end of file diff --git a/src/chart/utils/canvas/canvas-text-functions.utils.ts b/src/chart/utils/canvas/canvas-text-functions.utils.ts index ad6038c3..5b5ee627 100644 --- a/src/chart/utils/canvas/canvas-text-functions.utils.ts +++ b/src/chart/utils/canvas/canvas-text-functions.utils.ts @@ -19,6 +19,11 @@ export interface CanvasTextProperties { rtl?: boolean; } +/** + * Baseline Height in Project + */ +const __BASELINE__ = 1.33; + /** * Sets the font, fill style and text alignment of a canvas context based on the provided properties. * @param {CanvasRenderingContext2D} ctx - The canvas context to modify. @@ -47,9 +52,10 @@ export function prepareTextForFill(ctx: CanvasRenderingContext2D, properties: Ca /** * Calculates the line height of a text based on the font size of the provided CanvasRenderingContext2D. * @param {CanvasRenderingContext2D} ctx - The CanvasRenderingContext2D object used to draw the text. + * @param includeBaseLine * @returns {number} The calculated line height of the text. */ -export function getTextLineHeight(ctx: CanvasRenderingContext2D): number { +export function getTextLineHeight(ctx: CanvasRenderingContext2D, includeBaseLine: boolean = true): number { const textSizeMatch = ctx.font.match(/(\d+.)?\d+(px|pt)/gi); let textSize = '10px'; if (textSizeMatch && textSizeMatch.length) { @@ -60,7 +66,7 @@ export function getTextLineHeight(ctx: CanvasRenderingContext2D): number { textSize = textSizeMatch[0]; } } - return parseInt(textSize, 10) * 1.33; // Base Line Height in Project + return includeBaseLine ? parseInt(textSize, 10) * __BASELINE__ : parseInt(textSize, 10); } /** @@ -106,7 +112,7 @@ export function getTextLines(text: string): string[] { * @param {number} y - The y-coordinate of the starting position of the text. * @param {CanvasTextProperties} properties - An object containing properties for the text, such as font size, style, and alignment. * @returns {void} - + */ export function drawText( ctx: CanvasRenderingContext2D, From 63d761084b9258f1dfaa91e60628f5b01b190f5f Mon Sep 17 00:00:00 2001 From: Sergey Vlasov Date: Mon, 11 Dec 2023 15:38:53 +0200 Subject: [PATCH 2/2] fix: low label display --- .../components/high_low/high-low.drawer.ts | 3 ++- .../highlights/highlights.drawer.ts | 2 +- .../components/volumes/volumes.drawer.ts | 2 +- src/chart/components/y_axis/y-axis.drawer.ts | 2 +- .../candle-series-wrapper.ts | 3 ++- src/chart/drawers/data-series.drawer.ts | 9 +-------- src/chart/drawers/ht-data-series.drawer.ts | 3 ++- .../canvas/canvas-drawing-functions.utils.ts | 19 ++++++++++++++++++- .../canvas/canvas-text-functions.utils.ts | 4 ++-- 9 files changed, 30 insertions(+), 17 deletions(-) diff --git a/src/chart/components/high_low/high-low.drawer.ts b/src/chart/components/high_low/high-low.drawer.ts index ef7e5620..88b3b53d 100644 --- a/src/chart/components/high_low/high-low.drawer.ts +++ b/src/chart/components/high_low/high-low.drawer.ts @@ -9,7 +9,7 @@ import { FullChartConfig } from '../../chart.config'; import { ChartModel } from '../chart/chart.model'; import { CanvasModel } from '../../model/canvas.model'; import { getTextLineHeight } from '../../utils/canvas/canvas-text-functions.utils'; -import { clipToBounds } from '../../drawers/data-series.drawer'; +import { clipToBounds } from '../../utils/canvas/canvas-drawing-functions.utils'; type MarkerType = 'high' | 'low'; @@ -60,6 +60,7 @@ export class HighLowDrawer implements Drawer { private drawMarkerLabel(ctx: CanvasRenderingContext2D, candleIdx: number, yValue: number, type: MarkerType): void { const y = this.getMarkerY(ctx, yValue, type === 'low'); const fontSize = getTextLineHeight(ctx, false); + // we need to measure fit into the bounds for low label by its top point const yForBoundsTrack = type === 'low' ? y - fontSize : y; if (!this.checkMarkerInBounds(yForBoundsTrack)) { return; diff --git a/src/chart/components/highlights/highlights.drawer.ts b/src/chart/components/highlights/highlights.drawer.ts index 5706d7f9..f159f316 100644 --- a/src/chart/components/highlights/highlights.drawer.ts +++ b/src/chart/components/highlights/highlights.drawer.ts @@ -12,7 +12,7 @@ import { CanvasBoundsContainer, CanvasElement } from '../../canvas/canvas-bounds import { Drawer } from '../../drawers/drawing-manager'; import { ChartModel } from '../chart/chart.model'; import { unitToPixels } from '../../model/scaling/viewport.model'; -import { clipToBounds } from '../../drawers/data-series.drawer'; +import { clipToBounds } from '../../utils/canvas/canvas-drawing-functions.utils'; const LABEL_PADDINGS = [20, 10]; diff --git a/src/chart/components/volumes/volumes.drawer.ts b/src/chart/components/volumes/volumes.drawer.ts index 4d9e011a..f79d00f3 100644 --- a/src/chart/components/volumes/volumes.drawer.ts +++ b/src/chart/components/volumes/volumes.drawer.ts @@ -4,7 +4,7 @@ * If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import { BarType, FullChartConfig } from '../../chart.config'; -import { clipToBounds } from '../../drawers/data-series.drawer'; +import { clipToBounds } from '../../utils/canvas/canvas-drawing-functions.utils'; import { PriceMovement } from '../../model/candle-series.model'; import { CanvasModel } from '../../model/canvas.model'; import { Pixel, ViewportModel, unitToPixels } from '../../model/scaling/viewport.model'; diff --git a/src/chart/components/y_axis/y-axis.drawer.ts b/src/chart/components/y_axis/y-axis.drawer.ts index c72ba426..a5e8554d 100644 --- a/src/chart/components/y_axis/y-axis.drawer.ts +++ b/src/chart/components/y_axis/y-axis.drawer.ts @@ -4,7 +4,7 @@ * If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import { FullChartConfig, YAxisAlign, getFontFromConfig } from '../../chart.config'; -import { clipToBounds } from '../../drawers/data-series.drawer'; +import { clipToBounds } from '../../utils/canvas/canvas-drawing-functions.utils'; import { Drawer } from '../../drawers/drawing-manager'; import { Bounds } from '../../model/bounds.model'; import { CanvasModel } from '../../model/canvas.model'; diff --git a/src/chart/drawers/data-series-drawers/candle-series-wrapper.ts b/src/chart/drawers/data-series-drawers/candle-series-wrapper.ts index 5a4b2feb..4ae59599 100644 --- a/src/chart/drawers/data-series-drawers/candle-series-wrapper.ts +++ b/src/chart/drawers/data-series-drawers/candle-series-wrapper.ts @@ -6,7 +6,8 @@ import { BarType, FullChartConfig } from '../../chart.config'; import { BoundsProvider } from '../../model/bounds.model'; import { DataSeriesModel, VisualSeriesPoint } from '../../model/data-series.model'; -import { ChartDrawerConfig, clipToBounds, SeriesDrawer } from '../data-series.drawer'; +import { ChartDrawerConfig, SeriesDrawer } from '../data-series.drawer'; +import { clipToBounds } from '../../utils/canvas/canvas-drawing-functions.utils'; export const candleTypesList: BarType[] = [ 'candle', diff --git a/src/chart/drawers/data-series.drawer.ts b/src/chart/drawers/data-series.drawer.ts index 1118137c..54c0b868 100644 --- a/src/chart/drawers/data-series.drawer.ts +++ b/src/chart/drawers/data-series.drawer.ts @@ -5,9 +5,9 @@ */ import { DynamicModelDrawer } from '../components/dynamic-objects/dynamic-objects.drawer'; import { PaneManager } from '../components/pane/pane-manager.component'; -import { Bounds } from '../model/bounds.model'; import { CanvasModel } from '../model/canvas.model'; import { DataSeriesModel, VisualSeriesPoint } from '../model/data-series.model'; +import { clipToBounds } from '../utils/canvas/canvas-drawing-functions.utils'; export interface ChartDrawerConfig { singleColor?: string; @@ -69,13 +69,6 @@ export class DataSeriesDrawer implements DynamicModelDrawer { } } -export const clipToBounds = (ctx: CanvasRenderingContext2D, bounds: Bounds) => { - ctx.beginPath(); - ctx.rect(bounds.x, bounds.y, bounds.width, bounds.height); - ctx.clip(); - ctx.closePath(); -}; - export const setLineWidth = ( ctx: CanvasRenderingContext2D, lineWidth: number, diff --git a/src/chart/drawers/ht-data-series.drawer.ts b/src/chart/drawers/ht-data-series.drawer.ts index 0fa11d9a..898c5883 100644 --- a/src/chart/drawers/ht-data-series.drawer.ts +++ b/src/chart/drawers/ht-data-series.drawer.ts @@ -6,7 +6,8 @@ import { PaneManager } from '../components/pane/pane-manager.component'; import { DataSeriesModel } from '../model/data-series.model'; import { HitTestCanvasModel } from '../model/hit-test-canvas.model'; -import { ChartDrawerConfig, clipToBounds, SeriesDrawer } from './data-series.drawer'; +import { ChartDrawerConfig, SeriesDrawer } from './data-series.drawer'; +import { clipToBounds } from '../utils/canvas/canvas-drawing-functions.utils'; import { Drawer } from './drawing-manager'; /*** diff --git a/src/chart/utils/canvas/canvas-drawing-functions.utils.ts b/src/chart/utils/canvas/canvas-drawing-functions.utils.ts index b24fb424..0f95f5b2 100644 --- a/src/chart/utils/canvas/canvas-drawing-functions.utils.ts +++ b/src/chart/utils/canvas/canvas-drawing-functions.utils.ts @@ -4,6 +4,7 @@ * If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import { YAxisAlign } from '../../chart.config'; +import { Bounds } from '../../model/bounds.model'; /** * Draws a rounded rectangle on a canvas context @@ -171,7 +172,7 @@ export function avoidAntialiasing(ctx: CanvasRenderingContext2D, draw: () => voi * @param {{x: number, y: number}} a - The first point of the rectangle. * @param {{x: number, y: number}} b - The second point of the rectangle. * @returns {void} - + */ export function fillRect( ctx: CanvasRenderingContext2D, @@ -190,3 +191,19 @@ export function fillRect( const h = Math.abs(a.y - b.y); ctx.fillRect(x, y, w, h); } + +/** + * Sets clipping region for a Canvas 2D context according to the provided bounds. + * + * @param {CanvasRenderingContext2D} ctx - The canvas 2D context which will get the new clip region. + * @param {object} bounds - The bounds of the clipping region. Object containing x, y, width and height properties. + * + * @example + * clipToBounds(ctx, {x: 50, y: 50, width: 100, height: 100}); + */ +export const clipToBounds = (ctx: CanvasRenderingContext2D, bounds: Bounds) => { + ctx.beginPath(); + ctx.rect(bounds.x, bounds.y, bounds.width, bounds.height); + ctx.clip(); + ctx.closePath(); +}; diff --git a/src/chart/utils/canvas/canvas-text-functions.utils.ts b/src/chart/utils/canvas/canvas-text-functions.utils.ts index 5b5ee627..1085d460 100644 --- a/src/chart/utils/canvas/canvas-text-functions.utils.ts +++ b/src/chart/utils/canvas/canvas-text-functions.utils.ts @@ -22,7 +22,7 @@ export interface CanvasTextProperties { /** * Baseline Height in Project */ -const __BASELINE__ = 1.33; +const BASELINE = 1.33; /** * Sets the font, fill style and text alignment of a canvas context based on the provided properties. @@ -66,7 +66,7 @@ export function getTextLineHeight(ctx: CanvasRenderingContext2D, includeBaseLine textSize = textSizeMatch[0]; } } - return includeBaseLine ? parseInt(textSize, 10) * __BASELINE__ : parseInt(textSize, 10); + return includeBaseLine ? parseInt(textSize, 10) * BASELINE : parseInt(textSize, 10); } /**