diff --git a/change/@fluentui-react-charting-4722a315-f7b2-4e91-9f26-23585f066b78.json b/change/@fluentui-react-charting-4722a315-f7b2-4e91-9f26-23585f066b78.json new file mode 100644 index 00000000000000..a46878a6f6deca --- /dev/null +++ b/change/@fluentui-react-charting-4722a315-f7b2-4e91-9f26-23585f066b78.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "Bug Fix", + "packageName": "@fluentui/react-charting", + "email": "srmukher@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/packages/react-charting/src/components/DonutChart/Arc/Arc.styles.ts b/packages/react-charting/src/components/DonutChart/Arc/Arc.styles.ts index ffd932bb78a698..39cb9e7ef56425 100644 --- a/packages/react-charting/src/components/DonutChart/Arc/Arc.styles.ts +++ b/packages/react-charting/src/components/DonutChart/Arc/Arc.styles.ts @@ -29,5 +29,32 @@ export const getStyles = (props: IArcProps): IArcStyles => { }, }, }, + tooltip: { + ...theme.fonts.medium, + display: 'flex', + flexDirection: 'column', + padding: '8px', + position: 'absolute', + textAlign: 'center', + top: '0px', + background: theme.semanticColors.bodyBackground, + borderRadius: '2px', + pointerEvents: 'none', + }, + nodeTextContainer: { + selectors: { + text: { + selectors: { + [HighContrastSelectorBlack]: { + fill: 'rgb(179, 179, 179)', + }, + }, + }, + }, + marginTop: '4px', + marginLeft: '8px', + marginBottom: '4px', + marginRight: '8px', + }, }; }; diff --git a/packages/react-charting/src/components/DonutChart/Arc/Arc.tsx b/packages/react-charting/src/components/DonutChart/Arc/Arc.tsx index b120465729e6d8..fa1231ad8cf6fb 100644 --- a/packages/react-charting/src/components/DonutChart/Arc/Arc.tsx +++ b/packages/react-charting/src/components/DonutChart/Arc/Arc.tsx @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import * as React from 'react'; import * as shape from 'd3-shape'; import { classNamesFunction } from '@fluentui/react/lib/Utilities'; @@ -5,6 +6,8 @@ import { getStyles } from './Arc.styles'; import { IChartDataPoint } from '../index'; import { IArcProps, IArcStyles } from './index'; import { wrapTextInsideDonut } from '../../../utilities/index'; +import { select as d3Select } from 'd3-selection'; +import { IProcessedStyleSet } from '../../../Styling'; export interface IArcState { isCalloutVisible?: boolean; @@ -19,6 +22,8 @@ export class Arc extends React.Component { public state: {} = {}; + private _tooltip: any; + private _classNames: IProcessedStyleSet; private currentRef = React.createRef(); public static getDerivedStateFromProps(nextProps: Readonly): Partial | null { @@ -33,7 +38,7 @@ export class Arc extends React.Component { public render(): JSX.Element { const { arc, href, focusedArcId } = this.props; const getClassNames = classNamesFunction(); - const classNames = getClassNames(getStyles, { + this._classNames = getClassNames(getStyles, { color: this.props.color, href: href!, theme: this.props.theme!, @@ -41,16 +46,25 @@ export class Arc extends React.Component { const id = this.props.uniqText! + this.props.data!.data.legend!.replace(/\s+/, '') + this.props.data!.data.data; const opacity: number = this.props.activeArc === this.props.data!.data.legend || this.props.activeArc === '' ? 1 : 0.1; + let truncatedText: string = ''; + if (this.props.valueInsideDonut !== null && this.props.valueInsideDonut !== undefined) { + truncatedText = this._getTruncatedText( + this.props.valueInsideDonut!.toString(), + this.props.innerRadius! * 2 - TEXT_PADDING, + ); + } + const isTruncated: boolean = truncatedText.slice(-3) === '...'; + return ( {!!focusedArcId && focusedArcId === id && ( - + )} { aria-label={this._getAriaLabel()} role="img" /> - - {this.props.valueInsideDonut!} - + + + {truncatedText} + + ); } + public componentDidMount(): void { + this._tooltip = d3Select('body') + .append('div') + .attr('id', 'Donut_tooltip') + .attr('class', this._classNames.tooltip!) + .style('opacity', 0); + } + public componentDidUpdate(): void { const { href } = this.props; const getClassNames = classNamesFunction(); @@ -80,6 +111,59 @@ export class Arc extends React.Component { wrapTextInsideDonut(classNames.insideDonutString, this.props.innerRadius! * 2 - TEXT_PADDING); } + private _getTruncatedText(text: string, maxWidth: number): string { + const words = text.split(/\s+/).reverse(); + let word: string = ''; + const line: string[] = []; + let truncatedText = text; + const tspan = d3Select('#Donut_center_text').text(null).append('tspan'); + let ellipsisLength = 0; + + if (tspan.node() !== null && tspan.node() !== undefined) { + // Determine the ellipsis length for word truncation. + tspan.text('...'); + ellipsisLength = tspan.node()!.getComputedTextLength(); + tspan.text(null); + truncatedText = ''; + + while ((word = words.pop()!)) { + line.push(word); + tspan.text(line.join(' ') + ' '); + // Determine if truncation is required. If yes, append the ellipsis and break. + if (tspan.node()!.getComputedTextLength() > maxWidth - ellipsisLength && line.length) { + line.pop(); + while (tspan.node()!.getComputedTextLength() > maxWidth - ellipsisLength) { + word = word.slice(0, -1); + tspan.text(word); + } + word += '...'; + line.push(word); + tspan.text(line.join(' ')); + break; + } + } + truncatedText = tspan.text(); + tspan.text(null); + } + return truncatedText; + } + + private _showTooltip = (text: string | number, checkTruncated: boolean, evt: any) => { + if (checkTruncated && text !== null && text !== undefined && this._tooltip) { + this._tooltip.style('opacity', 0.9); + this._tooltip + .html(text) + .style('left', evt.pageX + 'px') + .style('top', evt.pageY - 28 + 'px'); + } + }; + + private _hideTooltip = () => { + if (this._tooltip) { + this._tooltip.style('opacity', 0); + } + }; + private _onFocus(data: IChartDataPoint, id: string): void { this.props.onFocusCallback!(data, id, this.currentRef.current); } diff --git a/packages/react-charting/src/components/DonutChart/Arc/Arc.types.ts b/packages/react-charting/src/components/DonutChart/Arc/Arc.types.ts index 0b99c2a30de7b3..9e5e8e64487457 100644 --- a/packages/react-charting/src/components/DonutChart/Arc/Arc.types.ts +++ b/packages/react-charting/src/components/DonutChart/Arc/Arc.types.ts @@ -128,4 +128,14 @@ export interface IArcStyles { * styles for the focus */ focusRing: IStyle; + + /** + * Style for tool tip + */ + tooltip?: IStyle; + + /** + * Style for overflow center text container + */ + nodeTextContainer?: IStyle; } diff --git a/packages/react-charting/src/components/DonutChart/__snapshots__/DonutChart.test.tsx.snap b/packages/react-charting/src/components/DonutChart/__snapshots__/DonutChart.test.tsx.snap index 2b9bd2d1f049c0..8fe2149d313b7f 100644 --- a/packages/react-charting/src/components/DonutChart/__snapshots__/DonutChart.test.tsx.snap +++ b/packages/react-charting/src/components/DonutChart/__snapshots__/DonutChart.test.tsx.snap @@ -70,19 +70,36 @@ exports[`DonutChart - mouse events Should render callout correctly on mouseover opacity={1} role="img" /> - + > + + - + > + + @@ -613,19 +647,36 @@ exports[`DonutChart - mouse events Should render customized callout on mouseover opacity={1} role="img" /> - + > + + - + > + + @@ -1072,19 +1140,38 @@ exports[`DonutChart snapShot testing renders DonutChart correctly 1`] = ` opacity={1} role="img" /> - + > + + + + - + > + + + + @@ -1452,19 +1558,38 @@ exports[`DonutChart snapShot testing renders enabledLegendsWrapLines correctly 1 opacity={1} role="img" /> - + > + + + + - + > + + + + @@ -1832,19 +1976,38 @@ exports[`DonutChart snapShot testing renders hideLegend correctly 1`] = ` opacity={1} role="img" /> - + > + + + + - + > + + + + @@ -1963,19 +2145,38 @@ exports[`DonutChart snapShot testing renders hideTooltip correctly 1`] = ` opacity={1} role="img" /> - + > + + + + - + > + + + + @@ -2343,21 +2563,38 @@ exports[`DonutChart snapShot testing renders value inside onf the pie 1`] = ` opacity={1} role="img" /> - - 1,000 - + + 1,000 + + - - 1,000 - + + 1,000 + +