Skip to content

Commit

Permalink
Charting: Adding custom Callout support for HorizontalBarChart, Stack…
Browse files Browse the repository at this point in the history
…edBarChart and DonutChart (#15697)

Cherry-pick of #15298.
  • Loading branch information
khmakoto authored Oct 26, 2020
1 parent 26ce5d6 commit 9340abc
Show file tree
Hide file tree
Showing 16 changed files with 409 additions and 312 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"type": "prerelease",
"comment": "Charting: Adding custom Callout support for HorizontalBarChart, StackedBarChart and DonutChart.",
"packageName": "@fluentui/react-charting",
"email": "[email protected]",
"dependentChangeType": "patch",
"date": "2020-10-26T18:59:34.573Z"
}
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ export interface ICartesianChartProps {
/**
* Callout customization props
*/
calloutProps?: ICalloutProps;
calloutProps?: Partial<ICalloutProps>;
}

export interface IYValueHover {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ interface IDonutChartState {
yCalloutValue?: string;
focusedArcId?: string;
selectedLegend: string;
dataPointCalloutProps?: IChartDataPoint;
}

export class DonutChartBase extends React.Component<IDonutChartProps, IDonutChartState> {
Expand Down Expand Up @@ -138,12 +139,17 @@ export class DonutChartBase extends React.Component<IDonutChartProps, IDonutChar
id={this._calloutId}
onDismiss={this._closeCallout}
preventDismissOnLostFocus={true}
{...this.props.calloutProps!}
>
<ChartHoverCard
Legend={this.state.xCalloutValue ? this.state.xCalloutValue : this.state.legend}
YValue={this.state.yCalloutValue ? this.state.yCalloutValue : this.state.value}
color={this.state.color}
/>
{this.props.onRenderCalloutPerDataPoint ? (
this.props.onRenderCalloutPerDataPoint(this.state.dataPointCalloutProps!)
) : (
<ChartHoverCard
Legend={this.state.xCalloutValue ? this.state.xCalloutValue : this.state.legend}
YValue={this.state.yCalloutValue ? this.state.yCalloutValue : this.state.value}
color={this.state.color}
/>
)}
</Callout>
<div className={this._classNames.legendContainer}>{!hideLegend && legendBars}</div>
</div>
Expand Down Expand Up @@ -233,6 +239,7 @@ export class DonutChartBase extends React.Component<IDonutChartProps, IDonutChar
xCalloutValue: data.xAxisCalloutData!,
yCalloutValue: data.yAxisCalloutData!,
focusedArcId: id,
dataPointCalloutProps: data,
});
};

Expand All @@ -247,6 +254,7 @@ export class DonutChartBase extends React.Component<IDonutChartProps, IDonutChar
xCalloutValue: data.xAxisCalloutData!,
yCalloutValue: data.yAxisCalloutData!,
activeLegend: data.legend,
dataPointCalloutProps: data,
});
};
private _onBlur = (): void => {
Expand Down
101 changes: 11 additions & 90 deletions packages/react-charting/src/components/DonutChart/DonutChart.types.ts
Original file line number Diff line number Diff line change
@@ -1,123 +1,44 @@
import { ITheme, IStyle } from '@fluentui/react/lib/Styling';
import { IStyleFunctionOrObject } from '@fluentui/react/lib/Utilities';
import { IOverflowSetProps } from '@fluentui/react/lib/OverflowSet';
import { IFocusZoneProps } from '@fluentui/react-focus';
import { ILegendsProps } from '../Legends/index';
import { IStyle } from '@fluentui/react/lib/Styling';
import { IRenderFunction, IStyleFunctionOrObject } from '@fluentui/react/lib/Utilities';
import { ICartesianChartProps, ICartesianChartStyleProps } from '../CommonComponents/index';
import { ICalloutProps } from '@fluentui/react/lib/Callout';
import { IChartProps, IChartDataPoint } from './index';

export interface IDonutChart {}
import { IChartProps } from './index';

export interface IDonutChartProps {
export interface IDonutChartProps extends ICartesianChartProps {
/**
* Data to render in the chart.
*/
data?: IChartProps;

/**
* Width of the donut.
*/
width?: number;

/**
* Height of the donut.
*/
height?: number;

/**
* Additional CSS class(es) to apply to the DonutChart.
*/
className?: string;

/**
* inner radius for donut size
*/
innerRadius?: number;

/**
* Theme (provided through customization.)
*/
theme?: ITheme;

/**
* Call to provide customized styling that will layer on top of the variant rules.
*/
styles?: IStyleFunctionOrObject<IDonutChartStyleProps, IDonutChartStyles>;

/**
* Width of line stroke
*/
strokeWidth?: string;

/**
* Url that the data-viz needs to redirect to upon clicking on it
*/
href?: string;

/**
* overflow props for the legends
*/
legendsOverflowProps?: Partial<IOverflowSetProps>;

/**
* text for overflow legends string
*/
legendsOverflowText?: string;

/**
* props for inside donut value
*/
valueInsideDonut?: string | number;

/**
* focus zone props in hover card for legends
*/
focusZonePropsForLegendsInHoverCard?: IFocusZoneProps;

/**
* decides wether to show/hide legends
* @defaultvalue false
* Define a custom callout renderer for a data point
*/
hideLegend?: boolean;
onRenderCalloutPerDataPoint?: IRenderFunction<IChartDataPoint>;

/**
* Do not show tooltips in chart
*
* @default false
* props for the callout in the chart
*/
hideTooltip?: boolean;

/**
* props for the legends in the chart
*/
legendProps?: Partial<ILegendsProps>;
calloutProps?: Partial<ICalloutProps>;
}

export interface IDonutChartStyleProps {
/**
* Theme (provided through customization.)
*/
theme: ITheme;

/**
* Additional CSS class(es) to apply to the Donut chart.
*/
className?: string;

/**
* Height of the donut.
*/
height?: number;

/**
* Width of the donut.
*/
width: number;

/**
* color for hover font color
*/
color?: string;
}
export interface IDonutChartStyleProps extends ICartesianChartStyleProps {}

export interface IDonutChartStyles {
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,35 +7,32 @@ import {
IHorizontalBarChartStyleProps,
IHorizontalBarChartStyles,
IChartDataPoint,
IRefArrayData,
} from './index';
import { Callout, DirectionalHint } from '@fluentui/react/lib/Callout';
import { ChartHoverCard } from '../../utilities/ChartHoverCard/index';
import { FocusZone, FocusZoneDirection } from '@fluentui/react-focus';

const getClassNames = classNamesFunction<IHorizontalBarChartStyleProps, IHorizontalBarChartStyles>();

export interface IRefArrayData {
legendText?: string;
refElement?: SVGGElement;
}

export interface IHorizontalBarChartState {
isCalloutVisible: boolean;
refArray: IRefArrayData[];
refSelected: SVGGElement | null | undefined;
color: string;
hoverValue: string | number | Date | null;
lineColor: string;
legend: string | null;
xCalloutValue?: string;
yCalloutValue?: string;
barCalloutProps?: IChartDataPoint;
}

export class HorizontalBarChartBase extends React.Component<IHorizontalBarChartProps, IHorizontalBarChartState> {
private _barHeight: number;
private _classNames: IProcessedStyleSet<IHorizontalBarChartStyles>;
private _uniqLineText: string;
private _calloutId: string;
private _refArray: IRefArrayData[];

constructor(props: IHorizontalBarChartProps) {
super(props);
Expand All @@ -44,13 +41,13 @@ export class HorizontalBarChartBase extends React.Component<IHorizontalBarChartP
hoverValue: '',
lineColor: '',
legend: '',
refArray: [],
refSelected: null,
// eslint-disable-next-line react/no-unused-state
color: '',
xCalloutValue: '',
yCalloutValue: '',
};
this._refArray = [];
this._uniqLineText =
'_HorizontalLine_' +
Math.random()
Expand Down Expand Up @@ -107,20 +104,14 @@ export class HorizontalBarChartBase extends React.Component<IHorizontalBarChartP
points!
.chartData![0].horizontalBarChartdata!.x.toString()
.replace(/\B(?=(\d{3})+(?!\d))/g, ','),
points!.chartData![0].color,
points!.chartData![0].legend,
points!.chartData![0].xAxisCalloutData!,
points!.chartData![0].yAxisCalloutData!,
points!.chartData![0],
)}
onFocus={this._hoverOn.bind(
this,
points!
.chartData![0].horizontalBarChartdata!.x.toString()
.replace(/\B(?=(\d{3})+(?!\d))/g, ','),
points!.chartData![0].color,
points!.chartData![0].legend,
points!.chartData![0].xAxisCalloutData!,
points!.chartData![0].yAxisCalloutData!,
points!.chartData![0],
)}
aria-labelledby={this._calloutId}
data-is-focusable={true}
Expand All @@ -142,43 +133,42 @@ export class HorizontalBarChartBase extends React.Component<IHorizontalBarChartP
hidden={!(!this.props.hideTooltip && this.state.isCalloutVisible)}
directionalHint={DirectionalHint.rightTopEdge}
id={this._calloutId}
{...this.props.calloutProps!}
>
<ChartHoverCard
Legend={this.state.xCalloutValue ? this.state.xCalloutValue : this.state.legend!}
YValue={this.state.yCalloutValue ? this.state.yCalloutValue : this.state.hoverValue!}
color={this.state.lineColor}
/>
{this.props.onRenderCalloutPerHorizontalBar ? (
this.props.onRenderCalloutPerHorizontalBar(this.state.barCalloutProps)
) : (
<ChartHoverCard
Legend={this.state.xCalloutValue ? this.state.xCalloutValue : this.state.legend!}
YValue={this.state.yCalloutValue ? this.state.yCalloutValue : this.state.hoverValue!}
color={this.state.lineColor}
/>
)}
</Callout>
</div>
</FocusZone>
);
}

private _refCallback(element: SVGGElement, legendTitle: string | undefined): void {
this.state.refArray.push({ legendText: legendTitle, refElement: element });
this._refArray.push({ index: legendTitle, refElement: element });
}

private _hoverOn(
hoverValue: string | number | Date | null,
lineColor: string,
legend: string,
xAxisCalloutData: string,
yAxisCalloutData: string,
): void {
if (!this.state.isCalloutVisible || this.state.legend !== legend) {
const refArray = this.state.refArray;
private _hoverOn(hoverValue: string | number | Date | null, point: IChartDataPoint): void {
if (!this.state.isCalloutVisible || this.state.legend !== point.legend!) {
const currentHoveredElement = find(
refArray,
(currentElement: IRefArrayData) => currentElement.legendText === legend,
this._refArray,
(currentElement: IRefArrayData) => currentElement.index === point.legend,
);
this.setState({
isCalloutVisible: true,
hoverValue: hoverValue,
lineColor: lineColor,
legend: legend,
lineColor: point.color!,
legend: point.legend!,
refSelected: currentHoveredElement!.refElement,
xCalloutValue: xAxisCalloutData,
yCalloutValue: yAxisCalloutData,
xCalloutValue: point.xAxisCalloutData!,
yCalloutValue: point.yAxisCalloutData!,
barCalloutProps: point,
});
}
}
Expand All @@ -196,12 +186,11 @@ export class HorizontalBarChartBase extends React.Component<IHorizontalBarChartP
}

private _adjustProps = (): void => {
const { theme, className, styles, width, barHeight } = this.props;
this._barHeight = barHeight || 8;
this._classNames = getClassNames(styles!, {
theme: theme!,
width: width,
className,
this._barHeight = this.props.barHeight || 8;
this._classNames = getClassNames(this.props.styles!, {
theme: this.props.theme!,
width: this.props.width,
className: this.props.className,
barHeight: this._barHeight,
color: this.state.lineColor,
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { IChartProps } from './index';
import { IChartProps, IChartDataPoint } from './index';
import { IStyle, ITheme } from '@fluentui/react/lib/Styling';
import { IStyleFunctionOrObject } from '@fluentui/react/lib/Utilities';
import { ICalloutProps } from '@fluentui/react/lib/Callout';
import { IRenderFunction, IStyleFunctionOrObject } from '@fluentui/react/lib/Utilities';

export interface IHorizontalBarChartProps {
/**
Expand Down Expand Up @@ -51,6 +52,16 @@ export interface IHorizontalBarChartProps {
* Call to provide customized styling that will layer on top of the variant rules.
*/
styles?: IStyleFunctionOrObject<IHorizontalBarChartStyleProps, IHorizontalBarChartStyles>;

/**
* Define a custom callout renderer for a horizontal bar
*/
onRenderCalloutPerHorizontalBar?: IRenderFunction<IChartDataPoint>;

/**
* props for the callout in the chart
*/
calloutProps?: Partial<ICalloutProps>;
}

export interface IHorizontalBarChartStyleProps {
Expand Down
Loading

0 comments on commit 9340abc

Please sign in to comment.