@dhiraj0720/report1chart 2.2.9 → 2.3.0

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": "2.2.9",
3
+ "version": "2.3.0",
4
4
  "main": "src/index.jsx",
5
5
  "scripts": {
6
6
  "test": "echo 'No tests'"
@@ -5,6 +5,16 @@ const Cell = ({ children, bold }) => (
5
5
  <Text style={[styles.cell, bold && styles.bold]}>{children}</Text>
6
6
  );
7
7
 
8
+ const PercentCell = ({ value }) => {
9
+ const positive = value >= 0;
10
+ return (
11
+ <Text style={{ color: positive ? '#2e7d32' : '#d32f2f', fontWeight: '700' }}>
12
+ {positive ? '↑' : '↓'} {Math.abs(value)}%
13
+ </Text>
14
+ );
15
+ };
16
+
17
+
8
18
  const FrozenTableReport2 = ({ rows }) => (
9
19
  <View style={styles.container}>
10
20
  <View style={styles.frozen}>
@@ -18,9 +28,9 @@ const FrozenTableReport2 = ({ rows }) => (
18
28
  <View>
19
29
  <View style={styles.headerRow}>
20
30
  {[
21
- '2024 TEU','2025 TEU','TEU %',
22
- '2024 Kar','2025 Kar','Kar %',
23
- '2025 Bütçe','Bütçe %'
31
+ '2024 TEU', '2025 TEU', 'TEU %',
32
+ '2024 Kar', '2025 Kar', 'Kar %',
33
+ '2025 Bütçe', 'Bütçe %'
24
34
  ].map(h => <Cell key={h} bold>{h}</Cell>)}
25
35
  </View>
26
36
 
@@ -28,12 +38,21 @@ const FrozenTableReport2 = ({ rows }) => (
28
38
  <View key={i} style={styles.row}>
29
39
  <Cell>{r.teu2024}</Cell>
30
40
  <Cell>{r.teu2025}</Cell>
31
- <Cell>{r.teuChangePercent}%</Cell>
41
+ <Cell>
42
+ <PercentCell value={r.teuChangePercent} />
43
+ </Cell>
44
+
32
45
  <Cell>{r.profitUsd2024}</Cell>
33
46
  <Cell>{r.profitUsd2025}</Cell>
34
- <Cell>{r.profitChangePercent}%</Cell>
47
+ <Cell>
48
+ <PercentCell value={r.profitChangePercent} />
49
+ </Cell>
50
+
35
51
  <Cell>{r.budgetProfitUsd2025}</Cell>
36
- <Cell>{r.budgetChangePercent}%</Cell>
52
+
53
+ <Cell>
54
+ <PercentCell value={r.budgetChangePercent} />
55
+ </Cell>
37
56
  </View>
38
57
  ))}
39
58
  </View>
@@ -5,6 +5,15 @@ const Cell = ({ children, bold }) => (
5
5
  <Text style={[styles.cell, bold && styles.bold]}>{children}</Text>
6
6
  );
7
7
 
8
+ const PercentCell = ({ value }) => {
9
+ const positive = value >= 0;
10
+ return (
11
+ <Text style={{ color: positive ? '#2e7d32' : '#d32f2f', fontWeight: '700' }}>
12
+ {positive ? '↑' : '↓'} {Math.abs(value)}%
13
+ </Text>
14
+ );
15
+ };
16
+
8
17
  const FrozenTableReport3 = ({ rows }) => (
9
18
  <View style={styles.container}>
10
19
  <View style={styles.frozen}>
@@ -28,12 +37,21 @@ const FrozenTableReport3 = ({ rows }) => (
28
37
  <View key={i} style={styles.row}>
29
38
  <Cell>{r.loadCount2024}</Cell>
30
39
  <Cell>{r.loadCount2025}</Cell>
31
- <Cell>{r.loadCountChangePercent}%</Cell>
40
+
41
+ <Cell>
42
+ <PercentCell value={r.loadCountChangePercent} />
43
+ </Cell>
32
44
  <Cell>{r.revenueTl2024}</Cell>
33
45
  <Cell>{r.revenueTl2025}</Cell>
34
- <Cell>{r.revenueChangePercent}%</Cell>
46
+
47
+ <Cell>
48
+ <PercentCell value={r.revenueChangePercent} />
49
+ </Cell>
35
50
  <Cell>{r.budgetRevenueTl2025}</Cell>
36
- <Cell>{r.budgetChangePercent}%</Cell>
51
+
52
+ <Cell>
53
+ <PercentCell value={r.budgetChangePercent} />
54
+ </Cell>
37
55
  </View>
38
56
  ))}
39
57
  </View>
@@ -1,16 +1,30 @@
1
- import React from 'react';
1
+ import React, { useState } from 'react';
2
2
  import { View, Text, ScrollView } from 'react-native';
3
- import Svg, { Rect, Circle, Text as SvgText } from 'react-native-svg';
3
+ import Svg, {
4
+ Rect,
5
+ Line,
6
+ Circle,
7
+ Text as SvgText,
8
+ } from 'react-native-svg';
9
+ import { formatNumber } from '../utils/formatNumber';
4
10
 
5
11
  const SvgBarLineChart = ({ data }) => {
6
- if (!data?.series) return null;
12
+ if (!data?.series || !data.labels?.length) return null;
13
+
14
+ const [activePoint, setActivePoint] = useState(null);
15
+
7
16
 
8
17
  const width = Math.max(360, data.labels.length * 90);
9
18
  const height = 240;
10
19
  const padding = 40;
20
+ const chartHeight = height - padding * 2;
11
21
 
12
22
  const max = Math.max(...data.series.flatMap(s => s.data));
13
23
  const barWidth = 14;
24
+ const step = 70;
25
+
26
+ const scaleY = v => (v / max) * chartHeight;
27
+ const yPos = v => height - padding - scaleY(v);
14
28
 
15
29
  return (
16
30
  <View style={{ marginVertical: 16 }}>
@@ -19,56 +33,181 @@ const SvgBarLineChart = ({ data }) => {
19
33
  </Text>
20
34
 
21
35
  <ScrollView horizontal>
22
- <Svg width={width} height={height}>
36
+ <Svg width={width} height={height} onPress={() => setActivePoint(null)}>
37
+ {/* Y AXIS GRID + LABELS */}
38
+ {[0, 0.5, 1].map((p, i) => {
39
+ const y = height - padding - chartHeight * p;
40
+ return (
41
+ <React.Fragment key={i}>
42
+ <Line
43
+ x1={padding}
44
+ x2={width - padding}
45
+ y1={y}
46
+ y2={y}
47
+ stroke="#ccc"
48
+ strokeDasharray="4"
49
+ />
50
+ <SvgText
51
+ x={padding - 8}
52
+ y={y + 4}
53
+ fontSize="10"
54
+ textAnchor="end"
55
+ >
56
+ {formatNumber(max * p)}
57
+ </SvgText>
58
+ </React.Fragment>
59
+ );
60
+ })}
61
+
62
+ {/* BARS + BUDGET LINE */}
23
63
  {data.labels.map((label, i) => {
24
- const x = padding + i * 70;
64
+ const x = padding + i * step;
25
65
 
26
66
  return (
27
67
  <React.Fragment key={i}>
28
- {/* 2024 bar */}
68
+ {/* 2024 BAR */}
29
69
  <Rect
30
70
  x={x}
31
- y={height - (data.series[0].data[i] / max) * 150}
71
+ y={yPos(data.series[0].data[i])}
32
72
  width={barWidth}
33
- height={(data.series[0].data[i] / max) * 150}
73
+ height={scaleY(dat.series[0].data[i])}
34
74
  fill="#E07A3F"
75
+ onPress={() =>
76
+ setActivePoint({
77
+ x: x + barWidth / 2,
78
+ y: yPos(data.series[0].data[i]),
79
+ value: data.series[0].data[i],
80
+ })
81
+ }
35
82
  />
83
+ <SvgText
84
+ x={x + barWidth / 2}
85
+ y={yPos(data.series[0].data[i]) - 6}
86
+ fontSize="10"
87
+ textAnchor="middle"
88
+ >
89
+ {formatNumber(data.series[0].data[i])}
90
+ </SvgText>
36
91
 
37
- {/* 2025 bar */}
92
+ {/* 2025 BAR */}
38
93
  <Rect
39
94
  x={x + barWidth + 4}
40
- y={height - (data.series[1].data[i] / max) * 150}
95
+ y={yPos(data.series[1].data[i])}
41
96
  width={barWidth}
42
- height={(data.series[1].data[i] / max) * 150}
97
+ height={scaleY(data.series[1].data[i])}
43
98
  fill="#4E79A7"
99
+ onPress={() =>
100
+ setActivePoint({
101
+ x: x + barWidth + 4 + barWidth / 2,
102
+ y: yPos(data.series[1].data[i]),
103
+ value: data.series[1].data[i],
104
+ })
105
+ }
44
106
  />
107
+ <SvgText
108
+ x={x + barWidth + 4 + barWidth / 2}
109
+ y={yPos(data.series[1].data[i]) - 6}
110
+ fontSize="10"
111
+ textAnchor="middle"
112
+ >
113
+ {formatNumber(data.series[1].data[i])}
114
+ </SvgText>
45
115
 
46
- {/* Budget dot */}
116
+ {/* BUDGET DOT */}
47
117
  <Circle
48
- cx={x + barWidth}
49
- cy={height - (data.series[2].data[i] / max) * 150}
118
+ cx={x + barWidth + 2}
119
+ cy={yPos(data.series[2].data[i])}
50
120
  r={4}
51
121
  fill="#8AB6E8"
122
+ onPress={() =>
123
+ setActivePoint({
124
+ x: x + barWidth + 2,
125
+ y: yPos(data.series[2].data[i]),
126
+ value: data.series[2].data[i],
127
+ })
128
+ }
52
129
  />
53
130
 
131
+ {/* BUDGET VALUE */}
132
+ <SvgText
133
+ x={x + barWidth + 2}
134
+ y={yPos(data.series[2].data[i]) - 8}
135
+ fontSize="10"
136
+ textAnchor="middle"
137
+ >
138
+ {formatNumber(data.series[2].data[i])}
139
+ </SvgText>
140
+
141
+ {/* X LABEL */}
54
142
  <SvgText
55
143
  x={x + barWidth}
56
- y={height - 6}
144
+ y={height - 10}
57
145
  fontSize="10"
58
146
  textAnchor="middle"
147
+ transform={`rotate(-35 ${x + barWidth} ${height - 10})`}
59
148
  >
60
149
  {label}
61
150
  </SvgText>
62
151
  </React.Fragment>
63
152
  );
64
153
  })}
154
+
155
+ {activePoint && (
156
+ <>
157
+ <Rect
158
+ x={activePoint.x - 32}
159
+ y={activePoint.y - 44}
160
+ width={64}
161
+ height={26}
162
+ rx={6}
163
+ fill="#000"
164
+ opacity={0.8}
165
+ />
166
+ <SvgText
167
+ x={activePoint.x}
168
+ y={activePoint.y - 26}
169
+ fontSize="10"
170
+ fill="#fff"
171
+ textAnchor="middle"
172
+ >
173
+ {formatNumber(activePoint.value)}
174
+ </SvgText>
175
+ </>
176
+ )}
177
+
178
+
179
+ {/* BUDGET DOTTED LINE */}
180
+ {data.series[2].data.map((v, i) => {
181
+ if (i === data.series[2].data.length - 1) return null;
182
+ return (
183
+ <Line
184
+ key={`line-${i}`}
185
+ x1={padding + i * step + barWidth + 2}
186
+ y1={yPos(v)}
187
+ x2={padding + (i + 1) * step + barWidth + 2}
188
+ y2={yPos(data.series[2].data[i + 1])}
189
+ stroke="#8AB6E8"
190
+ strokeWidth={2}
191
+ strokeDasharray="4"
192
+ />
193
+ );
194
+ })}
65
195
  </Svg>
66
196
  </ScrollView>
67
197
 
198
+ {/* LEGEND */}
68
199
  <View style={{ flexDirection: 'row', marginTop: 8 }}>
69
200
  {data.series.map((s, i) => (
70
201
  <Text key={i} style={{ marginRight: 16 }}>
71
- ● {s.name}
202
+ <Text
203
+ style={{
204
+ color:
205
+ i === 0 ? '#E07A3F' : i === 1 ? '#4E79A7' : '#8AB6E8',
206
+ }}
207
+ >
208
+
209
+ </Text>{' '}
210
+ {s.name}
72
211
  </Text>
73
212
  ))}
74
213
  </View>
@@ -1,23 +1,35 @@
1
- import React from 'react';
1
+ import React, { useState } from 'react';
2
2
  import { View, Text, ScrollView } from 'react-native';
3
- import Svg, { Line, Circle, Text as SvgText } from 'react-native-svg';
3
+ import Svg, {
4
+ Line,
5
+ Circle,
6
+ Rect,
7
+ Text as SvgText,
8
+ } from 'react-native-svg';
9
+ import { formatNumber } from '../utils/formatNumber';
4
10
 
5
11
  const SvgLineChart = ({ data }) => {
6
- if (!data?.series) return null;
12
+ if (!data?.series || !data.labels?.length) return null;
13
+
14
+ const [activePoint, setActivePoint] = useState(null);
7
15
 
8
16
  const width = Math.max(360, data.labels.length * 80);
9
17
  const height = 220;
10
18
  const padding = 40;
19
+ const chartHeight = height - padding * 2;
11
20
 
12
21
  const values = data.series.flatMap(s => s.data);
13
22
  const max = Math.max(...values);
14
- const min = Math.min(...values);
23
+ const min = 0;
15
24
 
16
25
  const y = v =>
17
- height - padding -
18
- ((v - min) / (max - min || 1)) * (height - padding * 2);
26
+ height - padding - ((v - min) / (max - min || 1)) * chartHeight;
27
+
28
+ const xStep =
29
+ data.labels.length === 1
30
+ ? 0
31
+ : (width - padding * 2) / (data.labels.length - 1);
19
32
 
20
- const xStep = (width - padding * 2) / (data.labels.length - 1 || 1);
21
33
  const colors = ['#E07A3F', '#4E79A7'];
22
34
 
23
35
  return (
@@ -27,55 +39,121 @@ const SvgLineChart = ({ data }) => {
27
39
  </Text>
28
40
 
29
41
  <ScrollView horizontal>
30
- <Svg width={width} height={height}>
31
- {/* Grid */}
32
- {[0.25, 0.5, 0.75].map((p, i) => (
33
- <Line
34
- key={i}
35
- x1={padding}
36
- x2={width - padding}
37
- y1={padding + (height - padding * 2) * p}
38
- y2={padding + (height - padding * 2) * p}
39
- stroke="#ccc"
40
- strokeDasharray="4"
41
- />
42
- ))}
42
+ <Svg width={width} height={height} onPress={() => setActivePoint(null)}>
43
+ {/* GRID + Y LABELS */}
44
+ {[0.25, 0.5, 0.75].map((p, i) => {
45
+ const yPos = padding + chartHeight * p;
46
+ const labelValue = Math.round(max * (1 - p));
47
+
48
+ return (
49
+ <React.Fragment key={i}>
50
+ <Line
51
+ x1={padding}
52
+ x2={width - padding}
53
+ y1={yPos}
54
+ y2={yPos}
55
+ stroke="#ccc"
56
+ strokeDasharray="4"
57
+ />
58
+ <SvgText
59
+ x={padding - 8}
60
+ y={yPos + 4}
61
+ fontSize="10"
62
+ textAnchor="end"
63
+ >
64
+ {formatNumber(labelValue)}
65
+ </SvgText>
66
+ </React.Fragment>
67
+ );
68
+ })}
43
69
 
70
+ {/* LINES + DOTS */}
44
71
  {data.series.map((s, si) =>
45
72
  s.data.map((v, i) => {
46
73
  const x = padding + i * xStep;
47
74
  const yVal = y(v);
48
75
 
49
- if (i === s.data.length - 1) return null;
50
-
51
76
  return (
52
77
  <React.Fragment key={`${si}-${i}`}>
53
- <Line
54
- x1={x}
55
- y1={yVal}
56
- x2={x + xStep}
57
- y2={y(s.data[i + 1])}
58
- stroke={colors[si]}
59
- strokeWidth={2}
60
- strokeDasharray={si === 0 ? '4' : '0'}
78
+ {i < s.data.length - 1 && (
79
+ <Line
80
+ x1={x}
81
+ y1={yVal}
82
+ x2={padding + (i + 1) * xStep}
83
+ y2={y(s.data[i + 1])}
84
+ stroke={colors[si]}
85
+ strokeWidth={2}
86
+ strokeDasharray={si === 1 ? '4' : '0'}
87
+ />
88
+ )}
89
+
90
+ <Circle
91
+ cx={x}
92
+ cy={yVal}
93
+ r={5}
94
+ fill={colors[si]}
95
+ onPress={() =>
96
+ setActivePoint({ x, y: yVal, value: v })
97
+ }
61
98
  />
62
- <Circle cx={x} cy={yVal} r={4} fill={colors[si]} />
99
+
63
100
  <SvgText
64
101
  x={x}
65
102
  y={yVal - 8}
66
103
  fontSize="10"
67
104
  textAnchor="middle"
68
105
  >
69
- {v.toLocaleString()}
106
+ {formatNumber(v)}
70
107
  </SvgText>
71
108
  </React.Fragment>
72
109
  );
73
110
  })
74
111
  )}
112
+
113
+ {/* TOOLTIP (ONLY ONCE) */}
114
+ {activePoint && (
115
+ <>
116
+ <Rect
117
+ x={activePoint.x - 30}
118
+ y={activePoint.y - 40}
119
+ width={60}
120
+ height={24}
121
+ rx={6}
122
+ fill="#000"
123
+ opacity={0.75}
124
+ />
125
+ <SvgText
126
+ x={activePoint.x}
127
+ y={activePoint.y - 24}
128
+ fontSize="10"
129
+ fill="#fff"
130
+ textAnchor="middle"
131
+ >
132
+ {formatNumber(activePoint.value)}
133
+ </SvgText>
134
+ </>
135
+ )}
136
+
137
+ {/* X LABELS */}
138
+ {data.labels.map((label, i) => {
139
+ const x = padding + i * xStep;
140
+ return (
141
+ <SvgText
142
+ key={i}
143
+ x={x}
144
+ y={height - 10}
145
+ fontSize="10"
146
+ textAnchor="middle"
147
+ transform={`rotate(-35 ${x} ${height - 10})`}
148
+ >
149
+ {label}
150
+ </SvgText>
151
+ );
152
+ })}
75
153
  </Svg>
76
154
  </ScrollView>
77
155
 
78
- {/* Legend */}
156
+ {/* LEGEND */}
79
157
  <View style={{ flexDirection: 'row', marginTop: 8 }}>
80
158
  {data.series.map((s, i) => (
81
159
  <Text key={i} style={{ marginRight: 16, color: colors[i] }}>
@@ -0,0 +1,12 @@
1
+ export const formatNumber = (value) => {
2
+ if (value >= 1_000_000_000) {
3
+ return (value / 1_000_000_000).toFixed(1) + 'bn';
4
+ }
5
+ if (value >= 1_000_000) {
6
+ return (value / 1_000_000).toFixed(1) + 'M';
7
+ }
8
+ if (value >= 1_000) {
9
+ return (value / 1_000).toFixed(0) + 'k';
10
+ }
11
+ return value.toString();
12
+ };