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

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 (100) hide show
  1. package/package.json +1 -2
  2. package/src/core/animation/index.ts +0 -113
  3. package/src/core/animation/index.web.ts +0 -112
  4. package/src/core/hooks/index.ts +0 -66
  5. package/src/core/index.ts +0 -26
  6. package/src/core/layout/index.ts +0 -101
  7. package/src/core/math/index.ts +0 -72
  8. package/src/core/package.json +0 -13
  9. package/src/core/theme/ThemeProvider.tsx +0 -36
  10. package/src/core/theme/index.ts +0 -5
  11. package/src/core/theme/themes.ts +0 -132
  12. package/src/core/types/index.ts +0 -164
  13. package/src/core/utils/responsive.ts +0 -203
  14. package/src/core/utils/time.ts +0 -100
  15. package/src/index.ts +0 -13
  16. package/src/renderer-svg/adapters/index.ts +0 -84
  17. package/src/renderer-svg/index.ts +0 -8
  18. package/src/renderer-svg/package.json +0 -17
  19. package/src/renderer-svg/paths/arc.ts +0 -93
  20. package/src/renderer-svg/paths/index.ts +0 -6
  21. package/src/renderer-svg/paths/line.ts +0 -83
  22. package/src/renderer-svg/paths/rect.ts +0 -80
  23. package/src/renderer-svg/primitives/AnimatedCircle.tsx +0 -48
  24. package/src/renderer-svg/primitives/AnimatedPath.tsx +0 -48
  25. package/src/renderer-svg/primitives/Text.tsx +0 -73
  26. package/src/renderer-svg/primitives/index.ts +0 -6
  27. package/src/widgets/AreaChart/AreaChart.tsx +0 -213
  28. package/src/widgets/AreaChart/index.ts +0 -2
  29. package/src/widgets/AreaChart/types.ts +0 -34
  30. package/src/widgets/BarChart/BarChart.tsx +0 -249
  31. package/src/widgets/BarChart/index.ts +0 -10
  32. package/src/widgets/BarChart/types.ts +0 -27
  33. package/src/widgets/BoxPlot/BoxPlot.tsx +0 -252
  34. package/src/widgets/BoxPlot/index.ts +0 -2
  35. package/src/widgets/BoxPlot/types.ts +0 -27
  36. package/src/widgets/BubbleChart/BubbleChart.tsx +0 -175
  37. package/src/widgets/BubbleChart/index.ts +0 -2
  38. package/src/widgets/BubbleChart/types.ts +0 -33
  39. package/src/widgets/CandlestickChart/CandlestickChart.tsx +0 -204
  40. package/src/widgets/CandlestickChart/index.ts +0 -2
  41. package/src/widgets/CandlestickChart/types.ts +0 -29
  42. package/src/widgets/FunnelChart/FunnelChart.tsx +0 -172
  43. package/src/widgets/FunnelChart/index.ts +0 -2
  44. package/src/widgets/FunnelChart/types.ts +0 -22
  45. package/src/widgets/Gauge/Gauge.tsx +0 -235
  46. package/src/widgets/Gauge/index.ts +0 -5
  47. package/src/widgets/Gauge/types.ts +0 -19
  48. package/src/widgets/GroupedBarChart/GroupedBarChart.tsx +0 -190
  49. package/src/widgets/GroupedBarChart/index.ts +0 -2
  50. package/src/widgets/GroupedBarChart/types.ts +0 -30
  51. package/src/widgets/Heatmap/Heatmap.tsx +0 -216
  52. package/src/widgets/Heatmap/index.ts +0 -2
  53. package/src/widgets/Heatmap/types.ts +0 -27
  54. package/src/widgets/Histogram/Histogram.tsx +0 -173
  55. package/src/widgets/Histogram/index.ts +0 -2
  56. package/src/widgets/Histogram/types.ts +0 -18
  57. package/src/widgets/HorizontalBarChart/HorizontalBarChart.tsx +0 -125
  58. package/src/widgets/HorizontalBarChart/index.ts +0 -2
  59. package/src/widgets/HorizontalBarChart/types.ts +0 -23
  60. package/src/widgets/KPI/KPI.tsx +0 -222
  61. package/src/widgets/KPI/index.ts +0 -5
  62. package/src/widgets/KPI/types.ts +0 -19
  63. package/src/widgets/LineChart/LineChart.tsx +0 -364
  64. package/src/widgets/LineChart/index.ts +0 -10
  65. package/src/widgets/LineChart/types.ts +0 -34
  66. package/src/widgets/MultiLineSparkline/MultiLineSparkline.tsx +0 -234
  67. package/src/widgets/MultiLineSparkline/index.ts +0 -10
  68. package/src/widgets/MultiLineSparkline/types.ts +0 -25
  69. package/src/widgets/PieChart/PieChart.tsx +0 -275
  70. package/src/widgets/PieChart/index.ts +0 -10
  71. package/src/widgets/PieChart/types.ts +0 -26
  72. package/src/widgets/Progress/Progress.tsx +0 -201
  73. package/src/widgets/Progress/index.ts +0 -5
  74. package/src/widgets/Progress/types.ts +0 -19
  75. package/src/widgets/RadarChart/RadarChart.tsx +0 -213
  76. package/src/widgets/RadarChart/index.ts +0 -2
  77. package/src/widgets/RadarChart/types.ts +0 -29
  78. package/src/widgets/SankeyDiagram/SankeyDiagram.tsx +0 -272
  79. package/src/widgets/SankeyDiagram/index.ts +0 -2
  80. package/src/widgets/SankeyDiagram/types.ts +0 -29
  81. package/src/widgets/ScatterPlot/ScatterPlot.tsx +0 -167
  82. package/src/widgets/ScatterPlot/index.ts +0 -2
  83. package/src/widgets/ScatterPlot/types.ts +0 -32
  84. package/src/widgets/Sparkline/Sparkline.tsx +0 -203
  85. package/src/widgets/Sparkline/index.ts +0 -5
  86. package/src/widgets/Sparkline/types.ts +0 -18
  87. package/src/widgets/StackedBarChart/StackedBarChart.tsx +0 -181
  88. package/src/widgets/StackedBarChart/index.ts +0 -2
  89. package/src/widgets/StackedBarChart/types.ts +0 -29
  90. package/src/widgets/SunburstChart/SunburstChart.tsx +0 -176
  91. package/src/widgets/SunburstChart/index.ts +0 -2
  92. package/src/widgets/SunburstChart/types.ts +0 -22
  93. package/src/widgets/Treemap/Treemap.tsx +0 -191
  94. package/src/widgets/Treemap/index.ts +0 -2
  95. package/src/widgets/Treemap/types.ts +0 -23
  96. package/src/widgets/WaterfallChart/WaterfallChart.tsx +0 -226
  97. package/src/widgets/WaterfallChart/index.ts +0 -2
  98. package/src/widgets/WaterfallChart/types.ts +0 -26
  99. package/src/widgets/index.ts +0 -40
  100. package/src/widgets/package.json +0 -18
@@ -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
- }
@@ -1,190 +0,0 @@
1
- /**
2
- * GroupedBarChart Widget - Multiple bars side by side
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 { Text } from '../../renderer-svg/primitives';
9
- import { GroupedBarChartWidgetProps } from './types';
10
-
11
- export const GroupedBarChart = memo<GroupedBarChartWidgetProps>(({
12
- data: widgetData,
13
- width,
14
- height,
15
- loading = false,
16
- theme: themeOverride,
17
- barWidth = 20,
18
- groupSpacing = 32,
19
- barSpacing = 4,
20
- showValues = false,
21
- showLabels = true,
22
- showLegend = true,
23
- maxGroups = 10,
24
- testID,
25
- }) => {
26
- const theme = useWidgetTheme(themeOverride);
27
- const dimensions = useWidgetDimensions(width, height, 350, 250);
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 } = widgetData;
46
- const displayData = data.slice(0, maxGroups);
47
- const padding = theme.spacing.md;
48
- const titleHeight = title ? theme.fontScale.md + theme.spacing.sm : 0;
49
- const legendHeight = showLegend ? 40 : 0;
50
- const labelHeight = showLabels ? theme.fontScale.sm + theme.spacing.xs : 0;
51
-
52
- const chartHeight = dimensions.height - padding * 2 - titleHeight - legendHeight - labelHeight;
53
- const chartWidth = dimensions.width - padding * 2;
54
-
55
- const maxValue = useMemo(() => {
56
- return Math.max(...displayData.flatMap(group => group.values.map(v => v.value)));
57
- }, [displayData]);
58
-
59
- // Get all unique value labels for legend
60
- const allValueLabels = useMemo(() => {
61
- const labels = new Map<string, string>();
62
- displayData.forEach(group => {
63
- group.values.forEach(val => {
64
- labels.set(val.label, val.color);
65
- });
66
- });
67
- return Array.from(labels.entries()).map(([label, color]) => ({ label, color }));
68
- }, [displayData]);
69
-
70
- const groups = useMemo(() => {
71
- return displayData.map((group, groupIndex) => {
72
- const barsInGroup = group.values.length;
73
- const groupWidth = barsInGroup * barWidth + (barsInGroup - 1) * barSpacing;
74
- const groupX = groupIndex * (groupWidth + groupSpacing);
75
-
76
- const bars = group.values.map((val, barIndex) => {
77
- const barX = groupX + barIndex * (barWidth + barSpacing);
78
- const normalizedValue = normalize(val.value, 0, maxValue);
79
- const barHeight = normalizedValue * chartHeight;
80
- const barY = chartHeight - barHeight;
81
-
82
- return {
83
- x: barX,
84
- y: barY,
85
- width: barWidth,
86
- height: barHeight,
87
- color: val.color,
88
- value: val.value,
89
- label: val.label,
90
- };
91
- });
92
-
93
- return {
94
- groupLabel: group.groupLabel,
95
- bars,
96
- groupX,
97
- groupWidth,
98
- };
99
- });
100
- }, [displayData, barWidth, barSpacing, groupSpacing, chartHeight, maxValue]);
101
-
102
- return (
103
- <View style={[styles.wrapper, { width: dimensions.width, height: dimensions.height, backgroundColor: theme.colors.surface, borderRadius: theme.radius.md, padding }]} testID={testID}>
104
- {title && (
105
- <RNText style={[styles.title, { color: theme.colors.text, fontSize: theme.fontScale.md, fontWeight: 'bold', marginBottom: theme.spacing.sm }]}>
106
- {title}
107
- </RNText>
108
- )}
109
-
110
- <View style={styles.chartContainer}>
111
- <Svg width={chartWidth} height={chartHeight + (showValues ? 20 : 0)}>
112
- {groups.map((group, groupIndex) => (
113
- group.bars.map((bar, barIndex) => (
114
- <React.Fragment key={`group-${groupIndex}-bar-${barIndex}`}>
115
- <Rect
116
- x={bar.x}
117
- y={bar.y}
118
- width={bar.width}
119
- height={bar.height}
120
- fill={bar.color}
121
- rx={theme.radius.sm}
122
- ry={theme.radius.sm}
123
- />
124
- {showValues && (
125
- <Text
126
- x={bar.x + bar.width / 2}
127
- y={bar.y - 4}
128
- text={bar.value.toString()}
129
- fontSize={theme.fontScale.xs}
130
- fill={theme.colors.textSecondary}
131
- textAnchor="middle"
132
- />
133
- )}
134
- </React.Fragment>
135
- ))
136
- ))}
137
- </Svg>
138
-
139
- {showLabels && (
140
- <View style={styles.labelsContainer}>
141
- {groups.map((group, index) => (
142
- <View
143
- key={`label-${index}`}
144
- style={[styles.labelItem, { left: group.groupX + group.groupWidth / 2 - 30, width: 60 }]}
145
- >
146
- <RNText
147
- style={[styles.labelText, { color: theme.colors.textSecondary, fontSize: theme.fontScale.xs }]}
148
- numberOfLines={1}
149
- >
150
- {group.groupLabel}
151
- </RNText>
152
- </View>
153
- ))}
154
- </View>
155
- )}
156
- </View>
157
-
158
- {showLegend && (
159
- <View style={styles.legend}>
160
- {allValueLabels.map((item, index) => (
161
- <View key={`legend-${index}`} style={styles.legendItem}>
162
- <View style={[styles.legendColor, { backgroundColor: item.color }]} />
163
- <RNText style={[styles.legendText, { color: theme.colors.textSecondary, fontSize: theme.fontScale.xs }]}>
164
- {item.label}
165
- </RNText>
166
- </View>
167
- ))}
168
- </View>
169
- )}
170
- </View>
171
- );
172
- });
173
-
174
- GroupedBarChart.displayName = 'GroupedBarChart';
175
-
176
- const styles = StyleSheet.create({
177
- wrapper: { justifyContent: 'flex-start', alignItems: 'flex-start' },
178
- container: { justifyContent: 'center', alignItems: 'center' },
179
- loadingText: { fontSize: 16 },
180
- emptyText: { fontSize: 16 },
181
- title: { textAlign: 'center', width: '100%' },
182
- chartContainer: { flex: 1, width: '100%' },
183
- labelsContainer: { position: 'relative', height: 30, marginTop: 4 },
184
- labelItem: { position: 'absolute', alignItems: 'center' },
185
- labelText: { textAlign: 'center' },
186
- legend: { flexDirection: 'row', flexWrap: 'wrap', justifyContent: 'center', marginTop: 12, gap: 12 },
187
- legendItem: { flexDirection: 'row', alignItems: 'center', gap: 6 },
188
- legendColor: { width: 12, height: 12, borderRadius: 2 },
189
- legendText: { textTransform: 'uppercase', letterSpacing: 0.5 },
190
- });
@@ -1,2 +0,0 @@
1
- export { GroupedBarChart } from './GroupedBarChart';
2
- export type { GroupedBarChartWidgetProps, GroupedBarChartData, GroupedBarDataPoint, GroupedBarValue } from './types';
@@ -1,30 +0,0 @@
1
- /**
2
- * GroupedBarChart Widget types
3
- */
4
- import { BaseWidgetProps } from '../../core';
5
-
6
- export interface GroupedBarValue {
7
- value: number;
8
- color: string;
9
- label: string;
10
- }
11
-
12
- export interface GroupedBarDataPoint {
13
- groupLabel: string;
14
- values: GroupedBarValue[];
15
- }
16
-
17
- export interface GroupedBarChartData {
18
- data: GroupedBarDataPoint[];
19
- title?: string;
20
- }
21
-
22
- export interface GroupedBarChartWidgetProps extends BaseWidgetProps<GroupedBarChartData> {
23
- barWidth?: number;
24
- groupSpacing?: number;
25
- barSpacing?: number;
26
- showValues?: boolean;
27
- showLabels?: boolean;
28
- showLegend?: boolean;
29
- maxGroups?: number;
30
- }
@@ -1,216 +0,0 @@
1
- /**
2
- * Heatmap Widget - Grid of colored cells
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 { Text } from '../../renderer-svg/primitives';
9
- import { HeatmapWidgetProps } from './types';
10
-
11
- export const Heatmap = memo<HeatmapWidgetProps>(({
12
- data: widgetData,
13
- width,
14
- height,
15
- loading = false,
16
- theme: themeOverride,
17
- cellSize = 30,
18
- cellSpacing = 2,
19
- showValues = false,
20
- showLabels = true,
21
- colorScheme = 'blue',
22
- minColor,
23
- maxColor,
24
- testID,
25
- }) => {
26
- const theme = useWidgetTheme(themeOverride);
27
- const dimensions = useWidgetDimensions(width, height, 400, 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, xLabels, yLabels, title } = widgetData;
46
- const padding = theme.spacing.md;
47
- const titleHeight = title ? theme.fontScale.md + theme.spacing.sm : 0;
48
- const yLabelWidth = showLabels ? 60 : 0;
49
- const xLabelHeight = showLabels ? 30 : 0;
50
-
51
- // Get min/max values for color scaling
52
- const { minValue, maxValue } = useMemo(() => {
53
- const values = data.map(d => d.value);
54
- return {
55
- minValue: Math.min(...values),
56
- maxValue: Math.max(...values),
57
- };
58
- }, [data]);
59
-
60
- // Color scheme mapping
61
- const getColorForValue = (value: number): string => {
62
- const normalizedValue = normalize(value, minValue, maxValue);
63
-
64
- if (minColor && maxColor) {
65
- // Custom colors - simple interpolation
66
- return interpolateColor(minColor, maxColor, normalizedValue);
67
- }
68
-
69
- // Predefined color schemes
70
- switch (colorScheme) {
71
- case 'blue':
72
- return `rgba(59, 130, 246, ${0.2 + normalizedValue * 0.8})`;
73
- case 'green':
74
- return `rgba(16, 185, 129, ${0.2 + normalizedValue * 0.8})`;
75
- case 'red':
76
- return `rgba(239, 68, 68, ${0.2 + normalizedValue * 0.8})`;
77
- case 'purple':
78
- return `rgba(139, 92, 246, ${0.2 + normalizedValue * 0.8})`;
79
- case 'gradient':
80
- if (normalizedValue < 0.5) {
81
- return `rgba(59, 130, 246, ${0.2 + normalizedValue * 1.6})`;
82
- } else {
83
- return `rgba(239, 68, 68, ${(normalizedValue - 0.5) * 2 * 0.8 + 0.2})`;
84
- }
85
- default:
86
- return `rgba(59, 130, 246, ${0.2 + normalizedValue * 0.8})`;
87
- }
88
- };
89
-
90
- // Create grid cells
91
- const cells = useMemo(() => {
92
- return data.map(point => {
93
- const xIndex = typeof point.x === 'number' ? point.x : xLabels.indexOf(point.x as string);
94
- const yIndex = typeof point.y === 'number' ? point.y : yLabels.indexOf(point.y as string);
95
-
96
- const x = xIndex * (cellSize + cellSpacing);
97
- const y = yIndex * (cellSize + cellSpacing);
98
-
99
- return {
100
- x,
101
- y,
102
- width: cellSize,
103
- height: cellSize,
104
- color: getColorForValue(point.value),
105
- value: point.value,
106
- xIndex,
107
- yIndex,
108
- };
109
- });
110
- }, [data, xLabels, yLabels, cellSize, cellSpacing, minValue, maxValue, colorScheme]);
111
-
112
- const chartWidth = xLabels.length * (cellSize + cellSpacing);
113
- const chartHeight = yLabels.length * (cellSize + cellSpacing);
114
-
115
- return (
116
- <View style={[styles.wrapper, { width: dimensions.width, height: dimensions.height, backgroundColor: theme.colors.surface, borderRadius: theme.radius.md, padding }]} testID={testID}>
117
- {title && (
118
- <RNText style={[styles.title, { color: theme.colors.text, fontSize: theme.fontScale.md, fontWeight: 'bold', marginBottom: theme.spacing.sm }]}>
119
- {title}
120
- </RNText>
121
- )}
122
-
123
- <View style={styles.chartRow}>
124
- {showLabels && (
125
- <View style={[styles.yLabels, { width: yLabelWidth, height: chartHeight }]}>
126
- {yLabels.map((label, index) => (
127
- <RNText
128
- key={`y-${index}`}
129
- style={[
130
- styles.yLabel,
131
- {
132
- color: theme.colors.textSecondary,
133
- fontSize: theme.fontScale.xs,
134
- top: index * (cellSize + cellSpacing) + cellSize / 2 - 6,
135
- },
136
- ]}
137
- >
138
- {label}
139
- </RNText>
140
- ))}
141
- </View>
142
- )}
143
-
144
- <View>
145
- <Svg width={chartWidth} height={chartHeight}>
146
- {cells.map((cell, index) => (
147
- <React.Fragment key={`cell-${index}`}>
148
- <Rect
149
- x={cell.x}
150
- y={cell.y}
151
- width={cell.width}
152
- height={cell.height}
153
- fill={cell.color}
154
- rx={theme.radius.sm}
155
- ry={theme.radius.sm}
156
- />
157
- {showValues && (
158
- <Text
159
- x={cell.x + cell.width / 2}
160
- y={cell.y + cell.height / 2}
161
- text={cell.value.toFixed(0)}
162
- fontSize={theme.fontScale.xs}
163
- fill={theme.colors.text}
164
- textAnchor="middle"
165
- />
166
- )}
167
- </React.Fragment>
168
- ))}
169
- </Svg>
170
-
171
- {showLabels && (
172
- <View style={[styles.xLabels, { width: chartWidth }]}>
173
- {xLabels.map((label, index) => (
174
- <RNText
175
- key={`x-${index}`}
176
- style={[
177
- styles.xLabel,
178
- {
179
- color: theme.colors.textSecondary,
180
- fontSize: theme.fontScale.xs,
181
- left: index * (cellSize + cellSpacing),
182
- width: cellSize,
183
- },
184
- ]}
185
- >
186
- {label}
187
- </RNText>
188
- ))}
189
- </View>
190
- )}
191
- </View>
192
- </View>
193
- </View>
194
- );
195
- });
196
-
197
- Heatmap.displayName = 'Heatmap';
198
-
199
- // Simple color interpolation helper
200
- function interpolateColor(color1: string, color2: string, factor: number): string {
201
- // Simple implementation - just use opacity for now
202
- return `${color2.substring(0, 7)}${Math.round((0.2 + factor * 0.8) * 255).toString(16)}`;
203
- }
204
-
205
- const styles = StyleSheet.create({
206
- wrapper: { justifyContent: 'flex-start', alignItems: 'flex-start' },
207
- container: { justifyContent: 'center', alignItems: 'center' },
208
- loadingText: { fontSize: 16 },
209
- emptyText: { fontSize: 16 },
210
- title: { textAlign: 'center', width: '100%' },
211
- chartRow: { flexDirection: 'row' },
212
- yLabels: { position: 'relative', marginRight: 8 },
213
- yLabel: { position: 'absolute', right: 0, textAlign: 'right' },
214
- xLabels: { position: 'relative', height: 30, marginTop: 4 },
215
- xLabel: { position: 'absolute', textAlign: 'center' },
216
- });
@@ -1,2 +0,0 @@
1
- export { Heatmap } from './Heatmap';
2
- export type { HeatmapWidgetProps, HeatmapData, HeatmapDataPoint } from './types';