@dhiraj0720/report1chart 3.0.6 → 3.0.7
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 -1
- package/src/components/CompactAxisBarLineChart.jsx +320 -0
- package/src/components/CompactAxisLineChart.jsx +238 -0
- package/src/index.jsx +14 -1
- package/src/screens/Report1ModernScreen.jsx +551 -0
- package/src/screens/Report2ModernScreen.jsx +89 -337
- package/src/screens/Report3ModernScreen.jsx +81 -220
- package/src/screens/ReportListScreen.jsx +5 -0
package/package.json
CHANGED
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
|
+
import { ScrollView, StyleSheet, Text, View } from 'react-native';
|
|
3
|
+
import Svg, {
|
|
4
|
+
Circle,
|
|
5
|
+
Line,
|
|
6
|
+
Rect,
|
|
7
|
+
Text as SvgText,
|
|
8
|
+
} from 'react-native-svg';
|
|
9
|
+
import { formatNumber } from '../utils/formatNumber';
|
|
10
|
+
|
|
11
|
+
const DEFAULT_COLORS = ['#F29F45', '#2E7DD1', '#8AB6E8'];
|
|
12
|
+
|
|
13
|
+
const toNumber = (value) => {
|
|
14
|
+
const numeric = Number(value);
|
|
15
|
+
return Number.isFinite(numeric) ? numeric : 0;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const compactAmount = (value) => {
|
|
19
|
+
const num = toNumber(value);
|
|
20
|
+
if (Math.abs(num) >= 1000000000) return `${(num / 1000000000).toFixed(1)}B`;
|
|
21
|
+
if (Math.abs(num) >= 1000000) return `${(num / 1000000).toFixed(1)}M`;
|
|
22
|
+
if (Math.abs(num) >= 1000) return `${(num / 1000).toFixed(1)}K`;
|
|
23
|
+
return `${num}`;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const makeTicks = (maxValue, count = 3) => {
|
|
27
|
+
if (count < 2) return [maxValue];
|
|
28
|
+
return Array.from({ length: count }, (_, index) => {
|
|
29
|
+
const ratio = (count - 1 - index) / (count - 1);
|
|
30
|
+
return maxValue * ratio;
|
|
31
|
+
});
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const CompactAxisBarLineChart = ({
|
|
35
|
+
data,
|
|
36
|
+
title,
|
|
37
|
+
legend,
|
|
38
|
+
height = 220,
|
|
39
|
+
minGraphWidth = 360,
|
|
40
|
+
groupSpacing = 86,
|
|
41
|
+
}) => {
|
|
42
|
+
const labels = data?.labels || [];
|
|
43
|
+
const series = data?.series || [];
|
|
44
|
+
|
|
45
|
+
if (!labels.length || series.length < 2) return null;
|
|
46
|
+
|
|
47
|
+
const barSeriesA = series[0]?.data || [];
|
|
48
|
+
const barSeriesB = series[1]?.data || [];
|
|
49
|
+
const lineSeries = series[2]?.data || [];
|
|
50
|
+
|
|
51
|
+
const {
|
|
52
|
+
graphWidth,
|
|
53
|
+
ticks,
|
|
54
|
+
paddingLeft,
|
|
55
|
+
paddingRight,
|
|
56
|
+
paddingTop,
|
|
57
|
+
chartHeight,
|
|
58
|
+
maxValue,
|
|
59
|
+
barWidth,
|
|
60
|
+
barGap,
|
|
61
|
+
} = useMemo(() => {
|
|
62
|
+
const allValues = [...barSeriesA, ...barSeriesB, ...lineSeries].map(toNumber);
|
|
63
|
+
const peak = Math.max(1, ...allValues);
|
|
64
|
+
const computedPaddingLeft = 8;
|
|
65
|
+
const computedPaddingRight = 10;
|
|
66
|
+
const computedPaddingTop = 14;
|
|
67
|
+
const computedPaddingBottom = 42;
|
|
68
|
+
const computedChartHeight = height - computedPaddingTop - computedPaddingBottom;
|
|
69
|
+
const computedGraphWidth = Math.max(minGraphWidth, labels.length * groupSpacing);
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
graphWidth: computedGraphWidth,
|
|
73
|
+
ticks: makeTicks(peak, 3),
|
|
74
|
+
paddingLeft: computedPaddingLeft,
|
|
75
|
+
paddingRight: computedPaddingRight,
|
|
76
|
+
paddingTop: computedPaddingTop,
|
|
77
|
+
chartHeight: computedChartHeight,
|
|
78
|
+
maxValue: peak,
|
|
79
|
+
barWidth: 18,
|
|
80
|
+
barGap: 8,
|
|
81
|
+
};
|
|
82
|
+
}, [barSeriesA, barSeriesB, groupSpacing, height, labels.length, lineSeries, minGraphWidth]);
|
|
83
|
+
|
|
84
|
+
const toY = (value) => {
|
|
85
|
+
return paddingTop + ((maxValue - toNumber(value)) / Math.max(1, maxValue)) * chartHeight;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const groupCenterX = (index) => {
|
|
89
|
+
const usableWidth = graphWidth - paddingLeft - paddingRight;
|
|
90
|
+
const step = labels.length > 1 ? usableWidth / (labels.length - 1) : usableWidth / 2;
|
|
91
|
+
if (labels.length <= 1) return graphWidth / 2;
|
|
92
|
+
return paddingLeft + index * step;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const legendItems = legend || [
|
|
96
|
+
{ label: series[0]?.name || '2024', color: DEFAULT_COLORS[0] },
|
|
97
|
+
{ label: series[1]?.name || '2025', color: DEFAULT_COLORS[1] },
|
|
98
|
+
...(series[2] ? [{ label: series[2]?.name || 'Budget', color: DEFAULT_COLORS[2] }] : []),
|
|
99
|
+
];
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<View style={styles.card}>
|
|
103
|
+
{title ? <Text style={styles.title}>{title}</Text> : null}
|
|
104
|
+
|
|
105
|
+
<View style={styles.chartRow}>
|
|
106
|
+
<Svg width={50} height={height}>
|
|
107
|
+
{ticks.map((tick) => (
|
|
108
|
+
<SvgText
|
|
109
|
+
key={`tick-${tick}`}
|
|
110
|
+
x={46}
|
|
111
|
+
y={toY(tick) + 4}
|
|
112
|
+
fontSize="10"
|
|
113
|
+
fill="#506178"
|
|
114
|
+
textAnchor="end"
|
|
115
|
+
>
|
|
116
|
+
{compactAmount(Math.round(tick))}
|
|
117
|
+
</SvgText>
|
|
118
|
+
))}
|
|
119
|
+
</Svg>
|
|
120
|
+
|
|
121
|
+
<ScrollView horizontal showsHorizontalScrollIndicator={false}>
|
|
122
|
+
<Svg width={graphWidth} height={height}>
|
|
123
|
+
{ticks.map((tick) => (
|
|
124
|
+
<Line
|
|
125
|
+
key={`grid-${tick}`}
|
|
126
|
+
x1={paddingLeft}
|
|
127
|
+
x2={graphWidth - paddingRight}
|
|
128
|
+
y1={toY(tick)}
|
|
129
|
+
y2={toY(tick)}
|
|
130
|
+
stroke="#e5ecf5"
|
|
131
|
+
strokeWidth={1}
|
|
132
|
+
strokeDasharray="4 5"
|
|
133
|
+
/>
|
|
134
|
+
))}
|
|
135
|
+
|
|
136
|
+
{labels.map((label, index) => {
|
|
137
|
+
const cx = groupCenterX(index);
|
|
138
|
+
const valueA = toNumber(barSeriesA[index]);
|
|
139
|
+
const valueB = toNumber(barSeriesB[index]);
|
|
140
|
+
const valueLine = toNumber(lineSeries[index]);
|
|
141
|
+
|
|
142
|
+
const yA = toY(valueA);
|
|
143
|
+
const yB = toY(valueB);
|
|
144
|
+
const heightA = Math.max(4, paddingTop + chartHeight - yA);
|
|
145
|
+
const heightB = Math.max(4, paddingTop + chartHeight - yB);
|
|
146
|
+
|
|
147
|
+
const xBarA = cx - barWidth - barGap / 2;
|
|
148
|
+
const xBarB = cx + barGap / 2;
|
|
149
|
+
const showInsideA = heightA > 40;
|
|
150
|
+
const showInsideB = heightB > 40;
|
|
151
|
+
|
|
152
|
+
return (
|
|
153
|
+
<React.Fragment key={`group-${label}-${index}`}>
|
|
154
|
+
<Rect
|
|
155
|
+
x={xBarA}
|
|
156
|
+
y={yA}
|
|
157
|
+
width={barWidth}
|
|
158
|
+
height={heightA}
|
|
159
|
+
rx={4}
|
|
160
|
+
fill={DEFAULT_COLORS[0]}
|
|
161
|
+
/>
|
|
162
|
+
{showInsideA ? (
|
|
163
|
+
<SvgText
|
|
164
|
+
x={xBarA + barWidth / 2}
|
|
165
|
+
y={yA + heightA / 2}
|
|
166
|
+
fill="#fff"
|
|
167
|
+
fontSize="8"
|
|
168
|
+
fontWeight="700"
|
|
169
|
+
textAnchor="middle"
|
|
170
|
+
transform={`rotate(-90 ${xBarA + barWidth / 2} ${yA + heightA / 2})`}
|
|
171
|
+
>
|
|
172
|
+
{formatNumber(valueA)}
|
|
173
|
+
</SvgText>
|
|
174
|
+
) : (
|
|
175
|
+
<SvgText
|
|
176
|
+
x={xBarA + barWidth / 2}
|
|
177
|
+
y={yA - 4}
|
|
178
|
+
fill="#4f6076"
|
|
179
|
+
fontSize="8"
|
|
180
|
+
fontWeight="700"
|
|
181
|
+
textAnchor="middle"
|
|
182
|
+
>
|
|
183
|
+
{compactAmount(valueA)}
|
|
184
|
+
</SvgText>
|
|
185
|
+
)}
|
|
186
|
+
|
|
187
|
+
<Rect
|
|
188
|
+
x={xBarB}
|
|
189
|
+
y={yB}
|
|
190
|
+
width={barWidth}
|
|
191
|
+
height={heightB}
|
|
192
|
+
rx={4}
|
|
193
|
+
fill={DEFAULT_COLORS[1]}
|
|
194
|
+
/>
|
|
195
|
+
{showInsideB ? (
|
|
196
|
+
<SvgText
|
|
197
|
+
x={xBarB + barWidth / 2}
|
|
198
|
+
y={yB + heightB / 2}
|
|
199
|
+
fill="#fff"
|
|
200
|
+
fontSize="8"
|
|
201
|
+
fontWeight="700"
|
|
202
|
+
textAnchor="middle"
|
|
203
|
+
transform={`rotate(-90 ${xBarB + barWidth / 2} ${yB + heightB / 2})`}
|
|
204
|
+
>
|
|
205
|
+
{formatNumber(valueB)}
|
|
206
|
+
</SvgText>
|
|
207
|
+
) : (
|
|
208
|
+
<SvgText
|
|
209
|
+
x={xBarB + barWidth / 2}
|
|
210
|
+
y={yB - 4}
|
|
211
|
+
fill="#4f6076"
|
|
212
|
+
fontSize="8"
|
|
213
|
+
fontWeight="700"
|
|
214
|
+
textAnchor="middle"
|
|
215
|
+
>
|
|
216
|
+
{compactAmount(valueB)}
|
|
217
|
+
</SvgText>
|
|
218
|
+
)}
|
|
219
|
+
|
|
220
|
+
{index > 0 && lineSeries.length > 0 ? (
|
|
221
|
+
<Line
|
|
222
|
+
x1={groupCenterX(index - 1)}
|
|
223
|
+
y1={toY(lineSeries[index - 1])}
|
|
224
|
+
x2={cx}
|
|
225
|
+
y2={toY(valueLine)}
|
|
226
|
+
stroke={DEFAULT_COLORS[2]}
|
|
227
|
+
strokeWidth={2}
|
|
228
|
+
strokeDasharray="6 4"
|
|
229
|
+
/>
|
|
230
|
+
) : null}
|
|
231
|
+
|
|
232
|
+
{lineSeries.length > 0 ? (
|
|
233
|
+
<>
|
|
234
|
+
<Circle cx={cx} cy={toY(valueLine)} r={4.6} fill={DEFAULT_COLORS[2]} />
|
|
235
|
+
<SvgText
|
|
236
|
+
x={cx}
|
|
237
|
+
y={toY(valueLine) - 8}
|
|
238
|
+
fill="#223247"
|
|
239
|
+
fontSize="9"
|
|
240
|
+
fontWeight="700"
|
|
241
|
+
textAnchor="middle"
|
|
242
|
+
>
|
|
243
|
+
{compactAmount(valueLine)}
|
|
244
|
+
</SvgText>
|
|
245
|
+
</>
|
|
246
|
+
) : null}
|
|
247
|
+
|
|
248
|
+
<SvgText
|
|
249
|
+
x={cx}
|
|
250
|
+
y={height - 14}
|
|
251
|
+
fontSize="10"
|
|
252
|
+
fill="#4f6076"
|
|
253
|
+
textAnchor="middle"
|
|
254
|
+
transform={`rotate(-35 ${cx} ${height - 14})`}
|
|
255
|
+
>
|
|
256
|
+
{label}
|
|
257
|
+
</SvgText>
|
|
258
|
+
</React.Fragment>
|
|
259
|
+
);
|
|
260
|
+
})}
|
|
261
|
+
</Svg>
|
|
262
|
+
</ScrollView>
|
|
263
|
+
</View>
|
|
264
|
+
|
|
265
|
+
<View style={styles.legendRow}>
|
|
266
|
+
{legendItems.map((item) => (
|
|
267
|
+
<View key={`legend-${item.label}`} style={styles.legendItem}>
|
|
268
|
+
<View style={[styles.legendDot, { backgroundColor: item.color }]} />
|
|
269
|
+
<Text style={styles.legendLabel}>{item.label}</Text>
|
|
270
|
+
</View>
|
|
271
|
+
))}
|
|
272
|
+
</View>
|
|
273
|
+
</View>
|
|
274
|
+
);
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
const styles = StyleSheet.create({
|
|
278
|
+
card: {
|
|
279
|
+
backgroundColor: '#fff',
|
|
280
|
+
borderWidth: 1,
|
|
281
|
+
borderColor: '#d4deed',
|
|
282
|
+
borderRadius: 14,
|
|
283
|
+
padding: 10,
|
|
284
|
+
marginBottom: 12,
|
|
285
|
+
},
|
|
286
|
+
title: {
|
|
287
|
+
fontSize: 13,
|
|
288
|
+
fontWeight: '800',
|
|
289
|
+
color: '#1c2f47',
|
|
290
|
+
marginBottom: 8,
|
|
291
|
+
},
|
|
292
|
+
chartRow: {
|
|
293
|
+
flexDirection: 'row',
|
|
294
|
+
alignItems: 'flex-start',
|
|
295
|
+
},
|
|
296
|
+
legendRow: {
|
|
297
|
+
marginTop: 8,
|
|
298
|
+
flexDirection: 'row',
|
|
299
|
+
flexWrap: 'wrap',
|
|
300
|
+
},
|
|
301
|
+
legendItem: {
|
|
302
|
+
flexDirection: 'row',
|
|
303
|
+
alignItems: 'center',
|
|
304
|
+
marginRight: 14,
|
|
305
|
+
marginBottom: 4,
|
|
306
|
+
},
|
|
307
|
+
legendDot: {
|
|
308
|
+
width: 8,
|
|
309
|
+
height: 8,
|
|
310
|
+
borderRadius: 4,
|
|
311
|
+
marginRight: 6,
|
|
312
|
+
},
|
|
313
|
+
legendLabel: {
|
|
314
|
+
fontSize: 11,
|
|
315
|
+
color: '#4f6076',
|
|
316
|
+
fontWeight: '600',
|
|
317
|
+
},
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
export default CompactAxisBarLineChart;
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
|
+
import { ScrollView, StyleSheet, Text, View } from 'react-native';
|
|
3
|
+
import Svg, { Circle, Line, Polyline, Text as SvgText } from 'react-native-svg';
|
|
4
|
+
import { formatNumber } from '../utils/formatNumber';
|
|
5
|
+
|
|
6
|
+
const DEFAULT_COLORS = ['#F29F45', '#2E7DD1', '#2A9D8F', '#C96C5A'];
|
|
7
|
+
|
|
8
|
+
const toNumber = (value) => {
|
|
9
|
+
const numeric = Number(value);
|
|
10
|
+
return Number.isFinite(numeric) ? numeric : 0;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const makeTicks = (minValue, maxValue, count = 3) => {
|
|
14
|
+
if (count < 2) return [maxValue];
|
|
15
|
+
const diff = maxValue - minValue;
|
|
16
|
+
if (diff === 0) return [maxValue, maxValue, maxValue];
|
|
17
|
+
|
|
18
|
+
return Array.from({ length: count }, (_, index) => {
|
|
19
|
+
const ratio = index / (count - 1);
|
|
20
|
+
return maxValue - diff * ratio;
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const CompactAxisLineChart = ({
|
|
25
|
+
data,
|
|
26
|
+
title,
|
|
27
|
+
legend,
|
|
28
|
+
height = 210,
|
|
29
|
+
minGraphWidth = 340,
|
|
30
|
+
pointSpacing = 72,
|
|
31
|
+
}) => {
|
|
32
|
+
const labels = data?.labels || [];
|
|
33
|
+
const series = data?.series || [];
|
|
34
|
+
|
|
35
|
+
const hasData = labels.length > 0 && series.length > 0;
|
|
36
|
+
if (!hasData) return null;
|
|
37
|
+
|
|
38
|
+
const {
|
|
39
|
+
yMin,
|
|
40
|
+
yMax,
|
|
41
|
+
graphWidth,
|
|
42
|
+
ticks,
|
|
43
|
+
paddingLeft,
|
|
44
|
+
paddingRight,
|
|
45
|
+
paddingTop,
|
|
46
|
+
chartHeight,
|
|
47
|
+
xStep,
|
|
48
|
+
} = useMemo(() => {
|
|
49
|
+
const allValues = series.flatMap((item) => (item?.data || []).map(toNumber));
|
|
50
|
+
const minValue = Math.min(...allValues);
|
|
51
|
+
const maxValue = Math.max(...allValues);
|
|
52
|
+
const pad = Math.max(1, (maxValue - minValue) * 0.16);
|
|
53
|
+
const adjustedMin = minValue - pad;
|
|
54
|
+
const adjustedMax = maxValue + pad;
|
|
55
|
+
|
|
56
|
+
const computedPaddingLeft = 8;
|
|
57
|
+
const computedPaddingRight = 12;
|
|
58
|
+
const computedPaddingTop = 16;
|
|
59
|
+
const computedPaddingBottom = 38;
|
|
60
|
+
const computedChartHeight = height - computedPaddingTop - computedPaddingBottom;
|
|
61
|
+
const computedGraphWidth = Math.max(minGraphWidth, labels.length * pointSpacing);
|
|
62
|
+
const computedXStep =
|
|
63
|
+
labels.length > 1
|
|
64
|
+
? (computedGraphWidth - computedPaddingLeft - computedPaddingRight) / (labels.length - 1)
|
|
65
|
+
: 0;
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
yMin: adjustedMin,
|
|
69
|
+
yMax: adjustedMax,
|
|
70
|
+
graphWidth: computedGraphWidth,
|
|
71
|
+
ticks: makeTicks(adjustedMin, adjustedMax, 3),
|
|
72
|
+
paddingLeft: computedPaddingLeft,
|
|
73
|
+
paddingRight: computedPaddingRight,
|
|
74
|
+
paddingTop: computedPaddingTop,
|
|
75
|
+
chartHeight: computedChartHeight,
|
|
76
|
+
xStep: computedXStep,
|
|
77
|
+
};
|
|
78
|
+
}, [height, labels.length, minGraphWidth, pointSpacing, series]);
|
|
79
|
+
|
|
80
|
+
const toY = (value) => {
|
|
81
|
+
return paddingTop + ((yMax - toNumber(value)) / Math.max(1, yMax - yMin)) * chartHeight;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const toX = (index) => {
|
|
85
|
+
if (labels.length <= 1) {
|
|
86
|
+
return graphWidth / 2;
|
|
87
|
+
}
|
|
88
|
+
return paddingLeft + index * xStep;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const legendItems = legend || series.map((item, index) => ({
|
|
92
|
+
label: item?.name || `Series ${index + 1}`,
|
|
93
|
+
color: DEFAULT_COLORS[index % DEFAULT_COLORS.length],
|
|
94
|
+
}));
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<View style={styles.card}>
|
|
98
|
+
{title ? <Text style={styles.title}>{title}</Text> : null}
|
|
99
|
+
<View style={styles.chartRow}>
|
|
100
|
+
<Svg width={48} height={height}>
|
|
101
|
+
{ticks.map((tick) => (
|
|
102
|
+
<SvgText
|
|
103
|
+
key={`tick-${tick}`}
|
|
104
|
+
x={44}
|
|
105
|
+
y={toY(tick) + 4}
|
|
106
|
+
fontSize="10"
|
|
107
|
+
fill="#506178"
|
|
108
|
+
textAnchor="end"
|
|
109
|
+
>
|
|
110
|
+
{formatNumber(Math.round(tick))}
|
|
111
|
+
</SvgText>
|
|
112
|
+
))}
|
|
113
|
+
</Svg>
|
|
114
|
+
|
|
115
|
+
<ScrollView horizontal showsHorizontalScrollIndicator={false}>
|
|
116
|
+
<Svg width={graphWidth} height={height}>
|
|
117
|
+
{ticks.map((tick) => (
|
|
118
|
+
<Line
|
|
119
|
+
key={`grid-${tick}`}
|
|
120
|
+
x1={paddingLeft}
|
|
121
|
+
x2={graphWidth - paddingRight}
|
|
122
|
+
y1={toY(tick)}
|
|
123
|
+
y2={toY(tick)}
|
|
124
|
+
stroke="#e5ecf5"
|
|
125
|
+
strokeWidth={1}
|
|
126
|
+
strokeDasharray="4 5"
|
|
127
|
+
/>
|
|
128
|
+
))}
|
|
129
|
+
|
|
130
|
+
{series.map((item, seriesIndex) => {
|
|
131
|
+
const color = legendItems[seriesIndex]?.color || DEFAULT_COLORS[seriesIndex % DEFAULT_COLORS.length];
|
|
132
|
+
const points = (item?.data || [])
|
|
133
|
+
.map((value, index) => `${toX(index)},${toY(value)}`)
|
|
134
|
+
.join(' ');
|
|
135
|
+
|
|
136
|
+
return (
|
|
137
|
+
<React.Fragment key={`series-${item?.name || seriesIndex}`}>
|
|
138
|
+
<Polyline
|
|
139
|
+
points={points}
|
|
140
|
+
fill="none"
|
|
141
|
+
stroke={color}
|
|
142
|
+
strokeWidth={3}
|
|
143
|
+
strokeLinecap="round"
|
|
144
|
+
strokeLinejoin="round"
|
|
145
|
+
strokeDasharray={seriesIndex === 0 ? '7 6' : undefined}
|
|
146
|
+
/>
|
|
147
|
+
{(item?.data || []).map((value, index) => (
|
|
148
|
+
<React.Fragment key={`point-${seriesIndex}-${index}`}>
|
|
149
|
+
<Circle cx={toX(index)} cy={toY(value)} r={4.8} fill={color} />
|
|
150
|
+
<SvgText
|
|
151
|
+
x={toX(index)}
|
|
152
|
+
y={toY(value) - 8}
|
|
153
|
+
fontSize="9"
|
|
154
|
+
fontWeight="700"
|
|
155
|
+
fill="#223247"
|
|
156
|
+
textAnchor="middle"
|
|
157
|
+
>
|
|
158
|
+
{formatNumber(toNumber(value))}
|
|
159
|
+
</SvgText>
|
|
160
|
+
</React.Fragment>
|
|
161
|
+
))}
|
|
162
|
+
</React.Fragment>
|
|
163
|
+
);
|
|
164
|
+
})}
|
|
165
|
+
|
|
166
|
+
{labels.map((label, index) => (
|
|
167
|
+
<SvgText
|
|
168
|
+
key={`x-${label}-${index}`}
|
|
169
|
+
x={toX(index)}
|
|
170
|
+
y={height - 14}
|
|
171
|
+
fontSize="10"
|
|
172
|
+
fill="#4f6076"
|
|
173
|
+
textAnchor="middle"
|
|
174
|
+
transform={`rotate(-35 ${toX(index)} ${height - 14})`}
|
|
175
|
+
>
|
|
176
|
+
{label}
|
|
177
|
+
</SvgText>
|
|
178
|
+
))}
|
|
179
|
+
</Svg>
|
|
180
|
+
</ScrollView>
|
|
181
|
+
</View>
|
|
182
|
+
|
|
183
|
+
<View style={styles.legendRow}>
|
|
184
|
+
{legendItems.map((item) => (
|
|
185
|
+
<View key={`legend-${item.label}`} style={styles.legendItem}>
|
|
186
|
+
<View style={[styles.legendDot, { backgroundColor: item.color }]} />
|
|
187
|
+
<Text style={styles.legendLabel}>{item.label}</Text>
|
|
188
|
+
</View>
|
|
189
|
+
))}
|
|
190
|
+
</View>
|
|
191
|
+
</View>
|
|
192
|
+
);
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
const styles = StyleSheet.create({
|
|
196
|
+
card: {
|
|
197
|
+
backgroundColor: '#fff',
|
|
198
|
+
borderWidth: 1,
|
|
199
|
+
borderColor: '#d4deed',
|
|
200
|
+
borderRadius: 14,
|
|
201
|
+
padding: 10,
|
|
202
|
+
marginBottom: 12,
|
|
203
|
+
},
|
|
204
|
+
title: {
|
|
205
|
+
fontSize: 13,
|
|
206
|
+
fontWeight: '800',
|
|
207
|
+
color: '#1c2f47',
|
|
208
|
+
marginBottom: 8,
|
|
209
|
+
},
|
|
210
|
+
chartRow: {
|
|
211
|
+
flexDirection: 'row',
|
|
212
|
+
alignItems: 'flex-start',
|
|
213
|
+
},
|
|
214
|
+
legendRow: {
|
|
215
|
+
marginTop: 8,
|
|
216
|
+
flexDirection: 'row',
|
|
217
|
+
flexWrap: 'wrap',
|
|
218
|
+
},
|
|
219
|
+
legendItem: {
|
|
220
|
+
flexDirection: 'row',
|
|
221
|
+
alignItems: 'center',
|
|
222
|
+
marginRight: 14,
|
|
223
|
+
marginBottom: 4,
|
|
224
|
+
},
|
|
225
|
+
legendDot: {
|
|
226
|
+
width: 8,
|
|
227
|
+
height: 8,
|
|
228
|
+
borderRadius: 4,
|
|
229
|
+
marginRight: 6,
|
|
230
|
+
},
|
|
231
|
+
legendLabel: {
|
|
232
|
+
fontSize: 11,
|
|
233
|
+
color: '#4f6076',
|
|
234
|
+
fontWeight: '600',
|
|
235
|
+
},
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
export default CompactAxisLineChart;
|
package/src/index.jsx
CHANGED
|
@@ -3,6 +3,7 @@ import { View, ActivityIndicator } from 'react-native';
|
|
|
3
3
|
import SafeScreen from './components/SafeScreen';
|
|
4
4
|
import ReportListScreen from './screens/ReportListScreen';
|
|
5
5
|
import Report1Screen from './screens/Report1Screen';
|
|
6
|
+
import Report1ModernScreen from './screens/Report1ModernScreen';
|
|
6
7
|
import Report2Screen from './screens/Report2Screen';
|
|
7
8
|
import Report2ModernScreen from './screens/Report2ModernScreen';
|
|
8
9
|
import Report3Screen from './screens/Report3Screen';
|
|
@@ -63,7 +64,7 @@ const AnalyticsReports = ({ config, onExit }) => {
|
|
|
63
64
|
);
|
|
64
65
|
}
|
|
65
66
|
|
|
66
|
-
|
|
67
|
+
if (active === '1A') {
|
|
67
68
|
return (
|
|
68
69
|
<SafeScreen>
|
|
69
70
|
<Report1AScreen
|
|
@@ -75,6 +76,18 @@ const AnalyticsReports = ({ config, onExit }) => {
|
|
|
75
76
|
);
|
|
76
77
|
}
|
|
77
78
|
|
|
79
|
+
if (active === '1N1') {
|
|
80
|
+
return (
|
|
81
|
+
<SafeScreen>
|
|
82
|
+
<Report1ModernScreen
|
|
83
|
+
endpoint={config.report1.url}
|
|
84
|
+
token={config.token}
|
|
85
|
+
onBack={() => setActive(null)}
|
|
86
|
+
/>
|
|
87
|
+
</SafeScreen>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
78
91
|
if (active === '2A') {
|
|
79
92
|
return (
|
|
80
93
|
<SafeScreen>
|