@opendata-ai/openchart-engine 6.28.4 → 6.28.6

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.6",
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.6",
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('custom palette arrays are not adapted in dark mode', () => {
116
116
  const col: ColumnConfig = {
117
117
  key: 'value',
118
118
  heatmap: { palette: ['#fca5a5', '#c44e52'], domain: [0, 100] },
@@ -122,16 +122,14 @@ 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
- expect(lightLowBg).not.toBe(lightHighBg);
134
- expect(darkLowBg).not.toBe(darkHighBg);
130
+ // Custom palettes produce the same colors in both modes
131
+ expect(darkLowBg).toBe(lightLowBg);
132
+ expect(darkHighBg).toBe(lightHighBg);
135
133
  });
136
134
 
137
135
  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.
@@ -106,29 +95,16 @@ export function computeHeatmapColors(
106
95
  domain = [min, max];
107
96
  }
108
97
 
109
- // Resolve palette and build scale
98
+ // Resolve palette and build scale.
99
+ // Only adapt theme-derived palettes for dark mode. Custom color arrays
100
+ // are used as-is since the author chose them for a reason, and
101
+ // adaptColorForDarkMode inverts the perceived intensity ordering
102
+ // (light pink -> dark red becomes dark red -> pink on dark backgrounds).
110
103
  let stops = resolvePalette(config.palette, theme);
111
- if (darkMode) {
104
+ if (darkMode && !Array.isArray(config.palette)) {
112
105
  const lightBg = '#ffffff';
113
106
  const darkBg = theme.colors.background;
114
- const originalStops = stops;
115
107
  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
108
  }
133
109
 
134
110
  const interpolator = interpolatorFromStops(stops);