@opendata-ai/openchart-engine 1.2.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 +366 -0
- package/dist/index.js +4227 -0
- package/dist/index.js.map +1 -0
- package/package.json +62 -0
- package/src/__test-fixtures__/specs.ts +124 -0
- package/src/__tests__/axes.test.ts +114 -0
- package/src/__tests__/compile-chart.test.ts +337 -0
- package/src/__tests__/dimensions.test.ts +151 -0
- package/src/__tests__/legend.test.ts +113 -0
- package/src/__tests__/scales.test.ts +109 -0
- package/src/annotations/__tests__/compute.test.ts +454 -0
- package/src/annotations/compute.ts +603 -0
- package/src/charts/__tests__/registry.test.ts +110 -0
- package/src/charts/bar/__tests__/compute.test.ts +294 -0
- package/src/charts/bar/__tests__/labels.test.ts +75 -0
- package/src/charts/bar/compute.ts +205 -0
- package/src/charts/bar/index.ts +33 -0
- package/src/charts/bar/labels.ts +132 -0
- package/src/charts/column/__tests__/compute.test.ts +277 -0
- package/src/charts/column/compute.ts +282 -0
- package/src/charts/column/index.ts +33 -0
- package/src/charts/column/labels.ts +108 -0
- package/src/charts/dot/__tests__/compute.test.ts +344 -0
- package/src/charts/dot/compute.ts +257 -0
- package/src/charts/dot/index.ts +46 -0
- package/src/charts/dot/labels.ts +97 -0
- package/src/charts/line/__tests__/compute.test.ts +437 -0
- package/src/charts/line/__tests__/labels.test.ts +93 -0
- package/src/charts/line/area.ts +288 -0
- package/src/charts/line/compute.ts +177 -0
- package/src/charts/line/index.ts +68 -0
- package/src/charts/line/labels.ts +144 -0
- package/src/charts/pie/__tests__/compute.test.ts +276 -0
- package/src/charts/pie/compute.ts +234 -0
- package/src/charts/pie/index.ts +49 -0
- package/src/charts/pie/labels.ts +142 -0
- package/src/charts/registry.ts +64 -0
- package/src/charts/scatter/__tests__/compute.test.ts +304 -0
- package/src/charts/scatter/__tests__/trendline.test.ts +191 -0
- package/src/charts/scatter/compute.ts +124 -0
- package/src/charts/scatter/index.ts +41 -0
- package/src/charts/scatter/trendline.ts +100 -0
- package/src/charts/utils.ts +120 -0
- package/src/compile.ts +368 -0
- package/src/compiler/__tests__/compile.test.ts +87 -0
- package/src/compiler/__tests__/normalize.test.ts +210 -0
- package/src/compiler/__tests__/validate.test.ts +440 -0
- package/src/compiler/index.ts +47 -0
- package/src/compiler/normalize.ts +269 -0
- package/src/compiler/types.ts +148 -0
- package/src/compiler/validate.ts +581 -0
- package/src/graphs/__tests__/community.test.ts +228 -0
- package/src/graphs/__tests__/compile-graph.test.ts +315 -0
- package/src/graphs/__tests__/encoding.test.ts +314 -0
- package/src/graphs/community.ts +92 -0
- package/src/graphs/compile-graph.ts +291 -0
- package/src/graphs/encoding.ts +302 -0
- package/src/graphs/types.ts +98 -0
- package/src/index.ts +74 -0
- package/src/layout/axes.ts +194 -0
- package/src/layout/dimensions.ts +199 -0
- package/src/layout/gridlines.ts +84 -0
- package/src/layout/scales.ts +426 -0
- package/src/legend/compute.ts +186 -0
- package/src/tables/__tests__/bar-column.test.ts +147 -0
- package/src/tables/__tests__/category-colors.test.ts +153 -0
- package/src/tables/__tests__/compile-table.test.ts +208 -0
- package/src/tables/__tests__/format-cells.test.ts +126 -0
- package/src/tables/__tests__/heatmap.test.ts +124 -0
- package/src/tables/__tests__/pagination.test.ts +78 -0
- package/src/tables/__tests__/search.test.ts +94 -0
- package/src/tables/__tests__/sort.test.ts +107 -0
- package/src/tables/__tests__/sparkline.test.ts +122 -0
- package/src/tables/bar-column.ts +94 -0
- package/src/tables/category-colors.ts +67 -0
- package/src/tables/compile-table.ts +420 -0
- package/src/tables/format-cells.ts +110 -0
- package/src/tables/heatmap.ts +121 -0
- package/src/tables/pagination.ts +46 -0
- package/src/tables/search.ts +66 -0
- package/src/tables/sort.ts +69 -0
- package/src/tables/sparkline.ts +113 -0
- package/src/tables/utils.ts +16 -0
- package/src/tooltips/__tests__/compute.test.ts +328 -0
- package/src/tooltips/compute.ts +231 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import type { LegendLayout } from '@opendata-ai/openchart-core';
|
|
2
|
+
import { adaptTheme, resolveTheme } from '@opendata-ai/openchart-core';
|
|
3
|
+
import { describe, expect, it } from 'vitest';
|
|
4
|
+
import type { NormalizedChartSpec } from '../compiler/types';
|
|
5
|
+
import { computeDimensions } from '../layout/dimensions';
|
|
6
|
+
|
|
7
|
+
const baseSpec: NormalizedChartSpec = {
|
|
8
|
+
type: 'line',
|
|
9
|
+
data: [
|
|
10
|
+
{ date: '2020-01-01', value: 10 },
|
|
11
|
+
{ date: '2021-01-01', value: 20 },
|
|
12
|
+
],
|
|
13
|
+
encoding: {
|
|
14
|
+
x: { field: 'date', type: 'temporal' },
|
|
15
|
+
y: { field: 'value', type: 'quantitative' },
|
|
16
|
+
},
|
|
17
|
+
chrome: { title: { text: 'Test Chart' } },
|
|
18
|
+
annotations: [],
|
|
19
|
+
responsive: true,
|
|
20
|
+
theme: {},
|
|
21
|
+
darkMode: 'off',
|
|
22
|
+
labels: { density: 'auto', format: '' },
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const lightTheme = resolveTheme(baseSpec.theme);
|
|
26
|
+
const darkTheme = adaptTheme(lightTheme);
|
|
27
|
+
|
|
28
|
+
const emptyLegend: LegendLayout = {
|
|
29
|
+
position: 'top',
|
|
30
|
+
entries: [],
|
|
31
|
+
bounds: { x: 0, y: 0, width: 0, height: 0 },
|
|
32
|
+
labelStyle: {
|
|
33
|
+
fontFamily: 'Inter',
|
|
34
|
+
fontSize: 11,
|
|
35
|
+
fontWeight: 400,
|
|
36
|
+
fill: '#333',
|
|
37
|
+
lineHeight: 1.3,
|
|
38
|
+
},
|
|
39
|
+
swatchSize: 12,
|
|
40
|
+
swatchGap: 6,
|
|
41
|
+
entryGap: 16,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const rightLegend: LegendLayout = {
|
|
45
|
+
...emptyLegend,
|
|
46
|
+
position: 'right',
|
|
47
|
+
entries: [{ label: 'US', color: '#1b7fa3', shape: 'line' }],
|
|
48
|
+
bounds: { x: 500, y: 0, width: 100, height: 200 },
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const topLegend: LegendLayout = {
|
|
52
|
+
...emptyLegend,
|
|
53
|
+
position: 'top',
|
|
54
|
+
entries: [{ label: 'US', color: '#1b7fa3', shape: 'line' }],
|
|
55
|
+
bounds: { x: 0, y: 0, width: 400, height: 28 },
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
describe('computeDimensions', () => {
|
|
59
|
+
it('computes chart area within total dimensions', () => {
|
|
60
|
+
const dims = computeDimensions(baseSpec, { width: 600, height: 400 }, emptyLegend, lightTheme);
|
|
61
|
+
|
|
62
|
+
expect(dims.total).toEqual({ x: 0, y: 0, width: 600, height: 400 });
|
|
63
|
+
expect(dims.chartArea.width).toBeLessThan(600);
|
|
64
|
+
expect(dims.chartArea.height).toBeLessThan(400);
|
|
65
|
+
expect(dims.chartArea.width).toBeGreaterThan(0);
|
|
66
|
+
expect(dims.chartArea.height).toBeGreaterThan(0);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('accounts for chrome height in chart area', () => {
|
|
70
|
+
const noChrome: NormalizedChartSpec = { ...baseSpec, chrome: {} };
|
|
71
|
+
const withChrome = baseSpec;
|
|
72
|
+
|
|
73
|
+
const dimsNoChrome = computeDimensions(
|
|
74
|
+
noChrome,
|
|
75
|
+
{ width: 600, height: 400 },
|
|
76
|
+
emptyLegend,
|
|
77
|
+
lightTheme,
|
|
78
|
+
);
|
|
79
|
+
const dimsWithChrome = computeDimensions(
|
|
80
|
+
withChrome,
|
|
81
|
+
{ width: 600, height: 400 },
|
|
82
|
+
emptyLegend,
|
|
83
|
+
lightTheme,
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
// With chrome, the chart area should be shorter (less height available)
|
|
87
|
+
expect(dimsWithChrome.chartArea.height).toBeLessThan(dimsNoChrome.chartArea.height);
|
|
88
|
+
// Chrome should have nonzero top height
|
|
89
|
+
expect(dimsWithChrome.chrome.topHeight).toBeGreaterThan(0);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('reserves space for right-positioned legend', () => {
|
|
93
|
+
const withoutLegend = computeDimensions(
|
|
94
|
+
baseSpec,
|
|
95
|
+
{ width: 600, height: 400 },
|
|
96
|
+
emptyLegend,
|
|
97
|
+
lightTheme,
|
|
98
|
+
);
|
|
99
|
+
const withLegend = computeDimensions(
|
|
100
|
+
baseSpec,
|
|
101
|
+
{ width: 600, height: 400 },
|
|
102
|
+
rightLegend,
|
|
103
|
+
lightTheme,
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
expect(withLegend.chartArea.width).toBeLessThan(withoutLegend.chartArea.width);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('reserves space for top-positioned legend', () => {
|
|
110
|
+
const withoutLegend = computeDimensions(
|
|
111
|
+
baseSpec,
|
|
112
|
+
{ width: 600, height: 400 },
|
|
113
|
+
emptyLegend,
|
|
114
|
+
lightTheme,
|
|
115
|
+
);
|
|
116
|
+
const withLegend = computeDimensions(
|
|
117
|
+
baseSpec,
|
|
118
|
+
{ width: 600, height: 400 },
|
|
119
|
+
topLegend,
|
|
120
|
+
lightTheme,
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
expect(withLegend.chartArea.height).toBeLessThan(withoutLegend.chartArea.height);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('applies dark mode theme adaptation', () => {
|
|
127
|
+
const lightDims = computeDimensions(
|
|
128
|
+
baseSpec,
|
|
129
|
+
{ width: 600, height: 400 },
|
|
130
|
+
emptyLegend,
|
|
131
|
+
lightTheme,
|
|
132
|
+
);
|
|
133
|
+
const darkDims = computeDimensions(
|
|
134
|
+
baseSpec,
|
|
135
|
+
{ width: 600, height: 400, darkMode: true },
|
|
136
|
+
emptyLegend,
|
|
137
|
+
darkTheme,
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
expect(lightDims.theme.isDark).toBe(false);
|
|
141
|
+
expect(darkDims.theme.isDark).toBe(true);
|
|
142
|
+
expect(darkDims.theme.colors.background).not.toBe(lightDims.theme.colors.background);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('prevents negative chart area dimensions', () => {
|
|
146
|
+
// Tiny container
|
|
147
|
+
const dims = computeDimensions(baseSpec, { width: 50, height: 30 }, emptyLegend, lightTheme);
|
|
148
|
+
expect(dims.chartArea.width).toBeGreaterThanOrEqual(0);
|
|
149
|
+
expect(dims.chartArea.height).toBeGreaterThanOrEqual(0);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type { LayoutStrategy, Rect, ResolvedTheme } from '@opendata-ai/openchart-core';
|
|
2
|
+
import { resolveTheme } from '@opendata-ai/openchart-core';
|
|
3
|
+
import { describe, expect, it } from 'vitest';
|
|
4
|
+
import type { NormalizedChartSpec } from '../compiler/types';
|
|
5
|
+
import { computeLegend } from '../legend/compute';
|
|
6
|
+
|
|
7
|
+
const specWithColor: NormalizedChartSpec = {
|
|
8
|
+
type: 'line',
|
|
9
|
+
data: [
|
|
10
|
+
{ date: '2020', value: 10, country: 'US' },
|
|
11
|
+
{ date: '2021', value: 20, country: 'UK' },
|
|
12
|
+
{ date: '2022', value: 30, country: 'Germany' },
|
|
13
|
+
],
|
|
14
|
+
encoding: {
|
|
15
|
+
x: { field: 'date', type: 'temporal' },
|
|
16
|
+
y: { field: 'value', type: 'quantitative' },
|
|
17
|
+
color: { field: 'country', type: 'nominal' },
|
|
18
|
+
},
|
|
19
|
+
chrome: {},
|
|
20
|
+
annotations: [],
|
|
21
|
+
responsive: true,
|
|
22
|
+
theme: {},
|
|
23
|
+
darkMode: 'off',
|
|
24
|
+
labels: { density: 'auto', format: '' },
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const specWithoutColor: NormalizedChartSpec = {
|
|
28
|
+
...specWithColor,
|
|
29
|
+
encoding: {
|
|
30
|
+
x: { field: 'date', type: 'temporal' },
|
|
31
|
+
y: { field: 'value', type: 'quantitative' },
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const chartArea: Rect = { x: 50, y: 50, width: 500, height: 300 };
|
|
36
|
+
const theme: ResolvedTheme = resolveTheme();
|
|
37
|
+
|
|
38
|
+
const fullStrategy: LayoutStrategy = {
|
|
39
|
+
labelMode: 'all',
|
|
40
|
+
legendPosition: 'right',
|
|
41
|
+
annotationPosition: 'inline',
|
|
42
|
+
axisLabelDensity: 'full',
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const compactStrategy: LayoutStrategy = {
|
|
46
|
+
labelMode: 'none',
|
|
47
|
+
legendPosition: 'top',
|
|
48
|
+
annotationPosition: 'tooltip-only',
|
|
49
|
+
axisLabelDensity: 'minimal',
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
describe('computeLegend', () => {
|
|
53
|
+
it('derives entries from color encoding unique values', () => {
|
|
54
|
+
const legend = computeLegend(specWithColor, fullStrategy, theme, chartArea);
|
|
55
|
+
expect(legend.entries).toHaveLength(3);
|
|
56
|
+
expect(legend.entries.map((e) => e.label)).toEqual(['US', 'UK', 'Germany']);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('assigns distinct colors from the theme palette', () => {
|
|
60
|
+
const legend = computeLegend(specWithColor, fullStrategy, theme, chartArea);
|
|
61
|
+
const colors = legend.entries.map((e) => e.color);
|
|
62
|
+
const uniqueColors = new Set(colors);
|
|
63
|
+
expect(uniqueColors.size).toBe(3);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('returns empty entries when no color encoding', () => {
|
|
67
|
+
const legend = computeLegend(specWithoutColor, fullStrategy, theme, chartArea);
|
|
68
|
+
expect(legend.entries).toHaveLength(0);
|
|
69
|
+
expect(legend.bounds.width).toBe(0);
|
|
70
|
+
expect(legend.bounds.height).toBe(0);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('positions legend on right at full width', () => {
|
|
74
|
+
const legend = computeLegend(specWithColor, fullStrategy, theme, chartArea);
|
|
75
|
+
expect(legend.position).toBe('right');
|
|
76
|
+
expect(legend.bounds.width).toBeGreaterThan(0);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('positions legend on top at compact width', () => {
|
|
80
|
+
const legend = computeLegend(specWithColor, compactStrategy, theme, chartArea);
|
|
81
|
+
expect(legend.position).toBe('top');
|
|
82
|
+
expect(legend.bounds.height).toBeGreaterThan(0);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('uses correct swatch shape for chart type', () => {
|
|
86
|
+
const lineLegend = computeLegend(specWithColor, fullStrategy, theme, chartArea);
|
|
87
|
+
expect(lineLegend.entries[0].shape).toBe('line');
|
|
88
|
+
|
|
89
|
+
const barSpec: NormalizedChartSpec = {
|
|
90
|
+
...specWithColor,
|
|
91
|
+
type: 'bar',
|
|
92
|
+
encoding: {
|
|
93
|
+
x: { field: 'value', type: 'quantitative' },
|
|
94
|
+
y: { field: 'date', type: 'nominal' },
|
|
95
|
+
color: { field: 'country', type: 'nominal' },
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
const barLegend = computeLegend(barSpec, fullStrategy, theme, chartArea);
|
|
99
|
+
expect(barLegend.entries[0].shape).toBe('square');
|
|
100
|
+
|
|
101
|
+
const scatterSpec: NormalizedChartSpec = {
|
|
102
|
+
...specWithColor,
|
|
103
|
+
type: 'scatter',
|
|
104
|
+
encoding: {
|
|
105
|
+
x: { field: 'value', type: 'quantitative' },
|
|
106
|
+
y: { field: 'value', type: 'quantitative' },
|
|
107
|
+
color: { field: 'country', type: 'nominal' },
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
const scatterLegend = computeLegend(scatterSpec, fullStrategy, theme, chartArea);
|
|
111
|
+
expect(scatterLegend.entries[0].shape).toBe('circle');
|
|
112
|
+
});
|
|
113
|
+
});
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import type { NormalizedChartSpec } from '../compiler/types';
|
|
3
|
+
import { computeScales } from '../layout/scales';
|
|
4
|
+
|
|
5
|
+
const lineSpec: NormalizedChartSpec = {
|
|
6
|
+
type: 'line',
|
|
7
|
+
data: [
|
|
8
|
+
{ date: '2020-01-01', value: 10, country: 'US' },
|
|
9
|
+
{ date: '2021-01-01', value: 50, country: 'US' },
|
|
10
|
+
{ date: '2022-01-01', value: 30, country: 'UK' },
|
|
11
|
+
],
|
|
12
|
+
encoding: {
|
|
13
|
+
x: { field: 'date', type: 'temporal' },
|
|
14
|
+
y: { field: 'value', type: 'quantitative' },
|
|
15
|
+
color: { field: 'country', type: 'nominal' },
|
|
16
|
+
},
|
|
17
|
+
chrome: {},
|
|
18
|
+
annotations: [],
|
|
19
|
+
responsive: true,
|
|
20
|
+
theme: {},
|
|
21
|
+
darkMode: 'off',
|
|
22
|
+
labels: { density: 'auto', format: '' },
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const barSpec: NormalizedChartSpec = {
|
|
26
|
+
type: 'bar',
|
|
27
|
+
data: [
|
|
28
|
+
{ category: 'A', count: 10 },
|
|
29
|
+
{ category: 'B', count: 30 },
|
|
30
|
+
{ category: 'C', count: 20 },
|
|
31
|
+
],
|
|
32
|
+
encoding: {
|
|
33
|
+
x: { field: 'count', type: 'quantitative' },
|
|
34
|
+
y: { field: 'category', type: 'nominal' },
|
|
35
|
+
},
|
|
36
|
+
chrome: {},
|
|
37
|
+
annotations: [],
|
|
38
|
+
responsive: true,
|
|
39
|
+
theme: {},
|
|
40
|
+
darkMode: 'off',
|
|
41
|
+
labels: { density: 'auto', format: '' },
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const chartArea = { x: 50, y: 50, width: 500, height: 300 };
|
|
45
|
+
|
|
46
|
+
describe('computeScales', () => {
|
|
47
|
+
it('creates a time scale for temporal x encoding', () => {
|
|
48
|
+
const scales = computeScales(lineSpec, chartArea, lineSpec.data);
|
|
49
|
+
expect(scales.x).toBeDefined();
|
|
50
|
+
expect(scales.x!.type).toBe('time');
|
|
51
|
+
|
|
52
|
+
// The scale should map dates within the range
|
|
53
|
+
const pos = scales.x!.scale(new Date('2020-06-01'));
|
|
54
|
+
expect(pos).toBeGreaterThanOrEqual(chartArea.x);
|
|
55
|
+
expect(pos).toBeLessThanOrEqual(chartArea.x + chartArea.width);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('creates a linear scale for quantitative y encoding', () => {
|
|
59
|
+
const scales = computeScales(lineSpec, chartArea, lineSpec.data);
|
|
60
|
+
expect(scales.y).toBeDefined();
|
|
61
|
+
expect(scales.y!.type).toBe('linear');
|
|
62
|
+
|
|
63
|
+
// Y is inverted (SVG coordinates)
|
|
64
|
+
const domain = scales.y!.scale.domain();
|
|
65
|
+
expect(domain[0]).toBeLessThanOrEqual(0); // Should include 0
|
|
66
|
+
expect(domain[1]).toBeGreaterThanOrEqual(50);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('includes zero in quantitative domain by default', () => {
|
|
70
|
+
const scales = computeScales(lineSpec, chartArea, lineSpec.data);
|
|
71
|
+
const domain = scales.y!.scale.domain();
|
|
72
|
+
expect(domain[0]).toBe(0);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('creates an ordinal scale for color encoding', () => {
|
|
76
|
+
const scales = computeScales(lineSpec, chartArea, lineSpec.data);
|
|
77
|
+
expect(scales.color).toBeDefined();
|
|
78
|
+
expect(scales.color!.type).toBe('ordinal');
|
|
79
|
+
|
|
80
|
+
const usColor = scales.color!.scale('US');
|
|
81
|
+
const ukColor = scales.color!.scale('UK');
|
|
82
|
+
expect(typeof usColor).toBe('string');
|
|
83
|
+
expect(usColor).not.toBe(ukColor);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('creates band scales for bar chart categorical axis', () => {
|
|
87
|
+
const scales = computeScales(barSpec, chartArea, barSpec.data);
|
|
88
|
+
expect(scales.y).toBeDefined();
|
|
89
|
+
expect(scales.y!.type).toBe('band');
|
|
90
|
+
|
|
91
|
+
// Band scale should have bandwidth
|
|
92
|
+
expect(scales.y!.scale.bandwidth()).toBeGreaterThan(0);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('derives correct domain from data', () => {
|
|
96
|
+
const scales = computeScales(barSpec, chartArea, barSpec.data);
|
|
97
|
+
|
|
98
|
+
// Y should have all categories
|
|
99
|
+
const yDomain = scales.y!.scale.domain();
|
|
100
|
+
expect(yDomain).toContain('A');
|
|
101
|
+
expect(yDomain).toContain('B');
|
|
102
|
+
expect(yDomain).toContain('C');
|
|
103
|
+
|
|
104
|
+
// X domain should span 0 to >= max value
|
|
105
|
+
const xDomain = scales.x!.scale.domain();
|
|
106
|
+
expect(xDomain[0]).toBe(0);
|
|
107
|
+
expect(xDomain[1]).toBeGreaterThanOrEqual(30);
|
|
108
|
+
});
|
|
109
|
+
});
|