@opendata-ai/openchart-core 6.28.6 → 7.0.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 +671 -111
- package/dist/index.js +163 -66
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/src/colors/__tests__/contrast.test.ts +2 -2
- package/src/colors/__tests__/palettes.test.ts +22 -2
- package/src/colors/index.ts +1 -0
- package/src/colors/palettes.ts +52 -20
- package/src/helpers/spec-builders.ts +3 -1
- package/src/layout/chrome.ts +91 -10
- package/src/styles/base.css +11 -1
- package/src/styles/chrome.css +127 -2
- package/src/styles/dark.css +27 -10
- package/src/styles/tokens.css +57 -14
- package/src/styles/tooltip.css +66 -22
- package/src/theme/__tests__/dark-mode.test.ts +53 -8
- package/src/theme/__tests__/defaults.test.ts +43 -17
- package/src/theme/dark-mode.ts +76 -16
- package/src/theme/defaults.ts +44 -30
- package/src/theme/index.ts +1 -1
- package/src/theme/resolve.ts +2 -0
- package/src/types/__tests__/spec.test.ts +85 -18
- package/src/types/index.ts +16 -0
- package/src/types/layout.ts +151 -5
- package/src/types/spec.ts +517 -85
- package/src/types/theme.ts +5 -0
package/src/theme/defaults.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Default theme definition.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Editorial design system with cyan-led categorical palette, Inter
|
|
5
|
+
* Variable typography (weights 400/510/590), and zinc-based achromatic
|
|
6
|
+
* surfaces. Light is the default surface; dark adaptation lives in
|
|
7
|
+
* dark-mode.ts.
|
|
8
|
+
*
|
|
9
|
+
* resolveTheme() deep-merges user overrides onto this base.
|
|
7
10
|
*/
|
|
8
11
|
|
|
9
12
|
import { CATEGORICAL_PALETTE, DIVERGING_PALETTES, SEQUENTIAL_PALETTES } from '../colors/palettes';
|
|
@@ -11,7 +14,6 @@ import type { Theme } from '../types/theme';
|
|
|
11
14
|
|
|
12
15
|
/**
|
|
13
16
|
* The default theme. All fields are required and fully specified.
|
|
14
|
-
* resolveTheme() deep-merges user overrides onto this base.
|
|
15
17
|
*/
|
|
16
18
|
export const DEFAULT_THEME: Theme = {
|
|
17
19
|
colors: {
|
|
@@ -19,26 +21,32 @@ export const DEFAULT_THEME: Theme = {
|
|
|
19
21
|
sequential: SEQUENTIAL_PALETTES,
|
|
20
22
|
diverging: DIVERGING_PALETTES,
|
|
21
23
|
background: '#ffffff',
|
|
22
|
-
text: '#
|
|
23
|
-
gridline: '
|
|
24
|
-
axis
|
|
24
|
+
text: '#09090b',
|
|
25
|
+
gridline: 'rgba(0,0,0,0.06)',
|
|
26
|
+
// Used for axis lines/ticks AND axis tick label fill. Must clear WCAG AA
|
|
27
|
+
// contrast (4.5:1) on white because tick labels are rendered with this
|
|
28
|
+
// color. Zinc-500 hits ~5.7:1.
|
|
29
|
+
axis: '#71717a',
|
|
25
30
|
annotationFill: 'rgba(0,0,0,0.04)',
|
|
26
|
-
annotationText: '#
|
|
31
|
+
annotationText: '#71717a',
|
|
32
|
+
positive: '#16a34a',
|
|
33
|
+
negative: '#dc2626',
|
|
27
34
|
},
|
|
28
35
|
fonts: {
|
|
29
|
-
family:
|
|
36
|
+
family:
|
|
37
|
+
'"Inter Variable", Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
30
38
|
mono: '"JetBrains Mono", "Fira Code", "Cascadia Code", monospace',
|
|
31
39
|
sizes: {
|
|
32
|
-
title:
|
|
33
|
-
subtitle:
|
|
40
|
+
title: 26,
|
|
41
|
+
subtitle: 14,
|
|
34
42
|
body: 13,
|
|
35
43
|
small: 11,
|
|
36
44
|
axisTick: 11,
|
|
37
45
|
},
|
|
38
46
|
weights: {
|
|
39
47
|
normal: 400,
|
|
40
|
-
medium:
|
|
41
|
-
semibold:
|
|
48
|
+
medium: 510,
|
|
49
|
+
semibold: 590,
|
|
42
50
|
bold: 700,
|
|
43
51
|
},
|
|
44
52
|
},
|
|
@@ -49,37 +57,43 @@ export const DEFAULT_THEME: Theme = {
|
|
|
49
57
|
chartToFooter: 8,
|
|
50
58
|
axisMargin: 6,
|
|
51
59
|
},
|
|
52
|
-
borderRadius:
|
|
60
|
+
borderRadius: 2,
|
|
53
61
|
chrome: {
|
|
62
|
+
eyebrow: {
|
|
63
|
+
fontSize: 11,
|
|
64
|
+
fontWeight: 510,
|
|
65
|
+
color: '#06b6d4',
|
|
66
|
+
lineHeight: 1.4,
|
|
67
|
+
},
|
|
54
68
|
title: {
|
|
55
|
-
fontSize:
|
|
56
|
-
fontWeight:
|
|
57
|
-
color: '#
|
|
58
|
-
lineHeight: 1.
|
|
69
|
+
fontSize: 26,
|
|
70
|
+
fontWeight: 590,
|
|
71
|
+
color: '#09090b',
|
|
72
|
+
lineHeight: 1.15,
|
|
59
73
|
},
|
|
60
74
|
subtitle: {
|
|
61
|
-
fontSize:
|
|
75
|
+
fontSize: 14,
|
|
62
76
|
fontWeight: 400,
|
|
63
|
-
color: '#
|
|
64
|
-
lineHeight: 1.
|
|
77
|
+
color: '#71717a',
|
|
78
|
+
lineHeight: 1.45,
|
|
65
79
|
},
|
|
66
80
|
source: {
|
|
67
|
-
fontSize:
|
|
81
|
+
fontSize: 11,
|
|
68
82
|
fontWeight: 400,
|
|
69
|
-
color: '#
|
|
70
|
-
lineHeight: 1.
|
|
83
|
+
color: '#71717a',
|
|
84
|
+
lineHeight: 1.4,
|
|
71
85
|
},
|
|
72
86
|
byline: {
|
|
73
|
-
fontSize:
|
|
87
|
+
fontSize: 11,
|
|
74
88
|
fontWeight: 400,
|
|
75
|
-
color: '#
|
|
76
|
-
lineHeight: 1.
|
|
89
|
+
color: '#71717a',
|
|
90
|
+
lineHeight: 1.4,
|
|
77
91
|
},
|
|
78
92
|
footer: {
|
|
79
|
-
fontSize:
|
|
93
|
+
fontSize: 11,
|
|
80
94
|
fontWeight: 400,
|
|
81
|
-
color: '#
|
|
82
|
-
lineHeight: 1.
|
|
95
|
+
color: '#71717a',
|
|
96
|
+
lineHeight: 1.4,
|
|
83
97
|
},
|
|
84
98
|
},
|
|
85
99
|
};
|
package/src/theme/index.ts
CHANGED
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
* Theme module barrel export.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
export { adaptColorForDarkMode, adaptTheme } from './dark-mode';
|
|
5
|
+
export { adaptColorForDarkMode, adaptForLightLineStroke, adaptTheme } from './dark-mode';
|
|
6
6
|
export { DEFAULT_THEME } from './defaults';
|
|
7
7
|
export { resolveTheme } from './resolve';
|
package/src/theme/resolve.ts
CHANGED
|
@@ -118,6 +118,8 @@ function adaptChromeForDarkBg(theme: Theme, textColor: string): Theme {
|
|
|
118
118
|
return {
|
|
119
119
|
...theme,
|
|
120
120
|
chrome: {
|
|
121
|
+
// Eyebrow keeps its accent tint regardless of surface mode.
|
|
122
|
+
eyebrow: theme.chrome.eyebrow,
|
|
121
123
|
title: {
|
|
122
124
|
...theme.chrome.title,
|
|
123
125
|
color:
|
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
// Test data factories
|
|
23
23
|
// ---------------------------------------------------------------------------
|
|
24
24
|
|
|
25
|
-
function makeChartSpec(
|
|
25
|
+
function makeChartSpec(): ChartSpec {
|
|
26
26
|
return {
|
|
27
27
|
mark: 'line',
|
|
28
28
|
data: [
|
|
@@ -33,7 +33,6 @@ function makeChartSpec(overrides?: Partial<ChartSpec>): ChartSpec {
|
|
|
33
33
|
x: { field: 'date', type: 'temporal' },
|
|
34
34
|
y: { field: 'value', type: 'quantitative' },
|
|
35
35
|
},
|
|
36
|
-
...overrides,
|
|
37
36
|
};
|
|
38
37
|
}
|
|
39
38
|
|
|
@@ -70,22 +69,10 @@ function makeGraphSpec(overrides?: Partial<GraphSpec>): GraphSpec {
|
|
|
70
69
|
|
|
71
70
|
describe('isChartSpec', () => {
|
|
72
71
|
it('returns true for all mark types', () => {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
'point',
|
|
78
|
-
'circle',
|
|
79
|
-
'arc',
|
|
80
|
-
'text',
|
|
81
|
-
'rule',
|
|
82
|
-
'tick',
|
|
83
|
-
'rect',
|
|
84
|
-
'lollipop',
|
|
85
|
-
] as const;
|
|
86
|
-
|
|
87
|
-
for (const markType of markTypes) {
|
|
88
|
-
const spec = makeChartSpec({ mark: markType });
|
|
72
|
+
// isChartSpec is a runtime guard that only checks for the presence of `mark`.
|
|
73
|
+
// Use `as ChartSpec` here since we're testing the guard, not encoding validity.
|
|
74
|
+
for (const markType of MARK_TYPES) {
|
|
75
|
+
const spec = { mark: markType, data: [], encoding: {} } as ChartSpec;
|
|
89
76
|
expect(isChartSpec(spec)).toBe(true);
|
|
90
77
|
}
|
|
91
78
|
});
|
|
@@ -426,3 +413,83 @@ describe('type-level spec construction', () => {
|
|
|
426
413
|
expect(spec.edges).toHaveLength(1);
|
|
427
414
|
});
|
|
428
415
|
});
|
|
416
|
+
|
|
417
|
+
// ---------------------------------------------------------------------------
|
|
418
|
+
// Negative type tests (compile-time rejection verification)
|
|
419
|
+
//
|
|
420
|
+
// Each variable below is annotated with @ts-expect-error on the line that
|
|
421
|
+
// should be rejected. If any @ts-expect-error becomes "unused" (no error),
|
|
422
|
+
// TypeScript will fail the build — meaning the type guarantee regressed.
|
|
423
|
+
// ---------------------------------------------------------------------------
|
|
424
|
+
|
|
425
|
+
describe('type-level rejection', () => {
|
|
426
|
+
it('compiles with @ts-expect-error annotations intact (runtime no-op)', () => {
|
|
427
|
+
// Arc requires y + color. Missing color must error.
|
|
428
|
+
const _arcMissingColor: ChartSpec = {
|
|
429
|
+
mark: 'arc',
|
|
430
|
+
data: [],
|
|
431
|
+
// @ts-expect-error ArcEncoding requires color channel
|
|
432
|
+
encoding: {
|
|
433
|
+
y: { field: 'value', type: 'quantitative' },
|
|
434
|
+
},
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
// Text mark requires text channel. Missing text must error.
|
|
438
|
+
const _textMissingText: ChartSpec = {
|
|
439
|
+
mark: 'text',
|
|
440
|
+
data: [],
|
|
441
|
+
// @ts-expect-error TextEncoding requires text channel
|
|
442
|
+
encoding: {},
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
// Typed spec: field typo must error when TData is provided.
|
|
446
|
+
type SalesRow = { date: string; revenue: number };
|
|
447
|
+
const _typoField: ChartSpec<SalesRow> = {
|
|
448
|
+
mark: 'line',
|
|
449
|
+
data: [],
|
|
450
|
+
encoding: {
|
|
451
|
+
// @ts-expect-error 'dat' is not a key of SalesRow — did you mean 'date'?
|
|
452
|
+
x: { field: 'dat', type: 'temporal' },
|
|
453
|
+
y: { field: 'revenue', type: 'quantitative' },
|
|
454
|
+
},
|
|
455
|
+
};
|
|
456
|
+
|
|
457
|
+
// Valid typed spec must compile without errors.
|
|
458
|
+
const _validTyped: ChartSpec<SalesRow> = {
|
|
459
|
+
mark: 'line',
|
|
460
|
+
data: [{ date: '2024-01', revenue: 100 }],
|
|
461
|
+
encoding: {
|
|
462
|
+
x: { field: 'date', type: 'temporal' },
|
|
463
|
+
y: { field: 'revenue', type: 'quantitative' },
|
|
464
|
+
},
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
// Arc without theta must compile (theta is optional per MARK_ENCODING_RULES).
|
|
468
|
+
const _arcNoTheta: ChartSpec = {
|
|
469
|
+
mark: 'arc',
|
|
470
|
+
data: [],
|
|
471
|
+
encoding: {
|
|
472
|
+
y: { field: 'value', type: 'quantitative' },
|
|
473
|
+
color: { field: 'category', type: 'nominal' },
|
|
474
|
+
},
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
// Untyped spec (no TData param) must compile — no migration cost.
|
|
478
|
+
const _untypedSpec: ChartSpec = {
|
|
479
|
+
mark: 'line',
|
|
480
|
+
data: [],
|
|
481
|
+
encoding: {
|
|
482
|
+
x: { field: 'anything', type: 'temporal' },
|
|
483
|
+
y: { field: 'anything', type: 'quantitative' },
|
|
484
|
+
},
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
void _arcMissingColor;
|
|
488
|
+
void _textMissingText;
|
|
489
|
+
void _typoField;
|
|
490
|
+
void _validTyped;
|
|
491
|
+
void _arcNoTheta;
|
|
492
|
+
void _untypedSpec;
|
|
493
|
+
expect(true).toBe(true);
|
|
494
|
+
});
|
|
495
|
+
});
|
package/src/types/index.ts
CHANGED
|
@@ -42,6 +42,8 @@ export type {
|
|
|
42
42
|
ChartLayout,
|
|
43
43
|
CompileOptions,
|
|
44
44
|
CompileTableOptions,
|
|
45
|
+
EndpointLabelEntry,
|
|
46
|
+
EndpointLabelsLayout,
|
|
45
47
|
FlagTableCell,
|
|
46
48
|
GradientColorStop,
|
|
47
49
|
GradientLegendLayout,
|
|
@@ -69,6 +71,8 @@ export type {
|
|
|
69
71
|
ResolvedChromeElement,
|
|
70
72
|
ResolvedColumn,
|
|
71
73
|
ResolvedLabel,
|
|
74
|
+
ResolvedMetricBar,
|
|
75
|
+
ResolvedMetricCell,
|
|
72
76
|
RuleMarkLayout,
|
|
73
77
|
SankeyLayout,
|
|
74
78
|
SankeyLinkMark,
|
|
@@ -101,7 +105,10 @@ export type {
|
|
|
101
105
|
Annotation,
|
|
102
106
|
AnnotationAnchor,
|
|
103
107
|
AnnotationOffset,
|
|
108
|
+
ArcEncoding,
|
|
109
|
+
AreaEncoding,
|
|
104
110
|
AxisConfig,
|
|
111
|
+
BarEncoding,
|
|
105
112
|
BarListEncoding,
|
|
106
113
|
BarListSpec,
|
|
107
114
|
BarListSpecWithoutData,
|
|
@@ -116,6 +123,7 @@ export type {
|
|
|
116
123
|
Chrome,
|
|
117
124
|
ChromeText,
|
|
118
125
|
ChromeTextStyle,
|
|
126
|
+
CircleEncoding,
|
|
119
127
|
Condition,
|
|
120
128
|
ConditionalValueDef,
|
|
121
129
|
DarkMode,
|
|
@@ -123,6 +131,7 @@ export type {
|
|
|
123
131
|
Display,
|
|
124
132
|
Encoding,
|
|
125
133
|
EncodingChannel,
|
|
134
|
+
EndpointLabelsConfig,
|
|
126
135
|
FieldPredicate,
|
|
127
136
|
FieldType,
|
|
128
137
|
FilterPredicate,
|
|
@@ -143,14 +152,19 @@ export type {
|
|
|
143
152
|
LayerSpec,
|
|
144
153
|
LegendConfig,
|
|
145
154
|
LinearGradient,
|
|
155
|
+
LineEncoding,
|
|
146
156
|
LogicalAnd,
|
|
147
157
|
LogicalNot,
|
|
148
158
|
LogicalOr,
|
|
159
|
+
LollipopEncoding,
|
|
149
160
|
MarkDef,
|
|
150
161
|
MarkType,
|
|
162
|
+
Metric,
|
|
151
163
|
NodeOverride,
|
|
164
|
+
PointEncoding,
|
|
152
165
|
RadialGradient,
|
|
153
166
|
RangeAnnotation,
|
|
167
|
+
RectEncoding,
|
|
154
168
|
RefLineAnnotation,
|
|
155
169
|
RelativeTimeRef,
|
|
156
170
|
ResolveConfig,
|
|
@@ -167,7 +181,9 @@ export type {
|
|
|
167
181
|
TableSpec,
|
|
168
182
|
TableSpecWithoutData,
|
|
169
183
|
TextAnnotation,
|
|
184
|
+
TextEncoding,
|
|
170
185
|
ThemeConfig,
|
|
186
|
+
TickEncoding,
|
|
171
187
|
TileMapEncoding,
|
|
172
188
|
TileMapPalette,
|
|
173
189
|
TileMapSpec,
|
package/src/types/layout.ts
CHANGED
|
@@ -102,11 +102,13 @@ export interface ResolvedChrome {
|
|
|
102
102
|
/** Total height consumed by chrome elements below the chart area. */
|
|
103
103
|
bottomHeight: number;
|
|
104
104
|
/** Resolved chrome elements. Only present if specified in the spec. */
|
|
105
|
+
eyebrow?: ResolvedChromeElement;
|
|
105
106
|
title?: ResolvedChromeElement;
|
|
106
107
|
subtitle?: ResolvedChromeElement;
|
|
107
108
|
source?: ResolvedChromeElement;
|
|
108
109
|
byline?: ResolvedChromeElement;
|
|
109
110
|
footer?: ResolvedChromeElement;
|
|
111
|
+
brand?: ResolvedChromeElement;
|
|
110
112
|
}
|
|
111
113
|
|
|
112
114
|
// ---------------------------------------------------------------------------
|
|
@@ -167,6 +169,13 @@ export interface AxisLayout {
|
|
|
167
169
|
labelOverlap?: boolean | 'parity' | 'greedy';
|
|
168
170
|
/** Whether to flush labels to the axis edges. */
|
|
169
171
|
labelFlush?: boolean;
|
|
172
|
+
/**
|
|
173
|
+
* Where tick labels render relative to the chart area.
|
|
174
|
+
* `'inline'` puts y-axis labels above their gridlines at chart-area x and
|
|
175
|
+
* suppresses the axis line and tick marks. `'gutter'` (default) keeps the
|
|
176
|
+
* classic outside-the-area placement.
|
|
177
|
+
*/
|
|
178
|
+
tickPosition?: 'inline' | 'gutter';
|
|
170
179
|
}
|
|
171
180
|
|
|
172
181
|
// ---------------------------------------------------------------------------
|
|
@@ -175,12 +184,17 @@ export interface AxisLayout {
|
|
|
175
184
|
|
|
176
185
|
/** Accessibility attributes for a mark. */
|
|
177
186
|
export interface MarkAria {
|
|
178
|
-
/** ARIA label for the mark.
|
|
179
|
-
label
|
|
187
|
+
/** ARIA label for the mark. Optional when `decorative: true` — decorative
|
|
188
|
+
* marks render with `aria-hidden="true"` and don't need a label. */
|
|
189
|
+
label?: string;
|
|
180
190
|
/** Optional longer description. */
|
|
181
191
|
description?: string;
|
|
182
192
|
/** ARIA role override. */
|
|
183
193
|
role?: string;
|
|
194
|
+
/** When true, the mark is purely decorative (e.g. a sparkline endpoint dot
|
|
195
|
+
* that duplicates an existing data point). Renderers translate this into
|
|
196
|
+
* `aria-hidden="true"` and skip the mark from the screen-reader data table. */
|
|
197
|
+
decorative?: boolean;
|
|
184
198
|
}
|
|
185
199
|
|
|
186
200
|
/**
|
|
@@ -292,6 +306,13 @@ export interface RectMark {
|
|
|
292
306
|
strokeWidth?: number;
|
|
293
307
|
/** Corner radius. */
|
|
294
308
|
cornerRadius?: number;
|
|
309
|
+
/**
|
|
310
|
+
* Which corners receive `cornerRadius`. Defaults to all four when unset.
|
|
311
|
+
* Used by stacked bars/columns to round only the leading edge of the
|
|
312
|
+
* topmost (vertical) or rightmost (horizontal) segment so the seams
|
|
313
|
+
* between stacked segments stay square and visually contiguous.
|
|
314
|
+
*/
|
|
315
|
+
cornerRadiusSides?: { tl?: boolean; tr?: boolean; br?: boolean; bl?: boolean };
|
|
295
316
|
/** Original data row. */
|
|
296
317
|
data: Record<string, unknown>;
|
|
297
318
|
/** Resolved label. */
|
|
@@ -512,12 +533,14 @@ export interface ResolvedLabel {
|
|
|
512
533
|
connector?: {
|
|
513
534
|
/** Connector start (at the label). */
|
|
514
535
|
from: Point;
|
|
515
|
-
/** Connector end (
|
|
536
|
+
/** Connector end (pulled back from the data point so the line doesn't touch it). */
|
|
516
537
|
to: Point;
|
|
538
|
+
/** Actual data point the connector is calling out. Renderer uses this for the endpoint marker. */
|
|
539
|
+
endpoint?: Point;
|
|
517
540
|
/** Connector line color. */
|
|
518
541
|
stroke: string;
|
|
519
|
-
/** Connector style: straight line or
|
|
520
|
-
style: 'straight' | 'curve';
|
|
542
|
+
/** Connector style: straight line, curved arrow, or vertical drop-line through the data point. */
|
|
543
|
+
style: 'straight' | 'curve' | 'drop-line';
|
|
521
544
|
};
|
|
522
545
|
/** Background color behind the label text. */
|
|
523
546
|
background?: string;
|
|
@@ -553,6 +576,28 @@ export interface ResolvedAnnotation {
|
|
|
553
576
|
strokeWidth?: number;
|
|
554
577
|
/** Z-index for render ordering. Higher values render on top. */
|
|
555
578
|
zIndex?: number;
|
|
579
|
+
/**
|
|
580
|
+
* For text annotations: optional dot marker drawn at the connector's
|
|
581
|
+
* data-point endpoint. Coordinates match `label.connector.to` exactly.
|
|
582
|
+
*/
|
|
583
|
+
dot?: {
|
|
584
|
+
x: number;
|
|
585
|
+
y: number;
|
|
586
|
+
radius: number;
|
|
587
|
+
fill: string;
|
|
588
|
+
stroke: string;
|
|
589
|
+
strokeWidth: number;
|
|
590
|
+
};
|
|
591
|
+
/**
|
|
592
|
+
* For text annotations: optional muted subtitle rendered below the primary
|
|
593
|
+
* label. Positioned `lineHeight * primaryLineCount + gap` below `label.y`.
|
|
594
|
+
*/
|
|
595
|
+
subtitle?: {
|
|
596
|
+
text: string;
|
|
597
|
+
x: number;
|
|
598
|
+
y: number;
|
|
599
|
+
style: TextStyle;
|
|
600
|
+
};
|
|
556
601
|
}
|
|
557
602
|
|
|
558
603
|
// ---------------------------------------------------------------------------
|
|
@@ -595,6 +640,8 @@ export interface CategoricalLegendLayout extends BaseLegendLayout {
|
|
|
595
640
|
swatchGap: number;
|
|
596
641
|
/** Gap between entries. */
|
|
597
642
|
entryGap: number;
|
|
643
|
+
/** Fill color for the rounded chip background behind the colored bar. */
|
|
644
|
+
swatchChipFill: string;
|
|
598
645
|
}
|
|
599
646
|
|
|
600
647
|
/** A color stop in a gradient legend. */
|
|
@@ -622,6 +669,61 @@ export interface GradientLegendLayout extends BaseLegendLayout {
|
|
|
622
669
|
/** Resolved legend layout — either categorical (swatches) or gradient (continuous bar). */
|
|
623
670
|
export type LegendLayout = CategoricalLegendLayout | GradientLegendLayout;
|
|
624
671
|
|
|
672
|
+
// ---------------------------------------------------------------------------
|
|
673
|
+
// Endpoint labels (right-side per-series column for line/area charts)
|
|
674
|
+
// ---------------------------------------------------------------------------
|
|
675
|
+
|
|
676
|
+
/** A single resolved endpoint-label entry, fully positioned. */
|
|
677
|
+
export interface EndpointLabelEntry {
|
|
678
|
+
/** Series identifier (matches mark.seriesKey). */
|
|
679
|
+
seriesKey: string;
|
|
680
|
+
/** Pre-wrapped label text lines. */
|
|
681
|
+
labelLines: string[];
|
|
682
|
+
/** Formatted value string for the last data point. */
|
|
683
|
+
value: string;
|
|
684
|
+
/** Series color. */
|
|
685
|
+
color: string;
|
|
686
|
+
/** True pixel y of the last data point on the line. */
|
|
687
|
+
dataY: number;
|
|
688
|
+
/** Displaced y after the bidirectional collision sweep. */
|
|
689
|
+
labelY: number;
|
|
690
|
+
/** True when |labelY - dataY| exceeds the threshold (renderer draws a leader line). */
|
|
691
|
+
showLeader: boolean;
|
|
692
|
+
/** Optional anchor circle drawn on the line at the chart's right edge. */
|
|
693
|
+
marker?: {
|
|
694
|
+
x: number;
|
|
695
|
+
y: number;
|
|
696
|
+
fill: string;
|
|
697
|
+
stroke: string;
|
|
698
|
+
strokeWidth: number;
|
|
699
|
+
radius: number;
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
/** Resolved layout for the endpoint labels column. */
|
|
704
|
+
export interface EndpointLabelsLayout {
|
|
705
|
+
/** Per-series resolved entries. */
|
|
706
|
+
entries: EndpointLabelEntry[];
|
|
707
|
+
/** Bounding box for the column. */
|
|
708
|
+
bounds: Rect;
|
|
709
|
+
/** Style for the series-name text. */
|
|
710
|
+
labelStyle: TextStyle;
|
|
711
|
+
/** Style for the formatted value text. */
|
|
712
|
+
valueStyle: TextStyle;
|
|
713
|
+
/** Width of the colored swatch line drawn left of the label. */
|
|
714
|
+
swatchSize: number;
|
|
715
|
+
/** Horizontal gap between swatch and label text. */
|
|
716
|
+
gap: number;
|
|
717
|
+
/**
|
|
718
|
+
* Vertical gap between the last wrapped label line and the value text
|
|
719
|
+
* directly below it. Resolved by the engine so the renderer can't drift
|
|
720
|
+
* away from the height the collision sweep budgeted for each entry.
|
|
721
|
+
*/
|
|
722
|
+
valueGap: number;
|
|
723
|
+
/** Fill color for the rounded chip background behind the colored bar. */
|
|
724
|
+
swatchChipFill: string;
|
|
725
|
+
}
|
|
726
|
+
|
|
625
727
|
// ---------------------------------------------------------------------------
|
|
626
728
|
// Tooltips
|
|
627
729
|
// ---------------------------------------------------------------------------
|
|
@@ -680,6 +782,42 @@ export interface ResolvedAnimation {
|
|
|
680
782
|
annotationDelay: number;
|
|
681
783
|
}
|
|
682
784
|
|
|
785
|
+
// ---------------------------------------------------------------------------
|
|
786
|
+
// Metric bar (resolved)
|
|
787
|
+
// ---------------------------------------------------------------------------
|
|
788
|
+
|
|
789
|
+
/**
|
|
790
|
+
* A resolved KPI metric cell with computed positions for label, value, and
|
|
791
|
+
* supplementary text spans. Cells render as a row above the chart area.
|
|
792
|
+
*/
|
|
793
|
+
export interface ResolvedMetricCell {
|
|
794
|
+
/** Cell left x in layout coordinates. */
|
|
795
|
+
x: number;
|
|
796
|
+
/** Cell width. */
|
|
797
|
+
cellWidth: number;
|
|
798
|
+
/** Baseline y for the uppercase label. */
|
|
799
|
+
labelY: number;
|
|
800
|
+
/** Baseline y for the primary value. */
|
|
801
|
+
valueY: number;
|
|
802
|
+
/** The original metric spec (label/value/delta/etc.). */
|
|
803
|
+
metric: import('./spec').Metric;
|
|
804
|
+
/** True if value+delta+secondary would overflow cellWidth. Set by layout for diagnostics. */
|
|
805
|
+
overflowed: boolean;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
/**
|
|
809
|
+
* The full metric-bar layout. Present only when spec.metrics is supplied
|
|
810
|
+
* and the bar fits the container.
|
|
811
|
+
*/
|
|
812
|
+
export interface ResolvedMetricBar {
|
|
813
|
+
/** Top y of the metric row in layout coordinates. */
|
|
814
|
+
y: number;
|
|
815
|
+
/** Total reserved height of the row. */
|
|
816
|
+
height: number;
|
|
817
|
+
/** Cells laid out evenly across the chart width. */
|
|
818
|
+
cells: ResolvedMetricCell[];
|
|
819
|
+
}
|
|
820
|
+
|
|
683
821
|
// ---------------------------------------------------------------------------
|
|
684
822
|
// ChartLayout (the main engine output for charts)
|
|
685
823
|
// ---------------------------------------------------------------------------
|
|
@@ -697,6 +835,8 @@ export interface ChartLayout {
|
|
|
697
835
|
area: Rect;
|
|
698
836
|
/** Resolved chrome text elements with positions and styles. */
|
|
699
837
|
chrome: ResolvedChrome;
|
|
838
|
+
/** Resolved KPI metric bar. Present only when spec.metrics is supplied and fits. */
|
|
839
|
+
metrics?: ResolvedMetricBar;
|
|
700
840
|
/** Resolved axis layouts. */
|
|
701
841
|
axes: {
|
|
702
842
|
x?: AxisLayout;
|
|
@@ -710,6 +850,12 @@ export interface ChartLayout {
|
|
|
710
850
|
annotations: ResolvedAnnotation[];
|
|
711
851
|
/** Legend layout (position, entries, bounds). */
|
|
712
852
|
legend: LegendLayout;
|
|
853
|
+
/**
|
|
854
|
+
* Right-side endpoint labels column for multi-series line/area charts.
|
|
855
|
+
* Empty `entries` means the column is suppressed (single-series, opt-out, or
|
|
856
|
+
* auto-suppressed by the truth table).
|
|
857
|
+
*/
|
|
858
|
+
endpointLabels?: EndpointLabelsLayout;
|
|
713
859
|
/** Tooltip descriptors keyed by a mark identifier. */
|
|
714
860
|
tooltipDescriptors: Map<string, TooltipContent>;
|
|
715
861
|
/** Accessibility metadata. */
|