Minetrack/assets/js/scale.js

92 lines
2.7 KiB
JavaScript
Raw Normal View History

export class RelativeScale {
2023-12-30 23:03:54 +00:00
static scale(data, tickCount, maxFactor) {
const { min, max } = RelativeScale.calculateBounds(data);
2023-12-30 23:03:54 +00:00
let factor = 1;
while (true) {
2023-12-30 23:03:54 +00:00
const scale = Math.pow(10, factor);
2023-12-30 23:03:54 +00:00
const scaledMin = min - (min % scale);
let scaledMax = max + (max % scale === 0 ? 0 : scale - (max % scale));
2020-06-18 03:22:29 +00:00
// Prevent min/max from being equal (and generating 0 ticks)
// This happens when all data points are products of scale value
if (scaledMin === scaledMax) {
2023-12-30 23:03:54 +00:00
scaledMax += scale;
2020-06-18 03:22:29 +00:00
}
2023-12-30 23:03:54 +00:00
const ticks = (scaledMax - scaledMin) / scale;
2023-12-30 23:03:54 +00:00
if (
ticks <= tickCount ||
(typeof maxFactor === "number" && factor === maxFactor)
) {
return {
scaledMin,
scaledMax,
2023-12-30 23:03:54 +00:00
scale,
};
} else {
// Too many steps between min/max, increase factor and try again
2023-12-30 23:03:54 +00:00
factor++;
}
}
}
2023-12-30 23:03:54 +00:00
static scaleMatrix(data, tickCount, maxFactor) {
const nonNullData = data.flat().filter((val) => val !== null);
// when used with the spread operator large nonNullData/data arrays can reach the max call stack size
// use reduce calls to safely determine min/max values for any size of array
// https://stackoverflow.com/questions/63705432/maximum-call-stack-size-exceeded-when-using-the-dots-operator/63706516#63706516
const max = nonNullData.reduce((a, b) => {
2023-12-30 23:03:54 +00:00
return Math.max(a, b);
}, Number.NEGATIVE_INFINITY);
return RelativeScale.scale(
[0, RelativeScale.isFiniteOrZero(max)],
tickCount,
maxFactor
2023-12-30 23:03:54 +00:00
);
}
2023-12-30 23:03:54 +00:00
static generateTicks(min, max, step) {
const ticks = [];
for (let i = min; i <= max; i += step) {
2023-12-30 23:03:54 +00:00
ticks.push(i);
}
2023-12-30 23:03:54 +00:00
return ticks;
}
2023-12-30 23:03:54 +00:00
static calculateBounds(data) {
if (data.length === 0) {
return {
min: 0,
2023-12-30 23:03:54 +00:00
max: 0,
};
} else {
2023-12-30 23:03:54 +00:00
const nonNullData = data.filter((val) => val !== null);
// when used with the spread operator large nonNullData/data arrays can reach the max call stack size
// use reduce calls to safely determine min/max values for any size of array
// https://stackoverflow.com/questions/63705432/maximum-call-stack-size-exceeded-when-using-the-dots-operator/63706516#63706516
const min = nonNullData.reduce((a, b) => {
2023-12-30 23:03:54 +00:00
return Math.min(a, b);
}, Number.POSITIVE_INFINITY);
const max = nonNullData.reduce((a, b) => {
2023-12-30 23:03:54 +00:00
return Math.max(a, b);
}, Number.NEGATIVE_INFINITY);
return {
min: RelativeScale.isFiniteOrZero(min),
2023-12-30 23:03:54 +00:00
max: RelativeScale.isFiniteOrZero(max),
};
}
}
2023-12-30 23:03:54 +00:00
static isFiniteOrZero(val) {
return Number.isFinite(val) ? val : 0;
}
}