Skip to content

Commit 2406da8

Browse files
committed
test: cover smoothing utilities
1 parent b592eb9 commit 2406da8

3 files changed

Lines changed: 83 additions & 0 deletions

File tree

src/components/ChartContainer.jsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
import zoomPlugin from 'chartjs-plugin-zoom';
1616
import { ImageDown, Copy, FileDown } from 'lucide-react';
1717
import { getMinSteps } from "../utils/getMinSteps.js";
18+
import { applySmoothing } from "../utils/smoothing.js";
1819
import { useTranslation } from 'react-i18next';
1920

2021
ChartJS.register(
@@ -276,6 +277,10 @@ export default function ChartContainer({
276277
}
277278
});
278279
}
280+
const smoothingCfg = metric.smoothing || file.config?.smoothing;
281+
if (smoothingCfg) {
282+
points = applySmoothing(points, smoothingCfg);
283+
}
279284
metricsData[metric.name || metric.keyword] = points;
280285
});
281286

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { describe, it, expect } from 'vitest';
2+
import { movingAverage, exponentialMovingAverage, applySmoothing } from '../smoothing.js';
3+
4+
describe('smoothing utilities', () => {
5+
it('calculates moving average', () => {
6+
const data = [
7+
{ x: 0, y: 1 },
8+
{ x: 1, y: 2 },
9+
{ x: 2, y: 3 },
10+
{ x: 3, y: 4 }
11+
];
12+
const result = movingAverage(data, 2);
13+
expect(result.map(p => p.y)).toEqual([1, 1.5, 2.5, 3.5]);
14+
});
15+
16+
it('calculates exponential moving average', () => {
17+
const data = [
18+
{ x: 0, y: 1 },
19+
{ x: 1, y: 2 },
20+
{ x: 2, y: 3 },
21+
{ x: 3, y: 4 }
22+
];
23+
const result = exponentialMovingAverage(data, 0.5);
24+
const expected = [1, 1.5, 2.25, 3.125];
25+
result.forEach((p, i) => {
26+
expect(p.y).toBeCloseTo(expected[i]);
27+
});
28+
});
29+
30+
it('applies smoothing via config', () => {
31+
const data = [
32+
{ x: 0, y: 1 },
33+
{ x: 1, y: 2 },
34+
{ x: 2, y: 3 },
35+
{ x: 3, y: 4 }
36+
];
37+
const result = applySmoothing(data, { type: 'movingAverage', windowSize: 2 });
38+
expect(result[3].y).toBe(3.5);
39+
});
40+
});

src/utils/smoothing.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
export function movingAverage(data, windowSize = 5) {
2+
if (!Array.isArray(data) || windowSize <= 1) return data;
3+
const result = [];
4+
for (let i = 0; i < data.length; i++) {
5+
const start = Math.max(0, i - windowSize + 1);
6+
const subset = data.slice(start, i + 1);
7+
const sum = subset.reduce((acc, p) => acc + p.y, 0);
8+
const avg = sum / subset.length;
9+
result.push({ x: data[i].x, y: avg });
10+
}
11+
return result;
12+
}
13+
14+
export function exponentialMovingAverage(data, alpha = 0.5) {
15+
if (!Array.isArray(data) || data.length === 0) return data;
16+
const result = [];
17+
let prev = data[0].y;
18+
result.push({ x: data[0].x, y: prev });
19+
for (let i = 1; i < data.length; i++) {
20+
const smoothed = alpha * data[i].y + (1 - alpha) * prev;
21+
result.push({ x: data[i].x, y: smoothed });
22+
prev = smoothed;
23+
}
24+
return result;
25+
}
26+
27+
export function applySmoothing(data, config) {
28+
if (!config || !config.type) return data;
29+
switch (config.type) {
30+
case 'movingAverage':
31+
return movingAverage(data, config.windowSize || config.window || 5);
32+
case 'ema':
33+
case 'exponential':
34+
return exponentialMovingAverage(data, config.alpha || 0.5);
35+
default:
36+
return data;
37+
}
38+
}

0 commit comments

Comments
 (0)