Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(react-charting): Heatmap text color based on Contrast Ratio #33659

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
Copy link
Collaborator

@fabricteam fabricteam Jan 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🕵🏾‍♀️ visual regressions to review in the fluentuiv8 Visual Regression Report

Keytip 1 screenshots
Image Name Diff(in Pixels) Image Type
Keytip.Offset.chromium.png 121 Changed
react-charting-AreaChart 3 screenshots
Image Name Diff(in Pixels) Image Type
react-charting-AreaChart.Basic RTL.chromium.png 841 Changed
react-charting-AreaChart.Basic Dark Mode.chromium.png 847 Changed
react-charting-AreaChart.Basic.chromium.png 847 Changed
react-charting-DonutChart 3 screenshots
Image Name Diff(in Pixels) Image Type
react-charting-DonutChart.Basic.chromium.png 634 Changed
react-charting-DonutChart.Basic RTL.chromium.png 642 Changed
react-charting-DonutChart.Basic Dark Mode.chromium.png 634 Changed
react-charting-HeatMapChart 3 screenshots
Image Name Diff(in Pixels) Image Type
react-charting-HeatMapChart.Basic RTL.chromium.png 1354 Changed
react-charting-HeatMapChart.Basic Dark Mode.chromium.png 1352 Changed
react-charting-HeatMapChart.Basic.chromium.png 1352 Changed
react-charting-HorizontalBarChart 6 screenshots
Image Name Diff(in Pixels) Image Type
react-charting-HorizontalBarChart.With Axis Dark Mode.chromium.png 843 Changed
react-charting-HorizontalBarChart.Basic.chromium.png 929 Changed
react-charting-HorizontalBarChart.Basic Dark Mode.chromium.png 929 Changed
react-charting-HorizontalBarChart.Basic RTL.chromium.png 939 Changed
react-charting-HorizontalBarChart.With Axis RTL.chromium.png 836 Changed
react-charting-HorizontalBarChart.With_Axis.chromium.png 843 Changed
react-charting-PieChart 3 screenshots
Image Name Diff(in Pixels) Image Type
react-charting-PieChart.Basic RTL.chromium.png 478 Changed
react-charting-PieChart.Basic Dark Mode.chromium.png 474 Changed
react-charting-PieChart.Basic.chromium.png 474 Changed
react-charting-VerticalBarChart 6 screenshots
Image Name Diff(in Pixels) Image Type
react-charting-VerticalBarChart.Basic Secondary Y Axis Dark Mode.chromium.png 843 Changed
react-charting-VerticalBarChart.Basic - Secondary Y Axis.chromium.png 843 Changed
react-charting-VerticalBarChart.Basic Secondary Y Axis RTL.chromium.png 836 Changed
react-charting-VerticalBarChart.Stacked RTL.chromium.png 836 Changed
react-charting-VerticalBarChart.Stacked.chromium.png 843 Changed
react-charting-VerticalBarChart.Stacked Dark Mode.chromium.png 843 Changed

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why did so many screenshots change

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are using different version of d3-color now (as per yarn.lock file). I can see that reason only.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no we are not. it is mapping to same 3.1.0.

Looks like something else has broken.
image

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems unrelated to my changes. let me take a master pull.

Copy link
Collaborator

@fabricteam fabricteam Jan 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🕵🏾‍♀️ visual regressions to review in the fluentuiv9 Visual Regression Report

Avatar Converged 1 screenshots
Image Name Diff(in Pixels) Image Type
Avatar Converged.badgeMask.chromium.png 5 Changed
Drawer 1 screenshots
Image Name Diff(in Pixels) Image Type
Drawer.Full Overlay RTL.chromium.png 1168 Changed

"type": "patch",
"comment": "Heatmap text color based on contrast ratio",
"packageName": "@fluentui/react-charting",
"email": "[email protected]",
"dependentChangeType": "patch"
}
2 changes: 2 additions & 0 deletions packages/charts/react-charting/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"@types/d3-shape": "^3.0.0",
"@types/d3-time-format": "^3.0.0",
"@types/d3-time": "^3.0.0",
"@types/d3-color": "^3.0.0",
"@fluentui/set-version": "^8.2.23",
"d3-array": "^3.0.0",
"d3-axis": "^3.0.0",
Expand All @@ -63,6 +64,7 @@
"d3-shape": "^3.0.0",
"d3-time-format": "^3.0.0",
"d3-time": "^3.0.0",
"d3-color": "^3.0.0",
"tslib": "^2.1.0"
},
"peerDependencies": {
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { create as d3Create, select as d3Select, Selection } from 'd3-selection';
import { resolveCSSVariables } from '../../utilities/utilities';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dont use relative imports

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see this pattern at multiple places. Lets start fixing from here.

Copy link
Contributor Author

@Anush2303 Anush2303 Jan 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay. But is it correct to use absolute paths?


/**
* {@docCategory DeclarativeChart}
Expand Down Expand Up @@ -196,15 +197,6 @@ 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<string> {
return new Promise((resolve, reject) => {
const scale = opts.scale || 1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ import {
IDomainNRange,
domainRangeOfXStringAxis,
createStringYAxis,
resolveCSSVariables,
} from '../../utilities/utilities';
import { Target } from '@fluentui/react';
import { format as d3Format } from 'd3-format';
import { timeFormat as d3TimeFormat } from 'd3-time-format';
import { getColorContrast } from '../../utilities/colors';

type DataSet = {
dataSet: RectanglesGraphData;
Expand Down Expand Up @@ -351,6 +353,12 @@ export class HeatMapChartBase extends React.Component<IHeatMapChartProps, IHeatM
});
};

private _getInvertedTextColor = (color: string): string => {
return color === this.props.theme!.semanticColors.bodyText
? this.props.theme!.semanticColors.bodyBackground
: this.props.theme!.semanticColors.bodyText;
};

/**
* This is the function which is responsible for
* drawing the rectangle in the graph and also
Expand All @@ -374,6 +382,15 @@ export class HeatMapChartBase extends React.Component<IHeatMapChartProps, IHeatM
* data point such as x, y , value, rectText property of the rectangle
*/
const dataPointObject = this._dataSet[yAxisDataPoint][index];
let styleRules = '';
let foregroundColor = this.props.theme!.semanticColors.bodyText;
if (this.chartContainer) {
styleRules = resolveCSSVariables(this.chartContainer!, foregroundColor);
}
const contrastRatio = getColorContrast(styleRules, this._colorScale(dataPointObject.value));
if (contrastRatio < 3) {
foregroundColor = this._getInvertedTextColor(foregroundColor);
}
rectElement = (
<g
key={id}
Expand Down Expand Up @@ -401,6 +418,7 @@ export class HeatMapChartBase extends React.Component<IHeatMapChartProps, IHeatM
textAnchor={'middle'}
className={this._classNames.text}
transform={`translate(${this._xAxisScale.bandwidth() / 2}, ${this._yAxisScale.bandwidth() / 2})`}
fill={foregroundColor}
>
{convertToLocaleString(dataPointObject.rectText, this.props.culture)}
</text>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ export const getHeatMapChartStyles = (props: IHeatMapChartStyleProps): IHeatMapC
theme.fonts.medium,
{
pointerEvents: 'none',
fill: theme.palette.white,
fontWeight: FontWeights.semibold,
},
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -828,13 +828,13 @@ exports[`HeatMapChart snapshot tests should render HeatMapChart correctly with n
{
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
fill: #ffffff;
font-family: 'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif;
font-size: 14px;
font-weight: 600;
pointer-events: none;
}
dominant-baseline="middle"
fill="#323130"
text-anchor="middle"
transform="translate(143.11881188118812, 57.49009900990099)"
/>
Expand All @@ -858,13 +858,13 @@ exports[`HeatMapChart snapshot tests should render HeatMapChart correctly with n
{
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
fill: #ffffff;
font-family: 'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif;
font-size: 14px;
font-weight: 600;
pointer-events: none;
}
dominant-baseline="middle"
fill="#323130"
text-anchor="middle"
transform="translate(143.11881188118812, 57.49009900990099)"
/>
Expand All @@ -888,13 +888,13 @@ exports[`HeatMapChart snapshot tests should render HeatMapChart correctly with n
{
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
fill: #ffffff;
font-family: 'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif;
font-size: 14px;
font-weight: 600;
pointer-events: none;
}
dominant-baseline="middle"
fill="#323130"
text-anchor="middle"
transform="translate(143.11881188118812, 57.49009900990099)"
/>
Expand All @@ -918,13 +918,13 @@ exports[`HeatMapChart snapshot tests should render HeatMapChart correctly with n
{
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
fill: #ffffff;
font-family: 'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif;
font-size: 14px;
font-weight: 600;
pointer-events: none;
}
dominant-baseline="middle"
fill="#323130"
text-anchor="middle"
transform="translate(143.11881188118812, 57.49009900990099)"
/>
Expand Down Expand Up @@ -1428,13 +1428,13 @@ exports[`HeatMapChart snapshot tests should render axis labels correctly When cu
{
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
fill: #ffffff;
font-family: 'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif;
font-size: 14px;
font-weight: 600;
pointer-events: none;
}
dominant-baseline="middle"
fill="#323130"
text-anchor="middle"
transform="translate(143.11881188118812, 57.49009900990099)"
>
Expand All @@ -1460,13 +1460,13 @@ exports[`HeatMapChart snapshot tests should render axis labels correctly When cu
{
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
fill: #ffffff;
font-family: 'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif;
font-size: 14px;
font-weight: 600;
pointer-events: none;
}
dominant-baseline="middle"
fill="#323130"
text-anchor="middle"
transform="translate(143.11881188118812, 57.49009900990099)"
>
Expand All @@ -1492,13 +1492,13 @@ exports[`HeatMapChart snapshot tests should render axis labels correctly When cu
{
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
fill: #ffffff;
font-family: 'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif;
font-size: 14px;
font-weight: 600;
pointer-events: none;
}
dominant-baseline="middle"
fill="#323130"
text-anchor="middle"
transform="translate(143.11881188118812, 57.49009900990099)"
>
Expand All @@ -1524,13 +1524,13 @@ exports[`HeatMapChart snapshot tests should render axis labels correctly When cu
{
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
fill: #ffffff;
font-family: 'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif;
font-size: 14px;
font-weight: 600;
pointer-events: none;
}
dominant-baseline="middle"
fill="#323130"
text-anchor="middle"
transform="translate(143.11881188118812, 57.49009900990099)"
>
Expand Down
25 changes: 25 additions & 0 deletions packages/charts/react-charting/src/utilities/colors.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { rgb as d3Rgb } from 'd3-color';

export const DataVizPalette = {
color1: 'qualitative.1',
color2: 'qualitative.2',
Expand Down Expand Up @@ -141,3 +143,26 @@ export const getColorFromToken = (token: string, isDarkTheme: boolean = false):
}
return token;
};

//For reference to how these numbers are calculated, refer https://www.w3.org/TR/WCAG/#dfn-contrast-ratio
const rgbLrgb1 = (v: number): number => {
return v <= 0.04045 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4;
};

const rgbLrgb = ({ r, g, b }: { r: number; g: number; b: number }): { r: number; g: number; b: number } => {
return {
r: rgbLrgb1(r / 255),
g: rgbLrgb1(g / 255),
b: rgbLrgb1(b / 255),
};
};
Anush2303 marked this conversation as resolved.
Show resolved Hide resolved

const lrgbLuminance = ({ r, g, b }: { r: number; g: number; b: number }): number => {
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
};

export const getColorContrast = (c1: string, c2: string): number => {
const l1 = lrgbLuminance(rgbLrgb(d3Rgb(c1)));
const l2 = lrgbLuminance(rgbLrgb(d3Rgb(c2)));
return (Math.max(l1, l2) + 0.05) / (Math.min(l1, l2) + 0.05);
};
9 changes: 9 additions & 0 deletions packages/charts/react-charting/src/utilities/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1433,3 +1433,12 @@ export function areArraysEqual(arr1?: string[], arr2?: string[]): boolean {
}
return true;
}

const cssVarRegExp = /var\((--[a-zA-Z0-9\-]+)\)/g;

export function resolveCSSVariables(chartContainer: HTMLElement, styleRules: string) {
Anush2303 marked this conversation as resolved.
Show resolved Hide resolved
const containerStyles = getComputedStyle(chartContainer);
return styleRules.replace(cssVarRegExp, (match, group1) => {
return containerStyles.getPropertyValue(group1);
});
}
7 changes: 6 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5079,6 +5079,11 @@
dependencies:
"@types/d3-selection" "*"

"@types/d3-color@^3.0.0":
version "3.1.3"
resolved "https://registry.yarnpkg.com/@types/d3-color/-/d3-color-3.1.3.tgz#368c961a18de721da8200e80bf3943fb53136af2"
integrity sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==

"@types/d3-dsv@*":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@types/d3-dsv/-/d3-dsv-3.0.0.tgz#f3c61fb117bd493ec0e814856feb804a14cfc311"
Expand Down Expand Up @@ -9365,7 +9370,7 @@ d3-collection@^1.0.7:
resolved "https://registry.yarnpkg.com/d3-collection/-/d3-collection-1.0.7.tgz#349bd2aa9977db071091c13144d5e4f16b5b310e"
integrity sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A==

"d3-color@1 - 3", d3-color@^3.1.0:
"d3-color@1 - 3", d3-color@^3.0.0, d3-color@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-3.1.0.tgz#395b2833dfac71507f12ac2f7af23bf819de24e2"
integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==
Expand Down
Loading