diff --git a/change/@fluentui-react-examples-2020-10-16-19-34-37-user-v-sivsar-addingLinesInVerticalStackedbarr.json b/change/@fluentui-react-examples-2020-10-16-19-34-37-user-v-sivsar-addingLinesInVerticalStackedbarr.json new file mode 100644 index 00000000000000..acb04bf3ca59b2 --- /dev/null +++ b/change/@fluentui-react-examples-2020-10-16-19-34-37-user-v-sivsar-addingLinesInVerticalStackedbarr.json @@ -0,0 +1,8 @@ +{ + "type": "patch", + "comment": "modified the example code as per the changes", + "packageName": "@fluentui/react-examples", + "email": "v-sivsar@microsoft.com", + "dependentChangeType": "patch", + "date": "2020-10-16T14:04:37.924Z" +} diff --git a/change/@uifabric-charting-2020-10-16-19-34-37-user-v-sivsar-addingLinesInVerticalStackedbarr.json b/change/@uifabric-charting-2020-10-16-19-34-37-user-v-sivsar-addingLinesInVerticalStackedbarr.json new file mode 100644 index 00000000000000..e08923047dca4e --- /dev/null +++ b/change/@uifabric-charting-2020-10-16-19-34-37-user-v-sivsar-addingLinesInVerticalStackedbarr.json @@ -0,0 +1,8 @@ +{ + "type": "minor", + "comment": "added the new feature, which is complex combo chart, where we give ability to the user to draw lines in the vertical stacked bar chart", + "packageName": "@uifabric/charting", + "email": "v-sivsar@microsoft.com", + "dependentChangeType": "patch", + "date": "2020-10-16T14:04:22.573Z" +} diff --git a/packages/charting/src/components/CommonComponents/CartesianChart.base.tsx b/packages/charting/src/components/CommonComponents/CartesianChart.base.tsx index c4b308539b0aa5..e3acb085ca7f7b 100644 --- a/packages/charting/src/components/CommonComponents/CartesianChart.base.tsx +++ b/packages/charting/src/components/CommonComponents/CartesianChart.base.tsx @@ -297,10 +297,26 @@ export class CartesianChartBase extends React.Component { const isLast: boolean = index + 1 === yValues.length; + const { shouldDrawBorderBottom = false } = yValue; return (
{this._getCalloutContent(yValue, index, yValueHoverSubCountsExists, isLast)}
diff --git a/packages/charting/src/components/CommonComponents/CartesianChart.types.ts b/packages/charting/src/components/CommonComponents/CartesianChart.types.ts index e8a86afc0dc48a..05cfa26573469a 100644 --- a/packages/charting/src/components/CommonComponents/CartesianChart.types.ts +++ b/packages/charting/src/components/CommonComponents/CartesianChart.types.ts @@ -307,6 +307,7 @@ export interface IYValueHover { y?: number; color?: string; data?: string | number; + shouldDrawBorderBottom?: boolean; yAxisCalloutData?: string | { [id: string]: number }; } diff --git a/packages/charting/src/components/VerticalStackedBarChart/VerticalStackedBarChart.base.tsx b/packages/charting/src/components/VerticalStackedBarChart/VerticalStackedBarChart.base.tsx index 0b9cac9fc68ad4..1f05e4ea2b4e68 100644 --- a/packages/charting/src/components/VerticalStackedBarChart/VerticalStackedBarChart.base.tsx +++ b/packages/charting/src/components/VerticalStackedBarChart/VerticalStackedBarChart.base.tsx @@ -2,7 +2,14 @@ import * as React from 'react'; 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 { + classNamesFunction, + getId, + getRTL, + find, + warnDeprecations, + memoizeFunction, +} from 'office-ui-fabric-react/lib/Utilities'; import { IPalette } from 'office-ui-fabric-react/lib/Styling'; import { DirectionalHint } from 'office-ui-fabric-react/lib/Callout'; import { ILegend, Legends } from '../Legends/index'; @@ -18,9 +25,10 @@ import { IVerticalStackedBarChartStyles, IVerticalStackedChartProps, IVSChartDataPoint, + ILineDataInVerticalStackedBarChart, } from '../../index'; import { FocusZoneDirection } from '@fluentui/react-focus'; -import { ChartTypes, XAxisTypes } from '../../utilities/index'; +import { ChartTypes, XAxisTypes, getTypeOfAxis } from '../../utilities/index'; const getClassNames = classNamesFunction(); type NumericAxis = D3Axis; @@ -37,11 +45,23 @@ const barGapMin = 1; interface IRefArrayData { refElement?: SVGGElement | null; } +type LineData = ILineDataInVerticalStackedBarChart & { x?: number | string; index?: number }; +type LinePoint = ILineDataInVerticalStackedBarChart & { index: number; x: number | string | undefined }; +type LineObject = { [key: string]: LinePoint[] }; +type LineLegends = { + title: string; + color: string; +}; +enum CircleVisbility { + show = 'visibility', + hide = 'hidden', +} export interface IVerticalStackedBarChartState extends IBasestate { selectedLegendTitle: string; dataPointCalloutProps?: IVSChartDataPoint; stackCalloutProps?: IVerticalStackedChartProps; + activeXAxisDataPoint: number | string; } export class VerticalStackedBarChartBase extends React.Component< IVerticalStackedBarChartProps, @@ -51,12 +71,15 @@ export class VerticalStackedBarChartBase extends React.Component< private _dataset: IDataPoint[]; private _xAxisLabels: string[]; private _bars: JSX.Element[]; - private _isNumeric: boolean; + private _xAxisType: XAxisTypes; private _barWidth: number; + private _additionalSpace: number; private _calloutId: string; private _colors: string[]; private margins: IMargins; private _isRtl: boolean = getRTL(); + private _createLegendsForLine: (data: IVerticalStackedChartProps[]) => LineLegends[]; + private _lineObject: LineObject; public constructor(props: IVerticalStackedBarChartProps) { super(props); @@ -72,6 +95,7 @@ export class VerticalStackedBarChartBase extends React.Component< YValueHover: [], xCalloutValue: '', yCalloutValue: '', + activeXAxisDataPoint: '', }; warnDeprecations(COMPONENT_NAME, props, { colors: 'IVSChartDataPoint.color', @@ -82,6 +106,7 @@ export class VerticalStackedBarChartBase extends React.Component< this._calloutId = getId('callout'); this._adjustProps(); this._dataset = this._createDataSetLayer(); + this._createLegendsForLine = memoizeFunction((data: IVerticalStackedChartProps[]) => this._getLineLegends(data)); } public componentDidUpdate(prevProps: IVerticalStackedBarChartProps): void { @@ -97,9 +122,17 @@ export class VerticalStackedBarChartBase extends React.Component< public render(): React.ReactNode { this._adjustProps(); + const _isHavingLines = this.props.data.some( + (item: IVerticalStackedChartProps) => item.lineData && item.lineData.length > 0, + ); + const shouldFocusWholeStack = this._toFocusWholeStack(_isHavingLines); + const { isCalloutForStack = false } = this.props; this._dataset = this._createDataSetLayer(); - this._isNumeric = this._dataset.length > 0 && typeof this._dataset[0].x === 'number'; - const legendBars: JSX.Element = this._getLegendData(this._points, this.props.theme!.palette); + const legendBars: JSX.Element = this._getLegendData( + this._points, + this.props.theme!.palette, + this._createLegendsForLine(this.props.data), + ); const calloutProps = { isCalloutVisible: this.state.isCalloutVisible, @@ -109,7 +142,7 @@ export class VerticalStackedBarChartBase extends React.Component< isBeakVisible: false, gapSpace: 15, color: this.state.color, - Legend: this.state.selectedLegendTitle, + legend: this.state.selectedLegendTitle, XValue: this.state.xCalloutValue!, YValue: this.state.yCalloutValue ? this.state.yCalloutValue : this.state.dataForHoverCard, YValueHover: this.state.YValueHover, @@ -126,33 +159,210 @@ export class VerticalStackedBarChartBase extends React.Component< {...this.props} points={this._dataset} chartType={ChartTypes.VerticalStackedBarChart} - xAxisType={this._isNumeric ? XAxisTypes.NumericAxis : XAxisTypes.StringAxis} + xAxisType={this._xAxisType} calloutProps={calloutProps} tickParams={tickParams} legendBars={legendBars} datasetForXAxisDomain={this._xAxisLabels} - isCalloutForStack={this.props.isCalloutForStack!} + isCalloutForStack={shouldFocusWholeStack} barwidth={this._barWidth} - focusZoneDirection={this.props.isCalloutForStack ? FocusZoneDirection.horizontal : FocusZoneDirection.vertical} + focusZoneDirection={ + isCalloutForStack || _isHavingLines ? FocusZoneDirection.horizontal : FocusZoneDirection.vertical + } getmargins={this._getMargins} getGraphData={this._getGraphData} customizedCallout={this._getCustomizedCallout()} /* eslint-disable react/jsx-no-bind */ // eslint-disable-next-line react/no-children-prop children={(props: IChildProps) => { - return {this._bars}; + return ( + <> + {this._bars} + + {_isHavingLines && + this._createLines(props.xScale!, props.yScale!, props.containerHeight!, props.containerWidth!)} + + + ); }} /> ); } + /** + * This function tells us what to foucs either the whole stack as focusable item. + * or each individual item in the stack as focusable item. basically it depends + * on the prop `isCalloutForStack` if it's false user can focus each individual bar + * within the bar if it's true then user can focus whole bar as item. + * but if we have lines in the chart then we force the user to focus only the whole + * bar, even if isCalloutForStack is false + */ + private _toFocusWholeStack = (_isHavingLines: boolean): boolean => { + const { isCalloutForStack = false } = this.props; + let shouldFocusStackOnly: boolean = false; + if (_isHavingLines) { + if (this.state.isLegendSelected) { + shouldFocusStackOnly = false; + } else { + shouldFocusStackOnly = true; + } + } else { + shouldFocusStackOnly = isCalloutForStack; + } + return shouldFocusStackOnly; + }; + + private _getFormattedLineData = (data: IVerticalStackedChartProps[]): LineObject => { + const linesData: LineData[] = []; + const formattedLineData: LineObject = {}; + data.forEach((item: IVerticalStackedChartProps, index: number) => { + if (item.lineData) { + linesData.push(...item.lineData); + // injecting corresponding x data point in each of the line data + // we inject index also , it will be helpful to draw lines when x asix is + // of string type + linesData.forEach((line: LineData) => { + if (line.x === undefined) { + line.x = item.xAxisPoint; + } + if (line.index === undefined) { + line.index = index; + } + }); + } + }); + linesData.forEach((item: LineData) => { + if (formattedLineData[item.legend]) { + formattedLineData[item.legend].push({ ...item, index: item.index!, x: item.x }); + } else { + formattedLineData[item.legend] = [{ ...item, index: item.index!, x: item.x }]; + } + }); + return formattedLineData; + }; + + private _getLineLegends = (data: IVerticalStackedChartProps[]): LineLegends[] => { + const lineObject: LineObject = this._lineObject; + const lineLegends: LineLegends[] = []; + Object.keys(lineObject).forEach((item: string) => { + lineLegends.push({ + title: item, + color: lineObject[item][0].color, + }); + }); + return lineLegends; + }; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + private _createLines = (xScale: any, yScale: any, containerHeight: number, containerWidth: number): JSX.Element => { + const { isLegendHovered, isLegendSelected, selectedLegendTitle } = this.state; + const isNumeric = this._xAxisType === XAxisTypes.NumericAxis; + const { xBarScale } = this._getScales(containerHeight, containerWidth, isNumeric); + const lineObject: LineObject = this._getFormattedLineData(this.props.data); + const lines: React.ReactNode[] = []; + const dots: React.ReactNode[] = []; + Object.keys(lineObject).forEach((item: string, index: number) => { + let shouldHighlight = true; + if (isLegendHovered || isLegendSelected) { + shouldHighlight = selectedLegendTitle === item; // item is legend name; + } + for (let i = 1; i < lineObject[item].length; i++) { + const x1 = isNumeric + ? xScale(lineObject[item][i - 1].x) + : xBarScale(lineObject[item][i - 1].index) + this._additionalSpace; + const y1 = yScale(lineObject[item][i - 1].y); + const x2 = isNumeric + ? xScale(lineObject[item][i].x) + : xBarScale(lineObject[item][i].index) + this._additionalSpace; + const y2 = yScale(lineObject[item][i].y); + lines.push( + , + ); + } + }); + Object.keys(lineObject).forEach((item: string, index: number) => { + lineObject[item].forEach((circlePoint: LinePoint, subIndex: number) => { + dots.push( + point.xAxisPoint === circlePoint.x) + ?.lineData, + ) + } + {...(isLegendSelected && + selectedLegendTitle === item && { + onMouseLeave: this._lineHoverOut, + })} + r={this._getCircleVisibilityAndRadius(circlePoint.x!, circlePoint.legend).radius} + stroke={circlePoint.color} + fill={this.props.theme!.palette.white} + strokeWidth={3} + visibility={this._getCircleVisibilityAndRadius(circlePoint.x!, circlePoint.legend).visibility} + />, + ); + }); + }); + return ( + <> + {lines} + {dots} + + ); + }; + + private _getCircleVisibilityAndRadius = ( + xAxispoint: string | number, + legend: string, + ): { visibility: CircleVisbility; radius: number } => { + const { isLegendSelected, activeXAxisDataPoint, selectedLegendTitle } = this.state; + if (isLegendSelected) { + if (xAxispoint === activeXAxisDataPoint && legend === selectedLegendTitle) { + return { visibility: CircleVisbility.show, radius: 8 }; + } else if (legend === selectedLegendTitle) { + return { visibility: CircleVisbility.show, radius: 0.3 }; + } else { + return { visibility: CircleVisbility.hide, radius: 0 }; + } + } else { + return { + visibility: activeXAxisDataPoint === xAxispoint ? CircleVisbility.show : CircleVisbility.hide, + radius: 8, + }; + } + }; + private _adjustProps(): void { this._points = this.props.data || []; this._barWidth = this.props.barWidth || 32; + this._additionalSpace = 0.5 * this._barWidth; const { theme } = this.props; const { palette } = theme!; // eslint-disable-next-line deprecation/deprecation this._colors = this.props.colors || [palette.blueLight, palette.blue, palette.blueMid, palette.red, palette.black]; + this._xAxisType = getTypeOfAxis(this.props.data[0].xAxisPoint, true) as XAxisTypes; + this._lineObject = this._getFormattedLineData(this.props.data); } private _createDataSetLayer(): IDataPoint[] { @@ -162,7 +372,7 @@ export class VerticalStackedBarChartBase extends React.Component< singlePointData.chartData!.forEach((point: IVSChartDataPoint) => { total = total + point.data; }); - !this._isNumeric && tempArr.push(singlePointData.xAxisPoint as string); + tempArr.push(singlePointData.xAxisPoint as string); return { x: singlePointData.xAxisPoint, y: total, @@ -188,9 +398,12 @@ export class VerticalStackedBarChartBase extends React.Component< } private _getCustomizedCallout = () => { + const _isHavingLines = this.props.data.some( + (item: IVerticalStackedChartProps) => item.lineData && item.lineData.length > 0, + ); return this.props.onRenderCalloutPerStack ? this.props.onRenderCalloutPerStack(this.state.stackCalloutProps) - : this.props.onRenderCalloutPerDataPoint + : this.props.onRenderCalloutPerDataPoint && !_isHavingLines ? this.props.onRenderCalloutPerDataPoint(this.state.dataPointCalloutProps, this._renderCallout) : null; }; @@ -234,7 +447,11 @@ export class VerticalStackedBarChartBase extends React.Component< } } - private _getLegendData(data: IVerticalStackedChartProps[], palette: IPalette): JSX.Element { + private _getLegendData( + data: IVerticalStackedChartProps[], + palette: IPalette, + lineLegends: LineLegends[], + ): JSX.Element { const defaultPalette: string[] = [palette.blueLight, palette.blue, palette.blueMid, palette.red, palette.black]; const actions: ILegend[] = []; @@ -263,9 +480,30 @@ export class VerticalStackedBarChartBase extends React.Component< actions.push(legend); }); }); + const legendsOfLine: ILegend[] = []; + if (lineLegends && lineLegends.length > 0) { + lineLegends.forEach((point: LineLegends) => { + const legend: ILegend = { + title: point.title, + color: point.color, + isLineLegendInBarChart: true, + action: () => { + this._onLegendClick(point.title); + }, + hoverAction: () => { + this._onLegendHover(point.title); + }, + onMouseOutAction: (isLegendSelected?: boolean) => { + this._onLegendLeave(isLegendSelected); + }, + }; + legendsOfLine.push(legend); + }); + } + const totalLegends: ILegend[] = legendsOfLine.concat(actions); return ( ): void => { + private _linehover = (lineData: LinePoint, mouseEvent: React.MouseEvent) => { + mouseEvent.persist(); + this.setState({ + refSelected: mouseEvent, + isCalloutVisible: true, + xCalloutValue: `${lineData.x}`, + yCalloutValue: `${lineData.yAxisCalloutData || lineData.data || lineData.y}`, + activeXAxisDataPoint: lineData.x!, + color: lineData.color, + }); + }; + + private _lineHoverOut = () => { + this.setState({ + refSelected: null, + isCalloutVisible: false, + xCalloutValue: '', + yCalloutValue: '', + activeXAxisDataPoint: '', + color: '', + }); + }; + + private _onStackHover = ( + xAxisPoint: string | number, + lineData: ILineDataInVerticalStackedBarChart[] | undefined, + mouseEvent: React.MouseEvent, + ): void => { mouseEvent.persist(); + const isLinesPresent: boolean = lineData !== undefined && lineData.length > 0; const found = find( this._points, (sinlgePoint: { xAxisPoint: string | number; chartData: IVSChartDataPoint[] }) => sinlgePoint.xAxisPoint === xAxisPoint, ); + if (isLinesPresent) { + lineData!.forEach((item: ILineDataInVerticalStackedBarChart & { shouldDrawBorderBottom?: boolean }) => { + item.data = item.data || item.y; + item.shouldDrawBorderBottom = true; + }); + } this.setState({ refSelected: mouseEvent, isCalloutVisible: true, - YValueHover: found!.chartData, + YValueHover: isLinesPresent + ? [...lineData!, ...found!.chartData.slice().reverse()] + : found!.chartData.slice().reverse(), hoverXValue: xAxisPoint, stackCalloutProps: found!, + activeXAxisDataPoint: xAxisPoint, }); }; @@ -332,24 +607,39 @@ export class VerticalStackedBarChartBase extends React.Component< } } - private _onStackFocus = (xAxisPoint: string | number, groupRef: IRefArrayData): void => { + private _onStackFocus = ( + xAxisPoint: string | number, + groupRef: IRefArrayData, + lineData: ILineDataInVerticalStackedBarChart[] | undefined, + ): void => { + const isLinesPresent: boolean = lineData !== undefined && lineData.length > 0; const found = find( this._points, (sinlgePoint: { xAxisPoint: string | number; chartData: IVSChartDataPoint[] }) => sinlgePoint.xAxisPoint === xAxisPoint, ); + if (isLinesPresent) { + lineData!.forEach((item: ILineDataInVerticalStackedBarChart & { shouldDrawBorderBottom?: boolean }) => { + item.data = item.data || item.y; + item.shouldDrawBorderBottom = true; + }); + } this.setState({ refSelected: groupRef.refElement, isCalloutVisible: true, - YValueHover: found!.chartData, + YValueHover: isLinesPresent + ? [...lineData!, ...found!.chartData.slice().reverse()] + : found!.chartData.slice().reverse(), hoverXValue: xAxisPoint, stackCalloutProps: found!, + activeXAxisDataPoint: xAxisPoint, }); }; private _handleMouseOut = (): void => { this.setState({ isCalloutVisible: false, + activeXAxisDataPoint: '', }); }; @@ -357,9 +647,9 @@ export class VerticalStackedBarChartBase extends React.Component< this.props.href ? (window.location.href = this.props.href) : ''; }; - private _getYMax(dataset: IDataPoint[]) { + private _getYMax = (dataset: IDataPoint[]) => { return Math.max(d3Max(dataset, (point: IDataPoint) => point.y)!, this.props.yMaxValue || 0); - } + }; private _createBar = ( xBarScale: NumericScale | StringScale, @@ -367,11 +657,16 @@ export class VerticalStackedBarChartBase extends React.Component< containerHeight: number, ): JSX.Element[] => { const { barGapMax = 0, barCornerRadius = 0 } = this.props; + const _isHavingLines = this.props.data.some( + (item: IVerticalStackedChartProps) => item.lineData && item.lineData.length > 0, + ); + const shouldFocusWholeStack = this._toFocusWholeStack(_isHavingLines); const bars = this._points.map((singleChartData: IVerticalStackedChartProps, indexNumber: number) => { let yPoint = containerHeight - this.margins.bottom!; - const isCalloutForStack = this.props.isCalloutForStack || false; - const xPoint = xBarScale(this._isNumeric ? (singleChartData.xAxisPoint as number) : indexNumber); + const xPoint = xBarScale( + this._xAxisType === XAxisTypes.NumericAxis ? (singleChartData.xAxisPoint as number) : indexNumber, + ); // Removing data points with zero data const nonZeroBars = singleChartData.chartData.filter(point => point.data > 0); @@ -401,7 +696,7 @@ export class VerticalStackedBarChartBase extends React.Component< shouldHighlight: shouldHighlight, href: this.props.href, }); - const rectFocusProps = !isCalloutForStack && { + const rectFocusProps = !shouldFocusWholeStack && { 'data-is-focusable': true, 'aria-labelledby': this._calloutId, onMouseOver: this._onRectHover.bind(this, singleChartData.xAxisPoint, point), @@ -439,7 +734,7 @@ export class VerticalStackedBarChartBase extends React.Component< return ( (groupRef.refElement = e)} {...stackFocusProps}> + (groupRef.refElement = e)} + {...stackFocusProps} + > {singleBar} ); @@ -471,41 +771,54 @@ export class VerticalStackedBarChartBase extends React.Component< return bars.filter((bar): bar is JSX.Element => !!bar); }; - private _createNumericBars = (containerHeight: number, containerWidth: number): JSX.Element[] => { + private _getScales = ( + containerHeight: number, + containerWidth: number, + isNumeric: boolean, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ): { xBarScale: any; yBarScale: any } => { const yMax = this._getYMax(this._dataset); - const xMax = d3Max(this._dataset, (point: IDataPoint) => point.x as number)!; - - const xBarScale = d3ScaleLinear() - .domain(this._isRtl ? [xMax, 0] : [0, xMax]) - .nice() - .range([this.margins.left!, containerWidth - this.margins.right! - this._barWidth]); const yBarScale = d3ScaleLinear() .domain([0, yMax]) .range([0, containerHeight - this.margins.bottom! - this.margins.top!]); + if (isNumeric) { + const xMax = d3Max(this._dataset, (point: IDataPoint) => point.x as number)!; + + const xBarScale = d3ScaleLinear() + .domain(this._isRtl ? [xMax, 0] : [0, xMax]) + .nice() + .range([this.margins.left!, containerWidth - this.margins.right! - this._barWidth]); + + return { xBarScale, yBarScale }; + } else { + const endpointDistance = 0.5 * ((containerWidth - this.margins.right!) / this._dataset.length); + const xBarScale = d3ScaleLinear() + .domain(this._isRtl ? [this._dataset.length - 1, 0] : [0, this._dataset.length - 1]) + .range([ + this.margins.left! + endpointDistance - this._additionalSpace, + containerWidth - this.margins.right! - endpointDistance - this._additionalSpace, + ]); + return { xBarScale, yBarScale }; + } + }; + + private _createNumericBars = (containerHeight: number, containerWidth: number): JSX.Element[] => { + const { xBarScale, yBarScale } = this._getScales(containerHeight, containerWidth, true); return this._createBar(xBarScale, yBarScale, containerHeight); }; private _createStringBars = (containerHeight: number, containerWidth: number): JSX.Element[] => { - const yMax = this._getYMax(this._dataset); - const endpointDistance = 0.5 * ((containerWidth - this.margins.right!) / this._dataset.length); - const xBarScale = d3ScaleLinear() - .domain(this._isRtl ? [this._dataset.length - 1, 0] : [0, this._dataset.length - 1]) - .range([ - this.margins.left! + endpointDistance - 0.5 * this._barWidth, - containerWidth - this.margins.right! - endpointDistance - 0.5 * this._barWidth, - ]); - const yBarScale = d3ScaleLinear() - .domain([0, yMax]) - .range([0, containerHeight - this.margins.bottom! - this.margins.top!]); + const { xBarScale, yBarScale } = this._getScales(containerHeight, containerWidth, false); return this._createBar(xBarScale, yBarScale, containerHeight); }; // eslint-disable-next-line @typescript-eslint/no-explicit-any private _getGraphData = (xScale: any, yScale: NumericAxis, containerHeight: number, containerWidth: number) => { - return (this._bars = this._isNumeric - ? this._createNumericBars(containerHeight, containerWidth) - : this._createStringBars(containerHeight, containerWidth)); + return (this._bars = + this._xAxisType === XAxisTypes.NumericAxis + ? this._createNumericBars(containerHeight, containerWidth) + : this._createStringBars(containerHeight, containerWidth)); }; } diff --git a/packages/charting/src/components/VerticalStackedBarChart/__snapshots__/VerticalStackedBarChart.test.tsx.snap b/packages/charting/src/components/VerticalStackedBarChart/__snapshots__/VerticalStackedBarChart.test.tsx.snap index f6eb2fc3062b29..345f5ffe7cd1f2 100644 --- a/packages/charting/src/components/VerticalStackedBarChart/__snapshots__/VerticalStackedBarChart.test.tsx.snap +++ b/packages/charting/src/components/VerticalStackedBarChart/__snapshots__/VerticalStackedBarChart.test.tsx.snap @@ -97,6 +97,7 @@ exports[`VerticalStackedBarChart snapShot testing renders VerticalStackedBarChar transform="translate(40, 0)" /> +
+
+
@@ -865,6 +868,7 @@ exports[`VerticalStackedBarChart snapShot testing renders hideTooltip correctly transform="translate(40, 0)" /> +
+
+
+
+
{ @@ -13,6 +16,8 @@ export class VerticalStackedBarChartBasicExample extends React.Component<{}, IVe this.state = { width: 650, height: 350, + showLine: true, + barGapMax: 2, }; } public render(): JSX.Element { @@ -26,7 +31,12 @@ export class VerticalStackedBarChartBasicExample extends React.Component<{}, IVe this.setState({ height: parseInt(e.target.value, 10) }); }; + private _onShowLineChange = (ev: React.FormEvent, checked: boolean): void => { + this.setState({ showLine: checked }); + }; + private _basicExample(): JSX.Element { + const { showLine } = this.state; const firstChartPoints: IVSChartDataPoint[] = [ { legend: 'Metadata1', @@ -124,12 +134,57 @@ export class VerticalStackedBarChartBasicExample extends React.Component<{}, IVe ]; const data: IVerticalStackedChartProps[] = [ - { chartData: firstChartPoints, xAxisPoint: 0 }, - { chartData: secondChartPoints, xAxisPoint: 20 }, - { chartData: thirdChartPoints, xAxisPoint: 40 }, - { chartData: firstChartPoints, xAxisPoint: 60 }, - { chartData: fourthChartPoints, xAxisPoint: 80 }, - { chartData: firstChartPoints, xAxisPoint: 100 }, + { + chartData: firstChartPoints, + xAxisPoint: 0, + ...(showLine && { + lineData: [ + { y: 42, legend: 'Supported Builds', color: DefaultPalette.magenta }, + { y: 10, legend: 'Recommended Builds', color: DefaultPalette.redDark }, + ], + }), + }, + { + chartData: secondChartPoints, + xAxisPoint: 20, + ...(showLine && { + lineData: [{ y: 33, legend: 'Supported Builds', color: DefaultPalette.magenta }], + }), + }, + { + chartData: thirdChartPoints, + xAxisPoint: 40, + ...(showLine && { + lineData: [ + { y: 60, legend: 'Supported Builds', color: DefaultPalette.magenta }, + { y: 20, legend: 'Recommended Builds', color: DefaultPalette.redDark }, + ], + }), + }, + { + chartData: firstChartPoints, + xAxisPoint: 60, + ...(showLine && { + lineData: [ + { y: 41, legend: 'Supported Builds', color: DefaultPalette.magenta }, + { y: 10, legend: 'Recommended Builds', color: DefaultPalette.redDark }, + ], + }), + }, + { + chartData: fourthChartPoints, + xAxisPoint: 80, + ...(showLine && { + lineData: [ + { y: 100, legend: 'Supported Builds', color: DefaultPalette.magenta }, + { y: 70, legend: 'Recommended Builds', color: DefaultPalette.redDark }, + ], + }), + }, + { + chartData: firstChartPoints, + xAxisPoint: 100, + }, ]; const rootStyle = { width: `${this.state.width}px`, height: `${this.state.height}px` }; @@ -140,8 +195,23 @@ export class VerticalStackedBarChartBasicExample extends React.Component<{}, IVe + + this.setState({ barGapMax: +e.target.value })} + /> +
{ @@ -22,6 +26,8 @@ export class VerticalStackedBarChartCalloutExample extends React.Component<{}, I this.state = { width: 650, height: 350, + barGapMax: 2, + showLine: true, selectedCallout: 'MultiCallout', }; } @@ -37,10 +43,15 @@ export class VerticalStackedBarChartCalloutExample extends React.Component<{}, I }; private _onChange = (ev: React.FormEvent, option: IChoiceGroupOption): void => { - this.setState({ selectedCallout: option.key }); + this.setState({ selectedCallout: option.key as IVerticalStackedBarState['selectedCallout'] }); + }; + + private _onShowLineChange = (ev: React.FormEvent, checked: boolean): void => { + this.setState({ showLine: checked }); }; private _basicExample(): JSX.Element { + const { showLine } = this.state; const firstChartPoints: IVSChartDataPoint[] = [ { legend: 'Metadata1', data: 40, color: DefaultPalette.accent }, { legend: 'Metadata2', data: 5, color: DefaultPalette.blueMid }, @@ -58,17 +69,120 @@ export class VerticalStackedBarChartCalloutExample extends React.Component<{}, I { legend: 'Metadata2', data: 60, color: DefaultPalette.blueMid }, { legend: 'Metadata3', data: 30, color: DefaultPalette.blueLight }, ]; + const fourthChartPoints: IVSChartDataPoint[] = [ + { legend: 'Metadata1', data: 40, color: DefaultPalette.accent }, + { legend: 'Metadata2', data: 10, color: DefaultPalette.blueMid }, + { legend: 'Metadata3', data: 30, color: DefaultPalette.blueLight }, + ]; + const fifthChartPoints: IVSChartDataPoint[] = [ + { legend: 'Metadata1', data: 40, color: DefaultPalette.accent }, + { legend: 'Metadata2', data: 40, color: DefaultPalette.blueMid }, + { legend: 'Metadata3', data: 40, color: DefaultPalette.blueLight }, + ]; + const sixthChartPoints: IVSChartDataPoint[] = [ + { legend: 'Metadata1', data: 40, color: DefaultPalette.accent }, + { legend: 'Metadata2', data: 20, color: DefaultPalette.blueMid }, + { legend: 'Metadata3', data: 40, color: DefaultPalette.blueLight }, + ]; + + const seventhChartPoints: IVSChartDataPoint[] = [ + { legend: 'Metadata1', data: 10, color: DefaultPalette.accent }, + { legend: 'Metadata2', data: 80, color: DefaultPalette.blueMid }, + { legend: 'Metadata3', data: 20, color: DefaultPalette.blueLight }, + ]; + const eightChartPoints: IVSChartDataPoint[] = [ + { legend: 'Metadata1', data: 50, color: DefaultPalette.accent }, + { legend: 'Metadata2', data: 50, color: DefaultPalette.blueMid }, + { legend: 'Metadata3', data: 20, color: DefaultPalette.blueLight }, + ]; const data: IVerticalStackedChartProps[] = [ - { chartData: firstChartPoints, xAxisPoint: 'Jan' }, - { chartData: secondChartPoints, xAxisPoint: 'Feb' }, - { chartData: thirdChartPoints, xAxisPoint: 'March' }, - { chartData: firstChartPoints, xAxisPoint: 'April' }, - { chartData: thirdChartPoints, xAxisPoint: 'May' }, - { chartData: firstChartPoints, xAxisPoint: 'June' }, - { chartData: secondChartPoints, xAxisPoint: 'July' }, - { chartData: thirdChartPoints, xAxisPoint: 'August' }, - { chartData: firstChartPoints, xAxisPoint: 'September' }, + { + chartData: firstChartPoints, + xAxisPoint: 'Jan', + ...(showLine && { lineData: [{ y: 40, color: DefaultPalette.yellowDark, legend: 'line1' }] }), + }, + { + chartData: secondChartPoints, + xAxisPoint: 'Feb', + ...(showLine && { + lineData: [ + { y: 15, color: DefaultPalette.yellowDark, legend: 'line1' }, + { y: 70, color: DefaultPalette.magenta, legend: 'line3' }, + ], + }), + }, + { + chartData: thirdChartPoints, + xAxisPoint: 'March', + ...(showLine && { + lineData: [ + { y: 65, color: DefaultPalette.greenDark, legend: 'line2' }, + { y: 98, color: DefaultPalette.magenta, legend: 'line3' }, + ], + }), + }, + { + chartData: fourthChartPoints, + xAxisPoint: 'April', + ...(showLine && { + lineData: [ + { y: 40, color: DefaultPalette.yellowDark, legend: 'line1' }, + { y: 50, color: DefaultPalette.greenDark, legend: 'line2' }, + { y: 65, color: DefaultPalette.magenta, legend: 'line3' }, + ], + }), + }, + { + chartData: fifthChartPoints, + xAxisPoint: 'May', + ...(showLine && { + lineData: [ + { y: 20, color: DefaultPalette.yellowDark, legend: 'line1' }, + { y: 65, color: DefaultPalette.greenDark, legend: 'line2' }, + ], + }), + }, + { + chartData: sixthChartPoints, + xAxisPoint: 'June', + ...(showLine && { + lineData: [ + { y: 54, color: DefaultPalette.greenDark, legend: 'line2' }, + { y: 87, color: DefaultPalette.magenta, legend: 'line3' }, + ], + }), + }, + { + chartData: seventhChartPoints, + xAxisPoint: 'July', + ...(showLine && { + lineData: [ + { y: 10, color: DefaultPalette.yellowDark, legend: 'line1' }, + { y: 110, color: DefaultPalette.magenta, legend: 'line3' }, + ], + }), + }, + { + chartData: eightChartPoints, + xAxisPoint: 'August', + ...(showLine && { + lineData: [ + { y: 45, color: DefaultPalette.yellowDark, legend: 'line1' }, + { y: 87, color: DefaultPalette.greenDark, legend: 'line2' }, + ], + }), + }, + { + chartData: firstChartPoints, + xAxisPoint: 'September', + ...(showLine && { + lineData: [ + { y: 15, color: DefaultPalette.yellowDark, legend: 'line1' }, + { y: 60, color: DefaultPalette.magenta, legend: 'line3' }, + ], + }), + }, ]; const rootStyle = { width: `${this.state.width}px`, height: `${this.state.height}px` }; @@ -79,15 +193,37 @@ export class VerticalStackedBarChartCalloutExample extends React.Component<{}, I - + + this.setState({ barGapMax: +e.target.value })} + /> + +
{ + return ( +
+
{JSON.stringify(props, undefined, 2)}
+
+ ); + }, + })} + {...(this.state.selectedCallout === 'MultiCustomCallout' && { + onRenderCalloutPerStack: (props: IVerticalStackedChartProps) => { + return ( +
+
+                      {JSON.stringify(props, null, 4)}
+                    
+
+ ); + }, + })} />