diff --git a/integration/tests/__image_snapshots__/legend-stories-test-ts-legend-stories-custom-width-position-bottom-should-limit-size-to-max-70-of-chart-dimension-1-snap.png b/integration/tests/__image_snapshots__/legend-stories-test-ts-legend-stories-custom-width-position-bottom-should-limit-size-to-max-70-of-chart-dimension-1-snap.png new file mode 100644 index 0000000000..a46f2ebf12 Binary files /dev/null and b/integration/tests/__image_snapshots__/legend-stories-test-ts-legend-stories-custom-width-position-bottom-should-limit-size-to-max-70-of-chart-dimension-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/legend-stories-test-ts-legend-stories-custom-width-position-bottom-should-set-exact-size-of-legend-within-constraints-1-snap.png b/integration/tests/__image_snapshots__/legend-stories-test-ts-legend-stories-custom-width-position-bottom-should-set-exact-size-of-legend-within-constraints-1-snap.png new file mode 100644 index 0000000000..a46f2ebf12 Binary files /dev/null and b/integration/tests/__image_snapshots__/legend-stories-test-ts-legend-stories-custom-width-position-bottom-should-set-exact-size-of-legend-within-constraints-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/legend-stories-test-ts-legend-stories-custom-width-position-left-should-limit-size-to-max-70-of-chart-dimension-1-snap.png b/integration/tests/__image_snapshots__/legend-stories-test-ts-legend-stories-custom-width-position-left-should-limit-size-to-max-70-of-chart-dimension-1-snap.png new file mode 100644 index 0000000000..aef465ff7c Binary files /dev/null and b/integration/tests/__image_snapshots__/legend-stories-test-ts-legend-stories-custom-width-position-left-should-limit-size-to-max-70-of-chart-dimension-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/legend-stories-test-ts-legend-stories-custom-width-position-left-should-limit-width-to-min-of-30-of-computed-width-1-snap.png b/integration/tests/__image_snapshots__/legend-stories-test-ts-legend-stories-custom-width-position-left-should-limit-width-to-min-of-30-of-computed-width-1-snap.png new file mode 100644 index 0000000000..0f95eef07a Binary files /dev/null and b/integration/tests/__image_snapshots__/legend-stories-test-ts-legend-stories-custom-width-position-left-should-limit-width-to-min-of-30-of-computed-width-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/legend-stories-test-ts-legend-stories-custom-width-position-left-should-set-exact-size-of-legend-within-constraints-1-snap.png b/integration/tests/__image_snapshots__/legend-stories-test-ts-legend-stories-custom-width-position-left-should-set-exact-size-of-legend-within-constraints-1-snap.png new file mode 100644 index 0000000000..c869fb0512 Binary files /dev/null and b/integration/tests/__image_snapshots__/legend-stories-test-ts-legend-stories-custom-width-position-left-should-set-exact-size-of-legend-within-constraints-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/legend-stories-test-ts-legend-stories-custom-width-position-right-should-limit-size-to-max-70-of-chart-dimension-1-snap.png b/integration/tests/__image_snapshots__/legend-stories-test-ts-legend-stories-custom-width-position-right-should-limit-size-to-max-70-of-chart-dimension-1-snap.png new file mode 100644 index 0000000000..86d62ff5d6 Binary files /dev/null and b/integration/tests/__image_snapshots__/legend-stories-test-ts-legend-stories-custom-width-position-right-should-limit-size-to-max-70-of-chart-dimension-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/legend-stories-test-ts-legend-stories-custom-width-position-right-should-limit-width-to-min-of-30-of-computed-width-1-snap.png b/integration/tests/__image_snapshots__/legend-stories-test-ts-legend-stories-custom-width-position-right-should-limit-width-to-min-of-30-of-computed-width-1-snap.png new file mode 100644 index 0000000000..b46ee15268 Binary files /dev/null and b/integration/tests/__image_snapshots__/legend-stories-test-ts-legend-stories-custom-width-position-right-should-limit-width-to-min-of-30-of-computed-width-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/legend-stories-test-ts-legend-stories-custom-width-position-right-should-set-exact-size-of-legend-within-constraints-1-snap.png b/integration/tests/__image_snapshots__/legend-stories-test-ts-legend-stories-custom-width-position-right-should-set-exact-size-of-legend-within-constraints-1-snap.png new file mode 100644 index 0000000000..78ec2fb197 Binary files /dev/null and b/integration/tests/__image_snapshots__/legend-stories-test-ts-legend-stories-custom-width-position-right-should-set-exact-size-of-legend-within-constraints-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/legend-stories-test-ts-legend-stories-custom-width-position-top-should-limit-size-to-max-70-of-chart-dimension-1-snap.png b/integration/tests/__image_snapshots__/legend-stories-test-ts-legend-stories-custom-width-position-top-should-limit-size-to-max-70-of-chart-dimension-1-snap.png new file mode 100644 index 0000000000..5d50db0016 Binary files /dev/null and b/integration/tests/__image_snapshots__/legend-stories-test-ts-legend-stories-custom-width-position-top-should-limit-size-to-max-70-of-chart-dimension-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/legend-stories-test-ts-legend-stories-custom-width-position-top-should-set-exact-size-of-legend-within-constraints-1-snap.png b/integration/tests/__image_snapshots__/legend-stories-test-ts-legend-stories-custom-width-position-top-should-set-exact-size-of-legend-within-constraints-1-snap.png new file mode 100644 index 0000000000..5d50db0016 Binary files /dev/null and b/integration/tests/__image_snapshots__/legend-stories-test-ts-legend-stories-custom-width-position-top-should-set-exact-size-of-legend-within-constraints-1-snap.png differ diff --git a/integration/tests/legend_stories.test.ts b/integration/tests/legend_stories.test.ts index c7ca1ce091..db6c1e2d83 100644 --- a/integration/tests/legend_stories.test.ts +++ b/integration/tests/legend_stories.test.ts @@ -234,4 +234,27 @@ describe('Legend stories', () => { ); }); }); + + describe('Custom width', () => { + // eslint-disable-next-line unicorn/consistent-function-scoping + const getUrl = (position: string, size: number) => + `http://localhost:9001/?path=/story/legend--${position}&knob-enable legend size=true&knob-legend size=${size}`; + + describe.each(['top', 'right', 'bottom', 'left'])('position %s', (position) => { + const isVertical = position === 'left' || position === 'right'; + if (isVertical) { + it('should limit width to min of 30% of computed width', async () => { + await common.expectChartAtUrlToMatchScreenshot(getUrl(position, 1)); + }); + } + + it('should limit size to max 70% of chart dimension', async () => { + await common.expectChartAtUrlToMatchScreenshot(getUrl(position, 100000)); + }); + + it('should set exact size of legend within constraints', async () => { + await common.expectChartAtUrlToMatchScreenshot(getUrl(position, isVertical ? 400 : 300)); + }); + }); + }); }); diff --git a/packages/charts/api/charts.api.md b/packages/charts/api/charts.api.md index c016f0cd98..13849ac255 100644 --- a/packages/charts/api/charts.api.md +++ b/packages/charts/api/charts.api.md @@ -752,7 +752,7 @@ export const DEFAULT_TOOLTIP_SNAP = true; export const DEFAULT_TOOLTIP_TYPE: "vertical"; // @public (undocumented) -export type DefaultSettingsProps = 'id' | 'chartType' | 'specType' | 'rendering' | 'rotation' | 'resizeDebounce' | 'pointerUpdateDebounce' | 'pointerUpdateTrigger' | 'animateData' | 'debug' | 'tooltip' | 'theme' | 'brushAxis' | 'minBrushDelta' | 'externalPointerEvents' | 'showLegend' | 'showLegendExtra' | 'legendPosition' | 'legendMaxDepth' | 'ariaUseDefaultSummary' | 'ariaLabelHeadingLevel' | 'ariaTableCaption' | 'allowBrushingLastHistogramBin'; +export type DefaultSettingsProps = 'id' | 'chartType' | 'specType' | 'rendering' | 'rotation' | 'resizeDebounce' | 'pointerUpdateDebounce' | 'pointerUpdateTrigger' | 'animateData' | 'debug' | 'tooltip' | 'theme' | 'brushAxis' | 'minBrushDelta' | 'externalPointerEvents' | 'showLegend' | 'showLegendExtra' | 'legendPosition' | 'legendMaxDepth' | 'legendSize' | 'ariaUseDefaultSummary' | 'ariaLabelHeadingLevel' | 'ariaTableCaption' | 'allowBrushingLastHistogramBin'; // @public (undocumented) export const DEPTH_KEY = "depth"; @@ -1373,6 +1373,7 @@ export interface LegendSpec { legendColorPicker?: LegendColorPicker; legendMaxDepth: number; legendPosition: Position | LegendPositionConfig; + legendSize: number; legendStrategy?: LegendStrategy; // (undocumented) onLegendItemClick?: LegendItemListener; diff --git a/packages/charts/src/components/__snapshots__/chart.test.tsx.snap b/packages/charts/src/components/__snapshots__/chart.test.tsx.snap index 5963edb05f..85d2d80872 100644 --- a/packages/charts/src/components/__snapshots__/chart.test.tsx.snap +++ b/packages/charts/src/components/__snapshots__/chart.test.tsx.snap @@ -56,7 +56,7 @@ exports[`Chart should render the legend name test 1`] = ` - + diff --git a/packages/charts/src/specs/constants.ts b/packages/charts/src/specs/constants.ts index 39b39beeca..eb10f5e4fe 100644 --- a/packages/charts/src/specs/constants.ts +++ b/packages/charts/src/specs/constants.ts @@ -144,6 +144,7 @@ export const DEFAULT_TOOLTIP_SNAP = true; */ export const DEFAULT_LEGEND_CONFIG = { showLegend: false, + legendSize: NaN, showLegendExtra: false, legendMaxDepth: Infinity, legendPosition: Position.Right, diff --git a/packages/charts/src/specs/settings.tsx b/packages/charts/src/specs/settings.tsx index 72f4a1057c..dd16e1af8c 100644 --- a/packages/charts/src/specs/settings.tsx +++ b/packages/charts/src/specs/settings.tsx @@ -453,6 +453,13 @@ export interface LegendSpec { * Limit the legend to the specified maximal depth when showing a hierarchical legend */ legendMaxDepth: number; + /** + * Sets the exact legend width (vertical) or height (horizontal) + * + * Limited to max of 70% of the chart container dimension + * Vertical legends limited to min of 30% of computed width + */ + legendSize: number; /** * Display the legend as a flat list. If true, legendStrategy is always `LegendStrategy.Key`. */ @@ -692,6 +699,7 @@ export type DefaultSettingsProps = | 'showLegendExtra' | 'legendPosition' | 'legendMaxDepth' + | 'legendSize' | 'ariaUseDefaultSummary' | 'ariaLabelHeadingLevel' | 'ariaTableCaption' diff --git a/packages/charts/src/state/selectors/get_legend_config_selector.ts b/packages/charts/src/state/selectors/get_legend_config_selector.ts index 8a5d16d5d1..e0d4d9d9d5 100644 --- a/packages/charts/src/state/selectors/get_legend_config_selector.ts +++ b/packages/charts/src/state/selectors/get_legend_config_selector.ts @@ -18,6 +18,7 @@ export const getLegendConfigSelector = createCustomCachedSelector( legendAction, legendColorPicker, legendMaxDepth, + legendSize, legendPosition, legendStrategy, onLegendItemClick, @@ -33,6 +34,7 @@ export const getLegendConfigSelector = createCustomCachedSelector( legendAction, legendColorPicker, legendMaxDepth, + legendSize, legendPosition: getLegendPositionConfig(legendPosition), legendStrategy, onLegendItemClick, diff --git a/packages/charts/src/state/selectors/get_legend_size.ts b/packages/charts/src/state/selectors/get_legend_size.ts index e42329e8e0..5f40ef1057 100644 --- a/packages/charts/src/state/selectors/get_legend_size.ts +++ b/packages/charts/src/state/selectors/get_legend_size.ts @@ -66,19 +66,28 @@ export const getLegendSizeSelector = createCustomCachedSelector( const legendItemHeight = bbox.height + VERTICAL_PADDING * 2; const legendHeight = legendItemHeight * labels.length + TOP_MARGIN; const scrollBarDimension = legendHeight > parentDimensions.height ? SCROLL_BAR_WIDTH : 0; + const staticWidth = spacingBuffer + actionDimension + scrollBarDimension; + + const width = Number.isFinite(legendConfig.legendSize) + ? Math.min(Math.max(legendConfig.legendSize, legendItemWidth * 0.3 + staticWidth), parentDimensions.width * 0.7) + : Math.floor(Math.min(legendItemWidth + staticWidth, verticalWidth)); return { - width: Math.floor( - Math.min(legendItemWidth + spacingBuffer + actionDimension + scrollBarDimension, verticalWidth), - ), + width, height: legendHeight, margin, position: legendPosition, }; } const isSingleLine = (parentDimensions.width - 20) / 200 > labels.length; + const height = Number.isFinite(legendConfig.legendSize) + ? Math.min(legendConfig.legendSize, parentDimensions.height * 0.7) + : isSingleLine + ? bbox.height + 16 + : bbox.height * 2 + 24; + return { - height: isSingleLine ? bbox.height + 16 : bbox.height * 2 + 24, + height, width: Math.floor(Math.min(legendItemWidth + spacingBuffer + actionDimension, verticalWidth)), margin, position: legendPosition, diff --git a/storybook/stories/legend/1_legend_right.story.tsx b/storybook/stories/legend/1_legend_right.story.tsx index ad25ce6ace..c375211a62 100644 --- a/storybook/stories/legend/1_legend_right.story.tsx +++ b/storybook/stories/legend/1_legend_right.story.tsx @@ -12,6 +12,7 @@ import { Axis, BarSeries, Chart, Position, ScaleType, Settings } from '@elastic/ import * as TestDatasets from '@elastic/charts/src/utils/data_samples/test_dataset'; import { useBaseTheme } from '../../use_base_theme'; +import { getLegendSizeKnob } from './legend_size_knob'; export const Example = () => { const yAccessors = ['y1', 'y2']; @@ -19,7 +20,13 @@ export const Example = () => { return ( - + Number(d).toFixed(2)} /> diff --git a/storybook/stories/legend/2_legend_bottom.story.tsx b/storybook/stories/legend/2_legend_bottom.story.tsx index 00fac5f232..31f5be349e 100644 --- a/storybook/stories/legend/2_legend_bottom.story.tsx +++ b/storybook/stories/legend/2_legend_bottom.story.tsx @@ -12,10 +12,17 @@ import { Axis, BarSeries, Chart, Position, ScaleType, Settings } from '@elastic/ import * as TestDatasets from '@elastic/charts/src/utils/data_samples/test_dataset'; import { useBaseTheme } from '../../use_base_theme'; +import { getLegendSizeKnob } from './legend_size_knob'; export const Example = () => ( - + Number(d).toFixed(2)} /> diff --git a/storybook/stories/legend/3_legend_left.story.tsx b/storybook/stories/legend/3_legend_left.story.tsx index 0d33649cd2..2e47dee072 100644 --- a/storybook/stories/legend/3_legend_left.story.tsx +++ b/storybook/stories/legend/3_legend_left.story.tsx @@ -12,10 +12,17 @@ import { Axis, BarSeries, Chart, Position, ScaleType, Settings } from '@elastic/ import * as TestDatasets from '@elastic/charts/src/utils/data_samples/test_dataset'; import { useBaseTheme } from '../../use_base_theme'; +import { getLegendSizeKnob } from './legend_size_knob'; export const Example = () => ( - + Number(d).toFixed(2)} /> diff --git a/storybook/stories/legend/4_legend_top.story.tsx b/storybook/stories/legend/4_legend_top.story.tsx index b264b359e0..86e07f7833 100644 --- a/storybook/stories/legend/4_legend_top.story.tsx +++ b/storybook/stories/legend/4_legend_top.story.tsx @@ -12,10 +12,17 @@ import { Axis, BarSeries, Chart, Position, ScaleType, Settings } from '@elastic/ import * as TestDatasets from '@elastic/charts/src/utils/data_samples/test_dataset'; import { useBaseTheme } from '../../use_base_theme'; +import { getLegendSizeKnob } from './legend_size_knob'; export const Example = () => ( - + Number(d).toFixed(2)} /> diff --git a/storybook/stories/legend/legend_size_knob.ts b/storybook/stories/legend/legend_size_knob.ts new file mode 100644 index 0000000000..f9cc7ea1fb --- /dev/null +++ b/storybook/stories/legend/legend_size_knob.ts @@ -0,0 +1,15 @@ +/* + * 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 { boolean, number } from '@storybook/addon-knobs'; + +export const getLegendSizeKnob = (group?: string) => { + const enabled = boolean('enable legend size', false, group); + const size = enabled ? number('legend size', 200, { min: 0, step: 1 }, group) : NaN; + return size; +};