Skip to content

Commit

Permalink
Uniform color distribution
Browse files Browse the repository at this point in the history
  • Loading branch information
IanMayo committed Nov 1, 2024
1 parent c49fc22 commit b08e005
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 103 deletions.
105 changes: 53 additions & 52 deletions data_highlight/support/color_picker.py
Original file line number Diff line number Diff line change
@@ -1,52 +1,53 @@
import random
import colorsys


def color_for(hash_code, color_dict):
"""
Get a color for a specific 'hash code' by either taking one we've already recorded for this hash code,
or generating a new random one.
"""
# do we have it already?
if hash_code in color_dict:
# yes, job done
return color_dict[hash_code]
else:
# no, generate one
hue = random.random()
sat = 0.9 + random.random() * 0.1
rgb = colorsys.hsv_to_rgb(hue, sat, 0.9)
r = int(rgb[0] * 255)
g = int(rgb[1] * 255)
b = int(rgb[2] * 255)
new_col = (r, g, b)
# store the new color
color_dict[hash_code] = new_col
return new_col


def hex_color_for(rgb):
"""
Convert a 3-element rgb structure to a HTML color definition
"""
opacity_shade = 0.3
return "rgba(%d,%d,%d,%f)" % (rgb[0], rgb[1], rgb[2], opacity_shade)


def mean_color_for(color_arr):
"""
find the mean of the provided colors
Args:
color_arr: three-element list of R, G and B components of color
"""
r = 0
g = 0
b = 0
for color in color_arr:
r += color[0]
g += color[1]
b += color[2]

arr_len = len(color_arr)
return int(r / arr_len), int(g / arr_len), int(b / arr_len)
import random
import colorsys


def color_for(hash_code, color_dict, total_colors):
"""
Get a color for a specific 'hash code' by either taking one we've already recorded for this hash code,
or generating a new one at regular intervals in the HSV color model.
"""
# do we have it already?
if hash_code in color_dict:
# yes, job done
return color_dict[hash_code]
else:
# no, generate one
hue = (len(color_dict) / total_colors) % 1.0
sat = 0.9
val = 0.9
rgb = colorsys.hsv_to_rgb(hue, sat, val)
r = int(rgb[0] * 255)
g = int(rgb[1] * 255)
b = int(rgb[2] * 255)
new_col = (r, g, b)
# store the new color
color_dict[hash_code] = new_col
return new_col


def hex_color_for(rgb):
"""
Convert a 3-element rgb structure to a HTML color definition
"""
opacity_shade = 0.3
return "rgba(%d,%d,%d,%f)" % (rgb[0], rgb[1], rgb[2], opacity_shade)


def mean_color_for(color_arr):
"""
find the mean of the provided colors
Args:
color_arr: three-element list of R, G and B components of color
"""
r = 0
g = 0
b = 0
for color in color_arr:
r += color[0]
g += color[1]
b += color[2]

arr_len = len(color_arr)
return int(r / arr_len), int(g / arr_len), int(b / arr_len)
114 changes: 63 additions & 51 deletions test/test_Colors.py
Original file line number Diff line number Diff line change
@@ -1,51 +1,63 @@
import unittest
from data_highlight.support.color_picker import color_for, hex_color_for, mean_color_for


class ColorTests(unittest.TestCase):

############################
#### setup and teardown ####
############################

def setUp(self):
pass

def tearDown(self):
pass

####################
#### file tests ####
####################

def test_ColorFor(self):
color_dict = {}
color1 = color_for("aaa", color_dict)
assert color1 is not None
self.assertEqual(1, len(color_dict))

color2 = color_for("bbb", color_dict)
assert color2 is not None
self.assertEqual(2, len(color_dict))
self.assertNotEqual(color1, color2)

color3 = color_for("aaa", color_dict)
self.assertEqual(2, len(color_dict), "Should not have created new dict entry")
self.assertEqual(color1, color3)

def test_HexConversion(self):
red = (255, 0, 0)
self.assertEqual("rgba(255,0,0,0.300000)", hex_color_for(red))

def test_MeanColor(self):
color1 = (100, 50, 200)
color2 = (50, 0, 150)
color3 = (150, 100, 250)

self.assertEqual((75, 25, 175), mean_color_for((color1, color2)))
self.assertEqual((100, 50, 200), mean_color_for((color3, color2)))
self.assertEqual((100, 50, 200), mean_color_for((color1, color2, color3)))


if __name__ == "__main__":
unittest.main()
import unittest
from data_highlight.support.color_picker import color_for, hex_color_for, mean_color_for


class ColorTests(unittest.TestCase):

############################
#### setup and teardown ####
############################

def setUp(self):
pass

def tearDown(self):
pass

#####################
#### color tests ####
#####################

def test_ColorFor(self):
color_dict = {}
color1 = color_for("aaa", color_dict, 10)
assert color1 is not None
self.assertEqual(1, len(color_dict))

color2 = color_for("bbb", color_dict, 10)
assert color2 is not None
self.assertEqual(2, len(color_dict))
self.assertNotEqual(color1, color2)

color3 = color_for("aaa", color_dict, 10)
self.assertEqual(2, len(color_dict), "Should not have created new dict entry")
self.assertEqual(color1, color3)

def test_HexConversion(self):
red = (255, 0, 0)
self.assertEqual("rgba(255,0,0,0.300000)", hex_color_for(red))

def test_MeanColor(self):
color1 = (100, 50, 200)
color2 = (50, 0, 150)
color3 = (150, 100, 250)

self.assertEqual((75, 25, 175), mean_color_for((color1, color2)))
self.assertEqual((100, 50, 200), mean_color_for((color3, color2)))
self.assertEqual((100, 50, 200), mean_color_for((color1, color2, color3)))

def test_UniformColorDistribution(self):
color_dict = {}
total_colors = 10
colors = [color_for(str(i), color_dict, total_colors) for i in range(total_colors)]
self.assertEqual(total_colors, len(set(colors)), "Colors are not uniformly distributed")

for i in range(total_colors - 1):
color1 = colors[i]
color2 = colors[i + 1]
contrast = abs(color1[0] - color2[0]) + abs(color1[1] - color2[1]) + abs(color1[2] - color2[2])
self.assertGreater(contrast, 50, "Adjacent colors do not have sufficient contrast")


if __name__ == "__main__":
unittest.main()

0 comments on commit b08e005

Please sign in to comment.