Skip to content

Commit

Permalink
feat(react-charting): Heatmap text color based on Contrast Ratio (mic…
Browse files Browse the repository at this point in the history
  • Loading branch information
Anush2303 authored Jan 22, 2025
1 parent d0f533a commit af631b9
Show file tree
Hide file tree
Showing 10 changed files with 476 additions and 419 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"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/index';

/**
* {@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),
};
};

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 @@ -1426,3 +1426,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) {
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

0 comments on commit af631b9

Please sign in to comment.