@opendata-ai/openchart-engine 6.28.2 → 6.28.4
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opendata-ai/openchart-engine",
|
|
3
|
-
"version": "6.28.
|
|
3
|
+
"version": "6.28.4",
|
|
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.4",
|
|
52
52
|
"d3-array": "^3.2.0",
|
|
53
53
|
"d3-format": "^3.1.2",
|
|
54
54
|
"d3-interpolate": "^3.0.0",
|
|
@@ -112,6 +112,28 @@ describe('computeHeatmapColors', () => {
|
|
|
112
112
|
}
|
|
113
113
|
});
|
|
114
114
|
|
|
115
|
+
it('preserves luminance ordering in dark mode with custom color stops', () => {
|
|
116
|
+
const col: ColumnConfig = {
|
|
117
|
+
key: 'value',
|
|
118
|
+
heatmap: { palette: ['#fca5a5', '#c44e52'], domain: [0, 100] },
|
|
119
|
+
};
|
|
120
|
+
const lightTheme = getTheme(false);
|
|
121
|
+
const darkTheme = getTheme(true);
|
|
122
|
+
const lightColors = computeHeatmapColors(data, col, lightTheme, false);
|
|
123
|
+
const darkColors = computeHeatmapColors(data, col, darkTheme, true);
|
|
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
|
+
const lightLowBg = lightColors.get(0)!.backgroundColor!;
|
|
128
|
+
const lightHighBg = lightColors.get(4)!.backgroundColor!;
|
|
129
|
+
const darkLowBg = darkColors.get(0)!.backgroundColor!;
|
|
130
|
+
const darkHighBg = darkColors.get(4)!.backgroundColor!;
|
|
131
|
+
|
|
132
|
+
// The low-value cell and high-value cell should have different backgrounds in both modes
|
|
133
|
+
expect(lightLowBg).not.toBe(lightHighBg);
|
|
134
|
+
expect(darkLowBg).not.toBe(darkHighBg);
|
|
135
|
+
});
|
|
136
|
+
|
|
115
137
|
it('supports array of color stops as palette', () => {
|
|
116
138
|
const col: ColumnConfig = {
|
|
117
139
|
key: 'value',
|
package/src/tables/heatmap.ts
CHANGED
|
@@ -11,6 +11,17 @@ 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
|
+
|
|
14
25
|
/**
|
|
15
26
|
* Build an interpolator from an array of color stops.
|
|
16
27
|
* Uses d3-interpolate for smooth color transitions.
|
|
@@ -100,7 +111,24 @@ export function computeHeatmapColors(
|
|
|
100
111
|
if (darkMode) {
|
|
101
112
|
const lightBg = '#ffffff';
|
|
102
113
|
const darkBg = theme.colors.background;
|
|
114
|
+
const originalStops = stops;
|
|
103
115
|
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
|
+
}
|
|
104
132
|
}
|
|
105
133
|
|
|
106
134
|
const interpolator = interpolatorFromStops(stops);
|
|
@@ -297,18 +297,20 @@ describe('compileTileMap', () => {
|
|
|
297
297
|
});
|
|
298
298
|
|
|
299
299
|
describe('dimensions', () => {
|
|
300
|
-
it('
|
|
300
|
+
it('width matches compile options, height fits content', () => {
|
|
301
301
|
const result = compileTileMap(basicSpec, defaultOptions);
|
|
302
302
|
|
|
303
303
|
expect(result.width).toBe(600);
|
|
304
|
-
expect(result.height).
|
|
304
|
+
expect(result.height).toBeGreaterThan(0);
|
|
305
|
+
expect(result.height).toBeLessThanOrEqual(400);
|
|
305
306
|
});
|
|
306
307
|
|
|
307
308
|
it('works with different container sizes', () => {
|
|
308
309
|
const result = compileTileMap(basicSpec, { width: 800, height: 600 });
|
|
309
310
|
|
|
310
311
|
expect(result.width).toBe(800);
|
|
311
|
-
expect(result.height).
|
|
312
|
+
expect(result.height).toBeGreaterThan(0);
|
|
313
|
+
expect(result.height).toBeLessThanOrEqual(600);
|
|
312
314
|
});
|
|
313
315
|
});
|
|
314
316
|
|
|
@@ -329,6 +329,15 @@ export function compileTileMap(spec: unknown, options: CompileOptions): TileMapL
|
|
|
329
329
|
// 15. Resolve animation
|
|
330
330
|
const resolvedAnimation: ResolvedAnimation | undefined = resolveAnimation(tilemapSpec.animation);
|
|
331
331
|
|
|
332
|
+
// Tight content height: tiles + legend + chrome + padding
|
|
333
|
+
const contentHeight =
|
|
334
|
+
tileGridOffsetY +
|
|
335
|
+
tilePositions.gridHeight +
|
|
336
|
+
legendGap +
|
|
337
|
+
legendTotalHeight +
|
|
338
|
+
chrome.bottomHeight +
|
|
339
|
+
padding;
|
|
340
|
+
|
|
332
341
|
return {
|
|
333
342
|
area: fullArea,
|
|
334
343
|
chrome,
|
|
@@ -338,7 +347,7 @@ export function compileTileMap(spec: unknown, options: CompileOptions): TileMapL
|
|
|
338
347
|
a11y,
|
|
339
348
|
theme,
|
|
340
349
|
width: options.width,
|
|
341
|
-
height:
|
|
350
|
+
height: contentHeight,
|
|
342
351
|
animation: resolvedAnimation,
|
|
343
352
|
watermark,
|
|
344
353
|
measureText:
|