@advui/charts 0.5.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.
Files changed (88) hide show
  1. package/LICENSE +49 -0
  2. package/README.md +41 -0
  3. package/dist/charts/ChartFrame.d.ts +75 -0
  4. package/dist/charts/ChartFrame.d.ts.map +1 -0
  5. package/dist/charts/ChartFrame.js +100 -0
  6. package/dist/charts/ChartFrame.js.map +1 -0
  7. package/dist/charts/LinePlot.d.ts +32 -0
  8. package/dist/charts/LinePlot.d.ts.map +1 -0
  9. package/dist/charts/LinePlot.js +93 -0
  10. package/dist/charts/LinePlot.js.map +1 -0
  11. package/dist/charts/cartesian.d.ts +35 -0
  12. package/dist/charts/cartesian.d.ts.map +1 -0
  13. package/dist/charts/cartesian.js +23 -0
  14. package/dist/charts/cartesian.js.map +1 -0
  15. package/dist/charts/format.d.ts +7 -0
  16. package/dist/charts/format.d.ts.map +1 -0
  17. package/dist/charts/format.js +12 -0
  18. package/dist/charts/format.js.map +1 -0
  19. package/dist/charts/index.d.ts +7 -0
  20. package/dist/charts/index.d.ts.map +1 -0
  21. package/dist/charts/index.js +7 -0
  22. package/dist/charts/index.js.map +1 -0
  23. package/dist/charts/scale.d.ts +20 -0
  24. package/dist/charts/scale.d.ts.map +1 -0
  25. package/dist/charts/scale.js +75 -0
  26. package/dist/charts/scale.js.map +1 -0
  27. package/dist/charts/svg.d.ts +51 -0
  28. package/dist/charts/svg.d.ts.map +1 -0
  29. package/dist/charts/svg.js +13 -0
  30. package/dist/charts/svg.js.map +1 -0
  31. package/dist/charts/svg.native.d.ts +50 -0
  32. package/dist/charts/svg.native.d.ts.map +1 -0
  33. package/dist/charts/svg.native.js +14 -0
  34. package/dist/charts/svg.native.js.map +1 -0
  35. package/dist/components/area-chart/AreaChart.d.ts +11 -0
  36. package/dist/components/area-chart/AreaChart.d.ts.map +1 -0
  37. package/dist/components/area-chart/AreaChart.js +10 -0
  38. package/dist/components/area-chart/AreaChart.js.map +1 -0
  39. package/dist/components/area-chart/index.d.ts +2 -0
  40. package/dist/components/area-chart/index.d.ts.map +1 -0
  41. package/dist/components/area-chart/index.js +2 -0
  42. package/dist/components/area-chart/index.js.map +1 -0
  43. package/dist/components/bar-chart/BarChart.d.ts +31 -0
  44. package/dist/components/bar-chart/BarChart.d.ts.map +1 -0
  45. package/dist/components/bar-chart/BarChart.js +114 -0
  46. package/dist/components/bar-chart/BarChart.js.map +1 -0
  47. package/dist/components/bar-chart/index.d.ts +2 -0
  48. package/dist/components/bar-chart/index.d.ts.map +1 -0
  49. package/dist/components/bar-chart/index.js +2 -0
  50. package/dist/components/bar-chart/index.js.map +1 -0
  51. package/dist/components/line-chart/LineChart.d.ts +8 -0
  52. package/dist/components/line-chart/LineChart.d.ts.map +1 -0
  53. package/dist/components/line-chart/LineChart.js +10 -0
  54. package/dist/components/line-chart/LineChart.js.map +1 -0
  55. package/dist/components/line-chart/index.d.ts +2 -0
  56. package/dist/components/line-chart/index.d.ts.map +1 -0
  57. package/dist/components/line-chart/index.js +2 -0
  58. package/dist/components/line-chart/index.js.map +1 -0
  59. package/dist/components/pie-chart/PieChart.d.ts +37 -0
  60. package/dist/components/pie-chart/PieChart.d.ts.map +1 -0
  61. package/dist/components/pie-chart/PieChart.js +89 -0
  62. package/dist/components/pie-chart/PieChart.js.map +1 -0
  63. package/dist/components/pie-chart/index.d.ts +2 -0
  64. package/dist/components/pie-chart/index.d.ts.map +1 -0
  65. package/dist/components/pie-chart/index.js +2 -0
  66. package/dist/components/pie-chart/index.js.map +1 -0
  67. package/dist/index.d.ts +5 -0
  68. package/dist/index.d.ts.map +1 -0
  69. package/dist/index.js +7 -0
  70. package/dist/index.js.map +1 -0
  71. package/package.json +93 -0
  72. package/src/charts/ChartFrame.tsx +303 -0
  73. package/src/charts/LinePlot.tsx +216 -0
  74. package/src/charts/cartesian.tsx +99 -0
  75. package/src/charts/format.ts +14 -0
  76. package/src/charts/index.ts +16 -0
  77. package/src/charts/scale.ts +91 -0
  78. package/src/charts/svg.native.tsx +62 -0
  79. package/src/charts/svg.tsx +57 -0
  80. package/src/components/area-chart/AreaChart.tsx +14 -0
  81. package/src/components/area-chart/index.ts +1 -0
  82. package/src/components/bar-chart/BarChart.tsx +300 -0
  83. package/src/components/bar-chart/index.ts +1 -0
  84. package/src/components/line-chart/LineChart.tsx +11 -0
  85. package/src/components/line-chart/index.ts +1 -0
  86. package/src/components/pie-chart/PieChart.tsx +192 -0
  87. package/src/components/pie-chart/index.ts +1 -0
  88. package/src/index.ts +6 -0
@@ -0,0 +1,89 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useIconColor } from '@advui/icons';
3
+ import { ChartFrame, G, Path, Svg, SvgText, arcPath, formatFull, useChartColors, useChartInk, } from '../../charts';
4
+ const percent = (part, total) => total > 0 ? `${Math.round((part / total) * 1000) / 10}%` : '0%';
5
+ /**
6
+ * Part-to-whole at a glance, for up to six parts. Each slice is named in the
7
+ * legend with its share; the tooltip (hover, tap or arrow keys) and the
8
+ * table view give the exact values. For close values, a bar chart reads better.
9
+ */
10
+ export function PieChart({ title, description, data, variant = 'donut', maxSlices = 6, height = 240, width, valueFormatter = formatFull, text: textProp, labels, }) {
11
+ const text = {
12
+ label: 'Label',
13
+ value: 'Value',
14
+ share: 'Share',
15
+ other: 'Other',
16
+ total: 'Total',
17
+ ...textProp,
18
+ };
19
+ const colors = useChartColors();
20
+ const ink = useChartInk('$background');
21
+ const foreground = useIconColor('$foreground') ?? 'currentColor';
22
+ // Largest first; past the limit, the tail folds into one "Other" slice.
23
+ const positive = data.filter((d) => d.value > 0).sort((a, b) => b.value - a.value);
24
+ const slices = positive.length > maxSlices
25
+ ? [
26
+ ...positive.slice(0, maxSlices - 1),
27
+ {
28
+ label: text.other,
29
+ value: positive.slice(maxSlices - 1).reduce((sum, d) => sum + d.value, 0),
30
+ },
31
+ ]
32
+ : positive;
33
+ const folded = positive.length > maxSlices;
34
+ const total = slices.reduce((sum, d) => sum + d.value, 0);
35
+ const colorOf = (i) => (folded && i === slices.length - 1 ? ink.axis : colors[i % 8]);
36
+ let angle = 0;
37
+ const arcs = slices.map((slice, i) => {
38
+ const start = angle;
39
+ angle += total > 0 ? (slice.value / total) * Math.PI * 2 : 0;
40
+ return { ...slice, start, end: angle, color: colorOf(i) };
41
+ });
42
+ const geometry = (w) => {
43
+ const outer = Math.min(w, height) / 2 - 8;
44
+ return { cx: w / 2, cy: height / 2, outer, inner: variant === 'donut' ? outer * 0.62 : 0 };
45
+ };
46
+ return (_jsx(ChartFrame, { title: title, description: description, legend: arcs.map((arc) => ({
47
+ label: arc.label,
48
+ color: arc.color,
49
+ shape: 'square',
50
+ detail: percent(arc.value, total),
51
+ })), height: height, width: width, count: arcs.length, hitTest: (x, y, w) => {
52
+ const { cx, cy, outer, inner } = geometry(w);
53
+ const dx = x - cx;
54
+ const dy = y - cy;
55
+ const r = Math.hypot(dx, dy);
56
+ if (r > outer + 8 || r < inner)
57
+ return null;
58
+ const a = (Math.atan2(dx, -dy) + Math.PI * 2) % (Math.PI * 2);
59
+ const found = arcs.findIndex((arc) => a >= arc.start && a < arc.end);
60
+ return found >= 0 ? found : null;
61
+ }, anchor: (i, w) => {
62
+ const { cx, outer } = geometry(w);
63
+ const mid = (arcs[i].start + arcs[i].end) / 2;
64
+ return { x: cx + outer * Math.sin(mid), y: 0 };
65
+ }, tooltip: (i) => ({
66
+ title: arcs[i].label,
67
+ rows: [
68
+ {
69
+ label: percent(arcs[i].value, total),
70
+ value: valueFormatter(arcs[i].value),
71
+ color: arcs[i].color,
72
+ shape: 'square',
73
+ },
74
+ ],
75
+ }), table: {
76
+ columns: [text.label, text.value, text.share],
77
+ rows: [
78
+ ...arcs.map((arc) => [arc.label, valueFormatter(arc.value), percent(arc.value, total)]),
79
+ [text.total, valueFormatter(total), '100%'],
80
+ ],
81
+ numeric: [false, true, true],
82
+ }, labels: labels, children: (w, active) => {
83
+ const { cx, cy, outer, inner } = geometry(w);
84
+ // A 2px gap between slices, as angle at the outer edge.
85
+ const gap = arcs.length > 1 ? 2 / outer : 0;
86
+ return (_jsxs(Svg, { width: w, height: height, children: [arcs.map((arc, i) => (_jsx(G, { opacity: active == null || active === i ? 1 : 0.55, children: _jsx(Path, { d: arcPath(cx, cy, active === i ? outer + 4 : outer, inner, arc.start + gap / 2, Math.max(arc.start + gap / 2, arc.end - gap / 2)), fill: arc.color }) }, arc.label))), variant === 'donut' ? (_jsxs(_Fragment, { children: [_jsx(SvgText, { x: cx, y: cy - 6, fill: foreground, fontSize: 20, fontWeight: "600", textAnchor: "middle", children: valueFormatter(total) }), _jsx(SvgText, { x: cx, y: cy + 16, fill: ink.text, fontSize: 12, textAnchor: "middle", children: text.total })] })) : null] }));
87
+ } }));
88
+ }
89
+ //# sourceMappingURL=PieChart.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PieChart.js","sourceRoot":"","sources":["../../../src/components/pie-chart/PieChart.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export { PieChart, type PieChartProps, type PieSlice } from './PieChart';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/pie-chart/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,KAAK,aAAa,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,CAAA"}
@@ -0,0 +1,2 @@
1
+ export { PieChart } from './PieChart';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/components/pie-chart/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAqC,MAAM,YAAY,CAAA"}
@@ -0,0 +1,5 @@
1
+ export * from './components/area-chart';
2
+ export * from './components/bar-chart';
3
+ export * from './components/line-chart';
4
+ export * from './components/pie-chart';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,cAAc,yBAAyB,CAAA;AACvC,cAAc,wBAAwB,CAAA;AACtC,cAAc,yBAAyB,CAAA;AACvC,cAAc,wBAAwB,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ // Chart components. The shared internals in ./charts (frame, legend, tooltip,
2
+ // table view, scales and the SVG layer) are not part of the public API.
3
+ export * from './components/area-chart';
4
+ export * from './components/bar-chart';
5
+ export * from './components/line-chart';
6
+ export * from './components/pie-chart';
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,wEAAwE;AACxE,cAAc,yBAAyB,CAAA;AACvC,cAAc,wBAAwB,CAAA;AACtC,cAAc,yBAAyB,CAAA;AACvC,cAAc,wBAAwB,CAAA"}
package/package.json ADDED
@@ -0,0 +1,93 @@
1
+ {
2
+ "name": "@advui/charts",
3
+ "version": "0.5.0",
4
+ "description": "Bar, line, area and pie charts for Adv UI: SVG on web, react-native-svg on iOS and Android, with a legend, tooltips and a table view.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/zinmin232/advui.git",
9
+ "directory": "packages/charts"
10
+ },
11
+ "homepage": "https://github.com/zinmin232/advui/tree/main/packages/charts#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/zinmin232/advui/issues"
14
+ },
15
+ "sideEffects": false,
16
+ "main": "./dist/index.js",
17
+ "types": "./dist/index.d.ts",
18
+ "react-native": "./src/index.ts",
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/index.d.ts",
22
+ "default": "./dist/index.js"
23
+ },
24
+ "./package.json": "./package.json"
25
+ },
26
+ "files": [
27
+ "dist",
28
+ "src",
29
+ "!src/**/*.test.*",
30
+ "!src/**/examples",
31
+ "!src/**/*.meta.ts"
32
+ ],
33
+ "publishConfig": {
34
+ "access": "public",
35
+ "react-native": "./dist/index.js"
36
+ },
37
+ "dependencies": {
38
+ "@advui/icons": "0.5.0"
39
+ },
40
+ "peerDependencies": {
41
+ "@advui/core": "^0.5.0",
42
+ "@advui/data": "^0.5.0",
43
+ "react": ">=19",
44
+ "react-native": "*",
45
+ "react-native-svg": ">=15",
46
+ "tamagui": "^2.7.7"
47
+ },
48
+ "peerDependenciesMeta": {
49
+ "react-native-svg": {
50
+ "optional": true
51
+ }
52
+ },
53
+ "devDependencies": {
54
+ "@jest/globals": "^29.7.0",
55
+ "@testing-library/jest-dom": "^7.0.1",
56
+ "@testing-library/react": "^16.3.3",
57
+ "@testing-library/react-native": "^14.0.1",
58
+ "@testing-library/user-event": "^14.6.7",
59
+ "@types/node": "^22.15.0",
60
+ "@types/react": "~19.2.2",
61
+ "@types/react-dom": "~19.2.2",
62
+ "@advui/data": "0.5.0",
63
+ "@advui/eslint-config": "0.1.0",
64
+ "@advui/tsconfig": "0.1.0",
65
+ "@vitejs/plugin-react": "^6.1.1",
66
+ "babel-preset-expo": "~57.0.13",
67
+ "expo": "~57.0.25",
68
+ "happy-dom": "^20.14.5",
69
+ "jest": "^29.7.0",
70
+ "jest-expo": "~57.0.5",
71
+ "react": "19.2.3",
72
+ "react-dom": "19.2.3",
73
+ "react-native": "0.86.3",
74
+ "react-native-safe-area-context": "~5.7.0",
75
+ "react-native-svg": "15.15.4",
76
+ "react-native-web": "~0.21.0",
77
+ "tamagui": "2.7.7",
78
+ "test-renderer": "~1.2.0",
79
+ "vite": "^8.3.1",
80
+ "vitest": "^5.0.2",
81
+ "@advui/core": "0.5.0",
82
+ "@advui/theme": "0.5.0"
83
+ },
84
+ "scripts": {
85
+ "build": "tsc -p tsconfig.build.json",
86
+ "typecheck": "tsc --noEmit",
87
+ "lint": "eslint .",
88
+ "test": "vitest run",
89
+ "test:watch": "vitest",
90
+ "clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
91
+ "test:native": "jest --config jest.native.config.js"
92
+ }
93
+ }
@@ -0,0 +1,303 @@
1
+ import { useIconColor } from '@advui/icons'
2
+ import { type ReactNode, useId, useRef, useState } from 'react'
3
+ import { type GetProps, View, VisuallyHidden, XStack, isWeb } from 'tamagui'
4
+ import { Button, Text } from '@advui/core'
5
+ import { Table } from '@advui/data'
6
+
7
+ /** One series (a line, a set of bars): the data field it reads and its name. */
8
+ export interface ChartSeries {
9
+ key: string
10
+ label: string
11
+ }
12
+
13
+ export interface ChartLabels {
14
+ showTable: string
15
+ hideTable: string
16
+ /** Read before the arrow-key hint on the focused plot. */
17
+ keyboardHint: string
18
+ }
19
+
20
+ export const defaultChartLabels: ChartLabels = {
21
+ showTable: 'Show table',
22
+ hideTable: 'Hide table',
23
+ keyboardHint: 'Use the arrow keys to read each value.',
24
+ }
25
+
26
+ /** The chart palette, in its fixed order, as resolved colors. */
27
+ export function useChartColors(): string[] {
28
+ // Hooks in a fixed order: one per slot.
29
+ return [
30
+ useIconColor('$chart1'),
31
+ useIconColor('$chart2'),
32
+ useIconColor('$chart3'),
33
+ useIconColor('$chart4'),
34
+ useIconColor('$chart5'),
35
+ useIconColor('$chart6'),
36
+ useIconColor('$chart7'),
37
+ useIconColor('$chart8'),
38
+ ].map((color) => color ?? 'currentColor')
39
+ }
40
+
41
+ /** Axis text, gridlines and the surface, as resolved colors. */
42
+ export function useChartInk(surface: string) {
43
+ return {
44
+ text: useIconColor('$mutedForeground') ?? 'currentColor',
45
+ grid: useIconColor('$border') ?? 'currentColor',
46
+ axis: useIconColor('$borderStrong') ?? 'currentColor',
47
+ surface: useIconColor(surface) ?? 'transparent',
48
+ }
49
+ }
50
+
51
+ export interface LegendItem {
52
+ label: string
53
+ color: string
54
+ /** Mirrors the mark: a line for line charts, a square for bars, areas and slices. */
55
+ shape: 'line' | 'square'
56
+ /** Extra text after the label, e.g. a slice's share. */
57
+ detail?: string
58
+ }
59
+
60
+ export interface TooltipContent {
61
+ title: string
62
+ rows: { label: string; value: string; color: string; shape: LegendItem['shape'] }[]
63
+ }
64
+
65
+ export interface ChartFrameProps {
66
+ /** Names the chart (its accessible name and heading). */
67
+ title: string
68
+ description?: string
69
+ legend: LegendItem[]
70
+ /** Plot height in pixels, including the axes. */
71
+ height: number
72
+ /** Fixed plot width; by default the chart fills its container. */
73
+ width?: number
74
+ /** Number of positions the arrow keys step through. */
75
+ count: number
76
+ /** The position under a point in the plot, or null. */
77
+ hitTest: (x: number, y: number, width: number) => number | null
78
+ tooltip: (index: number) => TooltipContent | null
79
+ /** Where the tooltip points, in plot coordinates. */
80
+ anchor: (index: number, width: number) => { x: number; y: number }
81
+ /** Draws the plot at the measured width; `active` is the hovered position. */
82
+ children: (width: number, active: number | null) => ReactNode
83
+ table: { columns: string[]; rows: string[][]; numeric: boolean[] }
84
+ labels?: Partial<ChartLabels>
85
+ frameProps?: GetProps<typeof View>
86
+ }
87
+
88
+ /**
89
+ * The frame every chart shares: title, legend, a measured plot with a
90
+ * tooltip (hover, tap or arrow keys), and a table view of the same data.
91
+ */
92
+ export function ChartFrame({
93
+ title,
94
+ description,
95
+ legend,
96
+ height,
97
+ width: fixedWidth,
98
+ count,
99
+ hitTest,
100
+ tooltip,
101
+ anchor,
102
+ children,
103
+ table,
104
+ labels: labelsProp,
105
+ frameProps,
106
+ }: ChartFrameProps) {
107
+ const labels = { ...defaultChartLabels, ...labelsProp }
108
+ const [measured, setMeasured] = useState(0)
109
+ const width = fixedWidth ?? measured
110
+ const [active, setActive] = useState<number | null>(null)
111
+ const [showTable, setShowTable] = useState(false)
112
+ const titleId = useId()
113
+ const tableId = useId()
114
+ const plotRef = useRef<HTMLElement | null>(null)
115
+ const content = active != null ? tooltip(active) : null
116
+
117
+ const pointAt = (clientX: number, clientY: number) => {
118
+ const rect = plotRef.current?.getBoundingClientRect()
119
+ if (!rect) return
120
+ setActive(hitTest(clientX - rect.left, clientY - rect.top, width))
121
+ }
122
+
123
+ const onKeyDown = (event: { key: string; preventDefault: () => void }) => {
124
+ const last = count - 1
125
+ const moves: Record<string, number> = {
126
+ ArrowRight: Math.min(last, (active ?? -1) + 1),
127
+ ArrowDown: Math.min(last, (active ?? -1) + 1),
128
+ ArrowLeft: Math.max(0, (active ?? count) - 1),
129
+ ArrowUp: Math.max(0, (active ?? count) - 1),
130
+ Home: 0,
131
+ End: last,
132
+ }
133
+ if (event.key in moves && count > 0) {
134
+ event.preventDefault()
135
+ setActive(moves[event.key]!)
136
+ } else if (event.key === 'Escape') setActive(null)
137
+ }
138
+
139
+ const point = active != null && width > 0 ? anchor(active, width) : null
140
+ const summary = content
141
+ ? `${content.title}: ${content.rows.map((r) => `${r.label} ${r.value}`).join(', ')}`
142
+ : ''
143
+
144
+ return (
145
+ <View
146
+ render="figure"
147
+ margin={0}
148
+ gap="$3"
149
+ width="100%"
150
+ aria-labelledby={titleId}
151
+ {...frameProps}
152
+ >
153
+ <View gap="$1">
154
+ <Text id={titleId} size="sm" weight="semibold">
155
+ {title}
156
+ </Text>
157
+ {description ? (
158
+ <Text size="xs" tone="muted">
159
+ {description}
160
+ </Text>
161
+ ) : null}
162
+ </View>
163
+
164
+ {legend.length > 0 ? (
165
+ <XStack flexWrap="wrap" columnGap="$4" rowGap="$1.5">
166
+ {legend.map((item) => (
167
+ <XStack key={item.label} alignItems="center" gap="$1.5">
168
+ <View
169
+ aria-hidden
170
+ backgroundColor={item.color as never}
171
+ width={item.shape === 'line' ? '$3' : '$2.5'}
172
+ height={item.shape === 'line' ? 2 : '$2.5'}
173
+ borderRadius={item.shape === 'line' ? '$full' : '$xs'}
174
+ />
175
+ <Text size="xs" tone="muted">
176
+ {item.label}
177
+ {item.detail ? ` · ${item.detail}` : ''}
178
+ </Text>
179
+ </XStack>
180
+ ))}
181
+ </XStack>
182
+ ) : null}
183
+
184
+ <View
185
+ ref={plotRef as never}
186
+ position="relative"
187
+ height={height}
188
+ width={fixedWidth ?? '100%'}
189
+ onLayout={(event: { nativeEvent: { layout: { width: number } } }) =>
190
+ setMeasured(Math.floor(event.nativeEvent.layout.width))
191
+ }
192
+ // The plot is one focusable image; the arrow keys read it point by point.
193
+ role="img"
194
+ aria-label={`${title}. ${description ? `${description}. ` : ''}${labels.keyboardHint}`}
195
+ {...(isWeb
196
+ ? {
197
+ tabIndex: 0,
198
+ onKeyDown,
199
+ onBlur: () => setActive(null),
200
+ onMouseMove: (event: { clientX: number; clientY: number }) =>
201
+ pointAt(event.clientX, event.clientY),
202
+ onMouseLeave: () => setActive(null),
203
+ }
204
+ : {
205
+ accessible: true,
206
+ // Native has no live region here: the reading becomes the plot's value.
207
+ ...(summary && { accessibilityValue: { text: summary } }),
208
+ onPress: (event: { nativeEvent: { locationX: number; locationY: number } }) =>
209
+ setActive(hitTest(event.nativeEvent.locationX, event.nativeEvent.locationY, width)),
210
+ })}
211
+ borderRadius="$sm"
212
+ focusVisibleStyle={{
213
+ outlineColor: '$ring',
214
+ outlineStyle: 'solid',
215
+ outlineWidth: 2,
216
+ outlineOffset: 4,
217
+ }}
218
+ >
219
+ {width > 0 ? children(width, active) : null}
220
+ {content && point ? (
221
+ <View
222
+ aria-hidden
223
+ pointerEvents="none"
224
+ position="absolute"
225
+ top={0}
226
+ {...(point.x < width / 2 ? { left: point.x + 12 } : { right: width - point.x + 12 })}
227
+ backgroundColor="$popover"
228
+ borderWidth={1}
229
+ borderColor="$border"
230
+ borderRadius="$md"
231
+ paddingHorizontal="$2.5"
232
+ paddingVertical="$2"
233
+ gap="$1"
234
+ minWidth="$32"
235
+ zIndex={1}
236
+ >
237
+ <Text size="xs" tone="muted">
238
+ {content.title}
239
+ </Text>
240
+ {content.rows.map((row) => (
241
+ <XStack key={row.label} alignItems="center" gap="$2">
242
+ <View
243
+ backgroundColor={row.color as never}
244
+ width="$2.5"
245
+ height={row.shape === 'line' ? 2 : '$2.5'}
246
+ borderRadius={row.shape === 'line' ? '$full' : '$xs'}
247
+ />
248
+ <Text size="sm" weight="semibold">
249
+ {row.value}
250
+ </Text>
251
+ <Text size="xs" tone="muted" flexShrink={1}>
252
+ {row.label}
253
+ </Text>
254
+ </XStack>
255
+ ))}
256
+ </View>
257
+ ) : null}
258
+ </View>
259
+ {/* Outside the plot: an img's contents are hidden from assistive technology. */}
260
+ {isWeb ? <VisuallyHidden aria-live="polite">{summary}</VisuallyHidden> : null}
261
+
262
+ <View alignItems="flex-start" gap="$2">
263
+ <Button
264
+ size="sm"
265
+ variant="ghost"
266
+ // Lines the label up with the title above.
267
+ marginLeft="$-3"
268
+ aria-expanded={showTable}
269
+ {...(isWeb && { 'aria-controls': tableId })}
270
+ onPress={() => setShowTable(!showTable)}
271
+ >
272
+ {showTable ? labels.hideTable : labels.showTable}
273
+ </Button>
274
+ {showTable ? (
275
+ <View id={tableId} width="100%">
276
+ <Table aria-label={title} size="sm" minWidth={Math.max(320, table.columns.length * 96)}>
277
+ <Table.Header>
278
+ <Table.Row>
279
+ {table.columns.map((column, i) => (
280
+ <Table.Head key={column} align={table.numeric[i] ? 'end' : 'start'}>
281
+ {column}
282
+ </Table.Head>
283
+ ))}
284
+ </Table.Row>
285
+ </Table.Header>
286
+ <Table.Body>
287
+ {table.rows.map((row, r) => (
288
+ <Table.Row key={r}>
289
+ {row.map((cell, i) => (
290
+ <Table.Cell key={i} align={table.numeric[i] ? 'end' : 'start'}>
291
+ {cell}
292
+ </Table.Cell>
293
+ ))}
294
+ </Table.Row>
295
+ ))}
296
+ </Table.Body>
297
+ </Table>
298
+ </View>
299
+ ) : null}
300
+ </View>
301
+ </View>
302
+ )
303
+ }