@opendata-ai/openchart-engine 7.8.0 → 7.9.1
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 +63 -4
- package/dist/index.js +1644 -1428
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/__snapshots__/compile-snapshot.test.ts.snap +31 -0
- package/src/__tests__/axes.test.ts +76 -0
- package/src/__tests__/compile-animation.test.ts +8 -8
- package/src/__tests__/compile-chart.test.ts +5 -5
- package/src/__tests__/dimensions.test.ts +116 -5
- package/src/__tests__/mark-keys.test.ts +294 -0
- package/src/charts/__tests__/post-process.test.ts +5 -26
- package/src/charts/bar/compute.ts +18 -0
- package/src/charts/column/compute.ts +18 -0
- package/src/charts/dot/compute.ts +30 -13
- package/src/charts/line/area.ts +20 -0
- package/src/charts/line/compute.ts +35 -1
- package/src/charts/line/curves.ts +47 -2
- package/src/charts/pie/compute.ts +13 -0
- package/src/charts/post-process.ts +2 -2
- package/src/charts/rule/index.ts +9 -0
- package/src/charts/scatter/compute.ts +15 -0
- package/src/charts/text/index.ts +9 -0
- package/src/charts/tick/index.ts +9 -0
- package/src/compiler/__tests__/animation.test.ts +78 -22
- package/src/compiler/__tests__/keys.test.ts +89 -0
- package/src/compiler/animation.ts +70 -50
- package/src/compiler/keys.ts +54 -0
- package/src/compiler/normalize.ts +1 -1
- package/src/index.ts +19 -1
- package/src/layout/axes.ts +125 -34
- package/src/layout/dimensions.ts +10 -11
- package/src/layout/plan.ts +11 -12
- package/src/tables/__tests__/compile-table.test.ts +5 -5
- package/src/tilemap/compile-tilemap.ts +93 -21
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opendata-ai/openchart-engine",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.9.1",
|
|
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": "7.
|
|
51
|
+
"@opendata-ai/openchart-core": "7.9.1",
|
|
52
52
|
"d3-array": "^3.2.0",
|
|
53
53
|
"d3-format": "^3.1.2",
|
|
54
54
|
"d3-interpolate": "^3.0.0",
|
|
@@ -224,6 +224,7 @@ exports[`compileChart snapshot (Step 7 oracle) > clipped-domain bar chart (data
|
|
|
224
224
|
},
|
|
225
225
|
"fill": "#06b6d4",
|
|
226
226
|
"height": 57.45223880597016,
|
|
227
|
+
"key": "|A",
|
|
227
228
|
"label": {
|
|
228
229
|
"connector": undefined,
|
|
229
230
|
"style": {
|
|
@@ -258,6 +259,7 @@ exports[`compileChart snapshot (Step 7 oracle) > clipped-domain bar chart (data
|
|
|
258
259
|
},
|
|
259
260
|
"fill": "#06b6d4",
|
|
260
261
|
"height": 57.45223880597016,
|
|
262
|
+
"key": "|B",
|
|
261
263
|
"label": {
|
|
262
264
|
"connector": undefined,
|
|
263
265
|
"style": {
|
|
@@ -292,6 +294,7 @@ exports[`compileChart snapshot (Step 7 oracle) > clipped-domain bar chart (data
|
|
|
292
294
|
},
|
|
293
295
|
"fill": "#06b6d4",
|
|
294
296
|
"height": 57.45223880597016,
|
|
297
|
+
"key": "|C",
|
|
295
298
|
"label": {
|
|
296
299
|
"connector": undefined,
|
|
297
300
|
"style": {
|
|
@@ -1037,8 +1040,14 @@ exports[`compileChart snapshot (Step 7 oracle) > legend-heavy multi-series line
|
|
|
1037
1040
|
"y": 105.2,
|
|
1038
1041
|
},
|
|
1039
1042
|
],
|
|
1043
|
+
"interpolate": "monotone",
|
|
1044
|
+
"key": "US",
|
|
1040
1045
|
"opacity": undefined,
|
|
1041
1046
|
"path": "M47,334.25L651.761,105.2",
|
|
1047
|
+
"pointKeys": [
|
|
1048
|
+
"2020-01-01",
|
|
1049
|
+
"2021-01-01",
|
|
1050
|
+
],
|
|
1042
1051
|
"points": [
|
|
1043
1052
|
{
|
|
1044
1053
|
"x": 47,
|
|
@@ -1129,8 +1138,14 @@ exports[`compileChart snapshot (Step 7 oracle) > legend-heavy multi-series line
|
|
|
1129
1138
|
"y": 143.375,
|
|
1130
1139
|
},
|
|
1131
1140
|
],
|
|
1141
|
+
"interpolate": "monotone",
|
|
1142
|
+
"key": "UK",
|
|
1132
1143
|
"opacity": undefined,
|
|
1133
1144
|
"path": "M47,296.075L651.761,143.375",
|
|
1145
|
+
"pointKeys": [
|
|
1146
|
+
"2020-01-01",
|
|
1147
|
+
"2021-01-01",
|
|
1148
|
+
],
|
|
1134
1149
|
"points": [
|
|
1135
1150
|
{
|
|
1136
1151
|
"x": 47,
|
|
@@ -1221,8 +1236,14 @@ exports[`compileChart snapshot (Step 7 oracle) > legend-heavy multi-series line
|
|
|
1221
1236
|
"y": 242.62999999999997,
|
|
1222
1237
|
},
|
|
1223
1238
|
],
|
|
1239
|
+
"interpolate": "monotone",
|
|
1240
|
+
"key": "FR",
|
|
1224
1241
|
"opacity": undefined,
|
|
1225
1242
|
"path": "M47,349.52L651.761,242.63",
|
|
1243
|
+
"pointKeys": [
|
|
1244
|
+
"2020-01-01",
|
|
1245
|
+
"2021-01-01",
|
|
1246
|
+
],
|
|
1226
1247
|
"points": [
|
|
1227
1248
|
{
|
|
1228
1249
|
"x": 47,
|
|
@@ -1313,8 +1334,14 @@ exports[`compileChart snapshot (Step 7 oracle) > legend-heavy multi-series line
|
|
|
1313
1334
|
"y": 196.82,
|
|
1314
1335
|
},
|
|
1315
1336
|
],
|
|
1337
|
+
"interpolate": "monotone",
|
|
1338
|
+
"key": "DE",
|
|
1316
1339
|
"opacity": undefined,
|
|
1317
1340
|
"path": "M47,318.98L651.761,196.82",
|
|
1341
|
+
"pointKeys": [
|
|
1342
|
+
"2020-01-01",
|
|
1343
|
+
"2021-01-01",
|
|
1344
|
+
],
|
|
1318
1345
|
"points": [
|
|
1319
1346
|
{
|
|
1320
1347
|
"x": 47,
|
|
@@ -1804,6 +1831,7 @@ exports[`compileChart snapshot (Step 7 oracle) > watermarked column chart with g
|
|
|
1804
1831
|
"type": "linear",
|
|
1805
1832
|
},
|
|
1806
1833
|
"height": 101.27142857142857,
|
|
1834
|
+
"key": "|A",
|
|
1807
1835
|
"label": {
|
|
1808
1836
|
"connector": undefined,
|
|
1809
1837
|
"style": {
|
|
@@ -1849,6 +1877,7 @@ exports[`compileChart snapshot (Step 7 oracle) > watermarked column chart with g
|
|
|
1849
1877
|
"type": "linear",
|
|
1850
1878
|
},
|
|
1851
1879
|
"height": 185.6642857142857,
|
|
1880
|
+
"key": "|B",
|
|
1852
1881
|
"label": {
|
|
1853
1882
|
"connector": undefined,
|
|
1854
1883
|
"style": {
|
|
@@ -1894,6 +1923,7 @@ exports[`compileChart snapshot (Step 7 oracle) > watermarked column chart with g
|
|
|
1894
1923
|
"type": "linear",
|
|
1895
1924
|
},
|
|
1896
1925
|
"height": 236.29999999999998,
|
|
1926
|
+
"key": "|C",
|
|
1897
1927
|
"label": {
|
|
1898
1928
|
"connector": undefined,
|
|
1899
1929
|
"style": {
|
|
@@ -1939,6 +1969,7 @@ exports[`compileChart snapshot (Step 7 oracle) > watermarked column chart with g
|
|
|
1939
1969
|
"type": "linear",
|
|
1940
1970
|
},
|
|
1941
1971
|
"height": 151.90714285714287,
|
|
1972
|
+
"key": "|D",
|
|
1942
1973
|
"label": {
|
|
1943
1974
|
"connector": undefined,
|
|
1944
1975
|
"style": {
|
|
@@ -482,6 +482,82 @@ describe('text-aware tick density', () => {
|
|
|
482
482
|
expect(axes.x!.ticks.length).toBe(categories.length);
|
|
483
483
|
});
|
|
484
484
|
|
|
485
|
+
it('keeps narrow band labels when one label is much wider (no global decimation)', () => {
|
|
486
|
+
// Regression: 5 rotated year labels where "2026 (to wk 17)" is much wider
|
|
487
|
+
// than the others. The old every-other thinning dropped BOTH "2023" and
|
|
488
|
+
// "2025" even though the narrow labels had ample room — a single wide label
|
|
489
|
+
// forced global decimation. Greedy per-label retention keeps the narrow
|
|
490
|
+
// ones; only the neighbor immediately crowded by the wide final label drops.
|
|
491
|
+
const categories = ['2022', '2023', '2024', '2025', '2026 (to wk 17)'];
|
|
492
|
+
const barSpec: NormalizedChartSpec = {
|
|
493
|
+
...lineSpec,
|
|
494
|
+
markType: 'bar',
|
|
495
|
+
markDef: { type: 'bar', orient: 'vertical' },
|
|
496
|
+
data: categories.map((cat, i) => ({ cat, val: (i + 1) * 10 })),
|
|
497
|
+
encoding: {
|
|
498
|
+
x: { field: 'cat', type: 'nominal' },
|
|
499
|
+
y: { field: 'val', type: 'quantitative' },
|
|
500
|
+
},
|
|
501
|
+
};
|
|
502
|
+
|
|
503
|
+
// measureText reports the wide label as genuinely wide and the year labels
|
|
504
|
+
// as narrow, matching real glyph metrics.
|
|
505
|
+
const measure = (text: string) => ({
|
|
506
|
+
width: text.length * 8,
|
|
507
|
+
height: 12,
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
// Narrow area (280px) forces -45° rotation. The middle narrow labels survive
|
|
511
|
+
// — old every-other thinning would have dropped 2023 too.
|
|
512
|
+
const narrowArea = { x: 50, y: 50, width: 280, height: 300 };
|
|
513
|
+
const narrowScales = computeScales(barSpec, narrowArea, barSpec.data);
|
|
514
|
+
const narrowAxes = computeAxes(narrowScales, narrowArea, fullStrategy, theme, measure);
|
|
515
|
+
const narrowKept = narrowAxes.x!.ticks.map((t) => t.label);
|
|
516
|
+
expect(narrowAxes.x!.tickAngle).toBe(-45);
|
|
517
|
+
expect(narrowKept).toContain('2023');
|
|
518
|
+
expect(narrowKept).toContain('2024');
|
|
519
|
+
expect(narrowKept).toContain('2026 (to wk 17)');
|
|
520
|
+
|
|
521
|
+
// Wider area (500px) has room for every label — none are thinned.
|
|
522
|
+
const wideArea = { x: 50, y: 50, width: 500, height: 300 };
|
|
523
|
+
const wideScales = computeScales(barSpec, wideArea, barSpec.data);
|
|
524
|
+
const wideAxes = computeAxes(wideScales, wideArea, fullStrategy, theme, measure);
|
|
525
|
+
expect(wideAxes.x!.ticks.length).toBe(categories.length);
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
it('drops every neighbor a very wide final label overlaps, not just the nearest', () => {
|
|
529
|
+
// A final category long enough to project across several bands at -45° must
|
|
530
|
+
// clear ALL kept labels its footprint overlaps. The last-label protection
|
|
531
|
+
// pass drops each colliding neighbor, not only the immediate one — otherwise
|
|
532
|
+
// an earlier kept label still overlaps the forced final label.
|
|
533
|
+
const categories = ['A', 'B', 'C', 'D', 'This Is A Very Long Final Category Name'];
|
|
534
|
+
const barSpec: NormalizedChartSpec = {
|
|
535
|
+
...lineSpec,
|
|
536
|
+
markType: 'bar',
|
|
537
|
+
markDef: { type: 'bar', orient: 'vertical' },
|
|
538
|
+
data: categories.map((cat, i) => ({ cat, val: (i + 1) * 10 })),
|
|
539
|
+
encoding: {
|
|
540
|
+
x: { field: 'cat', type: 'nominal' },
|
|
541
|
+
y: { field: 'val', type: 'quantitative' },
|
|
542
|
+
},
|
|
543
|
+
};
|
|
544
|
+
const measure = (text: string) => ({ width: text.length * 8, height: 12 });
|
|
545
|
+
const narrowArea = { x: 50, y: 50, width: 260, height: 300 };
|
|
546
|
+
const scales = computeScales(barSpec, narrowArea, barSpec.data);
|
|
547
|
+
const axes = computeAxes(scales, narrowArea, fullStrategy, theme, measure);
|
|
548
|
+
expect(axes.x!.tickAngle).toBe(-45);
|
|
549
|
+
|
|
550
|
+
const kept = axes.x!.ticks.map((t) => t.label);
|
|
551
|
+
// The forced final label is always retained.
|
|
552
|
+
expect(kept).toContain('This Is A Very Long Final Category Name');
|
|
553
|
+
// Every middle label the wide final label's footprint overlaps is dropped —
|
|
554
|
+
// not just the nearest one. Before the fix, "B" and "C" would survive and
|
|
555
|
+
// overlap the final label; only "D" (the immediate neighbor) was dropped.
|
|
556
|
+
expect(kept).not.toContain('B');
|
|
557
|
+
expect(kept).not.toContain('C');
|
|
558
|
+
expect(kept).not.toContain('D');
|
|
559
|
+
});
|
|
560
|
+
|
|
485
561
|
it('y-axis gridlines match ticks so every gridline has a label', () => {
|
|
486
562
|
// Force thinning by using a measureText that reports wide labels
|
|
487
563
|
const wideMeasure = () => ({ width: 200, height: 12 });
|
|
@@ -50,11 +50,11 @@ describe('compileChart with animation', () => {
|
|
|
50
50
|
const layout = compileChart(spec, { width: 600, height: 400 });
|
|
51
51
|
|
|
52
52
|
expect(layout.animation).toBeDefined();
|
|
53
|
-
expect(layout.animation!.
|
|
54
|
-
expect(layout.animation!.duration).toBe(500);
|
|
55
|
-
expect(layout.animation!.ease).toBe('smooth');
|
|
56
|
-
expect(layout.animation!.staggerDelay).toBe(80);
|
|
57
|
-
expect(layout.animation!.staggerOrder).toBe('index');
|
|
53
|
+
expect(layout.animation!.enter).toBeDefined();
|
|
54
|
+
expect(layout.animation!.enter!.duration).toBe(500);
|
|
55
|
+
expect(layout.animation!.enter!.ease).toBe('smooth');
|
|
56
|
+
expect(layout.animation!.enter!.staggerDelay).toBe(80);
|
|
57
|
+
expect(layout.animation!.enter!.staggerOrder).toBe('index');
|
|
58
58
|
expect(layout.animation!.annotationDelay).toBe(200);
|
|
59
59
|
});
|
|
60
60
|
|
|
@@ -80,8 +80,8 @@ describe('compileChart with animation', () => {
|
|
|
80
80
|
const layout = compileChart(spec, { width: 600, height: 400 });
|
|
81
81
|
|
|
82
82
|
expect(layout.animation).toBeDefined();
|
|
83
|
-
expect(layout.animation!.duration).toBe(800);
|
|
84
|
-
expect(layout.animation!.ease).toBe('smooth');
|
|
83
|
+
expect(layout.animation!.enter!.duration).toBe(800);
|
|
84
|
+
expect(layout.animation!.enter!.ease).toBe('smooth');
|
|
85
85
|
expect(layout.animation!.annotationDelay).toBe(500);
|
|
86
86
|
});
|
|
87
87
|
|
|
@@ -152,7 +152,7 @@ describe('compileChart with animation', () => {
|
|
|
152
152
|
} as ChartSpec;
|
|
153
153
|
const layoutCompact = compileChart(specNoAnim, { width: 350, height: 400 });
|
|
154
154
|
expect(layoutCompact.animation).toBeDefined();
|
|
155
|
-
expect(layoutCompact.animation!.
|
|
155
|
+
expect(layoutCompact.animation!.enter).toBeDefined();
|
|
156
156
|
|
|
157
157
|
// At full width (> 700), no override applies, so no animation
|
|
158
158
|
const layoutFull = compileChart(specNoAnim, { width: 800, height: 400 });
|
|
@@ -943,7 +943,7 @@ describe('compileChart', () => {
|
|
|
943
943
|
};
|
|
944
944
|
const layout = compileChart(spec, { width: 320, height: 200 });
|
|
945
945
|
expect(layout.display).toBe('sparkline');
|
|
946
|
-
expect(layout.animation?.
|
|
946
|
+
expect(layout.animation?.enter).toBeDefined();
|
|
947
947
|
});
|
|
948
948
|
|
|
949
949
|
it('breakpoint chrome wins when top-level is sparkline', () => {
|
|
@@ -1012,8 +1012,8 @@ describe('compileChart', () => {
|
|
|
1012
1012
|
{ ...lineSpec, display: 'sparkline' as const, animation: true },
|
|
1013
1013
|
{ width: 400, height: 80 },
|
|
1014
1014
|
);
|
|
1015
|
-
expect(layout.animation?.
|
|
1016
|
-
expect(layout.animation?.duration).toBe(1100);
|
|
1015
|
+
expect(layout.animation?.enter).toBeDefined();
|
|
1016
|
+
expect(layout.animation?.enter?.duration).toBe(1100);
|
|
1017
1017
|
});
|
|
1018
1018
|
|
|
1019
1019
|
it('sparkline + animation: { enter: { duration: 500 } } respects user duration', () => {
|
|
@@ -1025,12 +1025,12 @@ describe('compileChart', () => {
|
|
|
1025
1025
|
},
|
|
1026
1026
|
{ width: 400, height: 80 },
|
|
1027
1027
|
);
|
|
1028
|
-
expect(layout.animation?.duration).toBe(500);
|
|
1028
|
+
expect(layout.animation?.enter?.duration).toBe(500);
|
|
1029
1029
|
});
|
|
1030
1030
|
|
|
1031
1031
|
it('full mode + animation: true uses default 500ms (sparkline bump does not leak)', () => {
|
|
1032
1032
|
const layout = compileChart({ ...lineSpec, animation: true }, { width: 600, height: 400 });
|
|
1033
|
-
expect(layout.animation?.duration).toBe(500);
|
|
1033
|
+
expect(layout.animation?.enter?.duration).toBe(500);
|
|
1034
1034
|
});
|
|
1035
1035
|
|
|
1036
1036
|
// ---------------------------------------------------------------------------
|
|
@@ -162,7 +162,65 @@ describe('computeDimensions', () => {
|
|
|
162
162
|
expect(dimsWithTitle.margins.left - dimsWithoutTitle.margins.left).toBeGreaterThanOrEqual(11);
|
|
163
163
|
});
|
|
164
164
|
|
|
165
|
-
it('y-axis title margin scales with tick label width', () => {
|
|
165
|
+
it('y-axis title margin scales with tick label width (gutter ticks)', () => {
|
|
166
|
+
// Bar chart: y-tick labels sit in a left gutter, so the title must clear
|
|
167
|
+
// them. Wider tick labels ("2,000,000" vs "9") push the title further out.
|
|
168
|
+
const barBase: NormalizedChartSpec = {
|
|
169
|
+
...baseSpec,
|
|
170
|
+
markType: 'bar',
|
|
171
|
+
markDef: { type: 'bar' },
|
|
172
|
+
};
|
|
173
|
+
const smallValues: NormalizedChartSpec = {
|
|
174
|
+
...barBase,
|
|
175
|
+
data: [
|
|
176
|
+
{ cat: 'a', value: 5 },
|
|
177
|
+
{ cat: 'b', value: 9 },
|
|
178
|
+
],
|
|
179
|
+
encoding: {
|
|
180
|
+
x: { field: 'cat', type: 'nominal' },
|
|
181
|
+
y: { field: 'value', type: 'quantitative', axis: { title: 'Count' } },
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
const largeValues: NormalizedChartSpec = {
|
|
185
|
+
...barBase,
|
|
186
|
+
data: [
|
|
187
|
+
{ cat: 'a', value: 1_500_000 },
|
|
188
|
+
{ cat: 'b', value: 2_000_000 },
|
|
189
|
+
],
|
|
190
|
+
encoding: {
|
|
191
|
+
x: { field: 'cat', type: 'nominal' },
|
|
192
|
+
y: {
|
|
193
|
+
field: 'value',
|
|
194
|
+
type: 'quantitative',
|
|
195
|
+
axis: { title: 'Revenue ($)', format: ',.0f' },
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const dimsSmall = computeDimensions(
|
|
201
|
+
smallValues,
|
|
202
|
+
{ width: 600, height: 400 },
|
|
203
|
+
emptyLegend,
|
|
204
|
+
lightTheme,
|
|
205
|
+
);
|
|
206
|
+
const dimsLarge = computeDimensions(
|
|
207
|
+
largeValues,
|
|
208
|
+
{ width: 600, height: 400 },
|
|
209
|
+
emptyLegend,
|
|
210
|
+
lightTheme,
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
// Explicit format ",.0f" on large values produces "2,000,000" tick labels
|
|
214
|
+
// that are much wider than the abbreviated "0.0" from small values,
|
|
215
|
+
// so the y-axis title margin should grow to keep clearance
|
|
216
|
+
expect(dimsLarge.margins.left).toBeGreaterThan(dimsSmall.margins.left);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('y-axis title margin ignores tick label width for inline ticks (line)', () => {
|
|
220
|
+
// Line chart with continuous y: tick labels render inline (above their
|
|
221
|
+
// gridline inside the plot), not in a left gutter. The title only needs to
|
|
222
|
+
// clear the chart edge, so wide tick values must NOT inflate the margin —
|
|
223
|
+
// that dead padding is the exact bug this guards against.
|
|
166
224
|
const smallValues: NormalizedChartSpec = {
|
|
167
225
|
...baseSpec,
|
|
168
226
|
data: [
|
|
@@ -203,10 +261,63 @@ describe('computeDimensions', () => {
|
|
|
203
261
|
lightTheme,
|
|
204
262
|
);
|
|
205
263
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
264
|
+
expect(dimsLarge.margins.left).toBe(dimsSmall.margins.left);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it('reserves gutter margin when an explicit band scale forces gutter ticks (line)', () => {
|
|
268
|
+
// A line chart's y-tick position is decided by the RESOLVED scale type, not
|
|
269
|
+
// the field type. An explicit `scale.type: 'band'` on a quantitative field
|
|
270
|
+
// resolves to a categorical (gutter) axis, so computeAxes draws a gutter
|
|
271
|
+
// title. The margin reservation must agree — reserving the inline (narrow)
|
|
272
|
+
// margin here would let the title collide with the gutter tick labels.
|
|
273
|
+
const bandScaleLine: NormalizedChartSpec = {
|
|
274
|
+
...baseSpec,
|
|
275
|
+
data: [
|
|
276
|
+
{ date: '2020-01-01', value: 1_500_000 },
|
|
277
|
+
{ date: '2021-01-01', value: 2_000_000 },
|
|
278
|
+
],
|
|
279
|
+
encoding: {
|
|
280
|
+
x: { field: 'date', type: 'temporal' },
|
|
281
|
+
y: {
|
|
282
|
+
field: 'value',
|
|
283
|
+
type: 'quantitative',
|
|
284
|
+
scale: { type: 'band' },
|
|
285
|
+
axis: { title: 'Revenue ($)', format: ',.0f' },
|
|
286
|
+
},
|
|
287
|
+
},
|
|
288
|
+
};
|
|
289
|
+
const inlineLine: NormalizedChartSpec = {
|
|
290
|
+
...baseSpec,
|
|
291
|
+
data: [
|
|
292
|
+
{ date: '2020-01-01', value: 1_500_000 },
|
|
293
|
+
{ date: '2021-01-01', value: 2_000_000 },
|
|
294
|
+
],
|
|
295
|
+
encoding: {
|
|
296
|
+
x: { field: 'date', type: 'temporal' },
|
|
297
|
+
y: {
|
|
298
|
+
field: 'value',
|
|
299
|
+
type: 'quantitative',
|
|
300
|
+
axis: { title: 'Revenue ($)', format: ',.0f' },
|
|
301
|
+
},
|
|
302
|
+
},
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
const bandDims = computeDimensions(
|
|
306
|
+
bandScaleLine,
|
|
307
|
+
{ width: 600, height: 400 },
|
|
308
|
+
emptyLegend,
|
|
309
|
+
lightTheme,
|
|
310
|
+
);
|
|
311
|
+
const inlineDims = computeDimensions(
|
|
312
|
+
inlineLine,
|
|
313
|
+
{ width: 600, height: 400 },
|
|
314
|
+
emptyLegend,
|
|
315
|
+
lightTheme,
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
// The gutter (band-scale) reservation includes the wide tick-label width, so
|
|
319
|
+
// it must exceed the inline reservation that omits it.
|
|
320
|
+
expect(bandDims.margins.left).toBeGreaterThan(inlineDims.margins.left);
|
|
210
321
|
});
|
|
211
322
|
|
|
212
323
|
it('applies dark mode theme adaptation', () => {
|