Scale a Series Between Two Points

Scale a series between two points

It's straight-forward to create a small function to do this using basic arithmetic:

s = sort(rexp(100))

range01 <- function(x){(x-min(x))/(max(x)-min(x))}

range01(s)

[1] 0.000000000 0.003338782 0.007572326 0.012192201 0.016055006 0.017161145
[7] 0.019949532 0.023839810 0.024421602 0.027197168 0.029889484 0.033039408
[13] 0.033783376 0.038051265 0.045183382 0.049560233 0.056941611 0.057552543
[19] 0.062674982 0.066001242 0.066420884 0.067689067 0.069247825 0.069432174
[25] 0.070136067 0.076340460 0.078709590 0.080393512 0.085591881 0.087540132
[31] 0.090517295 0.091026499 0.091251213 0.099218526 0.103236344 0.105724733
[37] 0.107495340 0.113332392 0.116103438 0.124050331 0.125596034 0.126599323
[43] 0.127154661 0.133392300 0.134258532 0.138253452 0.141933433 0.146748798
[49] 0.147490227 0.149960293 0.153126478 0.154275371 0.167701855 0.170160948
[55] 0.180313542 0.181834891 0.182554291 0.189188137 0.193807559 0.195903010
[61] 0.208902645 0.211308713 0.232942314 0.236135220 0.251950116 0.260816843
[67] 0.284090255 0.284150541 0.288498370 0.295515143 0.299408623 0.301264703
[73] 0.306817872 0.307853369 0.324882091 0.353241217 0.366800517 0.389474449
[79] 0.398838576 0.404266315 0.408936260 0.409198619 0.415165553 0.433960390
[85] 0.440690262 0.458692639 0.464027428 0.474214070 0.517224262 0.538532221
[91] 0.544911543 0.559945121 0.585390414 0.647030109 0.694095422 0.708385079
[97] 0.736486707 0.787250428 0.870874773 1.000000000

How can I relatively scale something between two points?

Tweening in terms of position.

Keys and keyFrames

In animation we define known positions and states as key frames, normally we index the key frames in terms of time.

// an array of keys. The ? represents the applicable number value
var keys = [{
time : 0, // the state of an object at time 0
pos : {x : ? , y : ? }, // position
scale : ?,
rotation : ?,
colour : [?,?,?], // rgb colour, just for the hell of it
// and whatever else you may want to animate
},{
time : 100, // the state of the object at time 100
pos : {x : ? , y : ? },
scale : ?,
rotation : ?,
colour : [?,?,?],
// and whatever else you may want to animate
}
]

Normalised time

To get the state of an object at any time t between the key frames we find the normalised time ( a value from 0 to 1) between the times and multiply that to the difference between other states then add that to the beginning state.

So say the time is 50 first we get the normalised time

var currentTime = 50;
var timeDif = currentTime - keys[0].time; // difference from start time to current
// to get the normalised time divid by the differance
var normTime = timeDif / (keys[1].time - keys[0].time); // divide by the differance in time between keys

Now you have the normalised time you can easily calculate any of the states

var scaleDif =  keys[1].scale - keys[0].scale; // get diff in scale
var scaleChange = scaleDif * normTime; // multiply by the normalised time
var currentScale = keys[0].scale + scaleChange; // add to the starting scale

That is all a little long winded but that is to ease you into what is happening. the complete keying function would look like.

function tweenKeys(time,key1,key2){
var nt = (time - key1.time) / (key2.time - key1.time); // get normalised time
// because you can not divide by zero we need a little check. Javascript return infinity if we div by zero but we want the value 0
nt = nt < Infinity ? nt : 0; // zero if there was a divide by zero
var ck = {}; // ck for current key. the key represents the state at time
ck.scale = key1.scale + (key2.scale - key1.scale) * nt;
ck.rotation = key1.rotation + (key2.rotation - key1.rotation ) * nt;
ck.pos.x = key1.pos.x + (key2.pos.x- key1.pos.x) * nt;
ck.pos.y = key1.pos.y + (key2.pos.y- key1.pos.y) * nt;
ck.colour[0] = key1.colour[0] + (key2.colour[0] - key1.colour[0]) * nt;
ck.colour[1] = key1.colour[1] + (key2.colour[1] - key1.colour[1]) * nt;
ck.colour[2] = key1.colour[2] + (key2.colour[2] - key1.colour[2]) * nt;
return ck; // return the newly create state
}

Thats the basics of keyframing and you can find more on it in this answer How would I animate... ?

In space rather than time

All good but for your problem this has not helped, you are not using time you are using position to determine the current state of the object. Well it does not matter what we use to find our current state, any of the values in the key frame can be used to determine that state of all the others. All we need to do is find the normalised difference and then apply that like we did normalised time to all the other values.

Normalised position

So lets look at position

Consider two points p1 and p2, defined as

var p1 = {x : ?, y : ?}; // ? represent some number value
var p2 = {x : ?, y : ?}; // ? represent some number value

And representing your positions A,B

If we have a 3rd point C

var c = {x : ?, y : ?}; // ? represent some number value

somewhere on the 2D plane. We want a formula that will return a 0 when C is at point p1 and 1 when the point c is at point p2. This will be our normalised position used to get the current state.

As position is 2d we need to involve both the x and y in the calculations. We get the distance from p1 to point c and the divide that by the distance between point p1 and p2. that will give us the value we want. To find the distance we use the pythag solution. root of the sum of the squares

var dist = Math.sqrt( Math.pow( p2.x - p1.x, 2) + Math.pow( p2.y - p1.y, 2)); // for the twisted world of IE users and
var dist = Math.hypot(p2.x - p1.x, p2.y - p1.y); // for all good browsers

So the normalised distance is

var normDist = Math.hypot(c.x - p1.x, c.y - p1.y) / Math.hypot(p2.x - p1.x, p2.y - p1.y); 
// because you can not divide by zero we need a little check. Javascript returns infinity if we div by zero but we want the value 0
normDist = normDist < Infinity ? normDist : 0; // zero if there was a divide by zero

Then apply that (normDist) to all the key states.

var currentScale = (keys[1].scale - keys[0].scale) * normDist + keys[0].scale;

Problems with positioning

Ok you say thanks, sorry but that is not the solution, it would be if you knew that the point c is always on the line between p1, p2 but that is not always the case, and under a strict examination it is hardly ever because computers store digital information so there will be a little error in any calculation that requires very fine detail. Also the above method will return 1 for normalized distance for any point that is distance to p2 away from p1, that describes a circle around the point p1. We need to constrain this value a bit more. Also if c is befor the point p1 or after the point p2 it would be handy to know. Thus we can use the following to do so.

// get the unit distance on the line p1,p2 of point c representing 
// the distance along the line that is closest to c
function unitDistOfPoint(p1,p2,c){
var v1 = {}; // working vectors
var v2 = {};
v1.x = p2.x - p1.x; // vector between p1,p2
v1.y = p2.y - p1.y;
v2.x = c.x - p1.x; // vector to c from p1
v2.y = c.y - p1.y;
// a little math magic. Divide the dot product of the vectors v2, v1
// by the square of line length
return (v2.x * v1.x + v2.y * v1.y) / (v1.y * v1.y + v1.x * v1.x);
}

Now we can do the tweening and get your scale

// return the state for a object at point c in terms of key1, to key2
function tweenKeysViaPos(c,key1,key2){
// get the normalised distance of the point c between keys 1 and 2
var nd = unitDistOfPoint(c, key1.pos, key2.pos); // nd for normalised distance
// you may want to constrain the position to only between the points
// do that by clamping the value nd between 0 and 1 inclusive
nd = Math.max(0, Math.min(1, nd)); // clamp the normalise distance
var ck = {}; // ck for current key. the key represents the state at time
ck.scale = key1.scale + (key2.scale - key1.scale) * nt;
ck.rotation = key1.rotation + (key2.rotation - key1.rotation ) * nt;
ck.pos.x = key1.pos.x + (key2.pos.x- key1.pos.x) * nt;
ck.pos.y = key1.pos.y + (key2.pos.y- key1.pos.y) * nt;
ck.colour[0] = key1.colour[0] + (key2.colour[0] - key1.colour[0]) * nt;
ck.colour[1] = key1.colour[1] + (key2.colour[1] - key1.colour[1]) * nt;
ck.colour[2] = key1.colour[2] + (key2.colour[2] - key1.colour[2]) * nt;
return ck; // return the newly create state
}

That is the answer. As a side benefit if the point c does stray away from the line between the keys then the above function also return the position it should be.

For more if needed

You may want to extend this to adapt to many key frames. Normally for more than two key frames and using time it is easy to find the keys that we want by finding where time is greater than the first key and less than the next key. But this is not as simple if you are using the position to work out at which key you are at. So to help a more complex solution you will find this function handy

// returns the distance point c is from the line p1,p2. If on the line
// the the return value is 0. If befor point p1 or after p2 then the distance
// is the distance to p1, or p2 respectively
function distFromLine(p1,p2,c){
var v1 = {}; // working vectors
var v2 = {};
v1.x = p2.x - p1.x; // vector between p1,p2
v1.y = p2.y - p1.y;
v2.x = c.x - p1.x; // vector to c from p1
v2.y = c.y - p1.y;
// a little math magic. Divide the dot product of the vectors v2, v1
// by the square of line length
var u = (v2.x * v1.x + v2.y * v1.y) / (v1.y * v1.y + v1.x * v1.x);
var v3 = {};
if(u < 0){ // befor the start
return Math.hypot(v2.x,v2.y); // distance to p1
}
if(u > 1){ // after end
return Math.hypot(c.x - p2.x,c.y p2.y); // distance to p2
}
// get the point on the line that is closest
v3.x = p1.x + v1.x * u;
v3.y = p1.y + v1.y * u;
// return the distance from that point to c
return Math.hypot(c.x - v3.x,c.y - v3.y); // distance from line of c
}

You can then find the two keys you need by finding the keys that return the smallest distance from the line between them. You and then define a complicated line by defining many key frames and where ever you put an object you can calculate where it should be and in what state.

Hope this helps and did not go over the top. If anything is unclear to anyone that reads please do say so in the comments and I will clarify.

How to scale down a range of numbers with a known min and max value

Let's say you want to scale a range [min,max] to [a,b]. You're looking for a (continuous) function that satisfies

f(min) = a
f(max) = b

In your case, a would be 1 and b would be 30, but let's start with something simpler and try to map [min,max] into the range [0,1].

Putting min into a function and getting out 0 could be accomplished with

f(x) = x - min   ===>   f(min) = min - min = 0

So that's almost what we want. But putting in max would give us max - min when we actually want 1. So we'll have to scale it:

        x - min                                  max - min
f(x) = --------- ===> f(min) = 0; f(max) = --------- = 1
max - min max - min

which is what we want. So we need to do a translation and a scaling. Now if instead we want to get arbitrary values of a and b, we need something a little more complicated:

       (b-a)(x - min)
f(x) = -------------- + a
max - min

You can verify that putting in min for x now gives a, and putting in max gives b.

You might also notice that (b-a)/(max-min) is a scaling factor between the size of the new range and the size of the original range. So really we are first translating x by -min, scaling it to the correct factor, and then translating it back up to the new minimum value of a.

How to scale int values in R between 0 and 100

With treshold I mean, that all values < 50 keep their value.

I got it now with:

employ.data$value <- replace(employ.data$value, employ.data$value > 50, 100)

And my result:

> employ.data
employee value startdate
1 John Doe 1 2010-11-01
2 Peter Gynn 3 2008-03-25
3 Jolie Hope 100 2007-03-14

Range standardization (0 to 1) in R

s = sort(rexp(100))

range01 <- function(x){(x-min(x))/(max(x)-min(x))}

range01(s)

[1] 0.000000000 0.003338782 0.007572326 0.012192201 0.016055006 0.017161145
[7] 0.019949532 0.023839810 0.024421602 0.027197168 0.029889484 0.033039408
[13] 0.033783376 0.038051265 0.045183382 0.049560233 0.056941611 0.057552543
[19] 0.062674982 0.066001242 0.066420884 0.067689067 0.069247825 0.069432174
[25] 0.070136067 0.076340460 0.078709590 0.080393512 0.085591881 0.087540132
[31] 0.090517295 0.091026499 0.091251213 0.099218526 0.103236344 0.105724733
[37] 0.107495340 0.113332392 0.116103438 0.124050331 0.125596034 0.126599323
[43] 0.127154661 0.133392300 0.134258532 0.138253452 0.141933433 0.146748798
[49] 0.147490227 0.149960293 0.153126478 0.154275371 0.167701855 0.170160948
[55] 0.180313542 0.181834891 0.182554291 0.189188137 0.193807559 0.195903010
[61] 0.208902645 0.211308713 0.232942314 0.236135220 0.251950116 0.260816843
[67] 0.284090255 0.284150541 0.288498370 0.295515143 0.299408623 0.301264703
[73] 0.306817872 0.307853369 0.324882091 0.353241217 0.366800517 0.389474449
[79] 0.398838576 0.404266315 0.408936260 0.409198619 0.415165553 0.433960390
[85] 0.440690262 0.458692639 0.464027428 0.474214070 0.517224262 0.538532221
[91] 0.544911543 0.559945121 0.585390414 0.647030109 0.694095422 0.708385079
[97] 0.736486707 0.787250428 0.870874773 1.000000000

Adding ... will allow you to pass through na.rm = T if you want to omit missing values from the calculation (they will still be present in the results):

range01 <- function(x, ...){(x - min(x, ...)) / (max(x, ...) - min(x, ...))}

Scale relative to a value in each group (via dplyr)

This solution is very similar to @thelatemail, but I think it's sufficiently different enough to merit its own answer because it chooses the index based on a condition:

data %>%
group_by(category) %>%
mutate(value = value/value[year == baseYear])

# category year value
#... ... ... ...
#7 A 2002 1.00000000
#8 B 2002 1.00000000
#9 C 2002 1.00000000
#10 A 2003 0.86462789
#11 B 2003 1.07217943
#12 C 2003 0.82209897

(Data output has been truncated. To replicate these results, set.seed(123) when creating data.)



Related Topics



Leave a reply



Submit