@astryxdesign/charts 0.6.2-canary.2b2113d → 0.6.2-canary.413fa55

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.
@@ -1 +1 @@
1
- {"version":3,"file":"ChartGrid.d.ts","sourceRoot":"","sources":["../src/ChartGrid.tsx"],"names":[],"mappings":"AAeA,MAAM,WAAW,cAAc;IAC7B,mDAAmD;IACnD,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,kDAAkD;IAClD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AASD;;;;;;;;;;;GAWG;AACH,wBAAgB,SAAS,CAAC,EACxB,UAAiB,EACjB,QAAgB,EAChB,SAAa,GACd,EAAE,cAAc,+BAmEhB"}
1
+ {"version":3,"file":"ChartGrid.d.ts","sourceRoot":"","sources":["../src/ChartGrid.tsx"],"names":[],"mappings":"AAgBA,MAAM,WAAW,cAAc;IAC7B,mDAAmD;IACnD,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,kDAAkD;IAClD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AASD;;;;;;;;;;;GAWG;AACH,wBAAgB,SAAS,CAAC,EACxB,UAAiB,EACjB,QAAgB,EAChB,SAAa,GACd,EAAE,cAAc,+BAmEhB"}
package/dist/ChartGrid.js CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  /**
4
4
  * @file ChartGrid.tsx (v2)
5
+ * @input Chart context scales and dimensions, guide visibility, and tick density
5
6
  * @output Grid lines behind chart marks
6
7
  * @position Child of Chart v2; reads scales from chart context
7
8
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astryxdesign/charts",
3
- "version": "0.6.2-canary.2b2113d",
3
+ "version": "0.6.2-canary.413fa55",
4
4
  "description": "Astryx charts — a config-model data visualization library (d3-based). Published to npm only under the @canary dist-tag for early testing; never released as a stable (latest) version.",
5
5
  "author": "Meta Open Source",
6
6
  "license": "MIT",
@@ -47,7 +47,7 @@
47
47
  },
48
48
  "peerDependencies": {
49
49
  "@stylexjs/stylex": ">=0.10.0",
50
- "@astryxdesign/core": "0.6.2-canary.2b2113d",
50
+ "@astryxdesign/core": "0.6.2-canary.413fa55",
51
51
  "react": ">=19.0.0",
52
52
  "react-dom": ">=19.0.0"
53
53
  },
@@ -57,7 +57,7 @@
57
57
  "d3-shape": "^3.2.0"
58
58
  },
59
59
  "devDependencies": {
60
- "@astryxdesign/cli": "0.6.2-canary.2b2113d",
60
+ "@astryxdesign/cli": "0.6.2-canary.413fa55",
61
61
  "@babel/cli": "^7.29.7",
62
62
  "@babel/core": "^7.29.7",
63
63
  "@babel/preset-react": "^7.29.7",
@@ -0,0 +1,114 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /** @type {import('@astryxdesign/cli/authoring').ComponentDoc} */
4
+
5
+ export const docs = {
6
+ name: 'ChartGrid',
7
+ displayName: 'Chart Grid',
8
+ group: 'Charts',
9
+ category: 'Data Visualization',
10
+ isHiddenFromOverview: true,
11
+ keywords: ['chart', 'grid', 'guides', 'axis', 'ticks', 'data visualization'],
12
+
13
+ usage: {
14
+ description:
15
+ 'ChartGrid draws horizontal and vertical guide lines from the scales owned by a parent Chart. Use it in the Chart grid slot so guides share the plot dimensions and tick values used by the chart.',
16
+ bestPractices: [
17
+ {
18
+ guidance: true,
19
+ description:
20
+ 'Pair ChartGrid with ChartAxis when readers need labelled values beside the guide lines.',
21
+ },
22
+ {
23
+ guidance: true,
24
+ description:
25
+ 'Use horizontal guides for value comparison and add vertical guides only when they help readers align categories or continuous x values.',
26
+ },
27
+ {
28
+ guidance: false,
29
+ description:
30
+ 'Use grid lines as the only way to communicate a value or distinction. Keep meaningful chart information in labels, marks, and the parent Chart alternative.',
31
+ },
32
+ {
33
+ guidance: false,
34
+ description:
35
+ 'Expect tickCount to thin categorical band centers. It requests density only for continuous scales; categorical vertical guides render once per band.',
36
+ },
37
+ ],
38
+ accessibility: [
39
+ {
40
+ name: 'Chart alternative',
41
+ category: 'Content',
42
+ criterion: '1.1.1 Non-text Content',
43
+ requirement: 'Required on the parent Chart',
44
+ states: ['All'],
45
+ description:
46
+ 'Chart owns the accessible image name and supported small-data table. Grid lines supplement that chart-level alternative rather than replacing it.',
47
+ },
48
+ {
49
+ name: 'Visual-only guides',
50
+ category: 'Color contrast',
51
+ criterion: '1.4.11 Non-text Contrast',
52
+ requirement: 'Do not carry information alone',
53
+ states: ['Horizontal', 'Vertical', 'Combined'],
54
+ description:
55
+ 'Grid lines are supporting guides. Preserve visible labels, marks, or another qualifying cue so understanding does not depend on the grid alone.',
56
+ },
57
+ ],
58
+ anatomy: [
59
+ {
60
+ name: 'Grid lines',
61
+ required: false,
62
+ description:
63
+ 'Horizontal lines at continuous y ticks and vertical lines at x ticks or categorical band centers.',
64
+ },
65
+ ],
66
+ },
67
+
68
+ props: [
69
+ {
70
+ name: 'horizontal',
71
+ type: 'boolean',
72
+ description:
73
+ 'Whether to draw horizontal guides at continuous y ticks other than zero.',
74
+ default: 'true',
75
+ },
76
+ {
77
+ name: 'vertical',
78
+ type: 'boolean',
79
+ description:
80
+ 'Whether to draw vertical guides at continuous x ticks or categorical band centers.',
81
+ default: 'false',
82
+ },
83
+ {
84
+ name: 'tickCount',
85
+ type: 'number',
86
+ description:
87
+ 'Approximate guide count requested from continuous scales. It does not thin categorical band centers.',
88
+ default: '5',
89
+ },
90
+ ],
91
+
92
+ examples: [
93
+ {
94
+ label: 'Horizontal guides',
95
+ code: `import {Chart, ChartGrid, bar} from '@astryxdesign/charts';
96
+
97
+ <Chart
98
+ data={monthlyRevenue}
99
+ xKey="month"
100
+ series={[bar('revenue')]}
101
+ grid={<ChartGrid />}
102
+ />;`,
103
+ },
104
+ {
105
+ label: 'Horizontal and vertical guides',
106
+ code: `<Chart
107
+ data={monthlyRevenue}
108
+ xKey="month"
109
+ series={[bar('revenue')]}
110
+ grid={<ChartGrid horizontal vertical tickCount={6} />}
111
+ />;`,
112
+ },
113
+ ],
114
+ };
@@ -0,0 +1,184 @@
1
+ ---
2
+ schema_version: 3
3
+ template_version: 5
4
+ kind: component
5
+ id: component:ChartGrid
6
+ authority: draft
7
+ archive_reason: null
8
+ superseded_by: null
9
+ approved_by: null
10
+ approved_at: null
11
+ owners: [cixzhang]
12
+ review_triggers: [public-api, behavior, layout, theming, accessibility]
13
+ verified_by:
14
+ [
15
+ packages/charts/src/ChartGrid.test.tsx,
16
+ apps/storybook/stories/charts/ChartGrid.stories.tsx,
17
+ ]
18
+ modules: []
19
+ families: []
20
+ design_specs: []
21
+ architecture:
22
+ [
23
+ architecture:component-test-sufficiency,
24
+ architecture:component-theming-surface,
25
+ ]
26
+ contributing: []
27
+ system_specs: [spec:AST-002, spec:AST-029]
28
+ ---
29
+
30
+ # ChartGrid component contract
31
+
32
+ ## Contract at a glance
33
+
34
+ | Area | Contract |
35
+ | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
36
+ | Public contract | No API shape or default changes. This draft records the current horizontal, vertical, and continuous-scale tick-density surface. |
37
+ | Behavior | Horizontal guides follow continuous y ticks except zero; vertical guides follow categorical x-band centers or continuous x ticks; both directions remain independently optional. |
38
+ | End-user impact | Chart readers receive guide lines aligned with the parent Chart's plot scales; this audit changes no rendered behavior. |
39
+ | Builder impact | Existing callsites remain valid and gain direct CLI documentation and focused evidence for current density fallbacks. |
40
+ | Compatibility/readiness | The package remains canary-only. This observational draft is non-authoritative, and exact rendered/accessibility evidence remains pending. |
41
+ | Review checks | Reject a changed public shape/default, a guide using a scale outside Chart context, a zero baseline drawn twice, or categorical guides no longer centered on their bands. |
42
+ | Governing rules | `spec:AST-029/FR3–FR7`, `architecture:component-test-sufficiency/INV1–INV11`, `architecture:component-theming-surface/INV1–INV6`, and `spec:AST-002`. |
43
+
44
+ This table is a review projection; the body below is authoritative.
45
+
46
+ ## Intent
47
+
48
+ ChartGrid renders passive Cartesian guide lines inside a Chart. It owns line
49
+ selection and placement from Chart-provided scales and plot dimensions while Chart
50
+ owns the coordinate system, rendering order, and chart-level alternative.
51
+
52
+ ## Compatibility and migration
53
+
54
+ - Released default preserved: yes at the canary surface; the package has no stable release.
55
+ - Compatibility class: no public API, default, or rendered-behavior change.
56
+ - Controlled/uncontrolled behavior: not applicable.
57
+ - Migration decision: none; this draft does not authorize a public API change.
58
+
59
+ Consumer migration instructions belong in consumer docs and release notes.
60
+
61
+ ## Ownership boundary
62
+
63
+ **Owns**
64
+
65
+ - Selecting horizontal and vertical guide values from Chart-provided scales.
66
+ - Positioning each guide across the current plot width or height.
67
+ - Omitting the horizontal zero guide that the paired axis may emphasize.
68
+ - Sanitizing continuous-scale tick requests before passing them to d3.
69
+
70
+ **Does not own / non-goals**
71
+
72
+ - Data, domains, scale construction, plot dimensions, or paint order — owned by `component:Chart`.
73
+ - Axis labels, tick marks, or plot-edge lines — owned by `component:ChartAxis`.
74
+ - The chart's accessible name, description, or data alternative — owned by `component:Chart` and the product callsite.
75
+ - Product-specific decisions about whether guides are needed.
76
+
77
+ ## Public concepts
78
+
79
+ | Concept | Closed values or states | Meaning | Availability by variant/orientation/state | Default | Owner | Stability | Invalid-value behavior |
80
+ | ------------------- | --------------------------------------- | ------------------------------------------------------------- | ----------------------------------------- | --------- | --------------------- | ------------ | ---------------------------------------------------------------------------------- |
81
+ | Horizontal guides | shown or hidden | Draws lines at continuous y ticks, excluding zero. | Every ChartGrid | shown | `component:ChartGrid` | experimental | Not applicable. |
82
+ | Vertical guides | shown or hidden | Draws lines at x ticks or categorical x-band centers. | Every ChartGrid | hidden | `component:ChartGrid` | experimental | Not applicable. |
83
+ | Continuous density | finite non-negative number | Requests an approximate line count from d3 continuous scales. | Horizontal guides and continuous x guides | `5` | `component:ChartGrid` | experimental | Fractions floor; values above 1000 cap; negative/non-finite values fall back to 5. |
84
+ | Categorical density | one guide per categorical x-band center | Keeps vertical guides aligned to every band. | Categorical x with vertical guides | all bands | `component:ChartGrid` | experimental | `tickCount` currently has no effect. |
85
+ | Chart context | inside a parent `Chart` | Supplies scales and current plot dimensions. | Always | none | `component:Chart` | experimental | Rendering outside Chart throws the shared context error. |
86
+
87
+ ## Behavioral and layout contract
88
+
89
+ Draft requirements identify their basis so observed code is not mistaken for an
90
+ intentional decision.
91
+
92
+ | ID | Candidate invariant | Basis | Draft review state |
93
+ | --- | -------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ | ------------------ |
94
+ | FR1 | ChartGrid MUST consume the current width, height, x scale, and y scale from its parent Chart context. | implementation, package composition, and focused tests | verify |
95
+ | FR2 | `horizontal` and `vertical` MUST independently enable their guide sets; enabling both MUST render both sets. | implementation, JSDoc, tests, and Storybook | verify |
96
+ | FR3 | Horizontal guides MUST use continuous y ticks, span the plot width, and omit y=0 so a paired axis does not receive a duplicate line. | implementation, JSDoc, and focused tests | verify |
97
+ | FR4 | Vertical guides MUST use categorical band centers for band x scales and d3 tick positions for continuous x scales, spanning the plot height. | implementation and focused tests | verify |
98
+ | FR5 | Continuous tick requests MUST be finite, non-negative integers no greater than 1000; zero yields no continuous guides and invalid values use five. | implementation, crash-prevention rationale, and tests | verify |
99
+ | FR6 | Guide paint MUST consume a semantic theme token. Whether grid lines become a public theme target remains unsettled. | implementation and current theming architecture | verify |
100
+
101
+ ### Allowed variation
102
+
103
+ - **AV1 — Continuous tick count.** d3 may choose a nearby set of values rather than exactly the requested number.
104
+ - **AV2 — Categorical values.** Each current x-band domain value contributes one centered vertical guide.
105
+
106
+ ### Representative states
107
+
108
+ | State | Required invariant | Allowed variation |
109
+ | -------------------------- | ---------------------------------------------------------------- | ---------------------------------------------- |
110
+ | Default | Horizontal guides only; continuous density defaults to five. | d3 chooses the exact y tick set. |
111
+ | Horizontal disabled | No horizontal guides. | Vertical guides may remain enabled. |
112
+ | Vertical categorical | One vertical guide at each x-band center. | Band width and center follow Chart's x scale. |
113
+ | Vertical continuous | One guide at each generated continuous x tick. | d3 chooses the exact x tick set. |
114
+ | Both directions | Horizontal and vertical sets render together. | Their exact counts may differ. |
115
+ | Zero continuous tick count | Continuous guide sets are empty. | Categorical vertical guides remain unaffected. |
116
+ | Invalid tick count | Continuous guide generation falls back to the default request. | d3 chooses the resulting exact ticks. |
117
+ | Narrow / RTL | Guides remain tied to physical data coordinates and plot bounds. | Plot width and data-domain order vary. |
118
+
119
+ ### Transformation and precedence order
120
+
121
+ - **ORD1 — Continuous density.** Validate finiteness and non-negativity → floor fractions → cap at 1000 → request d3 ticks.
122
+ - **ORD2 — Placement.** Select enabled directions → choose the scale path → derive tick or band-center positions → span the current opposite plot dimension.
123
+
124
+ ### Performance and resources
125
+
126
+ - **PR1 — Derived output.** Guide arrays remain render-derived and memoized from enabled directions, scales, dimensions, and sanitized density. ChartGrid owns no listener, observer, timer, or persistent resource.
127
+ - **PR2 — Bounded tick allocation.** A public density value cannot request more than 1000 continuous ticks from d3.
128
+
129
+ ## Accessibility contract
130
+
131
+ - **AR1 — Chart-level alternative.** ChartGrid renders passive supporting guides inside Chart's named image. Chart owns the accessible chart name and supported data-table alternative.
132
+ - **AR2 — No grid-only information.** Grid lines do not replace visible labels, meaningful marks, or the parent Chart alternative.
133
+
134
+ ## Design relationships
135
+
136
+ | Anatomy or state | Design requirement | Representation authority | Hierarchy role | Component contract |
137
+ | ---------------- | ----------------------------------------------------------------- | --------------------------------------------- | -------------- | ------------------ |
138
+ | Grid lines | Support position comparison without carrying chart meaning alone. | unsettled; no current design record is linked | supporting | FR2–FR6, AR1–AR2 |
139
+
140
+ ### Theming anatomy
141
+
142
+ <!-- anatomy-theming:v1 -->
143
+
144
+ ```json
145
+ {
146
+ "Grid lines": {
147
+ "none": {
148
+ "reason": "unsettled: ChartGrid consumes a semantic color token, but no current component or family contract decides whether grid lines are a public theme target."
149
+ }
150
+ }
151
+ }
152
+ ```
153
+
154
+ ## Family and system relationships
155
+
156
+ - No current family or design record governs ChartGrid.
157
+ - `architecture:component-test-sufficiency` owns rational public-state partitions, observable assertions, and audit evidence quality.
158
+ - `architecture:component-theming-surface` owns anatomy-to-target disposition; token consumption alone does not enroll ChartGrid in public component theming.
159
+ - `spec:AST-002` owns public API admission and prevents this audit from adding or reinterpreting a public choice.
160
+ - `spec:AST-029` owns this observational backfill, closed evidence receipt, safe remediation, and fail-closed eligibility report.
161
+
162
+ ## Verification map
163
+
164
+ | Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
165
+ | -------- | ------------------------------------------------------ | ----------------------------------------------------------- | ---------------------------------------------------------------------------------- | ---------------------------------- |
166
+ | FR1–FR2 | `ChartGrid.test.tsx`; `ChartGrid.stories.tsx` | default, horizontal off, vertical on, both, neither | A direction stops respecting its input or output escapes the parent Chart context. | `audit:ChartGrid/behavior` |
167
+ | FR3 | `ChartGrid.test.tsx` | positive and mixed-sign y domains | Guides stop spanning the plot or y=0 is drawn twice. | `audit:ChartGrid/behavior-layout` |
168
+ | FR4 | `ChartGrid.test.tsx`; `ChartGrid.stories.tsx` | categorical and continuous x | A band guide leaves its center or a continuous tick lands at the wrong coordinate. | `audit:ChartGrid/behavior-layout` |
169
+ | FR5, PR2 | focused zero/invalid-density tests and source review | zero and invalid requests; fractional and capped partitions | Invalid input crashes render or bypasses the bounded fallback. | `audit:ChartGrid/behavior-testing` |
170
+ | FR6 | strict lint; Storybook theme matrix remains pending | light, dark, and supported non-default themes | A raw color or component-side mode choice replaces semantic token paint. | `audit:ChartGrid/theming-design` |
171
+ | AR1–AR2 | Chart accessibility tests; package axe remains pending | named Chart, small-data table, grid enabled | Grid content replaces or becomes the only source of required chart information. | `audit:ChartGrid/accessibility` |
172
+
173
+ ## Decision log
174
+
175
+ None. This draft records observed behavior and evidence gaps only.
176
+
177
+ ## Open questions
178
+
179
+ - **OQ1 — Categorical density.** Should `tickCount` remain continuous-scale-only, or should vertical categorical guides gain a documented thinning contract aligned with ChartAxis? (`human-api`)
180
+
181
+ ## Content boundary
182
+
183
+ This file does not duplicate consumer prop tables/examples, current audit results,
184
+ implementation steps, or shared system rules. It links to their owners.
@@ -4,7 +4,8 @@
4
4
  * @file ChartGrid.test.tsx
5
5
  * @input Uses vitest, @testing-library/react, Chart, ChartGrid, bar mark
6
6
  * @output Functional tests for grid lines — horizontal default, the skipped
7
- * y=0 line, and vertical lines at band centers
7
+ * y=0 line, vertical lines at band/continuous positions, combined
8
+ * directions, and safe continuous tick-count handling
8
9
  * @position Colocated test for ChartGrid.tsx (issue #4295 viz coverage)
9
10
  */
10
11
 
@@ -95,6 +96,24 @@ describe('ChartGrid', () => {
95
96
  expect(lines).toHaveLength(8); // 5 horizontal + 3 vertical
96
97
  });
97
98
 
99
+ it('uses zero to request no continuous grid lines', () => {
100
+ const {lines} = renderGrid(<ChartGrid tickCount={0} />);
101
+ expect(lines).toHaveLength(0);
102
+ });
103
+
104
+ it('falls back to the default density for a non-finite tick count', () => {
105
+ const {lines} = renderGrid(
106
+ <ChartGrid tickCount={Number.POSITIVE_INFINITY} />,
107
+ );
108
+ expect(lines).toHaveLength(5);
109
+ });
110
+
111
+ it('caps a huge continuous tick request without throwing', () => {
112
+ const {lines} = renderGrid(<ChartGrid tickCount={1e9} />);
113
+ expect(lines.length).toBeGreaterThan(0);
114
+ expect(lines.length).toBeLessThanOrEqual(1001);
115
+ });
116
+
98
117
  it('places vertical lines at d3 tick values on a linear x scale', () => {
99
118
  const numeric = [
100
119
  {month: 0, sales: 1},
package/src/ChartGrid.tsx CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  /**
4
4
  * @file ChartGrid.tsx (v2)
5
+ * @input Chart context scales and dimensions, guide visibility, and tick density
5
6
  * @output Grid lines behind chart marks
6
7
  * @position Child of Chart v2; reads scales from chart context
7
8
  */
@@ -0,0 +1,146 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /** @type {import('@astryxdesign/cli/authoring').ComponentDoc} */
4
+
5
+ export const docs = {
6
+ name: 'ChartLegend',
7
+ displayName: 'Chart Legend',
8
+ group: 'Charts',
9
+ category: 'Data Visualization',
10
+ isHiddenFromOverview: true,
11
+ keywords: ['chart', 'legend', 'series', 'key', 'label', 'data visualization'],
12
+
13
+ usage: {
14
+ description:
15
+ 'ChartLegend pairs series labels with decorative mark-shaped color swatches. Use Chart legend options for the generated legend, or render ChartLegend directly when the caller already owns the legend items.',
16
+ bestPractices: [
17
+ {
18
+ guidance: true,
19
+ description:
20
+ 'Give every item a concise label that identifies the corresponding series without relying on its color name.',
21
+ },
22
+ {
23
+ guidance: true,
24
+ description:
25
+ 'Use start or end for a vertical legend and top or bottom for a wrapping horizontal legend.',
26
+ },
27
+ {
28
+ guidance: false,
29
+ description:
30
+ 'Use the legend as the chart’s only text alternative. Give the parent Chart an accessible name and preserve an equivalent data view or summary when the data requires one.',
31
+ },
32
+ {
33
+ guidance: false,
34
+ description:
35
+ 'Assume arbitrary series colors will remain distinguishable in every theme. Verify the complete chart and legend together against the surfaces where they render.',
36
+ },
37
+ ],
38
+ accessibility: [
39
+ {
40
+ name: 'Named list',
41
+ category: 'Semantics',
42
+ criterion: '1.3.1 Info and Relationships',
43
+ requirement: 'Built in',
44
+ states: ['Non-empty'],
45
+ description:
46
+ 'Entries render as items in a localized Chart legend list. Each visible label supplies the series name while its swatch stays hidden from assistive technology.',
47
+ },
48
+ {
49
+ name: 'Chart alternative',
50
+ category: 'Content',
51
+ criterion: '1.1.1 Non-text Content',
52
+ requirement: 'Required on the parent Chart',
53
+ states: ['All'],
54
+ description:
55
+ 'The legend supplements the parent Chart. It does not replace the Chart accessible name, data table, summary, or other equivalent alternative.',
56
+ },
57
+ {
58
+ name: 'Series distinction',
59
+ category: 'Color contrast',
60
+ criterion: '1.4.1 Use of Color; 1.4.11 Non-text Contrast',
61
+ requirement: 'Consumer verification required',
62
+ states: ['Non-empty'],
63
+ description:
64
+ 'Caller-supplied series colors must remain distinguishable on the rendered surface. Do not depend on color alone when otherwise identical marks need to be matched across the chart and legend.',
65
+ },
66
+ ],
67
+ anatomy: [
68
+ {
69
+ name: 'Legend list',
70
+ required: true,
71
+ description:
72
+ 'Wrapping horizontal or stacked vertical list that groups the legend entries.',
73
+ },
74
+ {
75
+ name: 'Legend entry',
76
+ required: true,
77
+ description: 'One series label paired with its decorative swatch.',
78
+ },
79
+ {
80
+ name: 'Series swatch',
81
+ required: true,
82
+ description:
83
+ 'Decorative square for bar marks or short line for other mark types, painted with the item color.',
84
+ },
85
+ {
86
+ name: 'Series label',
87
+ required: true,
88
+ description: 'Supporting text that names the series.',
89
+ },
90
+ ],
91
+ },
92
+
93
+ props: [
94
+ {
95
+ name: 'items',
96
+ type: 'LegendItem[]',
97
+ description:
98
+ 'Series entries to render. Each entry supplies a label, color, and optional mark type. An omitted or empty array renders nothing.',
99
+ default: '[]',
100
+ },
101
+ {
102
+ name: 'position',
103
+ type: "'top' | 'bottom' | 'start' | 'end'",
104
+ description:
105
+ 'Logical placement used by Chart and the legend orientation: top and bottom are horizontal; start and end are vertical.',
106
+ default: "'bottom'",
107
+ },
108
+ {
109
+ name: 'alignment',
110
+ type: "'start' | 'center' | 'end'",
111
+ description:
112
+ 'For top and bottom, distributes the row along its inline axis. For start and end, aligns each entry horizontally within the vertical list.',
113
+ default: "'start'",
114
+ },
115
+ ],
116
+
117
+ examples: [
118
+ {
119
+ label: 'Generated chart legend',
120
+ code: `import {Chart, bar, line} from '@astryxdesign/charts';
121
+
122
+ <Chart
123
+ data={monthlyRevenue}
124
+ xKey="month"
125
+ series={[
126
+ bar('revenue', {label: 'Revenue'}),
127
+ line('forecast', {label: 'Forecast'}),
128
+ ]}
129
+ legend={{position: 'top', alignment: 'start'}}
130
+ />;`,
131
+ },
132
+ {
133
+ label: 'Standalone legend',
134
+ code: `import {ChartLegend} from '@astryxdesign/charts';
135
+
136
+ <ChartLegend
137
+ items={[
138
+ {label: 'Revenue', color: '#3b82f6', type: 'bar'},
139
+ {label: 'Forecast', color: '#f59e0b', type: 'line'},
140
+ ]}
141
+ position="start"
142
+ alignment="center"
143
+ />;`,
144
+ },
145
+ ],
146
+ };
@@ -0,0 +1,203 @@
1
+ ---
2
+ schema_version: 3
3
+ template_version: 5
4
+ kind: component
5
+ id: component:ChartLegend
6
+ authority: draft
7
+ archive_reason: null
8
+ superseded_by: null
9
+ approved_by: null
10
+ approved_at: null
11
+ owners: [cixzhang]
12
+ review_triggers: [public-api, behavior, layout, theming, accessibility]
13
+ verified_by:
14
+ [
15
+ packages/charts/src/ChartLegend.test.tsx,
16
+ apps/storybook/stories/charts/Legend.stories.tsx,
17
+ ]
18
+ modules: []
19
+ families: []
20
+ design_specs: []
21
+ architecture:
22
+ [
23
+ architecture:component-test-sufficiency,
24
+ architecture:component-theming-surface,
25
+ ]
26
+ contributing: []
27
+ system_specs: [spec:AST-002, spec:AST-029]
28
+ ---
29
+
30
+ # ChartLegend component contract
31
+
32
+ ## Contract at a glance
33
+
34
+ | Area | Contract |
35
+ | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
36
+ | Public contract | No API shape or default changes. This draft records the current item, logical placement, alignment, list-semantics, and empty-output surface. |
37
+ | Behavior | Non-empty items render in order as a named list; top/bottom use a wrapping row, start/end use a column, alignment remains logical, and omitted or empty items render nothing. |
38
+ | End-user impact | Chart readers receive visible series names paired with decorative mark-shaped color keys; this audit changes no rendered behavior. |
39
+ | Builder impact | Existing callsites remain valid and gain direct CLI documentation for the shipped props, defaults, composition, and accessibility boundary. |
40
+ | Compatibility/readiness | The package remains canary-only. This observational draft is non-authoritative; series distinction by arbitrary caller colors remains unresolved. |
41
+ | Review checks | Reject a changed public shape/default, lost list semantics or localization, reordered items, a wrong orientation, or a new claim that color alone is sufficient. |
42
+ | Governing rules | `spec:AST-029/FR3–FR7`, `architecture:component-test-sufficiency/INV1–INV11`, `architecture:component-theming-surface/INV1–INV6`, and `spec:AST-002`. |
43
+
44
+ This table is a review projection; the body below is authoritative.
45
+
46
+ ## Intent
47
+
48
+ ChartLegend presents the visible key for a chart's series. It owns the ordered
49
+ legend list, entry layout, mark-shaped decorative swatches, series labels, and
50
+ the empty render. A parent Chart may derive its items and place the resulting
51
+ legend around the plot; callers may also render the component directly.
52
+
53
+ ## Compatibility and migration
54
+
55
+ - Released default preserved: yes at the canary surface; the package has no stable release.
56
+ - Compatibility class: no public API, default, or rendered-behavior change.
57
+ - Controlled/uncontrolled behavior: not applicable.
58
+ - Migration decision: none; this draft does not authorize a public API change.
59
+
60
+ Consumer migration instructions belong in consumer docs and release notes.
61
+
62
+ ## Ownership boundary
63
+
64
+ **Owns**
65
+
66
+ - Rendering non-empty legend items in caller order.
67
+ - Choosing horizontal or vertical layout from logical placement.
68
+ - Aligning the entry collection along the relevant logical axis.
69
+ - Pairing each visible series label with a decorative swatch shape and caller color.
70
+ - Localizing and exposing the legend list name.
71
+
72
+ **Does not own / non-goals**
73
+
74
+ - Series derivation, palette assignment, plot placement, or chart render order — owned by `component:Chart` and its series utilities.
75
+ - The chart's accessible name, data alternative, or product summary — owned by `component:Chart` and the product callsite.
76
+ - Custom legend interactions, selection, filtering, or visibility controls.
77
+ - A guarantee that arbitrary caller-supplied colors alone distinguish every series.
78
+
79
+ ## Public concepts
80
+
81
+ | Concept | Closed values or states | Meaning | Availability by variant/orientation/state | Default | Owner | Stability | Invalid-value behavior |
82
+ | ---------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------ | ----------------------------------------- | -------- | ----------------------- | ------------ | --------------------------------------------- |
83
+ | Legend items | ordered array or empty | Supplies each entry's label, color, and optional mark type. | Every ChartLegend | `[]` | `component:ChartLegend` | experimental | Omitted and empty arrays render nothing. |
84
+ | Logical position | `top`, `bottom`, `start`, `end` | Gives Chart placement intent and selects row or column orientation. | Non-empty | `bottom` | `component:ChartLegend` | experimental | Closed by the exported TypeScript union. |
85
+ | Alignment | `start`, `center`, `end` | Distributes a horizontal row inline or aligns entries horizontally inside a vertical list. | Non-empty | `start` | `component:ChartLegend` | experimental | Closed by the exported TypeScript union. |
86
+ | Swatch shape | square for `bar`; line for every other type | Visually echoes the item's mark category without adding AT content. | Each rendered item | line | `component:ChartSwatch` | experimental | Unknown and omitted types use the line shape. |
87
+ | List semantics | localized list with one item per entry | Groups the visible labels as one named legend. | Non-empty | built in | `component:ChartLegend` | experimental | Not applicable. |
88
+
89
+ ## Behavioral and layout contract
90
+
91
+ Draft requirements identify their basis so observed code is not mistaken for an
92
+ intentional decision.
93
+
94
+ | ID | Candidate invariant | Basis | Draft review state |
95
+ | --- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ | ------------------ |
96
+ | FR1 | ChartLegend MUST render nothing when `items` is omitted or empty and MUST otherwise preserve item order and duplicate labels. | implementation and focused tests | verify |
97
+ | FR2 | Each rendered item MUST remain one visible label plus one assistive-technology-hidden ChartSwatch inside a list item. | implementation, focused tests, and browser accessibility run | verify |
98
+ | FR3 | The root MUST remain a localized named list with exactly one list item per supplied entry. | implementation, locale catalog, focused tests, and axe | verify |
99
+ | FR4 | `top` and `bottom` MUST use a wrapping horizontal row; `start` and `end` MUST use a vertical column. | implementation and receipted browser matrix | verify |
100
+ | FR5 | Alignment MUST remain logical: for `top` and `bottom`, it distributes the row along the inline axis; for `start` and `end`, it aligns entries horizontally within the vertical list, including under RTL. | implementation and receipted LTR/RTL browser evidence | verify |
101
+ | FR6 | Swatch shape MUST be square for `bar` and a short line for every other or omitted type; caller color supplies its paint. | ChartSwatch implementation and focused tests | verify |
102
+ | FR7 | Long labels MUST wrap without horizontal viewport overflow at 320 CSS pixels; no fixed-width truncation contract is currently exposed. | receipted browser evidence | verify |
103
+
104
+ ### Allowed variation
105
+
106
+ - **AV1 — Available space.** Horizontal entries wrap to additional rows as the containing width narrows; vertical entry labels may wrap within the available inline size.
107
+ - **AV2 — Series paint.** Swatch color is caller-owned and may vary by chart palette and rendered surface, subject to the unresolved distinction requirement below.
108
+
109
+ ### Representative states
110
+
111
+ | State | Required invariant | Allowed variation |
112
+ | --------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- |
113
+ | Omitted / empty items | No legend DOM is rendered. | Surrounding Chart layout may omit its legend slot. |
114
+ | Bottom or top | Entries form a wrapping horizontal list. | Logical alignment and wrapping depend on available width. |
115
+ | Start or end | Entries form a vertical list. | Alignment changes each entry's horizontal position within the list. |
116
+ | Duplicate labels | Every supplied item remains present and ordered. | Internal React keys may disambiguate equal labels. |
117
+ | Long narrow labels | Labels wrap and the page remains free of horizontal overflow. | Entry and list height grow with content. |
118
+ | RTL | Logical start/end behavior follows the computed document direction. | Visual item order follows the flex and direction model. |
119
+
120
+ ### Transformation and precedence order
121
+
122
+ - **ORD1 — Render.** Default omitted props → return nothing for zero items → derive orientation from logical position → map items in caller order → apply logical alignment to the list root.
123
+ - **ORD2 — Entry.** Map mark type to a swatch shape → paint the swatch with caller color → render the caller label as supporting text.
124
+
125
+ ### Performance and resources
126
+
127
+ - **PR1 — Derived output.** ChartLegend remains render-derived. It owns no state, Effect, listener, observer, timer, request, or persistent resource.
128
+
129
+ ## Accessibility contract
130
+
131
+ - **AR1 — Named structure.** A non-empty legend exposes one localized named list and one list item for every visible series label.
132
+ - **AR2 — Decorative swatches.** Series swatches stay hidden from assistive technology; the adjacent visible text supplies each entry name.
133
+ - **AR3 — Chart-level alternative.** The legend supplements rather than replaces the parent Chart's accessible name and equivalent data representation.
134
+ - **AR4 — Composed association evidence.** Standalone ChartLegend evidence does not establish how readers associate legend entries with rendered chart marks. Evaluate same-mark-shape series in a composed Chart or product callsite before deciding whether an additional visual cue is needed or which layer owns it.
135
+
136
+ ## Design relationships
137
+
138
+ | Anatomy or state | Design requirement | Representation authority | Hierarchy role | Component contract |
139
+ | ---------------- | ----------------------------------------------------------------- | --------------------------------------------- | -------------- | ------------------ |
140
+ | Legend list | Group related series entries without adding an interactive state. | unsettled; no current design record is linked | supporting | FR1, FR3–FR5 |
141
+ | Legend entry | Keep each label associated with one series key. | unsettled; no current design record is linked | supporting | FR1–FR3 |
142
+ | Series swatch | Echo the chart mark while remaining decorative to AT. | unsettled; caller color is current behavior | supporting | FR2, FR6, AR2–AR4 |
143
+ | Series label | Name the series as readable supporting text. | delegated to Text | supporting | FR2, AR1–AR2 |
144
+
145
+ ### Theming anatomy
146
+
147
+ <!-- anatomy-theming:v1 -->
148
+
149
+ ```json
150
+ {
151
+ "Legend list": {
152
+ "delegatesTo": {"owner": "component:Stack", "target": "stack"}
153
+ },
154
+ "Legend entry": {
155
+ "delegatesTo": {"owner": "component:Stack", "target": "stack"}
156
+ },
157
+ "Series swatch": {
158
+ "none": {
159
+ "reason": "unsettled: ChartSwatch owns the visible painter but exposes no current public target; this draft does not decide future reachability."
160
+ }
161
+ },
162
+ "Series label": {
163
+ "delegatesTo": {"owner": "component:Text", "target": "text"}
164
+ }
165
+ }
166
+ ```
167
+
168
+ ChartLegend delegates list and entry layout to Stack's existing target, and
169
+ series labels to Text. ChartSwatch is the visible swatch painter but does not
170
+ currently expose a public theme target. Whether that anatomy should become
171
+ reachable remains with the ChartSwatch and theming owners.
172
+
173
+ ## Family and system relationships
174
+
175
+ - No current family or design record governs ChartLegend.
176
+ - `architecture:component-test-sufficiency` owns rational public-state partitions, observable assertions, and audit evidence quality.
177
+ - `architecture:component-theming-surface` owns anatomy-to-target disposition; composition from themed primitives does not enroll ChartLegend in public component theming.
178
+ - `spec:AST-002` owns public API admission and prevents this audit from adding or reinterpreting a public choice.
179
+ - `spec:AST-029` owns this observational backfill, closed evidence receipt, safe remediation, and fail-closed eligibility report.
180
+
181
+ ## Verification map
182
+
183
+ | Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
184
+ | ------------ | -------------------------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------- | ------------------------------------------ |
185
+ | FR1–FR3 | `ChartLegend.test.tsx`; focused axe run | omitted, empty, non-empty, localized, duplicate | Empty output leaves DOM, an entry disappears, or list semantics/name are lost. | `audit:ChartLegend/accessibility-behavior` |
186
+ | FR4–FR5, FR7 | receipted Storybook browser matrix; RTL geometry receipt | row, column, all alignments, 320px long text, RTL | Orientation, logical alignment, wrapping, or viewport containment changes. | `audit:ChartLegend/layout-responsive-rtl` |
187
+ | FR6 | `ChartLegend.test.tsx`; `ChartSwatch.test.tsx` | bar, line, omitted and unknown types | A bar loses its square, another type gains one, or swatch paint stops following color. | `audit:ChartLegend/behavior-design` |
188
+ | AR1–AR3 | role/name DOM assertions; focused axe run; Chart tests | standalone and Chart-composed legend | Labels leave the named list or the legend replaces the Chart alternative. | `audit:ChartLegend/accessibility` |
189
+ | AR4 | future composed Chart/callsite browser evidence | at least two same-mark-shape series and their rendered marks | Association evidence must cover the legend and plotted marks before recording a defect or assigning an owner. | `audit:ChartLegend/unscored-evidence-gap` |
190
+
191
+ ## Decision log
192
+
193
+ None. This draft records observed behavior and evidence gaps only.
194
+
195
+ ## Open questions
196
+
197
+ - **OQ1 — Composed series association.** In a composed Chart or product callsite, does evidence with two or more same-mark-shape series show that readers need an additional visual association cue? If so, which layer owns it: ChartLegend, the parent Chart/series definition, or the product callsite? (`human-design`)
198
+ - **OQ2 — Swatch theming reachability.** Should series swatches become a public ChartSwatch theme target, or should their paint remain caller-owned? (`human-api`)
199
+
200
+ ## Content boundary
201
+
202
+ This file does not duplicate consumer prop tables/examples, current audit results,
203
+ implementation steps, or shared system rules. It links to their owners.