@opendata-ai/openchart-engine 7.2.3 → 7.3.0
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.d.ts +84 -36
- package/dist/index.js +4013 -2872
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/__snapshots__/compile-snapshot.test.ts.snap +139 -63
- package/src/__tests__/axes.test.ts +67 -1
- package/src/__tests__/compile-chart.test.ts +6 -0
- package/src/__tests__/compile-layer.test.ts +49 -0
- package/src/__tests__/dimensions.test.ts +7 -2
- package/src/__tests__/helpers/invariants.ts +267 -0
- package/src/__tests__/known-bugs.test.ts +113 -0
- package/src/__tests__/layout-fuzz.test.ts +342 -0
- package/src/__tests__/layout-invariants.test.ts +533 -0
- package/src/__tests__/legend.test.ts +37 -0
- package/src/annotations/__tests__/compute.test.ts +8 -15
- package/src/annotations/__tests__/placement-fuzz.test.ts +256 -0
- package/src/annotations/__tests__/placement-invariants.test.ts +345 -0
- package/src/annotations/__tests__/placement.test.ts +316 -0
- package/src/annotations/collisions.ts +19 -7
- package/src/annotations/compute.ts +257 -16
- package/src/annotations/geometry.ts +73 -15
- package/src/annotations/placement.ts +588 -0
- package/src/annotations/resolve-text.ts +58 -10
- package/src/charts/__tests__/post-process.test.ts +3 -3
- package/src/charts/bar/__tests__/compute.test.ts +67 -0
- package/src/charts/bar/compute.ts +8 -1
- package/src/charts/post-process.ts +21 -7
- package/src/compile/layer.ts +31 -9
- package/src/compile.ts +85 -148
- package/src/index.ts +9 -0
- package/src/layout/__tests__/metrics.test.ts +97 -0
- package/src/layout/axes.ts +93 -1
- package/src/layout/dimensions.ts +80 -98
- package/src/layout/plan.ts +524 -0
- package/src/layout/shared.ts +44 -0
- package/src/legend/compute.ts +315 -78
- package/src/legend/wrap.ts +11 -7
- package/src/sankey/__tests__/node-label-wrap.test.ts +2 -2
- package/src/tables/__tests__/heatmap.test.ts +24 -20
- package/src/tables/heatmap.ts +25 -9
|
@@ -20,13 +20,14 @@ describe('computeHeatmapColors', () => {
|
|
|
20
20
|
const theme = getTheme();
|
|
21
21
|
const colors = computeHeatmapColors(data, column, theme, false);
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
// Domain-minimum row (value=0) is skipped so the normal row
|
|
24
|
+
// background shows through instead of an opaque low-end color.
|
|
25
|
+
expect(colors.size).toBe(4);
|
|
26
|
+
expect(colors.has(0)).toBe(false);
|
|
24
27
|
|
|
25
|
-
|
|
26
|
-
const lowBg = colors.get(0)!.backgroundColor!;
|
|
28
|
+
const lowBg = colors.get(1)!.backgroundColor!;
|
|
27
29
|
const highBg = colors.get(4)!.backgroundColor!;
|
|
28
30
|
|
|
29
|
-
// Check that they're different colors
|
|
30
31
|
expect(lowBg).not.toBe(highBg);
|
|
31
32
|
});
|
|
32
33
|
|
|
@@ -51,8 +52,8 @@ describe('computeHeatmapColors', () => {
|
|
|
51
52
|
const theme = getTheme();
|
|
52
53
|
const colors = computeHeatmapColors(data, col, theme, false);
|
|
53
54
|
|
|
54
|
-
//
|
|
55
|
-
expect(colors.size).toBe(
|
|
55
|
+
// value=0 sits at the domain minimum and is skipped
|
|
56
|
+
expect(colors.size).toBe(4);
|
|
56
57
|
});
|
|
57
58
|
|
|
58
59
|
it('values outside custom domain are clamped', () => {
|
|
@@ -63,8 +64,9 @@ describe('computeHeatmapColors', () => {
|
|
|
63
64
|
const theme = getTheme();
|
|
64
65
|
const colors = computeHeatmapColors(data, col, theme, false);
|
|
65
66
|
|
|
66
|
-
// value=0
|
|
67
|
-
expect(colors.has(0)).toBe(
|
|
67
|
+
// value=0 clamps to domain[0]=25, which is the minimum, so it's skipped
|
|
68
|
+
expect(colors.has(0)).toBe(false);
|
|
69
|
+
// value=100 clamps to domain max and gets colored
|
|
68
70
|
expect(colors.has(4)).toBe(true);
|
|
69
71
|
});
|
|
70
72
|
|
|
@@ -80,8 +82,9 @@ describe('computeHeatmapColors', () => {
|
|
|
80
82
|
const theme = getTheme();
|
|
81
83
|
const colors = computeHeatmapColors(dataWithLabel, col, theme, false);
|
|
82
84
|
|
|
83
|
-
//
|
|
84
|
-
expect(colors.size).toBe(
|
|
85
|
+
// score=10 is the domain minimum and is skipped; only score=90 gets colored
|
|
86
|
+
expect(colors.size).toBe(1);
|
|
87
|
+
expect(colors.has(1)).toBe(true);
|
|
85
88
|
});
|
|
86
89
|
|
|
87
90
|
it('returns empty map for non-numeric columns', () => {
|
|
@@ -102,8 +105,8 @@ describe('computeHeatmapColors', () => {
|
|
|
102
105
|
const theme = getTheme(true);
|
|
103
106
|
const colors = computeHeatmapColors(data, column, theme, true);
|
|
104
107
|
|
|
105
|
-
|
|
106
|
-
|
|
108
|
+
// Domain-minimum row skipped
|
|
109
|
+
expect(colors.size).toBe(4);
|
|
107
110
|
for (const [, style] of colors) {
|
|
108
111
|
const bg = style.backgroundColor!;
|
|
109
112
|
const fg = style.color!;
|
|
@@ -112,7 +115,7 @@ describe('computeHeatmapColors', () => {
|
|
|
112
115
|
}
|
|
113
116
|
});
|
|
114
117
|
|
|
115
|
-
it('custom palette arrays are
|
|
118
|
+
it('custom palette arrays are adapted for dark mode', () => {
|
|
116
119
|
const col: ColumnConfig = {
|
|
117
120
|
key: 'value',
|
|
118
121
|
heatmap: { palette: ['#fca5a5', '#c44e52'], domain: [0, 100] },
|
|
@@ -122,14 +125,14 @@ describe('computeHeatmapColors', () => {
|
|
|
122
125
|
const lightColors = computeHeatmapColors(data, col, lightTheme, false);
|
|
123
126
|
const darkColors = computeHeatmapColors(data, col, darkTheme, true);
|
|
124
127
|
|
|
125
|
-
|
|
128
|
+
// Domain-minimum row (value=0) is skipped in both modes
|
|
129
|
+
expect(lightColors.has(0)).toBe(false);
|
|
130
|
+
expect(darkColors.has(0)).toBe(false);
|
|
131
|
+
|
|
132
|
+
// The high-end color gets adapted for dark mode
|
|
126
133
|
const lightHighBg = lightColors.get(4)!.backgroundColor!;
|
|
127
|
-
const darkLowBg = darkColors.get(0)!.backgroundColor!;
|
|
128
134
|
const darkHighBg = darkColors.get(4)!.backgroundColor!;
|
|
129
|
-
|
|
130
|
-
// Custom palettes produce the same colors in both modes
|
|
131
|
-
expect(darkLowBg).toBe(lightLowBg);
|
|
132
|
-
expect(darkHighBg).toBe(lightHighBg);
|
|
135
|
+
expect(darkHighBg).not.toBe(lightHighBg);
|
|
133
136
|
});
|
|
134
137
|
|
|
135
138
|
it('supports array of color stops as palette', () => {
|
|
@@ -139,6 +142,7 @@ describe('computeHeatmapColors', () => {
|
|
|
139
142
|
};
|
|
140
143
|
const theme = getTheme();
|
|
141
144
|
const colors = computeHeatmapColors(data, col, theme, false);
|
|
142
|
-
|
|
145
|
+
// Domain-minimum row skipped
|
|
146
|
+
expect(colors.size).toBe(4);
|
|
143
147
|
});
|
|
144
148
|
});
|
package/src/tables/heatmap.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { CellStyle, ColumnConfig, ResolvedTheme } from '@opendata-ai/openchart-core';
|
|
9
|
-
import { adaptColorForDarkMode } from '@opendata-ai/openchart-core';
|
|
9
|
+
import { adaptColorForDarkMode, contrastRatio } from '@opendata-ai/openchart-core';
|
|
10
10
|
import { interpolateRgb } from 'd3-interpolate';
|
|
11
11
|
import { scaleSequential } from 'd3-scale';
|
|
12
12
|
import { accessibleTextColor } from './utils';
|
|
@@ -96,22 +96,38 @@ export function computeHeatmapColors(
|
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
// Resolve palette and build scale.
|
|
99
|
-
//
|
|
100
|
-
//
|
|
101
|
-
//
|
|
102
|
-
// (light pink -> dark red becomes dark red -> pink on dark backgrounds).
|
|
99
|
+
// For dark mode with custom palettes: replace near-white low-end stops
|
|
100
|
+
// with the dark background so the gradient blends into the table surface.
|
|
101
|
+
// Theme-named palettes use the standard adaptColorForDarkMode path.
|
|
103
102
|
let stops = resolvePalette(config.palette, theme);
|
|
104
|
-
if (darkMode
|
|
103
|
+
if (darkMode) {
|
|
105
104
|
const lightBg = '#ffffff';
|
|
106
|
-
const
|
|
107
|
-
|
|
105
|
+
const themeBg = theme.colors.background;
|
|
106
|
+
// Use an opaque dark surface for palette blending even when the theme
|
|
107
|
+
// bg is transparent, so interpolation never passes through alpha=0
|
|
108
|
+
// (which breaks accessibleTextColor via NaN contrast ratios).
|
|
109
|
+
const darkBg = themeBg === 'transparent' ? '#09090b' : themeBg;
|
|
110
|
+
if (Array.isArray(config.palette)) {
|
|
111
|
+
// Custom arrays: swap near-white stops for the dark bg so the low end
|
|
112
|
+
// blends in, then adapt the remaining (saturated) stops normally.
|
|
113
|
+
stops = stops.map((c) => {
|
|
114
|
+
const ratio = contrastRatio(c, lightBg);
|
|
115
|
+
if (ratio < 1.15) return darkBg;
|
|
116
|
+
return adaptColorForDarkMode(c, lightBg, darkBg);
|
|
117
|
+
});
|
|
118
|
+
} else {
|
|
119
|
+
stops = stops.map((c) => adaptColorForDarkMode(c, lightBg, darkBg));
|
|
120
|
+
}
|
|
108
121
|
}
|
|
109
122
|
|
|
110
123
|
const interpolator = interpolatorFromStops(stops);
|
|
111
124
|
const scale = scaleSequential(interpolator).domain(domain).clamp(true);
|
|
112
125
|
|
|
113
|
-
// Apply to each row
|
|
126
|
+
// Apply to each row. Skip cells at the domain minimum so the normal
|
|
127
|
+
// row background (striping) shows through instead of painting an
|
|
128
|
+
// opaque low-end color that clashes with the table chrome.
|
|
114
129
|
for (const { index, value } of numericValues) {
|
|
130
|
+
if (value <= domain[0]) continue;
|
|
115
131
|
const bg = scale(value);
|
|
116
132
|
const textColor = accessibleTextColor(bg);
|
|
117
133
|
|