@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
|
@@ -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>
|
|
41
|
+
<Cell>
|
|
42
|
+
<PercentCell value={r.teuChangePercent} />
|
|
43
|
+
</Cell>
|
|
44
|
+
|
|
32
45
|
<Cell>{r.profitUsd2024}</Cell>
|
|
33
46
|
<Cell>{r.profitUsd2025}</Cell>
|
|
34
|
-
<Cell>
|
|
47
|
+
<Cell>
|
|
48
|
+
<PercentCell value={r.profitChangePercent} />
|
|
49
|
+
</Cell>
|
|
50
|
+
|
|
35
51
|
<Cell>{r.budgetProfitUsd2025}</Cell>
|
|
36
|
-
|
|
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
|
-
|
|
40
|
+
|
|
41
|
+
<Cell>
|
|
42
|
+
<PercentCell value={r.loadCountChangePercent} />
|
|
43
|
+
</Cell>
|
|
32
44
|
<Cell>{r.revenueTl2024}</Cell>
|
|
33
45
|
<Cell>{r.revenueTl2025}</Cell>
|
|
34
|
-
|
|
46
|
+
|
|
47
|
+
<Cell>
|
|
48
|
+
<PercentCell value={r.revenueChangePercent} />
|
|
49
|
+
</Cell>
|
|
35
50
|
<Cell>{r.budgetRevenueTl2025}</Cell>
|
|
36
|
-
|
|
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, {
|
|
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 *
|
|
64
|
+
const x = padding + i * step;
|
|
25
65
|
|
|
26
66
|
return (
|
|
27
67
|
<React.Fragment key={i}>
|
|
28
|
-
{/* 2024
|
|
68
|
+
{/* 2024 BAR */}
|
|
29
69
|
<Rect
|
|
30
70
|
x={x}
|
|
31
|
-
y={
|
|
71
|
+
y={yPos(data.series[0].data[i])}
|
|
32
72
|
width={barWidth}
|
|
33
|
-
height={(
|
|
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
|
|
92
|
+
{/* 2025 BAR */}
|
|
38
93
|
<Rect
|
|
39
94
|
x={x + barWidth + 4}
|
|
40
|
-
y={
|
|
95
|
+
y={yPos(data.series[1].data[i])}
|
|
41
96
|
width={barWidth}
|
|
42
|
-
height={(data.series[1].data[i]
|
|
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
|
-
{/*
|
|
116
|
+
{/* BUDGET DOT */}
|
|
47
117
|
<Circle
|
|
48
|
-
cx={x + barWidth}
|
|
49
|
-
cy={
|
|
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 -
|
|
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
|
-
|
|
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, {
|
|
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 =
|
|
23
|
+
const min = 0;
|
|
15
24
|
|
|
16
25
|
const y = v =>
|
|
17
|
-
height - padding -
|
|
18
|
-
|
|
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
|
-
{/*
|
|
32
|
-
{[0.25, 0.5, 0.75].map((p, i) =>
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
<
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
{/*
|
|
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
|
+
};
|