Java
How to automatically generate N distinct colors
Generating a palette of visually distinct colors is a common challenge across various fields, from data visualization and user interface design to scientific research and art. The human eye is surprisingly adept at discerning subtle differences in color, but ensuring that N colors are truly distinct – and perceived as such – requires careful consideration. Manually selecting colors often leads to bias and inconsistencies. Luckily, there are algorithms and programmatic approaches to automatically generate N “distinct” colors, saving time and improving the overall aesthetic and usability of visual representations. This article will explore different methods to achieve this, focusing on both the theoretical underpinnings and practical implementations.
Understanding Color Spaces and Perceptual Uniformity
Before diving into specific algorithms, it’s crucial to understand the concept of color spaces. RGB (Red, Green, Blue) is a common color space used in digital displays, but it’s not perceptually uniform. This means that equal numerical changes in RGB values may not result in equal perceived changes in color. For instance, a shift of 10 units in the red channel might appear more significant than a shift of 10 units in the blue channel. Because of this perceptual non-uniformity, using RGB to generate distinct colors can lead to palettes where some colors appear much closer to each other than others. This is where perceptually uniform color spaces come into play. CIE Lab (Lab) and LCh (Luminance, Chroma, Hue) are two examples of color spaces designed to be perceptually uniform. In these spaces, a given distance between two colors represents a similar perceived difference, regardless of the colors themselves. This makes them ideal for algorithms that aim to maximize the perceived distance between generated colors. According to Maureen Stone’s book, A Field Guide to Digital Color, “Perceptual uniformity is essential for creating effective color scales for data visualization” [1].
LCh offers an intuitive way to control color palettes. Luminance (L) represents the brightness of the color, Chroma (C) its saturation or colorfulness, and Hue (h) its tint (red, green, blue, etc.). By manipulating these three components, we can generate diverse and distinct color sets. Generating N distinct colors involves maximizing the perceptual distance between each color in the palette. In LCh space, this can be achieved by systematically varying the Hue component while keeping Luminance and Chroma relatively constant or using a gradient. Using a perceptually uniform color space is a major key in automatically generating N “distinct” colors.
Algorithms for Automatic Color Generation
Several algorithms can automatically generate distinct colors. One popular approach is the Golden Ratio method. This method leverages the mathematical properties of the Golden Ratio (approximately 1.618) to distribute hues evenly around the color wheel. The algorithm starts with an initial hue and then iteratively adds a value derived from the Golden Ratio to generate subsequent hues. This ensures that the generated colors are spread out across the spectrum, maximizing their visual distinctiveness. Another method is the k-means clustering algorithm, which can be used to partition a color space into N clusters, with each cluster representing a distinct color. The algorithm aims to minimize the within-cluster variance, effectively grouping similar colors together and separating them from other groups.
For more controlled color palettes, algorithms like ColorBrewer [2] offer pre-defined color schemes optimized for different data visualization purposes. These schemes are carefully designed to ensure perceptual distinctiveness and accessibility, considering factors such as colorblindness. The algorithm generates a range of color schemes including sequential, diverging, and qualitative palettes. Qualitative palettes are designed to provide distinct colors for categorical data, making them ideal for representing different groups or categories in a dataset. ColorBrewer also takes into account the contrast between the colors and the background, ensuring that the colors are easily visible and readable. The choice of algorithm depends on the specific requirements of the application. If a large number of colors are needed, the Golden Ratio or k-means clustering method may be more suitable. If a smaller number of colors are needed and perceptual accuracy is paramount, pre-defined color schemes like those offered by ColorBrewer may be a better choice.
Implementation Examples and Code Snippets
Implementing these algorithms requires some programming knowledge and familiarity with color manipulation libraries. Many programming languages, such as Python, JavaScript, and Java, offer libraries that provide functions for converting between different color spaces and performing color calculations. For example, in Python, the colorsys module provides functions for converting between RGB, HSL (Hue, Saturation, Lightness), and YIQ color spaces. This module can be used to generate distinct colors by manipulating the Hue component of the HSL color space and then converting back to RGB. The scikit-learn library provides an implementation of the k-means clustering algorithm, which can be used to partition a color space into N clusters. Here’s an example of how to use the Golden Ratio method in Python:
- Define a function that takes the number of colors N as input.
- Initialize the hue to a random value between 0 and 1.
- Iterate N times, each time calculating a new hue by adding the Golden Ratio (0.618034) to the previous hue and taking the fractional part.
- Convert each hue to an RGB color using the colorsys module.
- Return the list of RGB colors.
Another example is using Javascript and the chroma.js library. Chroma.js is a powerful library for color manipulation and analysis. Here’s how to generate N distinct colors using chroma.js:
- Install the chroma.js library using npm or yarn.
- Import the chroma.js library into your JavaScript file.
- Use the chroma.scale() function to create a color scale.
- Use the mode() function to specify the color space to use (e.g., LCh).
- Use the colors(N) function to generate N colors from the color scale.
These examples illustrate the basic steps involved in implementing color generation algorithms. The specific implementation will vary depending on the programming language and libraries used, but the underlying principles remain the same: choose a perceptually uniform color space, use an algorithm to maximize the distance between colors, and convert the colors to a suitable format for display or further processing.
Practical Applications and Considerations
The ability to automatically generate distinct colors has numerous practical applications. In data visualization, it allows us to represent different categories or groups of data with visually distinguishable colors, making it easier to understand complex datasets. For example, in a bar chart, each bar can be assigned a unique color to represent a different category. In a scatter plot, different clusters of data points can be highlighted with different colors. In user interface design, distinct colors can be used to differentiate between different elements of the interface, such as buttons, icons, and backgrounds. This can improve the usability and accessibility of the interface, making it easier for users to navigate and interact with the system. In scientific research, distinct colors can be used to label different samples or treatments in an experiment, making it easier to track and analyze the results. Consider a scenario where a researcher is studying the effect of different fertilizers on plant growth. Each fertilizer can be assigned a unique color, allowing the researcher to easily identify which plants received which fertilizer.
When generating distinct colors, it’s important to consider factors such as colorblindness. Approximately 8% of men and 0.5% of women have some form of color vision deficiency. Therefore, it’s essential to choose colors that are distinguishable even by individuals with colorblindness. Tools like ColorBrewer offer color schemes that are specifically designed to be colorblind-safe. Another important consideration is the context in which the colors will be used. The optimal color palette will depend on factors such as the background color, the size and shape of the objects being colored, and the viewing conditions. For example, colors that look distinct on a white background may not be distinguishable on a dark background. When choosing colors, it’s important to test them in the intended context to ensure that they are easily visible and distinguishable. The featured snippet below covers the importance of contrast.
One key factor in ensuring color distinctiveness is contrast. Ensure sufficient contrast between the generated colors and the background on which they will be displayed. A minimum contrast ratio of 4.5:1 is recommended for text and interactive elements to meet accessibility guidelines, as specified by the Web Content Accessibility Guidelines (WCAG) [3]. Insufficient contrast can make it difficult for users to distinguish between colors, especially for those with visual impairments.
FAQ
- What is the best color space for generating distinct colors?
- Perceptually uniform color spaces like CIE Lab and LCh are generally preferred because they ensure that equal numerical changes in color values correspond to equal perceived changes in color.
- How many distinct colors can the human eye perceive?
- The human eye can distinguish millions of colors, but the number of distinct colors that can be used effectively in a single visualization or interface is much smaller. Aim for a manageable palette of 5-10 truly distinct colors.
- What are some common mistakes to avoid when generating distinct colors?
- Avoid using RGB color space directly, ignoring colorblindness considerations, and failing to test the colors in the intended context.
From creating engaging data visualizations to designing user-friendly interfaces, the power of distinct colors is undeniable. Consider exploring techniques like color blending and interpolation to further refine your color palettes. Now that you have a solid foundation, why not explore advanced color theory or the psychology of color to elevate your designs even further?
Question & Answer :
I wrote the two methods below to automatically select N distinct colors. It works by defining a piecewise linear function on the RGB cube. The benefit of this is you can also get a progressive scale if that’s what you want, but when N gets large the colors can start to look similar. I can also imagine evenly subdividing the RGB cube into a lattice and then drawing points. Does anyone know any other methods? I’m ruling out defining a list and then just cycling through it. I should also say I don’t generally care if they clash or don’t look nice, they just have to be visually distinct.
public static List<Color> pick(int num) { List<Color> colors = new ArrayList<Color>(); if (num < 2) return colors; float dx = 1.0f / (float) (num - 1); for (int i = 0; i < num; i++) { colors.add(get(i * dx)); } return colors; } public static Color get(float x) { float r = 0.0f; float g = 0.0f; float b = 1.0f; if (x >= 0.0f && x < 0.2f) { x = x / 0.2f; r = 0.0f; g = x; b = 1.0f; } else if (x >= 0.2f && x < 0.4f) { x = (x - 0.2f) / 0.2f; r = 0.0f; g = 1.0f; b = 1.0f - x; } else if (x >= 0.4f && x < 0.6f) { x = (x - 0.4f) / 0.2f; r = x; g = 1.0f; b = 0.0f; } else if (x >= 0.6f && x < 0.8f) { x = (x - 0.6f) / 0.2f; r = 1.0f; g = 1.0f - x; b = 0.0f; } else if (x >= 0.8f && x <= 1.0f) { x = (x - 0.8f) / 0.2f; r = 1.0f; g = 0.0f; b = x; } return new Color(r, g, b); }
This questions appears in quite a few SO discussions:
- Algorithm For Generating Unique Colors
- Generate unique colours
- Generate distinctly different RGB colors in graphs
- How to generate n different colors for any natural number n?
Different solutions are proposed, but none are optimal. Luckily, science comes to the rescue
Arbitrary N
- Colour displays for categorical images (free download)
- A WEB SERVICE TO PERSONALISE MAP COLOURING (free download, a webservice solution should be available by next month)
- An Algorithm for the Selection of High-Contrast Color Sets (the authors offer a free C++ implementation)
- High-contrast sets of colors (The first algorithm for the problem)
The last 2 will be free via most university libraries / proxies.
N is finite and relatively small
In this case, one could go for a list solution. A very interesting article in the subject is freely available:
There are several color lists to consider:
- Boynton’s list of 11 colors that are almost never confused (available in the first paper of the previous section)
- Kelly’s 22 colors of maximum contrast (available in the paper above)
I also ran into this Palette by an MIT student. Lastly, The following links may be useful in converting between different color systems / coordinates (some colors in the articles are not specified in RGB, for instance):
- http://chem8.org/uch/space-55036-do-blog-id-5333.html
- https://metacpan.org/pod/Color::Library::Dictionary::NBS_ISCC
- Color Theory: How to convert Munsell HVC to RGB/HSB/HSL
For Kelly’s and Boynton’s list, I’ve already made the conversion to RGB (with the exception of white and black, which should be obvious). Some C# code:
public static ReadOnlyCollection<Color> KellysMaxContrastSet { get { return _kellysMaxContrastSet.AsReadOnly(); } } private static readonly List<Color> _kellysMaxContrastSet = new List<Color> { UIntToColor(0xFFFFB300), //Vivid Yellow UIntToColor(0xFF803E75), //Strong Purple UIntToColor(0xFFFF6800), //Vivid Orange UIntToColor(0xFFA6BDD7), //Very Light Blue UIntToColor(0xFFC10020), //Vivid Red UIntToColor(0xFFCEA262), //Grayish Yellow UIntToColor(0xFF817066), //Medium Gray //The following will not be good for people with defective color vision UIntToColor(0xFF007D34), //Vivid Green UIntToColor(0xFFF6768E), //Strong Purplish Pink UIntToColor(0xFF00538A), //Strong Blue UIntToColor(0xFFFF7A5C), //Strong Yellowish Pink UIntToColor(0xFF53377A), //Strong Violet UIntToColor(0xFFFF8E00), //Vivid Orange Yellow UIntToColor(0xFFB32851), //Strong Purplish Red UIntToColor(0xFFF4C800), //Vivid Greenish Yellow UIntToColor(0xFF7F180D), //Strong Reddish Brown UIntToColor(0xFF93AA00), //Vivid Yellowish Green UIntToColor(0xFF593315), //Deep Yellowish Brown UIntToColor(0xFFF13A13), //Vivid Reddish Orange UIntToColor(0xFF232C16), //Dark Olive Green }; public static ReadOnlyCollection<Color> BoyntonOptimized { get { return _boyntonOptimized.AsReadOnly(); } } private static readonly List<Color> _boyntonOptimized = new List<Color> { Color.FromArgb(0, 0, 255), //Blue Color.FromArgb(255, 0, 0), //Red Color.FromArgb(0, 255, 0), //Green Color.FromArgb(255, 255, 0), //Yellow Color.FromArgb(255, 0, 255), //Magenta Color.FromArgb(255, 128, 128), //Pink Color.FromArgb(128, 128, 128), //Gray Color.FromArgb(128, 0, 0), //Brown Color.FromArgb(255, 128, 0), //Orange }; static public Color UIntToColor(uint color) { var a = (byte)(color >> 24); var r = (byte)(color >> 16); var g = (byte)(color >> 8); var b = (byte)(color >> 0); return Color.FromArgb(a, r, g, b); }
And here are the RGB values in hex and 8-bit-per-channel representations:
kelly_colors_hex = [ 0xFFB300, # Vivid Yellow 0x803E75, # Strong Purple 0xFF6800, # Vivid Orange 0xA6BDD7, # Very Light Blue 0xC10020, # Vivid Red 0xCEA262, # Grayish Yellow 0x817066, # Medium Gray # The following don't work well for people with defective color vision 0x007D34, # Vivid Green 0xF6768E, # Strong Purplish Pink 0x00538A, # Strong Blue 0xFF7A5C, # Strong Yellowish Pink 0x53377A, # Strong Violet 0xFF8E00, # Vivid Orange Yellow 0xB32851, # Strong Purplish Red 0xF4C800, # Vivid Greenish Yellow 0x7F180D, # Strong Reddish Brown 0x93AA00, # Vivid Yellowish Green 0x593315, # Deep Yellowish Brown 0xF13A13, # Vivid Reddish Orange 0x232C16, # Dark Olive Green ] kelly_colors = dict(vivid_yellow=(255, 179, 0), strong_purple=(128, 62, 117), vivid_orange=(255, 104, 0), very_light_blue=(166, 189, 215), vivid_red=(193, 0, 32), grayish_yellow=(206, 162, 98), medium_gray=(129, 112, 102), # these aren't good for people with defective color vision: vivid_green=(0, 125, 52), strong_purplish_pink=(246, 118, 142), strong_blue=(0, 83, 138), strong_yellowish_pink=(255, 122, 92), strong_violet=(83, 55, 122), vivid_orange_yellow=(255, 142, 0), strong_purplish_red=(179, 40, 81), vivid_greenish_yellow=(244, 200, 0), strong_reddish_brown=(127, 24, 13), vivid_yellowish_green=(147, 170, 0), deep_yellowish_brown=(89, 51, 21), vivid_reddish_orange=(241, 58, 19), dark_olive_green=(35, 44, 22))
For all you Java developers, here are the JavaFX colors:
// Don't forget to import javafx.scene.paint.Color; private static final Color[] KELLY_COLORS = { Color.web("0xFFB300"), // Vivid Yellow Color.web("0x803E75"), // Strong Purple Color.web("0xFF6800"), // Vivid Orange Color.web("0xA6BDD7"), // Very Light Blue Color.web("0xC10020"), // Vivid Red Color.web("0xCEA262"), // Grayish Yellow Color.web("0x817066"), // Medium Gray Color.web("0x007D34"), // Vivid Green Color.web("0xF6768E"), // Strong Purplish Pink Color.web("0x00538A"), // Strong Blue Color.web("0xFF7A5C"), // Strong Yellowish Pink Color.web("0x53377A"), // Strong Violet Color.web("0xFF8E00"), // Vivid Orange Yellow Color.web("0xB32851"), // Strong Purplish Red Color.web("0xF4C800"), // Vivid Greenish Yellow Color.web("0x7F180D"), // Strong Reddish Brown Color.web("0x93AA00"), // Vivid Yellowish Green Color.web("0x593315"), // Deep Yellowish Brown Color.web("0xF13A13"), // Vivid Reddish Orange Color.web("0x232C16"), // Dark Olive Green };
the following is the unsorted kelly colors according to the order above.
the following is the sorted kelly colors according to hues (note that some yellows are not very contrasting)

