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 6969a79dbf..b4617e6534 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 { innerPad, outerPad, PerSideDistance } from '../../../utils/dimensions'; +import { Dimensions, innerPad, outerPad, PerSideDistance } from '../../../utils/dimensions'; import { AxisId } from '../../../utils/ids'; import { AxisStyle, Theme } from '../../../utils/themes/theme'; import { AxesTicksDimensions } from '../state/selectors/compute_axis_ticks_dimensions'; @@ -37,11 +37,7 @@ const getAxisSizeForLabel = ( const maxAxisGirth = axisDimension + (tickLabel.visible ? allLayersGirth : 0); // gives space to longer labels: if vertical use half of the label height, if horizontal, use half of the max label (not ideal) // don't overflow when the multiTimeAxis layer is used. - const maxLabelBoxHalfLength = isVerticalAxis(axisSpec.position) - ? maxLabelBboxHeight / 2 - : axisSpec.timeAxisLayerCount > 0 - ? 0 - : maxLabelBboxWidth / 2; + const maxLabelBoxHalfLength = isVerticalAxis(axisSpec.position) ? maxLabelBboxHeight / 2 : 0; return horizontal ? { top: axisSpec.position === Position.Top ? maxAxisGirth + chartMargins.top : 0, @@ -59,12 +55,17 @@ const getAxisSizeForLabel = ( /** @internal */ export function getAxesDimensions( + parentDimensions: Dimensions, theme: Theme, axisDimensions: AxesTicksDimensions, axesStyles: Map, axisSpecs: AxisSpec[], smSpec: SmallMultiplesSpec | null, ): PerSideDistance & { margin: { left: number } } { + const verticalAxesCount = + axisSpecs.reduce((count, spec) => { + return count + (isVerticalAxis(spec.position) ? 1 : 0); + }, 0) * 2; const sizes = [...axisDimensions].reduce( (acc, [id, tickLabelBounds]) => { const axisSpec = getSpecsById(axisSpecs, id); @@ -74,8 +75,8 @@ export function getAxesDimensions( if (isVerticalAxis(axisSpec.position)) { acc.axisLabelOverflow.top = Math.max(acc.axisLabelOverflow.top, top); acc.axisLabelOverflow.bottom = Math.max(acc.axisLabelOverflow.bottom, bottom); - acc.axisMainSize.left += left; - acc.axisMainSize.right += right; + acc.axisMainSize.left += Math.min(left, parentDimensions.width / verticalAxesCount); + acc.axisMainSize.right += Math.min(right, parentDimensions.width / verticalAxesCount); } else { // find the max half label size to accommodate the left/right labels acc.axisMainSize.top += top; diff --git a/packages/charts/src/chart_types/xy_chart/renderer/canvas/axes/index.ts b/packages/charts/src/chart_types/xy_chart/renderer/canvas/axes/index.ts index f4020aba13..a473026c65 100644 --- a/packages/charts/src/chart_types/xy_chart/renderer/canvas/axes/index.ts +++ b/packages/charts/src/chart_types/xy_chart/renderer/canvas/axes/index.ts @@ -30,6 +30,7 @@ export interface AxisProps { debug: boolean; renderingArea: Dimensions; layerGirth: number; + maxLabelSize: number; } /** @internal */ diff --git a/packages/charts/src/chart_types/xy_chart/renderer/canvas/axes/tick_label.ts b/packages/charts/src/chart_types/xy_chart/renderer/canvas/axes/tick_label.ts index b7f869e87d..e4513d230c 100644 --- a/packages/charts/src/chart_types/xy_chart/renderer/canvas/axes/tick_label.ts +++ b/packages/charts/src/chart_types/xy_chart/renderer/canvas/axes/tick_label.ts @@ -7,7 +7,9 @@ */ import { AxisProps } from '.'; +import { measureText } from '../../../../../utils/bbox/canvas_text_bbox_calculator'; import { Position } from '../../../../../utils/common'; +import { wrapText } from '../../../../../utils/text/wrap'; import { AxisTick, getTickLabelPosition } from '../../../utils/axis_utils'; import { renderText } from '../primitives/text'; import { renderDebugRectCenterRotated } from '../utils/debug'; @@ -19,7 +21,7 @@ export function renderTickLabel( ctx: CanvasRenderingContext2D, tick: AxisTick, showTicks: boolean, - { axisSpec: { position, timeAxisLayerCount }, dimension, size, debug, axisStyle }: AxisProps, + { axisSpec: { position, timeAxisLayerCount }, dimension, size, debug, axisStyle, maxLabelSize }: AxisProps, layerGirth: number, ) { const labelStyle = axisStyle.tickLabel; @@ -36,7 +38,22 @@ export function renderTickLabel( ); const center = { x: tickLabelProps.x + tickLabelProps.offsetX, y: tickLabelProps.y + tickLabelProps.offsetY }; - + const textMeasure = measureText(ctx); + const wrappedText = wrapText( + tick.label, + { + fontFamily: labelStyle.fontFamily, + fontStyle: labelStyle.fontStyle ?? 'normal', + fontVariant: 'normal', + fontWeight: 'normal', + textColor: labelStyle.fill, + }, + labelStyle.fontSize, + maxLabelSize, + 1, + textMeasure, + 'en', // TODO + ); if (debug) { const { maxLabelBboxWidth, maxLabelBboxHeight, maxLabelTextWidth: width, maxLabelTextHeight: height } = dimension; // full text container @@ -52,7 +69,7 @@ export function renderTickLabel( renderText( ctx, center, - tick.label, + wrappedText[0] ?? '', { fontFamily: labelStyle.fontFamily, fontStyle: labelStyle.fontStyle ?? 'normal', 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 5e694dd636..bee9c8ac87 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 @@ -69,6 +69,7 @@ export function renderPanelSubstrates(ctx: CanvasRenderingContext2D, props: Axes dimension, visibleTicks: ticks, parentSize, + maxLabelSize, } = geometry; const axisSpec = getSpecsById(axesSpecs, id); @@ -105,6 +106,7 @@ export function renderPanelSubstrates(ctx: CanvasRenderingContext2D, props: Axes debug, renderingArea, layerGirth, + maxLabelSize, }, locale, ); diff --git a/packages/charts/src/chart_types/xy_chart/state/selectors/compute_axes_geometries.ts b/packages/charts/src/chart_types/xy_chart/state/selectors/compute_axes_geometries.ts index b1b5a0cc89..76657d8e82 100644 --- a/packages/charts/src/chart_types/xy_chart/state/selectors/compute_axes_geometries.ts +++ b/packages/charts/src/chart_types/xy_chart/state/selectors/compute_axes_geometries.ts @@ -12,12 +12,14 @@ import { axisSpecsLookupSelector } from './get_specs'; import { getVisibleTickSetsSelector } from './visible_ticks'; import { createCustomCachedSelector } from '../../../../state/create_selector'; import { computeSmallMultipleScalesSelector } from '../../../../state/selectors/compute_small_multiple_scales'; +import { getChartContainerDimensionsSelector } from '../../../../state/selectors/get_chart_container_dimensions'; import { getChartThemeSelector } from '../../../../state/selectors/get_chart_theme'; import { getAxesGeometries } from '../../utils/axis_utils'; /** @internal */ export const computeAxesGeometriesSelector = createCustomCachedSelector( [ + getChartContainerDimensionsSelector, computeChartDimensionsSelector, getChartThemeSelector, axisSpecsLookupSelector, diff --git a/packages/charts/src/chart_types/xy_chart/state/selectors/visible_ticks.ts b/packages/charts/src/chart_types/xy_chart/state/selectors/visible_ticks.ts index aac19c35ee..f77ee86fb1 100644 --- a/packages/charts/src/chart_types/xy_chart/state/selectors/visible_ticks.ts +++ b/packages/charts/src/chart_types/xy_chart/state/selectors/visible_ticks.ts @@ -151,7 +151,7 @@ function getVisibleTicks( const { showOverlappingTicks, showOverlappingLabels, position } = axisSpec; const requiredSpace = isVerticalAxis(position) ? labelBox.maxLabelBboxHeight / 2 : labelBox.maxLabelBboxWidth / 2; - const bypassOverlapCheck = showOverlappingLabels || isMultilayerTimeAxis; + const bypassOverlapCheck = scale.type === ScaleType.Ordinal || showOverlappingLabels || isMultilayerTimeAxis; return bypassOverlapCheck ? allTicks : [...allTicks] 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 be6fdaaf7c..9cc880ea99 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 @@ -10,6 +10,7 @@ import { isHorizontalAxis, isVerticalAxis } from './axis_type_utils'; import { computeXScale, computeYScales } from './scales'; import { SmallMultipleScales, hasSMDomain, getPanelSize } from '../../../common/panel_utils'; import { ScaleBand, ScaleContinuous } from '../../../scales'; +import { isContinuousScale } from '../../../scales/types'; import { AxisSpec, SettingsSpec } from '../../../specs'; import { degToRad, @@ -261,6 +262,7 @@ export const getAllAxisLayersGirth = ( /** @internal */ export function getPosition( + parentDimension: Dimensions, { chartDimensions }: { chartDimensions: Dimensions }, chartMargins: PerSideDistance, { axisTitle, axisPanelTitle, tickLine, tickLabel }: AxisStyle, @@ -268,6 +270,7 @@ export function getPosition( { maxLabelBboxHeight, maxLabelBboxWidth }: TickLabelBounds, smScales: SmallMultipleScales, { top: cumTopSum, bottom: cumBottomSum, left: cumLeftSum, right: cumRightSum }: PerSideDistance, + verticalAxisCount: number, ) { const tickDimension = shouldShowTicks(tickLine, hide) ? tickLine.size + tickLine.padding : 0; const labelPaddingSum = tickLabel.visible ? innerPad(tickLabel.padding) + outerPad(tickLabel.padding) : 0; @@ -276,7 +279,8 @@ export function getPosition( const scaleBand = vertical ? smScales.vertical : smScales.horizontal; const panelTitleDimension = hasSMDomain(scaleBand) ? getTitleDimension(axisPanelTitle) : 0; const maxLabelBboxGirth = tickLabel.visible ? (vertical ? maxLabelBboxWidth : maxLabelBboxHeight) : 0; - const shownLabelSize = getAllAxisLayersGirth(timeAxisLayerCount, maxLabelBboxGirth, !vertical); + const layerGrith = getAllAxisLayersGirth(timeAxisLayerCount, maxLabelBboxGirth, !vertical); + const shownLabelSize = vertical ? Math.min(parentDimension.width / (verticalAxisCount * 2), layerGrith) : layerGrith; const parallelSize = labelPaddingSum + shownLabelSize + tickDimension + titleDimension + panelTitleDimension; return { leftIncrement: position === Position.Left ? parallelSize + chartMargins.left : 0, @@ -316,10 +320,12 @@ export interface AxisGeometry { }; dimension: TickLabelBounds; visibleTicks: AxisTick[]; + maxLabelSize: number; } /** @internal */ export function getAxesGeometries( + parentDimension: Dimensions, chartDims: { chartDimensions: Dimensions; leftMargin: number }, { chartPaddings, chartMargins, axes: sharedAxesStyle }: Theme, axisSpecs: Map, @@ -327,14 +333,18 @@ export function getAxesGeometries( smScales: SmallMultipleScales, visibleTicksSet: Map, ): AxisGeometry[] { + const verticalAxesCount = [...axisSpecs.values()].reduce((count, spec) => { + return count + (isVerticalAxis(spec.position) ? 1 : 0); + }, 0); const panel = getPanelSize(smScales); return [...visibleTicksSet].reduce( - (acc: PerSideDistance & { geoms: AxisGeometry[] }, [axisId, { ticks, labelBox }]: [AxisId, Projection]) => { + (acc: PerSideDistance & { geoms: AxisGeometry[] }, [axisId, { ticks, labelBox, scale }]: [AxisId, Projection]) => { const axisSpec = axisSpecs.get(axisId); if (axisSpec) { const vertical = isVerticalAxis(axisSpec.position); const axisStyle = axesStyles.get(axisId) ?? sharedAxesStyle; const { dimensions, topIncrement, bottomIncrement, leftIncrement, rightIncrement } = getPosition( + parentDimension, chartDims, chartMargins, axisStyle, @@ -342,6 +352,7 @@ export function getAxesGeometries( labelBox, smScales, acc, + verticalAxesCount, ); acc.top += topIncrement; acc.bottom += bottomIncrement; @@ -357,6 +368,12 @@ export function getAxesGeometries( width: labelBox.isHidden ? 0 : vertical ? dimensions.width : panel.width, height: labelBox.isHidden ? 0 : vertical ? panel.height : dimensions.height, }, + maxLabelSize: + vertical && !isContinuousScale(scale) + ? parentDimension.width / (verticalAxesCount * 2) + : isContinuousScale(scale) + ? Infinity + : scale.step, }); } else { throw new Error(`Cannot compute scale for axis spec ${axisId}`); // todo move this feedback as upstream as possible diff --git a/packages/charts/src/chart_types/xy_chart/utils/dimensions.ts b/packages/charts/src/chart_types/xy_chart/utils/dimensions.ts index f545d2b094..7c645bcea3 100644 --- a/packages/charts/src/chart_types/xy_chart/utils/dimensions.ts +++ b/packages/charts/src/chart_types/xy_chart/utils/dimensions.ts @@ -40,7 +40,7 @@ export interface ChartDimensions { axisSpecs: AxisSpec[], smSpec: SmallMultiplesSpec | null, ): ChartDimensions { - const axesDimensions = getAxesDimensions(theme, axisTickDimensions, axesStyles, axisSpecs, smSpec); + const axesDimensions = getAxesDimensions(parentDimensions, theme, axisTickDimensions, axesStyles, axisSpecs, smSpec); const chartWidth = parentDimensions.width - axesDimensions.left - axesDimensions.right; const chartHeight = parentDimensions.height - axesDimensions.top - axesDimensions.bottom; const pad = theme.chartPaddings; diff --git a/storybook/stories/bar/a1_horizontal_bars.story.tsx b/storybook/stories/bar/a1_horizontal_bars.story.tsx new file mode 100644 index 0000000000..59495a671b --- /dev/null +++ b/storybook/stories/bar/a1_horizontal_bars.story.tsx @@ -0,0 +1,136 @@ +/* + * 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 { range } from 'lodash'; +import React from 'react'; + +import { + Axis, + BarSeries, + Chart, + GroupBy, + Position, + ScaleType, + Settings, + SmallMultiples, + DataGenerator, +} from '@elastic/charts'; +import { getRandomNumberGenerator } from '@elastic/charts/src/mocks/utils'; + +import { ChartsStory } from '../../types'; +import { useBaseTheme } from '../../use_base_theme'; +const ng = getRandomNumberGenerator(); +const rng = new DataGenerator(); +const data = rng.generateSMGroupedSeries(3, 2, () => { + return rng.generateSimpleSeries(10).flatMap((d) => + range(0, 6, 1).map((y) => { + return { + x: `category-longer-then-${d.x}`, + y: ng(0, 1000), + }; + }), + ); +}); +console.log(data); +export const Example: ChartsStory = (_, { title, description }) => { + return ( +
+
+ + + + + + + + {/* datum.g} sort="alphaAsc" /> + */} + +
+
+ + + + + + {/* */} + + datum.h} sort="alphaAsc" /> + datum.v} sort="alphaAsc" /> + + +
+
+ ); +}; diff --git a/storybook/stories/bar/bars.stories.tsx b/storybook/stories/bar/bars.stories.tsx index 1d4bceffa1..86ef035b09 100644 --- a/storybook/stories/bar/bars.stories.tsx +++ b/storybook/stories/bar/bars.stories.tsx @@ -72,3 +72,4 @@ export { Example as testDualYAxis } from './49_test_dual_axis.story'; export { Example as testUseDefaultGroupDomain } from './56_test_use_dfl_gdomain.story'; export { Example as testRectBorder } from './57_test_rect_border_bars.story'; export { Example as dataValue } from './58_data_values.story'; +export { Example as horizontalBarChart } from './a1_horizontal_bars.story';