Skip to content

Commit

Permalink
Merge branch '78-improve-x-labels-generation-time' of https://github.…
Browse files Browse the repository at this point in the history
…com/devexperts/dxcharts-lite into 78-improve-x-labels-generation-time
  • Loading branch information
KirillBobkov committed Dec 12, 2023
2 parents 1a76d21 + 056c233 commit 8c16b8b
Show file tree
Hide file tree
Showing 14 changed files with 136 additions and 88 deletions.
1 change: 1 addition & 0 deletions src/chart/bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,7 @@ export default class ChartBootstrap {
paneManager,
this.cursorHandler,
this.dynamicObjects,
this.chartResizeHandler,
);
this.chartComponents.push(chartComponent);
this.chartComponent = chartComponent;
Expand Down
22 changes: 20 additions & 2 deletions src/chart/components/chart/chart.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
* If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { Observable } from 'rxjs';
import { Observable, BehaviorSubject } from 'rxjs';
import { CHART_UUID, CanvasBoundsContainer, CanvasElement } from '../../canvas/canvas-bounds-container';
import { CursorHandler } from '../../canvas/cursor.handler';
import { ChartBaseElement } from '../../model/chart-base-element';
Expand Down Expand Up @@ -51,6 +51,7 @@ import { CandleWidthCalculator, ChartModel, LastCandleLabelHandler, VisualCandle
import { PrependedCandlesData } from './chart-base.model';
import { TrendHistogramDrawer } from '../../drawers/data-series-drawers/trend-histogram.drawer';
import { DynamicObjectsComponent } from '../dynamic-objects/dynamic-objects.component';
import { ChartResizeHandler } from '../../inputhandlers/chart-resize.handler';

/**
* Represents a financial instrument to be displayed on a chart
Expand Down Expand Up @@ -81,6 +82,7 @@ export class ChartComponent extends ChartBaseElement {
private readonly backgroundDrawer: BackgroundDrawer;
private readonly _dataSeriesDrawers: Record<DataSeriesType, SeriesDrawer> = {};
private readonly dataSeriesDrawer: DataSeriesDrawer;
private backgroundDrawPredicateSubj = new BehaviorSubject<boolean>(true);

constructor(
public readonly chartModel: ChartModel,
Expand All @@ -96,6 +98,7 @@ export class ChartComponent extends ChartBaseElement {
private paneManager: PaneManager,
cursorHandler: CursorHandler,
private dynamicObjects: DynamicObjectsComponent,
private chartResizeHandler: ChartResizeHandler,
) {
super();
this.addChildEntity(this.chartModel);
Expand All @@ -117,7 +120,11 @@ export class ChartComponent extends ChartBaseElement {
//#region data series drawers
this.registerDefaultDataSeriesDrawers();
//#endregion
this.backgroundDrawer = new BackgroundDrawer(backgroundCanvasModel, this.config);
this.backgroundDrawer = new BackgroundDrawer(
backgroundCanvasModel,
this.config,
() => this.backgroundDrawPredicateSubj.getValue(),
);
drawingManager.addDrawer(this.backgroundDrawer, 'MAIN_BACKGROUND');
cursorHandler.setCursorForCanvasEl(CanvasElement.PANE_UUID(CHART_UUID), config.components.chart.cursor);

Expand Down Expand Up @@ -154,6 +161,17 @@ export class ChartComponent extends ChartBaseElement {
this.dynamicObjects.model.removeObject(series.id);
}),
);
// redraw background only when chart is resized
this.addRxSubscription(
this.canvasBoundsContainer.observeAnyBoundsChanged().subscribe(() => {
this.backgroundDrawPredicateSubj.next(false);
}),
);
this.addRxSubscription(
this.chartResizeHandler.canvasResized.subscribe(() => {
this.backgroundDrawPredicateSubj.next(true);
}),
);
}

/**
Expand Down
11 changes: 9 additions & 2 deletions src/chart/components/high_low/high-low.drawer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 '../../utils/canvas/canvas-drawing-functions.utils';

type MarkerType = 'high' | 'low';

Expand Down Expand Up @@ -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);
}
}

Expand All @@ -55,7 +59,10 @@ 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);
// 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;
}
const text = this.getMarkerText(yValue, type);
Expand Down Expand Up @@ -89,7 +96,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;
Expand Down
2 changes: 1 addition & 1 deletion src/chart/components/highlights/highlights.drawer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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];

Expand Down
2 changes: 1 addition & 1 deletion src/chart/components/volumes/volumes.drawer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
52 changes: 26 additions & 26 deletions src/chart/components/y_axis/price_labels/price-label.drawer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<Exclude<YAxisLabelMode, 'none'>, () => void> = {
'line': drawLineLabel,
'line-label': drawLineLabelLabel,
'label': drawLabelLabel,
}

if (mode !== 'none' && checkLabelInBoundaries(centralY, bounds, labelBoxHeight)) {
labelDrawerByMode[mode]();
}

ctx.restore();
Expand Down Expand Up @@ -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
Expand Down
45 changes: 27 additions & 18 deletions src/chart/components/y_axis/y-axis-labels.drawer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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);
Expand All @@ -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;
Expand Down Expand Up @@ -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,
Expand All @@ -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);
Expand All @@ -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;
Expand Down Expand Up @@ -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,
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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);
}
2 changes: 1 addition & 1 deletion src/chart/components/y_axis/y-axis.drawer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
41 changes: 18 additions & 23 deletions src/chart/drawers/chart-background.drawer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,40 +7,35 @@ import { Drawer } from './drawing-manager';
import { CanvasModel } from '../model/canvas.model';
import { ChartAreaTheme, FullChartConfig } from '../chart.config';
import { getDPR } from '../utils/device/device-pixel-ratio.utils';
import { deepEqual } from '../utils/object.utils';
import { floor } from '../utils/math.utils';
import { deepEqual } from '../utils/object.utils';

export class BackgroundDrawer implements Drawer {
constructor(private canvasModel: CanvasModel, private config: FullChartConfig) {}
constructor(
private canvasModel: CanvasModel,
private config: FullChartConfig,
private backgroundDrawPredicate: () => boolean = () => true,
) {}

// we need to save previous state to avoid unnecessary redraws
private prevState: Partial<ChartAreaTheme> = {};
private prevWidth = 0;
private prevHeight = 0;

draw(): void {
if (
deepEqual(this.config.colors.chartAreaTheme, this.prevState) &&
this.prevHeight === this.canvasModel.height &&
this.prevWidth === this.canvasModel.width
) {
return;
}
this.canvasModel.clear();
const ctx = this.canvasModel.ctx;
if (this.config.colors.chartAreaTheme.backgroundMode === 'gradient') {
const grd = ctx.createLinearGradient(0, 0, this.canvasModel.width, this.canvasModel.height);
grd.addColorStop(0, this.config.colors.chartAreaTheme.backgroundGradientTopColor);
grd.addColorStop(1, this.config.colors.chartAreaTheme.backgroundGradientBottomColor);
ctx.fillStyle = grd;
} else {
ctx.fillStyle = this.config.colors.chartAreaTheme.backgroundColor;
if (this.backgroundDrawPredicate() || !deepEqual(this.config.colors.chartAreaTheme, this.prevState)) {
this.canvasModel.clear();
const ctx = this.canvasModel.ctx;
if (this.config.colors.chartAreaTheme.backgroundMode === 'gradient') {
const grd = ctx.createLinearGradient(0, 0, this.canvasModel.width, this.canvasModel.height);
grd.addColorStop(0, this.config.colors.chartAreaTheme.backgroundGradientTopColor);
grd.addColorStop(1, this.config.colors.chartAreaTheme.backgroundGradientBottomColor);
ctx.fillStyle = grd;
} else {
ctx.fillStyle = this.config.colors.chartAreaTheme.backgroundColor;
}
ctx.fillRect(0, 0, this.canvasModel.width, this.canvasModel.height);
}
ctx.fillRect(0, 0, this.canvasModel.width, this.canvasModel.height);
// save prev state
this.prevState = { ...this.config.colors.chartAreaTheme };
this.prevWidth = this.canvasModel.width;
this.prevHeight = this.canvasModel.height;
}

getCanvasIds(): Array<string> {
Expand Down
Loading

0 comments on commit 8c16b8b

Please sign in to comment.