react-native-metrify 0.1.0-alpha.1 → 0.1.0-alpha.3

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 (101) hide show
  1. package/README.md +111 -51
  2. package/package.json +1 -2
  3. package/src/core/animation/index.ts +0 -113
  4. package/src/core/animation/index.web.ts +0 -112
  5. package/src/core/hooks/index.ts +0 -66
  6. package/src/core/index.ts +0 -26
  7. package/src/core/layout/index.ts +0 -101
  8. package/src/core/math/index.ts +0 -72
  9. package/src/core/package.json +0 -13
  10. package/src/core/theme/ThemeProvider.tsx +0 -36
  11. package/src/core/theme/index.ts +0 -5
  12. package/src/core/theme/themes.ts +0 -132
  13. package/src/core/types/index.ts +0 -164
  14. package/src/core/utils/responsive.ts +0 -203
  15. package/src/core/utils/time.ts +0 -100
  16. package/src/index.ts +0 -13
  17. package/src/renderer-svg/adapters/index.ts +0 -84
  18. package/src/renderer-svg/index.ts +0 -8
  19. package/src/renderer-svg/package.json +0 -17
  20. package/src/renderer-svg/paths/arc.ts +0 -93
  21. package/src/renderer-svg/paths/index.ts +0 -6
  22. package/src/renderer-svg/paths/line.ts +0 -83
  23. package/src/renderer-svg/paths/rect.ts +0 -80
  24. package/src/renderer-svg/primitives/AnimatedCircle.tsx +0 -48
  25. package/src/renderer-svg/primitives/AnimatedPath.tsx +0 -48
  26. package/src/renderer-svg/primitives/Text.tsx +0 -73
  27. package/src/renderer-svg/primitives/index.ts +0 -6
  28. package/src/widgets/AreaChart/AreaChart.tsx +0 -213
  29. package/src/widgets/AreaChart/index.ts +0 -2
  30. package/src/widgets/AreaChart/types.ts +0 -34
  31. package/src/widgets/BarChart/BarChart.tsx +0 -249
  32. package/src/widgets/BarChart/index.ts +0 -10
  33. package/src/widgets/BarChart/types.ts +0 -27
  34. package/src/widgets/BoxPlot/BoxPlot.tsx +0 -252
  35. package/src/widgets/BoxPlot/index.ts +0 -2
  36. package/src/widgets/BoxPlot/types.ts +0 -27
  37. package/src/widgets/BubbleChart/BubbleChart.tsx +0 -175
  38. package/src/widgets/BubbleChart/index.ts +0 -2
  39. package/src/widgets/BubbleChart/types.ts +0 -33
  40. package/src/widgets/CandlestickChart/CandlestickChart.tsx +0 -204
  41. package/src/widgets/CandlestickChart/index.ts +0 -2
  42. package/src/widgets/CandlestickChart/types.ts +0 -29
  43. package/src/widgets/FunnelChart/FunnelChart.tsx +0 -172
  44. package/src/widgets/FunnelChart/index.ts +0 -2
  45. package/src/widgets/FunnelChart/types.ts +0 -22
  46. package/src/widgets/Gauge/Gauge.tsx +0 -235
  47. package/src/widgets/Gauge/index.ts +0 -5
  48. package/src/widgets/Gauge/types.ts +0 -19
  49. package/src/widgets/GroupedBarChart/GroupedBarChart.tsx +0 -190
  50. package/src/widgets/GroupedBarChart/index.ts +0 -2
  51. package/src/widgets/GroupedBarChart/types.ts +0 -30
  52. package/src/widgets/Heatmap/Heatmap.tsx +0 -216
  53. package/src/widgets/Heatmap/index.ts +0 -2
  54. package/src/widgets/Heatmap/types.ts +0 -27
  55. package/src/widgets/Histogram/Histogram.tsx +0 -173
  56. package/src/widgets/Histogram/index.ts +0 -2
  57. package/src/widgets/Histogram/types.ts +0 -18
  58. package/src/widgets/HorizontalBarChart/HorizontalBarChart.tsx +0 -125
  59. package/src/widgets/HorizontalBarChart/index.ts +0 -2
  60. package/src/widgets/HorizontalBarChart/types.ts +0 -23
  61. package/src/widgets/KPI/KPI.tsx +0 -222
  62. package/src/widgets/KPI/index.ts +0 -5
  63. package/src/widgets/KPI/types.ts +0 -19
  64. package/src/widgets/LineChart/LineChart.tsx +0 -364
  65. package/src/widgets/LineChart/index.ts +0 -10
  66. package/src/widgets/LineChart/types.ts +0 -34
  67. package/src/widgets/MultiLineSparkline/MultiLineSparkline.tsx +0 -234
  68. package/src/widgets/MultiLineSparkline/index.ts +0 -10
  69. package/src/widgets/MultiLineSparkline/types.ts +0 -25
  70. package/src/widgets/PieChart/PieChart.tsx +0 -275
  71. package/src/widgets/PieChart/index.ts +0 -10
  72. package/src/widgets/PieChart/types.ts +0 -26
  73. package/src/widgets/Progress/Progress.tsx +0 -201
  74. package/src/widgets/Progress/index.ts +0 -5
  75. package/src/widgets/Progress/types.ts +0 -19
  76. package/src/widgets/RadarChart/RadarChart.tsx +0 -213
  77. package/src/widgets/RadarChart/index.ts +0 -2
  78. package/src/widgets/RadarChart/types.ts +0 -29
  79. package/src/widgets/SankeyDiagram/SankeyDiagram.tsx +0 -272
  80. package/src/widgets/SankeyDiagram/index.ts +0 -2
  81. package/src/widgets/SankeyDiagram/types.ts +0 -29
  82. package/src/widgets/ScatterPlot/ScatterPlot.tsx +0 -167
  83. package/src/widgets/ScatterPlot/index.ts +0 -2
  84. package/src/widgets/ScatterPlot/types.ts +0 -32
  85. package/src/widgets/Sparkline/Sparkline.tsx +0 -203
  86. package/src/widgets/Sparkline/index.ts +0 -5
  87. package/src/widgets/Sparkline/types.ts +0 -18
  88. package/src/widgets/StackedBarChart/StackedBarChart.tsx +0 -181
  89. package/src/widgets/StackedBarChart/index.ts +0 -2
  90. package/src/widgets/StackedBarChart/types.ts +0 -29
  91. package/src/widgets/SunburstChart/SunburstChart.tsx +0 -176
  92. package/src/widgets/SunburstChart/index.ts +0 -2
  93. package/src/widgets/SunburstChart/types.ts +0 -22
  94. package/src/widgets/Treemap/Treemap.tsx +0 -191
  95. package/src/widgets/Treemap/index.ts +0 -2
  96. package/src/widgets/Treemap/types.ts +0 -23
  97. package/src/widgets/WaterfallChart/WaterfallChart.tsx +0 -226
  98. package/src/widgets/WaterfallChart/index.ts +0 -2
  99. package/src/widgets/WaterfallChart/types.ts +0 -26
  100. package/src/widgets/index.ts +0 -40
  101. package/src/widgets/package.json +0 -18
@@ -1,167 +0,0 @@
1
- /**
2
- * ScatterPlot Widget - Points without connecting lines
3
- */
4
- import React, { memo, useMemo } from 'react';
5
- import { View, Text as RNText, StyleSheet } from 'react-native';
6
- import Svg, { Circle, Line as SvgLine } from 'react-native-svg';
7
- import { useWidgetDimensions, useWidgetTheme, normalize } from '../../core';
8
- import { ScatterPlotWidgetProps } from './types';
9
-
10
- export const ScatterPlot = memo<ScatterPlotWidgetProps>(({
11
- data: widgetData,
12
- width,
13
- height,
14
- loading = false,
15
- theme: themeOverride,
16
- showXAxis = true,
17
- showYAxis = true,
18
- showGrid = true,
19
- showLegend = true,
20
- defaultPointSize = 6,
21
- testID,
22
- }) => {
23
- const theme = useWidgetTheme(themeOverride);
24
- const dimensions = useWidgetDimensions(width, height, 350, 250);
25
-
26
- if (loading) {
27
- return (
28
- <View style={[styles.container, { width: dimensions.width, height: dimensions.height, backgroundColor: theme.colors.surface, borderRadius: theme.radius.md }]} testID={`${testID}-loading`}>
29
- <RNText style={[styles.loadingText, { color: theme.colors.textSecondary }]}>Loading...</RNText>
30
- </View>
31
- );
32
- }
33
-
34
- if (!widgetData || !widgetData.series || widgetData.series.length === 0) {
35
- return (
36
- <View style={[styles.container, { width: dimensions.width, height: dimensions.height, backgroundColor: theme.colors.surface, borderRadius: theme.radius.md }]} testID={`${testID}-empty`}>
37
- <RNText style={[styles.emptyText, { color: theme.colors.textSecondary }]}>No data</RNText>
38
- </View>
39
- );
40
- }
41
-
42
- const { series, title } = widgetData;
43
- const padding = theme.spacing.md;
44
- const titleHeight = title ? theme.fontScale.md + theme.spacing.sm : 0;
45
- const legendHeight = showLegend ? 30 : 0;
46
- const xAxisHeight = showXAxis ? 30 : 0;
47
- const yAxisWidth = showYAxis ? 40 : 0;
48
-
49
- const chartWidth = dimensions.width - padding * 2 - yAxisWidth;
50
- const chartHeight = dimensions.height - padding * 2 - titleHeight - legendHeight - xAxisHeight;
51
-
52
- const { globalMinX, globalMaxX, globalMinY, globalMaxY } = useMemo(() => {
53
- let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
54
- series.forEach(s => {
55
- s.data.forEach(point => {
56
- if (point.x < minX) minX = point.x;
57
- if (point.x > maxX) maxX = point.x;
58
- if (point.y < minY) minY = point.y;
59
- if (point.y > maxY) maxY = point.y;
60
- });
61
- });
62
- return { globalMinX: minX, globalMaxX: maxX, globalMinY: minY, globalMaxY: maxY };
63
- }, [series]);
64
-
65
- const yAxisLabels = useMemo(() => {
66
- const range = globalMaxY - globalMinY;
67
- const step = range / 4;
68
- return Array.from({ length: 5 }, (_, i) => ({
69
- value: globalMinY + i * step,
70
- y: chartHeight - (i * chartHeight) / 4,
71
- }));
72
- }, [globalMinY, globalMaxY, chartHeight]);
73
-
74
- const seriesPoints = useMemo(() => {
75
- return series.map(s => {
76
- const points = s.data.map(point => {
77
- const normalizedX = normalize(point.x, globalMinX, globalMaxX);
78
- const normalizedY = normalize(point.y, globalMinY, globalMaxY);
79
- return {
80
- cx: normalizedX * chartWidth,
81
- cy: chartHeight - normalizedY * chartHeight,
82
- label: point.label,
83
- };
84
- });
85
-
86
- return {
87
- points,
88
- color: s.color,
89
- label: s.label,
90
- pointSize: s.pointSize || defaultPointSize,
91
- };
92
- });
93
- }, [series, chartWidth, chartHeight, globalMinX, globalMaxX, globalMinY, globalMaxY, defaultPointSize]);
94
-
95
- return (
96
- <View style={[styles.wrapper, { width: dimensions.width, height: dimensions.height, backgroundColor: theme.colors.surface, borderRadius: theme.radius.md, padding }]} testID={testID}>
97
- {title && (
98
- <RNText style={[styles.title, { color: theme.colors.text, fontSize: theme.fontScale.md, fontWeight: 'bold', marginBottom: theme.spacing.sm }]}>
99
- {title}
100
- </RNText>
101
- )}
102
-
103
- <View style={styles.chartRow}>
104
- {showYAxis && (
105
- <View style={[styles.yAxis, { width: yAxisWidth }]}>
106
- {yAxisLabels.map((label, index) => (
107
- <RNText key={`y-${index}`} style={[styles.yAxisLabel, { color: theme.colors.textSecondary, fontSize: theme.fontScale.xs, top: label.y - 6 }]}>
108
- {label.value.toFixed(0)}
109
- </RNText>
110
- ))}
111
- </View>
112
- )}
113
-
114
- <View>
115
- <Svg width={chartWidth} height={chartHeight}>
116
- {showGrid && yAxisLabels.map((label, index) => (
117
- <SvgLine key={`grid-${index}`} x1={0} y1={label.y} x2={chartWidth} y2={label.y} stroke={theme.colors.borderLight} strokeWidth={1} />
118
- ))}
119
-
120
- {seriesPoints.map((series, seriesIndex) => (
121
- series.points.map((point, pointIndex) => (
122
- <Circle
123
- key={`series-${seriesIndex}-point-${pointIndex}`}
124
- cx={point.cx}
125
- cy={point.cy}
126
- r={series.pointSize}
127
- fill={series.color}
128
- opacity={0.8}
129
- />
130
- ))
131
- ))}
132
- </Svg>
133
- </View>
134
- </View>
135
-
136
- {showLegend && (
137
- <View style={styles.legend}>
138
- {seriesPoints.map((series, index) => (
139
- series.label && (
140
- <View key={`legend-${index}`} style={styles.legendItem}>
141
- <View style={[styles.legendColor, { backgroundColor: series.color }]} />
142
- <RNText style={[styles.legendText, { color: theme.colors.textSecondary, fontSize: theme.fontScale.xs }]}>{series.label}</RNText>
143
- </View>
144
- )
145
- ))}
146
- </View>
147
- )}
148
- </View>
149
- );
150
- });
151
-
152
- ScatterPlot.displayName = 'ScatterPlot';
153
-
154
- const styles = StyleSheet.create({
155
- wrapper: { justifyContent: 'flex-start', alignItems: 'flex-start' },
156
- container: { justifyContent: 'center', alignItems: 'center' },
157
- loadingText: { fontSize: 16 },
158
- emptyText: { fontSize: 16 },
159
- title: { textAlign: 'center', width: '100%' },
160
- chartRow: { flexDirection: 'row', alignItems: 'flex-start' },
161
- yAxis: { position: 'relative', marginRight: 8 },
162
- yAxisLabel: { position: 'absolute', right: 0, textAlign: 'right' },
163
- legend: { flexDirection: 'row', flexWrap: 'wrap', justifyContent: 'center', marginTop: 12, gap: 12 },
164
- legendItem: { flexDirection: 'row', alignItems: 'center', gap: 6 },
165
- legendColor: { width: 12, height: 12, borderRadius: 6 },
166
- legendText: { textTransform: 'uppercase', letterSpacing: 0.5 },
167
- });
@@ -1,2 +0,0 @@
1
- export { ScatterPlot } from './ScatterPlot';
2
- export type { ScatterPlotWidgetProps, ScatterPlotData, ScatterSeries, ScatterDataPoint } from './types';
@@ -1,32 +0,0 @@
1
- /**
2
- * ScatterPlot Widget types
3
- */
4
- import { BaseWidgetProps } from '../../core';
5
-
6
- export interface ScatterDataPoint {
7
- x: number;
8
- y: number;
9
- label?: string;
10
- }
11
-
12
- export interface ScatterSeries {
13
- data: ScatterDataPoint[];
14
- color: string;
15
- label?: string;
16
- pointSize?: number;
17
- }
18
-
19
- export interface ScatterPlotData {
20
- series: ScatterSeries[];
21
- title?: string;
22
- xAxisLabel?: string;
23
- yAxisLabel?: string;
24
- }
25
-
26
- export interface ScatterPlotWidgetProps extends BaseWidgetProps<ScatterPlotData> {
27
- showXAxis?: boolean;
28
- showYAxis?: boolean;
29
- showGrid?: boolean;
30
- showLegend?: boolean;
31
- defaultPointSize?: number;
32
- }
@@ -1,203 +0,0 @@
1
- /**
2
- * Sparkline Widget - Show trend at a glance
3
- * Minimal, no axes, capped data points
4
- */
5
- import React, { memo, useMemo } from 'react';
6
- import { View, Text as RNText, StyleSheet } from 'react-native';
7
- import Svg, { Defs, LinearGradient, Stop } from 'react-native-svg';
8
- import {
9
- useWidgetDimensions,
10
- useWidgetTheme,
11
- useWidgetPadding,
12
- useInnerDimensions,
13
- } from '../../core';
14
- import {
15
- createLinePath,
16
- createAreaPath,
17
- AnimatedPath,
18
- dataToPoints,
19
- } from '../../renderer-svg';
20
- import { SparklineWidgetProps } from './types';
21
-
22
- const MAX_DATA_POINTS = 50;
23
-
24
- /**
25
- * Sparkline Widget Component
26
- */
27
- export const Sparkline = memo<SparklineWidgetProps>(({
28
- data: widgetData,
29
- width,
30
- height,
31
- loading = false,
32
- theme: themeOverride,
33
- animated = true,
34
- style = 'line',
35
- strokeWidth = 2,
36
- showGradient = false,
37
- maxDataPoints = MAX_DATA_POINTS,
38
- testID,
39
- }) => {
40
- const theme = useWidgetTheme(themeOverride);
41
- const dimensions = useWidgetDimensions(width, height, 200, 80);
42
- const padding = useWidgetPadding(theme);
43
- const innerDimensions = useInnerDimensions(
44
- dimensions.width,
45
- dimensions.height,
46
- padding
47
- );
48
-
49
- // Handle states
50
- if (loading) {
51
- return (
52
- <View
53
- style={[
54
- styles.container,
55
- {
56
- width: dimensions.width,
57
- height: dimensions.height,
58
- backgroundColor: theme.colors.surface,
59
- borderRadius: theme.radius.md,
60
- },
61
- ]}
62
- testID={`${testID}-loading`}
63
- >
64
- <RNText style={[styles.loadingText, { color: theme.colors.textSecondary }]}>
65
- Loading...
66
- </RNText>
67
- </View>
68
- );
69
- }
70
-
71
- if (!widgetData || !widgetData.data || widgetData.data.length === 0) {
72
- return (
73
- <View
74
- style={[
75
- styles.container,
76
- {
77
- width: dimensions.width,
78
- height: dimensions.height,
79
- backgroundColor: theme.colors.surface,
80
- borderRadius: theme.radius.md,
81
- },
82
- ]}
83
- testID={`${testID}-empty`}
84
- >
85
- <RNText style={[styles.emptyText, { color: theme.colors.textSecondary }]}>
86
- No data
87
- </RNText>
88
- </View>
89
- );
90
- }
91
-
92
- const { data: rawData, label } = widgetData;
93
-
94
- // Cap data points for performance
95
- const data = useMemo(() => {
96
- if (rawData.length <= maxDataPoints) return rawData;
97
-
98
- const step = Math.ceil(rawData.length / maxDataPoints);
99
- return rawData.filter((_, index) => index % step === 0);
100
- }, [rawData, maxDataPoints]);
101
-
102
- // Convert data to SVG points
103
- const points = useMemo(
104
- () =>
105
- dataToPoints(
106
- data,
107
- innerDimensions.width,
108
- innerDimensions.height,
109
- theme.spacing.xs
110
- ),
111
- [data, innerDimensions.width, innerDimensions.height, theme.spacing.xs]
112
- );
113
-
114
- // Generate paths
115
- const linePath = useMemo(() => createLinePath(points), [points]);
116
-
117
- const areaPath = useMemo(
118
- () => createAreaPath(points, innerDimensions.height - theme.spacing.xs),
119
- [points, innerDimensions.height, theme.spacing.xs]
120
- );
121
-
122
- const svgWidth = dimensions.width;
123
- const svgHeight = dimensions.height - (label ? theme.fontScale.sm + theme.spacing.sm : 0);
124
-
125
- return (
126
- <View
127
- style={[
128
- styles.container,
129
- {
130
- width: dimensions.width,
131
- height: dimensions.height,
132
- backgroundColor: theme.colors.surface,
133
- borderRadius: theme.radius.md,
134
- padding: theme.spacing.md,
135
- },
136
- ]}
137
- testID={testID}
138
- >
139
- <Svg width={svgWidth - theme.spacing.md * 2} height={svgHeight}>
140
- {showGradient && style === 'area' && (
141
- <Defs>
142
- <LinearGradient id="sparklineGradient" x1="0" y1="0" x2="0" y2="1">
143
- <Stop offset="0%" stopColor={theme.colors.chartPrimary} stopOpacity="0.3" />
144
- <Stop offset="100%" stopColor={theme.colors.chartPrimary} stopOpacity="0" />
145
- </LinearGradient>
146
- </Defs>
147
- )}
148
-
149
- {style === 'area' && (
150
- <AnimatedPath
151
- d={areaPath}
152
- fill={showGradient ? 'url(#sparklineGradient)' : theme.colors.chartPrimary}
153
- opacity={showGradient ? 1 : 0.2}
154
- />
155
- )}
156
-
157
- <AnimatedPath
158
- d={linePath}
159
- stroke={theme.colors.chartPrimary}
160
- strokeWidth={strokeWidth}
161
- strokeLinecap="round"
162
- strokeLinejoin="round"
163
- fill="transparent"
164
- />
165
- </Svg>
166
-
167
- {label && (
168
- <RNText
169
- style={[
170
- styles.label,
171
- {
172
- color: theme.colors.textSecondary,
173
- fontSize: theme.fontScale.sm,
174
- marginTop: theme.spacing.xs,
175
- },
176
- ]}
177
- >
178
- {label}
179
- </RNText>
180
- )}
181
- </View>
182
- );
183
- });
184
-
185
- Sparkline.displayName = 'Sparkline';
186
-
187
- const styles = StyleSheet.create({
188
- container: {
189
- justifyContent: 'center',
190
- alignItems: 'center',
191
- },
192
- loadingText: {
193
- fontSize: 16,
194
- },
195
- emptyText: {
196
- fontSize: 16,
197
- },
198
- label: {
199
- textAlign: 'center',
200
- textTransform: 'uppercase',
201
- letterSpacing: 0.5,
202
- },
203
- });
@@ -1,5 +0,0 @@
1
- /**
2
- * Sparkline Widget exports
3
- */
4
- export { Sparkline } from './Sparkline';
5
- export type { SparklineWidgetProps, SparklineData, SparklineStyle } from './types';
@@ -1,18 +0,0 @@
1
- /**
2
- * Sparkline Widget types
3
- */
4
- import { BaseWidgetProps } from '../../core/types';
5
-
6
- export interface SparklineData {
7
- data: number[];
8
- label?: string;
9
- }
10
-
11
- export type SparklineStyle = 'line' | 'area';
12
-
13
- export interface SparklineWidgetProps extends BaseWidgetProps<SparklineData> {
14
- style?: SparklineStyle;
15
- strokeWidth?: number;
16
- showGradient?: boolean;
17
- maxDataPoints?: number;
18
- }
@@ -1,181 +0,0 @@
1
- /**
2
- * StackedBarChart Widget - Multiple values stacked on same bar
3
- */
4
- import React, { memo, useMemo } from 'react';
5
- import { View, Text as RNText, StyleSheet } from 'react-native';
6
- import Svg, { Rect } from 'react-native-svg';
7
- import { useWidgetDimensions, useWidgetTheme, normalize } from '../../core';
8
- import { StackedBarChartWidgetProps } from './types';
9
-
10
- export const StackedBarChart = memo<StackedBarChartWidgetProps>(({
11
- data: widgetData,
12
- width,
13
- height,
14
- loading = false,
15
- theme: themeOverride,
16
- barWidth: customBarWidth,
17
- barSpacing = 8,
18
- showValues = false,
19
- showLabels = true,
20
- showLegend = true,
21
- maxBars = 12,
22
- testID,
23
- }) => {
24
- const theme = useWidgetTheme(themeOverride);
25
- const dimensions = useWidgetDimensions(width, height, 350, 250);
26
-
27
- if (loading) {
28
- return (
29
- <View style={[styles.container, { width: dimensions.width, height: dimensions.height, backgroundColor: theme.colors.surface, borderRadius: theme.radius.md }]} testID={`${testID}-loading`}>
30
- <RNText style={[styles.loadingText, { color: theme.colors.textSecondary }]}>Loading...</RNText>
31
- </View>
32
- );
33
- }
34
-
35
- if (!widgetData || !widgetData.data || widgetData.data.length === 0) {
36
- return (
37
- <View style={[styles.container, { width: dimensions.width, height: dimensions.height, backgroundColor: theme.colors.surface, borderRadius: theme.radius.md }]} testID={`${testID}-empty`}>
38
- <RNText style={[styles.emptyText, { color: theme.colors.textSecondary }]}>No data</RNText>
39
- </View>
40
- );
41
- }
42
-
43
- const { data, title } = widgetData;
44
- const displayData = data.slice(0, maxBars);
45
- const padding = theme.spacing.md;
46
- const titleHeight = title ? theme.fontScale.md + theme.spacing.sm : 0;
47
- const legendHeight = showLegend ? 40 : 0;
48
- const labelHeight = showLabels ? theme.fontScale.sm + theme.spacing.xs : 0;
49
-
50
- const chartHeight = dimensions.height - padding * 2 - titleHeight - legendHeight - labelHeight;
51
- const chartWidth = dimensions.width - padding * 2;
52
-
53
- // Find max total value across all bars
54
- const maxTotalValue = useMemo(() => {
55
- return Math.max(...displayData.map(item =>
56
- item.segments.reduce((sum, seg) => sum + seg.value, 0)
57
- ));
58
- }, [displayData]);
59
-
60
- // Get all unique segment labels for legend
61
- const allSegmentLabels = useMemo(() => {
62
- const labels = new Map<string, string>();
63
- displayData.forEach(item => {
64
- item.segments.forEach(seg => {
65
- labels.set(seg.label, seg.color);
66
- });
67
- });
68
- return Array.from(labels.entries()).map(([label, color]) => ({ label, color }));
69
- }, [displayData]);
70
-
71
- const calculatedBarWidth = customBarWidth ||
72
- (chartWidth - (displayData.length - 1) * barSpacing) / displayData.length;
73
-
74
- const bars = useMemo(() => {
75
- return displayData.map((item, index) => {
76
- const barX = index * (calculatedBarWidth + barSpacing);
77
- const totalValue = item.segments.reduce((sum, seg) => sum + seg.value, 0);
78
-
79
- let cumulativeY = 0;
80
- const segments = item.segments.map(seg => {
81
- const segmentHeight = (seg.value / maxTotalValue) * chartHeight;
82
- const segmentY = chartHeight - cumulativeY - segmentHeight;
83
- cumulativeY += segmentHeight;
84
-
85
- return {
86
- x: barX,
87
- y: segmentY,
88
- width: calculatedBarWidth,
89
- height: segmentHeight,
90
- color: seg.color,
91
- value: seg.value,
92
- label: seg.label,
93
- };
94
- });
95
-
96
- return {
97
- label: item.label,
98
- segments,
99
- totalValue,
100
- };
101
- });
102
- }, [displayData, maxTotalValue, chartHeight, calculatedBarWidth, barSpacing]);
103
-
104
- return (
105
- <View style={[styles.wrapper, { width: dimensions.width, height: dimensions.height, backgroundColor: theme.colors.surface, borderRadius: theme.radius.md, padding }]} testID={testID}>
106
- {title && (
107
- <RNText style={[styles.title, { color: theme.colors.text, fontSize: theme.fontScale.md, fontWeight: 'bold', marginBottom: theme.spacing.sm }]}>
108
- {title}
109
- </RNText>
110
- )}
111
-
112
- <View style={styles.chartContainer}>
113
- <Svg width={chartWidth} height={chartHeight}>
114
- {bars.map((bar, barIndex) => (
115
- bar.segments.map((segment, segmentIndex) => (
116
- <Rect
117
- key={`bar-${barIndex}-seg-${segmentIndex}`}
118
- x={segment.x}
119
- y={segment.y}
120
- width={segment.width}
121
- height={segment.height}
122
- fill={segment.color}
123
- rx={segmentIndex === bar.segments.length - 1 ? theme.radius.sm : 0}
124
- ry={segmentIndex === bar.segments.length - 1 ? theme.radius.sm : 0}
125
- />
126
- ))
127
- ))}
128
- </Svg>
129
-
130
- {showLabels && (
131
- <View style={styles.labelsContainer}>
132
- {bars.map((bar, index) => (
133
- <View
134
- key={`label-${index}`}
135
- style={[styles.labelItem, { width: calculatedBarWidth + barSpacing }]}
136
- >
137
- <RNText
138
- style={[styles.labelText, { color: theme.colors.textSecondary, fontSize: theme.fontScale.xs }]}
139
- numberOfLines={1}
140
- >
141
- {bar.label}
142
- </RNText>
143
- </View>
144
- ))}
145
- </View>
146
- )}
147
- </View>
148
-
149
- {showLegend && (
150
- <View style={styles.legend}>
151
- {allSegmentLabels.map((item, index) => (
152
- <View key={`legend-${index}`} style={styles.legendItem}>
153
- <View style={[styles.legendColor, { backgroundColor: item.color }]} />
154
- <RNText style={[styles.legendText, { color: theme.colors.textSecondary, fontSize: theme.fontScale.xs }]}>
155
- {item.label}
156
- </RNText>
157
- </View>
158
- ))}
159
- </View>
160
- )}
161
- </View>
162
- );
163
- });
164
-
165
- StackedBarChart.displayName = 'StackedBarChart';
166
-
167
- const styles = StyleSheet.create({
168
- wrapper: { justifyContent: 'flex-start', alignItems: 'flex-start' },
169
- container: { justifyContent: 'center', alignItems: 'center' },
170
- loadingText: { fontSize: 16 },
171
- emptyText: { fontSize: 16 },
172
- title: { textAlign: 'center', width: '100%' },
173
- chartContainer: { flex: 1, width: '100%' },
174
- labelsContainer: { flexDirection: 'row', marginTop: 4 },
175
- labelItem: { alignItems: 'center' },
176
- labelText: { textAlign: 'center' },
177
- legend: { flexDirection: 'row', flexWrap: 'wrap', justifyContent: 'center', marginTop: 12, gap: 12 },
178
- legendItem: { flexDirection: 'row', alignItems: 'center', gap: 6 },
179
- legendColor: { width: 12, height: 12, borderRadius: 2 },
180
- legendText: { textTransform: 'uppercase', letterSpacing: 0.5 },
181
- });
@@ -1,2 +0,0 @@
1
- export { StackedBarChart } from './StackedBarChart';
2
- export type { StackedBarChartWidgetProps, StackedBarChartData, StackedBarDataPoint, StackedBarSegment } from './types';
@@ -1,29 +0,0 @@
1
- /**
2
- * StackedBarChart Widget types
3
- */
4
- import { BaseWidgetProps } from '../../core';
5
-
6
- export interface StackedBarSegment {
7
- value: number;
8
- color: string;
9
- label: string;
10
- }
11
-
12
- export interface StackedBarDataPoint {
13
- label: string;
14
- segments: StackedBarSegment[];
15
- }
16
-
17
- export interface StackedBarChartData {
18
- data: StackedBarDataPoint[];
19
- title?: string;
20
- }
21
-
22
- export interface StackedBarChartWidgetProps extends BaseWidgetProps<StackedBarChartData> {
23
- barWidth?: number;
24
- barSpacing?: number;
25
- showValues?: boolean;
26
- showLabels?: boolean;
27
- showLegend?: boolean;
28
- maxBars?: number;
29
- }