The KIRUPA orange logo! A stylized orange made to look like a glass of orange juice! Tutorials Books Videos Forums

Customize Theme


Color

Background


Done

Remapping Values

by kirupa   |   filed under JavaScript 101

Remapping is when we transform a value from one range to another. If this totally makes sense to you, then allow me to quickly give you the code. Meet the remap function:

function remap(value, istart, istop, ostart, ostop) {
  // Ensure values are numerical to avoid potential errors
  value = Number(value);
  istart = Number(istart);
  istop = Number(istop);
  ostart = Number(ostart);
  ostop = Number(ostop);

  // Perform the mapping calculation
  return ostart + (ostop - ostart) * ((value - istart) / (istop - istart));
}

// Some examples of this function being used!
let example1 = remap(50, 0, 100, 0, 2000);
console.log(example1); // 1000

let example2 = remap(15, -100, 100, 0, 100);
console.log(example2) // 57.5

let example3 = remap(100, 0, 2000, 0, 50);
console.log(example3) // 2.5 

If what I mentioned earlier instead sounds a bit cryptic, or you want to learn a bit more on why remapping values is an important technique to keep in your back pocket, then the following sections are for you.

Onwards!

Understanding Remapping

The best way to explain the need for remap is with an example. Here is the situation. We have our browser window:

In this window, we are displaying a page where we have a picture of an octopus animating from the top of the page to the bottom of the page:

As the octopus is animating down, it becomes more transparent. Another way of describing it is that our octopus has full opacity at the top of the browser window. It has no opacity at the bottom of our browser window where it appears to be invisible:

If we had to build this animation, notice that there are a handful of values we are trying to reconcile. The main input value is our octopus's vertical position. Where it is in our window will determine the level of opacity. Our input value has a finite range. This range is entirely determined by our window's height:

At the top of our window, the vertical position will be 0. At the bottom of our window, the vertical position will be specified by the actual window height. For simplicity, we can just say that our window has a height of 1000 pixels. There is another range of numbers at play here.

As our octopus is moving down, its opacity changes. Its opacity at the top of our window will be 100, aka fully visible. Its opacity at the bottom of the window will be 0, aka fully invisible. This can be visualized as follows:

We now have two ranges of numbers. One range deals with our vertical position (aka y-position), and it goes from 0 to 1000. Our other range specifies our opacity, and it goes from 100 to 0. Our input value is our octopus's vertical position, and this is in the range of our vertical position as well. How can we ensure that, as our octopus moves from the top of our window (y-position of 0) to the bottom of our window (y-position of 1000), the opacity decreases proportionally from 100 to 0? What is the mathematical scale value we will use to translate each unit our octopus moves down to a unit of opacity change?

The answer to this question is where remapping and our remap function comes in. The remap function takes five arguments, which in the context of our octopus example is as follows:

  1. Input value: our octopus's vertical position
  2. Input start: the vertical position at the top of our window, which is 0
  3. Input end: the vertical position at the bottom of our window, which is 1000
  4. Output start: the opacity at the top of our window, which is 100
  5. Output end: the opacity at the bottom of our window, which is 0

If our octopus has a vertical position of 300 and we want to calculate what the opacity will need to be, below is an example of our remap function and the arguments we provide to calculate the remapped value:pris

function remap(value, istart, istop, ostart, ostop) {
  // Ensure values are numerical to avoid potential errors
  value = Number(value);
  istart = Number(istart);
  istop = Number(istop);
  ostart = Number(ostart);
  ostop = Number(ostop);

  // Perform the mapping calculation
  return ostart + (ostop - ostart) * ((value - istart) / (istop - istart));
}

let opacity = remap(300, 0, 1000, 100, 0);
console.log(opacity); // 70

The answer is 70. When our octopus has a vertical position of 300, it will have an opacity value of 70. The quick math behind this is as follows.

  1. When our octopus's vertical position is 300, it means it has fallen 30% of the way to reaching the bottom at 1000.
  2. Given that our opacity goes from 100 to 0, a 30% drop leaves us right at the 70 mark

If this quick explanation wasn't very satisfactory, in the next section let's look at a more involved mathematical breakdown.

The Math Behind Remapping

The math behind remapping is all about finding the rate of change (aka slope) for each adjustment of our input value. To expand on this, we start with our three components:

Now, let's look at the various stages these components and subcomponents will be used.

Normalizing the Value

Next, to make values from different ranges comparable, we first normalize the input value within its original range:

normalizedValue = (value - istart) / (istop - istart)

This results in a value between 0 and 1, representing its relative position within the original range.

Rescaling

Now, we adjust the normalized value to fit within the target range:

rescaledValue = ostart + (ostop - ostart) * normalizedValue

This expands or compresses the normalized value to match the proportions of the target range.

Putting it All Together

The entire remap process can be expressed in a single formula:

remappedValue = ostart + (okstop - ostart) * ((value - istart) / (istop - istart))

It is this formula that we see represented in our remap function above, and even the order of operations is maintained for ease of readability.

Conclusion

The remap function is hugely useful since much of our time coding will involve taking an input from one range and translating it to another. This happens frequently when we are dealing with UI things, such as our animating octopus. This also crops up a whole lot when dealing with external data and ensuring we have some level of normalization of the data before it reaches our code. There are bunch more scenarios, but the three takeaways to keep in mind from a mathematical point of view are:

  1. Remap functions preserve a proportional relationship between values
  2. They are linear transformations, meaning they maintain straight-line relationships
  3. They are useful for adapting values to different scales or contexts

We are going to be seeing the remap function a whole lot, so it's good to understand both the code and a bit of the math behind why it works the way it does.

Just a final word before we wrap up. If you have a question and/or want to be part of a friendly, collaborative community of over 220k other developers like yourself, post on the forums for a quick response!

Kirupa's signature!

The KIRUPA Newsletter

Thought provoking content that lives at the intersection of design 🎨, development 🤖, and business 💰 - delivered weekly to over a bazillion subscribers!

SUBSCRIBE NOW

Serving you freshly baked content since 1998!
Killer icons by Dark Project Studios

Twitter Youtube Facebook Pinterest Instagram Github