@opendata-ai/openchart-engine 6.0.0 → 6.1.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 +155 -19
- package/dist/index.js +1513 -164
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/__test-fixtures__/specs.ts +6 -3
- package/src/__tests__/axes.test.ts +168 -4
- package/src/__tests__/compile-chart.test.ts +23 -12
- package/src/__tests__/compile-layer.test.ts +386 -0
- package/src/__tests__/dimensions.test.ts +6 -3
- package/src/__tests__/legend.test.ts +6 -3
- package/src/__tests__/scales.test.ts +176 -2
- package/src/annotations/__tests__/compute.test.ts +8 -4
- package/src/charts/bar/__tests__/compute.test.ts +12 -6
- package/src/charts/bar/compute.ts +21 -5
- package/src/charts/column/__tests__/compute.test.ts +14 -7
- package/src/charts/column/compute.ts +21 -6
- package/src/charts/dot/__tests__/compute.test.ts +10 -5
- package/src/charts/dot/compute.ts +10 -4
- package/src/charts/line/__tests__/compute.test.ts +102 -11
- package/src/charts/line/__tests__/curves.test.ts +51 -0
- package/src/charts/line/__tests__/labels.test.ts +2 -1
- package/src/charts/line/__tests__/mark-options.test.ts +175 -0
- package/src/charts/line/area.ts +19 -8
- package/src/charts/line/compute.ts +64 -25
- package/src/charts/line/curves.ts +40 -0
- package/src/charts/pie/__tests__/compute.test.ts +10 -5
- package/src/charts/pie/compute.ts +2 -1
- package/src/charts/rule/index.ts +127 -0
- package/src/charts/scatter/__tests__/compute.test.ts +10 -5
- package/src/charts/scatter/compute.ts +15 -5
- package/src/charts/text/index.ts +92 -0
- package/src/charts/tick/index.ts +84 -0
- package/src/charts/utils.ts +1 -1
- package/src/compile.ts +175 -23
- package/src/compiler/__tests__/compile.test.ts +4 -4
- package/src/compiler/__tests__/normalize.test.ts +4 -4
- package/src/compiler/__tests__/validate.test.ts +25 -26
- package/src/compiler/index.ts +1 -1
- package/src/compiler/normalize.ts +77 -4
- package/src/compiler/types.ts +6 -2
- package/src/compiler/validate.ts +167 -35
- package/src/graphs/__tests__/compile-graph.test.ts +2 -2
- package/src/graphs/compile-graph.ts +2 -2
- package/src/index.ts +17 -1
- package/src/layout/axes.ts +122 -20
- package/src/layout/dimensions.ts +15 -9
- package/src/layout/scales.ts +320 -31
- package/src/legend/compute.ts +9 -6
- package/src/tables/__tests__/compile-table.test.ts +1 -1
- package/src/tooltips/__tests__/compute.test.ts +10 -5
- package/src/tooltips/compute.ts +32 -14
- package/src/transforms/__tests__/bin.test.ts +88 -0
- package/src/transforms/__tests__/calculate.test.ts +146 -0
- package/src/transforms/__tests__/conditional.test.ts +109 -0
- package/src/transforms/__tests__/filter.test.ts +59 -0
- package/src/transforms/__tests__/index.test.ts +93 -0
- package/src/transforms/__tests__/predicates.test.ts +176 -0
- package/src/transforms/__tests__/timeunit.test.ts +129 -0
- package/src/transforms/bin.ts +87 -0
- package/src/transforms/calculate.ts +60 -0
- package/src/transforms/conditional.ts +46 -0
- package/src/transforms/filter.ts +17 -0
- package/src/transforms/index.ts +48 -0
- package/src/transforms/predicates.ts +90 -0
- package/src/transforms/timeunit.ts +88 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opendata-ai/openchart-engine",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.1.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",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"typecheck": "tsc --noEmit"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@opendata-ai/openchart-core": "6.
|
|
48
|
+
"@opendata-ai/openchart-core": "6.1.1",
|
|
49
49
|
"d3-array": "^3.2.0",
|
|
50
50
|
"d3-format": "^3.1.2",
|
|
51
51
|
"d3-interpolate": "^3.0.0",
|
|
@@ -48,7 +48,8 @@ export function makeCompactStrategy(): LayoutStrategy {
|
|
|
48
48
|
/** Single-series line chart with temporal x-axis. */
|
|
49
49
|
export function makeLineSpec(): NormalizedChartSpec {
|
|
50
50
|
return {
|
|
51
|
-
|
|
51
|
+
markType: 'line',
|
|
52
|
+
markDef: { type: 'line' },
|
|
52
53
|
data: [
|
|
53
54
|
{ date: '2020-01-01', value: 10, country: 'US' },
|
|
54
55
|
{ date: '2021-01-01', value: 40, country: 'US' },
|
|
@@ -82,7 +83,8 @@ export function makeLineSpec(): NormalizedChartSpec {
|
|
|
82
83
|
/** Basic bar chart (horizontal) with nominal y and quantitative x. */
|
|
83
84
|
export function makeBarSpec(): NormalizedChartSpec {
|
|
84
85
|
return {
|
|
85
|
-
|
|
86
|
+
markType: 'bar',
|
|
87
|
+
markDef: { type: 'bar' },
|
|
86
88
|
data: [
|
|
87
89
|
{ name: 'A', value: 10 },
|
|
88
90
|
{ name: 'B', value: 30 },
|
|
@@ -110,7 +112,8 @@ export function makeBarSpec(): NormalizedChartSpec {
|
|
|
110
112
|
/** Basic scatter chart with quantitative x and y. */
|
|
111
113
|
export function makeScatterSpec(): NormalizedChartSpec {
|
|
112
114
|
return {
|
|
113
|
-
|
|
115
|
+
markType: 'point',
|
|
116
|
+
markDef: { type: 'point' },
|
|
114
117
|
data: [
|
|
115
118
|
{ x: 10, y: 20 },
|
|
116
119
|
{ x: 30, y: 50 },
|
|
@@ -6,7 +6,8 @@ import { computeAxes, effectiveDensity, thinTicksUntilFit, ticksOverlap } from '
|
|
|
6
6
|
import { computeScales } from '../layout/scales';
|
|
7
7
|
|
|
8
8
|
const lineSpec: NormalizedChartSpec = {
|
|
9
|
-
|
|
9
|
+
markType: 'line',
|
|
10
|
+
markDef: { type: 'line' },
|
|
10
11
|
data: [
|
|
11
12
|
{ date: '2020-01-01', value: 100 },
|
|
12
13
|
{ date: '2021-01-01', value: 500 },
|
|
@@ -208,7 +209,8 @@ describe('computeAxes', () => {
|
|
|
208
209
|
it('propagates tickAngle from encoding to x-axis layout', () => {
|
|
209
210
|
const specWithAngle: NormalizedChartSpec = {
|
|
210
211
|
...lineSpec,
|
|
211
|
-
|
|
212
|
+
markType: 'bar',
|
|
213
|
+
markDef: { type: 'bar', orient: 'vertical' },
|
|
212
214
|
data: [
|
|
213
215
|
{ cat: 'California', val: 10 },
|
|
214
216
|
{ cat: 'New York', val: 20 },
|
|
@@ -439,7 +441,8 @@ describe('text-aware tick density', () => {
|
|
|
439
441
|
const categories = ['Alpha', 'Bravo', 'Charlie', 'Delta', 'Echo'];
|
|
440
442
|
const barSpec: NormalizedChartSpec = {
|
|
441
443
|
...lineSpec,
|
|
442
|
-
|
|
444
|
+
markType: 'bar',
|
|
445
|
+
markDef: { type: 'bar', orient: 'vertical' },
|
|
443
446
|
data: categories.map((cat, i) => ({ cat, val: (i + 1) * 10 })),
|
|
444
447
|
encoding: {
|
|
445
448
|
x: { field: 'cat', type: 'nominal' },
|
|
@@ -490,7 +493,8 @@ describe('text-aware tick density', () => {
|
|
|
490
493
|
it('passes measureText to auto-rotation detection', () => {
|
|
491
494
|
const barSpec: NormalizedChartSpec = {
|
|
492
495
|
...lineSpec,
|
|
493
|
-
|
|
496
|
+
markType: 'bar',
|
|
497
|
+
markDef: { type: 'bar', orient: 'vertical' },
|
|
494
498
|
data: [
|
|
495
499
|
{ cat: 'A', val: 10 },
|
|
496
500
|
{ cat: 'B', val: 20 },
|
|
@@ -509,3 +513,163 @@ describe('text-aware tick density', () => {
|
|
|
509
513
|
expect(axes.x!.tickAngle).toBe(-45);
|
|
510
514
|
});
|
|
511
515
|
});
|
|
516
|
+
|
|
517
|
+
// ---------------------------------------------------------------------------
|
|
518
|
+
// Axis config expansion tests
|
|
519
|
+
// ---------------------------------------------------------------------------
|
|
520
|
+
|
|
521
|
+
describe('axis config properties', () => {
|
|
522
|
+
it('uses title instead of deprecated label', () => {
|
|
523
|
+
const spec: NormalizedChartSpec = {
|
|
524
|
+
...lineSpec,
|
|
525
|
+
encoding: {
|
|
526
|
+
x: { field: 'date', type: 'temporal', axis: { title: 'Year' } },
|
|
527
|
+
y: { field: 'value', type: 'quantitative', axis: { title: 'Amount ($)' } },
|
|
528
|
+
},
|
|
529
|
+
};
|
|
530
|
+
const scales = computeScales(spec, chartArea, spec.data);
|
|
531
|
+
const axes = computeAxes(scales, chartArea, fullStrategy, theme);
|
|
532
|
+
|
|
533
|
+
expect(axes.x!.label).toBe('Year');
|
|
534
|
+
expect(axes.y!.label).toBe('Amount ($)');
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
it('falls back to deprecated label when title is not set', () => {
|
|
538
|
+
const spec: NormalizedChartSpec = {
|
|
539
|
+
...lineSpec,
|
|
540
|
+
encoding: {
|
|
541
|
+
x: { field: 'date', type: 'temporal', axis: { label: 'Old Label' } },
|
|
542
|
+
y: { field: 'value', type: 'quantitative' },
|
|
543
|
+
},
|
|
544
|
+
};
|
|
545
|
+
const scales = computeScales(spec, chartArea, spec.data);
|
|
546
|
+
const axes = computeAxes(scales, chartArea, fullStrategy, theme);
|
|
547
|
+
|
|
548
|
+
expect(axes.x!.label).toBe('Old Label');
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
it('prefers labelAngle over deprecated tickAngle', () => {
|
|
552
|
+
const spec: NormalizedChartSpec = {
|
|
553
|
+
...lineSpec,
|
|
554
|
+
encoding: {
|
|
555
|
+
x: {
|
|
556
|
+
field: 'date',
|
|
557
|
+
type: 'temporal',
|
|
558
|
+
axis: { labelAngle: -30, tickAngle: -90 },
|
|
559
|
+
},
|
|
560
|
+
y: { field: 'value', type: 'quantitative' },
|
|
561
|
+
},
|
|
562
|
+
};
|
|
563
|
+
const scales = computeScales(spec, chartArea, spec.data);
|
|
564
|
+
const axes = computeAxes(scales, chartArea, fullStrategy, theme);
|
|
565
|
+
|
|
566
|
+
// labelAngle takes precedence
|
|
567
|
+
expect(axes.x!.tickAngle).toBe(-30);
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
it('passes orient config to axis layout', () => {
|
|
571
|
+
const spec: NormalizedChartSpec = {
|
|
572
|
+
...lineSpec,
|
|
573
|
+
encoding: {
|
|
574
|
+
x: { field: 'date', type: 'temporal', axis: { orient: 'top' } },
|
|
575
|
+
y: { field: 'value', type: 'quantitative', axis: { orient: 'right' } },
|
|
576
|
+
},
|
|
577
|
+
};
|
|
578
|
+
const scales = computeScales(spec, chartArea, spec.data);
|
|
579
|
+
const axes = computeAxes(scales, chartArea, fullStrategy, theme);
|
|
580
|
+
|
|
581
|
+
expect(axes.x!.orient).toBe('top');
|
|
582
|
+
expect(axes.y!.orient).toBe('right');
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
it('passes domain and ticks visibility to axis layout', () => {
|
|
586
|
+
const spec: NormalizedChartSpec = {
|
|
587
|
+
...lineSpec,
|
|
588
|
+
encoding: {
|
|
589
|
+
x: { field: 'date', type: 'temporal', axis: { domain: false, ticks: false } },
|
|
590
|
+
y: { field: 'value', type: 'quantitative' },
|
|
591
|
+
},
|
|
592
|
+
};
|
|
593
|
+
const scales = computeScales(spec, chartArea, spec.data);
|
|
594
|
+
const axes = computeAxes(scales, chartArea, fullStrategy, theme);
|
|
595
|
+
|
|
596
|
+
expect(axes.x!.domainLine).toBe(false);
|
|
597
|
+
expect(axes.x!.tickMarks).toBe(false);
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
it('passes offset and padding configs to axis layout', () => {
|
|
601
|
+
const spec: NormalizedChartSpec = {
|
|
602
|
+
...lineSpec,
|
|
603
|
+
encoding: {
|
|
604
|
+
x: {
|
|
605
|
+
field: 'date',
|
|
606
|
+
type: 'temporal',
|
|
607
|
+
axis: { offset: 10, titlePadding: 8, labelPadding: 4 },
|
|
608
|
+
},
|
|
609
|
+
y: { field: 'value', type: 'quantitative' },
|
|
610
|
+
},
|
|
611
|
+
};
|
|
612
|
+
const scales = computeScales(spec, chartArea, spec.data);
|
|
613
|
+
const axes = computeAxes(scales, chartArea, fullStrategy, theme);
|
|
614
|
+
|
|
615
|
+
expect(axes.x!.offset).toBe(10);
|
|
616
|
+
expect(axes.x!.titlePadding).toBe(8);
|
|
617
|
+
expect(axes.x!.labelPadding).toBe(4);
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
it('passes labelOverlap and labelFlush to axis layout', () => {
|
|
621
|
+
const spec: NormalizedChartSpec = {
|
|
622
|
+
...lineSpec,
|
|
623
|
+
encoding: {
|
|
624
|
+
x: {
|
|
625
|
+
field: 'date',
|
|
626
|
+
type: 'temporal',
|
|
627
|
+
axis: { labelOverlap: 'parity', labelFlush: true },
|
|
628
|
+
},
|
|
629
|
+
y: { field: 'value', type: 'quantitative' },
|
|
630
|
+
},
|
|
631
|
+
};
|
|
632
|
+
const scales = computeScales(spec, chartArea, spec.data);
|
|
633
|
+
const axes = computeAxes(scales, chartArea, fullStrategy, theme);
|
|
634
|
+
|
|
635
|
+
expect(axes.x!.labelOverlap).toBe('parity');
|
|
636
|
+
expect(axes.x!.labelFlush).toBe(true);
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
it('uses explicit tick values when provided', () => {
|
|
640
|
+
const spec: NormalizedChartSpec = {
|
|
641
|
+
...lineSpec,
|
|
642
|
+
encoding: {
|
|
643
|
+
x: { field: 'date', type: 'temporal' },
|
|
644
|
+
y: {
|
|
645
|
+
field: 'value',
|
|
646
|
+
type: 'quantitative',
|
|
647
|
+
axis: { values: [0, 250, 500] },
|
|
648
|
+
},
|
|
649
|
+
},
|
|
650
|
+
};
|
|
651
|
+
const scales = computeScales(spec, chartArea, spec.data);
|
|
652
|
+
const axes = computeAxes(scales, chartArea, fullStrategy, theme);
|
|
653
|
+
|
|
654
|
+
// Should produce exactly 3 ticks matching our explicit values
|
|
655
|
+
expect(axes.y!.ticks.length).toBe(3);
|
|
656
|
+
expect(axes.y!.ticks[0].value).toBe(0);
|
|
657
|
+
expect(axes.y!.ticks[1].value).toBe(250);
|
|
658
|
+
expect(axes.y!.ticks[2].value).toBe(500);
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
it('defaults are undefined when axis config properties are not set', () => {
|
|
662
|
+
const scales = computeScales(lineSpec, chartArea, lineSpec.data);
|
|
663
|
+
const axes = computeAxes(scales, chartArea, fullStrategy, theme);
|
|
664
|
+
|
|
665
|
+
// All new properties should be undefined when not set
|
|
666
|
+
expect(axes.x!.orient).toBeUndefined();
|
|
667
|
+
expect(axes.x!.domainLine).toBeUndefined();
|
|
668
|
+
expect(axes.x!.tickMarks).toBeUndefined();
|
|
669
|
+
expect(axes.x!.offset).toBeUndefined();
|
|
670
|
+
expect(axes.x!.titlePadding).toBeUndefined();
|
|
671
|
+
expect(axes.x!.labelPadding).toBeUndefined();
|
|
672
|
+
expect(axes.x!.labelOverlap).toBeUndefined();
|
|
673
|
+
expect(axes.x!.labelFlush).toBeUndefined();
|
|
674
|
+
});
|
|
675
|
+
});
|
|
@@ -6,7 +6,7 @@ import { compileChart, compileGraph, compileTable } from '../compile';
|
|
|
6
6
|
// ---------------------------------------------------------------------------
|
|
7
7
|
|
|
8
8
|
const lineSpec = {
|
|
9
|
-
|
|
9
|
+
mark: 'line' as const,
|
|
10
10
|
data: [
|
|
11
11
|
{ date: '2020-01-01', value: 10, country: 'US' },
|
|
12
12
|
{ date: '2021-01-01', value: 40, country: 'US' },
|
|
@@ -26,7 +26,7 @@ const lineSpec = {
|
|
|
26
26
|
};
|
|
27
27
|
|
|
28
28
|
const barSpec = {
|
|
29
|
-
|
|
29
|
+
mark: 'bar' as const,
|
|
30
30
|
data: [
|
|
31
31
|
{ name: 'A', value: 10 },
|
|
32
32
|
{ name: 'B', value: 30 },
|
|
@@ -172,25 +172,36 @@ describe('compileChart', () => {
|
|
|
172
172
|
expect(layout.legend.position).toBe('top');
|
|
173
173
|
});
|
|
174
174
|
|
|
175
|
-
it('produces line
|
|
175
|
+
it('produces line marks with dataPoints (no PointMarks by default)', () => {
|
|
176
176
|
const layout = compileChart(lineSpec, { width: 600, height: 400 });
|
|
177
177
|
expect(layout.marks.length).toBeGreaterThan(0);
|
|
178
178
|
|
|
179
179
|
const lineMarks = layout.marks.filter((m) => m.type === 'line');
|
|
180
180
|
const pointMarks = layout.marks.filter((m) => m.type === 'point');
|
|
181
181
|
expect(lineMarks.length).toBeGreaterThan(0);
|
|
182
|
-
|
|
182
|
+
// Default: no PointMarks (voronoi overlay handles tooltips)
|
|
183
|
+
expect(pointMarks.length).toBe(0);
|
|
183
184
|
|
|
184
|
-
// Line marks should have points with valid coordinates
|
|
185
|
+
// Line marks should have points and dataPoints with valid coordinates
|
|
185
186
|
for (const mark of lineMarks) {
|
|
186
187
|
if (mark.type === 'line') {
|
|
187
188
|
expect(mark.points.length).toBeGreaterThan(0);
|
|
188
189
|
expect(mark.stroke).toBeTruthy();
|
|
189
190
|
expect(mark.strokeWidth).toBeGreaterThan(0);
|
|
191
|
+
expect(mark.dataPoints).toBeDefined();
|
|
192
|
+
expect(mark.dataPoints!.length).toBeGreaterThan(0);
|
|
190
193
|
}
|
|
191
194
|
}
|
|
192
195
|
});
|
|
193
196
|
|
|
197
|
+
it('produces PointMarks when mark.point is true', () => {
|
|
198
|
+
const specWithPoints = { ...lineSpec, mark: { type: 'line' as const, point: true as const } };
|
|
199
|
+
const layout = compileChart(specWithPoints, { width: 600, height: 400 });
|
|
200
|
+
|
|
201
|
+
const pointMarks = layout.marks.filter((m) => m.type === 'point');
|
|
202
|
+
expect(pointMarks.length).toBeGreaterThan(0);
|
|
203
|
+
});
|
|
204
|
+
|
|
194
205
|
it('includes accessibility metadata with meaningful content', () => {
|
|
195
206
|
const layout = compileChart(lineSpec, { width: 600, height: 400 });
|
|
196
207
|
expect(layout.a11y.altText).toContain('Line chart');
|
|
@@ -312,7 +323,7 @@ describe('compileChart', () => {
|
|
|
312
323
|
|
|
313
324
|
it('scale.clip filters data rows outside the y-axis domain', () => {
|
|
314
325
|
const spec = {
|
|
315
|
-
|
|
326
|
+
mark: 'point' as const,
|
|
316
327
|
data: [
|
|
317
328
|
{ x: 1, y: 5 },
|
|
318
329
|
{ x: 2, y: 15 },
|
|
@@ -337,7 +348,7 @@ describe('compileChart', () => {
|
|
|
337
348
|
|
|
338
349
|
it('scale.clip filters data rows outside the x-axis domain', () => {
|
|
339
350
|
const spec = {
|
|
340
|
-
|
|
351
|
+
mark: 'point' as const,
|
|
341
352
|
data: [
|
|
342
353
|
{ x: 1, y: 10 },
|
|
343
354
|
{ x: 5, y: 20 },
|
|
@@ -362,7 +373,7 @@ describe('compileChart', () => {
|
|
|
362
373
|
|
|
363
374
|
it('scale.clip=false does not filter data even with domain set', () => {
|
|
364
375
|
const spec = {
|
|
365
|
-
|
|
376
|
+
mark: 'point' as const,
|
|
366
377
|
data: [
|
|
367
378
|
{ x: 1, y: 5 },
|
|
368
379
|
{ x: 2, y: 15 },
|
|
@@ -446,7 +457,7 @@ describe('compileGraph', () => {
|
|
|
446
457
|
expect(() =>
|
|
447
458
|
compileGraph(
|
|
448
459
|
{
|
|
449
|
-
|
|
460
|
+
mark: 'point',
|
|
450
461
|
data: [{ x: 1, y: 2 }],
|
|
451
462
|
encoding: {
|
|
452
463
|
x: { field: 'x', type: 'quantitative' },
|
|
@@ -455,12 +466,12 @@ describe('compileGraph', () => {
|
|
|
455
466
|
},
|
|
456
467
|
{ width: 600, height: 400 },
|
|
457
468
|
),
|
|
458
|
-
).toThrow('compileGraph received a
|
|
469
|
+
).toThrow('compileGraph received a non-graph spec');
|
|
459
470
|
});
|
|
460
471
|
|
|
461
472
|
it('propagates tickAngle through the full compilation pipeline', () => {
|
|
462
473
|
const columnSpec = {
|
|
463
|
-
|
|
474
|
+
mark: 'bar' as const,
|
|
464
475
|
data: [
|
|
465
476
|
{ state: 'California', pop: 39000000 },
|
|
466
477
|
{ state: 'Texas', pop: 29000000 },
|
|
@@ -484,7 +495,7 @@ describe('compileGraph', () => {
|
|
|
484
495
|
|
|
485
496
|
it('reserves extra bottom margin for rotated x-axis labels', () => {
|
|
486
497
|
const baseColumnSpec = {
|
|
487
|
-
|
|
498
|
+
mark: 'bar' as const,
|
|
488
499
|
data: [
|
|
489
500
|
{ state: 'California', pop: 39000000 },
|
|
490
501
|
{ state: 'Texas', pop: 29000000 },
|