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.
- package/package.json +1 -2
- package/src/core/animation/index.ts +0 -113
- package/src/core/animation/index.web.ts +0 -112
- package/src/core/hooks/index.ts +0 -66
- package/src/core/index.ts +0 -26
- package/src/core/layout/index.ts +0 -101
- package/src/core/math/index.ts +0 -72
- package/src/core/package.json +0 -13
- package/src/core/theme/ThemeProvider.tsx +0 -36
- package/src/core/theme/index.ts +0 -5
- package/src/core/theme/themes.ts +0 -132
- package/src/core/types/index.ts +0 -164
- package/src/core/utils/responsive.ts +0 -203
- package/src/core/utils/time.ts +0 -100
- package/src/index.ts +0 -13
- package/src/renderer-svg/adapters/index.ts +0 -84
- package/src/renderer-svg/index.ts +0 -8
- package/src/renderer-svg/package.json +0 -17
- package/src/renderer-svg/paths/arc.ts +0 -93
- package/src/renderer-svg/paths/index.ts +0 -6
- package/src/renderer-svg/paths/line.ts +0 -83
- package/src/renderer-svg/paths/rect.ts +0 -80
- package/src/renderer-svg/primitives/AnimatedCircle.tsx +0 -48
- package/src/renderer-svg/primitives/AnimatedPath.tsx +0 -48
- package/src/renderer-svg/primitives/Text.tsx +0 -73
- package/src/renderer-svg/primitives/index.ts +0 -6
- package/src/widgets/AreaChart/AreaChart.tsx +0 -213
- package/src/widgets/AreaChart/index.ts +0 -2
- package/src/widgets/AreaChart/types.ts +0 -34
- package/src/widgets/BarChart/BarChart.tsx +0 -249
- package/src/widgets/BarChart/index.ts +0 -10
- package/src/widgets/BarChart/types.ts +0 -27
- package/src/widgets/BoxPlot/BoxPlot.tsx +0 -252
- package/src/widgets/BoxPlot/index.ts +0 -2
- package/src/widgets/BoxPlot/types.ts +0 -27
- package/src/widgets/BubbleChart/BubbleChart.tsx +0 -175
- package/src/widgets/BubbleChart/index.ts +0 -2
- package/src/widgets/BubbleChart/types.ts +0 -33
- package/src/widgets/CandlestickChart/CandlestickChart.tsx +0 -204
- package/src/widgets/CandlestickChart/index.ts +0 -2
- package/src/widgets/CandlestickChart/types.ts +0 -29
- package/src/widgets/FunnelChart/FunnelChart.tsx +0 -172
- package/src/widgets/FunnelChart/index.ts +0 -2
- package/src/widgets/FunnelChart/types.ts +0 -22
- package/src/widgets/Gauge/Gauge.tsx +0 -235
- package/src/widgets/Gauge/index.ts +0 -5
- package/src/widgets/Gauge/types.ts +0 -19
- package/src/widgets/GroupedBarChart/GroupedBarChart.tsx +0 -190
- package/src/widgets/GroupedBarChart/index.ts +0 -2
- package/src/widgets/GroupedBarChart/types.ts +0 -30
- package/src/widgets/Heatmap/Heatmap.tsx +0 -216
- package/src/widgets/Heatmap/index.ts +0 -2
- package/src/widgets/Heatmap/types.ts +0 -27
- package/src/widgets/Histogram/Histogram.tsx +0 -173
- package/src/widgets/Histogram/index.ts +0 -2
- package/src/widgets/Histogram/types.ts +0 -18
- package/src/widgets/HorizontalBarChart/HorizontalBarChart.tsx +0 -125
- package/src/widgets/HorizontalBarChart/index.ts +0 -2
- package/src/widgets/HorizontalBarChart/types.ts +0 -23
- package/src/widgets/KPI/KPI.tsx +0 -222
- package/src/widgets/KPI/index.ts +0 -5
- package/src/widgets/KPI/types.ts +0 -19
- package/src/widgets/LineChart/LineChart.tsx +0 -364
- package/src/widgets/LineChart/index.ts +0 -10
- package/src/widgets/LineChart/types.ts +0 -34
- package/src/widgets/MultiLineSparkline/MultiLineSparkline.tsx +0 -234
- package/src/widgets/MultiLineSparkline/index.ts +0 -10
- package/src/widgets/MultiLineSparkline/types.ts +0 -25
- package/src/widgets/PieChart/PieChart.tsx +0 -275
- package/src/widgets/PieChart/index.ts +0 -10
- package/src/widgets/PieChart/types.ts +0 -26
- package/src/widgets/Progress/Progress.tsx +0 -201
- package/src/widgets/Progress/index.ts +0 -5
- package/src/widgets/Progress/types.ts +0 -19
- package/src/widgets/RadarChart/RadarChart.tsx +0 -213
- package/src/widgets/RadarChart/index.ts +0 -2
- package/src/widgets/RadarChart/types.ts +0 -29
- package/src/widgets/SankeyDiagram/SankeyDiagram.tsx +0 -272
- package/src/widgets/SankeyDiagram/index.ts +0 -2
- package/src/widgets/SankeyDiagram/types.ts +0 -29
- package/src/widgets/ScatterPlot/ScatterPlot.tsx +0 -167
- package/src/widgets/ScatterPlot/index.ts +0 -2
- package/src/widgets/ScatterPlot/types.ts +0 -32
- package/src/widgets/Sparkline/Sparkline.tsx +0 -203
- package/src/widgets/Sparkline/index.ts +0 -5
- package/src/widgets/Sparkline/types.ts +0 -18
- package/src/widgets/StackedBarChart/StackedBarChart.tsx +0 -181
- package/src/widgets/StackedBarChart/index.ts +0 -2
- package/src/widgets/StackedBarChart/types.ts +0 -29
- package/src/widgets/SunburstChart/SunburstChart.tsx +0 -176
- package/src/widgets/SunburstChart/index.ts +0 -2
- package/src/widgets/SunburstChart/types.ts +0 -22
- package/src/widgets/Treemap/Treemap.tsx +0 -191
- package/src/widgets/Treemap/index.ts +0 -2
- package/src/widgets/Treemap/types.ts +0 -23
- package/src/widgets/WaterfallChart/WaterfallChart.tsx +0 -226
- package/src/widgets/WaterfallChart/index.ts +0 -2
- package/src/widgets/WaterfallChart/types.ts +0 -26
- package/src/widgets/index.ts +0 -40
- 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,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,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,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
|
-
}
|