@opendata-ai/openchart-engine 6.28.4 → 6.28.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +0 -21
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/tables/__tests__/heatmap.test.ts +25 -4
- package/src/tables/heatmap.ts +0 -28
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opendata-ai/openchart-engine",
|
|
3
|
-
"version": "6.28.
|
|
3
|
+
"version": "6.28.5",
|
|
4
4
|
"description": "Headless compiler for openchart: spec validation, data compilation, scales, and layout",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Riley Hilliard",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"typecheck": "tsc --noEmit"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@opendata-ai/openchart-core": "6.28.
|
|
51
|
+
"@opendata-ai/openchart-core": "6.28.5",
|
|
52
52
|
"d3-array": "^3.2.0",
|
|
53
53
|
"d3-format": "^3.1.2",
|
|
54
54
|
"d3-interpolate": "^3.0.0",
|
|
@@ -112,7 +112,7 @@ describe('computeHeatmapColors', () => {
|
|
|
112
112
|
}
|
|
113
113
|
});
|
|
114
114
|
|
|
115
|
-
it('
|
|
115
|
+
it('high values are more visually prominent than low values in both modes', () => {
|
|
116
116
|
const col: ColumnConfig = {
|
|
117
117
|
key: 'value',
|
|
118
118
|
heatmap: { palette: ['#fca5a5', '#c44e52'], domain: [0, 100] },
|
|
@@ -122,16 +122,37 @@ describe('computeHeatmapColors', () => {
|
|
|
122
122
|
const lightColors = computeHeatmapColors(data, col, lightTheme, false);
|
|
123
123
|
const darkColors = computeHeatmapColors(data, col, darkTheme, true);
|
|
124
124
|
|
|
125
|
-
// Low value should have a visually lighter/less intense background than high value
|
|
126
|
-
// in both modes. Extract the backgrounds and compare luminance ordering.
|
|
127
125
|
const lightLowBg = lightColors.get(0)!.backgroundColor!;
|
|
128
126
|
const lightHighBg = lightColors.get(4)!.backgroundColor!;
|
|
129
127
|
const darkLowBg = darkColors.get(0)!.backgroundColor!;
|
|
130
128
|
const darkHighBg = darkColors.get(4)!.backgroundColor!;
|
|
131
129
|
|
|
132
|
-
// The low-value cell and high-value cell should have different backgrounds in both modes
|
|
133
130
|
expect(lightLowBg).not.toBe(lightHighBg);
|
|
134
131
|
expect(darkLowBg).not.toBe(darkHighBg);
|
|
132
|
+
|
|
133
|
+
function parseLum(color: string): number {
|
|
134
|
+
const rgbMatch = /rgb\(\s*(\d+),\s*(\d+),\s*(\d+)\s*\)/.exec(color);
|
|
135
|
+
if (rgbMatch) {
|
|
136
|
+
const [r, g, b] = [rgbMatch[1], rgbMatch[2], rgbMatch[3]].map((c) => {
|
|
137
|
+
const v = Number(c) / 255;
|
|
138
|
+
return v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4;
|
|
139
|
+
});
|
|
140
|
+
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
141
|
+
}
|
|
142
|
+
const hexMatch = /^#?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(color);
|
|
143
|
+
if (!hexMatch) return 0;
|
|
144
|
+
const [r, g, b] = [hexMatch[1], hexMatch[2], hexMatch[3]].map((c) => {
|
|
145
|
+
const v = Number.parseInt(c, 16) / 255;
|
|
146
|
+
return v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4;
|
|
147
|
+
});
|
|
148
|
+
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Light mode: high values get darker/more saturated bg (lower luminance = more prominent)
|
|
152
|
+
expect(parseLum(lightLowBg)).toBeGreaterThan(parseLum(lightHighBg));
|
|
153
|
+
|
|
154
|
+
// Dark mode: high values get brighter bg (higher luminance = more prominent on dark bg)
|
|
155
|
+
expect(parseLum(darkHighBg)).toBeGreaterThan(parseLum(darkLowBg));
|
|
135
156
|
});
|
|
136
157
|
|
|
137
158
|
it('supports array of color stops as palette', () => {
|
package/src/tables/heatmap.ts
CHANGED
|
@@ -11,17 +11,6 @@ import { interpolateRgb } from 'd3-interpolate';
|
|
|
11
11
|
import { scaleSequential } from 'd3-scale';
|
|
12
12
|
import { accessibleTextColor } from './utils';
|
|
13
13
|
|
|
14
|
-
/** WCAG relative luminance from a hex color string. */
|
|
15
|
-
function relativeLuminance(hex: string): number {
|
|
16
|
-
const m = /^#?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(hex);
|
|
17
|
-
if (!m) return 0;
|
|
18
|
-
const [r, g, b] = [m[1], m[2], m[3]].map((c) => {
|
|
19
|
-
const v = Number.parseInt(c, 16) / 255;
|
|
20
|
-
return v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4;
|
|
21
|
-
});
|
|
22
|
-
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
14
|
/**
|
|
26
15
|
* Build an interpolator from an array of color stops.
|
|
27
16
|
* Uses d3-interpolate for smooth color transitions.
|
|
@@ -111,24 +100,7 @@ export function computeHeatmapColors(
|
|
|
111
100
|
if (darkMode) {
|
|
112
101
|
const lightBg = '#ffffff';
|
|
113
102
|
const darkBg = theme.colors.background;
|
|
114
|
-
const originalStops = stops;
|
|
115
103
|
stops = stops.map((c) => adaptColorForDarkMode(c, lightBg, darkBg));
|
|
116
|
-
|
|
117
|
-
// adaptColorForDarkMode preserves contrast ratios independently per stop,
|
|
118
|
-
// which can invert the luminance ordering (light-to-dark becomes dark-to-light).
|
|
119
|
-
// Detect this and reverse the adapted stops to preserve the intended gradient direction.
|
|
120
|
-
if (originalStops.length >= 2) {
|
|
121
|
-
const origDirection = Math.sign(
|
|
122
|
-
relativeLuminance(originalStops[originalStops.length - 1]) -
|
|
123
|
-
relativeLuminance(originalStops[0]),
|
|
124
|
-
);
|
|
125
|
-
const adaptedDirection = Math.sign(
|
|
126
|
-
relativeLuminance(stops[stops.length - 1]) - relativeLuminance(stops[0]),
|
|
127
|
-
);
|
|
128
|
-
if (origDirection !== 0 && adaptedDirection !== 0 && origDirection !== adaptedDirection) {
|
|
129
|
-
stops = stops.slice().reverse();
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
104
|
}
|
|
133
105
|
|
|
134
106
|
const interpolator = interpolatorFromStops(stops);
|