We have a heatmap we want to display. The numbers that will make up the values being displayed are unknown (except that they will be positive integers). The range of numbers
Continuing from Ian Boyd's excellent answer, I needed a distinguishable set of colours to build a heatmap. The trick was to find a way to differentiate close colours and I found a solution by converting to HSV and varying the V according to the value, with a little emphasis in the middle of the colour range to bring out the yellows and oranges.
Here's the code:
Imports System.Drawing
Imports RGBHSV
Module HeatToColour_
' Thanks to Ian Boyd's excellent post here:
' http://stackoverflow.com/questions/2374959/algorithm-to-convert-any-positive-integer-to-an-rgb-value
Private Const MinVisibleWaveLength As Double = 450.0
Private Const MaxVisibleWaveLength As Double = 700.0
Private Const Gamma As Double = 0.8
Private Const IntensityMax As Integer = 255
Function HeatToColour(ByVal value As Double, ByVal MinValue As Double, ByVal MaxValues As Double) As System.Drawing.Color
Dim wavelength As Double
Dim Red As Double
Dim Green As Double
Dim Blue As Double
Dim Factor As Double
Dim scaled As Double
scaled = (value - MinValue) / (MaxValues - MinValue)
wavelength = scaled * (MaxVisibleWaveLength - MinVisibleWaveLength) + MinVisibleWaveLength
Select Case Math.Floor(wavelength)
Case 380 To 439
Red = -(wavelength - 440) / (440 - 380)
Green = 0.0
Blue = 1.0
Case 440 To 489
Red = 0.0
Green = (wavelength - 440) / (490 - 440)
Blue = 1.0
Case 490 To 509
Red = 0.0
Green = 1.0
Blue = -(wavelength - 510) / (510 - 490)
Case 510 To 579
Red = (wavelength - 510) / (580 - 510)
Green = 1.0
Blue = 0.0
Case 580 To 644
Red = 1.0
Green = -(wavelength - 645) / (645 - 580)
Blue = 0.0
Case 645 To 780
Red = 1.0
Green = 0.0
Blue = 0.0
Case Else
Red = 0.0
Green = 0.0
Blue = 0.0
End Select
' Let the intensity fall off near the vision limits
Select Case Math.Floor(wavelength)
Case 380 To 419
Factor = 0.3 + 0.7 * (wavelength - 380) / (420 - 380)
Case 420 To 700
Factor = 1.0
Case 701 To 780
Factor = 0.3 + 0.7 * (780 - wavelength) / (780 - 700)
Case Else
Factor = 0.0
End Select
Dim R As Integer = Adjust(Red, Factor)
Dim G As Integer = Adjust(Green, Factor)
Dim B As Integer = Adjust(Blue, Factor)
Dim result As Color = System.Drawing.Color.FromArgb(255, R, G, B)
Dim resulthsv As New HSV
resulthsv = ColorToHSV(result)
resulthsv.Value = 0.7 + 0.1 * scaled + 0.2 * Math.Sin(scaled * Math.PI)
result = HSVToColour(resulthsv)
Return result
End Function
Private Function Adjust(ByVal Colour As Double, ByVal Factor As Double) As Integer
If Colour = 0 Then
Return 0
Else
Return Math.Round(IntensityMax * Math.Pow(Colour * Factor, Gamma))
End If
End Function
End Module
Imports System.Drawing
Public Module RGBHSV
Public Class HSV
Sub New()
Hue = 0
Saturation = 0
Value = 0
End Sub
Public Sub New(ByVal H As Double, ByVal S As Double, ByVal V As Double)
Hue = H
Saturation = S
Value = V
End Sub
Public Hue As Double
Public Saturation As Double
Public Value As Double
End Class
Public Function ColorToHSV(ByVal color As Color) As HSV
Dim max As Integer = Math.Max(color.R, Math.Max(color.G, color.B))
Dim min As Integer = Math.Min(color.R, Math.Min(color.G, color.B))
Dim result As New HSV
With result
.Hue = color.GetHue()
.Saturation = If((max = 0), 0, 1.0 - (1.0 * min / max))
.Value = max / 255.0
End With
Return result
End Function
Public Function HSVToColour(ByVal hsv As HSV) As Color
Dim hi As Integer
Dim f As Double
With hsv
hi = Convert.ToInt32(Math.Floor(.Hue / 60)) Mod 6
f = .Hue / 60 - Math.Floor(.Hue / 60)
.Value = .Value * 255
Dim v As Integer = Convert.ToInt32(.Value)
Dim p As Integer = Convert.ToInt32(.Value * (1 - .Saturation))
Dim q As Integer = Convert.ToInt32(.Value * (1 - f * .Saturation))
Dim t As Integer = Convert.ToInt32(.Value * (1 - (1 - f) * .Saturation))
If hi = 0 Then
Return Color.FromArgb(255, v, t, p)
ElseIf hi = 1 Then
Return Color.FromArgb(255, q, v, p)
ElseIf hi = 2 Then
Return Color.FromArgb(255, p, v, t)
ElseIf hi = 3 Then
Return Color.FromArgb(255, p, q, v)
ElseIf hi = 4 Then
Return Color.FromArgb(255, t, p, v)
Else
Return Color.FromArgb(255, v, p, q)
End If
End With
End Function
End Module
and a resulting heatmap, showing GDP per capita for the countries in the EEC: