diff --git a/api/charts.api.md b/api/charts.api.md index 73b69aec69..82deef6b39 100644 --- a/api/charts.api.md +++ b/api/charts.api.md @@ -1562,6 +1562,8 @@ export interface RectBorderStyle { export interface RectStyle { fill?: Color | ColorVariant; opacity: number; + widthPixel?: Pixels; + widthRatio?: Ratio; } // @public diff --git a/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-custom-bar-width-pixel-and-ratio-size-1-snap.png b/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-custom-bar-width-pixel-and-ratio-size-1-snap.png new file mode 100644 index 0000000000..e204b084eb Binary files /dev/null and b/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-custom-bar-width-pixel-and-ratio-size-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-custom-bar-width-pixel-size-1-snap.png b/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-custom-bar-width-pixel-size-1-snap.png new file mode 100644 index 0000000000..28394cb328 Binary files /dev/null and b/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-custom-bar-width-pixel-size-1-snap.png differ diff --git a/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-custom-bar-width-ratio-size-1-snap.png b/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-custom-bar-width-ratio-size-1-snap.png new file mode 100644 index 0000000000..3f09610f00 Binary files /dev/null and b/integration/tests/__image_snapshots__/bar-stories-test-ts-bar-series-stories-custom-bar-width-ratio-size-1-snap.png differ diff --git a/integration/tests/bar_stories.test.ts b/integration/tests/bar_stories.test.ts index 02e468497c..0f63b5edc6 100644 --- a/integration/tests/bar_stories.test.ts +++ b/integration/tests/bar_stories.test.ts @@ -227,4 +227,21 @@ describe('Bar series stories', () => { ); }); }); + describe('custom bar width', () => { + it('pixel size', async () => { + await common.expectChartAtUrlToMatchScreenshot( + 'http://localhost:9001/?path=/story/stylings--custom-series-styles-bars&knob-apply bar style (bar 1 series)_Chart Global Theme=true&knob-enable custom rect width (px)_Bar width=true&knob-rect width (px)_Bar width=15&knob-enable custom rect width (ratio)_Bar width=&knob-rect width (ratio)_Bar width=0.5&knob-border stroke_Bar 1 Style=blue&knob-border strokeWidth_Bar 1 Style=2&knob-border visible_Bar 1 Style=true&knob-rect fill_Bar 1 Style=#22C61A&knob-rect opacity_Bar 1 Style=0.3&knob-theme border stroke_Chart Global Theme=red&knob-theme border strokeWidth_Chart Global Theme=2&knob-theme border visible_Chart Global Theme=true&knob-theme opacity _Chart Global Theme=0.9', + ); + }); + it('ratio size', async () => { + await common.expectChartAtUrlToMatchScreenshot( + 'http://localhost:9001/?path=/story/stylings--custom-series-styles-bars&knob-apply bar style (bar 1 series)_Chart Global Theme=true&knob-enable custom rect width (px)_Bar width=&knob-rect width (px)_Bar width=30&knob-enable custom rect width (ratio)_Bar width=true&knob-rect width (ratio)_Bar width=0.5&knob-border stroke_Bar 1 Style=blue&knob-border strokeWidth_Bar 1 Style=2&knob-border visible_Bar 1 Style=true&knob-rect fill_Bar 1 Style=#22C61A&knob-rect opacity_Bar 1 Style=0.3&knob-theme border stroke_Chart Global Theme=red&knob-theme border strokeWidth_Chart Global Theme=2&knob-theme border visible_Chart Global Theme=true&knob-theme opacity _Chart Global Theme=0.9', + ); + }); + it('pixel and ratio size', async () => { + await common.expectChartAtUrlToMatchScreenshot( + 'http://localhost:9001/?path=/story/stylings--custom-series-styles-bars&knob-apply bar style (bar 1 series)_Chart Global Theme=true&knob-enable custom rect width (px)_Bar width=true&knob-rect width (px)_Bar width=40&knob-enable custom rect width (ratio)_Bar width=true&knob-rect width (ratio)_Bar width=0.2&knob-border stroke_Bar 1 Style=blue&knob-border strokeWidth_Bar 1 Style=2&knob-border visible_Bar 1 Style=true&knob-rect fill_Bar 1 Style=#22C61A&knob-rect opacity_Bar 1 Style=0.3&knob-theme border stroke_Chart Global Theme=red&knob-theme border strokeWidth_Chart Global Theme=2&knob-theme border visible_Chart Global Theme=true&knob-theme opacity _Chart Global Theme=0.9', + ); + }); + }); }); diff --git a/src/chart_types/xy_chart/rendering/bars.ts b/src/chart_types/xy_chart/rendering/bars.ts index 5bf35fb0e9..c37652bf7b 100644 --- a/src/chart_types/xy_chart/rendering/bars.ts +++ b/src/chart_types/xy_chart/rendering/bars.ts @@ -20,7 +20,7 @@ import { Scale } from '../../../scales'; import { ScaleType } from '../../../scales/constants'; import { CanvasTextBBoxCalculator } from '../../../utils/bbox/canvas_text_bbox_calculator'; -import { Color, mergePartial } from '../../../utils/common'; +import { clamp, Color, mergePartial } from '../../../utils/common'; import { Dimensions } from '../../../utils/dimensions'; import { BandedAccessorType, BarGeometry } from '../../../utils/geometry'; import { BarSeriesStyle, DisplayValueStyle } from '../../../utils/themes/theme'; @@ -107,8 +107,24 @@ export function renderBars( return; } - const x = xScaled + xScale.bandwidth * orderIndex; - const width = xScale.bandwidth; + 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 ? y1 - (y0 ?? 0) : initialY1; const formattedDisplayValue = displayValueSettings && displayValueSettings.valueFormatter @@ -156,18 +172,6 @@ export function renderBars( } : undefined; - 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 barGeometry: BarGeometry = { displayValue, x, diff --git a/src/utils/themes/theme.ts b/src/utils/themes/theme.ts index e978c39072..6ddbb10b2e 100644 --- a/src/utils/themes/theme.ts +++ b/src/utils/themes/theme.ts @@ -19,6 +19,7 @@ import { $Values } from 'utility-types'; +import { Pixels, Ratio } from '../../common/geometry'; import { Color, ColorVariant, HorizontalAlignment, RecursivePartial, VerticalAlignment } from '../common'; import { Margins, SimplePadding } from '../dimensions'; @@ -398,6 +399,12 @@ export interface RectStyle { fill?: Color | ColorVariant; /** the opacity of each rect on the theme/series */ opacity: number; + /** The width of the rect in pixel. If expressed together with `widthRatio` then the `widthRatio` + * will express the max available size, where the `widthPixel` express the derived/min width. */ + widthPixel?: Pixels; + /** The ratio of the width limited to [0,1]. If expressed together with `widthPixel` then the `widthRatio` + * will express the max available size, where the `widthPixel` express the derived/min width. */ + widthRatio?: Ratio; } /** @public */ diff --git a/stories/stylings/10_custom_bars.tsx b/stories/stylings/10_custom_bars.tsx index 5f08cd0235..035785e949 100644 --- a/stories/stylings/10_custom_bars.tsx +++ b/stories/stylings/10_custom_bars.tsx @@ -20,7 +20,7 @@ import { boolean, color, number } from '@storybook/addon-knobs'; import React from 'react'; -import { Axis, BarSeries, Chart, Position, ScaleType, Settings } from '../../src'; +import { Axis, BarSeries, Chart, Position, ScaleType, Settings, PartialTheme } from '../../src'; import * as TestDatasets from '../../src/utils/data_samples/test_dataset'; function range(title: string, min: number, max: number, value: number, groupId?: string, step = 1) { @@ -39,7 +39,10 @@ function range(title: string, min: number, max: number, value: number, groupId?: export const Example = () => { const applyBarStyle = boolean('apply bar style (bar 1 series)', true, 'Chart Global Theme'); - + const changeRectWidthPixel = boolean('enable custom rect width (px)', false, 'Bar width'); + const rectWidthPixel = range('rect width (px)', 0, 100, 30, 'Bar width', 1); + const changeRectWidthRatio = boolean('enable custom rect width (ratio)', false, 'Bar width'); + const rectWidthRatio = range('rect width (ratio)', 0, 1, 0.5, 'Bar width', 0.01); const barSeriesStyle = { rectBorder: { stroke: color('border stroke', 'blue', 'Bar 1 Style'), @@ -52,7 +55,7 @@ export const Example = () => { }, }; - const theme = { + const theme: PartialTheme = { barSeriesStyle: { rectBorder: { stroke: color('theme border stroke', 'red', 'Chart Global Theme'), @@ -61,6 +64,8 @@ export const Example = () => { }, rect: { opacity: range('theme opacity ', 0, 1, 0.9, 'Chart Global Theme', 0.1), + widthPixel: changeRectWidthPixel ? rectWidthPixel : undefined, + widthRatio: changeRectWidthRatio ? rectWidthRatio : undefined, }, }, };