How to map a number in some ranges - javascript

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);

Related

How would i get get min Y value for graph?

I don't know how to explain this but i will try my best, so sorry in advance
For example this numbers
i would like to get
5 => 1, 12 => 10, 128 => 120, 1493 => 1400, 13301 => 13000
I have came up with Math.pow(10, Math.floor(Math.log10(x)));
which returns me 1, 10, 100, 1000, 10000
You could get the exponent and a factor and check if the value is smaller than 100, then take just the decimal with the exponent or the adjusted value.
function format(v) {
var e = Math.floor(Math.log10(v)),
f = 10 ** (e - 1);
return v < 100
? 10 ** e
: Math.floor(v / f) * f;
}
console.log(...[5, 12, 128, 1493, 13301, 239584].map(format));
// 1, 10, 120, 1400, 13000, 230000

Represent a number with a color, difference not enough for percentage based rgb

I have an array (1200 values) of numbers
[123, 145, 158, 133...]
I'd like to have a div for each value with a background color from red to green, red being the smallest number and green the largest.
The base setup looks like this: (templating with vuejs but unrelated to the problem)
const values = [123, 145, 158, 133...]; // 1200 values inside
const total = values.length;
<div
v-for="(val, i) in values"
:key="i"
:style="{backgroundColor: `rgb(${(100 - (val*100/total)) * 256}, ${(val*100/total) * 256}, 0)`}">
{{val}}
</div>
I'm not a maths specialist but since all my numbers are around 100, the rgb generated is the same. (around 12% yellowish color)
How can I give more weight to the difference between 137 and 147?
EDIT: final formula:
:style="{backgroundColor: `rgb(${(256/(maxValue-minValue) * (boule-maxValue) - 255)}, ${(256/20 * (boule-maxValue) + 255)}, 0)`}"
Checkout this post: https://stats.stackexchange.com/questions/70801/how-to-normalize-data-to-0-1-range.
Basically you want to linearly rescale your values to another interval. You need your current min and max values from the array. Then define the new min' and max' which are the limits of the new interval. This would be [0, 255] in your case.
To do the transformation use the formula:
newvalue= (max'-min')/(max-min)*(value-max)+max'
As an example:
If your min value is 127 and max is 147, and you want to map 137. Then:
256/20 * (137-147) + 255 which results in 127.
If you want to map 130. Then:
256/20 * (130-147) + 255 = 37.4.
It really depends on what meaning those values actually have
However, you can try this: if your values are always bigger than 100 and always less than 150 (you can choose these number of course) you can "stretch" your values using the values as minimum and maximum. Let's take 137 and 147 as examples:
(val-min) : (max-min) = x : 255
(137-100):(150-100) = x:255 -> 37:50 = x:255 -> 188
(147-100):(150-100) = x:255 -> 47:50 = x:255 -> 239
That is for the math. In the end, this is the calculation:
newValue = (val-min)*255/(max-min)
where min and max are your chosen values.
You could take a kind of magnifier for a range of data. In this example, the values between 20 and 30 are mapped to a two times greater range than the outside values inside of an interval of 0 ... 100.
function magnifier(value, start, end, factor) {
var middle = (start + end) / 2,
size = (end - start) * factor / 2,
left = middle - size,
right = middle + size;
if (value <= start) return value * left / start;
if (value <= end) return (value - start) * factor + left;
return (value - end) * (100 - right) / (100 - end) + right;
}
var i;
for (i = 0; i <= 100; i += 5) {
console.log(i, magnifier(i, 20, 30, 2));
}
.as-console-wrapper { max-height: 100% !important; top: 0; }

Use scales to remap a number

I have a domain of numbers, for example domain = [100, 200] and a number of bands in which to divide the range, for example bands = 5.
I know that each band corresponds to a value:
band #1 --> v = 0.2
band #2 --> v = 0.4
band #3 --> v = 0.6
band #4 --> v = 0.8
band #5 --> v = 1.0
These values are fixed (hard coded): if bands became bands = 6 then is the developer that choose what is the value of band #6.
I want to divide the domain into bands whose size varies according to the scale used.
For example I might want to use either the linear or the logarithmic or the pow scale.
Then I want a function that in input takes a number x ∈ domain and must return the value v associated with the band to which the inout number belongs.
Here a similar question, but now I want to use different scales (for example I can use d3 scales) but I don't know how..
Here a piece of code:
function getLinearScaledValue(x, min, max, bands) {
const range = max - min
if (x === max) {
return 1
} else {
return Math.floor(1 + ((x - min) / range) * bands) / bands
}
}
where min and max are the min and max value of the domain.
I think sleepwalking's examples was good so I put them here:
if bands = 5:
band #1 --> v = 0.2
band #2 --> v = 0.4
band #3 --> v = 0.6
band #4 --> v = 0.8
band #5 --> v = 1.0
(1) if scale is linear and domain = [0, 100] --> bands are:
band #1 --> v = 0.2 --> [0, 20]
band #2 --> v = 0.4 --> [21, 40]
band #3 --> v = 0.6 --> [41, 60]
band #4 --> v = 0.8 --> [61, 80]
band #5 --> v = 1.0 --> [81, 100]
for example:
if x = 0 --> v = 0.2
if x = 10 --> v = 0.2
if x = 21 --> v = 0.4
if x = 98 --> v = 1.0
(2) if scale is linear and domain = [100, 200] --> bands are:
band #1 --> v = 0.2 --> [100, 120]
band #2 --> v = 0.4 --> [121, 140]
band #3 --> v = 0.6 --> [141, 160]
band #4 --> v = 0.8 --> [161, 180]
band #5 --> v = 1.0 --> [181, 200]
for example:
if x = 100 --> v = 0.2
if x = 110 --> v = 0.2
if x = 121 --> v = 0.4
if x = 198 --> v = 1.0
(3) if scale is logarithmic and domain = [0, 100] --> bands are:
band #1 --> v = 0.2 --> [?, ?]
band #2 --> v = 0.4 --> [?, ?]
band #3 --> v = 0.6 --> [?, ?]
band #4 --> v = 0.8 --> [?, ?]
band #5 --> v = 1.0 --> [?, ?]
for example:
if x = 0 --> v = ?
if x = 10 --> v = ?
if x = 21 --> v = ?
if x = 98 --> v = ?
In my previous answer I showed the correct function to calcuate the band index of a number within a range:
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
The above function uses a linear scale. However, it's easy to generalize it to use another scale:
const index = (scale, min, max, bands, n) =>
Math.floor(bands * scale(n - min) / scale(max - min + 1));
const log = x => Math.log(x + 1);
const logBand = n => index(log, 0, 100, 5, n);
console.log(logBand(0), logBand(1)); // 0 0
console.log(logBand(2), logBand(5)); // 1 1
console.log(logBand(6), logBand(15)); // 2 2
console.log(logBand(16), logBand(39)); // 3 3
console.log(logBand(40), logBand(100)); // 4 4
Here we used the logarithmic scale. Note that we incremented the index before calculating its logarithm because the logarithm of zero is undefined, although JavaScript happily returns the limit of the natural logarithm of x as x tends to zero (i.e. -Infinity). However, -Infinity is not a valid index.
Anyway, our ranges are as follows:
i: 0 --> [0 - 1] --> 0.2
i: 1 --> [2 - 5] --> 0.4
i: 2 --> [6 - 15] --> 0.6
i: 3 --> [16 - 39] --> 0.8
i: 4 --> [40 - 100] --> 1
Note that although our scale is logarithmic yet our ranges grow exponentially. This makes sense because when we scale our range logarithmically we're squishing numbers together. Hence, when we divide our squished range into bands, the number of elements in each band grows exponentially. It can be best explained by the following graph:
One the x-axis we have our linear scale with values from 1 to 101. On the y-axis we have our logarithmic scale with values from log(1) to log(101) (denoted as 5/5 for educational purposes). As you can see, we're dividing our logarithmic range into even sized bands. However, on our linear scale those bands become exponentially bigger.
First of all, you need conventions. You need to ask yourself the right questions.
What does logarithmic mean? The logarithm function has two inputs, a base and a value. The output is the power to which you raise the base in order to get the number. Let's suppose that the base is 2, and that the domain is 0–200. In this case you calculate log(2, 200), which is approximately 7.64. This means that we have the limits, [1, 2, 4, 8, 16, 32, 64, 128]. If we exclude 1 (i.e. 20), then we have 6 borders, which means we have 7 regions.
What happens if the domain is 200–400? In this case you would have only two regions, which doesn't make sense. In contrast with linear scaling, logarithmic scaling will yield different results for different domains even if their size is the same.
I think it makes sense to consider domains to be vectorial, i.e. it's irrelevant whether the domain is from 0–200 or from 200–400. The only constraints are that the domain is positive and 200 long. So, the domain 200–400 would be divided similarly to the domain 0–200, the only difference being the values. For the division itself we use the indices.
If we accept this convention, then the problem is greatly simplified. The domain 200–400 has a length of 201. Hence, instead of [1, 2, 4, 8, 16, 32, 64, 128] we have [201, 202, 204, 208, 216, 232, 264, 328].
Now the logarithmic scale look like the linear scale. The number of borders in the linear scale is equal to the base of the logarithm. Hence, in the case of a logarithmic scale, we would have this:
function getBaseLog(x, y) {
return Math.log(y) / Math.log(x);
}
function getLogarithmicScaledValue(x, min, max, base) {
return parseInt(getBaseLog(base, x - min));
}
See the logarithmic base conversion.
An obvious use-case of logarithmic scaling is that it plays well with heap data structures.

Scale a number by given range

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]
}

Generate axis scale

I am working on a project where I need to calculate some scale for slider. The user can define min, max and step.
You can find the code below:
var j = Math.round(cfg.max / cfg.step);
var l = (containerWidth / (j - 1));
for (var i = 0; i < j; i++) {
s.push('<span style="left:');
s.push(l * i);
s.push('px">');
s.push(cfg.step * (i + 1));
s.push('</span>');
}
Example: min=1 max=12 step=3
Generated scale: 3 6 9 12
Slider ticks: 1 4 7 10 12
I would like to know how I can generate ticks for slider.
Assuming your question can be rephrased like this:
Calculate n ticks for an arbitrary range with min x and max y
Then we can adapt the linear tick function from D3.js:
function calculateTicks(min, max, tickCount) {
var span = max - min,
step = Math.pow(10, Math.floor(Math.log(span / tickCount) / Math.LN10)),
err = tickCount / span * step;
// Filter ticks to get closer to the desired count.
if (err <= .15) step *= 10;
else if (err <= .35) step *= 5;
else if (err <= .75) step *= 2;
// Round start and stop values to step interval.
var tstart = Math.ceil(min / step) * step,
tstop = Math.floor(max / step) * step + step * .5,
ticks = [];
// now generate ticks
for (i=tstart; i < tstop; i += step) {
ticks.push(i);
}
return ticks;
}
This isn't exactly to your specifications - it generates a set of nicely-rounded ticks, often 1-2 more or fewer than tickCount:
calculateTicks(1, 12, 5); // [2, 4, 6, 8, 10, 12]
calculateTicks(0, 12, 4); // [0, 5, 10]
It's hard to come to an optimum solution here, but I think the D3 approach does it relatively well - in my opinion, I'd rather have the ticks 2, 4, 6, 8, 10, 12 for a range 1-12 than the ticks 1, 4, 7, 10, 12 that you suggest.
Working fiddle: http://jsfiddle.net/nrabinowitz/B3EM4/

Categories