47

Assuming values are normalized from 0 to 1, what is the algoritm to get a color to create a heatmap like this?

1 is red, .5 is green, 0 is dark blue.

Working in RMagick / ImageMagick.

heatmap

B Seven
  • 44,484
  • 66
  • 240
  • 385

8 Answers8

50

Here is a JavaScript code snippet to generate CSS hsl color code from [0, 1] value

function heatMapColorforValue(value){
  var h = (1.0 - value) * 240
  return "hsl(" + h + ", 100%, 50%)";
}

This algorithm is based on the 5 color heatmap,

In this algorithm, the colors corresponding with values are

0    : blue   (hsl(240, 100%, 50%))
0.25 : cyan   (hsl(180, 100%, 50%))
0.5  : green  (hsl(120, 100%, 50%))
0.75 : yellow (hsl(60, 100%, 50%))
1    : red    (hsl(0, 100%, 50%))

So simple!

Fareed Alnamrouti
  • 30,771
  • 4
  • 85
  • 76
Mekajiki
  • 911
  • 2
  • 8
  • 18
  • Nice solution, and this code convert HSL to RGB https://github.com/optimisme/javascript-temperatureMap/blob/master/temperatureMap.js#L57 – Cami Rodriguez Oct 12 '16 at 21:33
  • Your answer looks really interesting. And this one with heatmap / color scale examples looks great too: https://stackoverflow.com/a/30612603/470749 – Ryan Oct 16 '20 at 15:39
  • Can someone explain conceptually why this algorithm multiplies by `240` instead of `360`? It would appear to discard the colors between blue and red (e.g. magenta). From the referenced five-color heatmap, that appears correct, but I'm not sure exactly why—why we discard those; why the other hues just happen to match what we want in heat map colors; and why other algorithms (the selected answer to this question, for example) use `360`. – Garret Wilson Aug 31 '22 at 19:35
15

I found this surprisingly easy to do with HSL.

In Ruby:

def heatmap_color_for value # [0,1]
  h = (1 - value) * 100
  s = 100
  l = value * 50
  "hsl(#{h.round(2)}%,#{s.round(2)}%,#{l.round(2)}%)"
end

This method returns HSL values as a string between 0% and 100%. It can be used with RMagick or ImageMagick.

Reference: ImageMagick HSL documentation.

In Java, for CSS, tested:

private String getHeatmapColorForCSS(double normalizedValue0to1) {
    double h = (1 - normalizedValue0to1) * 360;
    double s = 100;
    double l = 50;
    return String.format("hsl(%.2f, %.2f%%, %.2f%%)", h, s, l);
}

Note the key difference between CSS and ImageMagick: the first value is 0-360 and without a percent sign.

Alex R
  • 11,364
  • 15
  • 100
  • 180
B Seven
  • 44,484
  • 66
  • 240
  • 385
  • 1
    h isn't a percent but a degree from 0 - 360 on the color wheel. – pixelearth Jul 04 '14 at 23:27
  • 1
    to create values for css, change to h = (1 - value) * 360 and get rid of the percent sign for the first value in "hsl(xx,xx,xx)" – jpw Jul 24 '18 at 05:00
11

Linear interpolation of the RGB components works quite well in practice, and the link Bruno shared mentions doing your interpolation in HSL which can help.

You can also intersperse your three basic colours with more nicely chosen intermediates. Check out http://colorbrewer2.org/ for some good colour progressions. Then break up your steps further:

0    red
0.25 yellow
0.5  green
0.75 cyan
1    blue
Superboggly
  • 5,804
  • 2
  • 24
  • 27
4

A general approach is to interpolate colors. You decided that

0: 0 0 255 (or any blue)
0.5: 0 255 0 (or any green)
1: 255 0 0 (or any red)

You simply do a linear interpolation of the RGB. Between 2 reference values (eg t between 0 and 0.5), the interpolated color C is like

C = (1 - t) * c0 + t * c1

You must apply this formula on each color component RGB. Some other hints about color linear interpolation: How to interpolate a color sequence?

---- edit ----- I removed the header of my answer, as I realized I misunderstood the question (see comment). I leave a copy for consistent reading, and information, just in case.

A first possibility is to build a reference heatmap with any software that would: create a image 256X1pixel with pixel values from 0 to 255 and apply the desired heatmap with ImageMagick: then you can read the RGB back and build a map (value:RGB).

Community
  • 1
  • 1
Bruno von Paris
  • 882
  • 1
  • 7
  • 26
  • No clue. Anyway, it is not my point for the first approach, I meant: create a file with values from 0-255, then apply an heatmap with your favorite tools, and use the created heatmap as a reference. – Bruno von Paris Oct 13 '12 at 18:24
  • Ah, I realised I misunderstood your last sentence. I though you said that the heatmap color transformation was working in Image magick, and that you wanted to do it on your own in a code. I edit my answer. – Bruno von Paris Oct 13 '12 at 18:26
1

I leave here a Swift 4 implementation based on this blog post for any amount of colors! Perfect explanation is there! Hope it helps and saves some time to someone!

import Foundation
import UIKit

struct ColorPoint {
    let color: UIColor
    let value: CGFloat
}

class HeatMapColor {
    var colorPoints: [ColorPoint]

    init(colorPoints: [ColorPoint]) {
        self.colorPoints = colorPoints
    }

    func colorAt(value: CGFloat) -> UIColor {
        if(colorPoints.isEmpty) { return UIColor.black }

        let colorsPointsToUse = colorPoints.sorted { (colorPointA, colorPointB) -> Bool in
            return colorPointA.value <= colorPointB.value
        }

        for (index, colorPoint) in colorsPointsToUse.enumerated() where value < colorPoint.value {
            let previousColorPoint = colorsPointsToUse[max(0, index - 1)]
            let valueDiff = previousColorPoint.value - colorPoint.value
            let fraction = valueDiff == 0 ? 0 : (value - colorPoint.value) / valueDiff

            guard
                let prevComp = previousColorPoint.color.cgColor.components,
                let currComp = colorPoint.color.cgColor.components else { continue }

            let red = (prevComp[0] - currComp[0]) * fraction + currComp[0]
            let green = (prevComp[1] - currComp[1]) * fraction + currComp[1]
            let blue = (prevComp[2] - currComp[2]) * fraction + currComp[2]

            return UIColor(red: red, green: green, blue: blue, alpha: 1.0)
        }

        return colorsPointsToUse.last!.color
    }
}
Andres
  • 11,439
  • 12
  • 48
  • 87
1

For Java AWT below works reasonably well:

Color.HSBtoRGB((1.0f - value) * 0.85f - 0.15f, 1.0f, 0.5f)

where value is a float between [0-1].

The reason for the multiplications in the first parameter (i.e. hue) is because AWT internally does some floor/subtraction/scaling - See Java Doc

ATutorMe
  • 820
  • 8
  • 14
0

Here's a simple 5 color heatmap in python (in pyqt, but easy to generalize)

def genColorMap(self):
    points = [(255,0,0), (255,255,0), (0,255,0), (0,255,255), (0,0,255)]
    cm = {}
    for i in range(0, 256):
        p0 = int(numpy.floor((i/256.0)/len(points)))
        p1 = int(numpy.ceil((i/256.0)/len(points)))
        rgb = map(lambda x: x[0]*max(0,(i-p0)) + x[1]*max(0,(i-p1)), zip(points[p0], points[p1]))
        cm[i] = QtGui.qRgb(rgb[0], rgb[1], rgb[2])
    return cm
Tim
  • 1,677
  • 1
  • 10
  • 4
  • 4
    Decent answer but try to explain the code so the OP can understand it rather than use it without grasping it! – ShellFish May 09 '15 at 18:58
0

In Delphi or maXbox:

function heatMapColorforValue(value: double): TColor;
var h: double;
begin
  h:= (1.0 - value) * 240
  result:= HSVtoRGB( h , 100, 50);
end;