@obitrain/charts 0.9.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/LICENSE +21 -0
- package/README.md +125 -0
- package/lib/commonjs/Charts/BottomAxis/AxisLine.js +38 -0
- package/lib/commonjs/Charts/BottomAxis/AxisLine.js.map +1 -0
- package/lib/commonjs/Charts/BottomAxis/BottomAxis.js +45 -0
- package/lib/commonjs/Charts/BottomAxis/BottomAxis.js.map +1 -0
- package/lib/commonjs/Charts/BottomAxis/Tick.js +43 -0
- package/lib/commonjs/Charts/BottomAxis/Tick.js.map +1 -0
- package/lib/commonjs/Charts/BottomAxis/index.js +27 -0
- package/lib/commonjs/Charts/BottomAxis/index.js.map +1 -0
- package/lib/commonjs/Charts/Cursor.js +50 -0
- package/lib/commonjs/Charts/Cursor.js.map +1 -0
- package/lib/commonjs/Charts/Linechart/index.js +55 -0
- package/lib/commonjs/Charts/Linechart/index.js.map +1 -0
- package/lib/commonjs/Charts/ZoomableLinechart/ScalablePath.js +43 -0
- package/lib/commonjs/Charts/ZoomableLinechart/ScalablePath.js.map +1 -0
- package/lib/commonjs/Charts/ZoomableLinechart/index.js +43 -0
- package/lib/commonjs/Charts/ZoomableLinechart/index.js.map +1 -0
- package/lib/commonjs/Charts/gesture.js +185 -0
- package/lib/commonjs/Charts/gesture.js.map +1 -0
- package/lib/commonjs/Charts/graphUtils.js +131 -0
- package/lib/commonjs/Charts/graphUtils.js.map +1 -0
- package/lib/commonjs/Charts/index.js +151 -0
- package/lib/commonjs/Charts/index.js.map +1 -0
- package/lib/commonjs/Charts/maths.js +195 -0
- package/lib/commonjs/Charts/maths.js.map +1 -0
- package/lib/commonjs/Charts/types.js +6 -0
- package/lib/commonjs/Charts/types.js.map +1 -0
- package/lib/commonjs/dateUtils.js +151 -0
- package/lib/commonjs/dateUtils.js.map +1 -0
- package/lib/commonjs/index.js +39 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/textUtils.js +21 -0
- package/lib/commonjs/textUtils.js.map +1 -0
- package/lib/module/Charts/BottomAxis/AxisLine.js +34 -0
- package/lib/module/Charts/BottomAxis/AxisLine.js.map +1 -0
- package/lib/module/Charts/BottomAxis/BottomAxis.js +40 -0
- package/lib/module/Charts/BottomAxis/BottomAxis.js.map +1 -0
- package/lib/module/Charts/BottomAxis/Tick.js +37 -0
- package/lib/module/Charts/BottomAxis/Tick.js.map +1 -0
- package/lib/module/Charts/BottomAxis/index.js +6 -0
- package/lib/module/Charts/BottomAxis/index.js.map +1 -0
- package/lib/module/Charts/Cursor.js +46 -0
- package/lib/module/Charts/Cursor.js.map +1 -0
- package/lib/module/Charts/Linechart/index.js +50 -0
- package/lib/module/Charts/Linechart/index.js.map +1 -0
- package/lib/module/Charts/ZoomableLinechart/ScalablePath.js +39 -0
- package/lib/module/Charts/ZoomableLinechart/ScalablePath.js.map +1 -0
- package/lib/module/Charts/ZoomableLinechart/index.js +33 -0
- package/lib/module/Charts/ZoomableLinechart/index.js.map +1 -0
- package/lib/module/Charts/gesture.js +176 -0
- package/lib/module/Charts/gesture.js.map +1 -0
- package/lib/module/Charts/graphUtils.js +121 -0
- package/lib/module/Charts/graphUtils.js.map +1 -0
- package/lib/module/Charts/index.js +10 -0
- package/lib/module/Charts/index.js.map +1 -0
- package/lib/module/Charts/maths.js +187 -0
- package/lib/module/Charts/maths.js.map +1 -0
- package/lib/module/Charts/types.js +4 -0
- package/lib/module/Charts/types.js.map +1 -0
- package/lib/module/dateUtils.js +142 -0
- package/lib/module/dateUtils.js.map +1 -0
- package/lib/module/index.js +6 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/textUtils.js +16 -0
- package/lib/module/textUtils.js.map +1 -0
- package/lib/typescript/src/Charts/BottomAxis/AxisLine.d.ts +15 -0
- package/lib/typescript/src/Charts/BottomAxis/AxisLine.d.ts.map +1 -0
- package/lib/typescript/src/Charts/BottomAxis/BottomAxis.d.ts +17 -0
- package/lib/typescript/src/Charts/BottomAxis/BottomAxis.d.ts.map +1 -0
- package/lib/typescript/src/Charts/BottomAxis/Tick.d.ts +14 -0
- package/lib/typescript/src/Charts/BottomAxis/Tick.d.ts.map +1 -0
- package/lib/typescript/src/Charts/BottomAxis/index.d.ts +4 -0
- package/lib/typescript/src/Charts/BottomAxis/index.d.ts.map +1 -0
- package/lib/typescript/src/Charts/Cursor.d.ts +14 -0
- package/lib/typescript/src/Charts/Cursor.d.ts.map +1 -0
- package/lib/typescript/src/Charts/Linechart/index.d.ts +25 -0
- package/lib/typescript/src/Charts/Linechart/index.d.ts.map +1 -0
- package/lib/typescript/src/Charts/ZoomableLinechart/ScalablePath.d.ts +14 -0
- package/lib/typescript/src/Charts/ZoomableLinechart/ScalablePath.d.ts.map +1 -0
- package/lib/typescript/src/Charts/ZoomableLinechart/index.d.ts +23 -0
- package/lib/typescript/src/Charts/ZoomableLinechart/index.d.ts.map +1 -0
- package/lib/typescript/src/Charts/gesture.d.ts +54 -0
- package/lib/typescript/src/Charts/gesture.d.ts.map +1 -0
- package/lib/typescript/src/Charts/graphUtils.d.ts +43 -0
- package/lib/typescript/src/Charts/graphUtils.d.ts.map +1 -0
- package/lib/typescript/src/Charts/index.d.ts +14 -0
- package/lib/typescript/src/Charts/index.d.ts.map +1 -0
- package/lib/typescript/src/Charts/maths.d.ts +19 -0
- package/lib/typescript/src/Charts/maths.d.ts.map +1 -0
- package/lib/typescript/src/Charts/types.d.ts +14 -0
- package/lib/typescript/src/Charts/types.d.ts.map +1 -0
- package/lib/typescript/src/dateUtils.d.ts +37 -0
- package/lib/typescript/src/dateUtils.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +5 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/textUtils.d.ts +3 -0
- package/lib/typescript/src/textUtils.d.ts.map +1 -0
- package/package.json +226 -0
- package/src/Charts/BottomAxis/AxisLine.tsx +40 -0
- package/src/Charts/BottomAxis/BottomAxis.tsx +41 -0
- package/src/Charts/BottomAxis/Tick.tsx +54 -0
- package/src/Charts/BottomAxis/index.tsx +3 -0
- package/src/Charts/Cursor.tsx +51 -0
- package/src/Charts/Linechart/index.tsx +79 -0
- package/src/Charts/ZoomableLinechart/ScalablePath.tsx +49 -0
- package/src/Charts/ZoomableLinechart/index.tsx +47 -0
- package/src/Charts/gesture.ts +254 -0
- package/src/Charts/graphUtils.ts +198 -0
- package/src/Charts/index.tsx +32 -0
- package/src/Charts/maths.ts +245 -0
- package/src/Charts/types.ts +16 -0
- package/src/dateUtils.ts +163 -0
- package/src/index.tsx +4 -0
- package/src/textUtils.ts +21 -0
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import type { PathCommand, Vector } from '@shopify/react-native-skia';
|
|
2
|
+
import { PathVerb, vec } from '@shopify/react-native-skia';
|
|
3
|
+
|
|
4
|
+
// code from William Candillon
|
|
5
|
+
|
|
6
|
+
const round = (value: number, precision = 0): number => {
|
|
7
|
+
'worklet';
|
|
8
|
+
|
|
9
|
+
const p = Math.pow(10, precision);
|
|
10
|
+
return Math.round(value * p) / p;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// https://stackoverflow.com/questions/27176423/function-to-solve-cubic-equation-analytically
|
|
14
|
+
const cuberoot = (x: number): number => {
|
|
15
|
+
'worklet';
|
|
16
|
+
|
|
17
|
+
const y = Math.pow(Math.abs(x), 1 / 3);
|
|
18
|
+
return x < 0 ? -y : y;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const solveCubic = (a: number, b: number, c: number, d: number): number[] => {
|
|
22
|
+
'worklet';
|
|
23
|
+
|
|
24
|
+
if (Math.abs(a) < 1e-8) {
|
|
25
|
+
// Quadratic case, ax^2+bx+c=0
|
|
26
|
+
a = b;
|
|
27
|
+
b = c;
|
|
28
|
+
c = d;
|
|
29
|
+
if (Math.abs(a) < 1e-8) {
|
|
30
|
+
// Linear case, ax+b=0
|
|
31
|
+
a = b;
|
|
32
|
+
b = c;
|
|
33
|
+
if (Math.abs(a) < 1e-8) {
|
|
34
|
+
// Degenerate case
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
return [-b / a];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const D = b * b - 4 * a * c;
|
|
41
|
+
if (Math.abs(D) < 1e-8) return [-b / (2 * a)];
|
|
42
|
+
if (D > 0)
|
|
43
|
+
return [(-b + Math.sqrt(D)) / (2 * a), (-b - Math.sqrt(D)) / (2 * a)];
|
|
44
|
+
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Convert to depressed cubic t^3+pt+q = 0 (subst x = t - b/3a)
|
|
49
|
+
const p = (3 * a * c - b * b) / (3 * a * a);
|
|
50
|
+
const q = (2 * b * b * b - 9 * a * b * c + 27 * a * a * d) / (27 * a * a * a);
|
|
51
|
+
let roots: number[] = [];
|
|
52
|
+
|
|
53
|
+
if (Math.abs(p) < 1e-8) {
|
|
54
|
+
// p = 0 -> t^3 = -q -> t = -q^1/3
|
|
55
|
+
roots = [cuberoot(-q)];
|
|
56
|
+
} else if (Math.abs(q) < 1e-8) {
|
|
57
|
+
// q = 0 -> t^3 + pt = 0 -> t(t^2+p)=0
|
|
58
|
+
roots = [0].concat(p < 0 ? [Math.sqrt(-p), -Math.sqrt(-p)] : []);
|
|
59
|
+
} else {
|
|
60
|
+
const D = (q * q) / 4 + (p * p * p) / 27;
|
|
61
|
+
if (Math.abs(D) < 1e-8) {
|
|
62
|
+
// D = 0 -> two roots
|
|
63
|
+
roots = [(-1.5 * q) / p, (3 * q) / p];
|
|
64
|
+
} else if (D > 0) {
|
|
65
|
+
// Only one real root
|
|
66
|
+
const u = cuberoot(-q / 2 - Math.sqrt(D));
|
|
67
|
+
roots = [u - p / (3 * u)];
|
|
68
|
+
} else {
|
|
69
|
+
// D < 0, three roots, but needs to use complex numbers/trigonometric solution
|
|
70
|
+
const u = 2 * Math.sqrt(-p / 3);
|
|
71
|
+
const t = Math.acos((3 * q) / p / u) / 3; // D < 0 implies p < 0 and acos argument in [-1..1]
|
|
72
|
+
const k = (2 * Math.PI) / 3;
|
|
73
|
+
roots = [u * Math.cos(t), u * Math.cos(t - k), u * Math.cos(t - 2 * k)];
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
// Convert back from depressed cubic
|
|
80
|
+
for (let i = 0; i < roots.length; i++) roots[i]! -= b / (3 * a);
|
|
81
|
+
|
|
82
|
+
return roots;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// const cubicBezier = (
|
|
86
|
+
// t: number,
|
|
87
|
+
// from: number,
|
|
88
|
+
// c1: number,
|
|
89
|
+
// c2: number,
|
|
90
|
+
// to: number
|
|
91
|
+
// ): number => {
|
|
92
|
+
// 'worklet';
|
|
93
|
+
|
|
94
|
+
// const term = 1 - t;
|
|
95
|
+
// const a = 1 * term ** 3 * t ** 0 * from;
|
|
96
|
+
// const b = 3 * term ** 2 * t ** 1 * c1;
|
|
97
|
+
// const c = 3 * term ** 1 * t ** 2 * c2;
|
|
98
|
+
// const d = 1 * term ** 0 * t ** 3 * to;
|
|
99
|
+
// return a + b + c + d;
|
|
100
|
+
// };
|
|
101
|
+
|
|
102
|
+
const cubicSolve = function (
|
|
103
|
+
t: number,
|
|
104
|
+
p0: number,
|
|
105
|
+
p1: number,
|
|
106
|
+
p2: number,
|
|
107
|
+
p3: number
|
|
108
|
+
) {
|
|
109
|
+
'worklet';
|
|
110
|
+
return (
|
|
111
|
+
(1 - t) * (1 - t) * (1 - t) * p0 +
|
|
112
|
+
3 * (1 - t) * (1 - t) * t * p1 +
|
|
113
|
+
3 * (1 - t) * t * t * p2 +
|
|
114
|
+
t * t * t * p3
|
|
115
|
+
);
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
export const cubicBezierYForX = (
|
|
119
|
+
x: number,
|
|
120
|
+
a: Vector,
|
|
121
|
+
b: Vector,
|
|
122
|
+
c: Vector,
|
|
123
|
+
d: Vector,
|
|
124
|
+
precision = 2
|
|
125
|
+
): number => {
|
|
126
|
+
'worklet';
|
|
127
|
+
|
|
128
|
+
const pa = -a.x + 3 * b.x - 3 * c.x + d.x;
|
|
129
|
+
const pb = 3 * a.x - 6 * b.x + 3 * c.x;
|
|
130
|
+
const pc = -3 * a.x + 3 * b.x;
|
|
131
|
+
const pd = a.x - x;
|
|
132
|
+
const ts = solveCubic(pa, pb, pc, pd)
|
|
133
|
+
.map((root) => round(root, precision))
|
|
134
|
+
.filter((root) => root >= 0 && root <= 1);
|
|
135
|
+
const t = ts[0];
|
|
136
|
+
if (t == null) return 0;
|
|
137
|
+
// return cubicBezier(t, a.y, b.y, c.y, d.y);
|
|
138
|
+
return cubicSolve(t, a.y, b.y, c.y, d.y);
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
interface Cubic {
|
|
142
|
+
from: Vector;
|
|
143
|
+
c1: Vector;
|
|
144
|
+
c2: Vector;
|
|
145
|
+
to: Vector;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export const selectCurve = (
|
|
149
|
+
cmds: PathCommand[],
|
|
150
|
+
x: number
|
|
151
|
+
): Cubic | undefined => {
|
|
152
|
+
'worklet';
|
|
153
|
+
|
|
154
|
+
let from: Vector = vec(0, 0);
|
|
155
|
+
for (let i = 0; i < cmds.length; i++) {
|
|
156
|
+
const cmd = cmds[i];
|
|
157
|
+
if (cmd == null) {
|
|
158
|
+
return undefined;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (cmd[0] === PathVerb.Move) {
|
|
162
|
+
from = vec(cmd[1], cmd[2]);
|
|
163
|
+
} else if (cmd[0] === PathVerb.Cubic) {
|
|
164
|
+
const c1 = vec(cmd[1], cmd[2]);
|
|
165
|
+
const c2 = vec(cmd[3], cmd[4]);
|
|
166
|
+
const to = vec(cmd[5], cmd[6]);
|
|
167
|
+
if (x >= from.x && x <= to.x) {
|
|
168
|
+
return {
|
|
169
|
+
from,
|
|
170
|
+
c1,
|
|
171
|
+
c2,
|
|
172
|
+
to,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
from = to;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return undefined;
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Convert commands to cubic bezier
|
|
184
|
+
*/
|
|
185
|
+
export const commandsToBezier = (cmds: PathCommand[]): PathCommand[] => {
|
|
186
|
+
'worklet';
|
|
187
|
+
const normCmds: PathCommand[] = [];
|
|
188
|
+
let x = 0,
|
|
189
|
+
y = 0;
|
|
190
|
+
for (let i = 0; i < cmds.length; i++) {
|
|
191
|
+
const currCmd = cmds[i];
|
|
192
|
+
if (currCmd === undefined)
|
|
193
|
+
throw new Error(`No command found at index ${i}`);
|
|
194
|
+
|
|
195
|
+
let normCmd;
|
|
196
|
+
const cmd = currCmd[0];
|
|
197
|
+
if (cmd === undefined) throw new Error('Got undefined command');
|
|
198
|
+
|
|
199
|
+
if (cmd === PathVerb.Line) {
|
|
200
|
+
normCmd = [
|
|
201
|
+
PathVerb.Cubic,
|
|
202
|
+
x,
|
|
203
|
+
y,
|
|
204
|
+
currCmd[1]!,
|
|
205
|
+
currCmd[2]!,
|
|
206
|
+
currCmd[1]!,
|
|
207
|
+
currCmd[2]!,
|
|
208
|
+
];
|
|
209
|
+
} else {
|
|
210
|
+
normCmd = currCmd;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
x = normCmd[normCmd.length - 2]!;
|
|
214
|
+
y = normCmd[normCmd.length - 1]!;
|
|
215
|
+
|
|
216
|
+
normCmds.push(normCmd);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return normCmds;
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
export const getYForX = (
|
|
223
|
+
cmds: PathCommand[],
|
|
224
|
+
x: number,
|
|
225
|
+
precision = 2
|
|
226
|
+
): number | undefined => {
|
|
227
|
+
'worklet';
|
|
228
|
+
const cmdsNorm = commandsToBezier(cmds);
|
|
229
|
+
const c = selectCurve(cmdsNorm, x);
|
|
230
|
+
if (c == null) return undefined;
|
|
231
|
+
|
|
232
|
+
return cubicBezierYForX(x, c.from, c.c1, c.c2, c.to, precision);
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
// See Path/getPath2D
|
|
236
|
+
|
|
237
|
+
import { type SkPoint } from '@shopify/react-native-skia';
|
|
238
|
+
|
|
239
|
+
export const magnitude = (p: SkPoint): number =>
|
|
240
|
+
Math.sqrt(p.x * p.x + p.y * p.y);
|
|
241
|
+
|
|
242
|
+
export const normalize = (p: SkPoint) => {
|
|
243
|
+
const m = magnitude(p);
|
|
244
|
+
return { x: p.x / m, y: p.y / m } as SkPoint;
|
|
245
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { SharedValue } from 'react-native-reanimated';
|
|
2
|
+
import type { GraphData } from './graphUtils';
|
|
3
|
+
|
|
4
|
+
export type LineGraphType = GraphData; // Omit<GraphData, 'path'> & { path: RPath };
|
|
5
|
+
|
|
6
|
+
export type DataPoint = {
|
|
7
|
+
x: number;
|
|
8
|
+
y: number;
|
|
9
|
+
value: number;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type AnimatedDot = {
|
|
13
|
+
x: SharedValue<number>;
|
|
14
|
+
y: SharedValue<number>;
|
|
15
|
+
opacity: SharedValue<number>;
|
|
16
|
+
};
|
package/src/dateUtils.ts
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
export type DateRange = 'day' | 'month' | 'trimester' | 'year' | 'all';
|
|
2
|
+
|
|
3
|
+
export const getMonthInterval = function (
|
|
4
|
+
month: number,
|
|
5
|
+
nbMonths: number
|
|
6
|
+
): string {
|
|
7
|
+
if (month <= 0) throw new Error('Months should start at 1');
|
|
8
|
+
return (month - (month % nbMonths) + 1).toString().padStart(2, '0');
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Get the date interval based on the date range.
|
|
13
|
+
* For example, for the date 2021-01-01:
|
|
14
|
+
* - day: 2021-01-01
|
|
15
|
+
* - month: 2021-01
|
|
16
|
+
* - trimester: 2021-01
|
|
17
|
+
* - year: 2021
|
|
18
|
+
* @param date
|
|
19
|
+
* @param dateRange
|
|
20
|
+
* @returns
|
|
21
|
+
*/
|
|
22
|
+
export const getDateInterval = function (
|
|
23
|
+
date: Date,
|
|
24
|
+
dateRange: DateRange
|
|
25
|
+
): string {
|
|
26
|
+
const year = date.getFullYear();
|
|
27
|
+
const month = date.getMonth() + 1;
|
|
28
|
+
const day = date.getDate().toString().padStart(2, '0');
|
|
29
|
+
|
|
30
|
+
const monthStr = month.toString().padStart(2, '0');
|
|
31
|
+
|
|
32
|
+
switch (dateRange) {
|
|
33
|
+
case 'day':
|
|
34
|
+
return `${year}-${monthStr}-${day}`;
|
|
35
|
+
case 'month':
|
|
36
|
+
return `${year}-${monthStr}`;
|
|
37
|
+
case 'trimester':
|
|
38
|
+
return `${year}-${getMonthInterval(month, 3)}`;
|
|
39
|
+
case 'year':
|
|
40
|
+
return `${year}`;
|
|
41
|
+
}
|
|
42
|
+
throw new Error('Invalid date range');
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
//@ts-ignore
|
|
46
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
47
|
+
const fmtData = function (data: [Date, number][], dateRange: DateRange) {
|
|
48
|
+
const _dataFmt = new Map<string, [number, number]>();
|
|
49
|
+
for (const [date, value] of data) {
|
|
50
|
+
const _dateStr = getDateInterval(date, dateRange);
|
|
51
|
+
if (!_dataFmt.has(_dateStr))
|
|
52
|
+
_dataFmt.set(_dateStr, [date.getTime(), value]);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return [..._dataFmt.values()];
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Groups an array of Date objects by a specific time interval (day, month, trimester, year)
|
|
60
|
+
* and calculates the earliest and latest dates within each interval.
|
|
61
|
+
* For instance, given the dates [2021-01-01, 2021-01-25, 2021-03-01, 2021-10-01]:
|
|
62
|
+
* - month: { '2021-01': [2021-01-01, 2021-01-25],
|
|
63
|
+
* '2021-03': [2021-03-01, 2021-03-01],
|
|
64
|
+
'2021-10': [2021-10-01, 2021-10-01] }
|
|
65
|
+
|
|
66
|
+
* @param {Date[]} dates - An array of dates to group and find boundary dates for.
|
|
67
|
+
* @returns {Map<string, [Date, Date]>} - A map where each key represents an interval
|
|
68
|
+
* as a string (formatted according to the interval type, e.g., '2021', '2021-03', '2021-01-01'),
|
|
69
|
+
* and each value is a tuple containing the earliest and latest dates within that interval.
|
|
70
|
+
*/
|
|
71
|
+
export function getDateBoundaries(dates: Date[], dateRange: DateRange) {
|
|
72
|
+
const ranges = new Map<string, [Date, Date]>();
|
|
73
|
+
|
|
74
|
+
for (const date of dates) {
|
|
75
|
+
const _dateStr = getDateInterval(date, dateRange);
|
|
76
|
+
const _range = ranges.get(_dateStr);
|
|
77
|
+
if (!_range) ranges.set(_dateStr, [date, date]);
|
|
78
|
+
else if (date > _range[1]) ranges.set(_dateStr, [_range[0], date]);
|
|
79
|
+
}
|
|
80
|
+
return ranges;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function sampleDates(
|
|
84
|
+
dates: Date[],
|
|
85
|
+
sampleSize: number,
|
|
86
|
+
indexOffset: number = 0
|
|
87
|
+
): { dates: Date[]; indexes: number[] } {
|
|
88
|
+
if (sampleSize <= 0 || dates.length === 0) {
|
|
89
|
+
return { dates: [], indexes: [] };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (sampleSize >= dates.length) {
|
|
93
|
+
return {
|
|
94
|
+
dates,
|
|
95
|
+
indexes: Array.from({ length: dates.length }, (_, i) => indexOffset + i),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Calculate total time span and interval
|
|
100
|
+
const firstItem = dates[0]!;
|
|
101
|
+
const lastItem = dates[dates.length - 1]!;
|
|
102
|
+
const startTime = firstItem.getTime();
|
|
103
|
+
const endTime = lastItem.getTime();
|
|
104
|
+
const timeSpan = endTime - startTime;
|
|
105
|
+
const timeInterval = timeSpan / (sampleSize - 1);
|
|
106
|
+
|
|
107
|
+
const sampledDates: Date[] = [firstItem]; // Always include the first date
|
|
108
|
+
const sampledIndexes = [0];
|
|
109
|
+
let nextTime = startTime + timeInterval;
|
|
110
|
+
|
|
111
|
+
// Select dates closest to each time interval
|
|
112
|
+
for (let i = 1; i < sampleSize - 1; i++) {
|
|
113
|
+
let closestDate = dates[0];
|
|
114
|
+
let minDiff = Math.abs(startTime - nextTime);
|
|
115
|
+
let closestIndex = 0;
|
|
116
|
+
for (let j = 1; j < dates.length; j++) {
|
|
117
|
+
const diff = Math.abs(dates[j]!.getTime() - nextTime);
|
|
118
|
+
if (diff < minDiff) {
|
|
119
|
+
closestDate = dates[j];
|
|
120
|
+
closestIndex = j;
|
|
121
|
+
minDiff = diff;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
sampledDates.push(closestDate!);
|
|
125
|
+
sampledIndexes.push(closestIndex);
|
|
126
|
+
nextTime += timeInterval;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
sampledDates.push(lastItem); // Always include the last date
|
|
130
|
+
sampledIndexes.push(dates.length - 1);
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
dates: sampledDates,
|
|
134
|
+
indexes: sampledIndexes.map((x) => indexOffset + x),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function sampleDatesByRange(
|
|
139
|
+
dates: Date[],
|
|
140
|
+
sampleSize: number,
|
|
141
|
+
dateRange: DateRange
|
|
142
|
+
): Map<string, { dates: Date[]; indexes: number[] }> {
|
|
143
|
+
const boundaries = getDateBoundaries(dates, dateRange);
|
|
144
|
+
const rangedData = new Map<string, Date[]>();
|
|
145
|
+
for (let i = 0; i < dates.length; i++) {
|
|
146
|
+
let date = dates[i]!;
|
|
147
|
+
for (const [key, [minDate, maxDate]] of boundaries) {
|
|
148
|
+
if (date >= minDate && date <= maxDate) {
|
|
149
|
+
if (!rangedData.has(key)) rangedData.set(key, []);
|
|
150
|
+
rangedData.get(key)!.push(date);
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
const sampledData = new Map<string, { dates: Date[]; indexes: number[] }>();
|
|
156
|
+
let offset = 0;
|
|
157
|
+
for (const [key, value] of rangedData) {
|
|
158
|
+
sampledData.set(key, sampleDates(value, sampleSize, offset));
|
|
159
|
+
offset += value.length;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return sampledData;
|
|
163
|
+
}
|
package/src/index.tsx
ADDED
package/src/textUtils.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useDerivedValue,
|
|
3
|
+
useSharedValue,
|
|
4
|
+
type SharedValue,
|
|
5
|
+
} from 'react-native-reanimated';
|
|
6
|
+
|
|
7
|
+
const textToString = function (value: number) {
|
|
8
|
+
'worklet';
|
|
9
|
+
return value.toFixed(0).toString();
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const useSharedNumberToStr = function (
|
|
13
|
+
initValue: number = 0
|
|
14
|
+
): [SharedValue<number>, SharedValue<string>] {
|
|
15
|
+
const value = useSharedValue<number>(initValue);
|
|
16
|
+
const valueStr = useDerivedValue(() => {
|
|
17
|
+
return textToString(value.value);
|
|
18
|
+
}, [value]);
|
|
19
|
+
|
|
20
|
+
return [value, valueStr];
|
|
21
|
+
};
|