Home > Net >  Generating an interval based on max and min value
Generating an interval based on max and min value

Time:06-26

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 when end 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)

  • Related