From b08e0057c0e5540d41e462b7cb3d327eafad5d7f Mon Sep 17 00:00:00 2001 From: Ian Mayo Date: Fri, 1 Nov 2024 15:58:23 +0000 Subject: [PATCH] Uniform color distribution Fixes #11 --- For more details, open the [Copilot Workspace session](https://copilot-workspace.githubnext.com/debrief/ExtractionHighlighter/issues/11?shareId=XXXX-XXXX-XXXX-XXXX). --- data_highlight/support/color_picker.py | 105 ++++++++++++----------- test/test_Colors.py | 114 ++++++++++++++----------- 2 files changed, 116 insertions(+), 103 deletions(-) diff --git a/data_highlight/support/color_picker.py b/data_highlight/support/color_picker.py index ed7de84..b04858c 100644 --- a/data_highlight/support/color_picker.py +++ b/data_highlight/support/color_picker.py @@ -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) diff --git a/test/test_Colors.py b/test/test_Colors.py index 4cd9c83..f2620ca 100644 --- a/test/test_Colors.py +++ b/test/test_Colors.py @@ -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()