From 7e3a3c2b97e878e7522866b4d74fa94902b08d38 Mon Sep 17 00:00:00 2001 From: srmukher Date: Thu, 5 Jan 2023 23:47:23 +0530 Subject: [PATCH 1/6] Handling text overflow and adding tooltip for center texts of donut charts --- ...-4722a315-f7b2-4e91-9f26-23585f066b78.json | 7 + .../components/DonutChart/Arc/Arc.styles.ts | 27 ++ .../src/components/DonutChart/Arc/Arc.tsx | 96 +++- .../components/DonutChart/Arc/Arc.types.ts | 10 + .../__snapshots__/DonutChart.test.tsx.snap | 454 ++++++++++++++---- 5 files changed, 488 insertions(+), 106 deletions(-) create mode 100644 change/@fluentui-react-charting-4722a315-f7b2-4e91-9f26-23585f066b78.json 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 + + From 42d263678979c48582813f9da3379e6cc0eeca2a Mon Sep 17 00:00:00 2001 From: srmukher Date: Thu, 19 Jan 2023 12:25:29 +0530 Subject: [PATCH 2/6] Wrapping followed by truncation of inner text --- .../src/components/DonutChart/Arc/Arc.tsx | 71 +++------- .../react-charting/src/utilities/utilities.ts | 133 ++++++++++++++++-- 2 files changed, 137 insertions(+), 67 deletions(-) diff --git a/packages/react-charting/src/components/DonutChart/Arc/Arc.tsx b/packages/react-charting/src/components/DonutChart/Arc/Arc.tsx index fa1231ad8cf6fb..67381e12c510ad 100644 --- a/packages/react-charting/src/components/DonutChart/Arc/Arc.tsx +++ b/packages/react-charting/src/components/DonutChart/Arc/Arc.tsx @@ -1,7 +1,7 @@ /* 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'; +import { classNamesFunction, getId } from '@fluentui/react/lib/Utilities'; import { getStyles } from './Arc.styles'; import { IChartDataPoint } from '../index'; import { IArcProps, IArcStyles } from './index'; @@ -31,6 +31,10 @@ export class Arc extends React.Component { return null; } + public constructor(props: IArcProps) { + super(props); + } + public updateChart(newProps: IArcProps): void { _updateChart(newProps); } @@ -46,14 +50,6 @@ 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 ( @@ -81,10 +77,10 @@ export class Arc extends React.Component { className={this._classNames.insideDonutString} y={5} id={'Donut_center_text'} - onMouseOver={this._showTooltip.bind(this, this.props.valueInsideDonut!, isTruncated)} + onMouseOver={this._showTooltip.bind(this, this.props.valueInsideDonut)} onMouseOut={this._hideTooltip} > - {truncatedText} + {this.props.valueInsideDonut} @@ -92,11 +88,13 @@ export class Arc extends React.Component { } public componentDidMount(): void { - this._tooltip = d3Select('body') - .append('div') - .attr('id', 'Donut_tooltip') - .attr('class', this._classNames.tooltip!) - .style('opacity', 0); + if (!this._tooltip) { + this._tooltip = d3Select('body') + .append('div') + .attr('id', getId('_Donut_tooltip_')) + .attr('class', this._classNames.tooltip!) + .style('opacity', 0); + } } public componentDidUpdate(): void { @@ -111,45 +109,8 @@ 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) { + private _showTooltip = (text: string | number, evt: any) => { + if (text !== null && text !== undefined && this._tooltip) { this._tooltip.style('opacity', 0.9); this._tooltip .html(text) diff --git a/packages/react-charting/src/utilities/utilities.ts b/packages/react-charting/src/utilities/utilities.ts index 0260dff4beeaf2..7089e8d86072df 100644 --- a/packages/react-charting/src/utilities/utilities.ts +++ b/packages/react-charting/src/utilities/utilities.ts @@ -1076,8 +1076,10 @@ export function rotateXAxisLabels(rotateLabelProps: IRotateLabelProps) { return Math.floor(maxHeight / 1.414); // Compute maxHeight/tanInverse(45) to get the vertical height of labels. } -export function wrapTextInsideDonut(selectorClass: string, maxWidth: number) { +export function wrapTextInsideDonut(selectorClass: string, maxWidth: number): string { let idx: number = 0; + let value: string = ''; + d3SelectAll(`.${selectorClass}`).each(function () { const text = d3Select(this); const words = text.text().split(/\s+/).reverse(); @@ -1086,6 +1088,8 @@ export function wrapTextInsideDonut(selectorClass: string, maxWidth: number) { let lineNumber: number = 0; const lineHeight = 1.1; // ems const y = text.attr('y'); + const ellipsis: string = '...'; + let isTruncationRequired: boolean = false; let tspan = text .text(null) @@ -1095,22 +1099,127 @@ export function wrapTextInsideDonut(selectorClass: string, maxWidth: number) { .attr('y', y) .attr('dy', lineNumber++ * lineHeight + 'em'); + // tspanEllipsis is used for checking if truncation is required vertically/horizontally + // before appending the text to the inner donut text + let tspanEllipsis = d3Select('#Donut_center_text').text(null).append('tspan').attr('opacity', 0); + + // Determine the ellipsis length for word truncation. + tspanEllipsis.text(ellipsis); + const ellipsisLength = tspanEllipsis.node()!.getComputedTextLength(); + tspanEllipsis.text(null); + + // Value concatinates and saves the final truncated string and returns it back to the donut chart + // to handle mouse over and mouse out scenarios + value = ''; + while ((word = words.pop()!)) { line.push(word); tspan.text(line.join(' ') + ' '); - if (tspan.node()!.getComputedTextLength() > maxWidth && line.length > 1) { - line.pop(); - tspan.text(line.join(' ') + ' '); - line = [word]; - tspan = text - .append('tspan') - .attr('id', `WordBreakId-${idx}-${lineNumber}`) - .attr('x', 0) - .attr('y', y) - .attr('dy', lineNumber++ * lineHeight + 'em') - .text(word); + tspanEllipsis.text(line.join(' ') + ' '); + + // Determine if wrapping is required + if (tspan.node()!.getComputedTextLength() > maxWidth - ellipsisLength && line.length > 1) { + while (tspan.node()!.getComputedTextLength() > maxWidth - ellipsisLength) { + line.pop(); + tspan.text(line.join(' ') + ' '); + tspanEllipsis.text(line.join(' ') + ' '); + } + // Determine if truncation is required vertically + // If truncation is not required vertically, append a new line while taking care of horizontal truncation + if (tspan.node()!.getBoundingClientRect().y < maxWidth) { + line = [word]; + tspanEllipsis.text(word); + + // Determine if truncation is appending the text exceeds maximum width vertically or horizontally + while ( + tspanEllipsis.node()!.getComputedTextLength() > maxWidth - ellipsisLength || + tspanEllipsis.node()!.getBoundingClientRect().y > maxWidth + ) { + word = line.pop()!; + word = word.slice(0, -1); + line = [word]; + tspanEllipsis.text(word); + isTruncationRequired = true; + } + + // If after truncation, the word becomes empty, append the ellipsis to the last line + if (word.length === 0) { + tspan.text(tspan.text().trim() + ellipsis); + value = value.trim() + ellipsis; + break; + } + // Trim whitespaces if any + word = word.trim(); + + // Append the ellipsis only if the word was truncated and word is not the last word in the sentence. + if (isTruncationRequired && !isTextTruncated(word)) { + // Append '.' only as much required + while (!isTextTruncated(word)) { + word = word + '.'; + } + } + tspan = text + .append('tspan') + .attr('id', `WordBreakId-${idx}-${lineNumber}`) + .attr('x', 0) + .attr('y', y) + .attr('dy', lineNumber++ * lineHeight + 'em') + .text(word); + tspanEllipsis = d3Select('#Donut_center_text') + .append('tspan') + .attr('id', `WordBreakId-${idx}-${lineNumber}`) + .attr('x', 0) + .attr('y', y) + .attr('dy', lineNumber++ * lineHeight + 'em') + .text(word); + value += word + ' '; + + // If truncation was done either verticaly or horizontally, break + if (isTruncationRequired) { + tspanEllipsis.text(null); + break; + } + } else { + // If truncation is required vertically, append ellipsis and break + tspan.text(line.join(' ') + ellipsis); + value += ellipsis; + break; + } + } else { + // If there is just 1 line which exceeds the max width horizontally, + // no wrapping required, only truncate + tspanEllipsis.text(tspanEllipsis.text().trim()); + tspan.text(tspan.text().trim()); + while (tspanEllipsis.node()!.getComputedTextLength() > maxWidth - ellipsisLength) { + word = line.pop()!.trim(); + word = word.slice(0, -1); + line = [word]; + tspanEllipsis.text(word); + isTruncationRequired = true; + } + // Trim whitespaces if any + word = word.trim(); + // Append the ellipsis only if the word was truncated + if (isTruncationRequired && !isTextTruncated(word)) { + // Append '.' only as much required + while (!isTextTruncated(word)) { + word = word + '.'; + } + value += word; + line = [word]; + tspan.text(word); + break; + } + // If no truncation is required + value += word + ' '; } } + tspanEllipsis.text(null); idx += 1; }); + return value.trim(); +} + +export function isTextTruncated(text: string): boolean { + return text.slice(-3) === '...'; } From 322ca58f0ad76dd0d41942a5e2d6e635e464a491 Mon Sep 17 00:00:00 2001 From: srmukher Date: Mon, 6 Feb 2023 11:26:51 +0530 Subject: [PATCH 3/6] Making the tooltip part of the donut chart svg instead of the body --- .../src/components/DonutChart/Arc/Arc.tsx | 39 +------------------ .../components/DonutChart/DonutChart.base.tsx | 34 ++++++++++++++++ .../DonutChart/DonutChart.styles.ts | 20 ++++++++++ .../components/DonutChart/DonutChart.types.ts | 10 +++++ 4 files changed, 66 insertions(+), 37 deletions(-) diff --git a/packages/react-charting/src/components/DonutChart/Arc/Arc.tsx b/packages/react-charting/src/components/DonutChart/Arc/Arc.tsx index 67381e12c510ad..25809f9715fbac 100644 --- a/packages/react-charting/src/components/DonutChart/Arc/Arc.tsx +++ b/packages/react-charting/src/components/DonutChart/Arc/Arc.tsx @@ -1,12 +1,11 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import * as React from 'react'; import * as shape from 'd3-shape'; -import { classNamesFunction, getId } from '@fluentui/react/lib/Utilities'; +import { classNamesFunction } from '@fluentui/react/lib/Utilities'; 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 { @@ -22,7 +21,6 @@ export class Arc extends React.Component { public state: {} = {}; - private _tooltip: any; private _classNames: IProcessedStyleSet; private currentRef = React.createRef(); @@ -72,14 +70,7 @@ export class Arc extends React.Component { role="img" /> - + {this.props.valueInsideDonut} @@ -87,16 +78,6 @@ export class Arc extends React.Component { ); } - public componentDidMount(): void { - if (!this._tooltip) { - this._tooltip = d3Select('body') - .append('div') - .attr('id', getId('_Donut_tooltip_')) - .attr('class', this._classNames.tooltip!) - .style('opacity', 0); - } - } - public componentDidUpdate(): void { const { href } = this.props; const getClassNames = classNamesFunction(); @@ -109,22 +90,6 @@ export class Arc extends React.Component { wrapTextInsideDonut(classNames.insideDonutString, this.props.innerRadius! * 2 - TEXT_PADDING); } - private _showTooltip = (text: string | number, evt: any) => { - if (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/DonutChart.base.tsx b/packages/react-charting/src/components/DonutChart/DonutChart.base.tsx index 3ac925f9cb9fba..bb7aa5ed32b04e 100644 --- a/packages/react-charting/src/components/DonutChart/DonutChart.base.tsx +++ b/packages/react-charting/src/components/DonutChart/DonutChart.base.tsx @@ -38,6 +38,9 @@ export class DonutChartBase extends React.Component, @@ -70,6 +73,8 @@ export class DonutChartBase extends React.Component
+
this._setViewBox(node)} > + { + this._tooltip = document.getElementById(this._tooltipId); + if (this._tooltip) { + this._tooltip!.innerHTML = text.toString(); + this._tooltip!.style.display = 'block'; + this._tooltip!.style.opacity = '0.9'; + this._tooltip!.style.left = evt.pageX + 'px'; + this._tooltip!.style.top = evt.pageY - 28 + 'px'; + } + }; + + private _hideTooltip = (evt: any) => { + if (this._tooltip) { + this._tooltip = document.getElementById(this._tooltipId); + this._tooltip!.style.display = 'none'; + } + }; } diff --git a/packages/react-charting/src/components/DonutChart/DonutChart.styles.ts b/packages/react-charting/src/components/DonutChart/DonutChart.styles.ts index 961be08edea9b2..af0b6124cd3b77 100644 --- a/packages/react-charting/src/components/DonutChart/DonutChart.styles.ts +++ b/packages/react-charting/src/components/DonutChart/DonutChart.styles.ts @@ -26,5 +26,25 @@ export const getStyles = (props: IDonutChartStyleProps): IDonutChartStyles => { paddingTop: '16px', width: `${width}px`, }, + tooltipContainer: { + width: '100%', + height: '100%', + textAlign: 'center', + fill: 'transparent', + display: 'flex', + flexDirection: 'column', + }, + tooltip: { + ...theme.fonts.medium, + display: 'flex', + flexDirection: 'column', + padding: '8px', + position: 'absolute', + textAlign: 'center', + top: '0px', + background: theme.semanticColors.bodyBackground, + borderRadius: '2px', + pointerEvents: 'none', + }, }; }; diff --git a/packages/react-charting/src/components/DonutChart/DonutChart.types.ts b/packages/react-charting/src/components/DonutChart/DonutChart.types.ts index e3b723f21ba459..7a11486a3f45f0 100644 --- a/packages/react-charting/src/components/DonutChart/DonutChart.types.ts +++ b/packages/react-charting/src/components/DonutChart/DonutChart.types.ts @@ -59,4 +59,14 @@ export interface IDonutChartStyles { * Style for the legend container. */ legendContainer: IStyle; + + /** + * Style for the tool tip container + */ + tooltipContainer?: IStyle; + + /** + * Style for the tool tip + */ + tooltip?: IStyle; } From 62a5b7f87539c9d9fa9d82d5a9dce2e5dd81f124 Mon Sep 17 00:00:00 2001 From: srmukher Date: Mon, 6 Feb 2023 11:29:45 +0530 Subject: [PATCH 4/6] Adding interactive tests for dynamic donut chart inner text --- .../DonutChart/DonutChart.Dynamic.Example.tsx | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/packages/react-examples/src/react-charting/DonutChart/DonutChart.Dynamic.Example.tsx b/packages/react-examples/src/react-charting/DonutChart/DonutChart.Dynamic.Example.tsx index 5df0d0d8a7ade9..892e2d4cd96953 100644 --- a/packages/react-examples/src/react-charting/DonutChart/DonutChart.Dynamic.Example.tsx +++ b/packages/react-examples/src/react-charting/DonutChart/DonutChart.Dynamic.Example.tsx @@ -5,6 +5,7 @@ import { DefaultButton } from '@fluentui/react/lib/Button'; export interface IExampleState { dynamicData: IChartDataPoint[]; + dynamicInnerText: string; } export class DonutChartDynamicExample extends React.Component { @@ -21,6 +22,16 @@ export class DonutChartDynamicExample extends React.Component +
); } @@ -83,6 +98,12 @@ export class DonutChartDynamicExample extends React.Component { public state: {} = {}; - private _classNames: IProcessedStyleSet; private currentRef = React.createRef(); public static getDerivedStateFromProps(nextProps: Readonly): Partial | null { @@ -40,7 +38,7 @@ export class Arc extends React.Component { public render(): JSX.Element { const { arc, href, focusedArcId } = this.props; const getClassNames = classNamesFunction(); - this._classNames = getClassNames(getStyles, { + const classNames = getClassNames(getStyles, { color: this.props.color, href: href!, theme: this.props.theme!, @@ -52,13 +50,13 @@ export class Arc extends React.Component { return ( {!!focusedArcId && focusedArcId === id && ( - + )} { aria-label={this._getAriaLabel()} role="img" /> - - - {this.props.valueInsideDonut} + + + {this.props.valueInsideDonut!} diff --git a/packages/react-charting/src/utilities/utilities.ts b/packages/react-charting/src/utilities/utilities.ts index 7089e8d86072df..2813290ca2eb03 100644 --- a/packages/react-charting/src/utilities/utilities.ts +++ b/packages/react-charting/src/utilities/utilities.ts @@ -1130,7 +1130,8 @@ export function wrapTextInsideDonut(selectorClass: string, maxWidth: number): st line = [word]; tspanEllipsis.text(word); - // Determine if truncation is appending the text exceeds maximum width vertically or horizontally + // Determine if truncation is appending the text exceeds maximum height vertically or + // maximum width horizontally. In this case, maximum height vertically is same as maxWidth while ( tspanEllipsis.node()!.getComputedTextLength() > maxWidth - ellipsisLength || tspanEllipsis.node()!.getBoundingClientRect().y > maxWidth From b9e2c85935e41376c600d083d0bc4035921f88fb Mon Sep 17 00:00:00 2001 From: srmukher Date: Thu, 3 Aug 2023 23:12:23 +0530 Subject: [PATCH 6/6] Added few more changes --- .../src/components/DonutChart/Arc/Arc.types.ts | 5 ----- .../src/components/DonutChart/DonutChart.base.tsx | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) 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 f449e4471e6e91..3ff17a2680b033 100644 --- a/packages/react-charting/src/components/DonutChart/Arc/Arc.types.ts +++ b/packages/react-charting/src/components/DonutChart/Arc/Arc.types.ts @@ -139,11 +139,6 @@ export interface IArcStyles { */ focusRing: IStyle; - /** - * Style for tool tip - */ - tooltip?: IStyle; - /** * Style for overflow center text container */ diff --git a/packages/react-charting/src/components/DonutChart/DonutChart.base.tsx b/packages/react-charting/src/components/DonutChart/DonutChart.base.tsx index e404aa97ce200e..87b4236d130ee9 100644 --- a/packages/react-charting/src/components/DonutChart/DonutChart.base.tsx +++ b/packages/react-charting/src/components/DonutChart/DonutChart.base.tsx @@ -138,6 +138,7 @@ export class DonutChartBase extends React.Component (this._tooltip = element)} /> { - this._tooltip = document.getElementById(this._tooltipId); if (this._tooltip) { this._tooltip!.innerHTML = text ? text.toString() : ''; this._tooltip!.style.display = 'block';