@rokkit/chart 1.3.5 → 1.3.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.
@@ -0,0 +1,113 @@
1
+ export function mean(ys) {
2
+ if (!ys.length) return null
3
+ return ys.reduce((s, v) => s + v, 0) / ys.length
4
+ }
5
+
6
+ export function median(ys) {
7
+ if (!ys.length) return null
8
+ const sorted = [...ys].sort((a, b) => a - b)
9
+ const mid = Math.floor(sorted.length / 2)
10
+ return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2
11
+ }
12
+
13
+ /** Least-squares line. Returns { m, b } or null (< 2 points or zero x-variance). */
14
+ export function linearRegression(xs, ys) {
15
+ const n = xs.length
16
+ if (n < 2) return null
17
+ const mx = mean(xs)
18
+ const my = mean(ys)
19
+ let num = 0
20
+ let den = 0
21
+ for (let i = 0; i < n; i++) {
22
+ const dx = xs[i] - mx
23
+ num += dx * (ys[i] - my)
24
+ den += dx * dx
25
+ }
26
+ if (den === 0) return null
27
+ const m = num / den
28
+ return { m, b: my - m * mx }
29
+ }
30
+
31
+ /** Trailing simple moving average; length preserved (partial window at the head). */
32
+ export function movingAverage(ys, window) {
33
+ const w = Math.max(1, Math.floor(window))
34
+ const out = []
35
+ for (let i = 0; i < ys.length; i++) {
36
+ const start = Math.max(0, i - w + 1)
37
+ const slice = ys.slice(start, i + 1)
38
+ out.push(slice.reduce((s, v) => s + v, 0) / slice.length)
39
+ }
40
+ return out
41
+ }
42
+
43
+ /** Exponential moving average; alpha = alpha ?? 2/(span+1). Length preserved. */
44
+ export function ema(ys, { span, alpha } = {}) {
45
+ if (!ys.length) return []
46
+ const a = alpha ?? 2 / ((span ?? 7) + 1)
47
+ const out = [ys[0]]
48
+ for (let i = 1; i < ys.length; i++) out.push(a * ys[i] + (1 - a) * out[i - 1])
49
+ return out
50
+ }
51
+
52
+ /** Fit y = a·e^(bx) via log-linear least squares. Null if any y ≤ 0 or < 2 points. */
53
+ export function expRegression(xs, ys) {
54
+ if (xs.length < 2) return null
55
+ if (ys.some((v) => v <= 0)) return null
56
+ const reg = linearRegression(xs, ys.map((v) => Math.log(v)))
57
+ if (!reg) return null
58
+ return { a: Math.exp(reg.b), b: reg.m }
59
+ }
60
+
61
+ function normalize(method) {
62
+ if (typeof method === 'number') return { type: 'value', value: method }
63
+ if (typeof method === 'string') return { type: method === 'mean' ? 'avg' : method }
64
+ if (method && typeof method === 'object' && method.type)
65
+ return { ...method, type: method.type === 'mean' ? 'avg' : method.type }
66
+ return null
67
+ }
68
+
69
+ /**
70
+ * Compute a trend for `rows` on channels {x, y}.
71
+ * @returns {{kind:'constant', value:number} | {kind:'series', values:number[]} | null}
72
+ */
73
+ export function computeTrend(rows, channels, method) {
74
+ const cfg = normalize(method)
75
+ if (!cfg || !Array.isArray(rows) || rows.length === 0) return null
76
+ const { x, y } = channels
77
+ const ys = rows.map((r) => Number(r[y]))
78
+ const xs = rows.map((r, i) => {
79
+ const v = Number(r[x])
80
+ return Number.isFinite(v) ? v : i
81
+ })
82
+
83
+ switch (cfg.type) {
84
+ case 'avg':
85
+ return { kind: 'constant', value: mean(ys) }
86
+ case 'median':
87
+ return { kind: 'constant', value: median(ys) }
88
+ case 'min':
89
+ return { kind: 'constant', value: Math.min(...ys) }
90
+ case 'max':
91
+ return { kind: 'constant', value: Math.max(...ys) }
92
+ case 'value':
93
+ return typeof cfg.value === 'number' ? { kind: 'constant', value: cfg.value } : null
94
+ case 'linear': {
95
+ const reg = linearRegression(xs, ys)
96
+ if (!reg) return null
97
+ return { kind: 'series', values: xs.map((v) => reg.m * v + reg.b) }
98
+ }
99
+ case 'ma': {
100
+ if (!cfg.window) return null
101
+ return { kind: 'series', values: movingAverage(ys, cfg.window) }
102
+ }
103
+ case 'ema':
104
+ return { kind: 'series', values: ema(ys, { span: cfg.span, alpha: cfg.alpha }) }
105
+ case 'exp': {
106
+ const reg = expRegression(xs, ys)
107
+ if (!reg) return null
108
+ return { kind: 'series', values: xs.map((v) => reg.a * Math.exp(reg.b * v)) }
109
+ }
110
+ default:
111
+ return null
112
+ }
113
+ }
@@ -1,3 +1,3 @@
1
1
  # Patterns
2
2
 
3
- These patterns are inspired from https://superdesigner.co/tools/svg-backgrounds
3
+ These patterns are inspired from <https://superdesigner.co/tools/svg-backgrounds>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rokkit/chart",
3
- "version": "1.3.5",
3
+ "version": "1.3.7",
4
4
  "type": "module",
5
5
  "description": "Data-driven chart components",
6
6
  "repository": {