Search code examples
javascriptcsscolors

How can I create a color heatmap in JavaScript and CSS?


I have a value representing the +/-ity of an article which ranges from -1 to 1 with -1 being strongly negative and 1 strongly positive.

I want to turn this into a color such that it ranges from red (strongly negative) pass through grey (neutral) and ends at green (strongly positive).

The solution that I have come up with does not fully satisfy what I am trying to achieve. I am not a color expert but for some reason any value even slightly negative e.g. -.07 is resulting in a bright red. I also expect any positive value to start looking green but what I see is actually orange.

What am I doing wrong? Is there a way to represent what I am after?

function getAsHslHeatMap(value: number) {
    value = Math.max(-1, Math.min(1, value));
    
    let hue;
    
    if (value <= 0) {
        // Transition from red to grey | Red is 0 degrees
        hue = (1 - value) * 0;
    } else {
        // Transition from grey to green | Green is 120 degrees
        hue = value * 120;
    }
    
    // Convert hue to an integer for CSS
    hue = Math.round(hue);
    
    let saturation = 100; // Full saturation
    let lightness = 50; // Mid lightness
    
    return `hsl(${hue}, ${saturation}%, ${lightness}%)`;
}

Solution

  • There's a couple of simple issues in your code:

    • Multiplication by 0 will always give you 0 (see: hue = (1 - value) * 0;)
    • Hue ranges from 0(red) to 360(red), 120 being green. Therefore, if value is less-or-equal to 0 you want hue to to be 0 (red) and if is greater than 0 - set it to 120 (green)
    • Going red ... gray ... green involves not only changing hue, but also saturation. If your value ranges from -1.0...0.0...1.0 that means that in order to define the saturation to be passed to the HSL — you'll need the absolute (non-negative) float number of value (times 100, for percentage)

    const getAsHslHeatMap = (value) => {
      value = Math.max(-1, Math.min(1, value));
      const hue = value > 0 ? 120 : 0; // 120 = green; 0 = red
      const saturation = Math.abs(value) * 100;
      return `hsl(${hue}, ${saturation}%, 50%)`;
    };
    
    
    // DEMO ONLY:
    const elInput = document.querySelector("input");
    const changeLikeness = () => document.body.style.backgroundColor = getAsHslHeatMap(elInput.valueAsNumber);
    elInput.addEventListener("input", changeLikeness); // on input 
    changeLikeness(); // on init
    Likeness: <input type=range min=-1 max=1 value=0 step=0.01>

    Or visually:

                  Negative...|...Positive
    value:       -1 ....... 0.0 ....... 1
    hue:               0     0     120
    saturation%:  100 ...... 0 ...... 100