Calculation of scaling factor - javascript

I've got multiple number values with a min and a max value. Assume the minimum value should be visualized by a 5px circle and the maximum value should be visualized by a 50px circle, I need to calculate a scaling factor for all other values.
50 / maxValue * value
...is not working well, because it does not consider the min value.
const array = [5, 20, 50, 100]
const min = 5 // => 5px
const max = 100 // => 50px

d3-scale does exactly this for you.
You just need to create a scaleLinear, define the domain (range of input values), and range (corresponding desired output values).
Have a look at the snippet below for an illustration:
const array = [5, 20, 50, 100]
const min = 5 // => 5px
const max = 100 // => 50px
// define the scaling function
const size = d3.scaleLinear()
.domain([min, max])
.range([5, 50])
// call it for each value in the input array
array.forEach(function(val) {
console.log(val, Math.round(size(val)) + 'px')
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/d3-scale#3.2.0/dist/d3-scale.min.js"></script>

const array = [5, 20, 50, 100]
const minpx = 5 // => 5px
const maxpx = 50 // => 50px
let max = Math.max(...array)
let min = Math.min(...array)
// (x-min)/(max-min) the ratio
// * (maxpx-minpx) the new range
// + minpx the new offset
let transform = x=>(x-min)/(max-min)*(maxpx-minpx)+minpx
console.log(array.map(transform))

Use the so called Linear Interpolation method.
The following function takes 3 parameters:
a: The starting value
b: The destination value
amount: The normal or actual value (percentage between 0 and 1) to control the Linear Interpolation
function mix(a, b, amount) {
return (1 - amount) * a + amount * b
}
Imagine a line from A to B and you put a circle on it:
A ----o--------------------------- B
If your normal value is equal to 1 the circle will instantly switch from A to B.
If your normal value is equal to 0 the circle will not move.
The closer your normal is to 0 the smoother will be the interpolation.
The closer your normal is to 1 the sharper will be the interpolation.
const array = [5, 20, 50, 100]
const minpx = 5 // => 5px
const maxpx = 50 // => 50px
const min = Math.min(...array) // 5
const max = Math.max(...array) // 100
function mix(a, b, amount) {
return (1 - amount) * a + amount * b
}
array.map(value => mix(minpx, maxpx, (value-min) / (max-min)))
// [ 5, 12.105263157894736, 26.31578947368421, 50 ]

Related

Create array with numbers +10/-10, where middle one is 0, depending on what length I want

I need to dynamically create array, knowing only how long I want it to be. So for example I need array to be 3 long so I need array = [-10, 0, 10], if I need array 9 long it will be [-40, -30, -20, -10, 0, 10, 20, 30, 40] etc. How can I do it automatically?
You can simply get the result using Array with fill
Since you only want 0 at the center that input should be odd.
const end = 9;
let start = Math.floor(end / 2);
const result = [
...Array(start).fill(0).map((_, i) => start * -10 + i * 10),
0,
...Array(start).fill(0).map((_, i) => (i + 1) * 10)
]
console.log(result)
Another solution with Array.from:
const createArray = (length) => Array.from({length}, (el, i) => Math.round(i - length / 2) * 10);
console.log(createArray(1))
console.log(createArray(3))
console.log(createArray(9))

find the value within a range using percent

I have a range of number
40 to 255 where 40 is 0% and 255 is 100%
how can I get the number which is 50% ?
I tried searching for a way to do this but can't find any.
You have (255-40) = 100%
1% = (255-40)/100
50% = ((255-40)/100) * 50 = 107.5
Since you have offset of 40 at the beginning, 50% from offset = 107.5 + 40 = 147.5
Extending #Kiran s answer with some generic Javascript
const rangeMin = 40;
const rangeMax = 255;
const percentage = 50;
const valueFromPercentage = (((rangeMax - rangeMin) / 100) * percentage ) + rangeMin;
const rangeMin = 40;
const rangeMax = 255;
const value = 147;
const percentageFromValue = ((value - rangeMin) / (rangeMax - rangeMin)) * 100;
Any number c in an interval [a, b] can be written as a linear combination of the two endpoints c = a*x + b*(1-x) for some x ∈ [0, 1].
For [a, b] = [0, 100], the number c = 50 can be written as 0*0.5 + 100*0.5 so x = 0.5.
If the interval [0, 100] is linearly mapped to [40, 255], the ratio x = 0.5 will stay the same, and so c gets mapped to c' = 40*0.5 + 255*0.5 = (40 + 255)/2 = 147.5.
Use the following
(255 - 40)/2 + 40

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

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

Calculating the length of a segment in a logarithmic scale

I want to calculate the length of a line for a series of events.
I'm doing this with the following code.
var maxLineLength = 20;
var lineLen = function(x, max) {
return maxLineLength * (x / max);
}
var events = [0.1, 1, 5, 20, 50];
var max = Math.max.apply(null, events);
events.map(function (x) {
console.log(lineLen(x, max));
});
This works, but I'm using linear scaling, while I'd like to use logarithms, because I don't want small events to become too small numbers when big ones are present.
I modified the lineLen function as you can see below, but - obviously - it doesn't work for events equals to one, because the log of one is zero. I want to show events equals to one (opportunely scaled) and not make them become zero. I also need positive numbers to remain positive (0.1 becomes a negative number)
How should I modify lineLen to use a logarithmic scale?
var maxLineLength = 20;
var lineLen = function(x, max) {
return maxLineLength * (Math.log(x) / Math.log(max));
}
var events = [0.1, 1, 5, 20, 50];
var max = Math.max.apply(null, events);
events.map(function (x) {
console.log(lineLen(x, max));
});
You can take log(x+1) instead of log(x), that doesn't change the value too much and the ratios are maintained for smaller numbers.
var maxLineLength = 20;
var lineLen = (x, max) => maxLineLength * Math.log(x+1)/Math.log(max+1);
var events = [ 0.1, 1, 5, 20, 50];
var visualizer = function(events){
var max = Math.max.apply(null, events);
return events.reduce((y, x) => {
y.push(lineLen(x, max));
return y;
}, []);
};
console.log(visualizer(events));
You can use an expression like Math.pow(x, 0.35) instead of Math.log(x). It keeps all values positive, and gives the behavior that you want for small ratios. You can experiment with different exponent values in the range (0,1) to find the one that fits your needs.
var maxLineLength = 20;
var exponent = 0.35;
var lineLen = function(x, max) {
return maxLineLength * Math.pow(x/max, exponent);
}
var events = [0, 0.01, 0.1, 1, 5, 20, 50];
var max = Math.max.apply(null, events);
events.map(function (x) {
console.log(lineLen(x, max));
});
You could decrement maxLineLength and add one at the end of the calculation.
For values smaller than one, you could use a factor which normalizes all values relative to the first value. The start value is always one, or in terms of logaritmic view, zero.
var maxLineLength = 20,
lineLen = function(max) {
return function (x) {
return (maxLineLength - 1) * Math.log(x) / Math.log(max) + 1;
};
},
events = [0.1, 1, 5, 20, 50],
normalized = events.map((v, _, a) => v / a[0]),
max = Math.max.apply(null, normalized),
result = normalized.map(lineLen(max));
console.log(result);
console.log(normalized);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You are scaling these numbers. Your starting set is the domain, what you end up with is the range. The shape of that transformation, it sounds like, will be either log or follow a power.
It turns out this is a very common problem in many fields, especially data visualization. That is why D3.js - THE data visualization tool - has everything you need to do this easily.
const x = d3.scale.log().range([0, events]);
It's the right to for the job. And if you need to do some graphs, you're all set!
Shorter but not too short; longer but not too long.
Actually I met the same problem years ago, and gave up for this (maybe?).
I've just read your question here and now, and I think I've just found the solution: shifting.
const log = (base, value) => (Math.log(value) / Math.log(base));
const weights = [0, 0.1, 1, 5, 20, 50, 100];
const base = Math.E; // Setting
const shifted = weights.map(x => x + base);
const logged = shifted.map(x => log(base, x));
const unshifted = logged.map(x => x - 1);
const total = unshifted.reduce((a, b) => a + b, 0);
const ratio = unshifted.map(x => x / total);
const percents = ratio.map(x => x * 100);
console.log(percents);
// [
// 0,
// 0.35723375538333857,
// 3.097582209424984,
// 10.3192042142806,
// 20.994247877004888,
// 29.318026542735115,
// 35.91370540117108
// ]
Visualization
The smaller the logarithmic base is, the more they are adjusted;
and vice versa.
Actually I don't know the reason. XD
<!DOCTYPE html>
<html>
<head>
<meta name="author" content="K.">
<title>Shorter but not too short; longer but not too long.</title>
<style>
canvas
{
background-color: whitesmoke;
}
</style>
</head>
<body>
<canvas id="canvas" height="5"></canvas>
<label>Weights: <input id="weights" type="text" value="[0, 0.1, 100, 1, 5, 20, 2.718, 50]">.</label>
<label>Base: <input id="base" type="number" value="2.718281828459045">.</label>
<button id="draw" type="button">Draw</button>
<script>
const input = new Proxy({}, {
get(_, thing)
{
return eval(document.getElementById(thing).value);
}
});
const color = ["tomato", "black"];
const canvas_element = document.getElementById("canvas");
const canvas_context = canvas_element.getContext("2d");
canvas_element.width = document.body.clientWidth;
document.getElementById("draw").addEventListener("click", _ => {
const log = (base, value) => (Math.log(value) / Math.log(base));
const weights = input.weights;
const base = input.base;
const orig_total = weights.reduce((a, b) => a + b, 0);
const orig_percents = weights.map(x => x / orig_total * 100);
const adjusted = weights.map(x => x + base);
const logged = adjusted.map(x => log(base, x));
const rebased = logged.map(x => x - 1);
const total = rebased.reduce((a, b) => a + b, 0);
const ratio = rebased.map(x => x / total);
const percents = ratio.map(x => x * 100);
const result = percents.map((percent, index) => `${weights[index]} | ${orig_percents[index]}% --> ${percent}% (${color[index & 1]})`);
console.info(result);
let position = 0;
ratio.forEach((rate, index) => {
canvas_context.beginPath();
canvas_context.moveTo(position, 0);
position += (canvas_element.width * rate);
canvas_context.lineTo(position, 0);
canvas_context.lineWidth = 10;
canvas_context.strokeStyle = color[index & 1];
canvas_context.stroke();
});
});
</script>
</body>
</html>
As long as your events are > 0 you can avoid shifting by scaling the events so the minimum value is above 1. The scalar can be calculated based on a minimum line length in addition to the maximum line length you already have.
// generates an array of line lengths between minLineLength and maxLineLength
// assumes events contains only values > 0 and 0 < minLineLength < maxLineLength
function generateLineLengths(events, minLineLength, maxLineLength) {
var min = Math.min.apply(null, events);
var max = Math.max.apply(null, events);
//calculate scalar that sets line length for the minimum value in events to minLineLength
var mmr = minLineLength / maxLineLength;
var scalar = Math.pow(Math.pow(max, mmr) / min, 1 / (1 - mmr));
function lineLength(x) {
return maxLineLength * (Math.log(x * scalar) / Math.log(max * scalar));
}
return events.map(lineLength)
}
var events = [0.1, 1, 5, 20, 50];
console.log('Between 1 and 20')
generateLineLengths(events, 1, 20).forEach(function(x) {
console.log(x)
})
// 1
// 8.039722549519123
// 12.960277450480875
// 17.19861274759562
// 20
console.log('Between 5 and 25')
generateLineLengths(events, 5, 25).forEach(function(x) {
console.log(x)
})
// 5
// 12.410234262651711
// 17.58976573734829
// 22.05117131325855
// 25

Categories