diff --git a/.huskyrc.js b/.huskyrc.js deleted file mode 100644 index 0a83f71706..0000000000 --- a/.huskyrc.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - hooks: { - 'commit-msg': 'commitlint -E HUSKY_GIT_PARAMS && yarn typecheck:all && yarn pq && lint-staged', - }, -}; diff --git a/package.json b/package.json index bee84e7e75..f471c409e4 100644 --- a/package.json +++ b/package.json @@ -130,7 +130,6 @@ "eslint-plugin-react-hooks": "^4.2.0", "eslint-plugin-unicorn": "^25.0.1", "html-webpack-plugin": "^4.5.2", - "husky": "^4.3.6", "jest": "^26.6.3", "jest-canvas-mock": "^2.3.1", "jest-extended": "^0.11.5", diff --git a/packages/charts/src/chart_types/heatmap/layout/viewmodel/viewmodel.ts b/packages/charts/src/chart_types/heatmap/layout/viewmodel/viewmodel.ts index 6ccdbeed64..bbeb135675 100644 --- a/packages/charts/src/chart_types/heatmap/layout/viewmodel/viewmodel.ts +++ b/packages/charts/src/chart_types/heatmap/layout/viewmodel/viewmodel.ts @@ -15,7 +15,7 @@ import { Box, TextMeasure } from '../../../../common/text_utils'; import { ScaleContinuous } from '../../../../scales'; import { ScaleType } from '../../../../scales/constants'; import { SettingsSpec } from '../../../../specs'; -import { CanvasTextBBoxCalculator } from '../../../../utils/bbox/canvas_text_bbox_calculator'; +import { withTextMeasure } from '../../../../utils/bbox/canvas_text_bbox_calculator'; import { snapDateToESInterval } from '../../../../utils/chrono/elasticsearch'; import { clamp, range } from '../../../../utils/common'; import { Dimensions } from '../../../../utils/dimensions'; @@ -56,21 +56,16 @@ function getValuesInRange( /** * Resolves the maximum number of ticks based on the chart width and sample label based on formatter config. */ -function getTicks(chartWidth: number, xAxisLabelConfig: Config['xAxisLabel']): number { - const bboxCompute = new CanvasTextBBoxCalculator(); - const labelSample = xAxisLabelConfig.formatter(Date.now()); - const { width } = bboxCompute.compute( - labelSample, - xAxisLabelConfig.padding, - xAxisLabelConfig.fontSize, - xAxisLabelConfig.fontFamily, - ); - bboxCompute.destroy(); - const maxTicks = Math.floor(chartWidth / width); - // Dividing by 2 is a temp fix to make sure {@link ScaleContinuous} won't produce - // to many ticks creating nice rounded tick values - // TODO add support for limiting the number of tick in {@link ScaleContinuous} - return maxTicks / 2; +function getTicks(chartWidth: number, { formatter, padding, fontSize, fontFamily }: Config['xAxisLabel']): number { + return withTextMeasure((textMeasure) => { + const labelSample = formatter(Date.now()); + const { width } = textMeasure(labelSample, padding, fontSize, fontFamily); + const maxTicks = Math.floor(chartWidth / width); + // Dividing by 2 is a temp fix to make sure {@link ScaleContinuous} won't produce + // to many ticks creating nice rounded tick values + // TODO add support for limiting the number of tick in {@link ScaleContinuous} + return maxTicks / 2; + }); } /** @internal */ diff --git a/packages/charts/src/chart_types/heatmap/state/selectors/get_x_axis_right_overflow.ts b/packages/charts/src/chart_types/heatmap/state/selectors/get_x_axis_right_overflow.ts index 53dcfb3ac1..a06de80717 100644 --- a/packages/charts/src/chart_types/heatmap/state/selectors/get_x_axis_right_overflow.ts +++ b/packages/charts/src/chart_types/heatmap/state/selectors/get_x_axis_right_overflow.ts @@ -9,7 +9,7 @@ import { ScaleContinuous } from '../../../../scales'; import { ScaleType } from '../../../../scales/constants'; import { createCustomCachedSelector } from '../../../../state/create_selector'; -import { CanvasTextBBoxCalculator } from '../../../../utils/bbox/canvas_text_bbox_calculator'; +import { withTextMeasure } from '../../../../utils/bbox/canvas_text_bbox_calculator'; import { getHeatmapConfigSelector } from './get_heatmap_config'; import { getHeatmapTableSelector } from './get_heatmap_table'; @@ -37,13 +37,11 @@ export const getXAxisRightOverflow = createCustomCachedSelector( timeZone, }, ); - const bboxCompute = new CanvasTextBBoxCalculator(); - const maxTextWidth = timeScale.ticks().reduce((acc, d) => { - const text = formatter(d); - const textSize = bboxCompute.compute(text, padding, fontSize, fontFamily, 1); - return Math.max(acc, textSize.width + padding); - }, 0); - bboxCompute.destroy(); - return maxTextWidth / 2; + return withTextMeasure( + (textMeasure) => + timeScale.ticks().reduce((acc, d) => { + return Math.max(acc, textMeasure(formatter(d), padding, fontSize, fontFamily, 1).width + padding); + }, 0) / 2, + ); }, ); diff --git a/packages/charts/src/chart_types/xy_chart/axes/axes_sizes.ts b/packages/charts/src/chart_types/xy_chart/axes/axes_sizes.ts index a566b124d1..8cf1c2d85a 100644 --- a/packages/charts/src/chart_types/xy_chart/axes/axes_sizes.ts +++ b/packages/charts/src/chart_types/xy_chart/axes/axes_sizes.ts @@ -8,7 +8,7 @@ import { SmallMultiplesSpec } from '../../../specs'; import { Position } from '../../../utils/common'; -import { getSimplePadding, PerSideDistance } from '../../../utils/dimensions'; +import { innerPad, outerPad, PerSideDistance } from '../../../utils/dimensions'; import { AxisId } from '../../../utils/ids'; import { AxisStyle, Theme } from '../../../utils/themes/theme'; import { getSpecsById } from '../state/utils/spec'; @@ -16,8 +16,6 @@ import { isVerticalAxis } from '../utils/axis_type_utils'; import { AxisViewModel, getTitleDimension, shouldShowTicks } from '../utils/axis_utils'; import { AxisSpec } from '../utils/specs'; -const nullPadding = (): PerSideDistance => ({ left: 0, right: 0, top: 0, bottom: 0 }); - /** @internal */ export function computeAxesSizes( { axes: sharedAxesStyles, chartMargins }: Theme, @@ -26,65 +24,43 @@ export function computeAxesSizes( axisSpecs: AxisSpec[], smSpec?: SmallMultiplesSpec, ): PerSideDistance & { margin: { left: number } } { - const axisMainSize = nullPadding(); - const axisLabelOverflow = nullPadding(); + const axisMainSize: PerSideDistance = { left: 0, right: 0, top: 0, bottom: 0 }; + const axisLabelOverflow: PerSideDistance = { left: 0, right: 0, top: 0, bottom: 0 }; axisDimensions.forEach(({ maxLabelBboxWidth = 0, maxLabelBboxHeight = 0, isHidden }, id) => { const axisSpec = getSpecsById(axisSpecs, id); - if (!axisSpec || isHidden) { + if (isHidden || !axisSpec) { return; } - const { tickLine, axisTitle, axisPanelTitle, tickLabel } = axesStyles.get(id) ?? sharedAxesStyles; - const showTicks = shouldShowTicks(tickLine, axisSpec.hide); const { position, title } = axisSpec; - const labelPadding = getSimplePadding(tickLabel.padding); - const labelPaddingSum = tickLabel.visible ? labelPadding.inner + labelPadding.outer : 0; - - const tickDimension = showTicks ? tickLine.size + tickLine.padding : 0; - const titleDimension = title ? getTitleDimension(axisTitle) : 0; + const { tickLine, axisTitle, axisPanelTitle, tickLabel } = axesStyles.get(id) ?? sharedAxesStyles; const hasPanelTitle = Boolean(isVerticalAxis(position) ? smSpec?.splitVertically : smSpec?.splitHorizontally); const panelTitleDimension = hasPanelTitle ? getTitleDimension(axisPanelTitle) : 0; + const labelPaddingSum = tickLabel.visible ? innerPad(tickLabel.padding) + outerPad(tickLabel.padding) : 0; + const titleDimension = title ? getTitleDimension(axisTitle) : 0; + const tickDimension = shouldShowTicks(tickLine, axisSpec.hide) ? tickLine.size + tickLine.padding : 0; const axisDimension = labelPaddingSum + tickDimension + titleDimension + panelTitleDimension; - const maxAxisHeight = tickLabel.visible ? maxLabelBboxHeight + axisDimension : axisDimension; - const maxAxisWidth = tickLabel.visible ? maxLabelBboxWidth + axisDimension : axisDimension; - switch (position) { - case Position.Top: - axisMainSize.top += maxAxisHeight + chartMargins.top; - // find the max half label size to accommodate the left/right labels - // TODO use first and last labels - axisLabelOverflow.left = Math.max(axisLabelOverflow.left, maxLabelBboxWidth / 2); - axisLabelOverflow.right = Math.max(axisLabelOverflow.right, maxLabelBboxWidth / 2); - break; - case Position.Bottom: - axisMainSize.bottom += maxAxisHeight + chartMargins.bottom; - // find the max half label size to accommodate the left/right labels - // TODO use first and last labels - axisLabelOverflow.left = Math.max(axisLabelOverflow.left, maxLabelBboxWidth / 2); - axisLabelOverflow.right = Math.max(axisLabelOverflow.right, maxLabelBboxWidth / 2); - break; - case Position.Right: - axisMainSize.right += maxAxisWidth + chartMargins.right; - // TODO use first and last labels - axisLabelOverflow.top = Math.max(axisLabelOverflow.top, maxLabelBboxHeight / 2); - axisLabelOverflow.bottom = Math.max(axisLabelOverflow.bottom, maxLabelBboxHeight / 2); - break; - case Position.Left: - default: - axisMainSize.left += maxAxisWidth + chartMargins.left; - // TODO use first and last labels - axisLabelOverflow.top = Math.max(axisLabelOverflow.top, maxLabelBboxHeight / 2); - axisLabelOverflow.bottom = Math.max(axisLabelOverflow.bottom, maxLabelBboxHeight / 2); + // TODO use first and last labels + if (isVerticalAxis(position)) { + axisLabelOverflow.top = Math.max(axisLabelOverflow.top, maxLabelBboxHeight / 2); + axisLabelOverflow.bottom = Math.max(axisLabelOverflow.bottom, maxLabelBboxHeight / 2); + const maxAxisWidth = axisDimension + (tickLabel.visible ? maxLabelBboxWidth : 0); + axisMainSize.left += position === Position.Left ? maxAxisWidth + chartMargins.left : 0; + axisMainSize.right += position === Position.Right ? maxAxisWidth + chartMargins.right : 0; + } else { + // find the max half label size to accommodate the left/right labels + axisLabelOverflow.left = Math.max(axisLabelOverflow.left, maxLabelBboxWidth / 2); + axisLabelOverflow.right = Math.max(axisLabelOverflow.right, maxLabelBboxWidth / 2); + const maxAxisHeight = axisDimension + (tickLabel.visible ? maxLabelBboxHeight : 0); + axisMainSize.top += position === Position.Top ? maxAxisHeight + chartMargins.top : 0; + axisMainSize.bottom += position === Position.Bottom ? maxAxisHeight + chartMargins.bottom : 0; } }); + const left = Math.max(axisLabelOverflow.left + chartMargins.left, axisMainSize.left); - return { - margin: { - left: left - axisMainSize.left, - }, - left, - right: Math.max(axisLabelOverflow.right + chartMargins.right, axisMainSize.right), - top: Math.max(axisLabelOverflow.top + chartMargins.top, axisMainSize.top), - bottom: Math.max(axisLabelOverflow.bottom + chartMargins.bottom, axisMainSize.bottom), - }; + const right = Math.max(axisLabelOverflow.right + chartMargins.right, axisMainSize.right); + const top = Math.max(axisLabelOverflow.top + chartMargins.top, axisMainSize.top); + const bottom = Math.max(axisLabelOverflow.bottom + chartMargins.bottom, axisMainSize.bottom); + return { left, right, top, bottom, margin: { left: left - axisMainSize.left } }; } diff --git a/packages/charts/src/chart_types/xy_chart/renderer/canvas/annotations/lines.ts b/packages/charts/src/chart_types/xy_chart/renderer/canvas/annotations/lines.ts index f3f6db3faf..cd58e23847 100644 --- a/packages/charts/src/chart_types/xy_chart/renderer/canvas/annotations/lines.ts +++ b/packages/charts/src/chart_types/xy_chart/renderer/canvas/annotations/lines.ts @@ -31,9 +31,7 @@ export function renderLineAnnotations( dash: lineStyle.line.dash, }; - annotations.forEach(({ linePathPoints, panel }) => { - withPanelTransform(ctx, panel, rotation, renderingArea, (ctx) => { - renderMultiLine(ctx, [linePathPoints], stroke); - }); - }); + annotations.forEach(({ linePathPoints, panel }) => + withPanelTransform(ctx, panel, rotation, renderingArea, () => renderMultiLine(ctx, [linePathPoints], stroke)), + ); } diff --git a/packages/charts/src/chart_types/xy_chart/renderer/canvas/annotations/rect.ts b/packages/charts/src/chart_types/xy_chart/renderer/canvas/annotations/rect.ts index 1e7b07f206..51b420575f 100644 --- a/packages/charts/src/chart_types/xy_chart/renderer/canvas/annotations/rect.ts +++ b/packages/charts/src/chart_types/xy_chart/renderer/canvas/annotations/rect.ts @@ -25,22 +25,12 @@ export function renderRectAnnotations( ) { const fillColor = stringToRGB(rectStyle.fill); fillColor.opacity *= rectStyle.opacity; - const fill: Fill = { - color: fillColor, - }; + const fill: Fill = { color: fillColor }; const strokeColor = stringToRGB(rectStyle.stroke); strokeColor.opacity *= rectStyle.opacity; - const stroke: Stroke = { - color: strokeColor, - width: rectStyle.strokeWidth, - }; + const stroke: Stroke = { color: strokeColor, width: rectStyle.strokeWidth }; - const rectsLength = annotations.length; - - for (let i = 0; i < rectsLength; i++) { - const { rect, panel } = annotations[i]; - withPanelTransform(ctx, panel, rotation, renderingArea, (ctx) => { - renderRect(ctx, rect, fill, stroke); - }); - } + annotations.forEach(({ rect, panel }) => + withPanelTransform(ctx, panel, rotation, renderingArea, () => renderRect(ctx, rect, fill, stroke)), + ); } diff --git a/packages/charts/src/chart_types/xy_chart/renderer/canvas/grids.ts b/packages/charts/src/chart_types/xy_chart/renderer/canvas/grids.ts index e288f16822..3e42b9bc24 100644 --- a/packages/charts/src/chart_types/xy_chart/renderer/canvas/grids.ts +++ b/packages/charts/src/chart_types/xy_chart/renderer/canvas/grids.ts @@ -22,14 +22,12 @@ interface GridProps { } /** @internal */ -export function renderGrids(ctx: CanvasRenderingContext2D, props: GridProps) { - const { - perPanelGridLines, - renderingArea: { left, top }, - } = props; +export function renderGrids( + ctx: CanvasRenderingContext2D, + { perPanelGridLines, renderingArea: { left, top } }: GridProps, +) { withContext(ctx, () => { ctx.translate(left, top); - perPanelGridLines.forEach(({ lineGroups, panelAnchor: { x, y } }) => { withContext(ctx, () => { ctx.translate(x, y); diff --git a/packages/charts/src/chart_types/xy_chart/renderer/canvas/panels/global_title.ts b/packages/charts/src/chart_types/xy_chart/renderer/canvas/panels/global_title.ts deleted file mode 100644 index ebae5ce917..0000000000 --- a/packages/charts/src/chart_types/xy_chart/renderer/canvas/panels/global_title.ts +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { Position } from '../../../../../utils/common'; -import { getSimplePadding } from '../../../../../utils/dimensions'; -import { Point } from '../../../../../utils/point'; -import { isHorizontalAxis } from '../../../utils/axis_type_utils'; -import { getTitleDimension, shouldShowTicks } from '../../../utils/axis_utils'; -import { AxisProps } from '../axes'; // todo revise if it should rely on AxisProps or axis-anything -import { renderText } from '../primitives/text'; -import { renderDebugRect } from '../utils/debug'; -import { getFontStyle } from './panel_title'; - -type TitleProps = Pick & { - anchorPoint: Point; -}; - -/** @internal */ -export function renderTitle(ctx: CanvasRenderingContext2D, props: TitleProps) { - if (!props.axisSpec.title || !props.axisStyle.axisTitle.visible) { - return; - } - - const { - size: { width, height }, - axisSpec: { position, hide: hideAxis, title }, - dimension: { maxLabelBboxWidth, maxLabelBboxHeight }, - axisStyle: { axisTitle, axisPanelTitle, tickLine, tickLabel }, - anchorPoint, - debug, - panelTitle, - } = props; - - const font = getFontStyle(axisTitle); - const titlePadding = getSimplePadding(axisTitle.visible && title ? axisTitle.padding : 0); - const panelTitleDimension = panelTitle ? getTitleDimension(axisPanelTitle) : 0; - const tickDimension = shouldShowTicks(tickLine, hideAxis) ? tickLine.size + tickLine.padding : 0; - const labelPadding = getSimplePadding(tickLabel.padding); - const horizontal = isHorizontalAxis(props.axisSpec.position); - const maxLabelBoxSize = horizontal ? maxLabelBboxHeight : maxLabelBboxWidth; - const labelSize = tickLabel.visible ? maxLabelBoxSize + labelPadding.outer + labelPadding.inner : 0; - const offset = - position === Position.Left || position === Position.Top - ? titlePadding.outer - : labelSize + tickDimension + titlePadding.inner + panelTitleDimension; - - const left = anchorPoint.x + (horizontal ? 0 : offset); - const top = anchorPoint.y + (horizontal ? offset : height); - - if (debug) { - renderDebugRect( - ctx, - { x: left, y: top, width: horizontal ? width : height, height: font.fontSize }, - undefined, - undefined, - horizontal ? 0 : -90, - ); - } - - renderText( - ctx, - { x: left + (horizontal ? width : font.fontSize) / 2, y: top + (horizontal ? font.fontSize : -height) / 2 }, - title ?? '', // title is always a string due to caller; consider turning `title` to be obligate string upstream - font, - horizontal ? 0 : -90, - ); -} diff --git a/packages/charts/src/chart_types/xy_chart/renderer/canvas/panels/panel_title.ts b/packages/charts/src/chart_types/xy_chart/renderer/canvas/panels/panel_title.ts deleted file mode 100644 index e111cb1b66..0000000000 --- a/packages/charts/src/chart_types/xy_chart/renderer/canvas/panels/panel_title.ts +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { Position } from '../../../../../utils/common'; -import { getSimplePadding } from '../../../../../utils/dimensions'; -import { TextStyle } from '../../../../../utils/themes/theme'; // todo revise if it should rely on axis-anything -import { isHorizontalAxis } from '../../../utils/axis_type_utils'; -import { getTitleDimension, shouldShowTicks } from '../../../utils/axis_utils'; -import { AxisProps } from '../axes'; -import { renderText, TextFont } from '../primitives/text'; -import { renderDebugRect } from '../utils/debug'; - -type PanelTitleProps = Pick; - -/** @internal */ -export function renderPanelTitle(ctx: CanvasRenderingContext2D, props: PanelTitleProps) { - const { - axisSpec: { position }, - axisStyle: { axisPanelTitle }, - panelTitle, - } = props; - if (!panelTitle || !axisPanelTitle.visible) { - return null; - } - return isHorizontalAxis(position) ? renderHorizontalTitle(ctx, props) : renderVerticalTitle(ctx, props); -} - -function renderVerticalTitle(ctx: CanvasRenderingContext2D, props: PanelTitleProps) { - const { - size: { height }, - axisSpec: { position, hide: hideAxis, title }, - dimension: { maxLabelBboxWidth }, - axisStyle: { axisTitle, axisPanelTitle, tickLine, tickLabel }, - debug, - panelTitle, - } = props; - if (!panelTitle) { - return null; - } - const font = getFontStyle(axisPanelTitle); - const tickDimension = shouldShowTicks(tickLine, hideAxis) ? tickLine.size + tickLine.padding : 0; - const panelTitlePadding = getSimplePadding(axisPanelTitle.visible && panelTitle ? axisPanelTitle.padding : 0); - const titleDimension = title ? getTitleDimension(axisTitle) : 0; - const labelPadding = getSimplePadding(tickLabel.padding); - const labelWidth = tickLabel.visible ? labelPadding.outer + maxLabelBboxWidth + labelPadding.inner : 0; - const top = height; - const left = - position === Position.Left - ? titleDimension + panelTitlePadding.outer - : tickDimension + labelWidth + panelTitlePadding.inner; - - if (debug) { - renderDebugRect(ctx, { x: left, y: top, width: height, height: font.fontSize }, undefined, undefined, -90); - } - - renderText( - ctx, - { - x: left + font.fontSize / 2, - y: top - height / 2, - }, - panelTitle, - font, - -90, - ); -} - -function renderHorizontalTitle(ctx: CanvasRenderingContext2D, props: PanelTitleProps) { - const { - size: { width }, - axisSpec: { position, hide: hideAxis, title }, - dimension: { maxLabelBboxHeight }, - axisStyle: { axisTitle, axisPanelTitle, tickLine, tickLabel }, - debug, - panelTitle, - } = props; - - if (!panelTitle) { - return; - } - - const font = getFontStyle(axisPanelTitle); - const tickDimension = shouldShowTicks(tickLine, hideAxis) ? tickLine.size + tickLine.padding : 0; - const panelTitlePadding = getSimplePadding(axisPanelTitle.visible && panelTitle ? axisPanelTitle.padding : 0); - const titleDimension = title ? getTitleDimension(axisTitle) : 0; - const labelPadding = getSimplePadding(tickLabel.padding); - const labelHeight = tickLabel.visible ? maxLabelBboxHeight + labelPadding.outer + labelPadding.inner : 0; - - const top = - position === Position.Top - ? titleDimension + panelTitlePadding.outer - : labelHeight + tickDimension + panelTitlePadding.inner; - const left = 0; - - if (debug) { - renderDebugRect(ctx, { x: left, y: top, width, height: font.fontSize }); - } - - renderText( - ctx, - { - x: left + width / 2, - y: top + font.fontSize / 2, - }, - panelTitle, - font, - ); -} - -/** @internal */ -export function getFontStyle({ fontFamily, fontStyle, fill, fontSize }: TextStyle): TextFont { - return { - fontFamily, - fontVariant: 'normal', - fontStyle: fontStyle ?? 'normal', - fontWeight: 'bold', - textColor: fill, - textOpacity: 1, - align: 'center', - baseline: 'middle', - fontSize, - }; -} diff --git a/packages/charts/src/chart_types/xy_chart/renderer/canvas/panels/panels.ts b/packages/charts/src/chart_types/xy_chart/renderer/canvas/panels/panels.ts index c097dda092..553bc26520 100644 --- a/packages/charts/src/chart_types/xy_chart/renderer/canvas/panels/panels.ts +++ b/packages/charts/src/chart_types/xy_chart/renderer/canvas/panels/panels.ts @@ -17,8 +17,7 @@ import { AxisSpec } from '../../../utils/specs'; import { AxesProps, AxisProps, renderAxis } from '../axes'; import { renderRect } from '../primitives/rect'; import { renderDebugRect } from '../utils/debug'; -import { renderTitle } from './global_title'; -import { renderPanelTitle } from './panel_title'; +import { renderTitle } from './title'; /** @internal */ export function renderGridPanels(ctx: CanvasRenderingContext2D, { x: chartX, y: chartY }: Point, panels: PanelGeoms) { @@ -46,7 +45,7 @@ function renderPanel(ctx: CanvasRenderingContext2D, props: AxisProps) { renderAxis(ctx, props); // For now, just render the axis line TODO: compute axis dimensions per panels if (!secondary) { const { panelTitle, dimension } = props; - renderPanelTitle(ctx, { panelTitle, axisSpec, axisStyle, size, dimension, debug }); // fixme axisSpec/Style? + renderTitle(ctx, true, { panelTitle, axisSpec, axisStyle, size, dimension, debug, anchorPoint: { x: 0, y: 0 } }); // fixme axisSpec/Style? } }); } @@ -76,7 +75,7 @@ export function renderPanelSubstrates(ctx: CanvasRenderingContext2D, props: Axes if (!seenAxesTitleIds.has(id)) { seenAxesTitleIds.add(id); - renderTitle(ctx, { size: parentSize, debug, panelTitle, anchorPoint, dimension, axisStyle, axisSpec }); + renderTitle(ctx, false, { size: parentSize, debug, panelTitle, anchorPoint, dimension, axisStyle, axisSpec }); } renderPanel(ctx, { diff --git a/packages/charts/src/chart_types/xy_chart/renderer/canvas/panels/title.ts b/packages/charts/src/chart_types/xy_chart/renderer/canvas/panels/title.ts new file mode 100644 index 0000000000..4433a73068 --- /dev/null +++ b/packages/charts/src/chart_types/xy_chart/renderer/canvas/panels/title.ts @@ -0,0 +1,71 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { Position } from '../../../../../utils/common'; +import { innerPad, outerPad } from '../../../../../utils/dimensions'; +import { Point } from '../../../../../utils/point'; +import { isHorizontalAxis } from '../../../utils/axis_type_utils'; +import { getTitleDimension, shouldShowTicks } from '../../../utils/axis_utils'; +import { AxisProps } from '../axes'; +import { renderText, TextFont } from '../primitives/text'; +import { renderDebugRect } from '../utils/debug'; + +type PanelTitleProps = Pick; +type TitleProps = PanelTitleProps & { anchorPoint: Point }; + +const titleFontDefaults: Omit = { + fontVariant: 'normal', + fontStyle: 'normal', // may be overridden (happens if prop on axis style is defined) + fontWeight: 'bold', + textOpacity: 1, + align: 'center', + baseline: 'middle', +}; + +/** @internal */ +export function renderTitle( + ctx: CanvasRenderingContext2D, + panel: boolean, + { + size: { width, height }, + dimension: { maxLabelBboxWidth, maxLabelBboxHeight }, + axisSpec: { position, hide: hideAxis, title }, + axisStyle: { axisPanelTitle, axisTitle, tickLabel, tickLine }, + panelTitle, + debug, + anchorPoint, + }: TitleProps, +) { + const titleToRender = panel ? panelTitle : title; + const axisTitleToUse = panel ? axisPanelTitle : axisTitle; + if (!titleToRender || !axisTitleToUse.visible) { + return; + } + const otherAxisTitleToUse = panel ? axisTitle : axisPanelTitle; + const otherTitle = panel ? title : panelTitle; + const horizontal = isHorizontalAxis(position); + const font: TextFont = { ...titleFontDefaults, ...axisTitleToUse, textColor: axisTitleToUse.fill }; + const tickDimension = shouldShowTicks(tickLine, hideAxis) ? tickLine.size + tickLine.padding : 0; + const maxLabelBoxSize = horizontal ? maxLabelBboxHeight : maxLabelBboxWidth; + const labelSize = tickLabel.visible ? maxLabelBoxSize + innerPad(tickLabel.padding) + outerPad(tickLabel.padding) : 0; + const otherTitleDimension = otherTitle ? getTitleDimension(otherAxisTitleToUse) : 0; + const titlePadding = panel || (axisTitleToUse.visible && title) ? axisTitleToUse.padding : 0; + const rotation = horizontal ? 0 : -90; + + const offset = + position === Position.Left || position === Position.Top + ? outerPad(titlePadding) + (panel ? otherTitleDimension : 0) + : tickDimension + labelSize + innerPad(titlePadding) + (panel ? 0 : otherTitleDimension); + const x = anchorPoint.x + (horizontal ? 0 : offset); + const y = anchorPoint.y + (horizontal ? offset : height); + const textX = horizontal ? width / 2 + (panel ? 0 : x) : font.fontSize / 2 + (panel ? offset : x); + const textY = horizontal ? font.fontSize / 2 + (panel ? offset : y) : (panel ? height : -height + 2 * y) / 2; + + if (debug) renderDebugRect(ctx, { x, y, width: horizontal ? width : height, height: font.fontSize }, rotation); + renderText(ctx, { x: textX, y: textY }, titleToRender ?? '', font, rotation); +} diff --git a/packages/charts/src/chart_types/xy_chart/renderer/canvas/renderers.ts b/packages/charts/src/chart_types/xy_chart/renderer/canvas/renderers.ts index f63a926693..e6d0dcb096 100644 --- a/packages/charts/src/chart_types/xy_chart/renderer/canvas/renderers.ts +++ b/packages/charts/src/chart_types/xy_chart/renderer/canvas/renderers.ts @@ -148,6 +148,7 @@ export function renderXYChartCanvas2d( renderDebugRect( ctx, { x: left, y: top, width, height }, + 0, { color: stringToRGB('transparent') }, { color: stringToRGB('red'), width: 4, dash: [4, 4] }, ); diff --git a/packages/charts/src/chart_types/xy_chart/renderer/canvas/utils/debug.ts b/packages/charts/src/chart_types/xy_chart/renderer/canvas/utils/debug.ts index fde4390f4d..9533d74932 100644 --- a/packages/charts/src/chart_types/xy_chart/renderer/canvas/utils/debug.ts +++ b/packages/charts/src/chart_types/xy_chart/renderer/canvas/utils/debug.ts @@ -24,9 +24,9 @@ const DEFAULT_DEBUG_STROKE: Stroke = { export function renderDebugRect( ctx: CanvasRenderingContext2D, rect: Rect, + rotation: number = 0, fill = DEFAULT_DEBUG_FILL, // violet stroke = DEFAULT_DEBUG_STROKE, - rotation: number = 0, ) { withContext(ctx, () => { ctx.translate(rect.x, rect.y); diff --git a/packages/charts/src/chart_types/xy_chart/renderer/canvas/values/bar.ts b/packages/charts/src/chart_types/xy_chart/renderer/canvas/values/bar.ts index 563542e770..233557c272 100644 --- a/packages/charts/src/chart_types/xy_chart/renderer/canvas/values/bar.ts +++ b/packages/charts/src/chart_types/xy_chart/renderer/canvas/values/bar.ts @@ -10,11 +10,10 @@ import { colorIsDark, getTextColorIfTextInvertible } from '../../../../../common import { fillTextColor } from '../../../../../common/fill_text_color'; import { Font, TextAlign, TextBaseline } from '../../../../../common/text_utils'; import { Rect } from '../../../../../geoms/types'; -import { Rotation, VerticalAlignment, HorizontalAlignment } from '../../../../../utils/common'; +import { HorizontalAlignment, Rotation, VerticalAlignment } from '../../../../../utils/common'; import { Dimensions } from '../../../../../utils/dimensions'; import { BarGeometry } from '../../../../../utils/geometry'; -import { Point } from '../../../../../utils/point'; -import { Theme, TextAlignment } from '../../../../../utils/themes/theme'; +import { TextAlignment, Theme } from '../../../../../utils/themes/theme'; import { LabelOverflowConstraint } from '../../../utils/specs'; import { renderText, wrapLines } from '../primitives/text'; import { renderDebugRect } from '../utils/debug'; @@ -40,20 +39,13 @@ const CHART_DIRECTION: Record = { export function renderBarValues(ctx: CanvasRenderingContext2D, props: BarValuesProps) { const { bars, debug, rotation, renderingArea, barSeriesStyle, panel } = props; const { fontFamily, fontStyle, fill, alignment } = barSeriesStyle.displayValue; - const barsLength = bars.length; - for (let i = 0; i < barsLength; i++) { - const { displayValue } = bars[i]; - if (!displayValue) { - continue; + bars.forEach((bar) => { + if (!bar.displayValue) { + return; } - const { text, fontSize, fontScale, overflowConstraints, isValueContainedInElement } = displayValue; - let textLines = { - lines: [text], - width: displayValue.width, - height: displayValue.height, - }; + const { text, fontSize, fontScale, overflowConstraints, isValueContainedInElement } = bar.displayValue; const shadowSize = getTextBorderSize(fill); - const { fillColor, shadowColor } = getTextColors(fill, bars[i].color, shadowSize); + const { fillColor, shadowColor } = getTextColors(fill, bar.color, shadowSize); const font: Font = { fontFamily, fontStyle: fontStyle ?? 'normal', @@ -64,179 +56,39 @@ export function renderBarValues(ctx: CanvasRenderingContext2D, props: BarValuesP }; const { x, y, align, baseline, rect, overflow } = positionText( - bars[i], - displayValue, + bar, + bar.displayValue, rotation, barSeriesStyle.displayValue, alignment, ); - if (isValueContainedInElement) { - const width = rotation === 0 || rotation === 180 ? bars[i].width : bars[i].height; - textLines = wrapLines(ctx, textLines.lines[0], font, fontSize, width, 100); - } if (overflowConstraints.has(LabelOverflowConstraint.ChartEdges) && isOverflow(rect, renderingArea, rotation)) { - continue; + return; } if (overflowConstraints.has(LabelOverflowConstraint.BarGeometry) && overflow) { - continue; + return; } - if (debug) { - withPanelTransform(ctx, panel, rotation, renderingArea, (ctx) => { - renderDebugRect(ctx, rect); - }); - } - const { width, height } = textLines; - const linesLength = textLines.lines.length; - - for (let j = 0; j < linesLength; j++) { - const textLine = textLines.lines[j]; - const origin = repositionTextLine({ x, y }, rotation, j, linesLength, { height, width }); - const fontAugment = { fontSize, align, baseline, shadow: shadowColor, shadowSize }; - withPanelTransform(ctx, panel, rotation, renderingArea, (ctx) => { - renderText(ctx, origin, textLine, { ...font, ...fontAugment }, -rotation, 0, 0, fontScale); - }); - } - } -} - -function repositionTextLine( - origin: Point, - chartRotation: Rotation, - i: number, - max: number, - box: { height: number; width: number }, -) { - const { x, y } = origin; - const { width, height } = box; - let lineX: number; - let lineY: number; - switch (chartRotation) { - case 180: - lineX = x; - lineY = y - (i - max + 1) * height; - break; - case -90: - lineX = x; - lineY = y; - break; - case 90: - lineX = x; - lineY = y - (i - max + 1) * width; - break; - case 0: - default: - lineX = x; - lineY = y + i * height; - } - - return { x: lineX, y: lineY }; -} + const { width, height, lines } = isValueContainedInElement + ? wrapLines(ctx, text, font, fontSize, rotation === 0 || rotation === 180 ? bar.width : bar.height, 100) + : { lines: [text], width: bar.displayValue.width, height: bar.displayValue.height }; -function computeHorizontalOffset( - geom: BarGeometry, - valueBox: { width: number; height: number }, - chartRotation: Rotation, - { horizontal }: Partial = {}, -) { - switch (chartRotation) { - case CHART_DIRECTION.LeftToRight: { - if (horizontal === HorizontalAlignment.Left) { - return geom.height - valueBox.width; - } - if (horizontal === HorizontalAlignment.Center) { - return geom.height / 2 - valueBox.width / 2; - } - break; - } - case CHART_DIRECTION.RightToLeft: { - if (horizontal === HorizontalAlignment.Right) { - return geom.height - valueBox.width; - } - if (horizontal === HorizontalAlignment.Center) { - return geom.height / 2 - valueBox.width / 2; - } - break; - } - case CHART_DIRECTION.TopToBottom: { - if (horizontal === HorizontalAlignment.Left) { - return geom.width / 2 - valueBox.width / 2; - } - if (horizontal === HorizontalAlignment.Right) { - return -geom.width / 2 + valueBox.width / 2; - } - break; - } - case CHART_DIRECTION.BottomUp: - default: { - if (horizontal === HorizontalAlignment.Left) { - return -geom.width / 2 + valueBox.width / 2; - } - if (horizontal === HorizontalAlignment.Right) { - return geom.width / 2 - valueBox.width / 2; - } - } - } - return 0; -} + if (debug) withPanelTransform(ctx, panel, rotation, renderingArea, () => renderDebugRect(ctx, rect)); -function computeVerticalOffset( - geom: BarGeometry, - valueBox: { width: number; height: number }, - chartRotation: Rotation, - { vertical }: Partial = {}, -) { - switch (chartRotation) { - case CHART_DIRECTION.LeftToRight: { - if (vertical === VerticalAlignment.Bottom) { - return geom.width - valueBox.height; - } - if (vertical === VerticalAlignment.Middle) { - return geom.width / 2 - valueBox.height / 2; - } - break; - } - case CHART_DIRECTION.RightToLeft: { - if (vertical === VerticalAlignment.Bottom) { - return -geom.width + valueBox.height; - } - if (vertical === VerticalAlignment.Middle) { - return -geom.width / 2 + valueBox.height / 2; - } - break; - } - case CHART_DIRECTION.TopToBottom: { - if (vertical === VerticalAlignment.Top) { - return geom.height - valueBox.height; - } - if (vertical === VerticalAlignment.Middle) { - return geom.height / 2 - valueBox.height / 2; - } - break; - } - case CHART_DIRECTION.BottomUp: - default: { - if (vertical === VerticalAlignment.Bottom) { - return geom.height - valueBox.height; - } - if (vertical === VerticalAlignment.Middle) { - return geom.height / 2 - valueBox.height / 2; - } - } - } - return 0; + lines.forEach((textLine, j) => { + const origin = lineOrigin(x, y, rotation, j, lines.length, width, height); + const fontAugment = { fontSize, align, baseline, shadow: shadowColor, shadowSize }; + withPanelTransform(ctx, panel, rotation, renderingArea, () => + renderText(ctx, origin, textLine, { ...font, ...fontAugment }, -rotation, 0, 0, fontScale), + ); + }); + }); } -function computeAlignmentOffset( - geom: BarGeometry, - valueBox: { width: number; height: number }, - chartRotation: Rotation, - textAlignment: Partial = {}, -) { - return { - alignmentOffsetX: computeHorizontalOffset(geom, valueBox, chartRotation, textAlignment), - alignmentOffsetY: computeVerticalOffset(geom, valueBox, chartRotation, textAlignment), - }; +function lineOrigin(x: number, y: number, rotation: Rotation, i: number, max: number, width: number, height: number) { + const size = Math.abs(rotation) === 90 ? width : height; + const sizeMultiplier = rotation > 0 ? -(i - max + 1) : rotation === 0 ? i : 0; + return { x, y: y + size * sizeMultiplier }; } function positionText( @@ -248,97 +100,93 @@ function positionText( ): { x: number; y: number; align: TextAlign; baseline: TextBaseline; rect: Rect; overflow: boolean } { const { offsetX, offsetY } = offsets; - const { alignmentOffsetX, alignmentOffsetY } = computeAlignmentOffset(geom, valueBox, chartRotation, alignment); + const horizontal = alignment?.horizontal; + const vertical = alignment?.vertical; + const horizontalOverflow = valueBox.width > geom.width || valueBox.height > geom.height; + const verticalOverflow = valueBox.height > geom.width || valueBox.width > geom.height; switch (chartRotation) { case CHART_DIRECTION.TopToBottom: { + const alignmentOffsetX = + horizontal === HorizontalAlignment.Left + ? geom.width / 2 - valueBox.width / 2 + : horizontal === HorizontalAlignment.Right + ? -geom.width / 2 + valueBox.width / 2 + : 0; + const alignmentOffsetY = + vertical === VerticalAlignment.Top + ? geom.height - valueBox.height + : vertical === VerticalAlignment.Middle + ? geom.height / 2 - valueBox.height / 2 + : 0; const x = geom.x + geom.width / 2 - offsetX + alignmentOffsetX; const y = geom.y + offsetY + alignmentOffsetY; - return { - x, - y, - align: 'center', - baseline: 'bottom', - rect: { - x: x - valueBox.width / 2, - y, - width: valueBox.width, - height: valueBox.height, - }, - overflow: valueBox.width > geom.width || valueBox.height > geom.height, - }; + const rect = { x: x - valueBox.width / 2, y, width: valueBox.width, height: valueBox.height }; + return { x, y, rect, align: 'center', baseline: 'bottom', overflow: horizontalOverflow }; } case CHART_DIRECTION.RightToLeft: { + const alignmentOffsetX = + horizontal === HorizontalAlignment.Right + ? geom.height - valueBox.width + : horizontal === HorizontalAlignment.Center + ? geom.height / 2 - valueBox.width / 2 + : 0; + const alignmentOffsetY = + vertical === VerticalAlignment.Bottom + ? -geom.width + valueBox.height + : vertical === VerticalAlignment.Middle + ? -geom.width / 2 + valueBox.height / 2 + : 0; const x = geom.x + geom.width + offsetY + alignmentOffsetY; const y = geom.y - offsetX + alignmentOffsetX; - return { - x, - y, - align: 'left', - baseline: 'top', - rect: { - x: x - valueBox.height, - y, - width: valueBox.height, - height: valueBox.width, - }, - overflow: valueBox.height > geom.width || valueBox.width > geom.height, - }; + const rect = { x: x - valueBox.height, y, width: valueBox.height, height: valueBox.width }; + return { x, y, rect, align: 'left', baseline: 'top', overflow: verticalOverflow }; } case CHART_DIRECTION.LeftToRight: { + const alignmentOffsetX = + horizontal === HorizontalAlignment.Left + ? geom.height - valueBox.width + : horizontal === HorizontalAlignment.Center + ? geom.height / 2 - valueBox.width / 2 + : 0; + const alignmentOffsetY = + vertical === VerticalAlignment.Bottom + ? geom.width - valueBox.height + : vertical === VerticalAlignment.Middle + ? geom.width / 2 - valueBox.height / 2 + : 0; const x = geom.x - offsetY + alignmentOffsetY; const y = geom.y + offsetX + alignmentOffsetX; - return { - x, - y, - align: 'right', - baseline: 'top', - rect: { - x, - y, - width: valueBox.height, - height: valueBox.width, - }, - overflow: valueBox.height > geom.width || valueBox.width > geom.height, - }; + const rect = { x, y, width: valueBox.height, height: valueBox.width }; + return { x, y, rect, align: 'right', baseline: 'top', overflow: verticalOverflow }; } case CHART_DIRECTION.BottomUp: default: { + const alignmentOffsetX = + horizontal === HorizontalAlignment.Left + ? -geom.width / 2 + valueBox.width / 2 + : horizontal === HorizontalAlignment.Right + ? geom.width / 2 - valueBox.width / 2 + : 0; + const alignmentOffsetY = + vertical === VerticalAlignment.Bottom + ? geom.height - valueBox.height + : vertical === VerticalAlignment.Middle + ? geom.height / 2 - valueBox.height / 2 + : 0; const x = geom.x + geom.width / 2 - offsetX + alignmentOffsetX; const y = geom.y - offsetY + alignmentOffsetY; - return { - x, - y, - align: 'center', - baseline: 'top', - rect: { - x: x - valueBox.width / 2, - y, - width: valueBox.width, - height: valueBox.height, - }, - overflow: valueBox.width > geom.width || valueBox.height > geom.height, - }; + const rect = { x: x - valueBox.width / 2, y, width: valueBox.width, height: valueBox.height }; + return { x, y, rect, align: 'center', baseline: 'top', overflow: horizontalOverflow }; } } } function isOverflow(rect: Rect, chartDimensions: Dimensions, chartRotation: Rotation) { - let cWidth = chartDimensions.width; - let cHeight = chartDimensions.height; - if (chartRotation === 90 || chartRotation === -90) { - cWidth = chartDimensions.height; - cHeight = chartDimensions.width; - } - - if (rect.x < 0 || rect.x + rect.width > cWidth) { - return true; - } - if (rect.y < 0 || rect.y + rect.height > cHeight) { - return true; - } - - return false; + const vertical = Math.abs(chartRotation) === 90; + const cWidth = vertical ? chartDimensions.height : chartDimensions.width; + const cHeight = vertical ? chartDimensions.width : chartDimensions.height; + return rect.x < 0 || rect.x + rect.width > cWidth || rect.y < 0 || rect.y + rect.height > cHeight; } const DEFAULT_VALUE_COLOR = 'black'; diff --git a/packages/charts/src/chart_types/xy_chart/rendering/bars.ts b/packages/charts/src/chart_types/xy_chart/rendering/bars.ts index b84b1aa3be..7e975256a1 100644 --- a/packages/charts/src/chart_types/xy_chart/rendering/bars.ts +++ b/packages/charts/src/chart_types/xy_chart/rendering/bars.ts @@ -8,7 +8,7 @@ import { Scale } from '../../../scales'; import { ScaleType } from '../../../scales/constants'; -import { CanvasTextBBoxCalculator } from '../../../utils/bbox/canvas_text_bbox_calculator'; +import { TextMeasure, withTextMeasure } from '../../../utils/bbox/canvas_text_bbox_calculator'; import { clamp, Color, isNil, mergePartial } from '../../../utils/common'; import { Dimensions } from '../../../utils/dimensions'; import { BandedAccessorType, BarGeometry } from '../../../utils/geometry'; @@ -17,6 +17,14 @@ import { IndexedGeometryMap } from '../utils/indexed_geometry_map'; import { DataSeries, DataSeriesDatum, XYChartSeriesIdentifier } from '../utils/series'; import { BarStyleAccessor, DisplayValueSpec, LabelOverflowConstraint, StackMode } from '../utils/specs'; +const PADDING = 1; // default padding for now +const FONT_SIZE_FACTOR = 0.7; // Take 70% of space for the label text + +type BarTuple = { + barGeometries: BarGeometry[]; + indexedGeometryMap: IndexedGeometryMap; +}; + /** @internal */ export function renderBars( orderIndex: number, @@ -24,177 +32,127 @@ export function renderBars( xScale: Scale, yScale: Scale, panel: Dimensions, + chartRotation: number, + minBarHeight: number, color: Color, sharedSeriesStyle: BarSeriesStyle, displayValueSettings?: DisplayValueSpec, styleAccessor?: BarStyleAccessor, - minBarHeight: number = 0, stackMode?: StackMode, - chartRotation?: number, -): { - barGeometries: BarGeometry[]; - indexedGeometryMap: IndexedGeometryMap; -} { - const indexedGeometryMap = new IndexedGeometryMap(); - const barGeometries: BarGeometry[] = []; - - const bboxCalculator = new CanvasTextBBoxCalculator(); - - // default padding to 1 for now - const padding = 1; +): BarTuple { const { fontSize, fontFamily } = sharedSeriesStyle.displayValue; - - dataSeries.data.forEach((datum) => { - const { y0, y1, initialY1, filled } = datum; - // don't create a bar if not within the xScale domain - if (!xScale.isValueInDomain(datum.x)) { - return; - } - - let y: number | null; - let y0Scaled; - if (yScale.type === ScaleType.Log) { - y = y1 === 0 || y1 === null ? yScale.range[0] : yScale.scale(y1); - if (yScale.isInverted) { - y0Scaled = y0 === 0 || y0 === null ? yScale.range[1] : yScale.scale(y0); - } else { - y0Scaled = y0 === 0 || y0 === null ? yScale.range[0] : yScale.scale(y0); + const initialBarTuple: BarTuple = { barGeometries: [], indexedGeometryMap: new IndexedGeometryMap() } as BarTuple; + const isLogY = yScale.type === ScaleType.Log; + const isInvertedY = yScale.isInverted; + return withTextMeasure((textMeasure) => + dataSeries.data.reduce((barTuple: BarTuple, datum) => { + const xScaled = xScale.scale(datum.x); + if (!xScale.isValueInDomain(datum.x) || xScaled === null) { + return barTuple; // don't create a bar if not within the xScale domain } - } else { - y = yScale.scale(y1); - // use always zero as baseline if y0 is null - y0Scaled = y0 === null ? yScale.scale(0) : yScale.scale(y0); - } - - const absMinHeight = Math.abs(minBarHeight); - - // safeguard against null y values - let height = isNil(y0Scaled) || isNil(y) ? 0 : y0Scaled - y; - - if (isNil(y0Scaled) || isNil(y)) { - y = 0; - } - - if (absMinHeight !== undefined && height !== 0 && Math.abs(height) < absMinHeight) { - const heightDelta = absMinHeight - Math.abs(height); - if (height < 0) { - height = -absMinHeight; - y += heightDelta; - } else { - height = absMinHeight; - y -= heightDelta; + const { barGeometries, indexedGeometryMap } = barTuple; + const { y0, y1, initialY1, filled } = datum; + const rawY = isLogY && (y1 === 0 || y1 === null) ? yScale.range[0] : yScale.scale(y1); + + const y0Scaled = isLogY + ? y0 === 0 || y0 === null + ? yScale.range[isInvertedY ? 1 : 0] + : yScale.scale(y0) + : yScale.scale(y0 === null ? 0 : y0); + + const finiteHeight = isNil(y0Scaled) || isNil(rawY) ? 0 : y0Scaled - rawY; // safeguard against null y values + const absHeight = Math.abs(finiteHeight); + const height = absHeight === 0 ? absHeight : Math.max(minBarHeight, absHeight); // extend nonzero bars + const heightExtension = height - absHeight; + const isUpsideDown = finiteHeight < 0; + const finiteY = isNil(y0Scaled) || isNil(rawY) ? 0 : rawY; + const y = isUpsideDown ? finiteY - height + heightExtension : finiteY - heightExtension; + + const seriesIdentifier: XYChartSeriesIdentifier = { + key: dataSeries.key, + specId: dataSeries.specId, + yAccessor: dataSeries.yAccessor, + splitAccessors: dataSeries.splitAccessors, + seriesKeys: dataSeries.seriesKeys, + smHorizontalAccessorValue: dataSeries.smHorizontalAccessorValue, + smVerticalAccessorValue: dataSeries.smVerticalAccessorValue, + }; + + const seriesStyle = getBarStyleOverrides(datum, seriesIdentifier, sharedSeriesStyle, styleAccessor); + + const maxPixelWidth = clamp(seriesStyle.rect.widthRatio ?? 1, 0, 1) * xScale.bandwidth; + const minPixelWidth = clamp(seriesStyle.rect.widthPixel ?? 0, 0, maxPixelWidth); + + const width = clamp(seriesStyle.rect.widthPixel ?? xScale.bandwidth, minPixelWidth, maxPixelWidth); + const x = xScaled + xScale.bandwidth * orderIndex + xScale.bandwidth / 2 - width / 2; + + const originalY1Value = stackMode === StackMode.Percentage ? (isNil(y1) ? null : y1 - (y0 ?? 0)) : initialY1; + const formattedDisplayValue = displayValueSettings?.valueFormatter?.(originalY1Value); + + // only show displayValue for even bars if showOverlappingValue + const displayValueText = + displayValueSettings?.isAlternatingValueLabel && barGeometries.length % 2 ? undefined : formattedDisplayValue; + + const { displayValueWidth, fixedFontScale } = computeBoxWidth( + displayValueText ?? '', + { padding: PADDING, fontSize, fontFamily, textMeasure, width }, + displayValueSettings, + ); + + const isHorizontalRotation = chartRotation % 180 === 0; + // Pick the right side of the label's box to use as factor reference + const referenceWidth = Math.max(isHorizontalRotation ? displayValueWidth : fixedFontScale, 1); + + const textScalingFactor = getFinalFontScalingFactor( + (width * FONT_SIZE_FACTOR) / referenceWidth, + fixedFontScale, + fontSize, + ); + const overflowConstraints: Set = new Set( + displayValueSettings?.overflowConstraints ?? [ + LabelOverflowConstraint.ChartEdges, + LabelOverflowConstraint.BarGeometry, + ], + ); + + // Based on rotation scale the width of the text box + const bboxWidthFactor = isHorizontalRotation ? textScalingFactor : 1; + + const displayValue: BarGeometry['displayValue'] | undefined = + displayValueText && displayValueSettings?.showValueLabel + ? { + fontScale: textScalingFactor, + fontSize: fixedFontScale, + text: displayValueText, + width: bboxWidthFactor * displayValueWidth, + height: textScalingFactor * fixedFontScale, + overflowConstraints, + isValueContainedInElement: displayValueSettings?.isValueContainedInElement ?? false, + } + : undefined; + + const barGeometry: BarGeometry = { + displayValue, + x, + y, + transform: { x: 0, y: 0 }, + width, + height, + color, + value: { x: datum.x, y: originalY1Value, mark: null, accessor: BandedAccessorType.Y1, datum: datum.datum }, + seriesIdentifier, + seriesStyle, + panel, + }; + indexedGeometryMap.set(barGeometry); + + if (y1 !== null && initialY1 !== null && filled?.y1 === undefined) { + barGeometries.push(barGeometry); } - } - const isUpsideDown = height < 0; - height = Math.abs(height); - y = isUpsideDown ? y - height : y; - - const xScaled = xScale.scale(datum.x); - - if (xScaled === null) { - return; - } - - const seriesIdentifier: XYChartSeriesIdentifier = { - key: dataSeries.key, - specId: dataSeries.specId, - yAccessor: dataSeries.yAccessor, - splitAccessors: dataSeries.splitAccessors, - seriesKeys: dataSeries.seriesKeys, - smHorizontalAccessorValue: dataSeries.smHorizontalAccessorValue, - smVerticalAccessorValue: dataSeries.smVerticalAccessorValue, - }; - - const seriesStyle = getBarStyleOverrides(datum, seriesIdentifier, sharedSeriesStyle, styleAccessor); - const maxPixelWidth = clamp(seriesStyle.rect.widthRatio ?? 1, 0, 1) * xScale.bandwidth; - const minPixelWidth = clamp(seriesStyle.rect.widthPixel ?? 0, 0, maxPixelWidth); - - const width = clamp(seriesStyle.rect.widthPixel ?? xScale.bandwidth, minPixelWidth, maxPixelWidth); - const x = xScaled + xScale.bandwidth * orderIndex + xScale.bandwidth / 2 - width / 2; - - const originalY1Value = stackMode === StackMode.Percentage ? (isNil(y1) ? null : y1 - (y0 ?? 0)) : initialY1; - const formattedDisplayValue = displayValueSettings?.valueFormatter?.(originalY1Value); - - // only show displayValue for even bars if showOverlappingValue - const displayValueText = - displayValueSettings?.isAlternatingValueLabel && barGeometries.length % 2 ? undefined : formattedDisplayValue; - - const { displayValueWidth, fixedFontScale } = computeBoxWidth( - displayValueText ?? '', - { padding, fontSize, fontFamily, bboxCalculator, width }, - displayValueSettings, - ); - - const isHorizontalRotation = chartRotation == null || [0, 180].includes(chartRotation); - // Take 70% of space for the label text - const fontSizeFactor = 0.7; - // Pick the right side of the label's box to use as factor reference - const referenceWidth = Math.max(isHorizontalRotation ? displayValueWidth : fixedFontScale, 1); - - const textScalingFactor = getFinalFontScalingFactor( - (width * fontSizeFactor) / referenceWidth, - fixedFontScale, - fontSize, - ); - const overflowConstraints: Set = new Set( - displayValueSettings?.overflowConstraints ?? [ - LabelOverflowConstraint.ChartEdges, - LabelOverflowConstraint.BarGeometry, - ], - ); - - // Based on rotation scale the width of the text box - const bboxWidthFactor = isHorizontalRotation ? textScalingFactor : 1; - - const displayValue: BarGeometry['displayValue'] | undefined = - displayValueText && displayValueSettings?.showValueLabel - ? { - fontScale: textScalingFactor, - fontSize: fixedFontScale, - text: displayValueText, - width: bboxWidthFactor * displayValueWidth, - height: textScalingFactor * fixedFontScale, - overflowConstraints, - isValueContainedInElement: displayValueSettings?.isValueContainedInElement ?? false, - } - : undefined; - - const barGeometry: BarGeometry = { - displayValue, - x, - y, - transform: { - x: 0, - y: 0, - }, - width, - height, - color, - value: { - x: datum.x, - y: originalY1Value, - mark: null, - accessor: BandedAccessorType.Y1, - datum: datum.datum, - }, - seriesIdentifier, - seriesStyle, - panel, - }; - indexedGeometryMap.set(barGeometry); - - if (y1 !== null && initialY1 !== null && filled?.y1 === undefined) { - barGeometries.push(barGeometry); - } - }); - - bboxCalculator.destroy(); - - return { - barGeometries, - indexedGeometryMap, - }; + return barTuple; + }, initialBarTuple), + ); } /** @@ -207,20 +165,20 @@ function computeBoxWidth( padding, fontSize, fontFamily, - bboxCalculator, + textMeasure, width, }: { padding: number; fontSize: number | { min: number; max: number }; fontFamily: string; - bboxCalculator: CanvasTextBBoxCalculator; + textMeasure: TextMeasure; width: number; }, displayValueSettings: DisplayValueSpec | undefined, ): { fixedFontScale: number; displayValueWidth: number } { const fixedFontScale = Math.max(typeof fontSize === 'number' ? fontSize : fontSize.min, 1); - const computedDisplayValueWidth = bboxCalculator.compute(text || '', padding, fixedFontScale, fontFamily).width; + const computedDisplayValueWidth = textMeasure(text || '', padding, fixedFontScale, fontFamily).width; if (typeof fontSize !== 'number') { return { fixedFontScale, diff --git a/packages/charts/src/chart_types/xy_chart/state/selectors/compute_axis_ticks_dimensions.ts b/packages/charts/src/chart_types/xy_chart/state/selectors/compute_axis_ticks_dimensions.ts index 63ed0266e3..1708d5dc9d 100644 --- a/packages/charts/src/chart_types/xy_chart/state/selectors/compute_axis_ticks_dimensions.ts +++ b/packages/charts/src/chart_types/xy_chart/state/selectors/compute_axis_ticks_dimensions.ts @@ -9,7 +9,7 @@ import { createCustomCachedSelector } from '../../../../state/create_selector'; import { getChartThemeSelector } from '../../../../state/selectors/get_chart_theme'; import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_specs'; -import { CanvasTextBBoxCalculator } from '../../../../utils/bbox/canvas_text_bbox_calculator'; +import { withTextMeasure } from '../../../../utils/bbox/canvas_text_bbox_calculator'; import { AxisId } from '../../../../utils/ids'; import { axisViewModel, AxisViewModel, hasDuplicateAxis, defaultTickFormatter } from '../../utils/axis_utils'; import { computeSeriesDomainsSelector } from './compute_series_domains'; @@ -45,31 +45,31 @@ export const computeAxisTicksDimensionsSelector = createCustomCachedSelector( ): Map => { const { xDomain, yDomains } = seriesDomainsAndData; const fallBackTickFormatter = seriesSpecs.find(({ tickFormat }) => tickFormat)?.tickFormat ?? defaultTickFormatter; - const bboxCalculator = new CanvasTextBBoxCalculator(); - const axesTicksDimensions: Map = new Map(); - axesSpecs.forEach((axisSpec) => { - const { id } = axisSpec; - const axisStyle = axesStyles.get(id) ?? chartTheme.axes; - const dimensions = axisViewModel( - axisSpec, - xDomain, - yDomains, - totalBarsInCluster, - bboxCalculator, - settingsSpec.rotation, - axisStyle, - fallBackTickFormatter, - barsPadding, - isHistogramMode, - ); - if ( - dimensions && - (!settingsSpec.hideDuplicateAxes || !hasDuplicateAxis(axisSpec, dimensions, axesTicksDimensions, axesSpecs)) - ) { - axesTicksDimensions.set(id, dimensions); - } + return withTextMeasure((textMeasure) => { + const axesTicksDimensions: Map = new Map(); + axesSpecs.forEach((axisSpec) => { + const { id } = axisSpec; + const axisStyle = axesStyles.get(id) ?? chartTheme.axes; + const dimensions = axisViewModel( + axisSpec, + xDomain, + yDomains, + totalBarsInCluster, + textMeasure, + settingsSpec.rotation, + axisStyle, + fallBackTickFormatter, + barsPadding, + isHistogramMode, + ); + if ( + dimensions && + (!settingsSpec.hideDuplicateAxes || !hasDuplicateAxis(axisSpec, dimensions, axesTicksDimensions, axesSpecs)) + ) { + axesTicksDimensions.set(id, dimensions); + } + }); + return axesTicksDimensions; }); - bboxCalculator.destroy(); - return axesTicksDimensions; }, ); diff --git a/packages/charts/src/chart_types/xy_chart/state/selectors/on_brush_end_caller.ts b/packages/charts/src/chart_types/xy_chart/state/selectors/on_brush_end_caller.ts index aefdde1cb1..801cde42a6 100644 --- a/packages/charts/src/chart_types/xy_chart/state/selectors/on_brush_end_caller.ts +++ b/packages/charts/src/chart_types/xy_chart/state/selectors/on_brush_end_caller.ts @@ -15,7 +15,7 @@ import { BrushAxis } from '../../../../specs/constants'; import { DragState, GlobalChartState } from '../../../../state/chart_state'; import { createCustomCachedSelector } from '../../../../state/create_selector'; import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_specs'; -import { maxValueWithUpperLimit, minValueWithLowerLimit, Rotation } from '../../../../utils/common'; +import { clamp, Rotation } from '../../../../utils/common'; import { Dimensions } from '../../../../utils/dimensions'; import { hasDragged, DragCheckProps } from '../../../../utils/events'; import { GroupId } from '../../../../utils/ids'; @@ -163,8 +163,8 @@ function getXBrushExtent( const maxDomainValue = xScale.domain[1] + (allowBrushingLastHistogramBucket ? xScale.minInterval : 0); - const minValue = minValueWithLowerLimit(minPosScaled, maxPosScaled, xScale.domain[0]); - const maxValue = maxValueWithUpperLimit(minPosScaled, maxPosScaled, maxDomainValue); + const minValue = clamp(minPosScaled, xScale.domain[0], maxPosScaled); + const maxValue = clamp(minPosScaled, maxPosScaled, maxDomainValue); return [minValue, maxValue]; } @@ -212,8 +212,8 @@ function getYBrushExtents( const minPosScaled = yScale.invert(minPos); const maxPosScaled = yScale.invert(maxPos); - const minValue = minValueWithLowerLimit(minPosScaled, maxPosScaled, yScale.domain[0]); - const maxValue = maxValueWithUpperLimit(minPosScaled, maxPosScaled, yScale.domain[1]); + const minValue = clamp(minPosScaled, yScale.domain[0], maxPosScaled); + const maxValue = clamp(minPosScaled, maxPosScaled, yScale.domain[1]); yValues.push({ extent: [minValue, maxValue], groupId }); }); return yValues.length === 0 ? undefined : yValues; diff --git a/packages/charts/src/chart_types/xy_chart/state/utils/utils.ts b/packages/charts/src/chart_types/xy_chart/state/utils/utils.ts index da90284586..8446af591f 100644 --- a/packages/charts/src/chart_types/xy_chart/state/utils/utils.ts +++ b/packages/charts/src/chart_types/xy_chart/state/utils/utils.ts @@ -381,13 +381,13 @@ function renderGeometries( xScale, yScale, panel, + chartRotation, + spec.minBarHeight ?? 0, color, barSeriesStyle, displayValueSettings, spec.styleAccessor, - spec.minBarHeight, stackMode, - chartRotation, ); geometriesIndex.merge(renderedBars.indexedGeometryMap); bars.push({ diff --git a/packages/charts/src/chart_types/xy_chart/utils/axis_utils.test.ts b/packages/charts/src/chart_types/xy_chart/utils/axis_utils.test.ts index 7a68127bcb..62aeff45ea 100644 --- a/packages/charts/src/chart_types/xy_chart/utils/axis_utils.test.ts +++ b/packages/charts/src/chart_types/xy_chart/utils/axis_utils.test.ts @@ -16,8 +16,7 @@ import { MockXDomain, MockYDomain } from '../../../mocks/xy/domains'; import { Scale } from '../../../scales'; import { ScaleType } from '../../../scales/constants'; import { SpecType } from '../../../specs/constants'; -import { CanvasTextBBoxCalculator } from '../../../utils/bbox/canvas_text_bbox_calculator'; -import { SvgTextBBoxCalculator } from '../../../utils/bbox/svg_text_bbox_calculator'; +import { withTextMeasure } from '../../../utils/bbox/canvas_text_bbox_calculator'; import { Position, mergePartial } from '../../../utils/common'; import { niceTimeFormatter } from '../../../utils/data/formatters'; import { OrdinalDomain } from '../../../utils/domain'; @@ -212,112 +211,103 @@ describe('Axis computational utils', () => { const { axes } = LIGHT_THEME; - test('should compute axis dimensions', () => { - const bboxCalculator = new SvgTextBBoxCalculator(); - const axisDimensions = axisViewModel( - verticalAxisSpec, - xDomain, - [yDomain], - 1, - bboxCalculator, - NO_ROTATION, - axes, - (v) => `${v}`, - ); - expect(axisDimensions).toEqual(axis1Dims); - - const ungroupedAxisSpec = { ...verticalAxisSpec, groupId: 'foo' }; - const result = axisViewModel( - ungroupedAxisSpec, - xDomain, - [yDomain], - 1, - bboxCalculator, - NO_ROTATION, - axes, - (v) => `${v}`, - undefined, - false, - ); + test('should compute axis dimensions', () => + withTextMeasure((textMeasure) => { + const axisDimensions = axisViewModel( + verticalAxisSpec, + xDomain, + [yDomain], + 1, + textMeasure, + NO_ROTATION, + axes, + (v) => `${v}`, + ); + expect(axisDimensions).toEqual({ ...axis1Dims, maxLabelBboxWidth: 16, maxLabelTextWidth: 16 }); // textMeasure defaults - expect(result).toBeNull(); + const ungroupedAxisSpec = { ...verticalAxisSpec, groupId: 'foo' }; + const result = axisViewModel( + ungroupedAxisSpec, + xDomain, + [yDomain], + 1, + textMeasure, + NO_ROTATION, + axes, + (v) => `${v}`, + undefined, + false, + ); - bboxCalculator.destroy(); - }); + expect(result).toBeNull(); + })); - test('should not compute axis dimensions when spec is configured to hide', () => { - const bboxCalculator = new CanvasTextBBoxCalculator(); - verticalAxisSpec.hide = true; - const axisDimensions = axisViewModel( - verticalAxisSpec, - xDomain, - [yDomain], - 1, - bboxCalculator, - NO_ROTATION, - axes, - (v) => `${v}`, - ); - expect(axisDimensions).toBe(null); - }); + test('should not compute axis dimensions when spec is configured to hide', () => + withTextMeasure((textMeasure) => { + verticalAxisSpec.hide = true; + const axisDimensions = axisViewModel( + verticalAxisSpec, + xDomain, + [yDomain], + 1, + textMeasure, + NO_ROTATION, + axes, + (v) => `${v}`, + ); + expect(axisDimensions).toBe(null); + })); test('should compute axis dimensions with timeZone', () => { - const bboxCalculator = new SvgTextBBoxCalculator(); - const xDomain = MockXDomain.fromScaleType(ScaleType.Time, { - domain: [1551438000000, 1551441300000], - isBandScale: false, - minInterval: 0, - timeZone: 'utc', - }); - let axisDimensions = axisViewModel( - xAxisWithTime, - xDomain, - [yDomain], - 1, - bboxCalculator, - NO_ROTATION, - axes, - (v) => `${v}`, - ); - expect(axisDimensions).not.toBeNull(); - expect(axisDimensions?.tickLabels[0]).toBe('11:00:00'); - expect(axisDimensions?.tickLabels[11]).toBe('11:55:00'); - - axisDimensions = axisViewModel( - xAxisWithTime, - { - ...xDomain, - timeZone: 'utc+3', - }, - [yDomain], - 1, - bboxCalculator, - NO_ROTATION, - axes, - (v) => `${v}`, - ); - expect(axisDimensions).not.toBeNull(); - expect(axisDimensions?.tickLabels[0]).toBe('14:00:00'); - expect(axisDimensions?.tickLabels[11]).toBe('14:55:00'); + withTextMeasure((textMeasure) => { + const xDomain = MockXDomain.fromScaleType(ScaleType.Time, { + domain: [1551438000000, 1551441300000], + isBandScale: false, + minInterval: 0, + timeZone: 'utc', + }); + let axisDimensions = axisViewModel( + xAxisWithTime, + xDomain, + [yDomain], + 1, + textMeasure, + NO_ROTATION, + axes, + (v) => `${v}`, + ); + expect(axisDimensions).not.toBeNull(); + expect(axisDimensions?.tickLabels[0]).toBe('11:00:00'); + expect(axisDimensions?.tickLabels[11]).toBe('11:55:00'); - axisDimensions = axisViewModel( - xAxisWithTime, - { - ...xDomain, - timeZone: 'utc-3', - }, - [yDomain], - 1, - bboxCalculator, - NO_ROTATION, - axes, - (v) => `${v}`, - ); - expect(axisDimensions).not.toBeNull(); - expect(axisDimensions?.tickLabels[0]).toBe('08:00:00'); - expect(axisDimensions?.tickLabels[11]).toBe('08:55:00'); + axisDimensions = axisViewModel( + xAxisWithTime, + { ...xDomain, timeZone: 'utc+3' }, + [yDomain], + 1, + textMeasure, + NO_ROTATION, + axes, + (v) => `${v}`, + ); + expect(axisDimensions).not.toBeNull(); + expect(axisDimensions?.tickLabels[0]).toBe('14:00:00'); + expect(axisDimensions?.tickLabels[11]).toBe('14:55:00'); - bboxCalculator.destroy(); + axisDimensions = axisViewModel( + xAxisWithTime, + { ...xDomain, timeZone: 'utc-3' }, + [yDomain], + 1, + textMeasure, + NO_ROTATION, + axes, + (v) => `${v}`, + ); + expect(axisDimensions).not.toBeNull(); + expect(axisDimensions?.tickLabels[0]).toBe('08:00:00'); + expect(axisDimensions?.tickLabels[11]).toBe('08:55:00'); + }); }); test('should compute dimensions for the bounding box containing a rotated label', () => { diff --git a/packages/charts/src/chart_types/xy_chart/utils/axis_utils.ts b/packages/charts/src/chart_types/xy_chart/utils/axis_utils.ts index c064b70b3e..28a603dc56 100644 --- a/packages/charts/src/chart_types/xy_chart/utils/axis_utils.ts +++ b/packages/charts/src/chart_types/xy_chart/utils/axis_utils.ts @@ -8,7 +8,7 @@ import { Line } from '../../../geoms/types'; import { Scale } from '../../../scales'; -import { BBox, BBoxCalculator } from '../../../utils/bbox/bbox_calculator'; +import { BBox, TextMeasure } from '../../../utils/bbox/canvas_text_bbox_calculator'; import { getPercentageValue, degToRad, @@ -18,7 +18,7 @@ import { Rotation, VerticalAlignment, } from '../../../utils/common'; -import { Dimensions, getSimplePadding, Margins, Size } from '../../../utils/dimensions'; +import { Dimensions, innerPad, Margins, outerPad, Size } from '../../../utils/dimensions'; import { Range } from '../../../utils/domain'; import { AxisId } from '../../../utils/ids'; import { Logger } from '../../../utils/logger'; @@ -84,7 +84,7 @@ export function axisViewModel( xDomain: XDomain, yDomains: YDomain[], totalBarsInCluster: number, - bboxCalculator: BBoxCalculator, + textMeasure: TextMeasure, chartRotation: Rotation, { gridLine, tickLabel }: AxisStyle, fallBackTickFormatter: TickFormatter, @@ -127,7 +127,7 @@ export function axisViewModel( if (tickLabel.visible) { for (const labelText of tickLabels) { - const bbox = bboxCalculator.compute(labelText, 0, tickLabel.fontSize, tickLabel.fontFamily); + const bbox = textMeasure(labelText, 0, tickLabel.fontSize, tickLabel.fontFamily); const rotatedBbox = computeRotatedLabelDimensions(bbox, tickLabel.rotation); maxLabelBboxWidth = Math.max(maxLabelBboxWidth, Math.ceil(rotatedBbox.width)); maxLabelBboxHeight = Math.max(maxLabelBboxHeight, Math.ceil(rotatedBbox.height)); @@ -337,7 +337,7 @@ export function getTickLabelProps( ): TickLabelProps { const { maxLabelBboxWidth, maxLabelTextWidth, maxLabelBboxHeight, maxLabelTextHeight } = tickDimensions; const tickDimension = showTicks ? tickLine.size + tickLine.padding : 0; - const labelPadding = getSimplePadding(tickLabel.padding); + const labelInnerPadding = innerPad(tickLabel.padding); const isLeftAxis = position === Position.Left; const isAxisTop = position === Position.Top; const horizontalAlign = getHorizontalAlign(position, rotation, textAlignment?.horizontal); @@ -348,7 +348,7 @@ export function getTickLabelProps( const textOffsetY = getVerticalTextOffset(maxLabelTextHeight, verticalAlign) + userOffsets.local.y; if (isVerticalAxis(position)) { - const x = isLeftAxis ? axisSize.width - tickDimension - labelPadding.inner : tickDimension + labelPadding.inner; + const x = isLeftAxis ? axisSize.width - tickDimension - labelInnerPadding : tickDimension + labelInnerPadding; const offsetX = (isLeftAxis ? -1 : 1) * (maxLabelBboxWidth / 2); return { @@ -367,7 +367,7 @@ export function getTickLabelProps( return { x: tickPosition, - y: isAxisTop ? axisSize.height - tickDimension - labelPadding.inner : tickDimension + labelPadding.inner, + y: isAxisTop ? axisSize.height - tickDimension - labelInnerPadding : tickDimension + labelInnerPadding, offsetX: userOffsets.global.x, offsetY: offsetY + userOffsets.global.y, textOffsetX, @@ -581,9 +581,7 @@ export function getTitleDimension({ fontSize, padding, }: AxisStyle['axisTitle'] | AxisStyle['axisPanelTitle']): number { - if (!visible || fontSize <= 0) return 0; - const { inner, outer } = getSimplePadding(padding); - return inner + fontSize + outer; + return visible && fontSize > 0 ? innerPad(padding) + fontSize + outerPad(padding) : 0; } /** @internal */ @@ -700,10 +698,9 @@ export function getAxesGeometries( } const { tickLine, tickLabel, axisTitle, axisPanelTitle } = axesStyles.get(axisId) ?? sharedAxesStyle; - const labelPadding = getSimplePadding(tickLabel.padding); const showTicks = shouldShowTicks(tickLine, axisSpec.hide); const tickDimension = showTicks ? tickLine.size + tickLine.padding : 0; - const labelPaddingSum = tickLabel.visible ? labelPadding.inner + labelPadding.outer : 0; + const labelPaddingSum = tickLabel.visible ? innerPad(tickLabel.padding) + outerPad(tickLabel.padding) : 0; const { dimensions, topIncrement, bottomIncrement, leftIncrement, rightIncrement } = getAxisPosition( chartDimensions, diff --git a/packages/charts/src/components/legend/legend.tsx b/packages/charts/src/components/legend/legend.tsx index 0bb39812e9..221f42b293 100644 --- a/packages/charts/src/components/legend/legend.tsx +++ b/packages/charts/src/components/legend/legend.tsx @@ -29,7 +29,7 @@ import { getLegendItemsSelector } from '../../state/selectors/get_legend_items'; import { getLegendExtraValuesSelector } from '../../state/selectors/get_legend_items_values'; import { getLegendSizeSelector } from '../../state/selectors/get_legend_size'; import { getSettingsSpecSelector } from '../../state/selectors/get_settings_specs'; -import { BBox } from '../../utils/bbox/bbox_calculator'; +import { BBox } from '../../utils/bbox/canvas_text_bbox_calculator'; import { HorizontalAlignment, LayoutDirection, VerticalAlignment } from '../../utils/common'; import { Dimensions } from '../../utils/dimensions'; import { LIGHT_THEME } from '../../utils/themes/light_theme'; diff --git a/packages/charts/src/components/legend/position_style.ts b/packages/charts/src/components/legend/position_style.ts index e1ec9a7953..6bc893c83e 100644 --- a/packages/charts/src/components/legend/position_style.ts +++ b/packages/charts/src/components/legend/position_style.ts @@ -9,7 +9,7 @@ import { CSSProperties } from 'react'; import { LegendSpec, LegendPositionConfig } from '../../specs/settings'; -import { BBox } from '../../utils/bbox/bbox_calculator'; +import { BBox } from '../../utils/bbox/canvas_text_bbox_calculator'; import { LayoutDirection, Position } from '../../utils/common'; import { Dimensions } from '../../utils/dimensions'; diff --git a/packages/charts/src/components/legend/style_utils.ts b/packages/charts/src/components/legend/style_utils.ts index b759756a5f..a923351f15 100644 --- a/packages/charts/src/components/legend/style_utils.ts +++ b/packages/charts/src/components/legend/style_utils.ts @@ -7,7 +7,7 @@ */ import { LegendPositionConfig } from '../../specs/settings'; -import { BBox } from '../../utils/bbox/bbox_calculator'; +import { BBox } from '../../utils/bbox/canvas_text_bbox_calculator'; import { clamp, LayoutDirection } from '../../utils/common'; import { Margins } from '../../utils/dimensions'; import { LegendStyle as ThemeLegendStyle } from '../../utils/themes/theme'; diff --git a/packages/charts/src/scales/scale_band.ts b/packages/charts/src/scales/scale_band.ts index 9fdf1244a1..04b6bec910 100644 --- a/packages/charts/src/scales/scale_band.ts +++ b/packages/charts/src/scales/scale_band.ts @@ -12,7 +12,7 @@ import { Scale, ScaleBandType } from '.'; import { PrimitiveValue } from '../chart_types/partition_chart/layout/utils/group_by_rollup'; import { Ratio } from '../common/geometry'; import { RelativeBandsPadding } from '../specs'; -import { maxValueWithUpperLimit, stringifyNullsUndefined } from '../utils/common'; +import { clamp, stringifyNullsUndefined } from '../utils/common'; import { Range } from '../utils/domain'; import { ScaleType } from './constants'; @@ -70,7 +70,7 @@ export class ScaleBand implements Scale { this.d3Scale.paddingOuter(barsPadding.outer); this.barsPadding = barsPadding.inner; } else { - safeBarPadding = maxValueWithUpperLimit(barsPadding, 0, 1); + safeBarPadding = clamp(barsPadding, 0, 1); this.d3Scale.paddingInner(safeBarPadding); this.barsPadding = safeBarPadding; this.d3Scale.paddingOuter(safeBarPadding / 2); diff --git a/packages/charts/src/scales/scale_continuous.ts b/packages/charts/src/scales/scale_continuous.ts index 50aa648ea9..8b1ad803be 100644 --- a/packages/charts/src/scales/scale_continuous.ts +++ b/packages/charts/src/scales/scale_continuous.ts @@ -23,7 +23,7 @@ import { $Values, Required } from 'utility-types'; import { ScaleContinuousType, Scale } from '.'; import { PrimitiveValue } from '../chart_types/partition_chart/layout/utils/group_by_rollup'; import { screenspaceMarkerScaleCompressor } from '../solvers/screenspace_marker_scale_compressor'; -import { maxValueWithUpperLimit, mergePartial } from '../utils/common'; +import { clamp, mergePartial } from '../utils/common'; import { getMomentWithTz } from '../utils/data/date_time'; import { ContinuousDomain, Range } from '../utils/domain'; import { LOG_MIN_ABS_DOMAIN, ScaleType } from './constants'; @@ -345,7 +345,7 @@ export class ScaleContinuous implements Scale { this.domain = this.d3Scale.domain(); } - const safeBarPadding = maxValueWithUpperLimit(barsPadding, 0, 1); + const safeBarPadding = clamp(barsPadding, 0, 1); this.barsPadding = safeBarPadding; this.bandwidth = bandwidth * (1 - safeBarPadding); this.bandwidthPadding = bandwidth * safeBarPadding; diff --git a/packages/charts/src/state/selectors/get_legend_size.ts b/packages/charts/src/state/selectors/get_legend_size.ts index 5c8e62338e..fe8d571c0c 100644 --- a/packages/charts/src/state/selectors/get_legend_size.ts +++ b/packages/charts/src/state/selectors/get_legend_size.ts @@ -9,8 +9,7 @@ import { LEGEND_HIERARCHY_MARGIN } from '../../components/legend/legend_item'; import { LEGEND_TO_FULL_CONFIG } from '../../components/legend/position_style'; import { LegendPositionConfig } from '../../specs/settings'; -import { BBox } from '../../utils/bbox/bbox_calculator'; -import { CanvasTextBBoxCalculator } from '../../utils/bbox/canvas_text_bbox_calculator'; +import { BBox, withTextMeasure } from '../../utils/bbox/canvas_text_bbox_calculator'; import { Position, isDefined, LayoutDirection } from '../../utils/common'; import { GlobalChartState } from '../chart_state'; import { createCustomCachedSelector } from '../create_selector'; @@ -25,6 +24,8 @@ const MARKER_WIDTH = 16; const SHARED_MARGIN = 4; const VERTICAL_PADDING = 4; const TOP_MARGIN = 2; +const MAGIC_FONT_FAMILY = + '"Inter UI", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"'; /** @internal */ export type LegendSizing = BBox & { @@ -40,30 +41,18 @@ export const getLegendSizeSelector = createCustomCachedSelector( return { width: 0, height: 0, margin: 0, position: LEGEND_TO_FULL_CONFIG[Position.Right] }; } - const bboxCalculator = new CanvasTextBBoxCalculator(); - const bbox = labels.reduce( - (acc, { label, depth }) => { - const labelBBox = bboxCalculator.compute( - label, - 1, - 12, - '"Inter UI", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"', - 1.5, - 400, - ); - labelBBox.width += depth * LEGEND_HIERARCHY_MARGIN; - if (acc.height < labelBBox.height) { - acc.height = labelBBox.height; - } - if (acc.width < labelBBox.width) { - acc.width = labelBBox.width; - } - return acc; - }, - { width: 0, height: 0 }, + const bbox = withTextMeasure((textMeasure) => + labels.reduce( + (acc, { label, depth }) => { + const { width, height } = textMeasure(label, 1, 12, MAGIC_FONT_FAMILY, 1.5, 400); + acc.width = Math.max(acc.width, width + depth * LEGEND_HIERARCHY_MARGIN); + acc.height = Math.max(acc.height, height); + return acc; + }, + { width: 0, height: 0 }, + ), ); - bboxCalculator.destroy(); const { showLegendExtra: showLegendDisplayValue, legendPosition, legendAction } = legendConfig; const { legend: { verticalWidth, spacingBuffer, margin }, diff --git a/packages/charts/src/utils/bbox/bbox_calculator.ts b/packages/charts/src/utils/bbox/bbox_calculator.ts deleted file mode 100644 index e34b19de22..0000000000 --- a/packages/charts/src/utils/bbox/bbox_calculator.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -/** @internal */ -export interface BBox { - width: number; - height: number; -} - -/** @internal */ -export const DEFAULT_EMPTY_BBOX = { - width: 0, - height: 0, -}; - -/** @internal */ -export interface BBoxCalculator { - compute(text: string, padding: number, fontSize?: number, fontFamily?: string): BBox; - destroy(): void; -} diff --git a/packages/charts/src/utils/bbox/canvas_text_bbox_calculator.test.ts b/packages/charts/src/utils/bbox/canvas_text_bbox_calculator.test.ts index 5751b92429..9daa933ebe 100644 --- a/packages/charts/src/utils/bbox/canvas_text_bbox_calculator.test.ts +++ b/packages/charts/src/utils/bbox/canvas_text_bbox_calculator.test.ts @@ -6,13 +6,13 @@ * Side Public License, v 1. */ -import { CanvasTextBBoxCalculator } from './canvas_text_bbox_calculator'; +import { withTextMeasure } from './canvas_text_bbox_calculator'; describe('CanvasTextBBoxCalculator', () => { - test('can create a canvas for computing text measurement values', () => { - const canvasBboxCalculator = new CanvasTextBBoxCalculator(); - const bbox = canvasBboxCalculator.compute('foo', 0); - expect(Math.abs(bbox.width - 23.2)).toBeLessThanOrEqual(2); - expect(bbox.height).toBe(16); - }); + test('can create a canvas for computing text measurement values', () => + withTextMeasure((textMeasure) => { + const bbox = textMeasure('foo', 0); + expect(Math.abs(bbox.width - 23.2)).toBeLessThanOrEqual(2); + expect(bbox.height).toBe(16); + })); }); diff --git a/packages/charts/src/utils/bbox/canvas_text_bbox_calculator.ts b/packages/charts/src/utils/bbox/canvas_text_bbox_calculator.ts index 4e2b00bac6..165e75e37e 100644 --- a/packages/charts/src/utils/bbox/canvas_text_bbox_calculator.ts +++ b/packages/charts/src/utils/bbox/canvas_text_bbox_calculator.ts @@ -6,44 +6,37 @@ * Side Public License, v 1. */ -import { BBox, BBoxCalculator, DEFAULT_EMPTY_BBOX } from './bbox_calculator'; - /** @internal */ -export class CanvasTextBBoxCalculator implements BBoxCalculator { - private attachedRoot: HTMLElement; - - private offscreenCanvas: HTMLCanvasElement; - - private context: CanvasRenderingContext2D | null; - - constructor(rootElement?: HTMLElement) { - this.offscreenCanvas = document.createElement('canvas'); - this.offscreenCanvas.style.position = 'absolute'; - this.offscreenCanvas.style.top = '-99999px'; - this.offscreenCanvas.style.left = '-99999px'; - this.context = this.offscreenCanvas.getContext('2d'); - this.attachedRoot = rootElement || document.documentElement; - this.attachedRoot.appendChild(this.offscreenCanvas); - } - - compute(text: string, padding: number, fontSize = 16, fontFamily = 'Arial', lineHeight = 1, fontWeight = 400): BBox { - if (!this.context) { - return DEFAULT_EMPTY_BBOX; - } - // Padding should be at least one to avoid browser measureText inconsistencies - if (padding < 1) { - padding = 1; - } - this.context.font = `${fontWeight} ${fontSize}px ${fontFamily}`; - const measure = this.context.measureText(text); +export interface BBox { + width: number; + height: number; +} - return { - width: measure.width + padding, - height: fontSize * lineHeight, - }; - } +/** @internal */ +export type TextMeasure = ( + text: string, + padding: number, + fontSize?: number, + fontFamily?: string, + lineHeight?: number, + fontWeight?: number, +) => BBox; - destroy(): void { - this.attachedRoot.removeChild(this.offscreenCanvas); - } -} +/** @internal */ +export const withTextMeasure = (fun: (textMeasure: TextMeasure) => T) => { + const canvas = document.createElement('canvas'); + canvas.style.display = 'none'; + const ctx = canvas.getContext('2d'); + const root = document.documentElement; + root.appendChild(canvas); + const textMeasure: TextMeasure = ctx + ? (text: string, padding: number, fontSize = 16, fontFamily = 'Arial', lineHeight = 1, fontWeight = 400) => { + ctx.font = `${fontWeight} ${fontSize}px ${fontFamily}`; + const measure = ctx.measureText(text); + return { width: measure.width + Math.max(padding, 1), height: fontSize * lineHeight }; // padding should be at least one to avoid browser measureText inconsistencies + } + : () => ({ width: 0, height: 0 }); + const result: T = fun(textMeasure); + root.removeChild(canvas); + return result; +}; diff --git a/packages/charts/src/utils/bbox/dom_text_bbox_calculator.ts b/packages/charts/src/utils/bbox/dom_text_bbox_calculator.ts deleted file mode 100644 index dbdc0705a3..0000000000 --- a/packages/charts/src/utils/bbox/dom_text_bbox_calculator.ts +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { BBox, BBoxCalculator } from './bbox_calculator'; - -/** @internal */ -export class DOMTextBBoxCalculator implements BBoxCalculator { - private attachedRoot: HTMLElement; - - private offscreenCanvas: HTMLSpanElement; - - constructor(rootElement?: HTMLElement) { - this.offscreenCanvas = document.createElement('span'); - this.offscreenCanvas.style.position = 'absolute'; - this.offscreenCanvas.style.top = '-9999px'; - this.offscreenCanvas.style.left = '-9999px'; - - this.attachedRoot = rootElement || document.documentElement; - this.attachedRoot.appendChild(this.offscreenCanvas); - } - - compute(text: string, padding: number, fontSize = 16, fontFamily = 'Arial', lineHeight = 1, fontWeight = 400): BBox { - this.offscreenCanvas.style.fontSize = `${fontSize}px`; - this.offscreenCanvas.style.fontFamily = fontFamily; - this.offscreenCanvas.style.fontWeight = `${fontWeight}`; - this.offscreenCanvas.style.lineHeight = `${lineHeight}px`; - this.offscreenCanvas.innerHTML = text; - - return { - width: Math.ceil(this.offscreenCanvas.clientWidth + padding), - height: Math.ceil(this.offscreenCanvas.clientHeight), - }; - } - - destroy(): void { - this.attachedRoot.removeChild(this.offscreenCanvas); - } -} diff --git a/packages/charts/src/utils/bbox/svg_text_bbox_calculator.ts b/packages/charts/src/utils/bbox/svg_text_bbox_calculator.ts deleted file mode 100644 index e08e773139..0000000000 --- a/packages/charts/src/utils/bbox/svg_text_bbox_calculator.ts +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { BBox, BBoxCalculator } from './bbox_calculator'; - -/** @internal */ -export class SvgTextBBoxCalculator implements BBoxCalculator { - svgElem: SVGSVGElement; - - textElem: SVGTextElement; - - attachedRoot: HTMLElement; - - textNode: Text; - - // TODO specify styles for text - // TODO specify how to hide the svg from the current dom view - // like moving it a -9999999px - constructor(rootElement?: HTMLElement) { - const xmlns = 'http://www.w3.org/2000/svg'; - this.svgElem = document.createElementNS(xmlns, 'svg'); - this.textElem = document.createElementNS(xmlns, 'text'); - this.svgElem.appendChild(this.textElem); - this.textNode = document.createTextNode(''); - this.textElem.appendChild(this.textNode); - this.attachedRoot = rootElement || document.documentElement; - this.attachedRoot.appendChild(this.svgElem); - } - - compute(text: string): BBox { - this.textNode.textContent = text; - const rect = this.textElem.getBoundingClientRect(); - return { - width: rect.width, - height: rect.height, - }; - } - - destroy(): void { - this.attachedRoot.removeChild(this.svgElem); - } -} diff --git a/packages/charts/src/utils/common.test.ts b/packages/charts/src/utils/common.test.ts index e6c6c76517..4efd01e9d2 100644 --- a/packages/charts/src/utils/common.test.ts +++ b/packages/charts/src/utils/common.test.ts @@ -7,7 +7,7 @@ */ import { - maxValueWithUpperLimit, + clamp, compareByValueAsc, identity, hasPartialObjectToMerge, @@ -16,7 +16,6 @@ import { getPartialValue, getAllKeys, shallowClone, - minValueWithLowerLimit, getColorFromVariant, ColorVariant, isUniqueArray, @@ -26,23 +25,15 @@ import { describe('common utilities', () => { test('return value bounded above', () => { - expect(maxValueWithUpperLimit(0, 0, 1)).toBe(0); - expect(maxValueWithUpperLimit(1, 0, 1)).toBe(1); + expect(clamp(0, 0, 1)).toBe(0); + expect(clamp(1, 0, 1)).toBe(1); - expect(maxValueWithUpperLimit(1.1, 0, 1)).toBe(1); - expect(maxValueWithUpperLimit(-0.1, 0, 1)).toBe(0); + expect(clamp(1.1, 0, 1)).toBe(1); + expect(clamp(-0.1, 0, 1)).toBe(0); - expect(maxValueWithUpperLimit(0.1, 0, 1)).toBe(0.1); - expect(maxValueWithUpperLimit(0.8, 0, 1)).toBe(0.8); + expect(clamp(0.1, 0, 1)).toBe(0.1); + expect(clamp(0.8, 0, 1)).toBe(0.8); }); - test('return value bounded below', () => { - expect(minValueWithLowerLimit(10, 20, 0)).toBe(10); - expect(minValueWithLowerLimit(20, 10, 0)).toBe(10); - - expect(minValueWithLowerLimit(10, -20, 0)).toBe(0); - expect(minValueWithLowerLimit(-20, 10, 0)).toBe(0); - }); - test('identity', () => { expect(identity('text')).toBe('text'); expect(identity(2)).toBe(2); @@ -65,6 +56,7 @@ describe('common utilities', () => { bar: number; test?: TestType; } + const base: TestType = { foo: 'elastic', bar: 123, @@ -229,6 +221,7 @@ describe('common utilities', () => { describe('mergePartial', () => { let baseClone: TestType; + interface TestType { string: string; number: number; @@ -237,6 +230,7 @@ describe('common utilities', () => { array2: number[]; nested: Partial; } + type PartialTestType = RecursivePartial; const base: TestType = { string: 'string1', @@ -260,6 +254,7 @@ describe('common utilities', () => { describe('Union types', () => { type TestObject = { string1?: string; string2?: string }; + interface TestUnionType { union: 'val1' | 'val2' | TestObject | string[]; } @@ -771,6 +766,7 @@ describe('common utilities', () => { animals: Set; numbers?: Set; } + const result = mergePartial( { animals: new Set(['cat', 'dog']), @@ -927,6 +923,7 @@ describe('common utilities', () => { value3: string; value4?: OptionalTestType; } + const defaultBase: OptionalTestType = { value1: 'foo', value3: 'bar', @@ -1079,6 +1076,7 @@ describe('#isDefinedFrom', () => { interface Test { a?: number | string | boolean | null; } + it('should filter out undefined values from complex types', () => { const values: Partial[] = [ { diff --git a/packages/charts/src/utils/common.tsx b/packages/charts/src/utils/common.tsx index f05d760344..3802c8de7b 100644 --- a/packages/charts/src/utils/common.tsx +++ b/packages/charts/src/utils/common.tsx @@ -132,34 +132,12 @@ export function identity(value: T): T { /** @internal */ export function compareByValueAsc(a: number | string, b: number | string): number { - return a > b ? 1 : -1; + return a > b ? 1 : a < b ? -1 : 0; } /** @internal */ export function clamp(value: number, lowerBound: number, upperBound: number) { - return minValueWithLowerLimit(value, upperBound, lowerBound); -} - -/** - * Return the minimum value between val1 and val2. The value is bounded from below by lowerLimit - * @param val1 a numeric value - * @param val2 a numeric value - * @param lowerLimit the lower limit - * @internal - */ -export function minValueWithLowerLimit(val1: number, val2: number, lowerLimit: number) { - return Math.max(Math.min(val1, val2), lowerLimit); -} - -/** - * Return the maximum value between val1 and val2. The value is bounded from above by upperLimit - * @param val1 a numeric value - * @param val2 a numeric value - * @param upperLimit the upper limit - * @internal - */ -export function maxValueWithUpperLimit(val1: number, val2: number, upperLimit: number) { - return Math.min(Math.max(val1, val2), upperLimit); + return Math.min(Math.max(value, lowerBound), upperBound); } /** diff --git a/packages/charts/src/utils/dimensions.ts b/packages/charts/src/utils/dimensions.ts index e0c68c66d6..22c5d51091 100644 --- a/packages/charts/src/utils/dimensions.ts +++ b/packages/charts/src/utils/dimensions.ts @@ -50,25 +50,10 @@ export interface SimplePadding { inner: number; } -/** - * Computes padding from number or `SimplePadding` with optional `minPadding` - * - * @param padding - * @param minPadding should be at least one to avoid browser measureText inconsistencies - * @internal - */ -export function getSimplePadding(padding: number | Partial, minPadding = 0): SimplePadding { - if (typeof padding === 'number') { - const adjustedPadding = Math.max(minPadding, padding); - - return { - inner: adjustedPadding, - outer: adjustedPadding, - }; - } +/** @internal */ +export const innerPad = (padding: number | Partial, minPadding = 0) => + Math.max(minPadding, typeof padding === 'number' ? padding : padding?.inner ?? 0); - return { - inner: Math.max(minPadding, padding?.inner ?? 0), - outer: Math.max(minPadding, padding?.outer ?? 0), - }; -} +/** @internal */ +export const outerPad = (padding: number | Partial, minPadding = 0) => + Math.max(minPadding, typeof padding === 'number' ? padding : padding?.outer ?? 0); diff --git a/renovate.json b/renovate.json index ba9207c069..587645044e 100644 --- a/renovate.json +++ b/renovate.json @@ -7,8 +7,7 @@ "separateMajorMinor": false, "rangeStrategy": "bump", "semanticCommits": "enabled", - "vulnerabilityAlerts": - { + "vulnerabilityAlerts": { "enabled": false }, "packageRules": [ diff --git a/yarn.lock b/yarn.lock index c797e9b219..5ac51e6d28 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9036,11 +9036,6 @@ compare-func@^2.0.0: array-ify "^1.0.0" dot-prop "^5.1.0" -compare-versions@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.6.0.tgz#1a5689913685e5a87637b8d3ffca75514ec41d62" - integrity sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA== - component-emitter@^1.2.1: version "1.3.0" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" @@ -11789,13 +11784,6 @@ find-up@^5.0.0: locate-path "^6.0.0" path-exists "^4.0.0" -find-versions@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/find-versions/-/find-versions-3.2.0.tgz#10297f98030a786829681690545ef659ed1d254e" - integrity sha512-P8WRou2S+oe222TOCHitLy8zj+SIsVJh52VP4lvXkaFVnOFFdoWv1H1Jjvel1aI6NCFOAaeAVm8qrI0odiLcww== - dependencies: - semver-regex "^2.0.0" - find-versions@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/find-versions/-/find-versions-4.0.0.tgz#3c57e573bf97769b8cb8df16934b627915da4965" @@ -13258,22 +13246,6 @@ humanize-ms@^1.2.1: dependencies: ms "^2.0.0" -husky@^4.3.6: - version "4.3.6" - resolved "https://registry.yarnpkg.com/husky/-/husky-4.3.6.tgz#ebd9dd8b9324aa851f1587318db4cccb7665a13c" - integrity sha512-o6UjVI8xtlWRL5395iWq9LKDyp/9TE7XMOTvIpEVzW638UcGxTmV5cfel6fsk/jbZSTlvfGVJf2svFtybcIZag== - dependencies: - chalk "^4.0.0" - ci-info "^2.0.0" - compare-versions "^3.6.0" - cosmiconfig "^7.0.0" - find-versions "^3.2.0" - opencollective-postinstall "^2.0.2" - pkg-dir "^4.2.0" - please-upgrade-node "^3.2.0" - slash "^3.0.0" - which-pm-runs "^1.0.0" - iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -17693,11 +17665,6 @@ open@^7.0.2, open@^7.0.3: is-docker "^2.0.0" is-wsl "^2.1.1" -opencollective-postinstall@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/opencollective-postinstall/-/opencollective-postinstall-2.0.2.tgz#5657f1bede69b6e33a45939b061eb53d3c6c3a89" - integrity sha512-pVOEP16TrAO2/fjej1IdOyupJY8KDUM1CvsaScRbw6oddvpQoOfGk4ywha0HKKVAD6RkW4x6Q+tNBwhf3Bgpuw== - opener@^1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598" @@ -20634,11 +20601,6 @@ semver-diff@^3.1.1: dependencies: semver "^6.3.0" -semver-regex@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/semver-regex/-/semver-regex-2.0.0.tgz#a93c2c5844539a770233379107b38c7b4ac9d338" - integrity sha512-mUdIBBvdn0PLOeP3TEkMH7HHeUP3GjsXCwKarjv/kGmUFOYg1VqEemKhoQpWMu6X2I8kHeuVdGibLGkVK+/5Qw== - semver-regex@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/semver-regex/-/semver-regex-3.1.2.tgz#34b4c0d361eef262e07199dbef316d0f2ab11807" @@ -23344,11 +23306,6 @@ which-module@^2.0.0: resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= -which-pm-runs@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb" - integrity sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs= - which@1, which@^1.2.12, which@^1.2.14, which@^1.2.9, which@^1.3.0, which@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"