From 98cf0585d1ddbd05f185a122364191b6f45c7bb1 Mon Sep 17 00:00:00 2001 From: Kirill Bobkov Date: Fri, 15 Dec 2023 17:39:27 +0400 Subject: [PATCH] feat: removed redrawBackgroundChartArea and improved performace with native ctx.rect --- src/chart/bootstrap.ts | 4 --- src/chart/chart.config.ts | 4 +-- .../cross_tool/cross-tool.component.ts | 3 -- .../types/cross-and-labels.drawer.ts | 3 -- .../components/events/events.component.ts | 2 -- src/chart/components/events/events.drawer.ts | 16 ++++------ .../x_axis/x-axis-draw.functions.ts | 15 +++++----- .../components/x_axis/x-axis-labels.drawer.ts | 3 +- .../components/x_axis/x-axis.component.ts | 2 -- .../y_axis/price_labels/price-label.drawer.ts | 29 ++++++++++++------- .../y-axis-price-labels.drawer.ts | 8 ++--- .../components/y_axis/y-axis-labels.drawer.ts | 11 ++++--- src/chart/drawers/chart-background.drawer.ts | 3 +- 13 files changed, 43 insertions(+), 60 deletions(-) diff --git a/src/chart/bootstrap.ts b/src/chart/bootstrap.ts index 224f0f02..ff531f44 100755 --- a/src/chart/bootstrap.ts +++ b/src/chart/bootstrap.ts @@ -385,7 +385,6 @@ export default class ChartBootstrap { timeZoneModel, chartPanComponent, this.cursorHandler, - backgroundCanvasModel, ); this.chartComponents.push(this.xAxisComponent); this.userInputListenerComponents.push(this.xAxisComponent.xAxisScaleHandler); @@ -512,7 +511,6 @@ export default class ChartBootstrap { paneManager, this.crossEventProducer, this.hoverProducer, - this.backgroundCanvasModel, ); this.chartComponents.push(this.crossToolComponent); @@ -538,7 +536,6 @@ export default class ChartBootstrap { drawingManager, formatterFactory, this.cursorHandler, - backgroundCanvasModel, ); this.eventsComponent = eventsComponent; this.chartComponents.push(eventsComponent); @@ -564,7 +561,6 @@ export default class ChartBootstrap { const yAxisLabelsDrawer = new YAxisPriceLabelsDrawer( yAxisLabelsCanvasModel, - this.backgroundCanvasModel, this.canvasBoundsContainer, this.config, this.paneManager, diff --git a/src/chart/chart.config.ts b/src/chart/chart.config.ts index 0f175097..1faef192 100644 --- a/src/chart/chart.config.ts +++ b/src/chart/chart.config.ts @@ -498,7 +498,7 @@ export const getDefaultConfig = (): FullChartConfig => ({ }, ], yAxis: { - backgroundColor: 'transparent', + backgroundColor: 'rgba(20,20,19,1)', labelBoxColor: 'rgba(20,20,19,1)', labelTextColor: 'rgba(128,128,128,1)', labelInvertedTextColor: 'rgba(20,20,19,1)', @@ -538,7 +538,7 @@ export const getDefaultConfig = (): FullChartConfig => ({ prevDayClose: { boxColor: 'rgba(107,96,86,1)', textColor: 'rgba(255,255,255,1)' }, }, xAxis: { - backgroundColor: 'transparent', + backgroundColor: 'rgba(20,20,19,1)', labelTextColor: 'rgba(128,128,128,1)', }, navigationMap: { diff --git a/src/chart/components/cross_tool/cross-tool.component.ts b/src/chart/components/cross_tool/cross-tool.component.ts index 5e09a655..2feec13f 100644 --- a/src/chart/components/cross_tool/cross-tool.component.ts +++ b/src/chart/components/cross_tool/cross-tool.component.ts @@ -34,7 +34,6 @@ export class CrossToolComponent extends ChartBaseElement { private paneManager: PaneManager, crossEventProducer: CrossEventProducerComponent, hoverProducer: HoverProducerComponent, - private backgroundCanvasModel: CanvasModel, ) { super(); this.model = new CrossToolModel( @@ -68,7 +67,6 @@ export class CrossToolComponent extends ChartBaseElement { this.config, this.canvasBoundsContainer, this.paneManager, - this.backgroundCanvasModel, () => true, ), ); @@ -78,7 +76,6 @@ export class CrossToolComponent extends ChartBaseElement { this.config, this.canvasBoundsContainer, this.paneManager, - this.backgroundCanvasModel, () => true, true, ), 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 9275d122..25fb9a06 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,7 +5,6 @@ */ 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'; @@ -22,7 +21,6 @@ export class CrossAndLabelsDrawerType implements CrossToolTypeDrawer { private config: FullChartConfig, private canvasBoundsContainer: CanvasBoundsContainer, private paneManager: PaneManager, - private backgroundCanvasModel: CanvasModel, private crossDrawPredicate: () => boolean = () => true, private noLines?: boolean, ) {} @@ -165,7 +163,6 @@ export class CrossAndLabelsDrawerType implements CrossToolTypeDrawer { extent.yAxis.state, this.config.colors.yAxis, true, - this.backgroundCanvasModel.ctx, ); } } diff --git a/src/chart/components/events/events.component.ts b/src/chart/components/events/events.component.ts index 6b8ce95f..b78fbd46 100644 --- a/src/chart/components/events/events.component.ts +++ b/src/chart/components/events/events.component.ts @@ -31,7 +31,6 @@ export class EventsComponent extends ChartBaseElement { private drawingManager: DrawingManager, private formatterFactory: DateTimeFormatterFactory, cursorHandler: CursorHandler, - backgroundCanvasModel: CanvasModel, ) { super(); this.eventsXAxisLabelFormatterProvider = () => @@ -46,7 +45,6 @@ export class EventsComponent extends ChartBaseElement { hitTestCanvasModel.addSubscriber(eventsModel); const eventsDrawer = new EventsDrawer( - backgroundCanvasModel, canvasModel, chartModel, config, diff --git a/src/chart/components/events/events.drawer.ts b/src/chart/components/events/events.drawer.ts index e44e5f68..9284daf7 100644 --- a/src/chart/components/events/events.drawer.ts +++ b/src/chart/components/events/events.drawer.ts @@ -7,7 +7,6 @@ import { Bounds } from '../../model/bounds.model'; import { CanvasBoundsContainer, CanvasElement } from '../../canvas/canvas-bounds-container'; import { CustomIcon, FullChartConfig } from '../../chart.config'; import { CanvasModel } from '../../model/canvas.model'; -import { redrawBackgroundArea } from '../../drawers/chart-background.drawer'; import { Drawer } from '../../drawers/drawing-manager'; import { DateTimeFormatter } from '../../model/date-time.formatter'; import { ChartModel } from '../chart/chart.model'; @@ -31,7 +30,6 @@ export class EventsDrawer implements Drawer { private customIcons: Record = {}; constructor( - private backgroundCanvas: CanvasModel, private canvasModel: CanvasModel, private chartModel: ChartModel, private config: FullChartConfig, @@ -185,7 +183,7 @@ export class EventsDrawer implements Drawer { /** * This function is responsible for drawing a label on the canvas at a given x coordinate. The label contains a formatted timestamp of a given event. The function takes two parameters: x, which is the x coordinate where the label will be drawn, and event, which is an object containing information about the event, including its timestamp and type. * The function first gets the canvas context and the bounds of the x-axis. It then retrieves the font family, font height, and top padding from the configuration object. The y coordinate of the label is calculated based on the font height, top padding, and the y coordinate of the x-axis bounds. The font is set using the retrieved font family and font height. - * The timestamp of the event is formatted using a formatter provider function. The width of the label is calculated using the canvas context's measureText() method. The function then calls another function, redrawBackgroundArea(), to hide the regular x-axis label that may overlap with the event label. + * The timestamp of the event is formatted using a formatter provider function. The width of the label is calculated using the canvas context's measureText() method. The function then draws rectangle with xAxis background, to be on to hide the regular x-axis label that may overlap with the event label. * Finally, the function sets the fill style of the canvas context to the color associated with the event type in the configuration object. The label text is then drawn on the canvas context at the calculated x and y coordinates. */ drawLabel(x: number, event: EventWithId) { @@ -199,14 +197,10 @@ export class EventsDrawer implements Drawer { const labelText = this.formatterProvider()(event.timestamp); const width = ctx.measureText(labelText).width; // label can overlap with regular x-axis label, so we need to hide regular x-axis label - redrawBackgroundArea( - this.backgroundCanvas.ctx, - ctx, - x - width / 2, - xAxisBounds.y + 1, - width, - xAxisBounds.height - 1, - ); + ctx.fillStyle = this.config.colors.xAxis.backgroundColor; + ctx.strokeStyle = this.config.colors.xAxis.backgroundColor; + ctx.fillRect(x - width / 2, xAxisBounds.y + 1, width, xAxisBounds.height - 1); + ctx.fillStyle = this.config.colors.events[event.type].color; ctx.fillText(labelText, x - width / 2, y); diff --git a/src/chart/components/x_axis/x-axis-draw.functions.ts b/src/chart/components/x_axis/x-axis-draw.functions.ts index c0764ef6..77ec480a 100644 --- a/src/chart/components/x_axis/x-axis-draw.functions.ts +++ b/src/chart/components/x_axis/x-axis-draw.functions.ts @@ -5,7 +5,6 @@ */ import { CanvasBoundsContainer, CanvasElement } from '../../canvas/canvas-bounds-container'; import { FullChartConfig } from '../../chart.config'; -import { redrawBackgroundArea } from '../../drawers/chart-background.drawer'; import { XAxisLabel } from './x-axis-labels.model'; const DEFAULT_X_LABEL_PADDING = { x: 4, y: 4 }; @@ -20,7 +19,6 @@ export type LabelAlign = 'start' | 'end' | 'middle'; * @param label */ export function drawXAxisLabel( - backgroundCtx: CanvasRenderingContext2D, ctx: CanvasRenderingContext2D, canvasBoundsContainer: CanvasBoundsContainer, config: FullChartConfig, @@ -28,9 +26,10 @@ export function drawXAxisLabel( ): void { const alignType = label.alignType ?? 'middle'; const { fontSize, fontFamily, padding } = config.components.xAxis; + const xAxisColors = config.colors.xAxis; const offsetTop = padding.top ?? 0; - const xAxis = canvasBoundsContainer.getBounds(CanvasElement.X_AXIS); + const xAxisBounds = canvasBoundsContainer.getBounds(CanvasElement.X_AXIS); ctx.save(); ctx.font = `bold ${fontSize}px ${fontFamily}`; const labelWidth = ctx.measureText(label.text).width; @@ -50,19 +49,21 @@ export function drawXAxisLabel( break; } // label can overlap with regular x-axis label, so we need to hide regular x-axis label - redrawBackgroundArea(backgroundCtx, ctx, boxStart, xAxis.y, boxWidth, xAxis.height - 1); + ctx.fillStyle = xAxisColors.backgroundColor; + ctx.strokeStyle = xAxisColors.backgroundColor; + ctx.fillRect(boxStart, xAxisBounds.y, boxWidth, xAxisBounds.height); if (alignType !== 'middle') { ctx.strokeStyle = label.color; ctx.beginPath(); - ctx.moveTo(x, xAxis.y); - ctx.lineTo(x, xAxis.y + xAxis.height); + ctx.moveTo(x, xAxisBounds.y); + ctx.lineTo(x, xAxisBounds.y + xAxisBounds.height); ctx.stroke(); } ctx.fillStyle = label.color; const xTextPos = boxStart + DEFAULT_X_LABEL_PADDING.x; - const yTextPos = xAxis.y + offsetTop + fontSize; // -2 for vertical adjustment + const yTextPos = xAxisBounds.y + offsetTop + fontSize; // -2 for vertical adjustment ctx.fillText(label.text, xTextPos, yTextPos); ctx.restore(); } diff --git a/src/chart/components/x_axis/x-axis-labels.drawer.ts b/src/chart/components/x_axis/x-axis-labels.drawer.ts index b9c9412c..ae718304 100644 --- a/src/chart/components/x_axis/x-axis-labels.drawer.ts +++ b/src/chart/components/x_axis/x-axis-labels.drawer.ts @@ -18,7 +18,6 @@ import { fillRect } from '../../utils/canvas/canvas-drawing-functions.utils'; */ export class XAxisLabelsDrawer implements Drawer { constructor( - private backgroundCanvasModel: CanvasModel, private config: FullChartConfig, private canvasModel: CanvasModel, private canvasBoundsContainer: CanvasBoundsContainer, @@ -36,7 +35,7 @@ export class XAxisLabelsDrawer implements Drawer { const ctx = this.canvasModel.ctx; this.drawHighlightedBackgroundBetweenLabels(); this.xAxisLabelsModel.labels.forEach(l => { - drawXAxisLabel(this.backgroundCanvasModel.ctx, ctx, this.canvasBoundsContainer, this.config, l); + drawXAxisLabel(ctx, this.canvasBoundsContainer, this.config, l); }); } diff --git a/src/chart/components/x_axis/x-axis.component.ts b/src/chart/components/x_axis/x-axis.component.ts index e55274bb..99767e78 100644 --- a/src/chart/components/x_axis/x-axis.component.ts +++ b/src/chart/components/x_axis/x-axis.component.ts @@ -51,7 +51,6 @@ export class XAxisComponent extends ChartBaseElement { private timeZoneModel: TimeZoneModel, chartPanComponent: ChartPanComponent, cursorHandler: CursorHandler, - backgroundCanvasModel: CanvasModel, ) { super(); const xAxisLabelsGenerator = new XAxisTimeLabelsGenerator( @@ -79,7 +78,6 @@ export class XAxisComponent extends ChartBaseElement { ); xAxisCompositeDrawer.addDrawer(this.xAxisDrawer); this.xAxisLabelsDrawer = new XAxisLabelsDrawer( - backgroundCanvasModel, config, canvasModel, canvasBoundsContainer, 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 4ceebf61..ee4c308f 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 @@ -11,7 +11,6 @@ import { YAxisAlign, 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'; @@ -41,13 +40,12 @@ export const priceLabelDrawersMap: Record = { */ export function drawLabel( ctx: CanvasRenderingContext2D, - backgroundCtx: CanvasRenderingContext2D, bounds: Bounds, paneBounds: Bounds, visualLabel: VisualYAxisLabel, canvasBoundsContainer: CanvasBoundsContainer, config: YAxisConfig, - colors: FullChartColors['yAxis'], + colors: FullChartColors, ) { const centralY = visualLabel.y; const text = visualLabel.labelText; @@ -76,7 +74,7 @@ export function drawLabel( const showLine = isLineVisible(bounds, labelY, labelBoxHeight); const _drawDescription = () => - showDescription && drawDescription(backgroundCtx, ctx, bounds, paneBounds, visualLabel, config.align, config); + showDescription && drawDescription(ctx, paneBounds, visualLabel, config, colors); let lineXStart: number; let lineXEnd: number; @@ -94,7 +92,7 @@ export function drawLabel( const lineY = visualLabel.lineY ?? visualLabel.y; const _drawLine = () => showLine && avoidAntialiasing(ctx, () => drawLine(ctx, lineXStart, lineY, lineXEnd, lineY, 1)); - const _drawLabel = () => drawLabel(ctx, bounds, text, centralY, visualLabel, config, colors, false, backgroundCtx); + const _drawLabel = () => drawLabel(ctx, bounds, text, centralY, visualLabel, config, colors.yAxis, false); const drawLineLabel = () => { _drawLine(); @@ -129,14 +127,13 @@ const isLineVisible = (bounds: Bounds, labelY: number, labelBoxHeight: number) = labelY > bounds.y + labelBoxHeight / 2 && labelY < bounds.y + bounds.height - labelBoxHeight / 2; function drawDescription( - backgroundCtx: CanvasRenderingContext2D, ctx: CanvasRenderingContext2D, - labelBounds: Bounds, paneBounds: Bounds, visualLabel: VisualYAxisLabel, - align: YAxisAlign = 'right', yAxisState: YAxisConfig, + colors: FullChartColors, ): void { + const align: YAxisAlign = yAxisState.align || 'right'; const description = visualLabel.description; if (!description || description.length === 0) { return; @@ -160,8 +157,19 @@ function drawDescription( const boundsEnd = paneBounds.x + paneBounds.width; const x = align === 'right' ? boundsEnd - rectWidth : paneBounds.x + descriptionPadding; - redrawBackgroundArea(backgroundCtx, ctx, x, labelBoxY, width, labelBoxHeight, 0.8); - + if (colors.chartAreaTheme.backgroundMode === 'gradient' && align === 'right') { + ctx.fillStyle = colors.chartAreaTheme.backgroundGradientBottomColor; + ctx.strokeStyle = colors.chartAreaTheme.backgroundGradientBottomColor; + } + if (colors.chartAreaTheme.backgroundMode === 'gradient' && align === 'left') { + ctx.fillStyle = colors.chartAreaTheme.backgroundGradientTopColor; + ctx.strokeStyle = colors.chartAreaTheme.backgroundGradientTopColor; + } + if (colors.chartAreaTheme.backgroundMode === 'regular') { + ctx.fillStyle = colors.chartAreaTheme.backgroundColor; + ctx.strokeStyle = colors.chartAreaTheme.backgroundColor; + } + ctx.fillRect(x, labelBoxY, width, labelBoxHeight); ctx.fillStyle = visualLabel.descColor ?? visualLabel.bgColor; ctx.font = textFont; const xTextBounds = @@ -170,6 +178,5 @@ function drawDescription( : paneBounds.x + descriptionPadding * 2; ctx.fillText(description, xTextBounds, centralY + fontHeight / 2 - 1); // -1 for font height adjustment - ctx.restore(); } diff --git a/src/chart/components/y_axis/price_labels/y-axis-price-labels.drawer.ts b/src/chart/components/y_axis/price_labels/y-axis-price-labels.drawer.ts index e72e0581..79e91d27 100644 --- a/src/chart/components/y_axis/price_labels/y-axis-price-labels.drawer.ts +++ b/src/chart/components/y_axis/price_labels/y-axis-price-labels.drawer.ts @@ -20,7 +20,6 @@ import { LabelGroup, VisualYAxisLabel } from './y-axis-labels.model'; export class YAxisPriceLabelsDrawer implements Drawer { constructor( private yAxisLabelsCanvasModel: CanvasModel, - private backgroundCanvasModel: CanvasModel, private canvasBoundsContainer: CanvasBoundsContainer, private fullConfig: FullChartConfig, private paneManager: PaneManager, @@ -28,7 +27,6 @@ export class YAxisPriceLabelsDrawer implements Drawer { draw() { const ctx = this.yAxisLabelsCanvasModel.ctx; - const backgroundCtx = this.backgroundCanvasModel.ctx; this.paneManager.yExtents.forEach(extent => { if (extent.yAxis.state.visible) { @@ -41,13 +39,12 @@ export class YAxisPriceLabelsDrawer implements Drawer { l.labels.forEach(vl => drawLabel( ctx, - backgroundCtx, bounds, paneBounds, vl, this.canvasBoundsContainer, extent.yAxis.state, - this.fullConfig.colors.yAxis, + this.fullConfig.colors, ), ); }); @@ -55,13 +52,12 @@ export class YAxisPriceLabelsDrawer implements Drawer { Object.values(extent.yAxis.model.fancyLabelsModel.customLabels).forEach(l => drawLabel( ctx, - backgroundCtx, yAxisBounds, paneBounds, l, this.canvasBoundsContainer, extent.yAxis.state, - this.fullConfig.colors.yAxis, + this.fullConfig.colors, ), ); } 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 c9e6490f..31b36f3e 100644 --- a/src/chart/components/y_axis/y-axis-labels.drawer.ts +++ b/src/chart/components/y_axis/y-axis-labels.drawer.ts @@ -4,7 +4,6 @@ * If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import { YAxisConfig, FullChartColors, getFontFromConfig } from '../../chart.config'; -import { redrawBackgroundArea } from '../../drawers/chart-background.drawer'; import { Bounds } from '../../model/bounds.model'; import { drawPriceLabel, drawRoundedRect } from '../../utils/canvas/canvas-drawing-functions.utils'; import { calculateSymbolHeight, calculateTextWidth } from '../../utils/canvas/canvas-font-measure-tool.utils'; @@ -200,11 +199,10 @@ export function drawPlainLabel( yAxisState: YAxisConfig, yAxisColors: FullChartColors['yAxis'], checkBoundaries: boolean = true, - backgroundCtx?: CanvasRenderingContext2D, ) { const align = yAxisState.align; const textFont = config.textFont ?? getFontFromConfig(yAxisState); - const bgColor = config.bgColor; + const bgColor = yAxisColors.backgroundColor; const textColor = config.textColor ?? getLabelTextColorByBackgroundColor(bgColor, yAxisColors.labelTextColor, yAxisColors.labelInvertedTextColor); @@ -230,14 +228,15 @@ export function drawPlainLabel( const xTextOffset = yAxisState.labelBoxMargin.end; ctx.font = textFont; const textWidth = calculateTextWidth(text, ctx, textFont); - const marginEnd = xTextOffset - paddingEnd; const width = paddingStart !== undefined ? textWidth + paddingStart + paddingEnd : bounds.width - marginEnd; const x = align === 'right' ? bounds.x + bounds.width - marginEnd - width : bounds.x + marginEnd; - const textX = align === 'right' ? bounds.x + bounds.width - textWidth - xTextOffset : xTextOffset; - backgroundCtx && redrawBackgroundArea(backgroundCtx, ctx, x, labelBoxTopY, width, labelBoxHeight); + // label can overlap with regular price y-axis label, so we need to hide regular y-axis label + ctx.fillStyle = bgColor; + ctx.strokeStyle = bgColor; + ctx.fillRect(x, labelBoxTopY, width, labelBoxHeight); ctx.fillStyle = textColor; ctx.fillText(text, textX, centralY + fontHeight / 2 - 1); // -1 for font height adjustment diff --git a/src/chart/drawers/chart-background.drawer.ts b/src/chart/drawers/chart-background.drawer.ts index 309c2db9..91bc23b0 100644 --- a/src/chart/drawers/chart-background.drawer.ts +++ b/src/chart/drawers/chart-background.drawer.ts @@ -25,7 +25,7 @@ export class BackgroundDrawer implements Drawer { 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); + const grd = ctx.createLinearGradient(0, 0 + this.canvasModel.height / 2, this.canvasModel.width, 0 + this.canvasModel.height / 2); grd.addColorStop(0, this.config.colors.chartAreaTheme.backgroundGradientTopColor); grd.addColorStop(1, this.config.colors.chartAreaTheme.backgroundGradientBottomColor); ctx.fillStyle = grd; @@ -45,6 +45,7 @@ export class BackgroundDrawer implements Drawer { // this function in used in case when // some entity can overlap with another chart entity, so we need to hide the another entity +// it has very (!!!) poor perfomance, use it carefully export const redrawBackgroundArea = ( backgroundCtx: CanvasRenderingContext2D, ctx: CanvasRenderingContext2D,