source: frontend/src/utils/timeSeries.js@ 2a034a2

Last change on this file since 2a034a2 was b3d5fed, checked in by Andrej <asumanovski@…>, 2 months ago

training graph developed

  • Property mode set to 100644
File size: 2.7 KB
Line 
1// Utilities for bucketing time-series data and computing percentage changes.
2
3function startOfDay(d) {
4 return new Date(d.getFullYear(), d.getMonth(), d.getDate());
5}
6
7function startOfMonth(d) {
8 return new Date(d.getFullYear(), d.getMonth(), 1);
9}
10
11function startOfYear(d) {
12 return new Date(d.getFullYear(), 0, 1);
13}
14
15// ISO week start (Monday)
16function startOfISOWeek(d) {
17 const date = startOfDay(d);
18 const day = date.getDay(); // 0..6 (Sun..Sat)
19 const diff = (day === 0 ? -6 : 1) - day;
20 date.setDate(date.getDate() + diff);
21 return startOfDay(date);
22}
23
24function bucketKey(date, granularity) {
25 const d = new Date(date);
26 if (Number.isNaN(d.getTime())) return null;
27
28 let s;
29 switch (granularity) {
30 case "daily":
31 s = startOfDay(d);
32 break;
33 case "weekly":
34 s = startOfISOWeek(d);
35 break;
36 case "monthly":
37 s = startOfMonth(d);
38 break;
39 case "yearly":
40 s = startOfYear(d);
41 break;
42 default:
43 s = startOfDay(d);
44 }
45
46 return s.getTime();
47}
48
49export function sumByTimeBucket(items, granularity, getDate, getValue) {
50 const map = new Map();
51
52 for (const it of items ?? []) {
53 const rawDate = getDate(it);
54 const k = bucketKey(rawDate, granularity);
55 if (k == null) continue;
56
57 const v = Number(getValue(it) ?? 0);
58 if (!Number.isFinite(v)) continue;
59
60 map.set(k, (map.get(k) ?? 0) + v);
61 }
62
63 const points = Array.from(map.entries())
64 .sort((a, b) => a[0] - b[0])
65 .map(([ts, value]) => ({ ts, value }));
66
67 return points;
68}
69
70export function percentChangeSeries(points) {
71 // Output: [{ ts, value: pctChange, base: sumValue }]
72 const out = [];
73 let prev = null;
74
75 for (const p of points ?? []) {
76 const curr = Number(p.value ?? 0);
77 let pct = 0;
78
79 if (prev == null) {
80 pct = 0;
81 } else if (prev === 0) {
82 // Avoid exploding infinity when previous is 0.
83 // Define: if curr is also 0 -> 0%, else -> 100%.
84 pct = curr === 0 ? 0 : 100;
85 } else {
86 pct = ((curr - prev) / prev) * 100;
87 }
88
89 out.push({ ts: p.ts, value: pct, base: curr });
90 prev = curr;
91 }
92
93 return out;
94}
95
96export function formatBucketLabel(ts, granularity) {
97 const d = new Date(ts);
98 if (Number.isNaN(d.getTime())) return "—";
99
100 const optsDay = { month: "short", day: "2-digit" };
101 const optsMonth = { year: "numeric", month: "short" };
102
103 switch (granularity) {
104 case "daily":
105 return d.toLocaleDateString(undefined, optsDay);
106 case "weekly":
107 return `Wk of ${d.toLocaleDateString(undefined, optsDay)}`;
108 case "monthly":
109 return d.toLocaleDateString(undefined, optsMonth);
110 case "yearly":
111 return String(d.getFullYear());
112 default:
113 return d.toLocaleDateString();
114 }
115}
116
Note: See TracBrowser for help on using the repository browser.