Scale a number by given range - javascript

I have a UI slider that returns a value in the range of 0 - 1 based on it's position, e.g.:
0
0.008333333333333333
0.041666666666666664
0.08333333333333333
0.10833333333333334
0.125
0.13333333333333333
0.14166666666666666
0.16666666666666666
0.175
0.19166666666666668
0.2
0.21666666666666667
0.24166666666666667
0.2833333333333333
0.31666666666666665
0.36666666666666664
0.4083333333333333
0.425
0.48333333333333334
0.55
0.6166666666666667
0.7
0.775
0.825
0.8833333333333333
0.9333333333333333
0.9833333333333333
1
I'm controlling a zoom level with the slider, which has a minimum value of 1 and a maximum value of 6.
How can I scale the number to sync with the range of zoom values?

Just calculate
function f(x) { // [0 ... 1]
return 5 * x + 1; // [1 ... 6]
}
The other way round
function f(y) { // [1 ... 6]
return (y - 1) / 5; // [0 ... 1]
}

Related

Percentage value from different ranges

I tried from yesterday to find a formula in JavaScript (also math formula) that return the value, of a given percentage from 3 different cases.
Example:
range A = [0, 10 ] - percentage 25% => value will be 2.5
range B = [0, 100] - percentage 50% => value will be 50
but how do I treat this 2 cases?:
case 1 = range [-5, 5 ] - percentage for example 50% => value will be 0
case 2 = range [-10, 0 ] - percentage for example 25% => value will be -7.5
case 3 = range [-11, -1] - percentage for example 30% => value will be ?
Here is your formula:
Try this.
const percentage = function(x, y, perc){
// x is start point
// y is end point
// so you need length of this range (between x and y) and we subtract x from y
// and dividing to 100 (because 100% is full range between x and y)
// when we divide it to 100, the result is 1% of the range
// then we multiply it to percentage we want for example 25%
// and adding x to result. Because we started from x;
return ((y-x)/100)*perc+x;
}
console.log(percentage(0,10,25));
console.log(percentage(0,100,50));
console.log(percentage(-5,5,50));
console.log(percentage(-10,0,25));
console.log(percentage(-11,-1,30));

How to map a number in some ranges

I have a range of values like, for example, [0, 100] = [minValue, maxValue] and the number of bands, for example BANDS_NUMBER = 5.
So I can obtain these bands:
[0 - 20]
[21 - 40]
[41 - 60]
[61 - 80]
[81 - 100]
Then I want to associate a scale value at each range:
i: 0 --> [0 - 20] --> 0.2
i: 1 --> [21 - 40] --> 0.4
i: 2 --> [41 - 60] --> 0.6
i: 3 --> [61 - 80] --> 0.8
i: 4 --> [81 - 100] --> 1
This value is computed in this way: (i + 1) / BANDS_NUMBER where i is the index of a hypothetical loop.
Then I have an input n whose value is in range [minValue, maxValue] = [0, 100].
What I want is the scale value related to this number.
So, for example, if:
n = 0 --> scaleValue = 0.2
n = 10 --> scaleValue = 0.2
n = 20 --> scaleValue = 0.2
n = 35 --> scaleValue = 0.4
n = 68 --> scaleValue = 0.8
n = 99 --> scaleValue = 1
...
How can I create a function like that? I imagine a function like that:
function map(n, minValue, maxValue, bandsNumber) {
const scaleValue = ...
return scaleValue
}
All the values here are examples, I want that all works with any other values.
I don't know how to do to that. I need some help...
Nina Scholz's answer is wrong. Her normalize function returns 0.4 instead of 0.2 for the value 20:
function normalize(min, max, bands, n) {
return n === max
? 1
: Math.floor(1 + ((n - min) / (max - min)) * bands) / bands;
}
console.log(normalize(0, 100, 5, 20)); // expected 0.2, actual 0.4
Because 20 is in the first band, it should have the value 0.2:
i: 0 --> [0 - 20] --> 0.2
i: 1 --> [21 - 40] --> 0.4
i: 2 --> [41 - 60] --> 0.6
i: 3 --> [61 - 80] --> 0.8
i: 4 --> [81 - 100] --> 1
The correct answer is:
const index = (min, max, bands, n) =>
Math.floor(bands * (n - min) / (max - min + 1));
const band = n => index(0, 100, 5, n);
console.log(band(0), band(20)); // 0 0
console.log(band(21), band(40)); // 1 1
console.log(band(41), band(60)); // 2 2
console.log(band(61), band(80)); // 3 3
console.log(band(81), band(100)); // 4 4
As you can see, the edge cases are handled correctly. How did we get to this answer?
First, we find the length of the range which is max - min + 1. The + 1 is important because there are 101 elements in the range [0 - 100] inclusive.
Next, we get the index of the number n in the given range (i.e. n - min).
Then, we divide the index of n by the number of elements in the range to get a value in the range [0 - 1). Note that 1 is not in the range.
Finally, we multiply this value by the number of bands and discard the fractional part. The result is our index.
Note that if the length of the range is not divisible by the number of bands then the first x bands will have one additional element, where x is the remainder of dividing the length of the range by the number of bands.
Finally, we can get the value you want by incrementing the resulting index and then dividing it by the number of bands:
const index = (min, max, bands, n) =>
Math.floor(bands * (n - min) / (max - min + 1));
const value = (min, max, bands, n) =>
(index(min, max, bands, n) + 1) / bands;
const result = n => value(0, 100, 5, n);
console.log(result(0), result(20)); // 0.2 0.2
console.log(result(21), result(40)); // 0.4 0.4
console.log(result(41), result(60)); // 0.6 0.6
console.log(result(61), result(80)); // 0.8 0.8
console.log(result(81), result(100)); // 1 1
Hope that helps.
You could take a formula, which take the range and the slot and returns a normalized value.
Because of the range, which is a bit too long (the last value is included in the interval), you need a check for the last value and prevent getting the next value, outside of the wanted interval.
function normalize(min, max, bands, n) {
return n === max
? 1
: Math.floor(1 + ((n - min) / (max - min)) * bands) / bands;
}
// 0.2 0.2 0.4 0.4 0.8 1 1
console.log(...[0, 10, 20, 35, 68, 99, 100].map(normalize.bind(null, 0, 100, 5)));
You can use a native Array.map function to map each value.
Something like this:
const vals = [
[0, 20],
[21, 40],
[41, 60],
[61, 80],
[81, 100],
];
const BANDS_NUMBER = 5;
const result = vals.map((range, index) => (index + 1) / BANDS_NUMBER);
console.log(result);

Custom Rounding up to nearest 0.05

I want to do custom round up to near 0.05 , based on following condition.It is hard to explain , but following example will be easy to understand .
12.910 - 12.90
12.920 - 12.90
12.930 - 12.90
12.940 - 12.90
12.941 - 12.95
12.950 - 12.95
12.960 - 12.95
12.970 - 12.95
12.980 - 12.95
12.990 - 12.95
12.991 - 13.00
13.000 - 13.00
I tried several function , but it is rounding up 12.98 to 13.00.
function customRound( num) {
return Math.round(num * 20) / 20;
}
Coming at this visually, your rounding algorithm seems to look like this:
The dot is where you want to round to for that interval. ( marks the open end of an interval, ] the closed end. (12.99 belongs to the red interval.) We'll implement this algorithm by manipulating the line to match Math.floor's.
First, let's work with integers.
num * 100
Your rounding interval is left-open and right-closed, but Math.floor is left-closed and right-open. We can flip the line to match by multiplying by −1:
num * 100 * -1
⇒ num * -100
Your rounding intervals' lengths are 5, so we need to put the ends of the intervals on multiples of 5...
num * -100 - 1
...before dividing by 5 to match Math.floor.
(num * -100 - 1 ) / 5
⇒ num * -20 - 0.2
Now we can take the floor.
return Math.floor(num * -20 - 0.2);
Scale back up to the original by multiplying by 5:
return Math.floor(num * -20 - 0.2) * 5;
Shift the returned value over to the dot by adding 4:
return Math.floor(num * -20 - 0.2) * 5 + 4;
Undo the alignment we did earlier:
return Math.floor(num * -20 - 0.2) * 5 + 4 + 1;
⇒ return Math.floor(num * -20 - 0.2) * 5 + 5;
Undo the flip:
return (Math.floor(num * -20 - 0.2) * 5 + 5) * -1;
⇒ return Math.floor(num * -20 - 0.2) * -5 - 5;
And divide the whole thing by 100 to get your original scale back:
return (Math.floor(num * -20 - 0.2) * -5 - 5) / 100;
⇒ return Math.floor(num * -20 - 0.2) * -0.05 - 0.05;
Using Robin Zigmond's testing framework,
function customRound(num) {
return Math.floor(num * -20 - 0.2) * -0.05 - 0.05;
}
// test desired results
var tests = [12.91, 12.92, 12.93, 12.94, 12.941, 12.95, 12.96, 12.97, 12.98, 12.99, 12.991, 13];
for (var i=0; i<tests.length; i++) {
console.log(`${tests[i].toFixed(3)} - ${customRound(tests[i]).toFixed(2)}`);
}
If you really intended to round everything within < .01 of the nearest .05 then try the below, to get the precision of the number it uses answer from Is there a reliable way in JavaScript to obtain the number of decimal places of an arbitrary number?
function decimalPlaces(n) {
var s = "" + (+n);
var match = /(?:\.(\d+))?(?:[eE]([+\-]?\d+))?$/.exec(s);
if (!match) { return 0; }
return Math.max(
0, // lower limit.
(match[1] == '0' ? 0 : (match[1] || '').length)
- (match[2] || 0));
}
var test = 12.941;
var factor = Math.pow(10,decimalPlaces(test));
var remainder = ((test * factor) % (.05 * factor))/factor;
var result;
if (remainder>.04) {
result = Math.round(test*20)/20;
} else {
result = (test*factor - remainder*factor)/factor;
}
console.log('result is:',result);
As far as I can tell from your example, the desired behaviour appears to be "round up to the nearest 0.01, then round that result down to the nearest 0.05".
This can be implemented as follows. As you can see, it agrees exactly with your examples (I even took care to format it the same way) - but please let me know if I've got the wrong end of the stick.
function customRound(num) {
var intermediateResult = Math.ceil(num*100)/100;
return Math.floor(intermediateResult*20)/20;
}
// test desired results
var tests = [12.91, 12.92, 12.93, 12.94, 12.941, 12.95, 12.96, 12.97, 12.98, 12.99, 12.991, 13];
for (var i=0; i<tests.length; i++) {
console.log(`${tests[i].toFixed(3)} - ${customRound(tests[i]).toFixed(2)}`);
}

How to get range from 0 - 1 based on two number range

In Javascript, when I am scrolling, I would like to get 0 - 1 when the scroll.x number is from 300 - 400.
So 300 would be 0, then 400 would be 1, and in between would be 0.1, 0.2.
I am attempting to try in the meantime and will post my attempt as well.
Yep, you'll just want a simple remapping function.
const remap = (
value,
sourceMin,
sourceMax,
destMin = 0,
destMax = 1,
) =>
destMin +
((value - sourceMin) / (sourceMax - sourceMin)) *
(destMax - destMin);
console.log(remap(300, 300, 400));
console.log(remap(400, 300, 400));
console.log(remap(350, 300, 400));
outputs
0
1
0.5
Note that this function does not clamp the output values to the range you specify; if you specify out-of-range inputs, you'll get out-of-range outputs.
You could subtract the start value and divide by 100 (the delta of the two values) for getting a value between zero and one.
function f(x) {
return (x - 300) / 100;
}
console.log(f(300)); // 0
console.log(f(350)); // 0.5
console.log(f(400)); // 1

Basic formula to calculate decimal between two numbers

I'm having a brain fart and need help with a basic formula.
if (d >= 0.1) {
scale = 0.1
} else if (d <= 0.004) {
scale = 1;
}
How can I set the value of scale to somewhere between 0.1 and 1 if the value of d is somewhere between 0.1 and 0.004?
You can add an else statement to handle the numbers between 0.004 and 0.1:
// ...
else {
scale = 1 - (d - 0.004) * (1 - 0.1) / (0.1 - 0.004);
}
Or you can do the whole thing in one go:
var scale = 1 - (Math.max(Math.min(d, 1), 0.004) - 0.004) * (1 - 0.1) / (0.1 - 0.004);
The scaling should look like this:
scale = 1 - (1 - 0.1)*(d - 0.004)/(0.1 - 0.004)
or more simply
scale = 1 - 9.375*(d - 0.004)
For example, if d = 0.05, then scale = 0.56875.
If I understand correctly from the comments, this is the pattern for your calculation. dRange is the range of d values. The input values are limited to also restrict the output.
Note: The input values d are first restricted, so that we have 2 absolute 'scales' that we can mirror: dRange and scaleRange, and their corresponding min- and max-values.
Note: The inverted scaleRange, where 1 (max value) is to the left, and 0.1 (min value) is to the right:
0.004 <-- dRange --> 0.1
~~~~~~~~~~~~|--------------------------------------------------------------|~~~~~~~~~~~~
In: ~~~d~~~~~d----------------------d---------------------------------------d-----d------
| | | | |
----->| | |<----
| | |
Limited: [d,d]--------------------d-------------------------------------[d,d]
| | |
Out: [x,x]--------------------x-------------------------------------[x,x]
~~~~~~~~~~~~|--------------------------------------------------------------|~~~~~~~~~~~~
1 <-- scaleRange --> 0.1
That's some of the more complex logic that deserves a slightly more declarative approach. I came up with the following to implement the scenario above. Further explanation is in the code comments:
// The minimum and maximum values for d - also the 'input'
const minDValue = 0.004;
const maxDValue = 0.1;
// The minimum and maximum values for scale - also the 'output'
const minScaleValue = 0.1;
const maxScaleValue = 1;
// Absolute ranges for d and scale:
const dRangeAbs = maxDValue - minDValue;
const scaleRangeAbs = maxScaleValue - minScaleValue;
// restricts the value of d if it exceeds the bounds:
// 'minDValue <= d <= maxDValue'
function restrictD(d) {
return Math.max(minDValue, Math.min(maxDValue, d));
}
// Calculate the absolute scale value for a certain d-value.
function scaleForDValue(d) {
// First, restrict the input. If not restricted the input 'scale' is
// not accurate, and we can not determine the corresponding scale.
d = restrictD(d);
// Determine how far (as fraction of 1) the d-value sits along
// the absolute d-range:
const dProgressFraction = (d - minDValue) / dRangeAbs;
// Use the d-progress-fraction to add a corresponding relative
// progress value for the scale-'scale'. Because the scale is
// inverted, we subtract the progress from the max-value this
// time.
const calculatedScale = maxScaleValue - (dProgressFraction * scaleRangeAbs);
// Restrict calculated value to prevent rouding error.
return Math.min(maxScaleValue, Math.max(minScaleValue, calculatedScale));
}
// Log some values!
const log = (val, info) => console.log({ d: val, scale: scaleForDValue(val), info });
log(minDValue, 'this is the min d-value');
log(minDValue - 1, 'this is a too low d-value');
log(maxDValue, 'this is the max d-value');
log(maxDValue + 1, 'this is a too high d-value');
for (let i = 0; i < 5; i++) {
log(minDValue + Math.random() * dRangeAbs, 'A random d-value');
}

Categories