@dhiraj0720/report1chart 3.0.5 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dhiraj0720/report1chart",
3
- "version": "3.0.5",
3
+ "version": "3.0.7",
4
4
  "main": "src/index.jsx",
5
5
  "scripts": {
6
6
  "test": "echo 'No tests'"
@@ -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,9 +3,11 @@ 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';
10
+ import Report3ModernScreen from './screens/Report3ModernScreen';
9
11
  import Report1AScreen from './screens/Report1AScreen';
10
12
  import Report2AScreen from './screens/Report2AScreen';
11
13
  import Report3AScreen from './screens/Report3AScreen';
@@ -62,7 +64,7 @@ const AnalyticsReports = ({ config, onExit }) => {
62
64
  );
63
65
  }
64
66
 
65
- if (active === '1A') {
67
+ if (active === '1A') {
66
68
  return (
67
69
  <SafeScreen>
68
70
  <Report1AScreen
@@ -74,6 +76,18 @@ const AnalyticsReports = ({ config, onExit }) => {
74
76
  );
75
77
  }
76
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
+
77
91
  if (active === '2A') {
78
92
  return (
79
93
  <SafeScreen>
@@ -98,6 +112,18 @@ if (active === '2N1') {
98
112
  );
99
113
  }
100
114
 
115
+ if (active === '3N1') {
116
+ return (
117
+ <SafeScreen>
118
+ <Report3ModernScreen
119
+ api={config.report3}
120
+ token={config.token}
121
+ onBack={() => setActive(null)}
122
+ />
123
+ </SafeScreen>
124
+ );
125
+ }
126
+
101
127
 
102
128
  // 👉 REPORT 2
103
129
  if (active === 2) {