With that observation, I finally turn to some actual code. I recently had to generate a set of bar graphs representing website traffic for the most active pages on my site, as shown in the figure to the right. I wanted each directory group to use a unique color scheme, so that it would stand out from its neighbors, and I also needed a distinct color scheme for the larger, popup graphs. What I did not want to do was to hardwire the color schemes, and what I really did not want was to produce a bunch of color schemes by hand. The image below shows what happens when I pick colors by hand not really awful, but not all that great, either. Each outlined bar represents the average number of hits per day for a given week; the inner red bar represents the standard deviation for that week.)
Fortunately, since I know about as much about HSB as you do now, I knew that I could generate reasonably appealing random color schemes by picking light colors with moderate saturation. I also had a Shemitz.Drawing namespace I'd done for my .NET book, which includes the HSB support that System.Drawing does not. (The Color structure will tell you the HSB values of a given Color, but it has no methods to construct a Color from an HSB triplet, nor does it have any methods to manipulate a color's HSB values.) The Shemitz.Drawing HSB class uses Bob Powell's code to convert a HSB triplet to RGB. An HSB instance has Hue, Saturation, and Brightness properties: You can create an instance from an existing Color or from an arbitrary Hue / Saturation / Brightness triplet; manipulate the properties; and then call ToColor (with an optional Alpha parameter) to turn the HSB instance into a standard .NET Color.
After a little tinkering, I settled on a simple algorithm that relies on my HSB class and a Rand.InRange function that returns a random number in a 'saw-toothed' distribution about a mean:
public static float InRange(Random Random, Range R)
{
return R.Mean
+ R.HalfWidth * (float)Random.NextDouble()
- R.HalfWidth * (float)Random.NextDouble();
}
To create a random color scheme, I choose a single, random base hue float BaseHue = 360 * (float)Random.NextDouble() and then use that to generate three pairs of related random colors for the chart background gradient, the outer (mean) bar gradient, and the inner (standard deviation) bar gradient.
Specifically, I set the outside background color to the random base Hue, with a Saturation of 0.55 ± 0.075 and a Brightness of 0.9 ± 0.066. I set the inside background color to the base Hue plus 120 ± 90°, with a Saturation of 0.55 ± 0.075 and a Brightness of 0.95 ± 0.045. The precise numbers aren't incredibly important; what you should note here is that the two hues are never too close (always at least 30° apart) and the inner color is a bit brighter than the outer color.
I use a similar process of rotating Hue within a range and setting Saturation and Brightness within a range to pick the four bar colors, as sown in the following table.
| Hue rotation | Saturation | Brightness |
| Mean | ± | Mean | ± | Mean | ± |
| Top mean bar | 180 | 120 | 0.85 | 0.145 | 0.84 | 0.2 |
| Bottom mean bar | 180 | 120 | 0.8 | 0.2 | 0.93 | 0.03 |
| Top deviation bar | 30 | 30 | 0.8 | 0.19 | 0.25 | 0.02 |
| Bottom Deviation bar | 30 | 30 | 0.5 | 0.2 | 0.95 | 0.025 |
As with the background colors, the precise values of these random ranges are less important than the relationships between them. The outer bar Hue is always at least 60° from the base Hue, which guarantees that the bars and the background will be noticeably distinct colors. Similarly, the inner bar Hue is never more than 60° from the base Hue, so that it stands out from the outer bar. The outer bar gets brighter from top to bottom, which makes taller bars stand out, and also makes for a bright background for the labels. The inner bar gets grayer from top to bottom, which also helps the labels stand out.
Finally, I draw the outer bar with an alpha of 150 and the inner bar with an alpha of 166. This lets the y-axis lines show through, as well as adding a bit of the background gradient to each bar. The end result is an infinite supply of legible, vaguely appealing, candy-colored graphs, as in these two figures. You can use a similar algorithm to generate your own random color schemes, or to generate an aesthetically pleasing color scheme from a user-supplied color.
Jon Shemitz is an independent developer in Santa Cruz, California, and the author of .NET 2.0 For Delphi Programmers (Apress, 2006).