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,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';
@@ -1,27 +0,0 @@
1
- /**
2
- * Heatmap Widget types
3
- */
4
- import { BaseWidgetProps } from '../../core';
5
-
6
- export interface HeatmapDataPoint {
7
- x: number | string;
8
- y: number | string;
9
- value: number;
10
- }
11
-
12
- export interface HeatmapData {
13
- data: HeatmapDataPoint[];
14
- xLabels: string[];
15
- yLabels: string[];
16
- title?: string;
17
- }
18
-
19
- export interface HeatmapWidgetProps extends BaseWidgetProps<HeatmapData> {
20
- cellSize?: number;
21
- cellSpacing?: number;
22
- showValues?: boolean;
23
- showLabels?: boolean;
24
- colorScheme?: 'blue' | 'green' | 'red' | 'purple' | 'gradient';
25
- minColor?: string;
26
- maxColor?: string;
27
- }
@@ -1,173 +0,0 @@
1
- /**
2
- * Histogram Widget - Frequency distribution
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 } from '../../core';
8
- import { HistogramWidgetProps } from './types';
9
-
10
- export const Histogram = memo<HistogramWidgetProps>(({
11
- data: widgetData,
12
- width,
13
- height,
14
- loading = false,
15
- theme: themeOverride,
16
- showXAxis = true,
17
- showYAxis = true,
18
- showGrid = false,
19
- color,
20
- barSpacing = 2,
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.data || widgetData.data.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 { data, title, binCount = 10 } = widgetData;
43
- const padding = theme.spacing.md;
44
- const titleHeight = title ? theme.fontScale.md + theme.spacing.sm : 0;
45
- const xAxisHeight = showXAxis ? 30 : 0;
46
- const yAxisWidth = showYAxis ? 40 : 0;
47
-
48
- const chartWidth = dimensions.width - padding * 2 - yAxisWidth;
49
- const chartHeight = dimensions.height - padding * 2 - titleHeight - xAxisHeight;
50
-
51
- const barColor = color || theme.colors.chartPrimary;
52
-
53
- // Calculate bins
54
- const { bins, maxFrequency, minValue, maxValue } = useMemo(() => {
55
- const min = Math.min(...data);
56
- const max = Math.max(...data);
57
- const binWidth = (max - min) / binCount;
58
-
59
- const binArray: { start: number; end: number; count: number }[] = [];
60
-
61
- for (let i = 0; i < binCount; i++) {
62
- const start = min + i * binWidth;
63
- const end = start + binWidth;
64
- const count = data.filter(val => val >= start && (i === binCount - 1 ? val <= end : val < end)).length;
65
- binArray.push({ start, end, count });
66
- }
67
-
68
- const maxFreq = Math.max(...binArray.map(b => b.count));
69
-
70
- return {
71
- bins: binArray,
72
- maxFrequency: maxFreq,
73
- minValue: min,
74
- maxValue: max,
75
- };
76
- }, [data, binCount]);
77
-
78
- const yAxisLabels = useMemo(() => {
79
- const step = Math.ceil(maxFrequency / 4);
80
- return Array.from({ length: 5 }, (_, i) => ({
81
- value: i * step,
82
- y: chartHeight - (i * chartHeight) / 4,
83
- }));
84
- }, [maxFrequency, chartHeight]);
85
-
86
- const barWidth = (chartWidth - (bins.length - 1) * barSpacing) / bins.length;
87
-
88
- const bars = useMemo(() => {
89
- return bins.map((bin, index) => {
90
- const barHeight = (bin.count / maxFrequency) * chartHeight;
91
- const barY = chartHeight - barHeight;
92
- const barX = index * (barWidth + barSpacing);
93
-
94
- return {
95
- x: barX,
96
- y: barY,
97
- width: barWidth,
98
- height: barHeight,
99
- count: bin.count,
100
- rangeLabel: `${bin.start.toFixed(0)}-${bin.end.toFixed(0)}`,
101
- };
102
- });
103
- }, [bins, maxFrequency, chartHeight, barWidth, barSpacing]);
104
-
105
- return (
106
- <View style={[styles.wrapper, { width: dimensions.width, height: dimensions.height, backgroundColor: theme.colors.surface, borderRadius: theme.radius.md, padding }]} testID={testID}>
107
- {title && (
108
- <RNText style={[styles.title, { color: theme.colors.text, fontSize: theme.fontScale.md, fontWeight: 'bold', marginBottom: theme.spacing.sm }]}>
109
- {title}
110
- </RNText>
111
- )}
112
-
113
- <View style={styles.chartRow}>
114
- {showYAxis && (
115
- <View style={[styles.yAxis, { width: yAxisWidth }]}>
116
- {yAxisLabels.map((label, index) => (
117
- <RNText key={`y-${index}`} style={[styles.yAxisLabel, { color: theme.colors.textSecondary, fontSize: theme.fontScale.xs, top: label.y - 6 }]}>
118
- {label.value}
119
- </RNText>
120
- ))}
121
- </View>
122
- )}
123
-
124
- <View>
125
- <Svg width={chartWidth} height={chartHeight}>
126
- {showGrid && yAxisLabels.map((label, index) => (
127
- <SvgLine key={`grid-${index}`} x1={0} y1={label.y} x2={chartWidth} y2={label.y} stroke={theme.colors.borderLight} strokeWidth={1} />
128
- ))}
129
-
130
- {bars.map((bar, index) => (
131
- <Rect
132
- key={`bar-${index}`}
133
- x={bar.x}
134
- y={bar.y}
135
- width={bar.width}
136
- height={bar.height}
137
- fill={barColor}
138
- rx={theme.radius.sm}
139
- ry={theme.radius.sm}
140
- />
141
- ))}
142
- </Svg>
143
-
144
- {showXAxis && (
145
- <View style={[styles.xAxis, { width: chartWidth }]}>
146
- <RNText style={[styles.xAxisLabel, { color: theme.colors.textSecondary, fontSize: theme.fontScale.xs, left: 0 }]}>
147
- {minValue.toFixed(0)}
148
- </RNText>
149
- <RNText style={[styles.xAxisLabel, { color: theme.colors.textSecondary, fontSize: theme.fontScale.xs, right: 0 }]}>
150
- {maxValue.toFixed(0)}
151
- </RNText>
152
- </View>
153
- )}
154
- </View>
155
- </View>
156
- </View>
157
- );
158
- });
159
-
160
- Histogram.displayName = 'Histogram';
161
-
162
- const styles = StyleSheet.create({
163
- wrapper: { justifyContent: 'flex-start', alignItems: 'flex-start' },
164
- container: { justifyContent: 'center', alignItems: 'center' },
165
- loadingText: { fontSize: 16 },
166
- emptyText: { fontSize: 16 },
167
- title: { textAlign: 'center', width: '100%' },
168
- chartRow: { flexDirection: 'row', alignItems: 'flex-start' },
169
- yAxis: { position: 'relative', marginRight: 8 },
170
- yAxisLabel: { position: 'absolute', right: 0, textAlign: 'right' },
171
- xAxis: { position: 'relative', height: 30, marginTop: 4 },
172
- xAxisLabel: { position: 'absolute' },
173
- });
@@ -1,2 +0,0 @@
1
- export { Histogram } from './Histogram';
2
- export type { HistogramWidgetProps, HistogramData } from './types';
@@ -1,18 +0,0 @@
1
- /**
2
- * Histogram Widget types
3
- */
4
- import { BaseWidgetProps } from '../../core';
5
-
6
- export interface HistogramData {
7
- data: number[];
8
- title?: string;
9
- binCount?: number;
10
- }
11
-
12
- export interface HistogramWidgetProps extends BaseWidgetProps<HistogramData> {
13
- showXAxis?: boolean;
14
- showYAxis?: boolean;
15
- showGrid?: boolean;
16
- color?: string;
17
- barSpacing?: number;
18
- }