Skip to content

Commit ac12164

Browse files
Anush2303Anush
andauthored
feat(react-charting): support chart title from plotly schema (#35643)
Co-authored-by: Anush <[email protected]>
1 parent 4926334 commit ac12164

33 files changed

+3546
-1844
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "patch",
3+
"comment": "support chart title from plotly schema",
4+
"packageName": "@fluentui/react-charting",
5+
"email": "[email protected]",
6+
"dependentChangeType": "patch"
7+
}

packages/charts/react-charting/etc/react-charting.api.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import { CurveFactory } from 'd3-shape';
88
import { FocusZoneDirection } from '@fluentui/react-focus';
9+
import type { Font } from '@fluentui/chart-utilities';
910
import { ICalloutContentStyleProps } from '@fluentui/react/lib/Callout';
1011
import { ICalloutContentStyles } from '@fluentui/react/lib/Callout';
1112
import { ICalloutProps } from '@fluentui/react/lib/Callout';
@@ -448,6 +449,8 @@ export interface ICartesianChartProps {
448449
tickPadding?: number;
449450
tickValues?: number[] | Date[] | string[];
450451
timeFormatLocale?: TimeLocaleDefinition;
452+
// Warning: (ae-forgotten-export) The symbol "ITitleStyles" needs to be exported by the entry point index.d.ts
453+
titleStyles?: ITitleStyles;
451454
useUTC?: boolean;
452455
width?: number;
453456
wrapXAxisLables?: boolean;
@@ -591,6 +594,7 @@ export interface IChartProps {
591594

592595
// @public
593596
export interface IChartTableProps {
597+
chartTitle?: string;
594598
className?: string;
595599
componentRef?: IRefObject<IChart>;
596600
headers: {
@@ -604,6 +608,7 @@ export interface IChartTableProps {
604608
}[][];
605609
styles?: IStyleFunctionOrObject<IChartTableStyleProps, IChartTableStyles>;
606610
theme?: ITheme;
611+
titleStyles?: ITitleStyles;
607612
width?: string | number;
608613
}
609614

@@ -622,10 +627,14 @@ export interface IChartTableStyles {
622627
// (undocumented)
623628
chart?: IStyle;
624629
// (undocumented)
630+
chartTitle?: IStyle;
631+
// (undocumented)
625632
headerCell?: IStyle;
626633
// (undocumented)
627634
root?: IStyle;
628635
// (undocumented)
636+
svgTooltip?: IStyle;
637+
// (undocumented)
629638
table?: IStyle;
630639
}
631640

@@ -744,6 +753,7 @@ export interface IDonutChartProps extends ICartesianChartProps {
744753
roundCorners?: boolean;
745754
showLabelsInPercent?: boolean;
746755
styles?: IStyleFunctionOrObject<IDonutChartStyleProps, IDonutChartStyles>;
756+
titleStyles?: ITitleStyles;
747757
valueInsideDonut?: string | number;
748758
}
749759

@@ -755,9 +765,11 @@ export interface IDonutChartStyleProps extends ICartesianChartStyleProps {
755765
export interface IDonutChartStyles {
756766
axisAnnotation?: IStyle;
757767
chart?: IStyle;
768+
chartTitle?: IStyle;
758769
chartWrapper?: IStyle;
759770
legendContainer: IStyle;
760771
root?: IStyle;
772+
svgTooltip?: IStyle;
761773
}
762774

763775
// @public (undocumented)
@@ -793,6 +805,7 @@ export interface IFunnelChartProps {
793805
orientation?: 'horizontal' | 'vertical';
794806
styles?: IStyleFunctionOrObject<IFunnelChartStyleProps, IFunnelChartStyles>;
795807
theme?: ITheme;
808+
titleStyles?: ITitleStyles;
796809
width?: number;
797810
}
798811

@@ -807,10 +820,12 @@ export interface IFunnelChartStyleProps {
807820
// @public
808821
export interface IFunnelChartStyles {
809822
chart?: IStyle;
823+
chartTitle?: IStyle;
810824
root?: IStyle;
811825
subComponentStyles: {
812826
calloutStyles: IStyleFunctionOrObject<ICalloutContentStyleProps, ICalloutContentStyles>;
813827
};
828+
svgTooltip?: IStyle;
814829
text?: IStyle;
815830
}
816831

@@ -875,6 +890,7 @@ export interface IGaugeChartProps {
875890
styles?: IStyleFunctionOrObject<IGaugeChartStyleProps, IGaugeChartStyles>;
876891
sublabel?: string;
877892
theme?: ITheme;
893+
titleStyles?: ITitleStyles;
878894
variant?: GaugeChartVariant;
879895
width?: number;
880896
}
@@ -924,6 +940,7 @@ export interface IGaugeChartStyles {
924940
segment?: IStyle;
925941
shapeStyles?: IStyle;
926942
sublabel?: IStyle;
943+
svgTooltip?: IStyle;
927944
}
928945

929946
// @public (undocumented)
@@ -1637,12 +1654,14 @@ export interface ISankeyChartProps {
16371654
enableReflow?: boolean;
16381655
formatNumberOptions?: Intl.NumberFormatOptions;
16391656
height?: number;
1657+
hideLegend?: boolean;
16401658
parentRef?: HTMLElement | null;
16411659
pathColor?: string;
16421660
shouldResize?: number;
16431661
strings?: ISankeyChartStrings;
16441662
styles?: IStyleFunctionOrObject<ISankeyChartStyleProps, ISankeyChartStyles>;
16451663
theme?: ITheme;
1664+
titleStyles?: ITitleStyles;
16461665
width?: number;
16471666
}
16481667

@@ -1671,11 +1690,13 @@ export interface ISankeyChartStyleProps {
16711690
export interface ISankeyChartStyles {
16721691
calloutContentRoot?: IStyle;
16731692
chart?: IStyle;
1693+
chartTitle?: IStyle;
16741694
chartWrapper?: IStyle;
16751695
links?: IStyle;
16761696
nodes?: IStyle;
16771697
nodeTextContainer?: IStyle;
16781698
root?: IStyle;
1699+
svgTooltip?: IStyle;
16791700
toolTip?: IStyle;
16801701
}
16811702

packages/charts/react-charting/src/components/ChartTable/ChartTable.base.tsx

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { IChartTableStyleProps, IChartTableStyles } from '../../index';
44
import { classNamesFunction, getRTL, initializeComponentRef } from '@fluentui/react/lib/Utilities';
55
import { IImageExportOptions } from '../../types/index';
66
import { exportChartsAsImage } from '../../utilities/image-export-utils';
7+
import { ChartTitle } from '../../utilities/index';
78
import * as d3 from 'd3-color';
89
import { getColorContrast } from '../../utilities/colors';
910
import { ITheme } from '@fluentui/react';
@@ -66,7 +67,7 @@ export class ChartTableBase extends React.Component<IChartTableProps> {
6667
};
6768

6869
public render(): JSXElement {
69-
const { headers, rows, width, height, styles, theme } = this.props;
70+
const { headers, rows, width, height, styles, theme, chartTitle } = this.props;
7071

7172
const classNames = getClassNames(styles!, {
7273
theme: theme!,
@@ -108,16 +109,33 @@ export class ChartTableBase extends React.Component<IChartTableProps> {
108109
}
109110
}
110111

112+
const titleHeight = chartTitle ? 30 : 0;
113+
const totalHeight = typeof height === 'number' ? height : 650;
114+
const tableHeight = `${totalHeight - titleHeight}px`;
115+
const svgWidth = typeof width === 'number' ? width : '100%';
116+
const titleMaxWidth = typeof width === 'number' ? width - 20 : undefined;
117+
const titleX = typeof width === 'number' ? width / 2 : 0;
118+
111119
return (
112120
<div
113121
ref={el => {
114122
this._rootElem = el;
115123
}}
116124
className={classNames.root}
117-
style={{ height: height ? `${height}px` : '650px', overflow: 'hidden' }}
125+
style={{ height: `${totalHeight}px`, overflow: 'hidden' }}
118126
>
119-
<svg width={width ?? '100%'} height={height ?? '650px'} className={classNames.chart}>
120-
<foreignObject x="0" y="0" width="100%" height="100%">
127+
<svg width={svgWidth} height={`${totalHeight}px`} className={classNames.chart}>
128+
{chartTitle && (
129+
<ChartTitle
130+
title={chartTitle}
131+
x={titleX}
132+
maxWidth={titleMaxWidth}
133+
className={classNames.chartTitle}
134+
titleStyles={this.props.titleStyles}
135+
tooltipClassName={classNames.svgTooltip}
136+
/>
137+
)}
138+
<foreignObject x="0" y={titleHeight} width="100%" height={tableHeight}>
121139
<div
122140
style={{
123141
maxHeight: height ? `${height}px` : '650px',

packages/charts/react-charting/src/components/ChartTable/ChartTable.styles.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { IChartTableStyleProps, IChartTableStyles } from './ChartTable.types';
22
import { FontWeights, HighContrastSelector } from '@fluentui/react/lib/Styling';
3+
import { getChartTitleStyle, getSvgTooltipStyle } from '../../utilities/Common.styles';
34

45
export const getStyles = (props: IChartTableStyleProps): IChartTableStyles => {
56
const { theme } = props;
@@ -40,5 +41,7 @@ export const getStyles = (props: IChartTableStyleProps): IChartTableStyles => {
4041
},
4142
},
4243
},
44+
chartTitle: getChartTitleStyle(theme!),
45+
svgTooltip: getSvgTooltipStyle(theme!),
4346
};
4447
};

packages/charts/react-charting/src/components/ChartTable/ChartTable.types.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as React from 'react';
22
import { ITheme, IStyle } from '@fluentui/react/lib/Styling';
33
import { IRefObject, IStyleFunctionOrObject } from '@fluentui/react/lib/Utilities';
44
import { IChart } from '../../types/index';
5+
import { ITitleStyles } from '../../utilities/Common.styles';
56

67
export interface IChartTableStyleProps {
78
theme: ITheme;
@@ -13,6 +14,11 @@ export interface IChartTableStyleProps {
1314
* {@docCategory ChartTable}
1415
*/
1516
export interface IChartTableProps {
17+
/**
18+
* Title styles configuration for the chart title
19+
*/
20+
titleStyles?: ITitleStyles;
21+
1622
/**
1723
* 1d or 2d Array of header values.
1824
*/
@@ -35,6 +41,11 @@ export interface IChartTableProps {
3541
*/
3642
height?: string | number;
3743

44+
/**
45+
* Chart title to display above the table
46+
*/
47+
chartTitle?: string;
48+
3849
/**
3950
* Theme (provided through customization)
4051
*/
@@ -67,4 +78,6 @@ export interface IChartTableStyles {
6778
headerCell?: IStyle;
6879
bodyCell?: IStyle;
6980
chart?: IStyle;
81+
chartTitle?: IStyle;
82+
svgTooltip?: IStyle;
7083
}

packages/charts/react-charting/src/components/CommonComponents/CartesianChart.base.tsx

Lines changed: 53 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {
3232
truncateString,
3333
tooltipOfAxislabels,
3434
DEFAULT_WRAP_WIDTH,
35+
getChartTitleInlineStyles,
3536
autoLayoutXAxisLabels,
3637
} from '../../utilities/index';
3738
import { LegendShape, Shape } from '../Legends/index';
@@ -585,20 +586,58 @@ export class CartesianChartBase
585586
{...commonSvgToolTipProps}
586587
/>
587588
)}
588-
{this.props.xAxisAnnotation !== undefined && this.props.xAxisAnnotation !== '' && (
589-
<SVGTooltipText
590-
content={this.props.xAxisAnnotation}
591-
textProps={{
592-
x: this.margins.left! + AXIS_TITLE_PADDING + xAxisTitleMaxWidth / 2,
593-
y: VERTICAL_MARGIN_FOR_XAXIS_TITLE - AXIS_TITLE_PADDING,
594-
className: this._classNames.axisAnnotation!,
595-
textAnchor: 'middle',
596-
'aria-hidden': true,
597-
}}
598-
maxWidth={xAxisTitleMaxWidth}
599-
{...commonSvgToolTipProps}
600-
/>
601-
)}
589+
{this.props.xAxisAnnotation !== undefined &&
590+
this.props.xAxisAnnotation !== '' &&
591+
(() => {
592+
const { titleFont, titleXAnchor, titleYAnchor, titlePad } = this.props.titleStyles ?? {};
593+
const fontSize = typeof titleFont?.size === 'number' ? titleFont.size : 13;
594+
const padL = titlePad?.l ?? 0;
595+
const padR = titlePad?.r ?? 0;
596+
const padT = titlePad?.t ?? 0;
597+
const padB = titlePad?.b ?? 0;
598+
599+
const xPos =
600+
(titleXAnchor === 'left'
601+
? this.margins.left! + AXIS_TITLE_PADDING
602+
: titleXAnchor === 'right'
603+
? this.margins.left! + AXIS_TITLE_PADDING + xAxisTitleMaxWidth
604+
: this.margins.left! + AXIS_TITLE_PADDING + xAxisTitleMaxWidth / 2) +
605+
padL -
606+
padR;
607+
608+
const yPos =
609+
Math.max(fontSize + AXIS_TITLE_PADDING, VERTICAL_MARGIN_FOR_XAXIS_TITLE - AXIS_TITLE_PADDING) +
610+
padT -
611+
padB;
612+
613+
const textAnchor = titleXAnchor === 'left' ? 'start' : titleXAnchor === 'right' ? 'end' : 'middle';
614+
615+
const dominantBaseline =
616+
titleYAnchor === 'top'
617+
? 'hanging'
618+
: titleYAnchor === 'bottom'
619+
? 'alphabetic'
620+
: titleYAnchor === 'middle'
621+
? 'central'
622+
: 'auto';
623+
624+
return (
625+
<SVGTooltipText
626+
content={this.props.xAxisAnnotation}
627+
textProps={{
628+
x: xPos,
629+
y: yPos,
630+
className: this._classNames.axisAnnotation!,
631+
textAnchor,
632+
dominantBaseline,
633+
'aria-hidden': true,
634+
style: getChartTitleInlineStyles(titleFont),
635+
}}
636+
maxWidth={xAxisTitleMaxWidth}
637+
{...commonSvgToolTipProps}
638+
/>
639+
);
640+
})()}
602641
<g
603642
ref={(e: SVGSVGElement | null) => {
604643
this.yAxisElement = e;

packages/charts/react-charting/src/components/CommonComponents/CartesianChart.types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { IOverflowSetProps } from '@fluentui/react/lib/OverflowSet';
55
import { IFocusZoneProps, FocusZoneDirection } from '@fluentui/react-focus';
66
import { ICalloutProps } from '@fluentui/react/lib/Callout';
77
import { ILegendsProps } from '../Legends/index';
8+
import { ITitleStyles } from '../../utilities/Common.styles';
89
import {
910
AxisCategoryOrder,
1011
AxisProps,
@@ -221,6 +222,11 @@ export interface ICartesianChartStyles {
221222
* {@docCategory CartesianChart}
222223
*/
223224
export interface ICartesianChartProps {
225+
/**
226+
* Title styles configuration for the chart title
227+
*/
228+
titleStyles?: ITitleStyles;
229+
224230
/**
225231
* Below height used for resizing of the chart
226232
* Wrap chart in your container and send the updated height and width to these props.

0 commit comments

Comments
 (0)