@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opendata-ai/openchart-engine",
3
- "version": "6.28.4",
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.4",
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('preserves luminance ordering in dark mode with custom color stops', () => {
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', () => {
@@ -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);