3

I am working on a simple program to just draw circles around some fixed points using the Python turtle package; however, I wanted to make it somewhat like a heat map where the colors get "cooler" as they get farther from the original point. My idea was to take the base white color, #FFFFFF, and subtract a percentage based from that on the distance away.

I assumed hex color codes worked by getting lower in its hex value as the color gets "cooler", but I have now read that the first two mean its red value, the second its green, and the last its blue. How would I go around implementing a heat map the way I want it to work?

I'm confident the distances are correct, I just think I'm using the color codes in the wrong way. The function I wrote to do the color calculation:

def findColor(dist):
    base = "FFFFFF"
    num = int(base, 16)
    percent = dist/800 #800 is the max distance away
    newNum = (num - (num*percent))
    color = hex(int(newNum))
    return color

The resulting map I get is:

circle heat map

With Ignacio Vazquez-Abrams' help about HSV, I've got it looking like this :) :

Updated color algorithm

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
NSchrading
  • 144
  • 1
  • 13

3 Answers3

4

You want to use HSV instead of RGB for the colors; slide across the hue dimension for your distances. colorsys can help.

Ignacio Vazquez-Abrams
  • 776,304
  • 153
  • 1,341
  • 1,358
  • Interesting...I don't know much about color theory, so do I just keep Saturation and Value constant, and slide through the angles 0-360 for Hue? Also, it appears that the color white can not be represented in HSV. Is there a way to include white? – NSchrading Dec 18 '11 at 08:11
  • Yup that's what I did, and I got it working. It looks so pretty :D. Thanks a bunch! – NSchrading Dec 18 '11 at 08:36
  • @NSchrading If the answer was the solution of the problem, mark the Ignacio's post as "the answer" by clicking int he big "V" at the left of the answer. It is the most polite thing to do and the most correct way of granting someone in StackOverflow. – brandizzi Dec 18 '11 at 11:35
1

The trick is to work on the three bytes individually. I'll give a shot at re-writing your function the way I think it ought to work:

def findColor(dist):
    base = 0xFFFFFF
    percent = dist/800 #800 is the max distance away
    hexpercent = 0xFF - (percent * 0xFF)
    first = (base & 0xFF0000) >> 16
    second = (base & 0xFF00) >> 8
    third = base & 0xFF
    color = hex(((first - hexpercent) << 16) | ((second - hexpercent) << 8) | (third - hexpercent))
    return color

I isolate each byte, subtract the percentage from each byte, and then re-assemble the bytes into an integer. There might be a more clever way to do this, but it ought to give you a steady gradient of grays. (Work on just 0xFF0000 or 0xFF00 or 0xFF if you want a steady gradient of red, green, or blue.)

Note that this doesn't take any physiological properties of color into account -- there might be more pleasing ways to approach this. (Actual color specialists were quite upset with the web-safe colors that were selected by computer scientists to be pleasing because of the numbers involved...)

sarnold
  • 102,305
  • 22
  • 181
  • 238
  • Is there a way to make it work using a gradient moving from white > red > green > blue > black? Ex: http://dylanvester.com/image.axd?picture=WindowsLiveWriter/CreatingHeatMapswith.NET2.0C_136B6/HeatMap_3.jpg – NSchrading Dec 18 '11 at 07:58
  • I'd like to suggest sticking to _single color_ gradients; multiple-color gradients are very non-linear and often more confusing than their single-hue equivalents. See [Edward Tufte's Books](http://www.edwardtufte.com/tufte/books_vdqi) for well-reasoned rants against multiple-color color gradients. :) – sarnold Dec 18 '11 at 09:25
1

Here's code that will interpolate any multi-color gradient. It's set up to do white > red > green > blue > black, but it's easy to change it to any other gradient.

The range for the 'd' parameter is 0 to 1, so if you have larger distances you may wish to scale them down for use in this function.

The code takes colors in RGB, but converts them into HSV for nicer interpolation.

import colorsys
import math

GRADIENT_SPEC = [
    (1.0, 1.0, 1.0),  # white
    (1.0, 0.0, 0.0),  # red
    (0.0, 1.0, 0.0),  # green
    (0.0, 0.0, 1.0),  # blue
    (0.0, 0.0, 0.0)]  # black

def gradient(d, spec=GRADIENT_SPEC):
    N = len(spec)
    idx = int(d * (N - 1))
    t = math.fmod(d * (N - 1), 1.0)
    col1 = colorsys.rgb_to_hsv(*spec[min(N - 1, idx)])
    col2 = colorsys.rgb_to_hsv(*spec[min(N - 1, idx + 1)])
    hsv = tuple(a * (1 - t) + b * t for a, b in zip(col1, col2))
    r, g, b = colorsys.hsv_to_rgb(*hsv)
    return '#%02X%02X%02X' % (r * 255, g * 255, b * 255)

for x in xrange(12):
    print x, gradient(x / 10.0)