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,204 +0,0 @@
1
- /**
2
- * CandlestickChart Widget - For stock/financial data
3
- */
4
- import React, { memo, useMemo } from 'react';
5
- import { View, Text as RNText, StyleSheet } from 'react-native';
6
- import Svg, { Rect, Line as SvgLine } from 'react-native-svg';
7
- import { useWidgetDimensions, useWidgetTheme, normalize, formatTimeLabel, reduceLabels } from '../../core';
8
- import { CandlestickChartWidgetProps } from './types';
9
-
10
- export const CandlestickChart = memo<CandlestickChartWidgetProps>(({
11
- data: widgetData,
12
- width,
13
- height,
14
- loading = false,
15
- theme: themeOverride,
16
- showXAxis = true,
17
- showYAxis = true,
18
- showGrid = true,
19
- candleWidth = 8,
20
- candleSpacing = 4,
21
- upColor,
22
- downColor,
23
- maxCandles = 30,
24
- testID,
25
- }) => {
26
- const theme = useWidgetTheme(themeOverride);
27
- const dimensions = useWidgetDimensions(width, height, 350, 300);
28
-
29
- if (loading) {
30
- return (
31
- <View style={[styles.container, { width: dimensions.width, height: dimensions.height, backgroundColor: theme.colors.surface, borderRadius: theme.radius.md }]} testID={`${testID}-loading`}>
32
- <RNText style={[styles.loadingText, { color: theme.colors.textSecondary }]}>Loading...</RNText>
33
- </View>
34
- );
35
- }
36
-
37
- if (!widgetData || !widgetData.data || widgetData.data.length === 0) {
38
- return (
39
- <View style={[styles.container, { width: dimensions.width, height: dimensions.height, backgroundColor: theme.colors.surface, borderRadius: theme.radius.md }]} testID={`${testID}-empty`}>
40
- <RNText style={[styles.emptyText, { color: theme.colors.textSecondary }]}>No data</RNText>
41
- </View>
42
- );
43
- }
44
-
45
- const { data, title, timeInterval = 'day' } = widgetData;
46
- const displayData = data.slice(-maxCandles); // Show last N candles
47
-
48
- const padding = theme.spacing.md;
49
- const titleHeight = title ? theme.fontScale.md + theme.spacing.sm : 0;
50
- const xAxisHeight = showXAxis ? 30 : 0;
51
- const yAxisWidth = showYAxis ? 40 : 0;
52
-
53
- const chartWidth = dimensions.width - padding * 2 - yAxisWidth;
54
- const chartHeight = dimensions.height - padding * 2 - titleHeight - xAxisHeight;
55
-
56
- const colorUp = upColor || theme.colors.chartPositive;
57
- const colorDown = downColor || theme.colors.chartNegative;
58
-
59
- // Find min/max prices
60
- const { minPrice, maxPrice } = useMemo(() => {
61
- const lows = displayData.map(d => d.low);
62
- const highs = displayData.map(d => d.high);
63
- return {
64
- minPrice: Math.min(...lows),
65
- maxPrice: Math.max(...highs),
66
- };
67
- }, [displayData]);
68
-
69
- const yAxisLabels = useMemo(() => {
70
- const range = maxPrice - minPrice;
71
- const step = range / 4;
72
- return Array.from({ length: 5 }, (_, i) => ({
73
- value: minPrice + i * step,
74
- y: chartHeight - (i * chartHeight) / 4,
75
- }));
76
- }, [minPrice, maxPrice, chartHeight]);
77
-
78
- const xLabels = useMemo(() => {
79
- return displayData.map(d => formatTimeLabel(d.date, timeInterval));
80
- }, [displayData, timeInterval]);
81
-
82
- const xAxisLabelsReduced = useMemo(() => {
83
- return reduceLabels(xLabels, 6);
84
- }, [xLabels]);
85
-
86
- const totalCandleWidth = candleWidth + candleSpacing;
87
- const chartStartX = Math.max(0, (chartWidth - displayData.length * totalCandleWidth) / 2);
88
-
89
- const candles = useMemo(() => {
90
- return displayData.map((point, index) => {
91
- const isUp = point.close >= point.open;
92
- const color = isUp ? colorUp : colorDown;
93
-
94
- // Normalize prices to chart height
95
- const highY = chartHeight - normalize(point.high, minPrice, maxPrice) * chartHeight;
96
- const lowY = chartHeight - normalize(point.low, minPrice, maxPrice) * chartHeight;
97
- const openY = chartHeight - normalize(point.open, minPrice, maxPrice) * chartHeight;
98
- const closeY = chartHeight - normalize(point.close, minPrice, maxPrice) * chartHeight;
99
-
100
- const candleX = chartStartX + index * totalCandleWidth;
101
- const candleCenterX = candleX + candleWidth / 2;
102
-
103
- // Candle body
104
- const bodyTop = Math.min(openY, closeY);
105
- const bodyHeight = Math.max(Math.abs(closeY - openY), 1); // Minimum 1px
106
-
107
- return {
108
- // High-Low line (wick)
109
- wickX: candleCenterX,
110
- wickTop: highY,
111
- wickBottom: lowY,
112
-
113
- // Body rectangle
114
- bodyX: candleX,
115
- bodyY: bodyTop,
116
- bodyWidth: candleWidth,
117
- bodyHeight: bodyHeight,
118
-
119
- color,
120
- isUp,
121
- };
122
- });
123
- }, [displayData, chartHeight, minPrice, maxPrice, colorUp, colorDown, candleWidth, totalCandleWidth, chartStartX]);
124
-
125
- return (
126
- <View style={[styles.wrapper, { width: dimensions.width, height: dimensions.height, backgroundColor: theme.colors.surface, borderRadius: theme.radius.md, padding }]} testID={testID}>
127
- {title && (
128
- <RNText style={[styles.title, { color: theme.colors.text, fontSize: theme.fontScale.md, fontWeight: 'bold', marginBottom: theme.spacing.sm }]}>
129
- {title}
130
- </RNText>
131
- )}
132
-
133
- <View style={styles.chartRow}>
134
- {showYAxis && (
135
- <View style={[styles.yAxis, { width: yAxisWidth }]}>
136
- {yAxisLabels.map((label, index) => (
137
- <RNText key={`y-${index}`} style={[styles.yAxisLabel, { color: theme.colors.textSecondary, fontSize: theme.fontScale.xs, top: label.y - 6 }]}>
138
- {label.value.toFixed(0)}
139
- </RNText>
140
- ))}
141
- </View>
142
- )}
143
-
144
- <View>
145
- <Svg width={chartWidth} height={chartHeight}>
146
- {showGrid && yAxisLabels.map((label, index) => (
147
- <SvgLine key={`grid-${index}`} x1={0} y1={label.y} x2={chartWidth} y2={label.y} stroke={theme.colors.borderLight} strokeWidth={1} />
148
- ))}
149
-
150
- {candles.map((candle, index) => (
151
- <React.Fragment key={`candle-${index}`}>
152
- {/* Wick (high-low line) */}
153
- <SvgLine
154
- x1={candle.wickX}
155
- y1={candle.wickTop}
156
- x2={candle.wickX}
157
- y2={candle.wickBottom}
158
- stroke={candle.color}
159
- strokeWidth={1}
160
- />
161
-
162
- {/* Body */}
163
- <Rect
164
- x={candle.bodyX}
165
- y={candle.bodyY}
166
- width={candle.bodyWidth}
167
- height={candle.bodyHeight}
168
- fill={candle.color}
169
- stroke={candle.color}
170
- strokeWidth={1}
171
- />
172
- </React.Fragment>
173
- ))}
174
- </Svg>
175
-
176
- {showXAxis && (
177
- <View style={[styles.xAxis, { width: chartWidth }]}>
178
- {xAxisLabelsReduced.map((item, index) => (
179
- <RNText key={`x-${index}`} style={[styles.xAxisLabel, { color: theme.colors.textSecondary, fontSize: theme.fontScale.xs, left: chartStartX + (item.index * totalCandleWidth) - 20 }]}>
180
- {item.label}
181
- </RNText>
182
- ))}
183
- </View>
184
- )}
185
- </View>
186
- </View>
187
- </View>
188
- );
189
- });
190
-
191
- CandlestickChart.displayName = 'CandlestickChart';
192
-
193
- const styles = StyleSheet.create({
194
- wrapper: { justifyContent: 'flex-start', alignItems: 'flex-start' },
195
- container: { justifyContent: 'center', alignItems: 'center' },
196
- loadingText: { fontSize: 16 },
197
- emptyText: { fontSize: 16 },
198
- title: { textAlign: 'center', width: '100%' },
199
- chartRow: { flexDirection: 'row', alignItems: 'flex-start' },
200
- yAxis: { position: 'relative', marginRight: 8 },
201
- yAxisLabel: { position: 'absolute', right: 0, textAlign: 'right' },
202
- xAxis: { position: 'relative', height: 30, marginTop: 4 },
203
- xAxisLabel: { position: 'absolute', width: 40, textAlign: 'center' },
204
- });
@@ -1,2 +0,0 @@
1
- export { CandlestickChart } from './CandlestickChart';
2
- export type { CandlestickChartWidgetProps, CandlestickChartData, CandlestickDataPoint } from './types';
@@ -1,29 +0,0 @@
1
- /**
2
- * CandlestickChart Widget types
3
- */
4
- import { BaseWidgetProps, TimeInterval } from '../../core';
5
-
6
- export interface CandlestickDataPoint {
7
- date: Date;
8
- open: number;
9
- high: number;
10
- low: number;
11
- close: number;
12
- }
13
-
14
- export interface CandlestickChartData {
15
- data: CandlestickDataPoint[];
16
- title?: string;
17
- timeInterval?: TimeInterval;
18
- }
19
-
20
- export interface CandlestickChartWidgetProps extends BaseWidgetProps<CandlestickChartData> {
21
- showXAxis?: boolean;
22
- showYAxis?: boolean;
23
- showGrid?: boolean;
24
- candleWidth?: number;
25
- candleSpacing?: number;
26
- upColor?: string;
27
- downColor?: string;
28
- maxCandles?: number;
29
- }
@@ -1,172 +0,0 @@
1
- /**
2
- * FunnelChart Widget - Conversion funnel visualization
3
- */
4
- import React, { memo, useMemo } from 'react';
5
- import { View, Text as RNText, StyleSheet } from 'react-native';
6
- import Svg, { Polygon } from 'react-native-svg';
7
- import { useWidgetDimensions, useWidgetTheme, normalize } from '../../core';
8
- import { FunnelChartWidgetProps } from './types';
9
-
10
- export const FunnelChart = memo<FunnelChartWidgetProps>(({
11
- data: widgetData,
12
- width,
13
- height,
14
- loading = false,
15
- theme: themeOverride,
16
- showLabels = true,
17
- showValues = true,
18
- showPercentages = true,
19
- stageSpacing = 8,
20
- testID,
21
- }) => {
22
- const theme = useWidgetTheme(themeOverride);
23
- const dimensions = useWidgetDimensions(width, height, 300, 400);
24
-
25
- if (loading) {
26
- return (
27
- <View style={[styles.container, { width: dimensions.width, height: dimensions.height, backgroundColor: theme.colors.surface, borderRadius: theme.radius.md }]} testID={`${testID}-loading`}>
28
- <RNText style={[styles.loadingText, { color: theme.colors.textSecondary }]}>Loading...</RNText>
29
- </View>
30
- );
31
- }
32
-
33
- if (!widgetData || !widgetData.stages || widgetData.stages.length === 0) {
34
- return (
35
- <View style={[styles.container, { width: dimensions.width, height: dimensions.height, backgroundColor: theme.colors.surface, borderRadius: theme.radius.md }]} testID={`${testID}-empty`}>
36
- <RNText style={[styles.emptyText, { color: theme.colors.textSecondary }]}>No data</RNText>
37
- </View>
38
- );
39
- }
40
-
41
- const { stages, title } = widgetData;
42
- const padding = theme.spacing.md;
43
- const titleHeight = title ? theme.fontScale.md + theme.spacing.sm : 0;
44
- const labelWidth = showLabels ? 100 : 0;
45
-
46
- const chartWidth = dimensions.width - padding * 2 - labelWidth;
47
- const chartHeight = dimensions.height - padding * 2 - titleHeight;
48
-
49
- const maxValue = stages[0].value;
50
- const totalSpacing = (stages.length - 1) * stageSpacing;
51
- const stageHeight = (chartHeight - totalSpacing) / stages.length;
52
-
53
- const funnelStages = useMemo(() => {
54
- return stages.map((stage, index) => {
55
- const normalizedValue = normalize(stage.value, 0, maxValue);
56
- const topWidth = normalizedValue * chartWidth;
57
- const bottomWidth = index < stages.length - 1
58
- ? normalize(stages[index + 1].value, 0, maxValue) * chartWidth
59
- : topWidth * 0.7; // Last stage tapers to 70%
60
-
61
- const y = index * (stageHeight + stageSpacing);
62
- const centerX = chartWidth / 2;
63
-
64
- // Create trapezoid points (top-left, top-right, bottom-right, bottom-left)
65
- const points = `
66
- ${centerX - topWidth / 2},${y}
67
- ${centerX + topWidth / 2},${y}
68
- ${centerX + bottomWidth / 2},${y + stageHeight}
69
- ${centerX - bottomWidth / 2},${y + stageHeight}
70
- `;
71
-
72
- const percentage = ((stage.value / maxValue) * 100).toFixed(1);
73
- const conversionRate = index > 0
74
- ? ((stage.value / stages[index - 1].value) * 100).toFixed(1)
75
- : '100.0';
76
-
77
- return {
78
- points,
79
- color: stage.color || getDefaultColor(index, theme),
80
- label: stage.label,
81
- value: stage.value,
82
- percentage,
83
- conversionRate,
84
- y: y + stageHeight / 2,
85
- };
86
- });
87
- }, [stages, chartWidth, chartHeight, stageHeight, stageSpacing, maxValue, theme]);
88
-
89
- return (
90
- <View style={[styles.wrapper, { width: dimensions.width, height: dimensions.height, backgroundColor: theme.colors.surface, borderRadius: theme.radius.md, padding }]} testID={testID}>
91
- {title && (
92
- <RNText style={[styles.title, { color: theme.colors.text, fontSize: theme.fontScale.md, fontWeight: 'bold', marginBottom: theme.spacing.sm }]}>
93
- {title}
94
- </RNText>
95
- )}
96
-
97
- <View style={styles.chartRow}>
98
- <View>
99
- <Svg width={chartWidth} height={chartHeight}>
100
- {funnelStages.map((stage, index) => (
101
- <Polygon
102
- key={`stage-${index}`}
103
- points={stage.points}
104
- fill={stage.color}
105
- opacity={0.9}
106
- />
107
- ))}
108
- </Svg>
109
- </View>
110
-
111
- {showLabels && (
112
- <View style={[styles.labelsContainer, { width: labelWidth, height: chartHeight }]}>
113
- {funnelStages.map((stage, index) => (
114
- <View
115
- key={`label-${index}`}
116
- style={[styles.labelItem, { top: stage.y - 20, height: 40 }]}
117
- >
118
- <RNText
119
- style={[styles.label, { color: theme.colors.text, fontSize: theme.fontScale.sm }]}
120
- numberOfLines={1}
121
- >
122
- {stage.label}
123
- </RNText>
124
- {showValues && (
125
- <RNText
126
- style={[styles.value, { color: theme.colors.textSecondary, fontSize: theme.fontScale.xs }]}
127
- >
128
- {stage.value.toLocaleString()}
129
- </RNText>
130
- )}
131
- {showPercentages && (
132
- <RNText
133
- style={[styles.percentage, { color: theme.colors.textSecondary, fontSize: theme.fontScale.xs }]}
134
- >
135
- {index > 0 ? `${stage.conversionRate}%` : '100%'}
136
- </RNText>
137
- )}
138
- </View>
139
- ))}
140
- </View>
141
- )}
142
- </View>
143
- </View>
144
- );
145
- });
146
-
147
- FunnelChart.displayName = 'FunnelChart';
148
-
149
- function getDefaultColor(index: number, theme: any): string {
150
- const colors = [
151
- theme.colors.chartPrimary,
152
- theme.colors.chartSecondary,
153
- theme.colors.chartTertiary,
154
- theme.colors.chartQuaternary,
155
- theme.colors.chartPositive,
156
- ];
157
- return colors[index % colors.length];
158
- }
159
-
160
- const styles = StyleSheet.create({
161
- wrapper: { justifyContent: 'flex-start', alignItems: 'flex-start' },
162
- container: { justifyContent: 'center', alignItems: 'center' },
163
- loadingText: { fontSize: 16 },
164
- emptyText: { fontSize: 16 },
165
- title: { textAlign: 'center', width: '100%' },
166
- chartRow: { flexDirection: 'row', flex: 1 },
167
- labelsContainer: { position: 'relative', marginLeft: 12 },
168
- labelItem: { position: 'absolute', justifyContent: 'center' },
169
- label: { fontWeight: '600', marginBottom: 2 },
170
- value: { marginBottom: 1 },
171
- percentage: { fontWeight: '500' },
172
- });
@@ -1,2 +0,0 @@
1
- export { FunnelChart } from './FunnelChart';
2
- export type { FunnelChartWidgetProps, FunnelChartData, FunnelStage } from './types';
@@ -1,22 +0,0 @@
1
- /**
2
- * FunnelChart Widget types
3
- */
4
- import { BaseWidgetProps } from '../../core';
5
-
6
- export interface FunnelStage {
7
- label: string;
8
- value: number;
9
- color?: string;
10
- }
11
-
12
- export interface FunnelChartData {
13
- stages: FunnelStage[];
14
- title?: string;
15
- }
16
-
17
- export interface FunnelChartWidgetProps extends BaseWidgetProps<FunnelChartData> {
18
- showLabels?: boolean;
19
- showValues?: boolean;
20
- showPercentages?: boolean;
21
- stageSpacing?: number;
22
- }
@@ -1,235 +0,0 @@
1
- /**
2
- * Gauge Widget - Show progress toward a goal
3
- * Demonstrates SVG + math separation
4
- */
5
- import React, { memo, useMemo } from 'react';
6
- import { View, Text as RNText, StyleSheet } from 'react-native';
7
- import Svg from 'react-native-svg';
8
- import {
9
- useWidgetDimensions,
10
- useWidgetTheme,
11
- clamp,
12
- interpolate,
13
- normalize,
14
- createFontSizeCalculator,
15
- } from '../../core';
16
- import {
17
- createArcPath,
18
- AnimatedPath,
19
- Text,
20
- formatNumber,
21
- } from '../../renderer-svg';
22
- import { GaugeWidgetProps } from './types';
23
-
24
- /**
25
- * Gauge Widget Component
26
- */
27
- export const Gauge = memo<GaugeWidgetProps>(({
28
- data,
29
- width,
30
- height,
31
- loading = false,
32
- theme: themeOverride,
33
- animated = true,
34
- startAngle = -120,
35
- endAngle = 120,
36
- thickness = 12,
37
- showValue = true,
38
- showLabel = true,
39
- fontSize: fontConfig,
40
- testID,
41
- }) => {
42
- const theme = useWidgetTheme(themeOverride);
43
- const dimensions = useWidgetDimensions(width, height, 200, 200);
44
-
45
- // Create responsive font size calculator
46
- const fontSizes = useMemo(
47
- () => createFontSizeCalculator(dimensions.width, dimensions.height, fontConfig),
48
- [dimensions.width, dimensions.height, fontConfig]
49
- );
50
-
51
- // Handle states
52
- if (loading) {
53
- return (
54
- <View
55
- style={[
56
- styles.container,
57
- {
58
- width: dimensions.width,
59
- height: dimensions.height,
60
- backgroundColor: theme.colors.surface,
61
- borderRadius: theme.radius.md,
62
- },
63
- ]}
64
- testID={`${testID}-loading`}
65
- >
66
- <RNText style={[styles.loadingText, { color: theme.colors.textSecondary }]}>
67
- Loading...
68
- </RNText>
69
- </View>
70
- );
71
- }
72
-
73
- if (!data) {
74
- return (
75
- <View
76
- style={[
77
- styles.container,
78
- {
79
- width: dimensions.width,
80
- height: dimensions.height,
81
- backgroundColor: theme.colors.surface,
82
- borderRadius: theme.radius.md,
83
- },
84
- ]}
85
- testID={`${testID}-empty`}
86
- >
87
- <RNText style={[styles.emptyText, { color: theme.colors.textSecondary }]}>
88
- No data
89
- </RNText>
90
- </View>
91
- );
92
- }
93
-
94
- const { value, max, label, unit = '' } = data;
95
- const normalizedValue = clamp(value, 0, max);
96
-
97
- // Calculate dimensions
98
- const size = Math.min(dimensions.width, dimensions.height);
99
- const center = size / 2;
100
- const radius = (size - thickness - theme.spacing.md * 2) / 2;
101
-
102
- // Calculate progress (no animation for Expo Go)
103
- const progressValue = normalize(normalizedValue, 0, max);
104
- const currentAngle = interpolate(progressValue, [0, 1], [startAngle, endAngle]);
105
-
106
- // Background arc path (full gauge)
107
- const backgroundArcPath = useMemo(
108
- () =>
109
- createArcPath({
110
- cx: center,
111
- cy: center,
112
- radius,
113
- startAngle,
114
- endAngle,
115
- }),
116
- [center, radius, startAngle, endAngle]
117
- );
118
-
119
- // Foreground arc path
120
- const foregroundArcPath = useMemo(
121
- () =>
122
- createArcPath({
123
- cx: center,
124
- cy: center,
125
- radius,
126
- startAngle,
127
- endAngle: currentAngle,
128
- }),
129
- [center, radius, startAngle, currentAngle]
130
- );
131
-
132
- // Display value
133
- const displayValue = useMemo(
134
- () => `${formatNumber(normalizedValue, 'compact')}${unit}`,
135
- [normalizedValue, unit]
136
- );
137
-
138
- const displayMax = useMemo(
139
- () => `/ ${formatNumber(max, 'compact')}${unit}`,
140
- [max, unit]
141
- );
142
-
143
- return (
144
- <View
145
- style={[
146
- styles.container,
147
- {
148
- width: dimensions.width,
149
- height: dimensions.height,
150
- backgroundColor: theme.colors.surface,
151
- borderRadius: theme.radius.md,
152
- padding: theme.spacing.md,
153
- },
154
- ]}
155
- testID={testID}
156
- >
157
- <Svg width={size} height={size}>
158
- {/* Background arc */}
159
- <AnimatedPath
160
- d={backgroundArcPath}
161
- stroke={theme.colors.borderLight}
162
- strokeWidth={thickness}
163
- strokeLinecap="round"
164
- fill="transparent"
165
- />
166
-
167
- {/* Foreground arc */}
168
- <AnimatedPath
169
- d={foregroundArcPath}
170
- stroke={theme.colors.chartPrimary}
171
- strokeWidth={thickness}
172
- strokeLinecap="round"
173
- fill="transparent"
174
- />
175
-
176
- {/* Center value text */}
177
- {showValue && (
178
- <>
179
- <Text
180
- x={center}
181
- y={center - 8}
182
- text={displayValue}
183
- fontSize={fontSizes.value(theme.fontScale.xl)}
184
- fontWeight="bold"
185
- fill={theme.colors.text}
186
- />
187
- <Text
188
- x={center}
189
- y={center + 16}
190
- text={displayMax}
191
- fontSize={fontSizes.secondary(theme.fontScale.sm)}
192
- fill={theme.colors.textSecondary}
193
- />
194
- </>
195
- )}
196
- </Svg>
197
-
198
- {/* Label */}
199
- {showLabel && label && (
200
- <RNText
201
- style={[
202
- styles.label,
203
- {
204
- color: theme.colors.textSecondary,
205
- fontSize: fontSizes.label(theme.fontScale.sm),
206
- marginTop: theme.spacing.sm,
207
- },
208
- ]}
209
- >
210
- {label}
211
- </RNText>
212
- )}
213
- </View>
214
- );
215
- });
216
-
217
- Gauge.displayName = 'Gauge';
218
-
219
- const styles = StyleSheet.create({
220
- container: {
221
- justifyContent: 'center',
222
- alignItems: 'center',
223
- },
224
- loadingText: {
225
- fontSize: 16,
226
- },
227
- emptyText: {
228
- fontSize: 16,
229
- },
230
- label: {
231
- textAlign: 'center',
232
- textTransform: 'uppercase',
233
- letterSpacing: 0.5,
234
- },
235
- });
@@ -1,5 +0,0 @@
1
- /**
2
- * Gauge Widget exports
3
- */
4
- export { Gauge } from './Gauge';
5
- export type { GaugeWidgetProps, GaugeData } from './types';
@@ -1,19 +0,0 @@
1
- /**
2
- * Gauge Widget types
3
- */
4
- import { BaseWidgetProps } from '../../core/types';
5
-
6
- export interface GaugeData {
7
- value: number;
8
- max: number;
9
- label?: string;
10
- unit?: string;
11
- }
12
-
13
- export interface GaugeWidgetProps extends BaseWidgetProps<GaugeData> {
14
- startAngle?: number;
15
- endAngle?: number;
16
- thickness?: number;
17
- showValue?: boolean;
18
- showLabel?: boolean;
19
- }