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.
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.
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!
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.
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
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).
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
}
}
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