From 6bd14a98fb5bec1eb26dfbada654fc1588249fa8 Mon Sep 17 00:00:00 2001 From: krkshitij <110246001+krkshitij@users.noreply.github.com> Date: Thu, 2 Jan 2025 07:49:40 +0530 Subject: [PATCH] fix(react-charting): resolve css variables in svg image data (#33538) --- ...-db3ac682-461d-4b7b-8f24-eeb42fe5e64b.json | 7 +++++++ .../DeclarativeChart/DeclarativeChart.tsx | 2 +- .../DeclarativeChart/imageExporter.ts | 21 +++++++++++++++---- 3 files changed, 25 insertions(+), 5 deletions(-) create mode 100644 change/@fluentui-react-charting-db3ac682-461d-4b7b-8f24-eeb42fe5e64b.json diff --git a/change/@fluentui-react-charting-db3ac682-461d-4b7b-8f24-eeb42fe5e64b.json b/change/@fluentui-react-charting-db3ac682-461d-4b7b-8f24-eeb42fe5e64b.json new file mode 100644 index 00000000000000..36181c5db0aa9a --- /dev/null +++ b/change/@fluentui-react-charting-db3ac682-461d-4b7b-8f24-eeb42fe5e64b.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "fix: resolve css variables in svg image data", + "packageName": "@fluentui/react-charting", + "email": "kumarkshitij@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/packages/charts/react-charting/src/components/DeclarativeChart/DeclarativeChart.tsx b/packages/charts/react-charting/src/components/DeclarativeChart/DeclarativeChart.tsx index 4ddf1a198b19d6..63f472a281be77 100644 --- a/packages/charts/react-charting/src/components/DeclarativeChart/DeclarativeChart.tsx +++ b/packages/charts/react-charting/src/components/DeclarativeChart/DeclarativeChart.tsx @@ -127,7 +127,7 @@ export const DeclarativeChart: React.FunctionComponent = const exportAsImage = React.useCallback( (opts?: IImageExportOptions) => { return toImage(chartRef.current?.chartContainer, { - background: theme.palette.white, + background: theme.semanticColors.bodyBackground, ...opts, }); }, diff --git a/packages/charts/react-charting/src/components/DeclarativeChart/imageExporter.ts b/packages/charts/react-charting/src/components/DeclarativeChart/imageExporter.ts index 3bad277b20a08b..cab5167bf6ccb2 100644 --- a/packages/charts/react-charting/src/components/DeclarativeChart/imageExporter.ts +++ b/packages/charts/react-charting/src/components/DeclarativeChart/imageExporter.ts @@ -17,7 +17,8 @@ export function toImage(chartContainer?: HTMLElement | null, opts: IImageExportO } try { - const background = 'white'; // Background is coming as --var(xxx) when used with v8 wrapper in v9 + const background = + typeof opts.background === 'string' ? resolveCSSVariables(chartContainer, opts.background) : 'transparent'; const svg = toSVG(chartContainer, background); const svgData = new XMLSerializer().serializeToString(svg.node); @@ -65,7 +66,7 @@ function toSVG(chartContainer: HTMLElement, background: string) { const svgElements = svg.getElementsByTagName('*'); const styleSheets = document.styleSheets; - const styleRules: string[] = []; + let styleRules: string = ''; for (let i = svgElements.length - 1; i--; ) { svgElements[i].classList.forEach(className => { @@ -81,14 +82,15 @@ function toSVG(chartContainer: HTMLElement, background: string) { const hasClassName = selectorText.split(' ').some(word => classNames.has(word)); if (hasClassName) { - styleRules.push(rules[j].cssText); + styleRules += rules[j].cssText + ' '; } } } } + styleRules = resolveCSSVariables(chartContainer, styleRules); const xmlDocument = new DOMParser().parseFromString('', 'image/svg+xml'); - const styleNode = xmlDocument.createCDATASection(styleRules.join(' ')); + const styleNode = xmlDocument.createCDATASection(styleRules); clonedSvg.insert('defs', ':first-child').append('style').attr('type', 'text/css').node()!.appendChild(styleNode); clonedSvg.attr('width', w1).attr('height', h1).attr('viewBox', `0 0 ${w1} ${h1}`); @@ -161,12 +163,14 @@ function cloneLegendsToSVG(chartContainer: HTMLElement, svgWidth: number, svgHei textOffset = 8; } + const { color: textColor } = getComputedStyle(legendText!); legendItem .append('text') .attr('x', legendX + textOffset) .attr('y', svgHeight + legendY + 8) .attr('dominant-baseline', 'hanging') .attr('class', legendText!.getAttribute('class')) + .attr('fill', textColor) .text(legendText!.textContent); legendX += legendWidth; } @@ -192,6 +196,15 @@ function cloneLegendsToSVG(chartContainer: HTMLElement, svgWidth: number, svgHei }; } +const cssVarRegExp = /var\((--[a-zA-Z0-9\-]+)\)/g; + +function resolveCSSVariables(chartContainer: HTMLElement, styleRules: string) { + const containerStyles = getComputedStyle(chartContainer); + return styleRules.replace(cssVarRegExp, (match, group1) => { + return containerStyles.getPropertyValue(group1); + }); +} + function svgToPng(svgDataUrl: string, opts: IImageExportOptions = {}): Promise { return new Promise((resolve, reject) => { const scale = opts.scale || 1;