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,176 +0,0 @@
1
- /**
2
- * SunburstChart Widget - Hierarchical data in concentric rings
3
- */
4
- import React, { memo, useMemo } from 'react';
5
- import { View, Text as RNText, StyleSheet } from 'react-native';
6
- import Svg, { Path } from 'react-native-svg';
7
- import { useWidgetDimensions, useWidgetTheme, polarToCartesian } from '../../core';
8
- import { createDonutArcPath } from '../../renderer-svg';
9
- import { SunburstChartWidgetProps, SunburstNode } from './types';
10
-
11
- interface SunburstSegment {
12
- path: string;
13
- color: string;
14
- label: string;
15
- level: number;
16
- }
17
-
18
- function calculateSunburst(
19
- nodes: SunburstNode[],
20
- cx: number,
21
- cy: number,
22
- innerRadius: number,
23
- outerRadius: number,
24
- startAngle: number,
25
- endAngle: number,
26
- level: number,
27
- colors: string[]
28
- ): SunburstSegment[] {
29
- const segments: SunburstSegment[] = [];
30
- const totalValue = nodes.reduce((sum, node) => sum + node.value, 0);
31
-
32
- if (totalValue === 0) return segments;
33
-
34
- let currentAngle = startAngle;
35
- const radiusStep = (outerRadius - innerRadius) / 3; // Support 3 levels
36
-
37
- nodes.forEach((node, index) => {
38
- const angleSpan = ((endAngle - startAngle) * node.value) / totalValue;
39
- const nodeEndAngle = currentAngle + angleSpan;
40
-
41
- const levelInnerRadius = innerRadius + level * radiusStep;
42
- const levelOuterRadius = innerRadius + (level + 1) * radiusStep;
43
-
44
- const color = node.color || colors[index % colors.length];
45
-
46
- // Create arc for this node
47
- const path = createDonutArcPath({
48
- cx,
49
- cy,
50
- radius: levelOuterRadius,
51
- innerRadius: levelInnerRadius,
52
- startAngle: currentAngle,
53
- endAngle: nodeEndAngle,
54
- });
55
-
56
- segments.push({
57
- path,
58
- color,
59
- label: node.label,
60
- level,
61
- });
62
-
63
- // Recursively process children
64
- if (node.children && node.children.length > 0) {
65
- const childSegments = calculateSunburst(
66
- node.children,
67
- cx,
68
- cy,
69
- innerRadius,
70
- outerRadius,
71
- currentAngle,
72
- nodeEndAngle,
73
- level + 1,
74
- colors
75
- );
76
- segments.push(...childSegments);
77
- }
78
-
79
- currentAngle = nodeEndAngle;
80
- });
81
-
82
- return segments;
83
- }
84
-
85
- export const SunburstChart = memo<SunburstChartWidgetProps>(({
86
- data: widgetData,
87
- width,
88
- height,
89
- loading = false,
90
- theme: themeOverride,
91
- showLabels = false,
92
- size: customSize,
93
- innerRadius = 30,
94
- testID,
95
- }) => {
96
- const theme = useWidgetTheme(themeOverride);
97
- const dimensions = useWidgetDimensions(width, height, 350, 350);
98
-
99
- if (loading) {
100
- return (
101
- <View style={[styles.container, { width: dimensions.width, height: dimensions.height, backgroundColor: theme.colors.surface, borderRadius: theme.radius.md }]} testID={`${testID}-loading`}>
102
- <RNText style={[styles.loadingText, { color: theme.colors.textSecondary }]}>Loading...</RNText>
103
- </View>
104
- );
105
- }
106
-
107
- if (!widgetData || !widgetData.data || widgetData.data.length === 0) {
108
- return (
109
- <View style={[styles.container, { width: dimensions.width, height: dimensions.height, backgroundColor: theme.colors.surface, borderRadius: theme.radius.md }]} testID={`${testID}-empty`}>
110
- <RNText style={[styles.emptyText, { color: theme.colors.textSecondary }]}>No data</RNText>
111
- </View>
112
- );
113
- }
114
-
115
- const { data, title } = widgetData;
116
- const padding = theme.spacing.md;
117
- const titleHeight = title ? theme.fontScale.md + theme.spacing.sm : 0;
118
-
119
- const availableSize = Math.min(
120
- dimensions.width - padding * 2,
121
- dimensions.height - padding * 2 - titleHeight
122
- );
123
-
124
- const chartSize = customSize || availableSize;
125
- const center = chartSize / 2;
126
- const outerRadius = chartSize / 2 - 10;
127
-
128
- const colors = [
129
- theme.colors.chartPrimary,
130
- theme.colors.chartSecondary,
131
- theme.colors.chartTertiary,
132
- theme.colors.chartQuaternary,
133
- theme.colors.chartPositive,
134
- theme.colors.chartNegative,
135
- ];
136
-
137
- const segments = useMemo(() => {
138
- return calculateSunburst(data, center, center, innerRadius, outerRadius, 0, 360, 0, colors);
139
- }, [data, center, innerRadius, outerRadius, colors]);
140
-
141
- return (
142
- <View style={[styles.wrapper, { width: dimensions.width, height: dimensions.height, backgroundColor: theme.colors.surface, borderRadius: theme.radius.md, padding }]} testID={testID}>
143
- {title && (
144
- <RNText style={[styles.title, { color: theme.colors.text, fontSize: theme.fontScale.md, fontWeight: 'bold', marginBottom: theme.spacing.sm }]}>
145
- {title}
146
- </RNText>
147
- )}
148
-
149
- <View style={styles.chartContainer}>
150
- <Svg width={chartSize} height={chartSize}>
151
- {segments.map((segment, index) => (
152
- <Path
153
- key={`segment-${index}`}
154
- d={segment.path}
155
- fill={segment.color}
156
- opacity={0.8 - segment.level * 0.1}
157
- stroke={theme.colors.background}
158
- strokeWidth={2}
159
- />
160
- ))}
161
- </Svg>
162
- </View>
163
- </View>
164
- );
165
- });
166
-
167
- SunburstChart.displayName = 'SunburstChart';
168
-
169
- const styles = StyleSheet.create({
170
- wrapper: { justifyContent: 'flex-start', alignItems: 'center' },
171
- container: { justifyContent: 'center', alignItems: 'center' },
172
- loadingText: { fontSize: 16 },
173
- emptyText: { fontSize: 16 },
174
- title: { textAlign: 'center', width: '100%' },
175
- chartContainer: { alignItems: 'center', justifyContent: 'center' },
176
- });
@@ -1,2 +0,0 @@
1
- export { SunburstChart } from './SunburstChart';
2
- export type { SunburstChartWidgetProps, SunburstChartData, SunburstNode } from './types';
@@ -1,22 +0,0 @@
1
- /**
2
- * SunburstChart Widget types - Circular hierarchical visualization
3
- */
4
- import { BaseWidgetProps } from '../../core';
5
-
6
- export interface SunburstNode {
7
- label: string;
8
- value: number;
9
- color?: string;
10
- children?: SunburstNode[];
11
- }
12
-
13
- export interface SunburstChartData {
14
- data: SunburstNode[];
15
- title?: string;
16
- }
17
-
18
- export interface SunburstChartWidgetProps extends BaseWidgetProps<SunburstChartData> {
19
- showLabels?: boolean;
20
- size?: number;
21
- innerRadius?: number;
22
- }
@@ -1,191 +0,0 @@
1
- /**
2
- * Treemap Widget - Hierarchical data visualization
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 } from '../../core';
8
- import { Text } from '../../renderer-svg/primitives';
9
- import { TreemapWidgetProps, TreemapNode } from './types';
10
-
11
- interface LayoutRect {
12
- x: number;
13
- y: number;
14
- width: number;
15
- height: number;
16
- label: string;
17
- value: number;
18
- color: string;
19
- }
20
-
21
- // Simple squarified treemap algorithm
22
- function squarify(
23
- data: TreemapNode[],
24
- x: number,
25
- y: number,
26
- width: number,
27
- height: number,
28
- colors: string[]
29
- ): LayoutRect[] {
30
- const rects: LayoutRect[] = [];
31
- const totalValue = data.reduce((sum, node) => sum + node.value, 0);
32
-
33
- if (totalValue === 0) return rects;
34
-
35
- let currentX = x;
36
- let currentY = y;
37
- let remainingWidth = width;
38
- let remainingHeight = height;
39
-
40
- // Sort by value descending for better layout
41
- const sortedData = [...data].sort((a, b) => b.value - a.value);
42
-
43
- sortedData.forEach((node, index) => {
44
- const ratio = node.value / totalValue;
45
- const color = node.color || colors[index % colors.length];
46
-
47
- // Simple layout: horizontal strip
48
- if (remainingWidth >= remainingHeight) {
49
- // Lay out vertically
50
- const rectWidth = width * ratio;
51
- rects.push({
52
- x: currentX,
53
- y: currentY,
54
- width: rectWidth,
55
- height: remainingHeight,
56
- label: node.label,
57
- value: node.value,
58
- color,
59
- });
60
- currentX += rectWidth;
61
- remainingWidth -= rectWidth;
62
- } else {
63
- // Lay out horizontally
64
- const rectHeight = height * ratio;
65
- rects.push({
66
- x: currentX,
67
- y: currentY,
68
- width: remainingWidth,
69
- height: rectHeight,
70
- label: node.label,
71
- value: node.value,
72
- color,
73
- });
74
- currentY += rectHeight;
75
- remainingHeight -= rectHeight;
76
- }
77
- });
78
-
79
- return rects;
80
- }
81
-
82
- export const Treemap = memo<TreemapWidgetProps>(({
83
- data: widgetData,
84
- width,
85
- height,
86
- loading = false,
87
- theme: themeOverride,
88
- showLabels = true,
89
- showValues = true,
90
- colorScheme = 'categorical',
91
- padding = 2,
92
- testID,
93
- }) => {
94
- const theme = useWidgetTheme(themeOverride);
95
- const dimensions = useWidgetDimensions(width, height, 350, 300);
96
-
97
- if (loading) {
98
- return (
99
- <View style={[styles.container, { width: dimensions.width, height: dimensions.height, backgroundColor: theme.colors.surface, borderRadius: theme.radius.md }]} testID={`${testID}-loading`}>
100
- <RNText style={[styles.loadingText, { color: theme.colors.textSecondary }]}>Loading...</RNText>
101
- </View>
102
- );
103
- }
104
-
105
- if (!widgetData || !widgetData.data || widgetData.data.length === 0) {
106
- return (
107
- <View style={[styles.container, { width: dimensions.width, height: dimensions.height, backgroundColor: theme.colors.surface, borderRadius: theme.radius.md }]} testID={`${testID}-empty`}>
108
- <RNText style={[styles.emptyText, { color: theme.colors.textSecondary }]}>No data</RNText>
109
- </View>
110
- );
111
- }
112
-
113
- const { data, title } = widgetData;
114
- const chartPadding = theme.spacing.md;
115
- const titleHeight = title ? theme.fontScale.md + theme.spacing.sm : 0;
116
-
117
- const chartWidth = dimensions.width - chartPadding * 2;
118
- const chartHeight = dimensions.height - chartPadding * 2 - titleHeight;
119
-
120
- const colors = [
121
- theme.colors.chartPrimary,
122
- theme.colors.chartSecondary,
123
- theme.colors.chartTertiary,
124
- theme.colors.chartQuaternary,
125
- theme.colors.chartPositive,
126
- theme.colors.chartNegative,
127
- ];
128
-
129
- const rectangles = useMemo(() => {
130
- return squarify(data, 0, 0, chartWidth, chartHeight, colors);
131
- }, [data, chartWidth, chartHeight, colors]);
132
-
133
- return (
134
- <View style={[styles.wrapper, { width: dimensions.width, height: dimensions.height, backgroundColor: theme.colors.surface, borderRadius: theme.radius.md, padding: chartPadding }]} testID={testID}>
135
- {title && (
136
- <RNText style={[styles.title, { color: theme.colors.text, fontSize: theme.fontScale.md, fontWeight: 'bold', marginBottom: theme.spacing.sm }]}>
137
- {title}
138
- </RNText>
139
- )}
140
-
141
- <Svg width={chartWidth} height={chartHeight}>
142
- {rectangles.map((rect, index) => (
143
- <React.Fragment key={`rect-${index}`}>
144
- <Rect
145
- x={rect.x + padding}
146
- y={rect.y + padding}
147
- width={Math.max(0, rect.width - padding * 2)}
148
- height={Math.max(0, rect.height - padding * 2)}
149
- fill={rect.color}
150
- opacity={0.8}
151
- rx={theme.radius.sm}
152
- ry={theme.radius.sm}
153
- />
154
- {showLabels && rect.width > 40 && rect.height > 30 && (
155
- <Text
156
- x={rect.x + rect.width / 2}
157
- y={rect.y + rect.height / 2 - (showValues ? 6 : 0)}
158
- text={rect.label}
159
- fontSize={theme.fontScale.xs}
160
- fill="#FFFFFF"
161
- textAnchor="middle"
162
- fontWeight="600"
163
- />
164
- )}
165
- {showValues && rect.width > 40 && rect.height > 30 && (
166
- <Text
167
- x={rect.x + rect.width / 2}
168
- y={rect.y + rect.height / 2 + (showLabels ? 10 : 0)}
169
- text={rect.value.toString()}
170
- fontSize={theme.fontScale.xs}
171
- fill="#FFFFFF"
172
- textAnchor="middle"
173
- opacity={0.9}
174
- />
175
- )}
176
- </React.Fragment>
177
- ))}
178
- </Svg>
179
- </View>
180
- );
181
- });
182
-
183
- Treemap.displayName = 'Treemap';
184
-
185
- const styles = StyleSheet.create({
186
- wrapper: { justifyContent: 'flex-start', alignItems: 'center' },
187
- container: { justifyContent: 'center', alignItems: 'center' },
188
- loadingText: { fontSize: 16 },
189
- emptyText: { fontSize: 16 },
190
- title: { textAlign: 'center', width: '100%' },
191
- });
@@ -1,2 +0,0 @@
1
- export { Treemap } from './Treemap';
2
- export type { TreemapWidgetProps, TreemapData, TreemapNode } from './types';
@@ -1,23 +0,0 @@
1
- /**
2
- * Treemap Widget types - Hierarchical data as nested rectangles
3
- */
4
- import { BaseWidgetProps } from '../../core';
5
-
6
- export interface TreemapNode {
7
- label: string;
8
- value: number;
9
- color?: string;
10
- children?: TreemapNode[];
11
- }
12
-
13
- export interface TreemapData {
14
- data: TreemapNode[];
15
- title?: string;
16
- }
17
-
18
- export interface TreemapWidgetProps extends BaseWidgetProps<TreemapData> {
19
- showLabels?: boolean;
20
- showValues?: boolean;
21
- colorScheme?: 'categorical' | 'sequential';
22
- padding?: number;
23
- }
@@ -1,226 +0,0 @@
1
- /**
2
- * WaterfallChart Widget - Shows incremental positive/negative changes
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 } from '../../core';
8
- import { Text } from '../../renderer-svg/primitives';
9
- import { WaterfallChartWidgetProps } from './types';
10
-
11
- export const WaterfallChart = memo<WaterfallChartWidgetProps>(({
12
- data: widgetData,
13
- width,
14
- height,
15
- loading = false,
16
- theme: themeOverride,
17
- showValues = true,
18
- showLabels = true,
19
- positiveColor,
20
- negativeColor,
21
- totalColor,
22
- barWidth = 40,
23
- barSpacing = 20,
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, title, startLabel = 'Start' } = widgetData;
46
- const padding = theme.spacing.md;
47
- const titleHeight = title ? theme.fontScale.md + theme.spacing.sm : 0;
48
- const labelHeight = showLabels ? 40 : 0;
49
-
50
- const chartHeight = dimensions.height - padding * 2 - titleHeight - labelHeight;
51
- const chartWidth = dimensions.width - padding * 2;
52
-
53
- const colorPositive = positiveColor || theme.colors.chartPositive;
54
- const colorNegative = negativeColor || theme.colors.chartNegative;
55
- const colorTotal = totalColor || theme.colors.chartPrimary;
56
-
57
- // Calculate cumulative values and positions
58
- const { bars, maxValue, minValue } = useMemo(() => {
59
- let cumulative = 0;
60
- const calculatedBars: any[] = [];
61
- let max = -Infinity;
62
- let min = Infinity;
63
-
64
- data.forEach((point, index) => {
65
- const isPositive = point.value >= 0;
66
- const isTotal = point.isTotal || false;
67
-
68
- const startValue = isTotal ? 0 : cumulative;
69
- const endValue = isTotal ? cumulative + point.value : cumulative + point.value;
70
-
71
- if (endValue > max) max = endValue;
72
- if (startValue < min) min = startValue;
73
-
74
- calculatedBars.push({
75
- label: point.label,
76
- value: point.value,
77
- startValue,
78
- endValue,
79
- isPositive,
80
- isTotal,
81
- x: index * (barWidth + barSpacing),
82
- });
83
-
84
- if (!isTotal) {
85
- cumulative += point.value;
86
- }
87
- });
88
-
89
- if (0 > min) min = 0;
90
- if (0 < max) max = max;
91
-
92
- return { bars: calculatedBars, maxValue: max, minValue: min };
93
- }, [data, barWidth, barSpacing]);
94
-
95
- const valueRange = maxValue - minValue;
96
- const zeroY = chartHeight - ((0 - minValue) / valueRange) * chartHeight;
97
-
98
- const renderedBars = useMemo(() => {
99
- return bars.map(bar => {
100
- const startY = chartHeight - ((bar.startValue - minValue) / valueRange) * chartHeight;
101
- const endY = chartHeight - ((bar.endValue - minValue) / valueRange) * chartHeight;
102
- const barHeight = Math.abs(startY - endY);
103
- const barY = Math.min(startY, endY);
104
-
105
- let color = colorTotal;
106
- if (!bar.isTotal) {
107
- color = bar.isPositive ? colorPositive : colorNegative;
108
- }
109
-
110
- return {
111
- x: bar.x,
112
- y: barY,
113
- width: barWidth,
114
- height: barHeight,
115
- color,
116
- label: bar.label,
117
- value: bar.value,
118
- startY,
119
- endY,
120
- isTotal: bar.isTotal,
121
- };
122
- });
123
- }, [bars, chartHeight, minValue, valueRange, barWidth, colorPositive, colorNegative, colorTotal]);
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.chartContainer}>
134
- <Svg width={chartWidth} height={chartHeight}>
135
- {/* Zero line */}
136
- <SvgLine
137
- x1={0}
138
- y1={zeroY}
139
- x2={chartWidth}
140
- y2={zeroY}
141
- stroke={theme.colors.border}
142
- strokeWidth={1}
143
- strokeDasharray="4 4"
144
- />
145
-
146
- {/* Connection lines between bars */}
147
- {renderedBars.map((bar, index) => {
148
- if (index < renderedBars.length - 1) {
149
- const nextBar = renderedBars[index + 1];
150
- return (
151
- <SvgLine
152
- key={`connector-${index}`}
153
- x1={bar.x + barWidth}
154
- y1={bar.endY}
155
- x2={nextBar.x}
156
- y2={nextBar.isTotal ? nextBar.y : bar.endY}
157
- stroke={theme.colors.borderLight}
158
- strokeWidth={2}
159
- strokeDasharray="4 2"
160
- />
161
- );
162
- }
163
- return null;
164
- })}
165
-
166
- {/* Bars */}
167
- {renderedBars.map((bar, index) => (
168
- <React.Fragment key={`bar-${index}`}>
169
- <Rect
170
- x={bar.x}
171
- y={bar.y}
172
- width={bar.width}
173
- height={bar.height}
174
- fill={bar.color}
175
- rx={theme.radius.sm}
176
- ry={theme.radius.sm}
177
- />
178
- {showValues && (
179
- <Text
180
- x={bar.x + bar.width / 2}
181
- y={bar.y - 4}
182
- text={bar.value >= 0 ? `+${bar.value}` : `${bar.value}`}
183
- fontSize={theme.fontScale.xs}
184
- fill={theme.colors.text}
185
- textAnchor="middle"
186
- />
187
- )}
188
- </React.Fragment>
189
- ))}
190
- </Svg>
191
-
192
- {showLabels && (
193
- <View style={styles.labelsContainer}>
194
- {renderedBars.map((bar, index) => (
195
- <View
196
- key={`label-${index}`}
197
- style={[styles.labelItem, { left: bar.x, width: barWidth }]}
198
- >
199
- <RNText
200
- style={[styles.labelText, { color: theme.colors.textSecondary, fontSize: theme.fontScale.xs }]}
201
- numberOfLines={2}
202
- >
203
- {bar.label}
204
- </RNText>
205
- </View>
206
- ))}
207
- </View>
208
- )}
209
- </View>
210
- </View>
211
- );
212
- });
213
-
214
- WaterfallChart.displayName = 'WaterfallChart';
215
-
216
- const styles = StyleSheet.create({
217
- wrapper: { justifyContent: 'flex-start', alignItems: 'flex-start' },
218
- container: { justifyContent: 'center', alignItems: 'center' },
219
- loadingText: { fontSize: 16 },
220
- emptyText: { fontSize: 16 },
221
- title: { textAlign: 'center', width: '100%' },
222
- chartContainer: { flex: 1, width: '100%' },
223
- labelsContainer: { position: 'relative', height: 40, marginTop: 4 },
224
- labelItem: { position: 'absolute', alignItems: 'center' },
225
- labelText: { textAlign: 'center' },
226
- });
@@ -1,2 +0,0 @@
1
- export { WaterfallChart } from './WaterfallChart';
2
- export type { WaterfallChartWidgetProps, WaterfallChartData, WaterfallDataPoint } from './types';
@@ -1,26 +0,0 @@
1
- /**
2
- * WaterfallChart Widget types - Shows incremental changes
3
- */
4
- import { BaseWidgetProps } from '../../core';
5
-
6
- export interface WaterfallDataPoint {
7
- label: string;
8
- value: number;
9
- isTotal?: boolean;
10
- }
11
-
12
- export interface WaterfallChartData {
13
- data: WaterfallDataPoint[];
14
- title?: string;
15
- startLabel?: string;
16
- }
17
-
18
- export interface WaterfallChartWidgetProps extends BaseWidgetProps<WaterfallChartData> {
19
- showValues?: boolean;
20
- showLabels?: boolean;
21
- positiveColor?: string;
22
- negativeColor?: string;
23
- totalColor?: string;
24
- barWidth?: number;
25
- barSpacing?: number;
26
- }