@astryxdesign/charts 0.1.4-canary.39ffd21 → 0.1.4-canary.3d64ef2

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.
Files changed (129) hide show
  1. package/README.md +26 -6
  2. package/dist/Chart.d.ts +31 -0
  3. package/dist/Chart.d.ts.map +1 -0
  4. package/dist/Chart.js +292 -0
  5. package/dist/ChartAxis.d.ts +36 -0
  6. package/dist/ChartAxis.d.ts.map +1 -0
  7. package/dist/ChartAxis.js +198 -0
  8. package/dist/ChartContext.d.ts +4 -0
  9. package/dist/ChartContext.d.ts.map +1 -0
  10. package/dist/ChartContext.js +13 -0
  11. package/dist/ChartGrid.d.ts +26 -0
  12. package/dist/ChartGrid.d.ts.map +1 -0
  13. package/dist/ChartGrid.js +94 -0
  14. package/dist/ChartLegend.d.ts +14 -0
  15. package/dist/ChartLegend.d.ts.map +1 -0
  16. package/dist/ChartLegend.js +62 -0
  17. package/dist/ChartSwatch.d.ts +36 -0
  18. package/dist/ChartSwatch.d.ts.map +1 -0
  19. package/dist/ChartSwatch.js +85 -0
  20. package/dist/ChartTooltip.d.ts +46 -0
  21. package/dist/ChartTooltip.d.ts.map +1 -0
  22. package/dist/ChartTooltip.js +291 -0
  23. package/dist/charts.css +33 -0
  24. package/dist/formatters.d.ts +37 -0
  25. package/dist/formatters.d.ts.map +1 -0
  26. package/dist/formatters.js +146 -0
  27. package/dist/getChartColors.d.ts +67 -0
  28. package/dist/getChartColors.d.ts.map +1 -0
  29. package/dist/getChartColors.js +191 -0
  30. package/dist/index.d.ts +16 -14
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +16 -14
  33. package/dist/layout.d.ts +34 -0
  34. package/dist/layout.d.ts.map +1 -0
  35. package/dist/layout.js +286 -0
  36. package/dist/legend.d.ts +17 -0
  37. package/dist/legend.d.ts.map +1 -0
  38. package/dist/legend.js +40 -0
  39. package/dist/markColor.d.ts +20 -0
  40. package/dist/markColor.d.ts.map +1 -0
  41. package/dist/markColor.js +49 -0
  42. package/dist/marks/area.d.ts +19 -0
  43. package/dist/marks/area.d.ts.map +1 -0
  44. package/dist/marks/area.js +121 -0
  45. package/dist/marks/band.d.ts +9 -0
  46. package/dist/marks/band.d.ts.map +1 -0
  47. package/dist/marks/band.js +69 -0
  48. package/dist/marks/bar.d.ts +21 -0
  49. package/dist/marks/bar.d.ts.map +1 -0
  50. package/dist/marks/bar.js +161 -0
  51. package/dist/marks/candlestick.d.ts +16 -0
  52. package/dist/marks/candlestick.d.ts.map +1 -0
  53. package/dist/marks/candlestick.js +115 -0
  54. package/dist/marks/dot.d.ts +19 -0
  55. package/dist/marks/dot.d.ts.map +1 -0
  56. package/dist/marks/dot.js +95 -0
  57. package/dist/marks/dotGL.d.ts +16 -0
  58. package/dist/marks/dotGL.d.ts.map +1 -0
  59. package/dist/marks/dotGL.js +231 -0
  60. package/dist/marks/dotGLInteractive.d.ts +35 -0
  61. package/dist/marks/dotGLInteractive.d.ts.map +1 -0
  62. package/dist/marks/dotGLInteractive.js +580 -0
  63. package/dist/marks/errorBar.d.ts +29 -0
  64. package/dist/marks/errorBar.d.ts.map +1 -0
  65. package/dist/marks/errorBar.js +98 -0
  66. package/dist/marks/heatmapGL.d.ts +23 -0
  67. package/dist/marks/heatmapGL.d.ts.map +1 -0
  68. package/dist/marks/heatmapGL.js +316 -0
  69. package/dist/marks/index.d.ts +13 -0
  70. package/dist/marks/index.d.ts.map +1 -0
  71. package/dist/marks/index.js +14 -0
  72. package/dist/marks/line.d.ts +18 -0
  73. package/dist/marks/line.d.ts.map +1 -0
  74. package/dist/marks/line.js +83 -0
  75. package/dist/marks/referenceLine.d.ts +37 -0
  76. package/dist/marks/referenceLine.d.ts.map +1 -0
  77. package/dist/marks/referenceLine.js +203 -0
  78. package/dist/marks/streamGL.d.ts +41 -0
  79. package/dist/marks/streamGL.d.ts.map +1 -0
  80. package/dist/marks/streamGL.js +317 -0
  81. package/dist/tooltip.d.ts +38 -0
  82. package/dist/tooltip.d.ts.map +1 -0
  83. package/dist/tooltip.js +57 -0
  84. package/dist/types.d.ts +159 -0
  85. package/dist/types.d.ts.map +1 -0
  86. package/dist/types.js +63 -0
  87. package/dist/useChartColors.d.ts +16 -0
  88. package/dist/useChartColors.d.ts.map +1 -0
  89. package/dist/useChartColors.js +37 -0
  90. package/dist/utils.d.ts +15 -0
  91. package/dist/utils.d.ts.map +1 -0
  92. package/dist/utils.js +29 -0
  93. package/dist/webgl.d.ts +90 -0
  94. package/dist/webgl.d.ts.map +1 -0
  95. package/dist/webgl.js +236 -0
  96. package/package.json +18 -4
  97. package/src/Chart.doc.mjs +16 -0
  98. package/src/Chart.tsx +363 -0
  99. package/src/ChartAxis.tsx +250 -0
  100. package/src/ChartContext.ts +16 -0
  101. package/src/ChartGrid.tsx +109 -0
  102. package/src/ChartLegend.tsx +77 -0
  103. package/src/ChartSwatch.doc.mjs +17 -0
  104. package/src/ChartSwatch.tsx +89 -0
  105. package/src/ChartTooltip.tsx +403 -0
  106. package/src/formatters.ts +157 -0
  107. package/src/getChartColors.ts +289 -0
  108. package/src/index.ts +85 -14
  109. package/src/layout.ts +324 -0
  110. package/src/legend.ts +44 -0
  111. package/src/markColor.ts +63 -0
  112. package/src/marks/area.tsx +142 -0
  113. package/src/marks/band.tsx +83 -0
  114. package/src/marks/bar.tsx +210 -0
  115. package/src/marks/candlestick.tsx +131 -0
  116. package/src/marks/dot.tsx +116 -0
  117. package/src/marks/dotGL.tsx +274 -0
  118. package/src/marks/dotGLInteractive.tsx +656 -0
  119. package/src/marks/errorBar.tsx +118 -0
  120. package/src/marks/heatmapGL.tsx +376 -0
  121. package/src/marks/index.ts +17 -0
  122. package/src/marks/line.tsx +118 -0
  123. package/src/marks/referenceLine.tsx +252 -0
  124. package/src/marks/streamGL.tsx +381 -0
  125. package/src/tooltip.ts +73 -0
  126. package/src/types.ts +177 -0
  127. package/src/useChartColors.ts +41 -0
  128. package/src/utils.ts +36 -0
  129. package/src/webgl.ts +263 -0
package/README.md CHANGED
@@ -1,21 +1,41 @@
1
1
  # @astryxdesign/charts
2
2
 
3
- Astryx chart components. This package provides thin, themeable wrappers over a peer charting engine — it owns the design-system surface while leaving the rendering engine as a peer dependency the consumer manages.
3
+ Astryx charts — a config-model data visualization library built on d3.
4
4
 
5
- It ships to npm **only under the `@canary` dist-tag** — there is never a stable (`latest`) release yet.
5
+ ```tsx
6
+ import {Chart, bar, line} from '@astryxdesign/charts';
6
7
 
7
- ## Status
8
+ <Chart data={data} xKey="month" series={[bar('revenue'), line('trend')]} />;
9
+ ```
10
+
11
+ Marks are factory functions (`bar`, `line`, `area`, `dot`, `band`, `candlestick`,
12
+ `errorBar`, `referenceLine`, and WebGL variants) that return config objects passed
13
+ via the `series` prop. The chart root owns **one** x/y scale that every mark, axis,
14
+ and grid line reads, so they can never disagree. It consumes `@astryxdesign/core`
15
+ theme tokens directly (StyleX build mirrors `@astryxdesign/lab`).
8
16
 
9
- **Bootstrap scaffold.** The package is set up and building but ships no components yet. Add components under `src/` and export them from `src/index.ts`. The StyleX + Astryx-token build setup mirrors `@astryxdesign/lab`, so components can consume `@astryxdesign/core` theme tokens directly.
17
+ It ships to npm **only under the `@canary` dist-tag** — there is never a stable
18
+ (`latest`) release yet.
19
+
20
+ > Note: this package is the successor to the experimental `Chart` (formerly
21
+ > `ChartV2`) that used to live in `@astryxdesign/lab`; that code has moved here and
22
+ > d3 is a direct dependency. (Supersedes the original "thin wrappers over a peer
23
+ > engine" framing of the bootstrap scaffold.)
24
+
25
+ ## Status
10
26
 
11
- > When the first component adds StyleX styles, wire up a pre-compiled stylesheet the same way `lab` does: add a `charts` target to `scripts/build-css.mjs`, restore the `build:css` script and the `./charts.css` export in `package.json`, and document the `import '@astryxdesign/charts/charts.css'` step below.
27
+ Under active development. The config-model chart and its marks/chrome are in place;
28
+ API and visuals are still being refined. See the repo-root `CHARTV2_*` docs for the
29
+ plan, design research, and verification checklist.
12
30
 
13
31
  ## Usage
14
32
 
15
33
  Inside the monorepo (storybook/sandbox), imports resolve via pnpm workspaces:
16
34
 
17
35
  ```tsx
18
- import {} from /* ... */ '@astryxdesign/charts';
36
+ import {Chart, bar, line, ChartGrid, ChartAxis} from '@astryxdesign/charts';
37
+ import '@astryxdesign/core/astryx.css';
38
+ import '@astryxdesign/charts/charts.css';
19
39
  ```
20
40
 
21
41
  ### Trying charts in your own project (canary)
@@ -0,0 +1,31 @@
1
+ import { type ReactNode } from 'react';
2
+ import type { SeriesDef, YBaseline } from './types';
3
+ import type { ChartMargin } from './types';
4
+ import { type ChartLegendProps } from './ChartLegend';
5
+ import { type ChartTooltipProps } from './ChartTooltip';
6
+ export interface ChartProps {
7
+ data: Record<string, unknown>[];
8
+ xKey: string;
9
+ series: SeriesDef[];
10
+ height?: number;
11
+ margin?: Partial<ChartMargin>;
12
+ /** How the y-domain is derived when `yDomain` is not set. Default `'auto'`. */
13
+ yBaseline?: YBaseline;
14
+ /** Explicit y-domain [min, max]. Authoritative — disables baseline/headroom. */
15
+ yDomain?: [number, number];
16
+ /**
17
+ * Explicit x-domain [min, max] for numeric/linear x. Honored even when `data`
18
+ * is empty (stable streaming window). Ignored for categorical (band) scales.
19
+ */
20
+ xDomain?: [number, number];
21
+ grid?: ReactNode;
22
+ axes?: ReactNode;
23
+ legend?: boolean | ChartLegendProps;
24
+ tooltip?: boolean | Omit<ChartTooltipProps, 'series'>;
25
+ interactions?: ReactNode;
26
+ children?: ReactNode;
27
+ title?: string;
28
+ subtitle?: string;
29
+ }
30
+ export declare function Chart({ data, xKey, series, height, margin: marginOverride, yBaseline, yDomain, xDomain, grid, axes, legend, tooltip, interactions, children, title, subtitle, }: ChartProps): import("react").JSX.Element;
31
+ //# sourceMappingURL=Chart.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Chart.d.ts","sourceRoot":"","sources":["../src/Chart.tsx"],"names":[],"mappings":"AAeA,OAAO,EACL,KAAK,SAAS,EAOf,MAAM,OAAO,CAAC;AACf,OAAO,KAAK,EAAC,SAAS,EAAE,SAAS,EAAC,MAAM,SAAS,CAAC;AAClD,OAAO,KAAK,EAAe,WAAW,EAAoB,MAAM,SAAS,CAAC;AAM1E,OAAO,EAAc,KAAK,gBAAgB,EAAC,MAAM,eAAe,CAAC;AAEjE,OAAO,EAAe,KAAK,iBAAiB,EAAC,MAAM,gBAAgB,CAAC;AAIpE,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;IAC9B,+EAA+E;IAC/E,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,gFAAgF;IAChF,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC3B;;;OAGG;IACH,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC3B,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,GAAG,gBAAgB,CAAC;IACpC,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC,iBAAiB,EAAE,QAAQ,CAAC,CAAC;IACtD,YAAY,CAAC,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAkBD,wBAAgB,KAAK,CAAC,EACpB,IAAI,EACJ,IAAI,EACJ,MAAM,EACN,MAAY,EACZ,MAAM,EAAE,cAAc,EACtB,SAAS,EACT,OAAO,EACP,OAAO,EACP,IAAI,EACJ,IAAI,EACJ,MAAM,EACN,OAAO,EACP,YAAY,EACZ,QAAQ,EACR,KAAK,EACL,QAAQ,GACT,EAAE,UAAU,+BA2QZ"}
package/dist/Chart.js ADDED
@@ -0,0 +1,292 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file Chart.tsx (v2)
5
+ * @output Root chart — coordinates layout, rendering, and events
6
+ * @position Top-level; delegates everything to series configs and interaction slots
7
+ *
8
+ * The chart root has ZERO knowledge of mark types. It:
9
+ * 1. Runs the layout engine (scales + stacking + grouping)
10
+ * 2. Calls each series' resolve() and render() methods
11
+ * 3. Provides a single event layer for interaction children
12
+ */
13
+
14
+ 'use client';
15
+
16
+ import { useId, useMemo, useRef, useState, useCallback, useLayoutEffect } from 'react';
17
+ import { computeLayout } from "./layout.js";
18
+ import { ChartProvider } from "./ChartContext.js";
19
+ import { Text } from '@astryxdesign/core';
20
+ import { VStack, HStack } from '@astryxdesign/core';
21
+ import * as stylex from '@stylexjs/stylex';
22
+ import { ChartLegend } from "./ChartLegend.js";
23
+ import { deriveLegendItems } from "./legend.js";
24
+ import { ChartTooltip } from "./ChartTooltip.js";
25
+ import { useChartColors } from "./useChartColors.js";
26
+ import { isUtilityMarkType } from "./types.js";
27
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
28
+ const DEFAULT_MARGIN = {
29
+ top: 24,
30
+ right: 24,
31
+ bottom: 32,
32
+ left: 48
33
+ };
34
+ export function Chart({
35
+ data,
36
+ xKey,
37
+ series,
38
+ height = 300,
39
+ margin: marginOverride,
40
+ yBaseline,
41
+ yDomain,
42
+ xDomain,
43
+ grid,
44
+ axes,
45
+ legend,
46
+ tooltip,
47
+ interactions,
48
+ children,
49
+ title,
50
+ subtitle
51
+ }) {
52
+ const chartId = useId();
53
+ const descId = `${chartId}-desc`;
54
+ // useId() can contain ':' which is invalid in an SVG url(#id) reference.
55
+ const clipId = `plot-${chartId.replace(/:/g, '')}`;
56
+ const containerRef = useRef(null);
57
+ const svgRef = useRef(null);
58
+ const [containerWidth, setContainerWidth] = useState(0);
59
+ const pointerHandlers = useRef(new Set());
60
+ useLayoutEffect(() => {
61
+ if (!containerRef.current) {
62
+ return;
63
+ }
64
+ const observer = new ResizeObserver(entries => {
65
+ const entry = entries[0];
66
+ if (entry) {
67
+ setContainerWidth(entry.contentRect.width);
68
+ }
69
+ });
70
+ observer.observe(containerRef.current);
71
+ return () => observer.disconnect();
72
+ }, []);
73
+ const margin = useMemo(() => ({
74
+ ...DEFAULT_MARGIN,
75
+ ...marginOverride
76
+ }), [marginOverride]);
77
+ const innerWidth = Math.max(0, containerWidth - margin.left - margin.right);
78
+ const innerHeight = Math.max(0, height - margin.top - margin.bottom);
79
+
80
+ // ─── Color assignment ─────────────────────────────────────────────────
81
+ // Give every primary series that doesn't supply a static color (auto-colored
82
+ // or accessor-colored) a distinct color from the theme's categorical palette.
83
+ // Utility marks (band/errorBar/referenceLine) don't consume palette slots.
84
+ // Assigned during render (like the layout pass annotates `_uid`) so the first
85
+ // paint is already colored; intentionally not a `useMemo` — it mutates the
86
+ // caller's series defs, and useMemo is allowed to drop its cached result.
87
+ const chartColors = useChartColors();
88
+ const needsColor = series.filter(s => !isUtilityMarkType(s.type) && s.color == null);
89
+ const palette = chartColors.categorical(Math.max(needsColor.length, 1));
90
+ for (let i = 0; i < needsColor.length; i++) {
91
+ needsColor[i]._resolvedColor = palette[i % palette.length];
92
+ }
93
+
94
+ // ─── Layout pass ──────────────────────────────────────────────────────
95
+ const layout = useMemo(() => computeLayout({
96
+ data,
97
+ xKey,
98
+ series,
99
+ width: innerWidth,
100
+ height: innerHeight,
101
+ yBaseline,
102
+ yDomain,
103
+ xDomain
104
+ }), [data, xKey, series, innerWidth, innerHeight, yBaseline, yDomain, xDomain]);
105
+ const seriesCtx = useMemo(() => ({
106
+ data,
107
+ xKey,
108
+ xScale: layout.xScale,
109
+ yScale: layout.yScale,
110
+ width: innerWidth,
111
+ height: innerHeight
112
+ }), [data, xKey, layout, innerWidth, innerHeight]);
113
+
114
+ // ─── Event dispatch ───────────────────────────────────────────────────
115
+ const onPointer = useCallback(handler => {
116
+ pointerHandlers.current.add(handler);
117
+ return () => {
118
+ pointerHandlers.current.delete(handler);
119
+ };
120
+ }, []);
121
+ const dispatch = useCallback(e => {
122
+ for (const handler of pointerHandlers.current) {
123
+ handler(e);
124
+ }
125
+ }, []);
126
+ const handlePointerMove = useCallback(e => {
127
+ const svg = svgRef.current;
128
+ if (!svg) {
129
+ return;
130
+ }
131
+ const point = svg.createSVGPoint();
132
+ point.x = e.clientX;
133
+ point.y = e.clientY;
134
+ const ctm = e.currentTarget.getScreenCTM()?.inverse();
135
+ if (!ctm) {
136
+ return;
137
+ }
138
+ const cursor = point.matrixTransform(ctm);
139
+
140
+ // Find nearest point by x-distance only (full vertical column is the hover zone)
141
+ let nearest = null;
142
+ let bestXDist = Infinity;
143
+ for (const [seriesKey, points] of layout.resolved) {
144
+ for (const p of points) {
145
+ const xDist = Math.abs(p.px - cursor.x);
146
+ if (xDist < bestXDist) {
147
+ bestXDist = xDist;
148
+ nearest = {
149
+ ...p,
150
+ seriesKey
151
+ };
152
+ }
153
+ }
154
+ }
155
+ dispatch({
156
+ x: cursor.x,
157
+ y: cursor.y,
158
+ nearest,
159
+ active: e.buttons > 0
160
+ });
161
+ }, [layout.resolved, dispatch]);
162
+ const handlePointerLeave = useCallback(() => {
163
+ dispatch({
164
+ x: -1,
165
+ y: -1,
166
+ nearest: null,
167
+ active: false
168
+ });
169
+ }, [dispatch]);
170
+
171
+ // ─── Context for interactions + v1 chrome ─────────────────────────────
172
+ const ctx = useMemo(() => ({
173
+ width: innerWidth,
174
+ height: innerHeight,
175
+ margin,
176
+ data,
177
+ xKey,
178
+ xScale: layout.xScale,
179
+ yScale: layout.yScale,
180
+ resolved: layout.resolved,
181
+ onPointer,
182
+ svgRef
183
+ }), [innerWidth, innerHeight, margin, data, xKey, layout, onPointer]);
184
+
185
+ // ─── Legend ────────────────────────────────────────────────────────────
186
+ const legendConfig = legend === true ? {} : legend || null;
187
+ const legendElement = legendConfig ? /*#__PURE__*/_jsx(ChartLegend, {
188
+ items: legendConfig.items ?? deriveLegendItems(series),
189
+ position: legendConfig.position,
190
+ alignment: legendConfig.alignment
191
+ }) : null;
192
+ const legendPosition = legendConfig?.position ?? 'bottom';
193
+ const isLegendHorizontal = legendPosition === 'start' || legendPosition === 'end';
194
+
195
+ // ─── Render ───────────────────────────────────────────────────────────
196
+ if (containerWidth === 0) {
197
+ return /*#__PURE__*/_jsx("div", {
198
+ ref: containerRef,
199
+ ...{
200
+ className: "xh8yej3"
201
+ },
202
+ style: {
203
+ height
204
+ }
205
+ });
206
+ }
207
+ return /*#__PURE__*/_jsxs("div", {
208
+ ref: containerRef,
209
+ ...{
210
+ className: "xh8yej3"
211
+ },
212
+ children: [(title || subtitle) && /*#__PURE__*/_jsxs("div", {
213
+ ...{
214
+ className: "x1yztbdb"
215
+ },
216
+ children: [title && /*#__PURE__*/_jsx(Text, {
217
+ type: "body",
218
+ weight: "semibold",
219
+ display: "block",
220
+ children: title
221
+ }), subtitle && /*#__PURE__*/_jsx(Text, {
222
+ type: "supporting",
223
+ display: "block",
224
+ children: subtitle
225
+ })]
226
+ }), /*#__PURE__*/_jsx(ChartProvider, {
227
+ value: ctx,
228
+ children: legendConfig == null ? renderSvg() : isLegendHorizontal ? /*#__PURE__*/_jsxs(HStack, {
229
+ gap: 4,
230
+ children: [legendPosition === 'start' && legendElement, /*#__PURE__*/_jsx("div", {
231
+ ...{
232
+ className: "x98rzlu xeuugli xb3r6kr"
233
+ },
234
+ children: renderSvg()
235
+ }), legendPosition === 'end' && legendElement]
236
+ }) : /*#__PURE__*/_jsxs(VStack, {
237
+ gap: 4,
238
+ children: [legendPosition === 'top' && legendElement, renderSvg(), legendPosition === 'bottom' && legendElement]
239
+ })
240
+ })]
241
+ });
242
+ function renderSvg() {
243
+ return /*#__PURE__*/_jsxs("svg", {
244
+ ref: svgRef,
245
+ width: "100%",
246
+ height: height,
247
+ "aria-label": title ?? undefined,
248
+ "aria-describedby": subtitle ? descId : undefined,
249
+ children: [title && /*#__PURE__*/_jsx("title", {
250
+ children: title
251
+ }), subtitle && /*#__PURE__*/_jsx("desc", {
252
+ id: descId,
253
+ children: subtitle
254
+ }), /*#__PURE__*/_jsx("defs", {
255
+ children: /*#__PURE__*/_jsx("clipPath", {
256
+ id: clipId,
257
+ children: /*#__PURE__*/_jsx("rect", {
258
+ x: 0,
259
+ y: 0,
260
+ width: innerWidth,
261
+ height: innerHeight
262
+ })
263
+ })
264
+ }), /*#__PURE__*/_jsxs("g", {
265
+ transform: `translate(${margin.left},${margin.top})`,
266
+ children: [grid, /*#__PURE__*/_jsx("g", {
267
+ clipPath: `url(#${clipId})`,
268
+ children: series.map(s => {
269
+ const resolved = s._uid ? layout.resolved.get(s._uid) : undefined;
270
+ if (!resolved) {
271
+ return null;
272
+ }
273
+ return /*#__PURE__*/_jsx("g", {
274
+ children: s.render(resolved, seriesCtx)
275
+ }, s._uid);
276
+ })
277
+ }), axes, /*#__PURE__*/_jsx("rect", {
278
+ x: 0,
279
+ y: 0,
280
+ width: innerWidth,
281
+ height: innerHeight,
282
+ fill: "transparent",
283
+ onPointerMove: handlePointerMove,
284
+ onPointerLeave: handlePointerLeave
285
+ }), interactions, tooltip && /*#__PURE__*/_jsx(ChartTooltip, {
286
+ series: series,
287
+ ...(tooltip === true ? {} : tooltip)
288
+ }), children]
289
+ })]
290
+ });
291
+ }
292
+ }
@@ -0,0 +1,36 @@
1
+ export interface ChartAxisProps {
2
+ /** Which edge to render the axis on */
3
+ position: 'top' | 'right' | 'bottom' | 'left';
4
+ /** Number of ticks (approximate — d3 decides final count) */
5
+ tickCount?: number;
6
+ /** Maximum number of tick labels to show. Labels are evenly skipped when exceeded. */
7
+ maxTicks?: number;
8
+ /** Custom tick formatter */
9
+ tickFormat?: (value: unknown) => string;
10
+ /** Truncate labels to this many characters (appends "\u2026"). */
11
+ truncate?: number;
12
+ /** Enable smooth transitions for streaming (default: true) */
13
+ animated?: boolean;
14
+ /**
15
+ * Show the axis edge line. Defaults to `true` for `position="bottom"`,
16
+ * `false` otherwise. Forced to `true` when `showTicks` is enabled.
17
+ * Pair with `<ChartGrid vertical />` and pass `showAxisLine` on the
18
+ * corresponding side axis to keep the visual edge grounded.
19
+ */
20
+ showAxisLine?: boolean;
21
+ /** Show perpendicular tick marks at each tick label. Default: `false`. */
22
+ showTicks?: boolean;
23
+ }
24
+ /**
25
+ * Chart axis. Always draws tick labels; the axis edge line and tick marks
26
+ * are independently toggleable. Pair with `<ChartGrid>` for grid lines.
27
+ *
28
+ * @example
29
+ * ```
30
+ * <ChartAxis position="bottom" /> // edge line + labels
31
+ * <ChartAxis position="left" /> // labels only (no edge line)
32
+ * <ChartAxis position="left" showAxisLine showTicks />
33
+ * ```
34
+ */
35
+ export declare function ChartAxis({ position, tickCount, maxTicks, tickFormat, truncate, animated, showAxisLine, showTicks, }: ChartAxisProps): import("react").JSX.Element;
36
+ //# sourceMappingURL=ChartAxis.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChartAxis.d.ts","sourceRoot":"","sources":["../src/ChartAxis.tsx"],"names":[],"mappings":"AA0BA,MAAM,WAAW,cAAc;IAC7B,uCAAuC;IACvC,QAAQ,EAAE,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC;IAC9C,6DAA6D;IAC7D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sFAAsF;IACtF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,4BAA4B;IAC5B,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,MAAM,CAAC;IACxC,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,0EAA0E;IAC1E,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAeD;;;;;;;;;;GAUG;AACH,wBAAgB,SAAS,CAAC,EACxB,QAAQ,EACR,SAAa,EACb,QAAQ,EACR,UAAU,EACV,QAAQ,EACR,QAAe,EACf,YAAoC,EACpC,SAAiB,GAClB,EAAE,cAAc,+BAsKhB"}
@@ -0,0 +1,198 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file ChartAxis.tsx (v2)
5
+ * @output Renders an axis (top, right, bottom, left) using the chart's scales
6
+ * @position Child of Chart v2; reads scales from chart context
7
+ *
8
+ * The axis edge line and tick marks are independently toggleable so callers
9
+ * can compose: line-only, line + ticks, or line + ticks + grid (paired with
10
+ * `<ChartGrid>`). To stay consistent with most charts, the bottom axis
11
+ * draws its edge line by default and other positions don't.
12
+ *
13
+ * Labels use d3's native tick formatter for continuous scales by default (clean
14
+ * numbers/dates) unless a `tickFormat` is supplied, and are auto-thinned to the
15
+ * available width (horizontal) or height (vertical) to avoid overlap.
16
+ *
17
+ * Ticks use CSS transitions for smooth sliding during streaming updates.
18
+ */
19
+
20
+ import { useCallback, useMemo } from 'react';
21
+ import * as stylex from '@stylexjs/stylex';
22
+ import '@astryxdesign/core/theme/tokens.stylex';
23
+ import { colorVars } from '@astryxdesign/core/theme/tokens.stylex';
24
+ import { useChart } from "./ChartContext.js";
25
+ import { isBandScale } from "./utils.js";
26
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
27
+ const TICK_SIZE = 6;
28
+
29
+ /**
30
+ * Chart axis. Always draws tick labels; the axis edge line and tick marks
31
+ * are independently toggleable. Pair with `<ChartGrid>` for grid lines.
32
+ *
33
+ * @example
34
+ * ```
35
+ * <ChartAxis position="bottom" /> // edge line + labels
36
+ * <ChartAxis position="left" /> // labels only (no edge line)
37
+ * <ChartAxis position="left" showAxisLine showTicks />
38
+ * ```
39
+ */
40
+ export function ChartAxis({
41
+ position,
42
+ tickCount = 5,
43
+ maxTicks,
44
+ tickFormat,
45
+ truncate,
46
+ animated = true,
47
+ showAxisLine = position === 'bottom',
48
+ showTicks = false
49
+ }) {
50
+ // Tick marks need an axis line to anchor against; force it on when ticks
51
+ // are enabled so the two visuals stay coherent.
52
+ const renderAxisLine = showAxisLine || showTicks;
53
+ const {
54
+ width,
55
+ height,
56
+ xScale,
57
+ yScale
58
+ } = useChart();
59
+ const isHorizontal = position === 'top' || position === 'bottom';
60
+ const scale = isHorizontal ? xScale : yScale;
61
+
62
+ // Default label formatter. For continuous (linear/time) scales, use d3's own
63
+ // tick formatter so numbers get sensible precision (no floating-point dust
64
+ // like "0.30000000000000004") and dates render as calendar labels instead of
65
+ // `String(Date)`. Band scales keep their category strings.
66
+ const autoFormat = useMemo(() => {
67
+ if (tickFormat || isBandScale(scale)) {
68
+ return null;
69
+ }
70
+ const continuous = scale;
71
+ const fmt = continuous.tickFormat(tickCount);
72
+ return v => fmt(v);
73
+ }, [tickFormat, scale, tickCount]);
74
+ const format = useCallback(value => {
75
+ const str = (tickFormat ?? autoFormat ?? String)(value);
76
+ return truncate && str.length > truncate ? str.slice(0, truncate) + '\u2026' : str;
77
+ }, [tickFormat, autoFormat, truncate]);
78
+ const ticks = useMemo(() => {
79
+ let allTicks;
80
+ if (isBandScale(scale)) {
81
+ allTicks = scale.domain().map(d => ({
82
+ value: d,
83
+ offset: (scale(d) ?? 0) + scale.bandwidth() / 2
84
+ }));
85
+ } else {
86
+ const linearScale = scale;
87
+ allTicks = linearScale.ticks(tickCount).map(d => ({
88
+ value: d,
89
+ offset: linearScale(d)
90
+ }));
91
+ }
92
+
93
+ // Cap label count to avoid overlap. An explicit `maxTicks` always wins.
94
+ // Otherwise derive a cap from the available space and label size so dense
95
+ // axes don't smear into an unreadable blur: width ÷ widest-label for
96
+ // horizontal (e.g. 30 daily categories), height ÷ line-height for vertical
97
+ // (e.g. a categorical y-axis on a horizontal bar chart). Labels are then
98
+ // evenly skipped down to the cap.
99
+ let cap = maxTicks;
100
+ if (cap == null && allTicks.length > 1) {
101
+ if (isHorizontal && width > 0) {
102
+ const widestChars = allTicks.reduce((m, t) => Math.max(m, format(t.value).length), 1);
103
+ const approxLabelPx = widestChars * 7 + 16;
104
+ cap = Math.max(1, Math.floor(width / approxLabelPx));
105
+ } else if (!isHorizontal && height > 0) {
106
+ // ~12px glyph + breathing room keeps stacked labels from touching.
107
+ cap = Math.max(1, Math.floor(height / 18));
108
+ }
109
+ }
110
+ if (cap && allTicks.length > cap) {
111
+ const step = Math.ceil(allTicks.length / cap);
112
+ allTicks = allTicks.filter((_, i) => i % step === 0);
113
+ }
114
+ return allTicks;
115
+ }, [scale, tickCount, maxTicks, isHorizontal, width, height, format]);
116
+ const transform = position === 'bottom' ? `translate(0,${height})` : position === 'right' ? `translate(${width},0)` : undefined;
117
+
118
+ // For the bottom axis, draw the axis line at y=0 when the domain spans
119
+ // negative values (so the axis line still represents zero, not the chart edge).
120
+ const axisLineY = (() => {
121
+ if (position !== 'bottom') {
122
+ return 0;
123
+ }
124
+ const domain = yScale.domain();
125
+ if (domain[0] < 0 && domain[1] > 0) {
126
+ return yScale(0) - height;
127
+ }
128
+ return 0;
129
+ })();
130
+ const tickTransition = animated ? 'transform 150ms linear, opacity 150ms ease' : undefined;
131
+ return /*#__PURE__*/_jsxs("g", {
132
+ transform: transform,
133
+ role: "group",
134
+ "aria-label": `${position} axis`,
135
+ children: [renderAxisLine && /*#__PURE__*/_jsx("line", {
136
+ x1: 0,
137
+ x2: isHorizontal ? width : 0,
138
+ y1: isHorizontal ? axisLineY : 0,
139
+ y2: isHorizontal ? axisLineY : height,
140
+ ...{
141
+ className: "x1lo1t26 xnpr05h"
142
+ }
143
+ }), ticks.map(({
144
+ value,
145
+ offset
146
+ }) => {
147
+ const label = format(value);
148
+ // Key off the raw tick value, not the formatted label: distinct ticks
149
+ // can format (or truncate) to the same string and would collide as keys.
150
+ const key = String(value);
151
+ const isVisible = isHorizontal ? offset >= -10 && offset <= width + 10 : offset >= -10 && offset <= height + 10;
152
+ if (isHorizontal) {
153
+ const y = position === 'bottom' ? TICK_SIZE : -TICK_SIZE;
154
+ return /*#__PURE__*/_jsxs("g", {
155
+ style: {
156
+ transform: `translateX(${offset}px)`,
157
+ transition: tickTransition,
158
+ opacity: isVisible ? 1 : 0
159
+ },
160
+ children: [showTicks && /*#__PURE__*/_jsx("line", {
161
+ y2: y,
162
+ ...{
163
+ className: "x1lo1t26 xnpr05h"
164
+ }
165
+ }), /*#__PURE__*/_jsx("text", {
166
+ y: position === 'bottom' ? TICK_SIZE + 12 : -(TICK_SIZE + 4),
167
+ textAnchor: "middle",
168
+ fill: "var(--color-text-secondary)",
169
+ fontSize: 12,
170
+ children: label
171
+ })]
172
+ }, key);
173
+ }
174
+ // Vertical axis
175
+ const x = position === 'left' ? -TICK_SIZE : TICK_SIZE;
176
+ return /*#__PURE__*/_jsxs("g", {
177
+ style: {
178
+ transform: `translateY(${offset}px)`,
179
+ transition: tickTransition,
180
+ opacity: isVisible ? 1 : 0
181
+ },
182
+ children: [showTicks && /*#__PURE__*/_jsx("line", {
183
+ x2: x,
184
+ ...{
185
+ className: "x1lo1t26 xnpr05h"
186
+ }
187
+ }), /*#__PURE__*/_jsx("text", {
188
+ x: position === 'left' ? -(TICK_SIZE + 4) : TICK_SIZE + 4,
189
+ dy: "0.32em",
190
+ textAnchor: position === 'left' ? 'end' : 'start',
191
+ fill: "var(--color-text-secondary)",
192
+ fontSize: 12,
193
+ children: label
194
+ })]
195
+ }, key);
196
+ })]
197
+ });
198
+ }
@@ -0,0 +1,4 @@
1
+ import type { ChartContext } from './types';
2
+ export declare const ChartProvider: import("react").Provider<ChartContext | null>;
3
+ export declare function useChart(): ChartContext;
4
+ //# sourceMappingURL=ChartContext.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChartContext.d.ts","sourceRoot":"","sources":["../src/ChartContext.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,SAAS,CAAC;AAI1C,eAAO,MAAM,aAAa,+CAAe,CAAC;AAE1C,wBAAgB,QAAQ,IAAI,YAAY,CAMvC"}
@@ -0,0 +1,13 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ import { createContext, useContext } from 'react';
4
+ const Ctx = /*#__PURE__*/createContext(null);
5
+ Ctx.displayName = 'ChartContext';
6
+ export const ChartProvider = Ctx.Provider;
7
+ export function useChart() {
8
+ const ctx = useContext(Ctx);
9
+ if (!ctx) {
10
+ throw new Error('Must be used inside <Chart>');
11
+ }
12
+ return ctx;
13
+ }
@@ -0,0 +1,26 @@
1
+ export interface ChartGridProps {
2
+ /** Show horizontal grid lines. Default: `true`. */
3
+ horizontal?: boolean;
4
+ /** Show vertical grid lines. Default: `false`. */
5
+ vertical?: boolean;
6
+ /**
7
+ * Approximate number of grid lines per axis. Match your `<ChartAxis>`
8
+ * `tickCount` so grid lines and tick labels land on the same values.
9
+ * Default: `5`.
10
+ */
11
+ tickCount?: number;
12
+ }
13
+ /**
14
+ * Grid lines behind chart marks.
15
+ *
16
+ * Pair with `<ChartAxis>` so users always see an axis line beside the
17
+ * grid — the components are designed to be used together.
18
+ *
19
+ * @example
20
+ * ```
21
+ * <ChartGrid /> // horizontal grid lines (default)
22
+ * <ChartGrid horizontal vertical />
23
+ * ```
24
+ */
25
+ export declare function ChartGrid({ horizontal, vertical, tickCount, }: ChartGridProps): import("react").JSX.Element;
26
+ //# sourceMappingURL=ChartGrid.d.ts.map
@@ -0,0 +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,+BAyDhB"}