From 7d17fa225cb03f6d39f4912d046df49a99b6eadc Mon Sep 17 00:00:00 2001 From: Makoto Morimoto Date: Fri, 23 Oct 2020 17:38:45 -0700 Subject: [PATCH] Charting: Adding customized callout support to AreaChart and LineChart (#15684) Cherry-pick of #15361. --- ...10-23-16-37-48-customCalloutForCharts.json | 8 + .../components/AreaChart/AreaChart.base.tsx | 16 + .../components/AreaChart/AreaChart.types.ts | 13 +- .../components/LineChart/LineChart.base.tsx | 18 + .../components/LineChart/LineChart.types.ts | 21 +- .../react-charting/src/types/IDataPoint.ts | 17 + .../AreaChart/AreaChart.Basic.Example.tsx | 310 ++++++++---------- .../LineChart/LineChart.Styled.Example.tsx | 20 +- 8 files changed, 244 insertions(+), 179 deletions(-) create mode 100644 change/@fluentui-react-charting-2020-10-23-16-37-48-customCalloutForCharts.json diff --git a/change/@fluentui-react-charting-2020-10-23-16-37-48-customCalloutForCharts.json b/change/@fluentui-react-charting-2020-10-23-16-37-48-customCalloutForCharts.json new file mode 100644 index 00000000000000..40f034e4d776fa --- /dev/null +++ b/change/@fluentui-react-charting-2020-10-23-16-37-48-customCalloutForCharts.json @@ -0,0 +1,8 @@ +{ + "type": "prerelease", + "comment": "Charting: Adding customized callout support to AreaChart and LineChart.", + "packageName": "@fluentui/react-charting", + "email": "humbertomakotomorimoto@gmail.com", + "dependentChangeType": "patch", + "date": "2020-10-23T23:37:48.170Z" +} diff --git a/packages/react-charting/src/components/AreaChart/AreaChart.base.tsx b/packages/react-charting/src/components/AreaChart/AreaChart.base.tsx index 5ebd7dd4add29e..67309d614791d7 100644 --- a/packages/react-charting/src/components/AreaChart/AreaChart.base.tsx +++ b/packages/react-charting/src/components/AreaChart/AreaChart.base.tsx @@ -7,6 +7,7 @@ import { find, getId, memoizeFunction } from '@fluentui/react/lib/Utilities'; import { CartesianChart, IChartProps, + ICustomizedCalloutData, IAreaChartProps, IChildProps, IRefArrayData, @@ -37,6 +38,8 @@ export interface IAreaChartState extends IBasestate { displayOfLine: string; activeCircleId: string; isCircleClicked: boolean; + dataPointCalloutProps?: ICustomizedCalloutData; + stackCalloutProps?: ICustomizedCalloutData; } export class AreaChartBase extends React.Component { @@ -116,6 +119,7 @@ export class AreaChartBase extends React.Component { @@ -201,6 +205,14 @@ export class AreaChartBase extends React.Component { + return this.props.onRenderCalloutPerStack + ? this.props.onRenderCalloutPerStack(this.state.stackCalloutProps) + : this.props.onRenderCalloutPerDataPoint + ? this.props.onRenderCalloutPerDataPoint(this.state.dataPointCalloutProps) + : null; + }; + // eslint-disable-next-line @typescript-eslint/no-explicit-any private _getGraphData = (xAxis: any, yAxis: any, containerHeight: number, containerWidth: number) => { this._chart = this._drawGraph(containerHeight, xAxis, yAxis); @@ -316,6 +328,8 @@ export class AreaChartBase extends React.Component; + + /** + * Define a custom callout renderer for a data point + */ + onRenderCalloutPerDataPoint?: IRenderFunction; + + /** + * Define a custom callout renderer for a stack; default is to render per data point + */ + onRenderCalloutPerStack?: IRenderFunction; } export interface IAreaChartStyles extends ICartesianChartStyles {} diff --git a/packages/react-charting/src/components/LineChart/LineChart.base.tsx b/packages/react-charting/src/components/LineChart/LineChart.base.tsx index eba5f999e3d8fc..7c7d00b3c1d570 100644 --- a/packages/react-charting/src/components/LineChart/LineChart.base.tsx +++ b/packages/react-charting/src/components/LineChart/LineChart.base.tsx @@ -9,6 +9,7 @@ import { IChildProps, ILineChartProps, ILineChartPoints, + ICustomizedCalloutData, IMargins, IRefArrayData, IColorFillBarsProps, @@ -33,6 +34,10 @@ export interface ILineChartState extends IBasestate { // This is a boolean value which is set to true // when at least one legend is selected isSelectedLegend: boolean; + // This value will be used as customized callout props - point callout. + dataPointCalloutProps?: ICustomizedCalloutData; + // This value will be used as Customized callout props - For stack callout. + stackCalloutProps?: ICustomizedCalloutData; } export class LineChartBase extends React.Component { @@ -137,6 +142,7 @@ export class LineChartBase extends React.Component { @@ -175,6 +181,14 @@ export class LineChartBase extends React.Component { + return this.props.onRenderCalloutPerStack + ? this.props.onRenderCalloutPerStack(this.state.stackCalloutProps) + : this.props.onRenderCalloutPerDataPoint + ? this.props.onRenderCalloutPerDataPoint(this.state.dataPointCalloutProps) + : null; + }; + private _getMargins = (margins: IMargins) => { this.margins = margins; }; @@ -498,6 +512,8 @@ export class LineChartBase extends React.Component; + + /** + * Define a custom callout renderer for a stack; default is to render per data point + */ + onRenderCalloutPerStack?: IRenderFunction; + /* * Color fill bars for the chart, */ diff --git a/packages/react-charting/src/types/IDataPoint.ts b/packages/react-charting/src/types/IDataPoint.ts index 91c52453c23cf5..7e75a81a20d306 100644 --- a/packages/react-charting/src/types/IDataPoint.ts +++ b/packages/react-charting/src/types/IDataPoint.ts @@ -372,3 +372,20 @@ export interface IHeatMapChartData { */ value: number; } + +export interface ICustomizedCalloutDataPoint { + legend: string; + y: number; + color: string; + xAxisCalloutData?: string; + yAxisCalloutData?: string | { [id: string]: number }; +} + +/** + * Used for custom callout data interface. As Area chart callout data will be prepared from given props.data, + * Those required data passing to onRenderCalloutPerDataPoint and onRenderCalloutPerStack. + */ +export interface ICustomizedCalloutData { + x: number | string | Date; + values: ICustomizedCalloutDataPoint[]; +} diff --git a/packages/react-examples/src/react-charting/AreaChart/AreaChart.Basic.Example.tsx b/packages/react-examples/src/react-charting/AreaChart/AreaChart.Basic.Example.tsx index 521688671fda21..ffb03c56d84d4b 100644 --- a/packages/react-examples/src/react-charting/AreaChart/AreaChart.Basic.Example.tsx +++ b/packages/react-examples/src/react-charting/AreaChart/AreaChart.Basic.Example.tsx @@ -1,182 +1,27 @@ import * as React from 'react'; -import { AreaChart } from '@fluentui/react-charting'; -import { ILineChartProps } from '@fluentui/react-charting'; +import { AreaChart, ICustomizedCalloutData } from '@fluentui/react-charting'; +import { IAreaChartProps, ChartHoverCard } from '@fluentui/react-charting'; import { DefaultPalette } from '@fluentui/react/lib/Styling'; +import { ChoiceGroup, IChoiceGroupOption } from '@fluentui/react/lib/ChoiceGroup'; interface IAreaChartBasicState { width: number; height: number; - chartData: any; + isCalloutselected: boolean; } -const chart1Points = [ - { - x: 20, - y: 7000, - xAxisCalloutData: '2018/01/01', - yAxisCalloutData: '10%', - }, - { - x: 25, - y: 9000, - xAxisCalloutData: '2018/01/15', - yAxisCalloutData: '18%', - }, - { - x: 30, - y: 13000, - xAxisCalloutData: '2018/01/28', - yAxisCalloutData: '24%', - }, - { - x: 35, - y: 15000, - xAxisCalloutData: '2018/02/01', - yAxisCalloutData: '25%', - }, - { - x: 40, - y: 11000, - xAxisCalloutData: '2018/03/01', - yAxisCalloutData: '15%', - }, - { - x: 45, - y: 8760, - xAxisCalloutData: '2018/03/15', - yAxisCalloutData: '30%', - }, - { - x: 50, - y: 3500, - xAxisCalloutData: '2018/03/28', - yAxisCalloutData: '18%', - }, - { - x: 55, - y: 20000, - xAxisCalloutData: '2018/04/04', - yAxisCalloutData: '32%', - }, - { - x: 60, - y: 17000, - xAxisCalloutData: '2018/04/15', - yAxisCalloutData: '29%', - }, - { - x: 65, - y: 1000, - xAxisCalloutData: '2018/05/05', - yAxisCalloutData: '43%', - }, - { - x: 70, - y: 12000, - xAxisCalloutData: '2018/06/01', - yAxisCalloutData: '45%', - }, - { - x: 75, - y: 6876, - xAxisCalloutData: '2018/01/15', - yAxisCalloutData: '18%', - }, - { - x: 80, - y: 12000, - xAxisCalloutData: '2018/04/30', - yAxisCalloutData: '55%', - }, - { - x: 85, - y: 7000, - xAxisCalloutData: '2018/05/04', - yAxisCalloutData: '12%', - }, - { - x: 90, - y: 10000, - xAxisCalloutData: '2018/06/01', - yAxisCalloutData: '45%', - }, -]; - -const chart2Points = [ - { - x: 50, - y: 3500, - xAxisCalloutData: '2018/03/28', - yAxisCalloutData: '18%', - }, - { - x: 55, - y: 20000, - xAxisCalloutData: '2018/04/04', - yAxisCalloutData: '32%', - }, - { - x: 60, - y: 17000, - xAxisCalloutData: '2018/04/15', - yAxisCalloutData: '29%', - }, - - { - x: 70, - y: 12000, - xAxisCalloutData: '2018/06/01', - yAxisCalloutData: '45%', - }, - { - x: 75, - y: 6876, - xAxisCalloutData: '2018/01/15', - yAxisCalloutData: '18%', - }, - { - x: 80, - y: 12000, - xAxisCalloutData: '2018/04/30', - yAxisCalloutData: '55%', - }, - { - x: 85, - y: 7000, - xAxisCalloutData: '2018/05/04', - yAxisCalloutData: '12%', - }, - { - x: 90, - y: 10000, - xAxisCalloutData: '2018/06/01', - yAxisCalloutData: '45%', - }, -]; - -const chartPoints1 = [ - { - legend: 'legend1', - data: chart1Points, - color: DefaultPalette.accent, - }, -]; - -const chartPoints2 = [ - { - legend: 'legend1', - data: chart2Points, - color: 'red', - }, +const options: IChoiceGroupOption[] = [ + { key: 'basicExample', text: 'Basic Example' }, + { key: 'calloutExample', text: 'Custom Callout Example' }, ]; export class AreaChartBasicExample extends React.Component<{}, IAreaChartBasicState> { - constructor(props: ILineChartProps) { + constructor(props: IAreaChartProps) { super(props); this.state = { width: 700, height: 300, - chartData: chartPoints1, + isCalloutselected: false, }; } @@ -191,14 +36,119 @@ export class AreaChartBasicExample extends React.Component<{}, IAreaChartBasicSt this.setState({ height: parseInt(e.target.value, 10) }); }; - private _onDataChange = () => { - this.setState({ chartData: chartPoints2 }); + private _onChange = (ev: React.FormEvent, option: IChoiceGroupOption): void => { + if (this.state.isCalloutselected) { + this.setState({ isCalloutselected: false }); + } else { + this.setState({ isCalloutselected: true }); + } }; private _basicExample(): JSX.Element { + const chart1Points = [ + { + x: 20, + y: 7000, + xAxisCalloutData: '2018/01/01', + yAxisCalloutData: '10%', + }, + { + x: 25, + y: 9000, + xAxisCalloutData: '2018/01/15', + yAxisCalloutData: '18%', + }, + { + x: 30, + y: 13000, + xAxisCalloutData: '2018/01/28', + yAxisCalloutData: '24%', + }, + { + x: 35, + y: 15000, + xAxisCalloutData: '2018/02/01', + yAxisCalloutData: '25%', + }, + { + x: 40, + y: 11000, + xAxisCalloutData: '2018/03/01', + yAxisCalloutData: '15%', + }, + { + x: 45, + y: 8760, + xAxisCalloutData: '2018/03/15', + yAxisCalloutData: '30%', + }, + { + x: 50, + y: 3500, + xAxisCalloutData: '2018/03/28', + yAxisCalloutData: '18%', + }, + { + x: 55, + y: 20000, + xAxisCalloutData: '2018/04/04', + yAxisCalloutData: '32%', + }, + { + x: 60, + y: 17000, + xAxisCalloutData: '2018/04/15', + yAxisCalloutData: '29%', + }, + { + x: 65, + y: 1000, + xAxisCalloutData: '2018/05/05', + yAxisCalloutData: '43%', + }, + { + x: 70, + y: 12000, + xAxisCalloutData: '2018/06/01', + yAxisCalloutData: '45%', + }, + { + x: 75, + y: 6876, + xAxisCalloutData: '2018/01/15', + yAxisCalloutData: '18%', + }, + { + x: 80, + y: 12000, + xAxisCalloutData: '2018/04/30', + yAxisCalloutData: '55%', + }, + { + x: 85, + y: 7000, + xAxisCalloutData: '2018/05/04', + yAxisCalloutData: '12%', + }, + { + x: 90, + y: 10000, + xAxisCalloutData: '2018/06/01', + yAxisCalloutData: '45%', + }, + ]; + + const chartPoints = [ + { + legend: 'legend1', + data: chart1Points, + color: DefaultPalette.accent, + }, + ]; + const chartData = { chartTitle: 'Area chart basic example', - lineChartData: this.state.chartData, + lineChartData: chartPoints, }; const rootStyle = { width: `${this.state.width}px`, height: `${this.state.height}px` }; @@ -209,15 +159,25 @@ export class AreaChartBasicExample extends React.Component<{}, IAreaChartBasicSt - +
- + + props && this.state.isCalloutselected ? ( + + ) : null + } + />
); diff --git a/packages/react-examples/src/react-charting/LineChart/LineChart.Styled.Example.tsx b/packages/react-examples/src/react-charting/LineChart/LineChart.Styled.Example.tsx index 76327e9eea7bb6..919c7a28dcf6fc 100644 --- a/packages/react-examples/src/react-charting/LineChart/LineChart.Styled.Example.tsx +++ b/packages/react-examples/src/react-charting/LineChart/LineChart.Styled.Example.tsx @@ -1,5 +1,12 @@ import * as React from 'react'; -import { IChartProps, ILineChartPoints, ILineChartProps, LineChart } from '@fluentui/react-charting'; +import { + IChartProps, + ILineChartPoints, + ILineChartProps, + LineChart, + ChartHoverCard, + ICustomizedCalloutData, +} from '@fluentui/react-charting'; import { DefaultPalette } from '@fluentui/react/lib/Styling'; interface IStyledLineChartExampleState { @@ -65,6 +72,17 @@ export class LineChartStyledExample extends React.Component<{}, IStyledLineChart hideLegend={true} height={this.state.height} width={this.state.width} + // eslint-disable-next-line react/jsx-no-bind + onRenderCalloutPerDataPoint={(props: ICustomizedCalloutData) => + props ? ( + + ) : null + } />