Skip to content

Commit 19017e6

Browse files
committed
Fix - Internal lib - Fix issues with calcLinearProgression method
1 parent 3c2bffe commit 19017e6

File tree

2 files changed

+69
-81
lines changed

2 files changed

+69
-81
lines changed

src/lib.js

Lines changed: 33 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -843,56 +843,39 @@ export function convertConfigColors(config, seen = new WeakSet()) {
843843
}
844844

845845
export function calcLinearProgression(plots) {
846-
let x1, y1, x2, y2;
847-
const len = plots.length;
848-
849-
if (!plots || plots.length === 0) {
850-
return {
851-
x1: 0,
852-
y1: 0,
853-
x2: 0,
854-
y2: 0,
855-
slope: 0,
856-
trend: 0
857-
}
858-
}
859-
860-
let sumX = 0;
861-
let sumY = 0;
862-
let sumXY = 0;
863-
let sumXX = 0;
864-
for (const { x, y } of plots) {
865-
sumX += x;
866-
sumY += y;
867-
sumXY += x * y;
868-
sumXX += x * x;
869-
}
870-
const slope = (len * sumXY - sumX * sumY) / (len * sumXX - sumX * sumX);
871-
const intercept = (sumY - slope * sumX) / len;
872-
x1 = plots[0].x;
873-
x2 = plots[len - 1].x;
874-
y1 = slope * x1 + intercept;
875-
y2 = slope * x2 + intercept;
876-
877-
const trend = calcPercentageTrend(plots.map(p => p.value));
878-
879-
return { x1, y1, x2, y2, slope, trend };
880-
}
881-
882-
export function calcPercentageTrend(arr) {
883-
const initialNumber = arr[0];
884-
const lastNumber = arr[arr.length - 1];
885-
const overallChange = lastNumber - initialNumber;
886-
887-
let totalMagnitude = 0;
888-
889-
for (let i = 1; i < arr.length; i++) {
890-
const difference = Math.abs(arr[i] - arr[i - 1]);
891-
totalMagnitude += difference;
892-
}
893-
894-
const percentageTrend = (overallChange / totalMagnitude);
895-
return isNaN(percentageTrend) ? 0 : percentageTrend;
846+
const len = plots?.length ?? 0;
847+
if (len < 2) return { x1: 0, y1: 0, x2: 0, y2: 0, slope: 0, trend: 0 };
848+
849+
let sx = 0, sy = 0, sxy = 0, sxx = 0;
850+
for (const { x, y } of plots) { sx += x; sy += y; sxy += x * y; sxx += x * x; }
851+
const denomPx = len * sxx - sx * sx || 1;
852+
const slopePx = (len * sxy - sx * sy) / denomPx;
853+
const interceptPx = (sy - slopePx * sx) / len;
854+
855+
const x1 = plots[0].x;
856+
const x2 = plots[len - 1].x;
857+
const y1 = slopePx * x1 + interceptPx;
858+
const y2 = slopePx * x2 + interceptPx;
859+
860+
let vx = 0, vy = 0, vxy = 0, vxx = 0;
861+
for (let i = 0; i < len; i += 1) {
862+
vx += i;
863+
vy += plots[i].value;
864+
vxy += i * plots[i].value;
865+
vxx += i * i;
866+
}
867+
const denomV = len * vxx - vx * vx || 1;
868+
const slopeV = (len * vxy - vx * vy) / denomV;
869+
const interceptV = (vy - slopeV * vx) / len;
870+
871+
const vStart = interceptV;
872+
const vEnd = slopeV * (len - 1) + interceptV;
873+
874+
const EPS = 1e-9;
875+
const scale = Math.max(Math.abs(vStart), Math.abs(vy / len), Math.abs(vEnd), EPS);
876+
const trend = (vEnd - vStart) / scale;
877+
878+
return { x1, y1, x2, y2, slope: slopePx, trend };
896879
}
897880

898881
export function calcMedian(arr) {

tests/lib.test.js

Lines changed: 36 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import {
1717
buildInterLineAreas,
1818
calcLinearProgression,
1919
calcMedian,
20-
calcPercentageTrend,
2120
calcPolygonPoints,
2221
calcStarPoints,
2322
calcTrend,
@@ -783,42 +782,48 @@ describe("createSmoothPath", () => {
783782
});
784783
});
785784

786-
describe("calcPercentageTrend", () => {
787-
test("returns a growth trend from an array of numbers", () => {
788-
const arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
789-
expect(calcPercentageTrend(arr)).toBe(1);
790-
791-
const arr2 = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0];
792-
expect(calcPercentageTrend(arr2)).toBe(-1);
785+
describe("calcLinearProgression", () => {
786+
test("creates a linear progression object from an array of coordinates", () => {
787+
const d1 = [
788+
{ x: 1, y: 1, value: 1 },
789+
{ x: 2, y: 2, value: 2 },
790+
];
791+
expect(calcLinearProgression(d1)).toStrictEqual({
792+
x1: 1,
793+
y1: 1,
794+
x2: 2,
795+
y2: 2,
796+
slope: 1,
797+
trend: 0.5,
798+
});
793799

794-
const arr3 = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
795-
expect(calcPercentageTrend(arr3)).toBe(0);
800+
const d2 = [
801+
{ x: 1, y: 1, value: 1 },
802+
{ x: 2, y: 0, value: 0 },
803+
];
796804

797-
const arr4 = [1, 2, 1.5, 1.25, 1.125, 1.065];
798-
expect(calcPercentageTrend(arr4)).toBe(0.033591731266149845);
799-
});
800-
});
805+
expect(calcLinearProgression(d2)).toStrictEqual({
806+
x1: 1,
807+
y1: 1,
808+
x2: 2,
809+
y2: 0,
810+
slope: -1,
811+
trend: -1,
812+
});
801813

802-
describe("calcLinearProgression", () => {
803-
test("creates a linear progression object from an array of coordinates", () => {
804-
const plots = [
814+
const d3 = [
805815
{ x: 1, y: 1, value: 1 },
806-
{ x: 2, y: 1.1, value: 1.1 },
807-
{ x: 3, y: 1.3, value: 1.3 },
808-
{ x: 4, y: 1.6, value: 1.6 },
809-
{ x: 5, y: 2, value: 2 },
810-
{ x: 6, y: 2.5, value: 2.5 },
811-
{ x: 7, y: 3.1, value: 3.1 },
812-
{ x: 8, y: 3.8, value: 3.8 },
813-
{ x: 9, y: 2.6, value: 2.6 },
816+
{ x: 2, y: 0, value: 0 },
817+
{ x: 3, y: 1, value: 1 },
814818
];
815-
expect(calcLinearProgression(plots)).toStrictEqual({
819+
820+
expect(calcLinearProgression(d3)).toStrictEqual({
816821
x1: 1,
817-
y1: 0.8444444444444444,
818-
x2: 9,
819-
y2: 3.3777777777777773,
820-
slope: 0.31666666666666665,
821-
trend: 0.4000000000000001,
822+
y1: 0.6666666666666666,
823+
x2: 3,
824+
y2: 0.6666666666666666,
825+
slope: 0,
826+
trend: 0,
822827
});
823828
});
824829
});

0 commit comments

Comments
 (0)