How To Map A Number Between Two Ranges

Often in creative web projects, I find myself having to take a number, which exists in a specific range of numbers, and find the number that would be in the same position if the range were changed. Examples of this type of operation can vary anywhere from converting Fahrenheit to Celsius, to my recent use-case of creating an array of 500 “particles” with 3D coordinates and assigning each one a position between { z: -10 } and { z: 12 } relative to its position in the original array (i.e. particles[0].z = -10 and particles[499].z = 12).

Here’s a basic tutorial on how to do that sort of thing, first as a mathematical rundown of the concept, and then a dirty JavaScript implementation.

TL;DR

In a rush? Don’t have time for a mere five minute tutorial blog post complete with handy diagrams even though I made them just for you? No worries, here’s a quick JS one-liner for mapping a value from one range to another:

const mapRange = (value, oldMin, oldMax, newMin, newMax) =>((value - oldMin) / (oldMax - oldMin)) * (newMax - newMin) + newMin;

The Math Part

What we’re doing here is a form of something called linear transformation, or linear mapping. Simply put, it’s just scaling any operation that happens on one side to the other. Take for instance that we have a line that goes between 0 and 2, and a value along that line of 1. Intuitively, we know that our value is halfway along the line, so if the line changes size (maybe from 0 to 4), our new value will still be halfway along the line.

The actual concept of linear transformation gets way more complex than this, often involving matrices and vectors and extra dimensions and other words. Since as web developers, we’re merely lemon-muffin-brained baboons, we’ll keep to this shallower end of the pool for this blog post.

Breaking It Down

To implement this concept more concretely, the first thing we’ll need to do is to find the relationship between our initial value and our initial range. We can do this by subtracting the minimum of the range from both our value and the maximum of the range, and then dividing the updated value from the updated maximum.

In the following diagram, we’ll use v to represent our input value and A and B to represent our input range’s minimum and maximum values.

(vA)/(BA)(10)/(20)12\displaystyle \begin{aligned} (v-A)/(B-A)\\ \\ (1-0)/(2-0)\\ \\ \frac{1}{2}\end{aligned}

Just as we understood earlier, our value of 1 is 1/2 of distance between 0 and 2.

Next, we’ll need to take this fraction multiply it by the actual size of the new range. We can find the actual size by subtracting the minimum of the new range from the maximum. We’ll add the minimum back at the very end.

Here we’ll continue to use variables v, A, and B as before, but with the addition of x as our transformed value, C as our output range minimum, and D as our output range maximum.

x=((vA)/(BA))(DC)+Cx=((10)/(20))(40)+0x=124x=2\displaystyle \begin{aligned} x=((v-A)/(B-A))*(D-C)+C\\ \\ x=((1-0)/(2-0))*(4-0)+0\\ \\ x=\frac{1}{2}*4\\ \\ x=2 \end{aligned}

Applying The Concept

Now that we understand what needs to happen mathematically, we can try it out with a more practical application. Let’s use the example of converting between Celsius and Fahrenheight we mentioned earlier.

For choosing the ranges, we can use any set of numbers we want, but let’s go with the freezing and boiling points of water, since we know those values in both scales. We can use any temperature, but for this blog post let’s try 17ºC. Knowing our ranges and input value, we can set up our problem:

((vA)/(BA))(DC)+Cv=17[A,B]=[0,100][C,D]=[32,212]\displaystyle \begin{aligned} ((v - A)/(B-A))*(D-C)+C\\ \\ v=17\\ [A,B]=[0,100]\\ [C,D]=[32,212] \end{aligned}

And all that’s left is to break out our variables and solve:

x=((170)/(1000))(21232)+32x=17100180+32x=62.6\displaystyle \begin{aligned} x=((17-0)/(100-0))*(212-32)+32\\ \\ x=\frac{17}{100}*180+32\\ \\ x=62.6 \end{aligned}

Swell! So 17ºC in Fahrenheit is a lovely 62.6ºF Autumn day.

For style points and to prove our salt, we can convert right back simply by swapping the positions of the ranges and solving.

x=((vA)/(BA))(DC)+Cv=62.6[A,B]=[32,212][C,D]=[0,100]x=((62.632)/(21232))(1000)+0x=30.6/180100x=17\displaystyle \begin{aligned} x=((v-A)/(B-A))*(D-C)+C\\ \\ v=62.6\\ [A,B]=[32,212]\\ [C,D]=[0,100]\\ \\ x=((62.6-32)/(212-32))*(100-0)+0\\ \\ x=30.6/180*100\\ \\ x=17 \end{aligned}

Stupendous. Utterly stunning. We’ll make an insufferable mathematician out of you yet, old friend.

The Code Part

With a mathematical function that does what we want, executing this operation in code is pretty straightforward, and we can even use more meaningful variables. We’ll take our input value, initial range minimum and maximum, and output range minimum and maximum as props, and build a useful JavaScript one-liner from there.

const mapRange = (v, A, B, C, D) => {
  return ((v - A) / (B - A)) * (D - C) + C;
};

// or better yet

const mapRange = (value, inputMin, inputMax, outputMin, outputMax) => {
  return (
    ((value - inputMin) / (inputMax - inputMin)) * (outputMax - outputMin) +
    outputMin
  );
};

Conclusion

I hope this crash course on range mapping was useful to you. As a disclaimer, I’m not A Big Math Guy™, I just end up using a lot of these concepts when I’m making websites. If I got something wrong (I’m specifically unsure about expressing this stuff correctly in the mathematical diagrams), or could explain some aspect more clearly, feel free to reach out via email; I’m glad for the opportunity to improve these blog posts.

Further Reading

More Writing

  • How I Set Up A New Laptop

    Resource

    • bash
    • configuration
    • powerusers
    Read the post
  • A One-Liner For Freeing Ports on OS X

    Resource

    • bash
    Read the post
  • Using Focal Points, Aspect Ratio & Object-Fit To Crop Images Correctly

    Tutorial

    • css
    • css variables
    • aspect-ratio
    • object-fit
    Read the post