From 1230c707d3f0c2ae6e2d36f5bdd8d75e7c694d92 Mon Sep 17 00:00:00 2001 From: Michael Best Date: Mon, 19 Oct 2020 21:26:53 -0700 Subject: [PATCH 1/5] [VerticalStackedBarChart] bar gaps and rounded corners --- ...7-VerticalStackedBarChart-bar-styling.json | 8 ++ ...7-VerticalStackedBarChart-bar-styling.json | 8 ++ .../VerticalStackedBarChart.base.tsx | 86 +++++++++++++------ .../VerticalStackedBarChart.types.ts | 12 +++ ...VerticalStackedBarChart.Styled.Example.tsx | 60 ++++++++++--- 5 files changed, 132 insertions(+), 42 deletions(-) create mode 100644 change/@fluentui-react-examples-2020-10-19-21-31-07-VerticalStackedBarChart-bar-styling.json create mode 100644 change/@uifabric-charting-2020-10-19-21-31-07-VerticalStackedBarChart-bar-styling.json diff --git a/change/@fluentui-react-examples-2020-10-19-21-31-07-VerticalStackedBarChart-bar-styling.json b/change/@fluentui-react-examples-2020-10-19-21-31-07-VerticalStackedBarChart-bar-styling.json new file mode 100644 index 0000000000000..e9a6ad16458a2 --- /dev/null +++ b/change/@fluentui-react-examples-2020-10-19-21-31-07-VerticalStackedBarChart-bar-styling.json @@ -0,0 +1,8 @@ +{ + "type": "minor", + "comment": "[VerticalStackedBarChart] bar gaps and rounded corners", + "packageName": "@fluentui/react-examples", + "email": "mibes@microsoft.com", + "dependentChangeType": "patch", + "date": "2020-10-20T04:31:07.173Z" +} diff --git a/change/@uifabric-charting-2020-10-19-21-31-07-VerticalStackedBarChart-bar-styling.json b/change/@uifabric-charting-2020-10-19-21-31-07-VerticalStackedBarChart-bar-styling.json new file mode 100644 index 0000000000000..93165088160f3 --- /dev/null +++ b/change/@uifabric-charting-2020-10-19-21-31-07-VerticalStackedBarChart-bar-styling.json @@ -0,0 +1,8 @@ +{ + "type": "minor", + "comment": "[VerticalStackedBarChart] bar gaps and rounded corners", + "packageName": "@uifabric/charting", + "email": "mibes@microsoft.com", + "dependentChangeType": "patch", + "date": "2020-10-20T04:31:00.016Z" +} diff --git a/packages/charting/src/components/VerticalStackedBarChart/VerticalStackedBarChart.base.tsx b/packages/charting/src/components/VerticalStackedBarChart/VerticalStackedBarChart.base.tsx index 946865532992a..105c047367b24 100644 --- a/packages/charting/src/components/VerticalStackedBarChart/VerticalStackedBarChart.base.tsx +++ b/packages/charting/src/components/VerticalStackedBarChart/VerticalStackedBarChart.base.tsx @@ -3,7 +3,7 @@ import { max as d3Max } from 'd3-array'; import { Axis as D3Axis } from 'd3-axis'; import { scaleLinear as d3ScaleLinear, ScaleLinear as D3ScaleLinear } from 'd3-scale'; import { classNamesFunction, getId, getRTL, find, warnDeprecations } from 'office-ui-fabric-react/lib/Utilities'; -import { IProcessedStyleSet, IPalette } from 'office-ui-fabric-react/lib/Styling'; +import { IPalette } from 'office-ui-fabric-react/lib/Styling'; import { DirectionalHint } from 'office-ui-fabric-react/lib/Callout'; import { ILegend, Legends } from '../Legends/index'; import { @@ -28,6 +28,12 @@ type NumericScale = D3ScaleLinear; type StringScale = D3ScaleLinear; const COMPONENT_NAME = 'VERTICAL STACKED BAR CHART'; +// When displaying gaps between bars, the max height of the gap is given in the +// props. The actual gap is calculated with this multiplier, with a minimum gap +// of 1 pixel. +const barGapMultiplier = 0.2; +const barGapMin = 1; + interface IRefArrayData { refElement?: SVGGElement | null; } @@ -48,7 +54,6 @@ export class VerticalStackedBarChartBase extends React.Component< private _isNumeric: boolean; private _barWidth: number; private _calloutId: string; - private _classNames: IProcessedStyleSet; private _colors: string[]; private margins: IMargins; private _isRtl: boolean = getRTL(); @@ -96,11 +101,6 @@ export class VerticalStackedBarChartBase extends React.Component< this._isNumeric = this._dataset.length > 0 && typeof this._dataset[0].x === 'number'; const legendBars: JSX.Element = this._getLegendData(this._points, this.props.theme!.palette); - this._classNames = getClassNames(this.props.styles!, { - href: this.props.href!, - theme: this.props.theme!, - }); - const calloutProps = { isCalloutVisible: this.state.isCalloutVisible, directionalHint: DirectionalHint.topRightEdge, @@ -366,20 +366,29 @@ export class VerticalStackedBarChartBase extends React.Component< yBarScale: NumericScale, containerHeight: number, ): JSX.Element[] => { + const { barGapMax = 0, barCornerRadius = 0 } = this.props; + const bars = this._points.map((singleChartData: IVerticalStackedChartProps, indexNumber: number) => { - let startingPointOfY = 0; + let yPoint = containerHeight - this.margins.bottom!; const isCalloutForStack = this.props.isCalloutForStack || false; + const xPoint = xBarScale(this._isNumeric ? (singleChartData.xAxisPoint as number) : indexNumber); - let xPoint: number | string; - if (this._isNumeric) { - xPoint = xBarScale(singleChartData.xAxisPoint as number); - } else { - xPoint = xBarScale(indexNumber); - } - // Removing datapoints with zero data + // Removing data points with zero data const nonZeroBars = singleChartData.chartData.filter(point => point.data > 0); + + // When displaying gaps between the bars, the height of each bar is + // adjusted so that the total of all bars remains correct + const totalData = nonZeroBars.reduce((iter, value) => iter + value.data, 0); + const totalHeight = yBarScale(totalData); + const spaces = barGapMax && nonZeroBars.length - 1; + const spaceHeight = spaces && Math.max(barGapMin, Math.min(barGapMax, (totalHeight * barGapMultiplier) / spaces)); + const heightValueRatio = (totalHeight - spaceHeight * spaces) / totalData; + + if (heightValueRatio < 0) { + return undefined; + } + const singleBar = nonZeroBars.map((point: IVSChartDataPoint, index: number) => { - startingPointOfY = startingPointOfY + point.data; const color = point.color ? point.color : this._colors[index]; const ref: IRefArrayData = {}; @@ -387,7 +396,7 @@ export class VerticalStackedBarChartBase extends React.Component< if (this.state.isLegendHovered || this.state.isLegendSelected) { shouldHighlight = this.state.selectedLegendTitle === point.legend; } - this._classNames = getClassNames(this.props.styles!, { + const classNames = getClassNames(this.props.styles!, { theme: this.props.theme!, shouldHighlight: shouldHighlight, href: this.props.href, @@ -402,14 +411,40 @@ export class VerticalStackedBarChartBase extends React.Component< onBlur: this._handleMouseOut, onClick: this._redirectToUrl, }; + + const barHeight = heightValueRatio * point.data; + yPoint = yPoint - barHeight - (index ? spaceHeight : 0); + + // If set, apply the corner radius to the top of the final bar + if (barCornerRadius && barHeight > barCornerRadius && index === nonZeroBars.length - 1) { + return ( + (ref.refElement = e)} + {...rectFocusProps} + /> + ); + } + return ( (ref.refElement = e)} {...rectFocusProps} @@ -427,18 +462,13 @@ export class VerticalStackedBarChartBase extends React.Component< onClick: this._redirectToUrl, }; return ( - (groupRef.refElement = e)} - {...stackFocusProps} - > + (groupRef.refElement = e)} {...stackFocusProps}> {singleBar} ); }); - return bars; + + return bars.filter((bar): bar is JSX.Element => !!bar); }; private _createNumericBars = (containerHeight: number, containerWidth: number): JSX.Element[] => { diff --git a/packages/charting/src/components/VerticalStackedBarChart/VerticalStackedBarChart.types.ts b/packages/charting/src/components/VerticalStackedBarChart/VerticalStackedBarChart.types.ts index ec9bab9602c50..a43650ee38881 100644 --- a/packages/charting/src/components/VerticalStackedBarChart/VerticalStackedBarChart.types.ts +++ b/packages/charting/src/components/VerticalStackedBarChart/VerticalStackedBarChart.types.ts @@ -20,6 +20,18 @@ export interface IVerticalStackedBarChartProps extends ICartesianChartProps { */ barWidth?: number; + /** + * Gap (max) between bars in a stack. + * @default 0 + */ + barGapMax?: number; + + /** + * Corner radius of the bars + * @default 0 + */ + barCornerRadius?: number; + /** * Colors from which to select the color of each bar. * @deprecated Not using this prop. DIrectly taking color from given data. diff --git a/packages/react-examples/src/charting/VerticalStackedBarChart/VerticalStackedBarChart.Styled.Example.tsx b/packages/react-examples/src/charting/VerticalStackedBarChart/VerticalStackedBarChart.Styled.Example.tsx index 9ae6917584fd8..d8a3b67381364 100644 --- a/packages/react-examples/src/charting/VerticalStackedBarChart/VerticalStackedBarChart.Styled.Example.tsx +++ b/packages/react-examples/src/charting/VerticalStackedBarChart/VerticalStackedBarChart.Styled.Example.tsx @@ -12,6 +12,8 @@ import { DirectionalHint } from 'office-ui-fabric-react'; interface IVerticalStackedBarState { width: number; height: number; + barGapMax: number; + barCornerRadius: number; } export class VerticalStackedBarChartStyledExample extends React.Component<{}, IVerticalStackedBarState> { @@ -20,19 +22,14 @@ export class VerticalStackedBarChartStyledExample extends React.Component<{}, IV this.state = { width: 650, height: 350, + barGapMax: 2, + barCornerRadius: 2, }; } public render(): JSX.Element { return
{this._basicExample()}
; } - private _onWidthChange = (e: React.ChangeEvent) => { - this.setState({ width: parseInt(e.target.value, 10) }); - }; - private _onHeightChange = (e: React.ChangeEvent) => { - this.setState({ height: parseInt(e.target.value, 10) }); - }; - private _basicExample(): JSX.Element { const firstChartPoints: IVSChartDataPoint[] = [ { legend: 'Metadata1', data: 40, color: DefaultPalette.accent }, @@ -94,15 +91,50 @@ export class VerticalStackedBarChartStyledExample extends React.Component<{}, IV return ( <> - - - - +
+ + {/* eslint-disable-next-line react/jsx-no-bind */} + this.setState({ width: +e.target.value })} + /> + + {/* eslint-disable-next-line react/jsx-no-bind */} + this.setState({ height: +e.target.value })} + /> +
+
+ + {/* eslint-disable-next-line react/jsx-no-bind */} + this.setState({ barGapMax: +e.target.value })} + /> + + {/* eslint-disable-next-line react/jsx-no-bind */} + this.setState({ barCornerRadius: +e.target.value })} + /> +
`${x} h`} margins={{ - bottom: 0, + bottom: 1, top: 0, left: 0, right: 0, From 2eed74ccf039ad107147609b32d519ecd476dc4e Mon Sep 17 00:00:00 2001 From: Michael Best Date: Tue, 20 Oct 2020 00:39:22 -0700 Subject: [PATCH 2/5] update example --- .../VerticalStackedBarChart.Styled.Example.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/react-examples/src/charting/VerticalStackedBarChart/VerticalStackedBarChart.Styled.Example.tsx b/packages/react-examples/src/charting/VerticalStackedBarChart/VerticalStackedBarChart.Styled.Example.tsx index d8a3b67381364..5fa7e40235358 100644 --- a/packages/react-examples/src/charting/VerticalStackedBarChart/VerticalStackedBarChart.Styled.Example.tsx +++ b/packages/react-examples/src/charting/VerticalStackedBarChart/VerticalStackedBarChart.Styled.Example.tsx @@ -32,8 +32,8 @@ export class VerticalStackedBarChartStyledExample extends React.Component<{}, IV private _basicExample(): JSX.Element { const firstChartPoints: IVSChartDataPoint[] = [ - { legend: 'Metadata1', data: 40, color: DefaultPalette.accent }, - { legend: 'Metadata2', data: 5, color: DefaultPalette.blueMid }, + { legend: 'Metadata1', data: 2, color: DefaultPalette.accent }, + { legend: 'Metadata2', data: 1, color: DefaultPalette.blueMid }, { legend: 'Metadata3', data: 0, color: DefaultPalette.blueLight }, ]; @@ -73,7 +73,7 @@ export class VerticalStackedBarChartStyledExample extends React.Component<{}, IV return { xAxis: { selectors: { - text: { fill: 'black', fontSize: '8px' }, + text: { fill: 'black', fontSize: '10px' }, }, }, chart: { @@ -148,9 +148,9 @@ export class VerticalStackedBarChartStyledExample extends React.Component<{}, IV // eslint-disable-next-line react/jsx-no-bind yAxisTickFormat={(x: number | string) => `${x} h`} margins={{ - bottom: 1, - top: 0, - left: 0, + bottom: 35, + top: 10, + left: 35, right: 0, }} legendProps={{ From c79b6ee423b587ec0f08400944dc0f694fae0835 Mon Sep 17 00:00:00 2001 From: Michael Best Date: Tue, 20 Oct 2020 16:15:00 -0700 Subject: [PATCH 3/5] add comments and update example --- .../VerticalStackedBarChart.base.tsx | 4 ++-- .../VerticalStackedBarChart.types.ts | 5 ++++- .../VerticalStackedBarChart.Styled.Example.tsx | 16 +++++++++++++++- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/packages/charting/src/components/VerticalStackedBarChart/VerticalStackedBarChart.base.tsx b/packages/charting/src/components/VerticalStackedBarChart/VerticalStackedBarChart.base.tsx index 105c047367b24..0b9cac9fc68ad 100644 --- a/packages/charting/src/components/VerticalStackedBarChart/VerticalStackedBarChart.base.tsx +++ b/packages/charting/src/components/VerticalStackedBarChart/VerticalStackedBarChart.base.tsx @@ -30,7 +30,7 @@ const COMPONENT_NAME = 'VERTICAL STACKED BAR CHART'; // When displaying gaps between bars, the max height of the gap is given in the // props. The actual gap is calculated with this multiplier, with a minimum gap -// of 1 pixel. +// of 1 pixel. (If these values are changed, update the comment for barGapMax.) const barGapMultiplier = 0.2; const barGapMin = 1; @@ -377,7 +377,7 @@ export class VerticalStackedBarChartBase extends React.Component< const nonZeroBars = singleChartData.chartData.filter(point => point.data > 0); // When displaying gaps between the bars, the height of each bar is - // adjusted so that the total of all bars remains correct + // adjusted so that the total of all bars is not changed by the gaps const totalData = nonZeroBars.reduce((iter, value) => iter + value.data, 0); const totalHeight = yBarScale(totalData); const spaces = barGapMax && nonZeroBars.length - 1; diff --git a/packages/charting/src/components/VerticalStackedBarChart/VerticalStackedBarChart.types.ts b/packages/charting/src/components/VerticalStackedBarChart/VerticalStackedBarChart.types.ts index a43650ee38881..46c57df245cd7 100644 --- a/packages/charting/src/components/VerticalStackedBarChart/VerticalStackedBarChart.types.ts +++ b/packages/charting/src/components/VerticalStackedBarChart/VerticalStackedBarChart.types.ts @@ -21,7 +21,10 @@ export interface IVerticalStackedBarChartProps extends ICartesianChartProps { barWidth?: number; /** - * Gap (max) between bars in a stack. + * Gap (max) between bars in a stack. When non-zero, the bars in a stack will + * be separated by gaps. The actual size of each gap is calculated as 20% of + * the height of that stack, with a minimum size of 1px and a maximum given by + * this prop. * @default 0 */ barGapMax?: number; diff --git a/packages/react-examples/src/charting/VerticalStackedBarChart/VerticalStackedBarChart.Styled.Example.tsx b/packages/react-examples/src/charting/VerticalStackedBarChart/VerticalStackedBarChart.Styled.Example.tsx index 5fa7e40235358..612809e518e70 100644 --- a/packages/react-examples/src/charting/VerticalStackedBarChart/VerticalStackedBarChart.Styled.Example.tsx +++ b/packages/react-examples/src/charting/VerticalStackedBarChart/VerticalStackedBarChart.Styled.Example.tsx @@ -7,13 +7,19 @@ import { IVerticalStackedBarChartProps, } from '@uifabric/charting'; import { DefaultPalette, IStyle, DefaultFontStyles } from 'office-ui-fabric-react/lib/Styling'; -import { DirectionalHint } from 'office-ui-fabric-react'; +import { ChoiceGroup, DirectionalHint, IChoiceGroupOption } from 'office-ui-fabric-react'; + +const options: IChoiceGroupOption[] = [ + { key: 'singleCallout', text: 'Single callout' }, + { key: 'MultiCallout', text: 'Stack callout' }, +]; interface IVerticalStackedBarState { width: number; height: number; barGapMax: number; barCornerRadius: number; + selectedCallout: string; } export class VerticalStackedBarChartStyledExample extends React.Component<{}, IVerticalStackedBarState> { @@ -24,6 +30,7 @@ export class VerticalStackedBarChartStyledExample extends React.Component<{}, IV height: 350, barGapMax: 2, barCornerRadius: 2, + selectedCallout: 'MultiCallout', }; } public render(): JSX.Element { @@ -130,6 +137,12 @@ export class VerticalStackedBarChartStyledExample extends React.Component<{}, IV max={10} onChange={e => this.setState({ barCornerRadius: +e.target.value })} /> + this.setState({ selectedCallout: option.key })} + label="Pick one" + />
Date: Tue, 20 Oct 2020 17:40:29 -0700 Subject: [PATCH 4/5] fix build error --- .../VerticalStackedBarChart.Styled.Example.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-examples/src/charting/VerticalStackedBarChart/VerticalStackedBarChart.Styled.Example.tsx b/packages/react-examples/src/charting/VerticalStackedBarChart/VerticalStackedBarChart.Styled.Example.tsx index 612809e518e70..591a2fbc5be61 100644 --- a/packages/react-examples/src/charting/VerticalStackedBarChart/VerticalStackedBarChart.Styled.Example.tsx +++ b/packages/react-examples/src/charting/VerticalStackedBarChart/VerticalStackedBarChart.Styled.Example.tsx @@ -140,7 +140,7 @@ export class VerticalStackedBarChartStyledExample extends React.Component<{}, IV this.setState({ selectedCallout: option.key })} + onChange={(_ev, option) => option && this.setState({ selectedCallout: option.key })} label="Pick one" />
From 6d685e5939285b852914808d7791bf4589434816 Mon Sep 17 00:00:00 2001 From: Michael Best Date: Wed, 21 Oct 2020 00:30:39 -0700 Subject: [PATCH 5/5] fix lint errors and warnings --- .../VerticalStackedBarChart.Styled.Example.tsx | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/packages/react-examples/src/charting/VerticalStackedBarChart/VerticalStackedBarChart.Styled.Example.tsx b/packages/react-examples/src/charting/VerticalStackedBarChart/VerticalStackedBarChart.Styled.Example.tsx index 591a2fbc5be61..beead3976375f 100644 --- a/packages/react-examples/src/charting/VerticalStackedBarChart/VerticalStackedBarChart.Styled.Example.tsx +++ b/packages/react-examples/src/charting/VerticalStackedBarChart/VerticalStackedBarChart.Styled.Example.tsx @@ -100,7 +100,6 @@ export class VerticalStackedBarChartStyledExample extends React.Component<{}, IV <>
- {/* eslint-disable-next-line react/jsx-no-bind */} this.setState({ width: +e.target.value })} /> - {/* eslint-disable-next-line react/jsx-no-bind */}
- {/* eslint-disable-next-line react/jsx-no-bind */} this.setState({ barGapMax: +e.target.value })} /> - {/* eslint-disable-next-line react/jsx-no-bind */} option && this.setState({ selectedCallout: option.key })} label="Pick one" /> @@ -175,8 +172,8 @@ export class VerticalStackedBarChartStyledExample extends React.Component<{}, IV }, }, }} - // eslint-disable-next-line react/jsx-no-bind, @typescript-eslint/no-explicit-any - onRenderCalloutPerDataPoint={(props: any) => + // eslint-disable-next-line react/jsx-no-bind + onRenderCalloutPerDataPoint={props => props ? (