panelui-native 0.22.6 → 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/lib/module/components/area-chart/index.js +826 -0
- package/lib/module/components/area-chart/index.js.map +1 -0
- package/lib/module/components/avatar/index.js +1 -1
- package/lib/module/components/avatar/index.js.map +1 -1
- package/lib/module/components/bar-chart/index.js +853 -0
- package/lib/module/components/bar-chart/index.js.map +1 -0
- package/lib/module/components/bottom-sheet/index.js +207 -23
- package/lib/module/components/bottom-sheet/index.js.map +1 -1
- package/lib/module/components/breadcrumb/index.js +275 -0
- package/lib/module/components/breadcrumb/index.js.map +1 -0
- package/lib/module/components/chip/index.js +2 -2
- package/lib/module/components/direction/index.js +13 -19
- package/lib/module/components/direction/index.js.map +1 -1
- package/lib/module/components/frame/index.js +17 -0
- package/lib/module/components/frame/index.js.map +1 -1
- package/lib/module/components/heatmap-chart/index.js +28 -8
- package/lib/module/components/heatmap-chart/index.js.map +1 -1
- package/lib/module/components/input-group/index.js +2 -2
- package/lib/module/components/input-group/index.js.map +1 -1
- package/lib/module/components/line-chart/index.js +4 -146
- package/lib/module/components/line-chart/index.js.map +1 -1
- package/lib/module/components/loader/index.js +758 -0
- package/lib/module/components/loader/index.js.map +1 -0
- package/lib/module/components/map/basemap.js +265 -0
- package/lib/module/components/map/basemap.js.map +1 -0
- package/lib/module/components/map/index.js +850 -0
- package/lib/module/components/map/index.js.map +1 -0
- package/lib/module/components/map/maplibre.js +87 -0
- package/lib/module/components/map/maplibre.js.map +1 -0
- package/lib/module/components/message/index.js +8 -7
- package/lib/module/components/message/index.js.map +1 -1
- package/lib/module/components/otp-input/index.js +2 -2
- package/lib/module/components/progress/index.js +8 -2
- package/lib/module/components/progress/index.js.map +1 -1
- package/lib/module/components/ring-chart/index.js +423 -0
- package/lib/module/components/ring-chart/index.js.map +1 -0
- package/lib/module/components/section-rail/index.js +2 -2
- package/lib/module/components/separator/index.js +76 -22
- package/lib/module/components/separator/index.js.map +1 -1
- package/lib/module/components/shimmer/index.js +10 -1
- package/lib/module/components/shimmer/index.js.map +1 -1
- package/lib/module/components/slider/index.js +19 -5
- package/lib/module/components/slider/index.js.map +1 -1
- package/lib/module/components/steps/index.js +1 -1
- package/lib/module/components/switch/index.js +6 -1
- package/lib/module/components/switch/index.js.map +1 -1
- package/lib/module/components/typography/index.js +5 -0
- package/lib/module/components/typography/index.js.map +1 -1
- package/lib/module/hooks/use-direction.js +43 -0
- package/lib/module/hooks/use-direction.js.map +1 -0
- package/lib/module/icons/index.js +117 -2
- package/lib/module/icons/index.js.map +1 -1
- package/lib/module/index.js +8 -2
- package/lib/module/index.js.map +1 -1
- package/lib/module/primitives/text.js +30 -10
- package/lib/module/primitives/text.js.map +1 -1
- package/lib/module/utils/chart.js +279 -0
- package/lib/module/utils/chart.js.map +1 -0
- package/lib/typescript/src/components/area-chart/index.d.ts +197 -0
- package/lib/typescript/src/components/area-chart/index.d.ts.map +1 -0
- package/lib/typescript/src/components/bar-chart/index.d.ts +228 -0
- package/lib/typescript/src/components/bar-chart/index.d.ts.map +1 -0
- package/lib/typescript/src/components/bottom-sheet/index.d.ts +75 -4
- package/lib/typescript/src/components/bottom-sheet/index.d.ts.map +1 -1
- package/lib/typescript/src/components/breadcrumb/index.d.ts +100 -0
- package/lib/typescript/src/components/breadcrumb/index.d.ts.map +1 -0
- package/lib/typescript/src/components/direction/index.d.ts +11 -10
- package/lib/typescript/src/components/direction/index.d.ts.map +1 -1
- package/lib/typescript/src/components/frame/index.d.ts +17 -0
- package/lib/typescript/src/components/frame/index.d.ts.map +1 -1
- package/lib/typescript/src/components/heatmap-chart/index.d.ts +22 -3
- package/lib/typescript/src/components/heatmap-chart/index.d.ts.map +1 -1
- package/lib/typescript/src/components/line-chart/index.d.ts +1 -0
- package/lib/typescript/src/components/line-chart/index.d.ts.map +1 -1
- package/lib/typescript/src/components/loader/index.d.ts +38 -0
- package/lib/typescript/src/components/loader/index.d.ts.map +1 -0
- package/lib/typescript/src/components/map/basemap.d.ts +76 -0
- package/lib/typescript/src/components/map/basemap.d.ts.map +1 -0
- package/lib/typescript/src/components/map/index.d.ts +367 -0
- package/lib/typescript/src/components/map/index.d.ts.map +1 -0
- package/lib/typescript/src/components/map/maplibre.d.ts +193 -0
- package/lib/typescript/src/components/map/maplibre.d.ts.map +1 -0
- package/lib/typescript/src/components/message/index.d.ts.map +1 -1
- package/lib/typescript/src/components/progress/index.d.ts.map +1 -1
- package/lib/typescript/src/components/ring-chart/index.d.ts +156 -0
- package/lib/typescript/src/components/ring-chart/index.d.ts.map +1 -0
- package/lib/typescript/src/components/separator/index.d.ts +36 -1
- package/lib/typescript/src/components/separator/index.d.ts.map +1 -1
- package/lib/typescript/src/components/shimmer/index.d.ts.map +1 -1
- package/lib/typescript/src/components/slider/index.d.ts.map +1 -1
- package/lib/typescript/src/components/switch/index.d.ts.map +1 -1
- package/lib/typescript/src/components/typography/index.d.ts +6 -0
- package/lib/typescript/src/components/typography/index.d.ts.map +1 -1
- package/lib/typescript/src/hooks/use-direction.d.ts +20 -0
- package/lib/typescript/src/hooks/use-direction.d.ts.map +1 -0
- package/lib/typescript/src/icons/index.d.ts +5 -0
- package/lib/typescript/src/icons/index.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +9 -3
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/primitives/text.d.ts.map +1 -1
- package/lib/typescript/src/utils/chart.d.ts +96 -0
- package/lib/typescript/src/utils/chart.d.ts.map +1 -0
- package/package.json +9 -1
- package/src/components/area-chart/index.tsx +994 -0
- package/src/components/avatar/index.tsx +1 -1
- package/src/components/bar-chart/index.tsx +1048 -0
- package/src/components/bottom-sheet/index.tsx +280 -20
- package/src/components/breadcrumb/index.tsx +365 -0
- package/src/components/chip/index.tsx +2 -2
- package/src/components/direction/index.tsx +20 -21
- package/src/components/frame/index.tsx +17 -0
- package/src/components/heatmap-chart/index.tsx +58 -11
- package/src/components/input-group/index.tsx +2 -2
- package/src/components/line-chart/index.tsx +13 -181
- package/src/components/loader/index.tsx +812 -0
- package/src/components/map/basemap.ts +304 -0
- package/src/components/map/index.tsx +1118 -0
- package/src/components/map/maplibre.ts +201 -0
- package/src/components/message/index.tsx +8 -7
- package/src/components/otp-input/index.tsx +2 -2
- package/src/components/progress/index.tsx +10 -2
- package/src/components/ring-chart/index.tsx +543 -0
- package/src/components/section-rail/index.tsx +2 -2
- package/src/components/separator/index.tsx +77 -19
- package/src/components/shimmer/index.tsx +10 -1
- package/src/components/slider/index.tsx +20 -5
- package/src/components/steps/index.tsx +1 -1
- package/src/components/switch/index.tsx +6 -1
- package/src/components/typography/index.tsx +5 -0
- package/src/hooks/use-direction.ts +45 -0
- package/src/icons/index.tsx +66 -0
- package/src/index.ts +84 -0
- package/src/primitives/text.tsx +25 -7
- package/src/utils/chart.ts +355 -0
|
@@ -0,0 +1,853 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* BarChart — categories compared by length, drawn and animated on the UI thread.
|
|
5
|
+
*
|
|
6
|
+
* Composed the same way `LineChart` is: the grid, each series, the axes and the
|
|
7
|
+
* readout are separate children, so a chart that wants no grid simply does not
|
|
8
|
+
* have one.
|
|
9
|
+
*
|
|
10
|
+
* ```tsx
|
|
11
|
+
* <BarChart data={revenue} xDataKey="month">
|
|
12
|
+
* <BarChart.Grid />
|
|
13
|
+
* <BarChart.Bar dataKey="revenue" />
|
|
14
|
+
* <BarChart.XAxis />
|
|
15
|
+
* <BarChart.Tooltip />
|
|
16
|
+
* </BarChart>
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* ## What is different from a line
|
|
20
|
+
*
|
|
21
|
+
* **The baseline is zero, and not negotiable.** A line's job is to show change,
|
|
22
|
+
* so it may crop its axis to the range the data actually occupies. A bar's job
|
|
23
|
+
* is to compare *lengths*, and a bar cropped at the bottom is a length that
|
|
24
|
+
* lies — twice as tall no longer means twice as much. So the domain always
|
|
25
|
+
* reaches zero unless `yDomain` says otherwise, and saying otherwise is opting
|
|
26
|
+
* into a chart that misreads.
|
|
27
|
+
*
|
|
28
|
+
* **Bands, not points.** A line has a point at each x; a bar owns a slice of
|
|
29
|
+
* width around it. `barGap` is the fraction of that slice left empty, so bars
|
|
30
|
+
* stay proportional at any width instead of needing a pixel gap that is wrong
|
|
31
|
+
* on half of them.
|
|
32
|
+
*
|
|
33
|
+
* **Every series is one path.** A `Bar` draws all its rectangles as subpaths of
|
|
34
|
+
* a single animated path, split in two so the band under the finger keeps full
|
|
35
|
+
* ink while the rest fade. Fifty bars is two animated props a frame rather
|
|
36
|
+
* than fifty, and the corners are drawn as a path because a bar is rounded on
|
|
37
|
+
* the end it grows towards and square on the end it grows from — which `rx`
|
|
38
|
+
* cannot express, since it rounds all four corners or none.
|
|
39
|
+
*/
|
|
40
|
+
import { Children, createContext, forwardRef, isValidElement, useContext, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
|
|
41
|
+
import { StyleSheet, View } from 'react-native';
|
|
42
|
+
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
|
|
43
|
+
import Animated, { Easing, runOnJS, useAnimatedProps, useAnimatedStyle, useReducedMotion, useSharedValue, withTiming } from 'react-native-reanimated';
|
|
44
|
+
import Svg, { G, Line as SvgLine, Path } from 'react-native-svg';
|
|
45
|
+
import { useCSSVariable } from 'uniwind';
|
|
46
|
+
import { Text } from "../../primitives/text.js";
|
|
47
|
+
import { barPath, columnValues, compactNumber, useSeriesColor } from "../../utils/chart.js";
|
|
48
|
+
import { cn } from "../../utils/cn.js";
|
|
49
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
50
|
+
const AnimatedPath = Animated.createAnimatedComponent(Path);
|
|
51
|
+
|
|
52
|
+
/** Room left around the plot for the axis labels. */
|
|
53
|
+
const PADDING = {
|
|
54
|
+
top: 12,
|
|
55
|
+
right: 10,
|
|
56
|
+
bottom: 22,
|
|
57
|
+
left: 10
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Sideways, the category names run down the left instead of along the bottom,
|
|
62
|
+
* so the room has to come off that side rather than off the bottom. Reserved
|
|
63
|
+
* rather than overlaid: a name drawn on top of the bars is unreadable against
|
|
64
|
+
* them and makes the bars unreadable too.
|
|
65
|
+
*/
|
|
66
|
+
const PADDING_SIDEWAYS = {
|
|
67
|
+
top: 6,
|
|
68
|
+
right: 10,
|
|
69
|
+
bottom: 6,
|
|
70
|
+
left: 68
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/** Width the readout is laid out at, so it can be clamped inside the plot. */
|
|
74
|
+
const LABEL_WIDTH = 132;
|
|
75
|
+
|
|
76
|
+
/** Left gutter reserved when a `YAxis` is present, for its labels to sit in. */
|
|
77
|
+
const Y_AXIS_WIDTH = 40;
|
|
78
|
+
const BarChartContext = /*#__PURE__*/createContext(null);
|
|
79
|
+
function useChart(component) {
|
|
80
|
+
const context = useContext(BarChartContext);
|
|
81
|
+
if (!context) {
|
|
82
|
+
throw new Error(`${component} must be used within a <BarChart>`);
|
|
83
|
+
}
|
|
84
|
+
return context;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* The band under the finger, for something rendered *inside* the chart. A
|
|
89
|
+
* readout in the card's header is outside this provider — use
|
|
90
|
+
* `onActiveIndexChange` for that.
|
|
91
|
+
*/
|
|
92
|
+
export function useBarChart() {
|
|
93
|
+
const {
|
|
94
|
+
data,
|
|
95
|
+
activeIndexJS,
|
|
96
|
+
xDataKey
|
|
97
|
+
} = useChart('useBarChart');
|
|
98
|
+
return {
|
|
99
|
+
activeIndex: activeIndexJS,
|
|
100
|
+
activePoint: activeIndexJS >= 0 ? data[activeIndexJS] ?? null : null,
|
|
101
|
+
xDataKey
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Imperative handle: re-run the grow-in, for a "replay" control. */
|
|
106
|
+
|
|
107
|
+
function partition(children) {
|
|
108
|
+
const svg = [];
|
|
109
|
+
const overlay = [];
|
|
110
|
+
Children.forEach(children, (child, index) => {
|
|
111
|
+
if (! /*#__PURE__*/isValidElement(child)) return;
|
|
112
|
+
const layer = child.type.layer ?? 'svg';
|
|
113
|
+
const slot = /*#__PURE__*/_jsx(ChildSlot, {
|
|
114
|
+
children: child
|
|
115
|
+
}, index);
|
|
116
|
+
(layer === 'overlay' ? overlay : svg).push(slot);
|
|
117
|
+
});
|
|
118
|
+
return {
|
|
119
|
+
svg,
|
|
120
|
+
overlay
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function ChildSlot({
|
|
124
|
+
children
|
|
125
|
+
}) {
|
|
126
|
+
return /*#__PURE__*/_jsx(_Fragment, {
|
|
127
|
+
children: children
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
const BarChartRoot = /*#__PURE__*/forwardRef(function BarChartRoot({
|
|
131
|
+
className,
|
|
132
|
+
data,
|
|
133
|
+
xDataKey = 'name',
|
|
134
|
+
status = 'ready',
|
|
135
|
+
aspectRatio = 2,
|
|
136
|
+
animationDuration = 1100,
|
|
137
|
+
domainDuration = 500,
|
|
138
|
+
yDomain,
|
|
139
|
+
orientation = 'vertical',
|
|
140
|
+
stacked = false,
|
|
141
|
+
barGap = 0.2,
|
|
142
|
+
barWidth,
|
|
143
|
+
stackGap = 0,
|
|
144
|
+
cornerRadius = 4,
|
|
145
|
+
minBarLength = 0,
|
|
146
|
+
fadedOpacity = 0.3,
|
|
147
|
+
onActiveIndexChange,
|
|
148
|
+
compact = false,
|
|
149
|
+
children,
|
|
150
|
+
...props
|
|
151
|
+
}, ref) {
|
|
152
|
+
const [size, setSize] = useState({
|
|
153
|
+
width: 0,
|
|
154
|
+
height: 0
|
|
155
|
+
});
|
|
156
|
+
const [series, setSeries] = useState([]);
|
|
157
|
+
const [activeIndexJS, setActiveIndexJS] = useState(-1);
|
|
158
|
+
const reveal = useSharedValue(0);
|
|
159
|
+
const domainMin = useSharedValue(0);
|
|
160
|
+
const domainMax = useSharedValue(0);
|
|
161
|
+
const activeIndex = useSharedValue(-1);
|
|
162
|
+
const reducedMotion = useReducedMotion();
|
|
163
|
+
const registerSeries = useMemo(() => (key, color) => setSeries(current => {
|
|
164
|
+
const existing = current.find(([k]) => k === key);
|
|
165
|
+
if (existing?.[1] === color) return current;
|
|
166
|
+
return [...current.filter(([k]) => k !== key), [key, color]];
|
|
167
|
+
}), []);
|
|
168
|
+
const unregisterSeries = useMemo(() => key => setSeries(current => current.filter(([k]) => k !== key)), []);
|
|
169
|
+
|
|
170
|
+
/*
|
|
171
|
+
* Whether an axis is asking for room. It has to be known before the plot is
|
|
172
|
+
* laid out, and only the root sees the children early enough to ask — the
|
|
173
|
+
* axis itself renders into a box that has already been decided.
|
|
174
|
+
*/
|
|
175
|
+
const hasYAxis = useMemo(() => {
|
|
176
|
+
let found = false;
|
|
177
|
+
Children.forEach(children, child => {
|
|
178
|
+
if (/*#__PURE__*/isValidElement(child) && child.type.axis === 'y') {
|
|
179
|
+
found = true;
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
return found;
|
|
183
|
+
}, [children]);
|
|
184
|
+
const pad = compact ? {
|
|
185
|
+
top: 2,
|
|
186
|
+
right: 1,
|
|
187
|
+
bottom: 2,
|
|
188
|
+
left: 1
|
|
189
|
+
} : orientation === 'horizontal' ? PADDING_SIDEWAYS : {
|
|
190
|
+
...PADDING,
|
|
191
|
+
left: hasYAxis ? Y_AXIS_WIDTH : PADDING.left
|
|
192
|
+
};
|
|
193
|
+
const plot = {
|
|
194
|
+
left: pad.left,
|
|
195
|
+
top: pad.top,
|
|
196
|
+
width: Math.max(size.width - pad.left - pad.right, 0),
|
|
197
|
+
height: Math.max(size.height - pad.top - pad.bottom, 0)
|
|
198
|
+
};
|
|
199
|
+
const seriesKeys = series.map(([key]) => key).join('|');
|
|
200
|
+
const extent = useMemo(() => {
|
|
201
|
+
if (yDomain) return yDomain;
|
|
202
|
+
const keys = seriesKeys ? seriesKeys.split('|') : [];
|
|
203
|
+
let min = 0;
|
|
204
|
+
let max = 0;
|
|
205
|
+
for (const row of data) {
|
|
206
|
+
// Stacked, the tall thing is the total; grouped, it is the tallest bar.
|
|
207
|
+
// Reading the same number for both would crop a stack at its largest
|
|
208
|
+
// segment and clip everything above it.
|
|
209
|
+
let positive = 0;
|
|
210
|
+
let negative = 0;
|
|
211
|
+
for (const key of keys) {
|
|
212
|
+
const value = row[key];
|
|
213
|
+
if (typeof value !== 'number' || Number.isNaN(value)) continue;
|
|
214
|
+
if (stacked) {
|
|
215
|
+
if (value >= 0) positive += value;else negative += value;
|
|
216
|
+
} else {
|
|
217
|
+
if (value > positive) positive = value;
|
|
218
|
+
if (value < negative) negative = value;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (positive > max) max = positive;
|
|
222
|
+
if (negative < min) min = negative;
|
|
223
|
+
}
|
|
224
|
+
if (min === 0 && max === 0) return [0, 1];
|
|
225
|
+
// Headroom above the tallest bar only. The zero end is left exactly where
|
|
226
|
+
// it is: padding it would lift the bars off their own baseline.
|
|
227
|
+
return [min === 0 ? 0 : min * 1.1, max === 0 ? 0 : max * 1.1];
|
|
228
|
+
}, [data, yDomain, seriesKeys, stacked]);
|
|
229
|
+
const loading = status === 'loading';
|
|
230
|
+
useEffect(() => {
|
|
231
|
+
if (loading) return;
|
|
232
|
+
const [min, max] = extent;
|
|
233
|
+
const first = domainMin.value === 0 && domainMax.value === 0;
|
|
234
|
+
if (first || reducedMotion) {
|
|
235
|
+
domainMin.value = min;
|
|
236
|
+
domainMax.value = max;
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
domainMin.value = withTiming(min, {
|
|
240
|
+
duration: domainDuration
|
|
241
|
+
});
|
|
242
|
+
domainMax.value = withTiming(max, {
|
|
243
|
+
duration: domainDuration
|
|
244
|
+
});
|
|
245
|
+
}, [extent, loading, reducedMotion, domainDuration, domainMin, domainMax]);
|
|
246
|
+
const revealed = useRef(false);
|
|
247
|
+
const playReveal = useMemo(() => () => {
|
|
248
|
+
if (reducedMotion) {
|
|
249
|
+
reveal.value = 1;
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
reveal.value = 0;
|
|
253
|
+
reveal.value = withTiming(1, {
|
|
254
|
+
duration: animationDuration,
|
|
255
|
+
easing: Easing.bezier(0.85, 0, 0.15, 1)
|
|
256
|
+
});
|
|
257
|
+
}, [reducedMotion, animationDuration, reveal]);
|
|
258
|
+
useEffect(() => {
|
|
259
|
+
if (revealed.current || loading || plot.width <= 0 || !data.length) return;
|
|
260
|
+
revealed.current = true;
|
|
261
|
+
playReveal();
|
|
262
|
+
}, [loading, plot.width, data.length, playReveal]);
|
|
263
|
+
useImperativeHandle(ref, () => ({
|
|
264
|
+
replay: playReveal
|
|
265
|
+
}), [playReveal]);
|
|
266
|
+
const handleActiveIndex = useMemo(() => index => {
|
|
267
|
+
setActiveIndexJS(index);
|
|
268
|
+
onActiveIndexChange?.(index, index >= 0 ? data[index] ?? null : null);
|
|
269
|
+
}, [onActiveIndexChange, data]);
|
|
270
|
+
const onLayout = event => {
|
|
271
|
+
const {
|
|
272
|
+
width,
|
|
273
|
+
height
|
|
274
|
+
} = event.nativeEvent.layout;
|
|
275
|
+
setSize(current => Math.abs(current.width - width) < 1 && Math.abs(current.height - height) < 1 ? current : {
|
|
276
|
+
width,
|
|
277
|
+
height
|
|
278
|
+
});
|
|
279
|
+
props.onLayout?.(event);
|
|
280
|
+
};
|
|
281
|
+
const context = useMemo(() => ({
|
|
282
|
+
data,
|
|
283
|
+
xDataKey,
|
|
284
|
+
plot,
|
|
285
|
+
status,
|
|
286
|
+
orientation,
|
|
287
|
+
stacked,
|
|
288
|
+
barGap,
|
|
289
|
+
barWidth,
|
|
290
|
+
stackGap,
|
|
291
|
+
cornerRadius,
|
|
292
|
+
minBarLength,
|
|
293
|
+
fadedOpacity,
|
|
294
|
+
series,
|
|
295
|
+
registerSeries,
|
|
296
|
+
unregisterSeries,
|
|
297
|
+
domainMin,
|
|
298
|
+
domainMax,
|
|
299
|
+
extent,
|
|
300
|
+
reveal,
|
|
301
|
+
activeIndex,
|
|
302
|
+
activeIndexJS,
|
|
303
|
+
setActiveIndexJS: handleActiveIndex
|
|
304
|
+
}),
|
|
305
|
+
// `plot` is rebuilt every render from `size`, so it is compared by value.
|
|
306
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
307
|
+
[data, xDataKey, plot.width, plot.height, plot.left, plot.top, status, orientation, stacked, barGap, barWidth, stackGap, cornerRadius, minBarLength, fadedOpacity, series, registerSeries, unregisterSeries, domainMin, domainMax, extent, reveal, activeIndex, activeIndexJS, handleActiveIndex]);
|
|
308
|
+
const {
|
|
309
|
+
svg,
|
|
310
|
+
overlay
|
|
311
|
+
} = partition(children);
|
|
312
|
+
return /*#__PURE__*/_jsx(BarChartContext.Provider, {
|
|
313
|
+
value: context,
|
|
314
|
+
children: /*#__PURE__*/_jsx(View, {
|
|
315
|
+
...props,
|
|
316
|
+
onLayout: onLayout,
|
|
317
|
+
style: [{
|
|
318
|
+
aspectRatio
|
|
319
|
+
}, props.style],
|
|
320
|
+
className: cn('w-full', className),
|
|
321
|
+
children: plot.width > 0 ? /*#__PURE__*/_jsxs(_Fragment, {
|
|
322
|
+
children: [/*#__PURE__*/_jsx(Svg, {
|
|
323
|
+
width: "100%",
|
|
324
|
+
height: "100%",
|
|
325
|
+
style: StyleSheet.absoluteFill,
|
|
326
|
+
children: svg
|
|
327
|
+
}), overlay]
|
|
328
|
+
}) : null
|
|
329
|
+
})
|
|
330
|
+
});
|
|
331
|
+
});
|
|
332
|
+
BarChartRoot.displayName = 'BarChart';
|
|
333
|
+
|
|
334
|
+
/* -------------------------------------------------------------------------- */
|
|
335
|
+
/* SVG layer */
|
|
336
|
+
/* -------------------------------------------------------------------------- */
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Lines across the value axis, so a bar can be read against a number rather
|
|
340
|
+
* than only against the bar beside it.
|
|
341
|
+
*/
|
|
342
|
+
function BarChartGrid({
|
|
343
|
+
rows = 4,
|
|
344
|
+
color,
|
|
345
|
+
dashArray = '4,6',
|
|
346
|
+
opacity = 1
|
|
347
|
+
}) {
|
|
348
|
+
const {
|
|
349
|
+
plot,
|
|
350
|
+
orientation
|
|
351
|
+
} = useChart('BarChart.Grid');
|
|
352
|
+
const token = useCSSVariable('--color-border');
|
|
353
|
+
const stroke = color ?? (typeof token === 'string' ? token : 'rgba(0,0,0,0.1)');
|
|
354
|
+
const lines = Array.from({
|
|
355
|
+
length: rows + 1
|
|
356
|
+
}, (_unused, index) => index / rows);
|
|
357
|
+
return /*#__PURE__*/_jsx(G, {
|
|
358
|
+
opacity: opacity,
|
|
359
|
+
children: lines.map(fraction => orientation === 'vertical' ? /*#__PURE__*/_jsx(SvgLine, {
|
|
360
|
+
x1: plot.left,
|
|
361
|
+
x2: plot.left + plot.width,
|
|
362
|
+
y1: plot.top + plot.height * fraction,
|
|
363
|
+
y2: plot.top + plot.height * fraction,
|
|
364
|
+
stroke: stroke,
|
|
365
|
+
strokeDasharray: dashArray,
|
|
366
|
+
strokeWidth: 1
|
|
367
|
+
}, fraction) : /*#__PURE__*/_jsx(SvgLine, {
|
|
368
|
+
x1: plot.left + plot.width * fraction,
|
|
369
|
+
x2: plot.left + plot.width * fraction,
|
|
370
|
+
y1: plot.top,
|
|
371
|
+
y2: plot.top + plot.height,
|
|
372
|
+
stroke: stroke,
|
|
373
|
+
strokeDasharray: dashArray,
|
|
374
|
+
strokeWidth: 1
|
|
375
|
+
}, fraction))
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
BarChartGrid.displayName = 'BarChart.Grid';
|
|
379
|
+
BarChartGrid.layer = 'svg';
|
|
380
|
+
/**
|
|
381
|
+
* One series of bars.
|
|
382
|
+
*
|
|
383
|
+
* Drawn as two paths rather than one rectangle per band: the band under the
|
|
384
|
+
* finger, and everything else. That is the fewest animated props that can
|
|
385
|
+
* still dim the rest — one path could not, since a path has one opacity, and
|
|
386
|
+
* a view per bar would be one animated prop per bar for the same picture.
|
|
387
|
+
*/
|
|
388
|
+
function BarChartBar({
|
|
389
|
+
dataKey,
|
|
390
|
+
color,
|
|
391
|
+
colorIndex = 1,
|
|
392
|
+
cornerRadius
|
|
393
|
+
}) {
|
|
394
|
+
const {
|
|
395
|
+
data,
|
|
396
|
+
plot,
|
|
397
|
+
status,
|
|
398
|
+
orientation,
|
|
399
|
+
stacked,
|
|
400
|
+
barGap,
|
|
401
|
+
barWidth,
|
|
402
|
+
stackGap,
|
|
403
|
+
cornerRadius: chartRadius,
|
|
404
|
+
minBarLength,
|
|
405
|
+
fadedOpacity,
|
|
406
|
+
series,
|
|
407
|
+
registerSeries,
|
|
408
|
+
unregisterSeries,
|
|
409
|
+
domainMin,
|
|
410
|
+
domainMax,
|
|
411
|
+
reveal,
|
|
412
|
+
activeIndex
|
|
413
|
+
} = useChart('BarChart.Bar');
|
|
414
|
+
const fill = useSeriesColor(color, colorIndex);
|
|
415
|
+
const radius = cornerRadius ?? chartRadius;
|
|
416
|
+
useEffect(() => {
|
|
417
|
+
registerSeries(dataKey, fill);
|
|
418
|
+
return () => unregisterSeries(dataKey);
|
|
419
|
+
}, [dataKey, fill, registerSeries, unregisterSeries]);
|
|
420
|
+
const values = useMemo(() => columnValues(data, dataKey), [data, dataKey]);
|
|
421
|
+
|
|
422
|
+
/*
|
|
423
|
+
* What each bar in this series sits on. Grouped, that is nothing — every bar
|
|
424
|
+
* starts at zero. Stacked, it is the running total of the series registered
|
|
425
|
+
* *before* this one, which is why registration order is the stacking order.
|
|
426
|
+
*/
|
|
427
|
+
const baselines = useMemo(() => {
|
|
428
|
+
if (!stacked) return null;
|
|
429
|
+
const below = series.slice(0, series.findIndex(([key]) => key === dataKey));
|
|
430
|
+
if (!below.length) return null;
|
|
431
|
+
return data.map(row => {
|
|
432
|
+
let total = 0;
|
|
433
|
+
for (const [key] of below) {
|
|
434
|
+
const value = row[key];
|
|
435
|
+
if (typeof value === 'number' && !Number.isNaN(value)) total += value;
|
|
436
|
+
}
|
|
437
|
+
return total;
|
|
438
|
+
});
|
|
439
|
+
}, [stacked, series, dataKey, data]);
|
|
440
|
+
const seriesIndex = Math.max(0, series.findIndex(([key]) => key === dataKey));
|
|
441
|
+
const seriesCount = Math.max(1, series.length);
|
|
442
|
+
const loading = status === 'loading';
|
|
443
|
+
const total = data.length;
|
|
444
|
+
const horizontal = orientation === 'horizontal';
|
|
445
|
+
|
|
446
|
+
/*
|
|
447
|
+
* Both paths come out of one builder, filtered by whether the band is the
|
|
448
|
+
* active one. Two passes over the data a frame is still cheaper than the
|
|
449
|
+
* bookkeeping needed to build both at once, and it keeps the geometry in
|
|
450
|
+
* exactly one place.
|
|
451
|
+
*/
|
|
452
|
+
const build = wantActive => () => {
|
|
453
|
+
'worklet';
|
|
454
|
+
|
|
455
|
+
if (!total || plot.width <= 0) {
|
|
456
|
+
return {
|
|
457
|
+
d: '',
|
|
458
|
+
opacity: 1
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// The category axis runs across the plot when the bars grow up, and down
|
|
463
|
+
// it when they grow right. `along` is that axis; `across` is the value one.
|
|
464
|
+
const along = horizontal ? plot.height : plot.width;
|
|
465
|
+
const across = horizontal ? plot.width : plot.height;
|
|
466
|
+
const alongStart = horizontal ? plot.top : plot.left;
|
|
467
|
+
const acrossStart = horizontal ? plot.left : plot.top;
|
|
468
|
+
const band = along / total;
|
|
469
|
+
// The slot a single bar gets: the band, less the gap, divided between the
|
|
470
|
+
// series when they stand side by side.
|
|
471
|
+
const usable = band * (1 - barGap);
|
|
472
|
+
const slot = stacked ? usable : usable / seriesCount;
|
|
473
|
+
const thickness = Math.min(barWidth ?? slot, slot);
|
|
474
|
+
const min = domainMin.value;
|
|
475
|
+
const max = domainMax.value;
|
|
476
|
+
const range = max - min || 1;
|
|
477
|
+
const grow = reveal.value;
|
|
478
|
+
const active = activeIndex.value;
|
|
479
|
+
|
|
480
|
+
/*
|
|
481
|
+
* Where a value sits along the value axis. Vertical counts down from the
|
|
482
|
+
* top, horizontal counts up from the left — the same scale read in
|
|
483
|
+
* opposite directions, which is the only thing orientation changes.
|
|
484
|
+
*/
|
|
485
|
+
const project = value => {
|
|
486
|
+
'worklet';
|
|
487
|
+
|
|
488
|
+
const fraction = (value - min) / range;
|
|
489
|
+
return horizontal ? acrossStart + fraction * across : acrossStart + across - fraction * across;
|
|
490
|
+
};
|
|
491
|
+
let d = '';
|
|
492
|
+
for (let i = 0; i < total; i++) {
|
|
493
|
+
if (i === active !== wantActive) continue;
|
|
494
|
+
const value = values[i];
|
|
495
|
+
if (value === null || value === undefined) continue;
|
|
496
|
+
const base = baselines?.[i] ?? 0;
|
|
497
|
+
/*
|
|
498
|
+
* Staggered by band, but every bar still finishes inside the one
|
|
499
|
+
* duration: the window each gets is what is left after the stagger, so
|
|
500
|
+
* a chart of fifty bands does not take fifty times as long to arrive.
|
|
501
|
+
*/
|
|
502
|
+
const start = total > 1 ? i / total * 0.45 : 0;
|
|
503
|
+
const eased = Math.max(0, Math.min(1, (grow - start) / 0.55));
|
|
504
|
+
const shown = loading ? 0 : value * eased;
|
|
505
|
+
const zero = project(base);
|
|
506
|
+
const tip = project(base + shown);
|
|
507
|
+
const shrink = stacked && stackGap > 0 ? stackGap : 0;
|
|
508
|
+
let length = Math.abs(tip - zero) - shrink;
|
|
509
|
+
// A value that rounds to nothing still happened, and a bar of zero
|
|
510
|
+
// length says it did not.
|
|
511
|
+
if (minBarLength > 0 && shown !== 0 && length < minBarLength) {
|
|
512
|
+
length = minBarLength;
|
|
513
|
+
}
|
|
514
|
+
if (length <= 0) continue;
|
|
515
|
+
const offset = (stacked ? 0 : seriesIndex * slot) + (band - usable) / 2 + (slot - thickness) / 2;
|
|
516
|
+
const lead = alongStart + i * band + offset;
|
|
517
|
+
const positive = shown >= 0;
|
|
518
|
+
d += horizontal ? barPath(positive ? zero : zero - length, lead, length, thickness, radius, positive ? 'right' : 'left') : barPath(lead, positive ? zero - length : zero, thickness, length, radius, positive ? 'up' : 'down');
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// Dimming only happens while something *is* active; with nothing under the
|
|
522
|
+
// finger every bar is at full ink, which is the resting state.
|
|
523
|
+
const dim = !wantActive && active >= 0 ? fadedOpacity : 1;
|
|
524
|
+
return {
|
|
525
|
+
d,
|
|
526
|
+
opacity: dim
|
|
527
|
+
};
|
|
528
|
+
};
|
|
529
|
+
const restProps = useAnimatedProps(build(false));
|
|
530
|
+
const activeProps = useAnimatedProps(build(true));
|
|
531
|
+
return /*#__PURE__*/_jsxs(G, {
|
|
532
|
+
children: [/*#__PURE__*/_jsx(AnimatedPath, {
|
|
533
|
+
animatedProps: restProps,
|
|
534
|
+
fill: fill
|
|
535
|
+
}), /*#__PURE__*/_jsx(AnimatedPath, {
|
|
536
|
+
animatedProps: activeProps,
|
|
537
|
+
fill: fill
|
|
538
|
+
})]
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
BarChartBar.displayName = 'BarChart.Bar';
|
|
542
|
+
BarChartBar.layer = 'svg';
|
|
543
|
+
|
|
544
|
+
/* -------------------------------------------------------------------------- */
|
|
545
|
+
/* Overlay layer */
|
|
546
|
+
/* -------------------------------------------------------------------------- */
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* The category labels, one under each band it has room for. Real text rather
|
|
550
|
+
* than SVG text, so they follow the theme's font and the platform's text
|
|
551
|
+
* scaling — SVG text does neither.
|
|
552
|
+
*/
|
|
553
|
+
function BarChartXAxis({
|
|
554
|
+
ticks = 6,
|
|
555
|
+
format,
|
|
556
|
+
className
|
|
557
|
+
}) {
|
|
558
|
+
const {
|
|
559
|
+
data,
|
|
560
|
+
xDataKey,
|
|
561
|
+
plot,
|
|
562
|
+
orientation
|
|
563
|
+
} = useChart('BarChart.XAxis');
|
|
564
|
+
const labels = useMemo(() => {
|
|
565
|
+
if (!data.length) return [];
|
|
566
|
+
const count = Math.min(ticks, data.length);
|
|
567
|
+
const step = count > 1 ? (data.length - 1) / (count - 1) : 0;
|
|
568
|
+
return Array.from({
|
|
569
|
+
length: count
|
|
570
|
+
}, (_unused, index) => {
|
|
571
|
+
const dataIndex = Math.round(index * step);
|
|
572
|
+
const datum = data[dataIndex];
|
|
573
|
+
if (!datum) return null;
|
|
574
|
+
return {
|
|
575
|
+
key: dataIndex,
|
|
576
|
+
text: format ? format(datum, dataIndex) : String(datum[xDataKey] ?? '')
|
|
577
|
+
};
|
|
578
|
+
}).filter(label => label !== null);
|
|
579
|
+
}, [data, ticks, format, xDataKey]);
|
|
580
|
+
if (orientation === 'horizontal') return null;
|
|
581
|
+
return /*#__PURE__*/_jsx(View, {
|
|
582
|
+
pointerEvents: "none",
|
|
583
|
+
style: {
|
|
584
|
+
position: 'absolute',
|
|
585
|
+
left: plot.left,
|
|
586
|
+
right: PADDING.right,
|
|
587
|
+
bottom: 0,
|
|
588
|
+
height: PADDING.bottom
|
|
589
|
+
},
|
|
590
|
+
className: cn('flex-row items-center justify-between', className),
|
|
591
|
+
children: labels.map(label => /*#__PURE__*/_jsx(Text, {
|
|
592
|
+
size: "xs",
|
|
593
|
+
muted: true,
|
|
594
|
+
numberOfLines: 1,
|
|
595
|
+
children: label.text
|
|
596
|
+
}, label.key))
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
BarChartXAxis.displayName = 'BarChart.XAxis';
|
|
600
|
+
BarChartXAxis.layer = 'overlay';
|
|
601
|
+
/** Value labels down the side, aligned to the grid lines. */
|
|
602
|
+
function BarChartYAxis({
|
|
603
|
+
ticks = 4,
|
|
604
|
+
format,
|
|
605
|
+
className
|
|
606
|
+
}) {
|
|
607
|
+
const {
|
|
608
|
+
plot,
|
|
609
|
+
data,
|
|
610
|
+
xDataKey,
|
|
611
|
+
orientation,
|
|
612
|
+
extent
|
|
613
|
+
} = useChart('BarChart.YAxis');
|
|
614
|
+
|
|
615
|
+
/*
|
|
616
|
+
* Read off the settled domain rather than the shared values the paths use.
|
|
617
|
+
* A label is text, and text is JS — following the tween would re-render on
|
|
618
|
+
* every frame of it to redraw a number nobody can read while it moves.
|
|
619
|
+
*/
|
|
620
|
+
const horizontal = orientation === 'horizontal';
|
|
621
|
+
const labels = useMemo(() => {
|
|
622
|
+
if (horizontal) {
|
|
623
|
+
// Sideways, the side of the chart is the category axis.
|
|
624
|
+
return data.map((row, index) => ({
|
|
625
|
+
key: index,
|
|
626
|
+
text: String(row[xDataKey] ?? '')
|
|
627
|
+
}));
|
|
628
|
+
}
|
|
629
|
+
const [min, max] = extent;
|
|
630
|
+
if (min === 0 && max === 0) return [];
|
|
631
|
+
return Array.from({
|
|
632
|
+
length: ticks + 1
|
|
633
|
+
}, (_unused, index) => {
|
|
634
|
+
const value = max - (max - min) * index / ticks;
|
|
635
|
+
return {
|
|
636
|
+
key: index,
|
|
637
|
+
text: format ? format(value) : compactNumber(value)
|
|
638
|
+
};
|
|
639
|
+
});
|
|
640
|
+
}, [extent, ticks, format, horizontal, data, xDataKey]);
|
|
641
|
+
|
|
642
|
+
/*
|
|
643
|
+
* Sideways the labels sit in the gutter the plot already left for them, one
|
|
644
|
+
* band each — `flex-1` per row rather than spacing them edge to edge, so
|
|
645
|
+
* every name lands beside its own bar instead of only the first and last
|
|
646
|
+
* doing so. Upright there are no bands to line up with, so the ticks space
|
|
647
|
+
* themselves against the grid.
|
|
648
|
+
*/
|
|
649
|
+
return /*#__PURE__*/_jsx(View, {
|
|
650
|
+
pointerEvents: "none",
|
|
651
|
+
style: {
|
|
652
|
+
position: 'absolute',
|
|
653
|
+
left: 0,
|
|
654
|
+
top: plot.top,
|
|
655
|
+
height: plot.height,
|
|
656
|
+
width: horizontal ? Math.max(plot.left - 8, 0) : undefined
|
|
657
|
+
},
|
|
658
|
+
className: cn(horizontal ? 'items-end' : 'justify-between', className),
|
|
659
|
+
children: labels.map(label => /*#__PURE__*/_jsx(View, {
|
|
660
|
+
className: horizontal ? 'flex-1 justify-center' : undefined,
|
|
661
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
662
|
+
size: "xs",
|
|
663
|
+
muted: true,
|
|
664
|
+
numberOfLines: 1,
|
|
665
|
+
children: label.text
|
|
666
|
+
})
|
|
667
|
+
}, label.key))
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
BarChartYAxis.displayName = 'BarChart.YAxis';
|
|
671
|
+
BarChartYAxis.layer = 'overlay';
|
|
672
|
+
// Read by the root, which has to leave room for the labels before it
|
|
673
|
+
// lays the plot out — an axis drawn over the plot is unreadable, and
|
|
674
|
+
// makes what it is drawn over unreadable too.
|
|
675
|
+
BarChartYAxis.axis = 'y';
|
|
676
|
+
/**
|
|
677
|
+
* The readout, and the gesture that drives it.
|
|
678
|
+
*
|
|
679
|
+
* There is no crosshair. A line needs one because a point on a line has no
|
|
680
|
+
* width of its own to point at; a bar is already the thing being pointed at,
|
|
681
|
+
* so highlighting it and dimming the rest says the same thing without drawing
|
|
682
|
+
* a line through the chart.
|
|
683
|
+
*
|
|
684
|
+
* The hit area is the whole plot. A readout you have to land on the bar to
|
|
685
|
+
* summon is a readout nobody finds — and the thinner the bars, the truer that
|
|
686
|
+
* gets.
|
|
687
|
+
*/
|
|
688
|
+
function BarChartTooltip({
|
|
689
|
+
formatValue,
|
|
690
|
+
formatX,
|
|
691
|
+
className
|
|
692
|
+
}) {
|
|
693
|
+
const {
|
|
694
|
+
data,
|
|
695
|
+
xDataKey,
|
|
696
|
+
plot,
|
|
697
|
+
series,
|
|
698
|
+
orientation,
|
|
699
|
+
activeIndex,
|
|
700
|
+
activeIndexJS,
|
|
701
|
+
setActiveIndexJS,
|
|
702
|
+
status
|
|
703
|
+
} = useChart('BarChart.Tooltip');
|
|
704
|
+
const total = data.length;
|
|
705
|
+
const horizontal = orientation === 'horizontal';
|
|
706
|
+
const left = plot.left;
|
|
707
|
+
const top = plot.top;
|
|
708
|
+
const width = plot.width;
|
|
709
|
+
const height = plot.height;
|
|
710
|
+
|
|
711
|
+
/*
|
|
712
|
+
* Declared inside the memo, next to its callers: a worklet may only call
|
|
713
|
+
* another worklet, and the rule is enforced by crashing rather than warning.
|
|
714
|
+
*/
|
|
715
|
+
const pan = useMemo(() => {
|
|
716
|
+
const resolve = (x, y) => {
|
|
717
|
+
'worklet';
|
|
718
|
+
|
|
719
|
+
if (!total) return;
|
|
720
|
+
const span = horizontal ? height : width;
|
|
721
|
+
const offset = (horizontal ? y - top : x - left) / (span || 1);
|
|
722
|
+
// Bands, not points: the finger is inside whichever slice it lands on,
|
|
723
|
+
// which is a floor rather than a round to the nearest centre.
|
|
724
|
+
const next = Math.max(0, Math.min(total - 1, Math.floor(offset * total)));
|
|
725
|
+
if (next === activeIndex.value) return;
|
|
726
|
+
activeIndex.value = next;
|
|
727
|
+
runOnJS(setActiveIndexJS)(next);
|
|
728
|
+
};
|
|
729
|
+
return Gesture.Pan().minDistance(0).onBegin(event => {
|
|
730
|
+
'worklet';
|
|
731
|
+
|
|
732
|
+
resolve(event.x, event.y);
|
|
733
|
+
}).onUpdate(event => {
|
|
734
|
+
'worklet';
|
|
735
|
+
|
|
736
|
+
resolve(event.x, event.y);
|
|
737
|
+
}).onFinalize(() => {
|
|
738
|
+
'worklet';
|
|
739
|
+
|
|
740
|
+
activeIndex.value = -1;
|
|
741
|
+
runOnJS(setActiveIndexJS)(-1);
|
|
742
|
+
});
|
|
743
|
+
}, [total, left, top, width, height, horizontal, activeIndex, setActiveIndexJS]);
|
|
744
|
+
|
|
745
|
+
// The readout centres over its band and is clamped inside the plot, so it
|
|
746
|
+
// never runs off the edge at the first or last one.
|
|
747
|
+
const labelStyle = useAnimatedStyle(() => {
|
|
748
|
+
const index = activeIndex.value;
|
|
749
|
+
if (index < 0 || !total) return {
|
|
750
|
+
opacity: 0
|
|
751
|
+
};
|
|
752
|
+
const band = (horizontal ? plot.height : plot.width) / total;
|
|
753
|
+
const centre = (horizontal ? plot.top : plot.left) + band * (index + 0.5);
|
|
754
|
+
const half = LABEL_WIDTH / 2;
|
|
755
|
+
const clamped = Math.min(plot.left + plot.width - half, Math.max(plot.left + half, horizontal ? plot.left + plot.width / 2 : centre));
|
|
756
|
+
return {
|
|
757
|
+
opacity: 1,
|
|
758
|
+
transform: [{
|
|
759
|
+
translateX: clamped - half
|
|
760
|
+
}]
|
|
761
|
+
};
|
|
762
|
+
});
|
|
763
|
+
const active = activeIndexJS >= 0 ? data[activeIndexJS] : null;
|
|
764
|
+
const fmtValue = formatValue ?? (value => compactNumber(value));
|
|
765
|
+
const fmtX = formatX ?? (datum => String(datum[xDataKey] ?? ''));
|
|
766
|
+
if (status === 'loading') return null;
|
|
767
|
+
return /*#__PURE__*/_jsx(GestureDetector, {
|
|
768
|
+
gesture: pan,
|
|
769
|
+
children: /*#__PURE__*/_jsx(View, {
|
|
770
|
+
style: StyleSheet.absoluteFill,
|
|
771
|
+
children: /*#__PURE__*/_jsx(Animated.View, {
|
|
772
|
+
pointerEvents: "none",
|
|
773
|
+
style: [{
|
|
774
|
+
position: 'absolute',
|
|
775
|
+
left: 0,
|
|
776
|
+
top: 0,
|
|
777
|
+
width: LABEL_WIDTH
|
|
778
|
+
}, labelStyle],
|
|
779
|
+
children: active ? /*#__PURE__*/_jsxs(View, {
|
|
780
|
+
className: cn('rounded-xl border border-border bg-popover px-2.5 py-1.5 shadow-lg', className),
|
|
781
|
+
children: [/*#__PURE__*/_jsx(Text, {
|
|
782
|
+
size: "xs",
|
|
783
|
+
muted: true,
|
|
784
|
+
numberOfLines: 1,
|
|
785
|
+
children: fmtX(active)
|
|
786
|
+
}), series.map(([key, color]) => {
|
|
787
|
+
const value = active[key];
|
|
788
|
+
if (typeof value !== 'number') return null;
|
|
789
|
+
return /*#__PURE__*/_jsxs(View, {
|
|
790
|
+
className: "flex-row items-center gap-1.5",
|
|
791
|
+
children: [/*#__PURE__*/_jsx(View, {
|
|
792
|
+
style: {
|
|
793
|
+
width: 6,
|
|
794
|
+
height: 6,
|
|
795
|
+
borderRadius: 3,
|
|
796
|
+
backgroundColor: color
|
|
797
|
+
}
|
|
798
|
+
}), /*#__PURE__*/_jsx(Text, {
|
|
799
|
+
size: "xs",
|
|
800
|
+
weight: "medium",
|
|
801
|
+
children: fmtValue(value, key)
|
|
802
|
+
})]
|
|
803
|
+
}, key);
|
|
804
|
+
})]
|
|
805
|
+
}) : null
|
|
806
|
+
})
|
|
807
|
+
})
|
|
808
|
+
});
|
|
809
|
+
}
|
|
810
|
+
BarChartTooltip.displayName = 'BarChart.Tooltip';
|
|
811
|
+
BarChartTooltip.layer = 'overlay';
|
|
812
|
+
/** A swatch and a name per series, in the order the series were declared. */
|
|
813
|
+
function BarChartLegend({
|
|
814
|
+
className,
|
|
815
|
+
labels,
|
|
816
|
+
...props
|
|
817
|
+
}) {
|
|
818
|
+
const {
|
|
819
|
+
series
|
|
820
|
+
} = useChart('BarChart.Legend');
|
|
821
|
+
if (!series.length) return null;
|
|
822
|
+
return /*#__PURE__*/_jsx(View, {
|
|
823
|
+
...props,
|
|
824
|
+
pointerEvents: "none",
|
|
825
|
+
className: cn('absolute right-2 top-1 flex-row gap-3', className),
|
|
826
|
+
children: series.map(([key, color]) => /*#__PURE__*/_jsxs(View, {
|
|
827
|
+
className: "flex-row items-center gap-1.5",
|
|
828
|
+
children: [/*#__PURE__*/_jsx(View, {
|
|
829
|
+
style: {
|
|
830
|
+
width: 8,
|
|
831
|
+
height: 8,
|
|
832
|
+
borderRadius: 2,
|
|
833
|
+
backgroundColor: color
|
|
834
|
+
}
|
|
835
|
+
}), /*#__PURE__*/_jsx(Text, {
|
|
836
|
+
size: "xs",
|
|
837
|
+
muted: true,
|
|
838
|
+
children: labels?.[key] ?? key
|
|
839
|
+
})]
|
|
840
|
+
}, key))
|
|
841
|
+
});
|
|
842
|
+
}
|
|
843
|
+
BarChartLegend.displayName = 'BarChart.Legend';
|
|
844
|
+
BarChartLegend.layer = 'overlay';
|
|
845
|
+
export const BarChart = Object.assign(BarChartRoot, {
|
|
846
|
+
Grid: BarChartGrid,
|
|
847
|
+
Bar: BarChartBar,
|
|
848
|
+
XAxis: BarChartXAxis,
|
|
849
|
+
YAxis: BarChartYAxis,
|
|
850
|
+
Tooltip: BarChartTooltip,
|
|
851
|
+
Legend: BarChartLegend
|
|
852
|
+
});
|
|
853
|
+
//# sourceMappingURL=index.js.map
|