I've been working on a script in JavaScript to draw graphic charts and curves based on various type of data on different measures on each category like g, kg, tons for weight or C° or F for temperature etc. and I left the choice of scaling for the user to enter manually the interval which they want to cover, and this part is done.
Now I want to add an option for auto scaling and generate an optimal interval based on min and max values and by optimal I mean a good reading numbers for intervals. the data varies depending on categories and here all the cases of it:
{min=0, max=45 } => [0,100] // min= 0 so begin = 0 and 45 is 2 digits so end = 100
{min=0, max=123 } => [0,200] // begin = 0 , 123 is 3 digits end= 200
{min=-45, max=201 } => [-100,300]
{min=-1, max=1 } => [-1,1]
{min=0.01, max=0.06 } => [0,0.1]
{min=-0.009, max=-0.004 } => [-0.01,0]
{min=-0.8, max=0.9 } => [-1,1]
{min=-335, max=-12 } => [-400,0]
... and so on
so I started by the idea of getting numbers of digits using
// if min != 0 && max>=min && max!=0
// rounding
begin=Math.floor(min);
end=Math.ceil(max);
// then if end is positive
// getting the endvalue meaning getting the 10 exponent
threshold = Math.pow(10,Math.floor(Math.log10(end)));
// loop while
while( end % threshold !=0) end ;
// else do the rest the same and care for negative values
this way it works but not in all cases if anyone want to improve be my guest thank you.
CodePudding user response:
Some remarks:
Math.log10(0)
is -Infinity, so take care whenend
is 0.- When the threshold for the min-value is different from the one for the max-value, I assume you want to apply the greatest of those two.
- The
while
loop is not efficient. Instead, you can divide the number by the threshold, and ceil that, and then multiply again.
Here is the adjusted code. I added your test cases. For the first one it reports a difference, but I think the logic should be that 45 is rounding up to 50, not 100:
function getThreshold(n) {
return n && Math.pow(10, Math.floor(Math.log10(Math.abs(n))));
}
function getRange({min, max}) {
min = Math.floor(min);
max = Math.ceil(max);
let threshold = Math.max(getThreshold(min), getThreshold(max));
min = min && Math.round(Math.floor(min / threshold) * threshold);
max = max && Math.round(Math.ceil(max / threshold) * threshold);
return [min, max];
}
let tests = [
[{min:0, max:45 }, [0,100]],
[{min:0, max:123 }, [0,200]],
[{min:-45, max:201 }, [-100,300]],
[{min:-1, max:1 }, [-1,1]],
[{min:0.01, max:0.6 }, [0,1]],
[{min:-0.8, max:0.9 }, [-1,1]],
[{min:-335, max:-12 }, [-400,0]],
];
for (let [input, expected] of tests) {
let result = getRange(input);
if (JSON.stringify(result) !== JSON.stringify(expected)) {
console.log("got", ...result, "expected", ...expected);
}
}
console.log("all done");
As discussed in comments, for small ranges (like [0.00193, 0.00221]), it would be better to not require that the output consists of integers, but could be something like [0.001, 0.003].
In that case you can use this:
function getThresholdPower(n) {
return n && Math.floor(Math.log10(Math.abs(n)));
}
function getRange({min, max}) {
let thresholdPower = Math.max(getThresholdPower(min), getThresholdPower(max));
let threshold = 10 ** Math.abs(thresholdPower);
// Deal with negative powers differently
// to avoid floating point precision issues
if (thresholdPower < 0) {
min = min && Math.floor(min * threshold) / threshold;
max = max && Math.ceil(max * threshold) / threshold;
} else {
min = min && Math.floor(min / threshold) * threshold;
max = max && Math.ceil(max / threshold) * threshold;
}
return [min, max];
}
let tests = [
[{min:0, max:45 }, [0,100]],
[{min:0, max:123 }, [0,200]],
[{min:-45, max:201 }, [-100,300]],
[{min:-1, max:1 }, [-1,1]],
[{min:0.01, max:0.6 }, [0,1]],
[{min:-0.8, max:0.9 }, [-1,1]],
[{min:-335, max:-12 }, [-400,0]],
];
for (let [input, expected] of tests) {
let result = getRange(input);
if (JSON.stringify(result) !== JSON.stringify(expected)) {
console.log("got", ...result, "expected", ...expected);
}
}
console.log("all done");
Obviously, this snippet reports more deviations from what was mentioned as expected output.
CodePudding user response:
Well it's not exactly like you wanted, but this normalizes to a scale that is a power of 10.
function normalize_to_10_power(value, is_down) {
if (is_down) {
if (value > 0) {
return Math.pow(10, Math.floor(Math.log10(value)))
}
if (value < 0) {
return -Math.pow(10, Math.ceil(Math.log10(-value)))
}
} else {
if (value > 0) {
return Math.pow(10, Math.ceil(Math.log10(value)))
}
if (value < 0) {
return -Math.pow(10, Math.floor(Math.log10(-value)))
}
}
return 0
}
function normalize_range(min, max) {
return [
normalize_to_10_power(min, true),
normalize_to_10_power(max)
]
}
var tests = [
[{ min: 0, max: 45 }, [0, 100]],
[{ min: 0, max: 123 }, [0, 200]],
[{ min: -45, max: 201 }, [-100, 300]],
[{ min: -1, max: 1 }, [-1, 1]],
[{ min: 0.01, max: 0.6 }, [0, 1]],
[{ min: -0.009, max: -0.004 }, [0, 1]],
[{ min: -0.8, max: 0.9 }, [-1, 1]],
[{ min: -335, max: -12 }, [-400, 0]],
];
for (let [input, expected] of tests) {
var result = normalize_range(input.min, input.max);
console.log("input: ", input, "result: ", result);
}
note that {min=-0.009, max=-0.004 } => [-0.01,0] should be [-0.01, -0.001] (if we go by this algorithm)