@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,216 @@
1
+ import { CategoryLabels, ValueGrid, cartesianPadding } from './cartesian'
2
+ import {
3
+ ChartFrame,
4
+ type ChartLabels,
5
+ type ChartSeries,
6
+ useChartColors,
7
+ useChartInk,
8
+ } from './ChartFrame'
9
+ import { formatFull, numberAt } from './format'
10
+ import { linearScale, niceTicks } from './scale'
11
+ import { Circle, G, Line, Path, Svg } from './svg'
12
+
13
+ export interface LinePlotProps {
14
+ title: string
15
+ description?: string
16
+ /** One object per x position, e.g. `{ month: 'Jan', reached: 1200 }`. */
17
+ data: Record<string, unknown>[]
18
+ /** The field holding each row's x label. */
19
+ index: string
20
+ /** Heading of the x column in the table view. Default: `index`. */
21
+ indexLabel?: string
22
+ /** The numeric fields to plot, in color order. */
23
+ series: ChartSeries[]
24
+ /** Height in pixels, including the axes. Default: 240. */
25
+ height?: number
26
+ width?: number
27
+ /** Formats values in the tooltip and table. Axis ticks are compact (1.2K). */
28
+ valueFormatter?: (value: number) => string
29
+ /** Plot area always starts at zero. Default: true for areas, false for lines. */
30
+ startAtZero?: boolean
31
+ /**
32
+ * Color of the surface the chart sits on, for the ring around markers.
33
+ * Default: `$background`; use `$card` inside a Card.
34
+ */
35
+ surface?: string
36
+ labels?: Partial<ChartLabels>
37
+ }
38
+
39
+ /** Shared by LineChart and AreaChart. */
40
+ export function LinePlot({
41
+ title,
42
+ description,
43
+ data,
44
+ index,
45
+ indexLabel = index,
46
+ series,
47
+ height = 240,
48
+ width,
49
+ valueFormatter = formatFull,
50
+ startAtZero,
51
+ surface = '$background',
52
+ labels,
53
+ area = false,
54
+ stacked = false,
55
+ }: LinePlotProps & { area?: boolean; stacked?: boolean }) {
56
+ const colors = useChartColors()
57
+ const ink = useChartInk(surface)
58
+ const categories = data.map((row) => String(row[index] ?? ''))
59
+ const values = data.map((row) => series.map((s) => numberAt(row, s.key)))
60
+
61
+ // Stacked areas draw each series on top of the ones before it.
62
+ const tops = values.map((row) => {
63
+ let sum = 0
64
+ return row.map((v) => (v == null ? null : stacked ? (sum += v) : v))
65
+ })
66
+ const all = tops.flat().filter((v): v is number => v != null)
67
+ const zero = startAtZero ?? area
68
+ const ticks = niceTicks(
69
+ zero ? Math.min(0, ...all) : Math.min(...all),
70
+ zero ? Math.max(0, ...all) : Math.max(...all),
71
+ )
72
+ const pad = cartesianPadding(ticks)
73
+ const n = data.length
74
+
75
+ const scales = (w: number) => {
76
+ const step = (w - pad.left - pad.right) / Math.max(1, n)
77
+ return {
78
+ step,
79
+ x: (i: number) => pad.left + (i + 0.5) * step,
80
+ y: linearScale([ticks[0]!, ticks.at(-1)!], [height - pad.bottom, pad.top]),
81
+ }
82
+ }
83
+
84
+ const format = (value: number | null) => (value == null ? '–' : valueFormatter(value))
85
+ const shape = area ? ('square' as const) : ('line' as const)
86
+
87
+ return (
88
+ <ChartFrame
89
+ title={title}
90
+ description={description}
91
+ legend={
92
+ series.length > 1
93
+ ? series.map((s, i) => ({ label: s.label, color: colors[i % 8]!, shape }))
94
+ : []
95
+ }
96
+ height={height}
97
+ width={width}
98
+ count={n}
99
+ hitTest={(x, _y, w) => {
100
+ const { step } = scales(w)
101
+ const i = Math.round((x - pad.left) / step - 0.5)
102
+ return Math.max(0, Math.min(n - 1, i))
103
+ }}
104
+ anchor={(i, w) => ({ x: scales(w).x(i), y: pad.top })}
105
+ tooltip={(i) => ({
106
+ title: categories[i]!,
107
+ rows: series.map((s, j) => ({
108
+ label: s.label,
109
+ value: format(values[i]![j]!),
110
+ color: colors[j % 8]!,
111
+ shape,
112
+ })),
113
+ })}
114
+ table={{
115
+ columns: [indexLabel, ...series.map((s) => s.label)],
116
+ rows: data.map((_, i) => [categories[i]!, ...values[i]!.map(format)]),
117
+ numeric: [false, ...series.map(() => true)],
118
+ }}
119
+ labels={labels}
120
+ >
121
+ {(w, active) => {
122
+ const { x, y, step } = scales(w)
123
+ const base = (i: number, j: number) =>
124
+ stacked && j > 0 ? (tops[i]![j - 1] ?? 0) : Math.max(ticks[0]!, 0)
125
+ return (
126
+ <Svg width={w} height={height}>
127
+ <ValueGrid ticks={ticks} y={y} left={pad.left} right={w - pad.right} ink={ink} />
128
+ <CategoryLabels
129
+ labels={categories}
130
+ x={x}
131
+ y={height - pad.bottom + 4}
132
+ available={step}
133
+ ink={ink}
134
+ />
135
+ {active != null ? (
136
+ <Line
137
+ x1={x(active)}
138
+ x2={x(active)}
139
+ y1={pad.top}
140
+ y2={height - pad.bottom}
141
+ stroke={ink.axis}
142
+ strokeWidth={1}
143
+ />
144
+ ) : null}
145
+ {series.map((s, j) => {
146
+ const color = colors[j % 8]!
147
+ // A missing value breaks the line instead of dropping to zero.
148
+ const runs: number[][] = [[]]
149
+ tops.forEach((row, i) => {
150
+ if (row[j] == null) runs.push([])
151
+ else runs.at(-1)!.push(i)
152
+ })
153
+ return (
154
+ <G key={s.key}>
155
+ {runs
156
+ .filter((run) => run.length > 0)
157
+ .map((run) => {
158
+ const line = run
159
+ .map((i, k) => `${k === 0 ? 'M' : 'L'}${x(i)},${y(tops[i]![j]!)}`)
160
+ .join('')
161
+ const fill = area
162
+ ? `${line}${[...run]
163
+ .reverse()
164
+ .map((i) => `L${x(i)},${y(base(i, j))}`)
165
+ .join('')}Z`
166
+ : null
167
+ // A lone value between gaps has no line to draw: show it as a dot.
168
+ if (run.length === 1) {
169
+ const i = run[0]!
170
+ return <Circle key={i} cx={x(i)} cy={y(tops[i]![j]!)} r={3} fill={color} />
171
+ }
172
+ return (
173
+ <G key={run[0]}>
174
+ {fill ? <Path d={fill} fill={color} fillOpacity={0.1} /> : null}
175
+ <Path
176
+ d={line}
177
+ fill="none"
178
+ stroke={color}
179
+ strokeWidth={2}
180
+ strokeLinejoin="round"
181
+ strokeLinecap="round"
182
+ />
183
+ </G>
184
+ )
185
+ })}
186
+ </G>
187
+ )
188
+ })}
189
+ {/* End markers, and every series' marker at the hovered position, ringed in the surface color. */}
190
+ {series.map((s, j) => {
191
+ const color = colors[j % 8]!
192
+ const lastIndex = tops.reduce<number>(
193
+ (last, row, i) => (row[j] != null ? i : last),
194
+ -1,
195
+ )
196
+ const points = new Set([lastIndex, ...(active != null ? [active] : [])])
197
+ return [...points]
198
+ .filter((i) => i >= 0 && tops[i]?.[j] != null)
199
+ .map((i) => (
200
+ <Circle
201
+ key={`${s.key}-${i}`}
202
+ cx={x(i)}
203
+ cy={y(tops[i]![j]!)}
204
+ r={4}
205
+ fill={color}
206
+ stroke={ink.surface}
207
+ strokeWidth={2}
208
+ />
209
+ ))
210
+ })}
211
+ </Svg>
212
+ )
213
+ }}
214
+ </ChartFrame>
215
+ )
216
+ }
@@ -0,0 +1,99 @@
1
+ import { Line, SvgText } from './svg'
2
+ import { formatCompact } from './format'
3
+
4
+ /** Space around the plot for axis labels. */
5
+ export function cartesianPadding(
6
+ ticks: number[],
7
+ format: (value: number) => string = formatCompact,
8
+ ) {
9
+ // About 7px per character of the widest tick label at 12px.
10
+ const labelWidth = Math.max(...ticks.map((t) => format(t).length)) * 7
11
+ return { top: 8, right: 8, bottom: 24, left: labelWidth + 12 }
12
+ }
13
+
14
+ /** Horizontal gridlines with value labels on the left. */
15
+ export function ValueGrid({
16
+ ticks,
17
+ y,
18
+ left,
19
+ right,
20
+ ink,
21
+ format = formatCompact,
22
+ }: {
23
+ ticks: number[]
24
+ y: (value: number) => number
25
+ left: number
26
+ right: number
27
+ ink: { text: string; grid: string; axis: string }
28
+ format?: (value: number) => string
29
+ }) {
30
+ return (
31
+ <>
32
+ {ticks.map((tick) => (
33
+ <Line
34
+ key={`grid-${tick}`}
35
+ x1={left}
36
+ x2={right}
37
+ y1={y(tick)}
38
+ y2={y(tick)}
39
+ stroke={tick === 0 ? ink.axis : ink.grid}
40
+ strokeWidth={1}
41
+ />
42
+ ))}
43
+ {ticks.map((tick) => (
44
+ <SvgText
45
+ key={`label-${tick}`}
46
+ x={left - 8}
47
+ y={y(tick)}
48
+ dy="0.35em"
49
+ fill={ink.text}
50
+ fontSize={12}
51
+ textAnchor="end"
52
+ >
53
+ {format(tick)}
54
+ </SvgText>
55
+ ))}
56
+ </>
57
+ )
58
+ }
59
+
60
+ /**
61
+ * Category labels under the plot. When they would overlap, only every nth
62
+ * is drawn (the first and the rest in steps), so none collide.
63
+ */
64
+ export function CategoryLabels({
65
+ labels,
66
+ x,
67
+ y,
68
+ available,
69
+ ink,
70
+ }: {
71
+ labels: string[]
72
+ x: (index: number) => number
73
+ y: number
74
+ /** Width each label may use before thinning kicks in. */
75
+ available: number
76
+ ink: { text: string }
77
+ }) {
78
+ const widest = Math.max(1, ...labels.map((l) => l.length)) * 7 + 8
79
+ const step = Math.max(1, Math.ceil(widest / Math.max(1, available)))
80
+ return (
81
+ <>
82
+ {labels.map((label, index) =>
83
+ index % step === 0 ? (
84
+ <SvgText
85
+ key={`${label}-${index}`}
86
+ x={x(index)}
87
+ y={y}
88
+ dy="0.9em"
89
+ fill={ink.text}
90
+ fontSize={12}
91
+ textAnchor="middle"
92
+ >
93
+ {label}
94
+ </SvgText>
95
+ ) : null,
96
+ )}
97
+ </>
98
+ )
99
+ }
@@ -0,0 +1,14 @@
1
+ const compact = new Intl.NumberFormat('en-US', { notation: 'compact', maximumFractionDigits: 1 })
2
+ const full = new Intl.NumberFormat('en-US', { maximumFractionDigits: 2 })
3
+
4
+ /** Axis ticks: 1.2K, 3.4M. */
5
+ export const formatCompact = (value: number) => compact.format(value)
6
+
7
+ /** Tooltips and the table: 1,234.5. */
8
+ export const formatFull = (value: number) => full.format(value)
9
+
10
+ /** Reads a numeric field; anything else counts as missing. */
11
+ export function numberAt(row: Record<string, unknown>, key: string): number | null {
12
+ const value = row[key]
13
+ return typeof value === 'number' && Number.isFinite(value) ? value : null
14
+ }
@@ -0,0 +1,16 @@
1
+ export {
2
+ ChartFrame,
3
+ defaultChartLabels,
4
+ useChartColors,
5
+ useChartInk,
6
+ type ChartFrameProps,
7
+ type ChartLabels,
8
+ type ChartSeries,
9
+ type LegendItem,
10
+ type TooltipContent,
11
+ } from './ChartFrame'
12
+ export { CategoryLabels, ValueGrid, cartesianPadding } from './cartesian'
13
+ export { LinePlot, type LinePlotProps } from './LinePlot'
14
+ export { formatCompact, formatFull, numberAt } from './format'
15
+ export { arcPath, bandScale, barPath, linearScale, niceTicks } from './scale'
16
+ export { Circle, G, Line, Path, Rect, Svg, SvgText } from './svg'
@@ -0,0 +1,91 @@
1
+ /** Clean, round tick values ("nice numbers") covering [min, max]. */
2
+ export function niceTicks(min: number, max: number, count = 5): number[] {
3
+ if (!Number.isFinite(min) || !Number.isFinite(max)) return [0]
4
+ if (min === max) {
5
+ if (min === 0) return [0, 1]
6
+ ;[min, max] = min > 0 ? [0, min] : [min, 0]
7
+ }
8
+ const step = niceStep((max - min) / Math.max(1, count - 1))
9
+ const start = Math.floor(min / step) * step
10
+ const end = Math.ceil(max / step) * step
11
+ const ticks: number[] = []
12
+ // Rounding keeps 0.1 + 0.2 style float noise out of the labels.
13
+ for (let v = start; v <= end + step / 2; v += step) ticks.push(Number(v.toPrecision(12)))
14
+ return ticks
15
+ }
16
+
17
+ function niceStep(raw: number): number {
18
+ const power = 10 ** Math.floor(Math.log10(raw))
19
+ const fraction = raw / power
20
+ const nice =
21
+ fraction <= 1 ? 1 : fraction <= 2 ? 2 : fraction <= 2.5 ? 2.5 : fraction <= 5 ? 5 : 10
22
+ return nice * power
23
+ }
24
+
25
+ /** Maps a value in [d0, d1] to [r0, r1]. */
26
+ export function linearScale([d0, d1]: [number, number], [r0, r1]: [number, number]) {
27
+ const span = d1 - d0 || 1
28
+ return (value: number) => r0 + ((value - d0) / span) * (r1 - r0)
29
+ }
30
+
31
+ /**
32
+ * Splits [start, end] into `count` equal bands; `band(i)` is the start of
33
+ * band i and `bandwidth` its size.
34
+ */
35
+ export function bandScale(count: number, [start, end]: [number, number]) {
36
+ const bandwidth = (end - start) / Math.max(1, count)
37
+ return { band: (index: number) => start + index * bandwidth, bandwidth }
38
+ }
39
+
40
+ /**
41
+ * A rectangle with rounded corners on its data end only (the end away from
42
+ * the baseline), as an SVG path. `direction` is where the data end points.
43
+ */
44
+ export function barPath(
45
+ x: number,
46
+ y: number,
47
+ width: number,
48
+ height: number,
49
+ radius: number,
50
+ direction: 'up' | 'down' | 'right' | 'left',
51
+ ): string {
52
+ if (width <= 0 || height <= 0) return ''
53
+ const r = Math.max(0, Math.min(radius, width / 2, height / 2))
54
+ const x2 = x + width
55
+ const y2 = y + height
56
+ switch (direction) {
57
+ case 'up':
58
+ return `M${x},${y2}V${y + r}Q${x},${y} ${x + r},${y}H${x2 - r}Q${x2},${y} ${x2},${y + r}V${y2}Z`
59
+ case 'down':
60
+ return `M${x},${y}V${y2 - r}Q${x},${y2} ${x + r},${y2}H${x2 - r}Q${x2},${y2} ${x2},${y2 - r}V${y}Z`
61
+ case 'right':
62
+ return `M${x},${y}H${x2 - r}Q${x2},${y} ${x2},${y + r}V${y2 - r}Q${x2},${y2} ${x2 - r},${y2}H${x}Z`
63
+ case 'left':
64
+ return `M${x2},${y}H${x + r}Q${x},${y} ${x},${y + r}V${y2 - r}Q${x},${y2} ${x + r},${y2}H${x2}Z`
65
+ }
66
+ }
67
+
68
+ /** A pie or donut slice as an SVG path; angles in radians, 0 at 12 o'clock. */
69
+ export function arcPath(
70
+ cx: number,
71
+ cy: number,
72
+ outer: number,
73
+ inner: number,
74
+ start: number,
75
+ end: number,
76
+ ): string {
77
+ const point = (radius: number, angle: number) =>
78
+ `${cx + radius * Math.sin(angle)},${cy - radius * Math.cos(angle)}`
79
+ const large = end - start > Math.PI ? 1 : 0
80
+ // A full circle cannot be one arc: split it in two.
81
+ if (end - start >= Math.PI * 2 - 1e-6) {
82
+ const mid = start + Math.PI
83
+ return inner > 0
84
+ ? `M${point(outer, start)}A${outer},${outer} 0 1 1 ${point(outer, mid)}A${outer},${outer} 0 1 1 ${point(outer, start)}` +
85
+ `M${point(inner, start)}A${inner},${inner} 0 1 0 ${point(inner, mid)}A${inner},${inner} 0 1 0 ${point(inner, start)}Z`
86
+ : `M${point(outer, start)}A${outer},${outer} 0 1 1 ${point(outer, mid)}A${outer},${outer} 0 1 1 ${point(outer, start)}Z`
87
+ }
88
+ return inner > 0
89
+ ? `M${point(outer, start)}A${outer},${outer} 0 ${large} 1 ${point(outer, end)}L${point(inner, end)}A${inner},${inner} 0 ${large} 0 ${point(inner, start)}Z`
90
+ : `M${cx},${cy}L${point(outer, start)}A${outer},${outer} 0 ${large} 1 ${point(outer, end)}Z`
91
+ }
@@ -0,0 +1,62 @@
1
+ import type { ReactNode } from 'react'
2
+ import NativeSvg, {
3
+ Circle as NativeCircle,
4
+ G as NativeG,
5
+ Line as NativeLine,
6
+ Path as NativePath,
7
+ Rect as NativeRect,
8
+ Text as NativeText,
9
+ } from 'react-native-svg'
10
+
11
+ // Native: the web file's components, mapped onto react-native-svg.
12
+
13
+ type Common = { children?: ReactNode; opacity?: number }
14
+ type Paint = { fill?: string; fillOpacity?: number; stroke?: string; strokeWidth?: number }
15
+
16
+ export function Svg({ width, height, children }: Common & { width: number; height: number }) {
17
+ return (
18
+ <NativeSvg
19
+ width={width}
20
+ height={height}
21
+ viewBox={`0 0 ${width} ${height}`}
22
+ accessible={false}
23
+ importantForAccessibility="no-hide-descendants"
24
+ >
25
+ {children}
26
+ </NativeSvg>
27
+ )
28
+ }
29
+
30
+ export const G = ({ children, opacity }: Common) => <NativeG opacity={opacity}>{children}</NativeG>
31
+
32
+ export const Rect = (props: Paint & { x: number; y: number; width: number; height: number }) => (
33
+ <NativeRect {...props} />
34
+ )
35
+
36
+ export const Path = (
37
+ props: Paint & { d: string; strokeLinejoin?: 'round'; strokeLinecap?: 'round' },
38
+ ) => <NativePath {...props} />
39
+
40
+ export const Line = (props: Paint & { x1: number; y1: number; x2: number; y2: number }) => (
41
+ <NativeLine {...props} />
42
+ )
43
+
44
+ export const Circle = (props: Paint & { cx: number; cy: number; r: number }) => (
45
+ <NativeCircle {...props} />
46
+ )
47
+
48
+ export function SvgText({
49
+ children,
50
+ ...props
51
+ }: {
52
+ children: ReactNode
53
+ x: number
54
+ y: number
55
+ fill: string
56
+ fontSize: number
57
+ fontWeight?: string
58
+ textAnchor?: 'start' | 'middle' | 'end'
59
+ dy?: string
60
+ }) {
61
+ return <NativeText {...(props as object)}>{children}</NativeText>
62
+ }
@@ -0,0 +1,57 @@
1
+ import type { ReactNode } from 'react'
2
+
3
+ // Web: plain DOM SVG, so web bundles do not need react-native-svg. The
4
+ // native file maps the same components onto react-native-svg.
5
+
6
+ type Common = { children?: ReactNode; opacity?: number }
7
+ type Paint = { fill?: string; fillOpacity?: number; stroke?: string; strokeWidth?: number }
8
+
9
+ export function Svg({ width, height, children }: Common & { width: number; height: number }) {
10
+ return (
11
+ <svg
12
+ width={width}
13
+ height={height}
14
+ viewBox={`0 0 ${width} ${height}`}
15
+ style={{ display: 'block', overflow: 'visible', fontFamily: 'inherit' }}
16
+ aria-hidden
17
+ focusable="false"
18
+ >
19
+ {children}
20
+ </svg>
21
+ )
22
+ }
23
+
24
+ export const G = ({ children, opacity }: Common) => <g opacity={opacity}>{children}</g>
25
+
26
+ export const Rect = (props: Paint & { x: number; y: number; width: number; height: number }) => (
27
+ <rect {...props} />
28
+ )
29
+
30
+ export const Path = (
31
+ props: Paint & { d: string; strokeLinejoin?: 'round'; strokeLinecap?: 'round' },
32
+ ) => <path {...props} />
33
+
34
+ export const Line = (props: Paint & { x1: number; y1: number; x2: number; y2: number }) => (
35
+ <line {...props} />
36
+ )
37
+
38
+ export const Circle = (props: Paint & { cx: number; cy: number; r: number }) => (
39
+ <circle {...props} />
40
+ )
41
+
42
+ export function SvgText({
43
+ children,
44
+ ...props
45
+ }: {
46
+ children: ReactNode
47
+ x: number
48
+ y: number
49
+ fill: string
50
+ fontSize: number
51
+ fontWeight?: string
52
+ textAnchor?: 'start' | 'middle' | 'end'
53
+ /** Vertical nudge: 0.35em centers text on y. */
54
+ dy?: string
55
+ }) {
56
+ return <text {...props}>{children}</text>
57
+ }
@@ -0,0 +1,14 @@
1
+ import { LinePlot, type LinePlotProps } from '../../charts'
2
+
3
+ export interface AreaChartProps extends LinePlotProps {
4
+ /** Stacks the series, so the top edge is their total (part-to-whole over time). */
5
+ stacked?: boolean
6
+ }
7
+
8
+ /**
9
+ * A line chart with a light wash under each line, from zero. Best for one
10
+ * series, or for stacked totals over time.
11
+ */
12
+ export function AreaChart({ stacked = false, ...props }: AreaChartProps) {
13
+ return <LinePlot {...props} area stacked={stacked} />
14
+ }
@@ -0,0 +1 @@
1
+ export { AreaChart, type AreaChartProps } from './AreaChart'