@opendata-ai/openchart-engine 8.0.0-rc.10 → 8.0.0-rc.12
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 +7 -0
- package/dist/index.js +123 -54
- package/dist/index.js.map +1 -1
- package/package.json +3 -4
- package/src/__test-fixtures__/specs.ts +0 -182
- package/src/__tests__/__snapshots__/compile-snapshot.test.ts.snap +0 -2401
- package/src/__tests__/annotation-behavior.test.ts +0 -852
- package/src/__tests__/axes.test.ts +0 -1579
- package/src/__tests__/compile-animation.test.ts +0 -161
- package/src/__tests__/compile-chart.test.ts +0 -1303
- package/src/__tests__/compile-footnotes.test.ts +0 -277
- package/src/__tests__/compile-layer.test.ts +0 -969
- package/src/__tests__/compile-snapshot.test.ts +0 -156
- package/src/__tests__/compound-labels.test.ts +0 -147
- package/src/__tests__/dimensions.test.ts +0 -918
- package/src/__tests__/encoding-sugar.test.ts +0 -390
- package/src/__tests__/facet.test.ts +0 -359
- package/src/__tests__/helpers/invariants.ts +0 -299
- package/src/__tests__/known-bugs.test.ts +0 -566
- package/src/__tests__/layout-fuzz.test.ts +0 -358
- package/src/__tests__/layout-invariants.test.ts +0 -535
- package/src/__tests__/legend.test.ts +0 -721
- package/src/__tests__/mark-keys.test.ts +0 -294
- package/src/__tests__/ranking-recipes.test.ts +0 -260
- package/src/__tests__/rotation.test.ts +0 -83
- package/src/__tests__/scales.test.ts +0 -323
- package/src/__tests__/series-search.test.ts +0 -92
- package/src/__tests__/spec-sugar.test.ts +0 -801
- package/src/__tests__/you-draw-it.test.ts +0 -160
- package/src/annotations/__tests__/compute.test.ts +0 -1672
- package/src/annotations/__tests__/geometry.test.ts +0 -66
- package/src/annotations/__tests__/placement-fuzz.test.ts +0 -256
- package/src/annotations/__tests__/placement-invariants.test.ts +0 -345
- package/src/annotations/__tests__/placement.test.ts +0 -316
- package/src/annotations/__tests__/resolve-text.test.ts +0 -788
- package/src/annotations/__tests__/rich-text.test.ts +0 -91
- package/src/annotations/__tests__/thinning.test.ts +0 -285
- package/src/annotations/collisions.ts +0 -326
- package/src/annotations/compute.ts +0 -333
- package/src/annotations/constants.ts +0 -162
- package/src/annotations/geometry.ts +0 -376
- package/src/annotations/move.ts +0 -135
- package/src/annotations/placement.ts +0 -636
- package/src/annotations/position.ts +0 -131
- package/src/annotations/resolve-range.ts +0 -117
- package/src/annotations/resolve-refline.ts +0 -157
- package/src/annotations/resolve-text.ts +0 -545
- package/src/annotations/rich-text.ts +0 -65
- package/src/annotations/thinning.ts +0 -190
- package/src/barlist/__tests__/compile-barlist.test.ts +0 -200
- package/src/barlist/compile-barlist.ts +0 -391
- package/src/barlist/types.ts +0 -28
- package/src/charts/__tests__/post-process.test.ts +0 -237
- package/src/charts/__tests__/registry.test.ts +0 -116
- package/src/charts/__tests__/utils.test.ts +0 -195
- package/src/charts/_shared/__tests__/density-filter.test.ts +0 -32
- package/src/charts/_shared/density-filter.ts +0 -26
- package/src/charts/_shared/format-label-value.ts +0 -16
- package/src/charts/bar/__tests__/compute.test.ts +0 -934
- package/src/charts/bar/__tests__/gradient-orient.test.ts +0 -127
- package/src/charts/bar/__tests__/labels.test.ts +0 -292
- package/src/charts/bar/compute.ts +0 -539
- package/src/charts/bar/index.ts +0 -60
- package/src/charts/bar/labels.ts +0 -311
- package/src/charts/beeswarm/__tests__/compute.test.ts +0 -280
- package/src/charts/beeswarm/__tests__/dodge.test.ts +0 -148
- package/src/charts/beeswarm/compute.ts +0 -203
- package/src/charts/beeswarm/dodge.ts +0 -155
- package/src/charts/beeswarm/index.ts +0 -32
- package/src/charts/builtin.ts +0 -75
- package/src/charts/calendar/__tests__/compute.test.ts +0 -417
- package/src/charts/calendar/compute.ts +0 -319
- package/src/charts/calendar/index.ts +0 -32
- package/src/charts/column/__tests__/compute.test.ts +0 -540
- package/src/charts/column/__tests__/labels.test.ts +0 -179
- package/src/charts/column/compute.ts +0 -493
- package/src/charts/column/index.ts +0 -53
- package/src/charts/column/labels.ts +0 -229
- package/src/charts/dot/__tests__/compute.test.ts +0 -380
- package/src/charts/dot/__tests__/labels.test.ts +0 -98
- package/src/charts/dot/compute.ts +0 -285
- package/src/charts/dot/index.ts +0 -62
- package/src/charts/dot/labels.ts +0 -121
- package/src/charts/line/__tests__/compute.test.ts +0 -1194
- package/src/charts/line/__tests__/curves.test.ts +0 -51
- package/src/charts/line/__tests__/labels.test.ts +0 -94
- package/src/charts/line/__tests__/mark-options.test.ts +0 -175
- package/src/charts/line/area.ts +0 -484
- package/src/charts/line/compute.ts +0 -356
- package/src/charts/line/curves.ts +0 -85
- package/src/charts/line/index.ts +0 -176
- package/src/charts/line/labels.ts +0 -177
- package/src/charts/parliament/__tests__/compute.test.ts +0 -411
- package/src/charts/parliament/compute.ts +0 -313
- package/src/charts/parliament/index.ts +0 -32
- package/src/charts/pie/__tests__/compute.test.ts +0 -384
- package/src/charts/pie/__tests__/labels.test.ts +0 -132
- package/src/charts/pie/compute.ts +0 -324
- package/src/charts/pie/index.ts +0 -49
- package/src/charts/pie/labels.ts +0 -143
- package/src/charts/post-process.ts +0 -229
- package/src/charts/range/__tests__/compute.test.ts +0 -486
- package/src/charts/range/compute.ts +0 -273
- package/src/charts/range/index.ts +0 -82
- package/src/charts/range/labels.ts +0 -169
- package/src/charts/rect/__tests__/compute.test.ts +0 -120
- package/src/charts/rect/index.ts +0 -127
- package/src/charts/registry.ts +0 -72
- package/src/charts/rule/index.ts +0 -139
- package/src/charts/scatter/__tests__/compute.test.ts +0 -488
- package/src/charts/scatter/__tests__/trendline.test.ts +0 -191
- package/src/charts/scatter/compute.ts +0 -192
- package/src/charts/scatter/index.ts +0 -46
- package/src/charts/scatter/trendline.ts +0 -100
- package/src/charts/text/__tests__/compute.test.ts +0 -307
- package/src/charts/text/index.ts +0 -141
- package/src/charts/tick/__tests__/compute.test.ts +0 -75
- package/src/charts/tick/index.ts +0 -99
- package/src/charts/utils.ts +0 -170
- package/src/charts/waffle/__tests__/compute.test.ts +0 -305
- package/src/charts/waffle/compute.ts +0 -197
- package/src/charts/waffle/index.ts +0 -31
- package/src/compile/__tests__/color-scale-range.test.ts +0 -158
- package/src/compile/__tests__/color-scale-reverse.test.ts +0 -89
- package/src/compile/__tests__/data-clip.test.ts +0 -127
- package/src/compile/__tests__/fill-patterns.test.ts +0 -154
- package/src/compile/__tests__/size-scale.test.ts +0 -108
- package/src/compile/__tests__/watermark-obstacle.test.ts +0 -93
- package/src/compile/color-scale-range.ts +0 -101
- package/src/compile/data-clip.ts +0 -48
- package/src/compile/fill-patterns.ts +0 -74
- package/src/compile/layer.ts +0 -630
- package/src/compile/size-scale.ts +0 -141
- package/src/compile/spec-sugar.ts +0 -767
- package/src/compile/watermark-obstacle.ts +0 -54
- package/src/compile/you-draw-it.ts +0 -123
- package/src/compile.ts +0 -1548
- package/src/compiler/__tests__/a11y-warnings.test.ts +0 -148
- package/src/compiler/__tests__/animation.test.ts +0 -177
- package/src/compiler/__tests__/compile.test.ts +0 -87
- package/src/compiler/__tests__/fixtures/vl-parity/axis-null.json +0 -19
- package/src/compiler/__tests__/fixtures/vl-parity/color-value.json +0 -20
- package/src/compiler/__tests__/fixtures/vl-parity/count-aggregate.json +0 -22
- package/src/compiler/__tests__/fixtures/vl-parity/data-values.json +0 -19
- package/src/compiler/__tests__/fixtures/vl-parity/layer-axis-null.json +0 -14
- package/src/compiler/__tests__/fixtures/vl-parity/layer-count.json +0 -34
- package/src/compiler/__tests__/fixtures/vl-parity/layer-data-values.json +0 -22
- package/src/compiler/__tests__/fixtures/vl-parity/layer-legend-null.json +0 -41
- package/src/compiler/__tests__/fixtures/vl-parity/layer-scheme.json +0 -38
- package/src/compiler/__tests__/fixtures/vl-parity/layer-sort.json +0 -28
- package/src/compiler/__tests__/fixtures/vl-parity/layer-theta.json +0 -28
- package/src/compiler/__tests__/fixtures/vl-parity/layer-title.json +0 -30
- package/src/compiler/__tests__/fixtures/vl-parity/layer-value-def.json +0 -29
- package/src/compiler/__tests__/fixtures/vl-parity/legend-null.json +0 -32
- package/src/compiler/__tests__/fixtures/vl-parity/scheme.json +0 -25
- package/src/compiler/__tests__/fixtures/vl-parity/sort-by-value.json +0 -19
- package/src/compiler/__tests__/fixtures/vl-parity/theta-arc.json +0 -19
- package/src/compiler/__tests__/fixtures/vl-parity/title-subtitle.json +0 -21
- package/src/compiler/__tests__/fixtures/vl-parity/width-height.json +0 -23
- package/src/compiler/__tests__/keys.test.ts +0 -83
- package/src/compiler/__tests__/normalize-highlight.test.ts +0 -103
- package/src/compiler/__tests__/normalize.test.ts +0 -399
- package/src/compiler/__tests__/sparkline-defaults.test.ts +0 -153
- package/src/compiler/__tests__/validate-did-you-mean.test.ts +0 -170
- package/src/compiler/__tests__/validate-highlight.test.ts +0 -112
- package/src/compiler/__tests__/validate-near-miss.test.ts +0 -251
- package/src/compiler/__tests__/validate.test.ts +0 -731
- package/src/compiler/__tests__/vl-parity.test.ts +0 -68
- package/src/compiler/a11y-warnings.ts +0 -110
- package/src/compiler/animation.ts +0 -142
- package/src/compiler/index.ts +0 -47
- package/src/compiler/keys.ts +0 -55
- package/src/compiler/normalize.ts +0 -734
- package/src/compiler/sparkline-defaults.ts +0 -138
- package/src/compiler/types.ts +0 -241
- package/src/compiler/validate.ts +0 -2093
- package/src/endpoint-labels/__tests__/compute.test.ts +0 -587
- package/src/endpoint-labels/compute.ts +0 -621
- package/src/endpoint-labels/constants.ts +0 -54
- package/src/endpoint-labels/format.ts +0 -60
- package/src/endpoint-labels/predict.ts +0 -139
- package/src/format/__tests__/field-format.test.ts +0 -164
- package/src/format/field-format.ts +0 -27
- package/src/geo/__tests__/compile-map.test.ts +0 -1128
- package/src/geo/compile-map.ts +0 -1376
- package/src/geo/join.ts +0 -115
- package/src/geo/projections.ts +0 -65
- package/src/geo/types.ts +0 -26
- package/src/graphs/__tests__/community.test.ts +0 -228
- package/src/graphs/__tests__/compile-graph.test.ts +0 -396
- package/src/graphs/__tests__/encoding.test.ts +0 -415
- package/src/graphs/community.ts +0 -92
- package/src/graphs/compile-graph.ts +0 -329
- package/src/graphs/encoding.ts +0 -356
- package/src/graphs/types.ts +0 -106
- package/src/index.ts +0 -177
- package/src/layout/__tests__/metrics.test.ts +0 -97
- package/src/layout/__tests__/scales-highlight.test.ts +0 -192
- package/src/layout/axes/rotation.ts +0 -152
- package/src/layout/axes/thinning.ts +0 -98
- package/src/layout/axes/ticks.ts +0 -409
- package/src/layout/axes.ts +0 -757
- package/src/layout/dimensions.ts +0 -912
- package/src/layout/facet.ts +0 -91
- package/src/layout/gridlines.ts +0 -84
- package/src/layout/metrics.ts +0 -132
- package/src/layout/plan.ts +0 -631
- package/src/layout/scales.ts +0 -1053
- package/src/layout/shared.ts +0 -44
- package/src/legend/__tests__/continuous.test.ts +0 -318
- package/src/legend/__tests__/redundancy.test.ts +0 -108
- package/src/legend/__tests__/size.test.ts +0 -348
- package/src/legend/__tests__/suppression.test.ts +0 -382
- package/src/legend/compute.ts +0 -748
- package/src/legend/continuous.ts +0 -327
- package/src/legend/size.ts +0 -407
- package/src/legend/suppression.ts +0 -226
- package/src/legend/wrap.ts +0 -117
- package/src/sankey/__tests__/compile-sankey.test.ts +0 -458
- package/src/sankey/__tests__/layout.test.ts +0 -165
- package/src/sankey/__tests__/node-label-wrap.test.ts +0 -114
- package/src/sankey/__tests__/node-sort.test.ts +0 -45
- package/src/sankey/compile-sankey.ts +0 -706
- package/src/sankey/layout.ts +0 -184
- package/src/sankey/types.ts +0 -41
- package/src/tables/__tests__/bar-column.test.ts +0 -147
- package/src/tables/__tests__/category-colors.test.ts +0 -230
- package/src/tables/__tests__/compile-table.test.ts +0 -236
- package/src/tables/__tests__/format-cells.test.ts +0 -143
- package/src/tables/__tests__/heatmap.test.ts +0 -148
- package/src/tables/__tests__/pagination.test.ts +0 -78
- package/src/tables/__tests__/search.test.ts +0 -94
- package/src/tables/__tests__/sort.test.ts +0 -107
- package/src/tables/__tests__/sparkline.test.ts +0 -122
- package/src/tables/bar-column.ts +0 -94
- package/src/tables/category-colors.ts +0 -78
- package/src/tables/compile-table.ts +0 -427
- package/src/tables/format-cells.ts +0 -115
- package/src/tables/heatmap.ts +0 -141
- package/src/tables/pagination.ts +0 -46
- package/src/tables/search.ts +0 -66
- package/src/tables/sort.ts +0 -69
- package/src/tables/sparkline.ts +0 -113
- package/src/tables/utils.ts +0 -16
- package/src/tilemap/__tests__/compile-tilemap.test.ts +0 -576
- package/src/tilemap/compile-tilemap.ts +0 -755
- package/src/tilemap/layout.ts +0 -172
- package/src/tilemap/types.ts +0 -33
- package/src/tooltips/__tests__/compute.test.ts +0 -582
- package/src/tooltips/compute.ts +0 -700
- package/src/transforms/__tests__/aggregate.test.ts +0 -159
- package/src/transforms/__tests__/bin.test.ts +0 -88
- package/src/transforms/__tests__/calculate.test.ts +0 -146
- package/src/transforms/__tests__/conditional.test.ts +0 -127
- package/src/transforms/__tests__/filter-relative.test.ts +0 -202
- package/src/transforms/__tests__/filter.test.ts +0 -59
- package/src/transforms/__tests__/fold.test.ts +0 -79
- package/src/transforms/__tests__/index.test.ts +0 -93
- package/src/transforms/__tests__/predicates.test.ts +0 -193
- package/src/transforms/__tests__/timeunit.test.ts +0 -129
- package/src/transforms/__tests__/window.test.ts +0 -286
- package/src/transforms/aggregate.ts +0 -131
- package/src/transforms/bin.ts +0 -87
- package/src/transforms/calculate.ts +0 -60
- package/src/transforms/conditional.ts +0 -46
- package/src/transforms/filter.ts +0 -122
- package/src/transforms/fold.ts +0 -49
- package/src/transforms/index.ts +0 -60
- package/src/transforms/predicates.ts +0 -123
- package/src/transforms/timeunit.ts +0 -88
- package/src/transforms/window.ts +0 -185
|
@@ -1,540 +0,0 @@
|
|
|
1
|
-
import type { LayoutStrategy, Rect } from '@opendata-ai/openchart-core';
|
|
2
|
-
import { buildD3Formatter } from '@opendata-ai/openchart-core';
|
|
3
|
-
import { describe, expect, it } from 'vitest';
|
|
4
|
-
import type { NormalizedChartSpec } from '../../../compiler/types';
|
|
5
|
-
import { computeScales } from '../../../layout/scales';
|
|
6
|
-
import { computeColumnMarks } from '../compute';
|
|
7
|
-
import { computeColumnLabels } from '../labels';
|
|
8
|
-
|
|
9
|
-
// ---------------------------------------------------------------------------
|
|
10
|
-
// Shared fixtures
|
|
11
|
-
// ---------------------------------------------------------------------------
|
|
12
|
-
|
|
13
|
-
const chartArea: Rect = { x: 50, y: 20, width: 500, height: 300 };
|
|
14
|
-
|
|
15
|
-
const fullStrategy: LayoutStrategy = {
|
|
16
|
-
labelMode: 'all',
|
|
17
|
-
legendPosition: 'right',
|
|
18
|
-
annotationPosition: 'inline',
|
|
19
|
-
axisLabelDensity: 'full',
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
function makeSimpleColumnSpec(): NormalizedChartSpec {
|
|
23
|
-
return {
|
|
24
|
-
markType: 'bar',
|
|
25
|
-
markDef: { type: 'bar', orient: 'vertical' },
|
|
26
|
-
data: [
|
|
27
|
-
{ month: 'Jan', sales: 120 },
|
|
28
|
-
{ month: 'Feb', sales: 80 },
|
|
29
|
-
{ month: 'Mar', sales: 150 },
|
|
30
|
-
{ month: 'Apr', sales: 200 },
|
|
31
|
-
],
|
|
32
|
-
encoding: {
|
|
33
|
-
x: { field: 'month', type: 'nominal' },
|
|
34
|
-
y: { field: 'sales', type: 'quantitative' },
|
|
35
|
-
},
|
|
36
|
-
chrome: {},
|
|
37
|
-
annotations: [],
|
|
38
|
-
responsive: true,
|
|
39
|
-
theme: {},
|
|
40
|
-
darkMode: 'off',
|
|
41
|
-
labels: { density: 'auto', format: '' },
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function makeGroupedColumnSpec(): NormalizedChartSpec {
|
|
46
|
-
return {
|
|
47
|
-
markType: 'bar',
|
|
48
|
-
markDef: { type: 'bar', orient: 'vertical' },
|
|
49
|
-
data: [
|
|
50
|
-
{ month: 'Jan', sales: 120, region: 'North' },
|
|
51
|
-
{ month: 'Jan', sales: 80, region: 'South' },
|
|
52
|
-
{ month: 'Feb', sales: 90, region: 'North' },
|
|
53
|
-
{ month: 'Feb', sales: 110, region: 'South' },
|
|
54
|
-
{ month: 'Mar', sales: 150, region: 'North' },
|
|
55
|
-
{ month: 'Mar', sales: 130, region: 'South' },
|
|
56
|
-
],
|
|
57
|
-
encoding: {
|
|
58
|
-
x: { field: 'month', type: 'nominal' },
|
|
59
|
-
// Grouped is opt-out since v8 (stacked is now the default): stamp
|
|
60
|
-
// stack: null explicitly so this fixture stays grouped.
|
|
61
|
-
y: { field: 'sales', type: 'quantitative', stack: null },
|
|
62
|
-
color: { field: 'region', type: 'nominal' },
|
|
63
|
-
},
|
|
64
|
-
chrome: {},
|
|
65
|
-
annotations: [],
|
|
66
|
-
responsive: true,
|
|
67
|
-
theme: {},
|
|
68
|
-
darkMode: 'off',
|
|
69
|
-
labels: { density: 'auto', format: '' },
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function makeNegativeColumnSpec(): NormalizedChartSpec {
|
|
74
|
-
return {
|
|
75
|
-
markType: 'bar',
|
|
76
|
-
markDef: { type: 'bar', orient: 'vertical' },
|
|
77
|
-
data: [
|
|
78
|
-
{ quarter: 'Q1', growth: 5 },
|
|
79
|
-
{ quarter: 'Q2', growth: -3 },
|
|
80
|
-
{ quarter: 'Q3', growth: 8 },
|
|
81
|
-
{ quarter: 'Q4', growth: -1 },
|
|
82
|
-
],
|
|
83
|
-
encoding: {
|
|
84
|
-
x: { field: 'quarter', type: 'nominal' },
|
|
85
|
-
y: { field: 'growth', type: 'quantitative' },
|
|
86
|
-
},
|
|
87
|
-
chrome: {},
|
|
88
|
-
annotations: [],
|
|
89
|
-
responsive: true,
|
|
90
|
-
theme: {},
|
|
91
|
-
darkMode: 'off',
|
|
92
|
-
labels: { density: 'auto', format: '' },
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// ---------------------------------------------------------------------------
|
|
97
|
-
// computeColumnMarks tests
|
|
98
|
-
// ---------------------------------------------------------------------------
|
|
99
|
-
|
|
100
|
-
describe('computeColumnMarks', () => {
|
|
101
|
-
describe('simple columns', () => {
|
|
102
|
-
it('produces one RectMark per data row', () => {
|
|
103
|
-
const spec = makeSimpleColumnSpec();
|
|
104
|
-
const scales = computeScales(spec, chartArea, spec.data);
|
|
105
|
-
const marks = computeColumnMarks(spec, scales, chartArea, fullStrategy);
|
|
106
|
-
|
|
107
|
-
expect(marks).toHaveLength(4);
|
|
108
|
-
expect(marks.every((m) => m.type === 'rect')).toBe(true);
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
it('columns have positive width and height', () => {
|
|
112
|
-
const spec = makeSimpleColumnSpec();
|
|
113
|
-
const scales = computeScales(spec, chartArea, spec.data);
|
|
114
|
-
const marks = computeColumnMarks(spec, scales, chartArea, fullStrategy);
|
|
115
|
-
|
|
116
|
-
for (const mark of marks) {
|
|
117
|
-
expect(mark.width).toBeGreaterThan(0);
|
|
118
|
-
expect(mark.height).toBeGreaterThan(0);
|
|
119
|
-
}
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
it('taller columns correspond to larger values', () => {
|
|
123
|
-
const spec = makeSimpleColumnSpec();
|
|
124
|
-
const scales = computeScales(spec, chartArea, spec.data);
|
|
125
|
-
const marks = computeColumnMarks(spec, scales, chartArea, fullStrategy);
|
|
126
|
-
|
|
127
|
-
// Apr (200) should be taller than Feb (80)
|
|
128
|
-
const apr = marks.find((m) => m.aria.label.includes('Apr'))!;
|
|
129
|
-
const feb = marks.find((m) => m.aria.label.includes('Feb'))!;
|
|
130
|
-
expect(apr.height).toBeGreaterThan(feb.height);
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
it('higher values have lower y positions (SVG coordinates)', () => {
|
|
134
|
-
const spec = makeSimpleColumnSpec();
|
|
135
|
-
const scales = computeScales(spec, chartArea, spec.data);
|
|
136
|
-
const marks = computeColumnMarks(spec, scales, chartArea, fullStrategy);
|
|
137
|
-
|
|
138
|
-
const apr = marks.find((m) => m.aria.label.includes('Apr'))!;
|
|
139
|
-
const feb = marks.find((m) => m.aria.label.includes('Feb'))!;
|
|
140
|
-
// Apr (200) should start higher (lower y) than Feb (80)
|
|
141
|
-
expect(apr.y).toBeLessThan(feb.y);
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
it('each column has an aria label with category and value', () => {
|
|
145
|
-
const spec = makeSimpleColumnSpec();
|
|
146
|
-
const scales = computeScales(spec, chartArea, spec.data);
|
|
147
|
-
const marks = computeColumnMarks(spec, scales, chartArea, fullStrategy);
|
|
148
|
-
|
|
149
|
-
expect(marks[0].aria.label).toContain('Jan');
|
|
150
|
-
expect(marks[0].aria.label).toContain('120');
|
|
151
|
-
});
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
describe('stacked columns (stack: zero)', () => {
|
|
155
|
-
function makeStackedColumnSpec(): NormalizedChartSpec {
|
|
156
|
-
const spec = makeGroupedColumnSpec();
|
|
157
|
-
(spec.encoding.y as { stack?: string }).stack = 'zero';
|
|
158
|
-
return spec;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
it('produces marks for all data rows', () => {
|
|
162
|
-
const spec = makeStackedColumnSpec();
|
|
163
|
-
const scales = computeScales(spec, chartArea, spec.data);
|
|
164
|
-
const marks = computeColumnMarks(spec, scales, chartArea, fullStrategy);
|
|
165
|
-
|
|
166
|
-
// 3 categories * 2 groups = 6
|
|
167
|
-
expect(marks).toHaveLength(6);
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
it('stacked segments within a category have different colors', () => {
|
|
171
|
-
const spec = makeStackedColumnSpec();
|
|
172
|
-
const scales = computeScales(spec, chartArea, spec.data);
|
|
173
|
-
const marks = computeColumnMarks(spec, scales, chartArea, fullStrategy);
|
|
174
|
-
|
|
175
|
-
const janMarks = marks.filter((m) => m.aria.label.includes('Jan'));
|
|
176
|
-
expect(janMarks).toHaveLength(2);
|
|
177
|
-
expect(janMarks[0].fill).not.toBe(janMarks[1].fill);
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
it('stacked columns within a category share the same x position', () => {
|
|
181
|
-
const spec = makeStackedColumnSpec();
|
|
182
|
-
const scales = computeScales(spec, chartArea, spec.data);
|
|
183
|
-
const marks = computeColumnMarks(spec, scales, chartArea, fullStrategy);
|
|
184
|
-
|
|
185
|
-
const janNorth = marks.find(
|
|
186
|
-
(m) => m.aria.label.includes('Jan') && m.aria.label.includes('North'),
|
|
187
|
-
)!;
|
|
188
|
-
const janSouth = marks.find(
|
|
189
|
-
(m) => m.aria.label.includes('Jan') && m.aria.label.includes('South'),
|
|
190
|
-
)!;
|
|
191
|
-
|
|
192
|
-
// Stacked columns share the same x position
|
|
193
|
-
expect(janNorth.x).toBe(janSouth.x);
|
|
194
|
-
// But have different y positions (stacked vertically)
|
|
195
|
-
expect(janNorth.y).not.toBe(janSouth.y);
|
|
196
|
-
});
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
describe('grouped columns (stack: null)', () => {
|
|
200
|
-
it('produces marks for all data rows', () => {
|
|
201
|
-
const spec = makeGroupedColumnSpec();
|
|
202
|
-
const scales = computeScales(spec, chartArea, spec.data);
|
|
203
|
-
const marks = computeColumnMarks(spec, scales, chartArea, fullStrategy);
|
|
204
|
-
|
|
205
|
-
expect(marks).toHaveLength(6);
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
it('grouped columns within a category have different x positions', () => {
|
|
209
|
-
const spec = makeGroupedColumnSpec();
|
|
210
|
-
const scales = computeScales(spec, chartArea, spec.data);
|
|
211
|
-
const marks = computeColumnMarks(spec, scales, chartArea, fullStrategy);
|
|
212
|
-
|
|
213
|
-
const janNorth = marks.find(
|
|
214
|
-
(m) => m.aria.label.includes('Jan') && m.aria.label.includes('North'),
|
|
215
|
-
)!;
|
|
216
|
-
const janSouth = marks.find(
|
|
217
|
-
(m) => m.aria.label.includes('Jan') && m.aria.label.includes('South'),
|
|
218
|
-
)!;
|
|
219
|
-
|
|
220
|
-
expect(janNorth.x).not.toBe(janSouth.x);
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
it('grouped columns have subdivided widths (vs stacked full bandwidth)', () => {
|
|
224
|
-
const groupedSpec = makeGroupedColumnSpec();
|
|
225
|
-
const groupedScales = computeScales(groupedSpec, chartArea, groupedSpec.data);
|
|
226
|
-
const groupedMarks = computeColumnMarks(groupedSpec, groupedScales, chartArea, fullStrategy);
|
|
227
|
-
|
|
228
|
-
// With 2 groups, each sub-column should be narrower than full bandwidth
|
|
229
|
-
const stackedSpec = makeGroupedColumnSpec();
|
|
230
|
-
(stackedSpec.encoding.y as { stack?: string }).stack = 'zero';
|
|
231
|
-
const stackedScales = computeScales(stackedSpec, chartArea, stackedSpec.data);
|
|
232
|
-
const stackedMarks = computeColumnMarks(stackedSpec, stackedScales, chartArea, fullStrategy);
|
|
233
|
-
|
|
234
|
-
expect(groupedMarks[0].width).toBeLessThan(stackedMarks[0].width);
|
|
235
|
-
expect(groupedMarks[0].width).toBeGreaterThan(0);
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
it('grouped columns have cornerRadius 2 and no stackGroup', () => {
|
|
239
|
-
const spec = makeGroupedColumnSpec();
|
|
240
|
-
const scales = computeScales(spec, chartArea, spec.data);
|
|
241
|
-
const marks = computeColumnMarks(spec, scales, chartArea, fullStrategy);
|
|
242
|
-
|
|
243
|
-
for (const mark of marks) {
|
|
244
|
-
expect(mark.cornerRadius).toBe(2);
|
|
245
|
-
expect(mark.stackGroup).toBeUndefined();
|
|
246
|
-
}
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
it('scale domain covers max individual value, not stacked sum', () => {
|
|
250
|
-
const spec = makeGroupedColumnSpec();
|
|
251
|
-
const scales = computeScales(spec, chartArea, spec.data);
|
|
252
|
-
|
|
253
|
-
// Max individual value is 150 (Mar North), not 280 (Mar stacked sum)
|
|
254
|
-
const yScale = scales.y!.scale;
|
|
255
|
-
const domain = yScale.domain() as number[];
|
|
256
|
-
expect(domain[1]).toBeLessThanOrEqual(170); // some nice rounding above 150
|
|
257
|
-
});
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
describe('negative values', () => {
|
|
261
|
-
it('negative columns extend downward from baseline', () => {
|
|
262
|
-
const spec = makeNegativeColumnSpec();
|
|
263
|
-
const scales = computeScales(spec, chartArea, spec.data);
|
|
264
|
-
const marks = computeColumnMarks(spec, scales, chartArea, fullStrategy);
|
|
265
|
-
|
|
266
|
-
const q2 = marks.find((m) => m.aria.label.includes('Q2'))!;
|
|
267
|
-
const q1 = marks.find((m) => m.aria.label.includes('Q1'))!;
|
|
268
|
-
|
|
269
|
-
// Negative value column should start at a higher y (lower on page = baseline)
|
|
270
|
-
expect(q2.y).toBeGreaterThan(q1.y);
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
it('all columns have positive height', () => {
|
|
274
|
-
const spec = makeNegativeColumnSpec();
|
|
275
|
-
const scales = computeScales(spec, chartArea, spec.data);
|
|
276
|
-
const marks = computeColumnMarks(spec, scales, chartArea, fullStrategy);
|
|
277
|
-
|
|
278
|
-
for (const mark of marks) {
|
|
279
|
-
expect(mark.height).toBeGreaterThan(0);
|
|
280
|
-
}
|
|
281
|
-
});
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
describe('edge cases', () => {
|
|
285
|
-
it('returns empty array when no y encoding', () => {
|
|
286
|
-
const spec: NormalizedChartSpec = {
|
|
287
|
-
markType: 'bar',
|
|
288
|
-
markDef: { type: 'bar', orient: 'vertical' },
|
|
289
|
-
data: [{ month: 'Jan', sales: 100 }],
|
|
290
|
-
encoding: {
|
|
291
|
-
x: { field: 'month', type: 'nominal' },
|
|
292
|
-
},
|
|
293
|
-
chrome: {},
|
|
294
|
-
annotations: [],
|
|
295
|
-
responsive: true,
|
|
296
|
-
theme: {},
|
|
297
|
-
darkMode: 'off',
|
|
298
|
-
labels: { density: 'auto', format: '' },
|
|
299
|
-
};
|
|
300
|
-
const scales = computeScales(spec, chartArea, spec.data);
|
|
301
|
-
const marks = computeColumnMarks(spec, scales, chartArea, fullStrategy);
|
|
302
|
-
expect(marks).toHaveLength(0);
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
it('returns empty array for empty data', () => {
|
|
306
|
-
const spec: NormalizedChartSpec = {
|
|
307
|
-
markType: 'bar',
|
|
308
|
-
markDef: { type: 'bar', orient: 'vertical' },
|
|
309
|
-
data: [],
|
|
310
|
-
encoding: {
|
|
311
|
-
x: { field: 'month', type: 'nominal' },
|
|
312
|
-
y: { field: 'sales', type: 'quantitative' },
|
|
313
|
-
},
|
|
314
|
-
chrome: {},
|
|
315
|
-
annotations: [],
|
|
316
|
-
responsive: true,
|
|
317
|
-
theme: {},
|
|
318
|
-
darkMode: 'off',
|
|
319
|
-
labels: { density: 'auto', format: '' },
|
|
320
|
-
};
|
|
321
|
-
const scales = computeScales(spec, chartArea, spec.data);
|
|
322
|
-
const marks = computeColumnMarks(spec, scales, chartArea, fullStrategy);
|
|
323
|
-
expect(marks).toHaveLength(0);
|
|
324
|
-
});
|
|
325
|
-
});
|
|
326
|
-
});
|
|
327
|
-
|
|
328
|
-
// ---------------------------------------------------------------------------
|
|
329
|
-
// computeColumnLabels tests
|
|
330
|
-
// ---------------------------------------------------------------------------
|
|
331
|
-
|
|
332
|
-
describe('computeColumnLabels', () => {
|
|
333
|
-
it('produces one label per column mark', () => {
|
|
334
|
-
const spec = makeSimpleColumnSpec();
|
|
335
|
-
const scales = computeScales(spec, chartArea, spec.data);
|
|
336
|
-
const marks = computeColumnMarks(spec, scales, chartArea, fullStrategy);
|
|
337
|
-
const labels = computeColumnLabels(marks, chartArea);
|
|
338
|
-
|
|
339
|
-
expect(labels).toHaveLength(marks.length);
|
|
340
|
-
});
|
|
341
|
-
|
|
342
|
-
it('labels contain the value text', () => {
|
|
343
|
-
const spec = makeSimpleColumnSpec();
|
|
344
|
-
const scales = computeScales(spec, chartArea, spec.data);
|
|
345
|
-
const marks = computeColumnMarks(spec, scales, chartArea, fullStrategy);
|
|
346
|
-
const labels = computeColumnLabels(marks, chartArea);
|
|
347
|
-
|
|
348
|
-
const texts = labels.map((l) => l.text);
|
|
349
|
-
expect(texts).toContain('120');
|
|
350
|
-
expect(texts).toContain('200');
|
|
351
|
-
});
|
|
352
|
-
|
|
353
|
-
it('applies d3 label format string', () => {
|
|
354
|
-
const spec = makeSimpleColumnSpec();
|
|
355
|
-
const scales = computeScales(spec, chartArea, spec.data);
|
|
356
|
-
const marks = computeColumnMarks(spec, scales, chartArea, fullStrategy);
|
|
357
|
-
const labels = computeColumnLabels(marks, chartArea, 'auto', buildD3Formatter('$,.0f'));
|
|
358
|
-
|
|
359
|
-
const texts = labels.map((l) => l.text);
|
|
360
|
-
expect(texts).toContain('$120');
|
|
361
|
-
expect(texts).toContain('$200');
|
|
362
|
-
});
|
|
363
|
-
|
|
364
|
-
it('applies format with trailing zero trim (~)', () => {
|
|
365
|
-
const spec: NormalizedChartSpec = {
|
|
366
|
-
markType: 'bar',
|
|
367
|
-
markDef: { type: 'bar', orient: 'vertical' },
|
|
368
|
-
data: [
|
|
369
|
-
{ company: 'A', cap: 3.1 },
|
|
370
|
-
{ company: 'B', cap: 2.85 },
|
|
371
|
-
],
|
|
372
|
-
encoding: {
|
|
373
|
-
x: { field: 'company', type: 'nominal' },
|
|
374
|
-
y: { field: 'cap', type: 'quantitative' },
|
|
375
|
-
},
|
|
376
|
-
chrome: {},
|
|
377
|
-
annotations: [],
|
|
378
|
-
responsive: true,
|
|
379
|
-
theme: {},
|
|
380
|
-
darkMode: 'off',
|
|
381
|
-
labels: { density: 'all', format: '$,.2~f' },
|
|
382
|
-
};
|
|
383
|
-
const scales = computeScales(spec, chartArea, spec.data);
|
|
384
|
-
const marks = computeColumnMarks(spec, scales, chartArea, fullStrategy);
|
|
385
|
-
const labels = computeColumnLabels(marks, chartArea, 'all', buildD3Formatter('$,.2~f'));
|
|
386
|
-
|
|
387
|
-
const texts = labels.map((l) => l.text);
|
|
388
|
-
expect(texts).toContain('$3.1');
|
|
389
|
-
expect(texts).toContain('$2.85');
|
|
390
|
-
});
|
|
391
|
-
|
|
392
|
-
it('applies format with literal alpha suffix (e.g. "T")', () => {
|
|
393
|
-
const spec: NormalizedChartSpec = {
|
|
394
|
-
markType: 'bar',
|
|
395
|
-
markDef: { type: 'bar', orient: 'vertical' },
|
|
396
|
-
data: [
|
|
397
|
-
{ company: 'Apple', cap: 3.75 },
|
|
398
|
-
{ company: 'Meta', cap: 1.63 },
|
|
399
|
-
],
|
|
400
|
-
encoding: {
|
|
401
|
-
x: { field: 'company', type: 'nominal' },
|
|
402
|
-
y: { field: 'cap', type: 'quantitative' },
|
|
403
|
-
},
|
|
404
|
-
chrome: {},
|
|
405
|
-
annotations: [],
|
|
406
|
-
responsive: true,
|
|
407
|
-
theme: {},
|
|
408
|
-
darkMode: 'off',
|
|
409
|
-
labels: { density: 'all', format: '$,.2~fT' },
|
|
410
|
-
};
|
|
411
|
-
const scales = computeScales(spec, chartArea, spec.data);
|
|
412
|
-
const marks = computeColumnMarks(spec, scales, chartArea, fullStrategy);
|
|
413
|
-
const labels = computeColumnLabels(marks, chartArea, 'all', buildD3Formatter('$,.2~fT'));
|
|
414
|
-
|
|
415
|
-
const texts = labels.map((l) => l.text);
|
|
416
|
-
expect(texts).toContain('$3.75T');
|
|
417
|
-
expect(texts).toContain('$1.63T');
|
|
418
|
-
});
|
|
419
|
-
|
|
420
|
-
it('applies format with non-alpha suffix (e.g. "%")', () => {
|
|
421
|
-
const spec = makeSimpleColumnSpec();
|
|
422
|
-
const scales = computeScales(spec, chartArea, spec.data);
|
|
423
|
-
const marks = computeColumnMarks(spec, scales, chartArea, fullStrategy);
|
|
424
|
-
const labels = computeColumnLabels(marks, chartArea, 'auto', buildD3Formatter('.0f%'));
|
|
425
|
-
|
|
426
|
-
const texts = labels.map((l) => l.text);
|
|
427
|
-
expect(texts).toContain('120%');
|
|
428
|
-
expect(texts).toContain('200%');
|
|
429
|
-
});
|
|
430
|
-
});
|
|
431
|
-
|
|
432
|
-
// ---------------------------------------------------------------------------
|
|
433
|
-
// Stack mode tests
|
|
434
|
-
// ---------------------------------------------------------------------------
|
|
435
|
-
|
|
436
|
-
describe('stack modes', () => {
|
|
437
|
-
function makeStackedSpec(
|
|
438
|
-
stackMode: boolean | 'zero' | 'normalize' | 'center' | null | undefined,
|
|
439
|
-
): NormalizedChartSpec {
|
|
440
|
-
return {
|
|
441
|
-
markType: 'bar',
|
|
442
|
-
markDef: { type: 'bar', orient: 'vertical' },
|
|
443
|
-
data: [
|
|
444
|
-
{ cat: 'A', val: 30, grp: 'X' },
|
|
445
|
-
{ cat: 'A', val: 70, grp: 'Y' },
|
|
446
|
-
{ cat: 'B', val: 40, grp: 'X' },
|
|
447
|
-
{ cat: 'B', val: 60, grp: 'Y' },
|
|
448
|
-
],
|
|
449
|
-
encoding: {
|
|
450
|
-
x: { field: 'cat', type: 'nominal' },
|
|
451
|
-
y: { field: 'val', type: 'quantitative', stack: stackMode },
|
|
452
|
-
color: { field: 'grp', type: 'nominal' },
|
|
453
|
-
},
|
|
454
|
-
chrome: {},
|
|
455
|
-
annotations: [],
|
|
456
|
-
responsive: true,
|
|
457
|
-
theme: {},
|
|
458
|
-
darkMode: 'off',
|
|
459
|
-
labels: { density: 'auto', format: '' },
|
|
460
|
-
};
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
it('normalize: produces marks whose stacked fractions sum to ~1 per category', () => {
|
|
464
|
-
const spec = makeStackedSpec('normalize');
|
|
465
|
-
const scales = computeScales(spec, chartArea, spec.data);
|
|
466
|
-
const marks = computeColumnMarks(spec, scales, chartArea, fullStrategy);
|
|
467
|
-
|
|
468
|
-
expect(marks.length).toBe(4);
|
|
469
|
-
|
|
470
|
-
// Group by category (stackGroup) and verify normalized heights
|
|
471
|
-
const catA = marks.filter((m) => m.stackGroup === 'A');
|
|
472
|
-
const catB = marks.filter((m) => m.stackGroup === 'B');
|
|
473
|
-
expect(catA).toHaveLength(2);
|
|
474
|
-
expect(catB).toHaveLength(2);
|
|
475
|
-
|
|
476
|
-
// The y scale domain is [0, 1] for normalize. Verify marks don't overlap
|
|
477
|
-
// and each category's marks span the full [0, 1] range when mapped back.
|
|
478
|
-
// Category A: 30/(30+70)=0.3, 70/(30+70)=0.7
|
|
479
|
-
// Category B: 40/(40+60)=0.4, 60/(40+60)=0.6
|
|
480
|
-
// All marks should have non-zero height
|
|
481
|
-
for (const mark of marks) {
|
|
482
|
-
expect(mark.height).toBeGreaterThan(0);
|
|
483
|
-
}
|
|
484
|
-
});
|
|
485
|
-
|
|
486
|
-
it('center: produces marks with symmetric offsets around zero', () => {
|
|
487
|
-
const spec = makeStackedSpec('center');
|
|
488
|
-
const scales = computeScales(spec, chartArea, spec.data);
|
|
489
|
-
const marks = computeColumnMarks(spec, scales, chartArea, fullStrategy);
|
|
490
|
-
|
|
491
|
-
expect(marks.length).toBe(4);
|
|
492
|
-
|
|
493
|
-
// All marks should have non-zero height
|
|
494
|
-
for (const mark of marks) {
|
|
495
|
-
expect(mark.height).toBeGreaterThan(0);
|
|
496
|
-
}
|
|
497
|
-
});
|
|
498
|
-
|
|
499
|
-
it('existing zero mode still works correctly', () => {
|
|
500
|
-
const spec = makeStackedSpec('zero');
|
|
501
|
-
const scales = computeScales(spec, chartArea, spec.data);
|
|
502
|
-
const marks = computeColumnMarks(spec, scales, chartArea, fullStrategy);
|
|
503
|
-
|
|
504
|
-
expect(marks.length).toBe(4);
|
|
505
|
-
// All stacked marks should have stackGroup set
|
|
506
|
-
for (const mark of marks) {
|
|
507
|
-
expect(mark.stackGroup).toBeDefined();
|
|
508
|
-
}
|
|
509
|
-
});
|
|
510
|
-
|
|
511
|
-
it('null/false disables stacking (grouped mode)', () => {
|
|
512
|
-
const specNull = makeStackedSpec(null);
|
|
513
|
-
const scalesNull = computeScales(specNull, chartArea, specNull.data);
|
|
514
|
-
const marksNull = computeColumnMarks(specNull, scalesNull, chartArea, fullStrategy);
|
|
515
|
-
|
|
516
|
-
// Grouped mode: no stackGroup
|
|
517
|
-
for (const mark of marksNull) {
|
|
518
|
-
expect(mark.stackGroup).toBeUndefined();
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
const specFalse = makeStackedSpec(false);
|
|
522
|
-
const scalesFalse = computeScales(specFalse, chartArea, specFalse.data);
|
|
523
|
-
const marksFalse = computeColumnMarks(specFalse, scalesFalse, chartArea, fullStrategy);
|
|
524
|
-
|
|
525
|
-
for (const mark of marksFalse) {
|
|
526
|
-
expect(mark.stackGroup).toBeUndefined();
|
|
527
|
-
}
|
|
528
|
-
});
|
|
529
|
-
|
|
530
|
-
it('undefined (omitted) stacks by default (v8 VL-aligned default)', () => {
|
|
531
|
-
const spec = makeStackedSpec(undefined);
|
|
532
|
-
const scales = computeScales(spec, chartArea, spec.data);
|
|
533
|
-
const marks = computeColumnMarks(spec, scales, chartArea, fullStrategy);
|
|
534
|
-
|
|
535
|
-
expect(marks.length).toBe(4);
|
|
536
|
-
for (const mark of marks) {
|
|
537
|
-
expect(mark.stackGroup).toBeDefined();
|
|
538
|
-
}
|
|
539
|
-
});
|
|
540
|
-
});
|