@cdx-ui/components 0.3.4 → 0.3.6
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 -1
- package/coverage.svg +1 -1
- package/docs/components/bar-chart/content.md +703 -0
- package/docs/components/content.md +9 -1
- package/docs/components/line-chart/content.md +567 -0
- package/docs/components/pie-chart/content.md +493 -0
- package/docs/components/progress-segmented/content.md +1 -1
- package/docs/components/select/content.md +144 -2
- package/docs/components/stepper/content.md +599 -0
- package/docs/components/system-alert/content.md +533 -0
- package/docs/foundations/expressions/icons/content.md +1 -1
- package/docs/foundations/styles/tokens/all-tokens/content.md +2 -1
- package/docs/llms.txt +10 -0
- package/docs/patterns/content.md +1 -0
- package/docs/patterns/dashboard/content.md +423 -19
- package/docs/patterns/insights/content.md +590 -0
- package/docs/resources/release-notes/0.3.4/content.md +27 -0
- package/docs/resources/release-notes/0.3.5/content.md +7 -0
- package/docs/resources/release-notes/content.md +2 -0
- package/lib/commonjs/components/BarChart/geometry.js +1130 -0
- package/lib/commonjs/components/BarChart/geometry.js.map +1 -0
- package/lib/commonjs/components/BarChart/index.js +942 -0
- package/lib/commonjs/components/BarChart/index.js.map +1 -0
- package/lib/commonjs/components/BarChart/styles.js +26 -0
- package/lib/commonjs/components/BarChart/styles.js.map +1 -0
- package/lib/commonjs/components/Button/index.js +2 -2
- package/lib/commonjs/components/Button/index.js.map +1 -1
- package/lib/commonjs/components/Chip/styles.js +3 -1
- package/lib/commonjs/components/Chip/styles.js.map +1 -1
- package/lib/commonjs/components/LineChart/geometry.js +698 -0
- package/lib/commonjs/components/LineChart/geometry.js.map +1 -0
- package/lib/commonjs/components/LineChart/index.js +747 -0
- package/lib/commonjs/components/LineChart/index.js.map +1 -0
- package/lib/commonjs/components/LineChart/styles.js +25 -0
- package/lib/commonjs/components/LineChart/styles.js.map +1 -0
- package/lib/commonjs/components/PieChart/geometry.js +216 -0
- package/lib/commonjs/components/PieChart/geometry.js.map +1 -0
- package/lib/commonjs/components/PieChart/index.js +476 -0
- package/lib/commonjs/components/PieChart/index.js.map +1 -0
- package/lib/commonjs/components/PieChart/styles.js +88 -0
- package/lib/commonjs/components/PieChart/styles.js.map +1 -0
- package/lib/commonjs/components/ProgressCircle/ProgressCircleRing.js +9 -31
- package/lib/commonjs/components/ProgressCircle/ProgressCircleRing.js.map +1 -1
- package/lib/commonjs/components/ProgressCircle/ProgressRing.js +52 -0
- package/lib/commonjs/components/ProgressCircle/ProgressRing.js.map +1 -0
- package/lib/commonjs/components/ScrollShadow/useScrollShadow.js +8 -7
- package/lib/commonjs/components/ScrollShadow/useScrollShadow.js.map +1 -1
- package/lib/commonjs/components/Stepper/index.js +369 -0
- package/lib/commonjs/components/Stepper/index.js.map +1 -0
- package/lib/commonjs/components/Stepper/styles.js +211 -0
- package/lib/commonjs/components/Stepper/styles.js.map +1 -0
- package/lib/commonjs/components/Stepper/useStepperLabelFit.js +22 -0
- package/lib/commonjs/components/Stepper/useStepperLabelFit.js.map +1 -0
- package/lib/commonjs/components/Stepper/useStepperLabelFit.web.js +52 -0
- package/lib/commonjs/components/Stepper/useStepperLabelFit.web.js.map +1 -0
- package/lib/commonjs/components/Stepper/useStepperRingColors.js +31 -0
- package/lib/commonjs/components/Stepper/useStepperRingColors.js.map +1 -0
- package/lib/commonjs/components/SystemAlert/animation.js +218 -0
- package/lib/commonjs/components/SystemAlert/animation.js.map +1 -0
- package/lib/commonjs/components/SystemAlert/index.js +525 -0
- package/lib/commonjs/components/SystemAlert/index.js.map +1 -0
- package/lib/commonjs/components/SystemAlert/styles.js +178 -0
- package/lib/commonjs/components/SystemAlert/styles.js.map +1 -0
- package/lib/commonjs/components/SystemAlert/useSystemAlertStatusBarStyle.js +53 -0
- package/lib/commonjs/components/SystemAlert/useSystemAlertStatusBarStyle.js.map +1 -0
- package/lib/commonjs/components/Tabs/index.js +4 -0
- package/lib/commonjs/components/Tabs/index.js.map +1 -1
- package/lib/commonjs/components/Tabs/styles.js +1 -1
- package/lib/commonjs/components/Tabs/styles.js.map +1 -1
- package/lib/commonjs/components/chart/ChartInspector.js +610 -0
- package/lib/commonjs/components/chart/ChartInspector.js.map +1 -0
- package/lib/commonjs/components/chart/format.js +93 -0
- package/lib/commonjs/components/chart/format.js.map +1 -0
- package/lib/commonjs/components/chart/index.js +2 -0
- package/lib/commonjs/components/chart/index.js.map +1 -0
- package/lib/commonjs/components/chart/motion.js +97 -0
- package/lib/commonjs/components/chart/motion.js.map +1 -0
- package/lib/commonjs/components/chart/palette.js +178 -0
- package/lib/commonjs/components/chart/palette.js.map +1 -0
- package/lib/commonjs/components/chart/size.js +34 -0
- package/lib/commonjs/components/chart/size.js.map +1 -0
- package/lib/commonjs/components/chart/ticks.js +59 -0
- package/lib/commonjs/components/chart/ticks.js.map +1 -0
- package/lib/commonjs/components/chart/types.js +6 -0
- package/lib/commonjs/components/chart/types.js.map +1 -0
- package/lib/commonjs/components/chart/useChartHostWidth.js +27 -0
- package/lib/commonjs/components/chart/useChartHostWidth.js.map +1 -0
- package/lib/commonjs/components/index.js +60 -0
- package/lib/commonjs/components/index.js.map +1 -1
- package/lib/commonjs/figma/SystemAlert.Button.figma.js +28 -0
- package/lib/commonjs/figma/SystemAlert.Button.figma.js.map +1 -0
- package/lib/commonjs/figma/SystemAlert.Close.figma.js +22 -0
- package/lib/commonjs/figma/SystemAlert.Close.figma.js.map +1 -0
- package/lib/commonjs/figma/SystemAlert.Description.figma.js +20 -0
- package/lib/commonjs/figma/SystemAlert.Description.figma.js.map +1 -0
- package/lib/commonjs/figma/SystemAlert.IconButton.figma.js +31 -0
- package/lib/commonjs/figma/SystemAlert.IconButton.figma.js.map +1 -0
- package/lib/commonjs/figma/SystemAlert.figma.js +48 -0
- package/lib/commonjs/figma/SystemAlert.figma.js.map +1 -0
- package/lib/module/components/BarChart/geometry.js +1110 -0
- package/lib/module/components/BarChart/geometry.js.map +1 -0
- package/lib/module/components/BarChart/index.js +936 -0
- package/lib/module/components/BarChart/index.js.map +1 -0
- package/lib/module/components/BarChart/styles.js +22 -0
- package/lib/module/components/BarChart/styles.js.map +1 -0
- package/lib/module/components/Button/index.js +2 -2
- package/lib/module/components/Button/index.js.map +1 -1
- package/lib/module/components/Chip/styles.js +3 -1
- package/lib/module/components/Chip/styles.js.map +1 -1
- package/lib/module/components/LineChart/geometry.js +685 -0
- package/lib/module/components/LineChart/geometry.js.map +1 -0
- package/lib/module/components/LineChart/index.js +741 -0
- package/lib/module/components/LineChart/index.js.map +1 -0
- package/lib/module/components/LineChart/styles.js +21 -0
- package/lib/module/components/LineChart/styles.js.map +1 -0
- package/lib/module/components/PieChart/geometry.js +209 -0
- package/lib/module/components/PieChart/geometry.js.map +1 -0
- package/lib/module/components/PieChart/index.js +471 -0
- package/lib/module/components/PieChart/index.js.map +1 -0
- package/lib/module/components/PieChart/styles.js +84 -0
- package/lib/module/components/PieChart/styles.js.map +1 -0
- package/lib/module/components/ProgressCircle/ProgressCircleRing.js +10 -32
- package/lib/module/components/ProgressCircle/ProgressCircleRing.js.map +1 -1
- package/lib/module/components/ProgressCircle/ProgressRing.js +48 -0
- package/lib/module/components/ProgressCircle/ProgressRing.js.map +1 -0
- package/lib/module/components/ScrollShadow/useScrollShadow.js +8 -7
- package/lib/module/components/ScrollShadow/useScrollShadow.js.map +1 -1
- package/lib/module/components/Stepper/index.js +360 -0
- package/lib/module/components/Stepper/index.js.map +1 -0
- package/lib/module/components/Stepper/styles.js +206 -0
- package/lib/module/components/Stepper/styles.js.map +1 -0
- package/lib/module/components/Stepper/useStepperLabelFit.js +18 -0
- package/lib/module/components/Stepper/useStepperLabelFit.js.map +1 -0
- package/lib/module/components/Stepper/useStepperLabelFit.web.js +48 -0
- package/lib/module/components/Stepper/useStepperLabelFit.web.js.map +1 -0
- package/lib/module/components/Stepper/useStepperRingColors.js +28 -0
- package/lib/module/components/Stepper/useStepperRingColors.js.map +1 -0
- package/lib/module/components/SystemAlert/animation.js +213 -0
- package/lib/module/components/SystemAlert/animation.js.map +1 -0
- package/lib/module/components/SystemAlert/index.js +515 -0
- package/lib/module/components/SystemAlert/index.js.map +1 -0
- package/lib/module/components/SystemAlert/styles.js +174 -0
- package/lib/module/components/SystemAlert/styles.js.map +1 -0
- package/lib/module/components/SystemAlert/useSystemAlertStatusBarStyle.js +49 -0
- package/lib/module/components/SystemAlert/useSystemAlertStatusBarStyle.js.map +1 -0
- package/lib/module/components/Tabs/index.js +4 -0
- package/lib/module/components/Tabs/index.js.map +1 -1
- package/lib/module/components/Tabs/styles.js +1 -1
- package/lib/module/components/Tabs/styles.js.map +1 -1
- package/lib/module/components/chart/ChartInspector.js +600 -0
- package/lib/module/components/chart/ChartInspector.js.map +1 -0
- package/lib/module/components/chart/format.js +84 -0
- package/lib/module/components/chart/format.js.map +1 -0
- package/lib/module/components/chart/index.js +2 -0
- package/lib/module/components/chart/index.js.map +1 -0
- package/lib/module/components/chart/motion.js +89 -0
- package/lib/module/components/chart/motion.js.map +1 -0
- package/lib/module/components/chart/palette.js +168 -0
- package/lib/module/components/chart/palette.js.map +1 -0
- package/lib/module/components/chart/size.js +29 -0
- package/lib/module/components/chart/size.js.map +1 -0
- package/lib/module/components/chart/ticks.js +55 -0
- package/lib/module/components/chart/ticks.js.map +1 -0
- package/lib/module/components/chart/types.js +4 -0
- package/lib/module/components/chart/types.js.map +1 -0
- package/lib/module/components/chart/useChartHostWidth.js +23 -0
- package/lib/module/components/chart/useChartHostWidth.js.map +1 -0
- package/lib/module/components/index.js +5 -0
- package/lib/module/components/index.js.map +1 -1
- package/lib/module/figma/SystemAlert.Button.figma.js +22 -0
- package/lib/module/figma/SystemAlert.Button.figma.js.map +1 -0
- package/lib/module/figma/SystemAlert.Close.figma.js +18 -0
- package/lib/module/figma/SystemAlert.Close.figma.js.map +1 -0
- package/lib/module/figma/SystemAlert.Description.figma.js +14 -0
- package/lib/module/figma/SystemAlert.Description.figma.js.map +1 -0
- package/lib/module/figma/SystemAlert.IconButton.figma.js +25 -0
- package/lib/module/figma/SystemAlert.IconButton.figma.js.map +1 -0
- package/lib/module/figma/SystemAlert.figma.js +42 -0
- package/lib/module/figma/SystemAlert.figma.js.map +1 -0
- package/lib/typescript/components/BarChart/geometry.d.ts +236 -0
- package/lib/typescript/components/BarChart/geometry.d.ts.map +1 -0
- package/lib/typescript/components/BarChart/index.d.ts +193 -0
- package/lib/typescript/components/BarChart/index.d.ts.map +1 -0
- package/lib/typescript/components/BarChart/styles.d.ts +32 -0
- package/lib/typescript/components/BarChart/styles.d.ts.map +1 -0
- package/lib/typescript/components/Chip/styles.d.ts.map +1 -1
- package/lib/typescript/components/LineChart/geometry.d.ts +248 -0
- package/lib/typescript/components/LineChart/geometry.d.ts.map +1 -0
- package/lib/typescript/components/LineChart/index.d.ts +105 -0
- package/lib/typescript/components/LineChart/index.d.ts.map +1 -0
- package/lib/typescript/components/LineChart/styles.d.ts +12 -0
- package/lib/typescript/components/LineChart/styles.d.ts.map +1 -0
- package/lib/typescript/components/PieChart/geometry.d.ts +43 -0
- package/lib/typescript/components/PieChart/geometry.d.ts.map +1 -0
- package/lib/typescript/components/PieChart/index.d.ts +147 -0
- package/lib/typescript/components/PieChart/index.d.ts.map +1 -0
- package/lib/typescript/components/PieChart/styles.d.ts +27 -0
- package/lib/typescript/components/PieChart/styles.d.ts.map +1 -0
- package/lib/typescript/components/ProgressCircle/ProgressCircleRing.d.ts.map +1 -1
- package/lib/typescript/components/ProgressCircle/ProgressRing.d.ts +17 -0
- package/lib/typescript/components/ProgressCircle/ProgressRing.d.ts.map +1 -0
- package/lib/typescript/components/ScrollShadow/useScrollShadow.d.ts.map +1 -1
- package/lib/typescript/components/Stepper/index.d.ts +68 -0
- package/lib/typescript/components/Stepper/index.d.ts.map +1 -0
- package/lib/typescript/components/Stepper/styles.d.ts +46 -0
- package/lib/typescript/components/Stepper/styles.d.ts.map +1 -0
- package/lib/typescript/components/Stepper/useStepperLabelFit.d.ts +15 -0
- package/lib/typescript/components/Stepper/useStepperLabelFit.d.ts.map +1 -0
- package/lib/typescript/components/Stepper/useStepperLabelFit.web.d.ts +10 -0
- package/lib/typescript/components/Stepper/useStepperLabelFit.web.d.ts.map +1 -0
- package/lib/typescript/components/Stepper/useStepperRingColors.d.ts +6 -0
- package/lib/typescript/components/Stepper/useStepperRingColors.d.ts.map +1 -0
- package/lib/typescript/components/SystemAlert/animation.d.ts +56 -0
- package/lib/typescript/components/SystemAlert/animation.d.ts.map +1 -0
- package/lib/typescript/components/SystemAlert/index.d.ts +110 -0
- package/lib/typescript/components/SystemAlert/index.d.ts.map +1 -0
- package/lib/typescript/components/SystemAlert/styles.d.ts +38 -0
- package/lib/typescript/components/SystemAlert/styles.d.ts.map +1 -0
- package/lib/typescript/components/SystemAlert/useSystemAlertStatusBarStyle.d.ts +14 -0
- package/lib/typescript/components/SystemAlert/useSystemAlertStatusBarStyle.d.ts.map +1 -0
- package/lib/typescript/components/Tabs/index.d.ts.map +1 -1
- package/lib/typescript/components/chart/ChartInspector.d.ts +101 -0
- package/lib/typescript/components/chart/ChartInspector.d.ts.map +1 -0
- package/lib/typescript/components/chart/format.d.ts +16 -0
- package/lib/typescript/components/chart/format.d.ts.map +1 -0
- package/lib/typescript/components/chart/index.d.ts +3 -0
- package/lib/typescript/components/chart/index.d.ts.map +1 -0
- package/lib/typescript/components/chart/motion.d.ts +27 -0
- package/lib/typescript/components/chart/motion.d.ts.map +1 -0
- package/lib/typescript/components/chart/palette.d.ts +44 -0
- package/lib/typescript/components/chart/palette.d.ts.map +1 -0
- package/lib/typescript/components/chart/size.d.ts +25 -0
- package/lib/typescript/components/chart/size.d.ts.map +1 -0
- package/lib/typescript/components/chart/ticks.d.ts +3 -0
- package/lib/typescript/components/chart/ticks.d.ts.map +1 -0
- package/lib/typescript/components/chart/types.d.ts +163 -0
- package/lib/typescript/components/chart/types.d.ts.map +1 -0
- package/lib/typescript/components/chart/useChartHostWidth.d.ts +9 -0
- package/lib/typescript/components/chart/useChartHostWidth.d.ts.map +1 -0
- package/lib/typescript/components/index.d.ts +5 -0
- package/lib/typescript/components/index.d.ts.map +1 -1
- package/lib/typescript/figma/SystemAlert.Button.figma.d.ts +11 -0
- package/lib/typescript/figma/SystemAlert.Button.figma.d.ts.map +1 -0
- package/lib/typescript/figma/SystemAlert.Close.figma.d.ts +11 -0
- package/lib/typescript/figma/SystemAlert.Close.figma.d.ts.map +1 -0
- package/lib/typescript/figma/SystemAlert.Description.figma.d.ts +8 -0
- package/lib/typescript/figma/SystemAlert.Description.figma.d.ts.map +1 -0
- package/lib/typescript/figma/SystemAlert.IconButton.figma.d.ts +11 -0
- package/lib/typescript/figma/SystemAlert.IconButton.figma.d.ts.map +1 -0
- package/lib/typescript/figma/SystemAlert.figma.d.ts +8 -0
- package/lib/typescript/figma/SystemAlert.figma.d.ts.map +1 -0
- package/package.json +5 -5
- package/src/components/BarChart/geometry.ts +1469 -0
- package/src/components/BarChart/index.tsx +1424 -0
- package/src/components/BarChart/styles.ts +31 -0
- package/src/components/Button/index.tsx +1 -1
- package/src/components/Chip/styles.ts +3 -1
- package/src/components/LineChart/geometry.ts +957 -0
- package/src/components/LineChart/index.tsx +1109 -0
- package/src/components/LineChart/styles.ts +26 -0
- package/src/components/PieChart/geometry.ts +295 -0
- package/src/components/PieChart/index.tsx +715 -0
- package/src/components/PieChart/styles.ts +101 -0
- package/src/components/ProgressCircle/ProgressCircleRing.tsx +10 -39
- package/src/components/ProgressCircle/ProgressRing.tsx +60 -0
- package/src/components/ScrollShadow/useScrollShadow.ts +12 -11
- package/src/components/Stepper/index.tsx +452 -0
- package/src/components/Stepper/styles.ts +234 -0
- package/src/components/Stepper/useStepperLabelFit.ts +21 -0
- package/src/components/Stepper/useStepperLabelFit.web.ts +53 -0
- package/src/components/Stepper/useStepperRingColors.ts +29 -0
- package/src/components/SystemAlert/animation.ts +252 -0
- package/src/components/SystemAlert/index.tsx +684 -0
- package/src/components/SystemAlert/styles.ts +212 -0
- package/src/components/SystemAlert/useSystemAlertStatusBarStyle.ts +57 -0
- package/src/components/Tabs/index.tsx +4 -0
- package/src/components/Tabs/styles.ts +1 -1
- package/src/components/chart/ChartInspector.tsx +726 -0
- package/src/components/chart/format.ts +95 -0
- package/src/components/chart/index.ts +24 -0
- package/src/components/chart/motion.ts +98 -0
- package/src/components/chart/palette.tsx +247 -0
- package/src/components/chart/size.ts +21 -0
- package/src/components/chart/ticks.ts +53 -0
- package/src/components/chart/types.ts +183 -0
- package/src/components/chart/useChartHostWidth.ts +26 -0
- package/src/components/index.ts +5 -0
|
@@ -0,0 +1,715 @@
|
|
|
1
|
+
import { forwardRef, useEffect, useMemo, useRef, useState, type ReactNode } from 'react';
|
|
2
|
+
import { Platform, Pressable, Text, View, type ViewProps } from 'react-native';
|
|
3
|
+
import Animated, { useAnimatedProps } from 'react-native-reanimated';
|
|
4
|
+
import { Path, Svg } from 'react-native-svg';
|
|
5
|
+
import { dataAttributes, mergeDataAttributes } from '@cdx-ui/primitives';
|
|
6
|
+
import { cn, useControllableState } from '@cdx-ui/utils';
|
|
7
|
+
import type { AnimationRootDisableAll } from '../../animation';
|
|
8
|
+
import { ChartTooltipLayer } from '../chart/ChartInspector';
|
|
9
|
+
import { formatChartPercent, resolveChartLocaleText, resolveValueFormatter } from '../chart/format';
|
|
10
|
+
import { resolveChartBox } from '../chart/size';
|
|
11
|
+
import type {
|
|
12
|
+
ChartLocaleText,
|
|
13
|
+
ChartSelectedItem,
|
|
14
|
+
ChartSeriesCommon,
|
|
15
|
+
ChartSizeProps,
|
|
16
|
+
ChartTooltipContent,
|
|
17
|
+
ChartTooltipProps,
|
|
18
|
+
ChartValueFormatterContext,
|
|
19
|
+
ChartValueLocation,
|
|
20
|
+
} from '../chart/types';
|
|
21
|
+
import {
|
|
22
|
+
ChartLinearFills,
|
|
23
|
+
chartFillUrl,
|
|
24
|
+
resolveChartColor,
|
|
25
|
+
useChartColorPaints,
|
|
26
|
+
useChartGradientId,
|
|
27
|
+
useChartPalette,
|
|
28
|
+
type ChartColor,
|
|
29
|
+
} from '../chart/palette';
|
|
30
|
+
import { lerpVector, useChartMorph, useChartMotionEnabled } from '../chart/motion';
|
|
31
|
+
import {
|
|
32
|
+
layoutPieSlices,
|
|
33
|
+
pieRadii,
|
|
34
|
+
resolveInnerRadiusRatio,
|
|
35
|
+
slicePath,
|
|
36
|
+
type PieRadii,
|
|
37
|
+
} from './geometry';
|
|
38
|
+
import {
|
|
39
|
+
pieChartCanvasVariants,
|
|
40
|
+
pieChartCenterLabelVariants,
|
|
41
|
+
pieChartLegendItemVariants,
|
|
42
|
+
pieChartLegendLabelVariants,
|
|
43
|
+
pieChartLegendMetaVariants,
|
|
44
|
+
pieChartLegendShareVariants,
|
|
45
|
+
pieChartLegendVariants,
|
|
46
|
+
pieChartRootVariants,
|
|
47
|
+
type PieChartLegendLayout,
|
|
48
|
+
type PieChartLegendPlacement,
|
|
49
|
+
} from './styles';
|
|
50
|
+
|
|
51
|
+
const INACTIVE_OPACITY = 0.4;
|
|
52
|
+
const SLICE_HOVER_EXIT_DELAY_MS = 120;
|
|
53
|
+
const ENTRY_ANGLES: readonly number[] = [0, 0];
|
|
54
|
+
const NO_SLICES: readonly PieChartValue[] = [];
|
|
55
|
+
|
|
56
|
+
let hasWarnedPieChartSeries = false;
|
|
57
|
+
|
|
58
|
+
const AnimatedPath = Animated.createAnimatedComponent(Path);
|
|
59
|
+
|
|
60
|
+
export type * from '../chart';
|
|
61
|
+
export type { PieChartLegendLayout, PieChartLegendPlacement };
|
|
62
|
+
|
|
63
|
+
/** One slice. Same shape as MUI X `PieValueType`. */
|
|
64
|
+
export interface PieChartValue {
|
|
65
|
+
/**
|
|
66
|
+
* Stable slice id. React keys and animation follow it, so a reordered slice moves to its new
|
|
67
|
+
* position instead of the slices morphing by index.
|
|
68
|
+
*/
|
|
69
|
+
id: string;
|
|
70
|
+
/**
|
|
71
|
+
* Must be a positive finite number. Zero, negative, and `NaN` slices are skipped so they cannot
|
|
72
|
+
* break an arc; `dataIndex` still counts them.
|
|
73
|
+
*/
|
|
74
|
+
value: number;
|
|
75
|
+
/** Legend, tooltip, and screen reader name. Defaults to `id`. */
|
|
76
|
+
label?: string;
|
|
77
|
+
/** Chart token for this slice. Defaults to the next `categorical-1`…`6` slot. */
|
|
78
|
+
color?: ChartColor;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Turns a slice's 0–1 share of the visible total into text. `location` is `label` for the
|
|
83
|
+
* legend and `a11y` for screen reader labels and the summary.
|
|
84
|
+
*/
|
|
85
|
+
export type PieShareFormatter = (share: number, context: ChartValueFormatterContext) => string;
|
|
86
|
+
|
|
87
|
+
/** One ring of slices. Same shape as MUI X `PieSeriesType`; slices carry their own `color`. */
|
|
88
|
+
export interface PieChartSeries extends Omit<ChartSeriesCommon, 'color'> {
|
|
89
|
+
/** Series kind. Optional while `PieChart` only draws pies. */
|
|
90
|
+
type?: 'pie';
|
|
91
|
+
/** Slices, drawn clockwise from 12 o'clock in array order. */
|
|
92
|
+
data: readonly PieChartValue[];
|
|
93
|
+
/**
|
|
94
|
+
* Hole radius. A number is pixels; a string such as `'68%'` is a percent of the outer radius.
|
|
95
|
+
* `0` is a full pie. Clamped to 85% of the outer radius.
|
|
96
|
+
* @default 0
|
|
97
|
+
*/
|
|
98
|
+
innerRadius?: number | `${number}%`;
|
|
99
|
+
/**
|
|
100
|
+
* Degrees between neighboring slices. Skipped on slices too thin to fit it.
|
|
101
|
+
* @default 0
|
|
102
|
+
*/
|
|
103
|
+
paddingAngle?: number;
|
|
104
|
+
/**
|
|
105
|
+
* Rounds slice corners in pixels. Donuts round all four corners; pies round the outer edge.
|
|
106
|
+
* Clamped so thin slices stay valid.
|
|
107
|
+
* @default 0
|
|
108
|
+
*/
|
|
109
|
+
cornerRadius?: number;
|
|
110
|
+
/**
|
|
111
|
+
* Formats the legend share column and the share in screen reader text. MUI X has no share
|
|
112
|
+
* column, so there is no MUI name. Defaults to a whole percent for `locale`.
|
|
113
|
+
*/
|
|
114
|
+
shareFormatter?: PieShareFormatter;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** The slice under the pointer or keyboard focus, falling back to the selected slice. */
|
|
118
|
+
export interface PieChartActiveSlice {
|
|
119
|
+
/** The slice `id`. */
|
|
120
|
+
id: string;
|
|
121
|
+
/** Index into the series `data`, including slices that were skipped. */
|
|
122
|
+
dataIndex: number;
|
|
123
|
+
/** The slice `label`, or its `id` when it has none. */
|
|
124
|
+
label: string;
|
|
125
|
+
/** The raw slice `value`, unformatted. */
|
|
126
|
+
value: number;
|
|
127
|
+
/** Share of the visible total, from 0 to 1. */
|
|
128
|
+
percentage: number;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export interface PieChartProps extends ViewProps, ChartSizeProps {
|
|
132
|
+
/**
|
|
133
|
+
* Pie series. Only the first renders; more than one logs a development warning. The pie
|
|
134
|
+
* diameter is the shorter side of the `size` / `width` / `height` box.
|
|
135
|
+
*/
|
|
136
|
+
series: readonly PieChartSeries[];
|
|
137
|
+
/**
|
|
138
|
+
* Hides the category legend. With the legend hidden, a selectable chart makes each slice a
|
|
139
|
+
* button; otherwise the plot is one image with a generated summary.
|
|
140
|
+
* @default false
|
|
141
|
+
*/
|
|
142
|
+
hideLegend?: boolean;
|
|
143
|
+
/**
|
|
144
|
+
* How legend items are laid out when `legendPlacement` is `top` or `bottom`.
|
|
145
|
+
* `wrap` is centered chips (`Housing 46%`). `list` is full-width rows: name
|
|
146
|
+
* on the left, formatted value and share on the right, with a hairline between
|
|
147
|
+
* rows. Ignored when the legend is on the left or right.
|
|
148
|
+
* @default 'wrap'
|
|
149
|
+
*/
|
|
150
|
+
legendLayout?: PieChartLegendLayout;
|
|
151
|
+
/**
|
|
152
|
+
* Where the legend sits relative to the plot, matching Drawer `placement`.
|
|
153
|
+
* `top` / `bottom` use `legendLayout`. `left` / `right` are swatch + name only
|
|
154
|
+
* (no amount, no percent).
|
|
155
|
+
* @default 'bottom'
|
|
156
|
+
*/
|
|
157
|
+
legendPlacement?: PieChartLegendPlacement;
|
|
158
|
+
/**
|
|
159
|
+
* `item` shows the slice under the pointer or keyboard focus (on touch, the slice under the finger until it lifts).
|
|
160
|
+
* `axis` behaves like `item` because a pie has no axis. `none` hides the tooltip.
|
|
161
|
+
* @default { trigger: 'item' }
|
|
162
|
+
*/
|
|
163
|
+
tooltip?: ChartTooltipProps;
|
|
164
|
+
/**
|
|
165
|
+
* Content in the donut hole. A string becomes styled `Text`. Pass a `View` for
|
|
166
|
+
* stacked copy such as “Total” over an amount.
|
|
167
|
+
* Pass a function to follow the active slice: it receives the hovered or focused
|
|
168
|
+
* slice, else the selected one, else `null` — return the total for `null`.
|
|
169
|
+
*/
|
|
170
|
+
centerLabel?: ReactNode | ((slice: PieChartActiveSlice | null) => ReactNode);
|
|
171
|
+
/**
|
|
172
|
+
* Controlled selected slice: `{ seriesId, dataIndex }`, where `dataIndex` counts skipped
|
|
173
|
+
* slices. Pass `null` to clear the selection while staying controlled.
|
|
174
|
+
*/
|
|
175
|
+
selectedItem?: ChartSelectedItem;
|
|
176
|
+
/** Initial selected slice when `selectedItem` is omitted. */
|
|
177
|
+
defaultSelectedItem?: ChartSelectedItem;
|
|
178
|
+
/**
|
|
179
|
+
* Called when a slice or legend row is pressed. Pressing the selected slice again reports
|
|
180
|
+
* `null`. Providing this, `selectedItem`, or `defaultSelectedItem` makes the chart selectable.
|
|
181
|
+
*/
|
|
182
|
+
onSelectedItemChange?: (item: ChartSelectedItem) => void;
|
|
183
|
+
/**
|
|
184
|
+
* BCP 47 locale for default value and share formatting. Defaults to the `@cdx-ui/formatters`
|
|
185
|
+
* locale.
|
|
186
|
+
*/
|
|
187
|
+
locale?: string;
|
|
188
|
+
/** Overrides for generated strings such as the screen reader summary. */
|
|
189
|
+
localeText?: Partial<ChartLocaleText>;
|
|
190
|
+
/**
|
|
191
|
+
* Slices sweep in clockwise on mount and tween to new shares when `data` changes. Pass
|
|
192
|
+
* `'disable-all'` to draw the final state. Reduce Motion and `GlobalAnimationSettingsProvider`
|
|
193
|
+
* `disable-all` also turn it off.
|
|
194
|
+
*/
|
|
195
|
+
animation?: AnimationRootDisableAll;
|
|
196
|
+
/**
|
|
197
|
+
* Names the plot image. Defaults to `localeText.pieChartSummary` over labels and shares. Not
|
|
198
|
+
* applied when `hideLegend` on a selectable chart makes each slice a button.
|
|
199
|
+
*/
|
|
200
|
+
accessibilityLabel?: string;
|
|
201
|
+
/** Additional Uniwind/Tailwind classes, merged after the root's own styles. */
|
|
202
|
+
className?: string;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export const PieChart = forwardRef<View, PieChartProps>(
|
|
206
|
+
(
|
|
207
|
+
{
|
|
208
|
+
series,
|
|
209
|
+
size = 'lg',
|
|
210
|
+
width,
|
|
211
|
+
height,
|
|
212
|
+
hideLegend = false,
|
|
213
|
+
legendLayout = 'wrap',
|
|
214
|
+
legendPlacement = 'bottom',
|
|
215
|
+
tooltip,
|
|
216
|
+
centerLabel,
|
|
217
|
+
selectedItem: selectedItemProp,
|
|
218
|
+
defaultSelectedItem,
|
|
219
|
+
onSelectedItemChange,
|
|
220
|
+
locale,
|
|
221
|
+
localeText,
|
|
222
|
+
animation,
|
|
223
|
+
accessibilityLabel,
|
|
224
|
+
className,
|
|
225
|
+
style,
|
|
226
|
+
...props
|
|
227
|
+
},
|
|
228
|
+
ref,
|
|
229
|
+
) => {
|
|
230
|
+
useEffect(() => {
|
|
231
|
+
if (__DEV__ && series.length > 1 && !hasWarnedPieChartSeries) {
|
|
232
|
+
hasWarnedPieChartSeries = true;
|
|
233
|
+
console.warn(
|
|
234
|
+
'[PieChart] Only the first series renders. Concentric rings are not supported yet.',
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
}, [series.length]);
|
|
238
|
+
|
|
239
|
+
const pieSeries = series.at(0);
|
|
240
|
+
const seriesId = pieSeries?.id ?? '';
|
|
241
|
+
const data = pieSeries?.data ?? NO_SLICES;
|
|
242
|
+
const cornerRadius = pieSeries?.cornerRadius ?? 0;
|
|
243
|
+
const paddingAngle = pieSeries?.paddingAngle ?? 0;
|
|
244
|
+
const text = resolveChartLocaleText(localeText);
|
|
245
|
+
const formatValue = resolveValueFormatter(pieSeries, undefined, locale);
|
|
246
|
+
const formatShare: PieShareFormatter =
|
|
247
|
+
pieSeries?.shareFormatter ?? ((share) => formatChartPercent(share, locale));
|
|
248
|
+
const context = (location: ChartValueLocation, dataIndex: number) => ({
|
|
249
|
+
location,
|
|
250
|
+
seriesId,
|
|
251
|
+
dataIndex,
|
|
252
|
+
});
|
|
253
|
+
const animated = useChartMotionEnabled(animation);
|
|
254
|
+
const palette = useChartPalette();
|
|
255
|
+
const paints = useChartColorPaints();
|
|
256
|
+
const gradientId = useChartGradientId('pie-fill');
|
|
257
|
+
const isSideLegend = legendPlacement === 'left' || legendPlacement === 'right';
|
|
258
|
+
const legendFirst = legendPlacement === 'top' || legendPlacement === 'left';
|
|
259
|
+
const legendMode = isSideLegend ? 'names' : legendLayout;
|
|
260
|
+
const box = resolveChartBox(size, width, height);
|
|
261
|
+
const dimension = Math.min(box.width, box.height);
|
|
262
|
+
const innerRadiusRatio = resolveInnerRadiusRatio(pieSeries?.innerRadius ?? 0, dimension);
|
|
263
|
+
const layout = useMemo(
|
|
264
|
+
() =>
|
|
265
|
+
layoutPieSlices({
|
|
266
|
+
data: data.map((slice) => ({
|
|
267
|
+
id: slice.id,
|
|
268
|
+
label: slice.label ?? slice.id,
|
|
269
|
+
value: slice.value,
|
|
270
|
+
})),
|
|
271
|
+
size: dimension,
|
|
272
|
+
innerRadiusRatio,
|
|
273
|
+
paddingAngle,
|
|
274
|
+
cornerRadius,
|
|
275
|
+
}),
|
|
276
|
+
[data, dimension, innerRadiusRatio, paddingAngle, cornerRadius],
|
|
277
|
+
);
|
|
278
|
+
const slices = layout.map((slice, visibleIndex) => ({
|
|
279
|
+
...slice,
|
|
280
|
+
fill:
|
|
281
|
+
resolveChartColor(data[slice.index]?.color, paints) ??
|
|
282
|
+
palette[visibleIndex % palette.length],
|
|
283
|
+
}));
|
|
284
|
+
const radii = pieRadii(dimension, innerRadiusRatio);
|
|
285
|
+
const summary =
|
|
286
|
+
accessibilityLabel ??
|
|
287
|
+
text.pieChartSummary(
|
|
288
|
+
slices.map((slice) => ({
|
|
289
|
+
label: slice.label,
|
|
290
|
+
formattedValue: formatShare(slice.percentage, context('a11y', slice.index)),
|
|
291
|
+
})),
|
|
292
|
+
);
|
|
293
|
+
const tooltipEnabled = (tooltip?.trigger ?? 'item') !== 'none';
|
|
294
|
+
const interactive =
|
|
295
|
+
onSelectedItemChange !== undefined ||
|
|
296
|
+
selectedItemProp !== undefined ||
|
|
297
|
+
defaultSelectedItem !== undefined;
|
|
298
|
+
const hoverable = interactive || tooltipEnabled;
|
|
299
|
+
const sliceButtons = interactive && hideLegend;
|
|
300
|
+
const hidePlotFromAccessibility = !sliceButtons;
|
|
301
|
+
const [selectedItemState, setSelectedItem] = useControllableState<ChartSelectedItem>({
|
|
302
|
+
prop: selectedItemProp,
|
|
303
|
+
defaultProp: defaultSelectedItem ?? null,
|
|
304
|
+
onChange: onSelectedItemChange,
|
|
305
|
+
});
|
|
306
|
+
const selectedItem = selectedItemState ?? null;
|
|
307
|
+
const selectedIndex =
|
|
308
|
+
selectedItem !== null && selectedItem.seriesId === seriesId ? selectedItem.dataIndex : null;
|
|
309
|
+
const toggleSelection = (dataIndex: number) => {
|
|
310
|
+
setSelectedItem(selectedIndex === dataIndex ? null : { seriesId, dataIndex });
|
|
311
|
+
};
|
|
312
|
+
const [previewIndex, setPreviewIndex] = useState<number | null>(null);
|
|
313
|
+
const highlightedIndex = previewIndex ?? selectedIndex;
|
|
314
|
+
const clearPreviewTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
315
|
+
useEffect(
|
|
316
|
+
() => () => {
|
|
317
|
+
if (clearPreviewTimer.current) {
|
|
318
|
+
clearTimeout(clearPreviewTimer.current);
|
|
319
|
+
}
|
|
320
|
+
},
|
|
321
|
+
[],
|
|
322
|
+
);
|
|
323
|
+
const cancelPreviewClear = () => {
|
|
324
|
+
if (clearPreviewTimer.current) {
|
|
325
|
+
clearTimeout(clearPreviewTimer.current);
|
|
326
|
+
clearPreviewTimer.current = null;
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
const previewSlice = (index: number) => {
|
|
330
|
+
cancelPreviewClear();
|
|
331
|
+
setPreviewIndex(index);
|
|
332
|
+
};
|
|
333
|
+
const clearSlicePreview = () => {
|
|
334
|
+
clearPreviewTimer.current = setTimeout(() => {
|
|
335
|
+
clearPreviewTimer.current = null;
|
|
336
|
+
setPreviewIndex(null);
|
|
337
|
+
}, SLICE_HOVER_EXIT_DELAY_MS);
|
|
338
|
+
};
|
|
339
|
+
const pressSlice = (index: number) => {
|
|
340
|
+
if (interactive) {
|
|
341
|
+
toggleSelection(index);
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
const sliceButtonLabel = (slice: { label: string; percentage: number; index: number }) =>
|
|
345
|
+
`${slice.label}, ${formatShare(slice.percentage, context('a11y', slice.index))}`;
|
|
346
|
+
const releaseSlice = () => {
|
|
347
|
+
cancelPreviewClear();
|
|
348
|
+
setPreviewIndex(null);
|
|
349
|
+
};
|
|
350
|
+
const activeLayout = slices.find((slice) => slice.index === highlightedIndex);
|
|
351
|
+
const activeSlice: PieChartActiveSlice | null = activeLayout
|
|
352
|
+
? {
|
|
353
|
+
id: activeLayout.id,
|
|
354
|
+
dataIndex: activeLayout.index,
|
|
355
|
+
label: activeLayout.label,
|
|
356
|
+
value: activeLayout.value,
|
|
357
|
+
percentage: activeLayout.percentage,
|
|
358
|
+
}
|
|
359
|
+
: null;
|
|
360
|
+
const centerContent =
|
|
361
|
+
typeof centerLabel === 'function' ? centerLabel(activeSlice) : centerLabel;
|
|
362
|
+
const tooltipSlice = tooltipEnabled
|
|
363
|
+
? slices.find((slice) => slice.index === previewIndex)
|
|
364
|
+
: undefined;
|
|
365
|
+
const tooltipContent: ChartTooltipContent | undefined = tooltipSlice
|
|
366
|
+
? {
|
|
367
|
+
title: tooltipSlice.label,
|
|
368
|
+
items: [
|
|
369
|
+
{
|
|
370
|
+
seriesId,
|
|
371
|
+
label: pieSeries?.label ?? '',
|
|
372
|
+
color: tooltipSlice.fill,
|
|
373
|
+
formattedValue: formatValue(
|
|
374
|
+
tooltipSlice.value,
|
|
375
|
+
context('tooltip', tooltipSlice.index),
|
|
376
|
+
),
|
|
377
|
+
},
|
|
378
|
+
],
|
|
379
|
+
}
|
|
380
|
+
: undefined;
|
|
381
|
+
const tooltipAnchor = tooltipSlice
|
|
382
|
+
? anglePoint(
|
|
383
|
+
radii,
|
|
384
|
+
(radii.innerRadius + radii.outerRadius) / 2,
|
|
385
|
+
(tooltipSlice.startAngle + tooltipSlice.endAngle) / 2,
|
|
386
|
+
)
|
|
387
|
+
: undefined;
|
|
388
|
+
|
|
389
|
+
const chart = (
|
|
390
|
+
<View className="relative shrink-0" style={{ width: dimension, height: dimension }}>
|
|
391
|
+
<View
|
|
392
|
+
collapsable={false}
|
|
393
|
+
className={
|
|
394
|
+
width === undefined && height === undefined
|
|
395
|
+
? pieChartCanvasVariants({ size })
|
|
396
|
+
: 'relative shrink-0 items-center justify-center overflow-hidden web:outline-none'
|
|
397
|
+
}
|
|
398
|
+
style={{ width: dimension, height: dimension, overflow: 'hidden' }}
|
|
399
|
+
accessible={!sliceButtons}
|
|
400
|
+
accessibilityRole={!sliceButtons ? 'image' : undefined}
|
|
401
|
+
accessibilityLabel={!sliceButtons ? summary : undefined}
|
|
402
|
+
focusable={false}
|
|
403
|
+
tabIndex={-1}
|
|
404
|
+
{...(Platform.OS === 'web'
|
|
405
|
+
? {
|
|
406
|
+
onMouseDown: (event: { preventDefault?: () => void }) => {
|
|
407
|
+
event.preventDefault?.();
|
|
408
|
+
},
|
|
409
|
+
}
|
|
410
|
+
: {})}
|
|
411
|
+
{...dataAttributes({ slot: 'pie-chart-plot' })}
|
|
412
|
+
>
|
|
413
|
+
<Svg
|
|
414
|
+
width={dimension}
|
|
415
|
+
height={dimension}
|
|
416
|
+
viewBox={`0 0 ${String(dimension)} ${String(dimension)}`}
|
|
417
|
+
focusable={false}
|
|
418
|
+
tabIndex={-1}
|
|
419
|
+
className="web:outline-none"
|
|
420
|
+
pointerEvents={hoverable ? 'auto' : 'none'}
|
|
421
|
+
accessible={false}
|
|
422
|
+
accessibilityElementsHidden={hidePlotFromAccessibility}
|
|
423
|
+
importantForAccessibility={hidePlotFromAccessibility ? 'no-hide-descendants' : 'no'}
|
|
424
|
+
>
|
|
425
|
+
<ChartLinearFills
|
|
426
|
+
idPrefix={gradientId}
|
|
427
|
+
colors={slices.map((slice) => slice.fill)}
|
|
428
|
+
x2="100%"
|
|
429
|
+
y2="100%"
|
|
430
|
+
/>
|
|
431
|
+
{slices.map((slice, visibleIndex) => {
|
|
432
|
+
const dimmed =
|
|
433
|
+
interactive && activeLayout !== undefined && activeLayout.index !== slice.index;
|
|
434
|
+
return (
|
|
435
|
+
<AnimatedSlice
|
|
436
|
+
key={slice.id}
|
|
437
|
+
startAngle={slice.startAngle}
|
|
438
|
+
endAngle={slice.endAngle}
|
|
439
|
+
animated={animated}
|
|
440
|
+
radii={radii}
|
|
441
|
+
cornerRadius={cornerRadius}
|
|
442
|
+
fill={chartFillUrl(gradientId, visibleIndex)}
|
|
443
|
+
opacity={dimmed ? INACTIVE_OPACITY : 1}
|
|
444
|
+
accessible={false}
|
|
445
|
+
{...(hoverable
|
|
446
|
+
? {
|
|
447
|
+
onPress: () => {
|
|
448
|
+
pressSlice(slice.index);
|
|
449
|
+
},
|
|
450
|
+
onMouseEnter: () => {
|
|
451
|
+
previewSlice(slice.index);
|
|
452
|
+
},
|
|
453
|
+
onMouseLeave: clearSlicePreview,
|
|
454
|
+
// Touch has no hover: the finger previews the slice until it lifts.
|
|
455
|
+
...(Platform.OS !== 'web'
|
|
456
|
+
? {
|
|
457
|
+
onPressIn: () => {
|
|
458
|
+
previewSlice(slice.index);
|
|
459
|
+
},
|
|
460
|
+
onPressOut: releaseSlice,
|
|
461
|
+
}
|
|
462
|
+
: {}),
|
|
463
|
+
...(interactive ? { cursor: 'pointer' as const } : {}),
|
|
464
|
+
}
|
|
465
|
+
: {})}
|
|
466
|
+
accessibilityElementsHidden
|
|
467
|
+
importantForAccessibility="no-hide-descendants"
|
|
468
|
+
{...(sliceButtons ? { accessibilityLabel: sliceButtonLabel(slice) } : {})}
|
|
469
|
+
/>
|
|
470
|
+
);
|
|
471
|
+
})}
|
|
472
|
+
</Svg>
|
|
473
|
+
{sliceButtons
|
|
474
|
+
? slices.map((slice) => {
|
|
475
|
+
const mid = anglePoint(
|
|
476
|
+
radii,
|
|
477
|
+
(radii.innerRadius + radii.outerRadius) / 2,
|
|
478
|
+
(slice.startAngle + slice.endAngle) / 2,
|
|
479
|
+
);
|
|
480
|
+
const target = 24;
|
|
481
|
+
return (
|
|
482
|
+
<Pressable
|
|
483
|
+
key={slice.id}
|
|
484
|
+
accessibilityRole="button"
|
|
485
|
+
accessibilityLabel={sliceButtonLabel(slice)}
|
|
486
|
+
accessibilityState={{ selected: selectedIndex === slice.index }}
|
|
487
|
+
aria-pressed={Platform.OS === 'web' ? selectedIndex === slice.index : undefined}
|
|
488
|
+
focusable
|
|
489
|
+
tabIndex={0}
|
|
490
|
+
className="absolute"
|
|
491
|
+
style={{
|
|
492
|
+
left: mid.x - target / 2,
|
|
493
|
+
top: mid.y - target / 2,
|
|
494
|
+
width: target,
|
|
495
|
+
height: target,
|
|
496
|
+
}}
|
|
497
|
+
// Pointer hits stay on the wedge. This host is the keyboard and
|
|
498
|
+
// screen-reader target, because an SVG path does not activate on Enter or Space.
|
|
499
|
+
pointerEvents="none"
|
|
500
|
+
onPress={() => {
|
|
501
|
+
pressSlice(slice.index);
|
|
502
|
+
}}
|
|
503
|
+
onFocus={() => {
|
|
504
|
+
previewSlice(slice.index);
|
|
505
|
+
}}
|
|
506
|
+
onBlur={clearSlicePreview}
|
|
507
|
+
{...dataAttributes({ slot: 'pie-chart-slice' })}
|
|
508
|
+
/>
|
|
509
|
+
);
|
|
510
|
+
})
|
|
511
|
+
: null}
|
|
512
|
+
{centerContent ? (
|
|
513
|
+
<View
|
|
514
|
+
className="pointer-events-none absolute inset-0 items-center justify-center px-6"
|
|
515
|
+
accessible={false}
|
|
516
|
+
accessibilityElementsHidden
|
|
517
|
+
importantForAccessibility="no-hide-descendants"
|
|
518
|
+
{...dataAttributes({ slot: 'pie-chart-center' })}
|
|
519
|
+
>
|
|
520
|
+
{typeof centerContent === 'string' ? (
|
|
521
|
+
<Text className={pieChartCenterLabelVariants()}>{centerContent}</Text>
|
|
522
|
+
) : (
|
|
523
|
+
centerContent
|
|
524
|
+
)}
|
|
525
|
+
</View>
|
|
526
|
+
) : null}
|
|
527
|
+
</View>
|
|
528
|
+
{tooltipContent !== undefined && tooltipAnchor !== undefined ? (
|
|
529
|
+
<ChartTooltipLayer
|
|
530
|
+
content={tooltipContent}
|
|
531
|
+
noValueText={text.noValue}
|
|
532
|
+
slot="pie-chart"
|
|
533
|
+
anchorX={tooltipAnchor.x}
|
|
534
|
+
anchorY={tooltipAnchor.y}
|
|
535
|
+
width={dimension}
|
|
536
|
+
height={dimension}
|
|
537
|
+
/>
|
|
538
|
+
) : null}
|
|
539
|
+
</View>
|
|
540
|
+
);
|
|
541
|
+
|
|
542
|
+
return (
|
|
543
|
+
<View
|
|
544
|
+
ref={ref}
|
|
545
|
+
className={cn(pieChartRootVariants({ placement: legendPlacement }), className)}
|
|
546
|
+
style={style}
|
|
547
|
+
accessible={false}
|
|
548
|
+
{...props}
|
|
549
|
+
{...mergeDataAttributes(props, dataAttributes({ slot: 'pie-chart' }))}
|
|
550
|
+
>
|
|
551
|
+
{legendFirst ? null : chart}
|
|
552
|
+
{hideLegend ? null : (
|
|
553
|
+
<View
|
|
554
|
+
className={pieChartLegendVariants({ layout: legendMode })}
|
|
555
|
+
{...dataAttributes({ slot: 'pie-chart-legend' })}
|
|
556
|
+
>
|
|
557
|
+
{slices.map((slice, visibleIndex) => {
|
|
558
|
+
const isSelected = selectedIndex === slice.index;
|
|
559
|
+
const isList = legendMode === 'list';
|
|
560
|
+
const share = formatShare(slice.percentage, context('label', slice.index));
|
|
561
|
+
const valueText = formatValue(slice.value, context('label', slice.index));
|
|
562
|
+
const a11yShare = formatShare(slice.percentage, context('a11y', slice.index));
|
|
563
|
+
const itemLabel = isList
|
|
564
|
+
? `${slice.label}, ${formatValue(slice.value, context('a11y', slice.index))}, ${a11yShare}`
|
|
565
|
+
: `${slice.label}, ${a11yShare}`;
|
|
566
|
+
const swatch = (
|
|
567
|
+
<View className="size-2.5 rounded-full" style={{ backgroundColor: slice.fill }} />
|
|
568
|
+
);
|
|
569
|
+
const labelClass = pieChartLegendLabelVariants({
|
|
570
|
+
selected: isSelected,
|
|
571
|
+
layout: legendMode,
|
|
572
|
+
});
|
|
573
|
+
const body = isList ? (
|
|
574
|
+
<>
|
|
575
|
+
{swatch}
|
|
576
|
+
<Text className={labelClass} numberOfLines={1}>
|
|
577
|
+
{slice.label}
|
|
578
|
+
</Text>
|
|
579
|
+
<View className="flex-row items-center gap-3">
|
|
580
|
+
<Text className={pieChartLegendMetaVariants()}>{valueText}</Text>
|
|
581
|
+
<Text className={pieChartLegendShareVariants()}>{share}</Text>
|
|
582
|
+
</View>
|
|
583
|
+
</>
|
|
584
|
+
) : (
|
|
585
|
+
<>
|
|
586
|
+
{swatch}
|
|
587
|
+
<Text className={labelClass} numberOfLines={1}>
|
|
588
|
+
{legendMode === 'names' ? slice.label : `${slice.label} ${share}`}
|
|
589
|
+
</Text>
|
|
590
|
+
</>
|
|
591
|
+
);
|
|
592
|
+
|
|
593
|
+
if (!interactive) {
|
|
594
|
+
return (
|
|
595
|
+
<View
|
|
596
|
+
key={slice.id}
|
|
597
|
+
className={pieChartLegendItemVariants({
|
|
598
|
+
layout: legendMode,
|
|
599
|
+
divided: isList && visibleIndex < slices.length - 1,
|
|
600
|
+
})}
|
|
601
|
+
{...dataAttributes({ slot: 'pie-chart-legend-item' })}
|
|
602
|
+
>
|
|
603
|
+
{body}
|
|
604
|
+
</View>
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
return (
|
|
609
|
+
<Pressable
|
|
610
|
+
key={slice.id}
|
|
611
|
+
accessibilityRole="button"
|
|
612
|
+
accessibilityLabel={itemLabel}
|
|
613
|
+
accessibilityState={{ selected: isSelected }}
|
|
614
|
+
className={pieChartLegendItemVariants({
|
|
615
|
+
layout: legendMode,
|
|
616
|
+
divided: isList && visibleIndex < slices.length - 1,
|
|
617
|
+
interactive: true,
|
|
618
|
+
})}
|
|
619
|
+
onPress={() => {
|
|
620
|
+
toggleSelection(slice.index);
|
|
621
|
+
}}
|
|
622
|
+
onFocus={() => {
|
|
623
|
+
previewSlice(slice.index);
|
|
624
|
+
}}
|
|
625
|
+
onBlur={() => {
|
|
626
|
+
setPreviewIndex(null);
|
|
627
|
+
}}
|
|
628
|
+
onHoverIn={() => {
|
|
629
|
+
previewSlice(slice.index);
|
|
630
|
+
}}
|
|
631
|
+
onHoverOut={() => {
|
|
632
|
+
setPreviewIndex(null);
|
|
633
|
+
}}
|
|
634
|
+
{...dataAttributes({ slot: 'pie-chart-legend-item', selected: isSelected })}
|
|
635
|
+
>
|
|
636
|
+
{body}
|
|
637
|
+
</Pressable>
|
|
638
|
+
);
|
|
639
|
+
})}
|
|
640
|
+
</View>
|
|
641
|
+
)}
|
|
642
|
+
{legendFirst ? chart : null}
|
|
643
|
+
</View>
|
|
644
|
+
);
|
|
645
|
+
},
|
|
646
|
+
);
|
|
647
|
+
|
|
648
|
+
PieChart.displayName = 'PieChart';
|
|
649
|
+
|
|
650
|
+
function anglePoint(
|
|
651
|
+
radii: PieRadii,
|
|
652
|
+
radius: number,
|
|
653
|
+
angleFromTop: number,
|
|
654
|
+
): { x: number; y: number } {
|
|
655
|
+
const radians = ((angleFromTop - 90) * Math.PI) / 180;
|
|
656
|
+
return {
|
|
657
|
+
x: radii.cx + radius * Math.cos(radians),
|
|
658
|
+
y: radii.cy + radius * Math.sin(radians),
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
/** Owns its tween, so a slice keyed by `id` animates from wherever it was last drawn. */
|
|
663
|
+
function AnimatedSlice({
|
|
664
|
+
startAngle,
|
|
665
|
+
endAngle,
|
|
666
|
+
animated,
|
|
667
|
+
radii,
|
|
668
|
+
cornerRadius = 0,
|
|
669
|
+
...pathProps
|
|
670
|
+
}: {
|
|
671
|
+
startAngle: number;
|
|
672
|
+
endAngle: number;
|
|
673
|
+
animated: boolean;
|
|
674
|
+
radii: PieRadii;
|
|
675
|
+
cornerRadius?: number;
|
|
676
|
+
fill: string;
|
|
677
|
+
opacity: number;
|
|
678
|
+
accessible?: boolean;
|
|
679
|
+
accessibilityRole?: 'button';
|
|
680
|
+
accessibilityLabel?: string;
|
|
681
|
+
accessibilityState?: { selected: boolean };
|
|
682
|
+
accessibilityElementsHidden?: boolean;
|
|
683
|
+
importantForAccessibility?: 'auto' | 'yes' | 'no' | 'no-hide-descendants';
|
|
684
|
+
onPress?: () => void;
|
|
685
|
+
onPressIn?: () => void;
|
|
686
|
+
onPressOut?: () => void;
|
|
687
|
+
onMouseEnter?: () => void;
|
|
688
|
+
onMouseLeave?: () => void;
|
|
689
|
+
cursor?: 'pointer';
|
|
690
|
+
}) {
|
|
691
|
+
const { from, to, progress } = useChartMorph([startAngle, endAngle], ENTRY_ANGLES, animated);
|
|
692
|
+
const { cx, cy, outerRadius, innerRadius } = radii;
|
|
693
|
+
const animatedProps = useAnimatedProps(() => {
|
|
694
|
+
const angles = lerpVector(from.get(), to.get(), progress.get());
|
|
695
|
+
return {
|
|
696
|
+
d: slicePath({
|
|
697
|
+
cx,
|
|
698
|
+
cy,
|
|
699
|
+
outerRadius,
|
|
700
|
+
innerRadius,
|
|
701
|
+
startAngle: angles[0] ?? 0,
|
|
702
|
+
endAngle: angles[1] ?? 0,
|
|
703
|
+
cornerRadius,
|
|
704
|
+
}),
|
|
705
|
+
};
|
|
706
|
+
}, [from, to, progress, cx, cy, outerRadius, innerRadius, cornerRadius]);
|
|
707
|
+
|
|
708
|
+
return (
|
|
709
|
+
<AnimatedPath
|
|
710
|
+
animatedProps={animatedProps}
|
|
711
|
+
{...pathProps}
|
|
712
|
+
{...({ focusable: false, tabIndex: -1 } as object)}
|
|
713
|
+
/>
|
|
714
|
+
);
|
|
715
|
+
}
|