Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Charting: Adding custom Callout support for HorizontalBarChart, StackedBarChart and DonutChart #15697

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -299,7 +299,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
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