Generates a given number of distinct colors for use with GUI for .NET e.g. UltraCharts.
I was looking for a way to have colors that are different looking, here's the result after some research and testing.
CLASS Libraries.ColorGenerator USE-WIDGET-POOL :
DEFINE PUBLIC PROPERTY UniqueColor AS CLASS System.Drawing.Color NO-UNDO EXTENT
GET.
SET.
CONSTRUCTOR PUBLIC ColorGenerator ( piNumUniqueColorsToGenerate AS INTEGER ):
DEFINE VARIABLE deColorSpacing AS DECIMAL NO-UNDO.
DEFINE VARIABLE iColorComponent AS INTEGER NO-UNDO INITIAL 1.
DEFINE VARIABLE iNumHueSteps AS INTEGER NO-UNDO.
DEFINE VARIABLE iNumSaturationSteps AS INTEGER NO-UNDO.
DEFINE VARIABLE iNumBrightnessSteps AS INTEGER NO-UNDO.
DEFINE VARIABLE deExtHueSteps AS DECIMAL EXTENT NO-UNDO.
DEFINE VARIABLE deExtSaturationSteps AS DECIMAL EXTENT NO-UNDO.
DEFINE VARIABLE deExtBrightnessSteps AS DECIMAL EXTENT NO-UNDO.
DEFINE VARIABLE iHueStepIndex AS INTEGER NO-UNDO.
DEFINE VARIABLE iSaturationStepIndex AS INTEGER NO-UNDO.
DEFINE VARIABLE iBrightnessStepIndex AS INTEGER NO-UNDO.
DEFINE VARIABLE iColorIndex AS INTEGER NO-UNDO.
/* number of distinguishable colors (hue) on a given Saturation and Brightness slice */
&SCOPED-DEFINE MaxNumHueSteps 15
/* under these levels, colors are hard to distinguish (too dark or too white) */
&SCOPED-DEFINE MinimumSaturation 0.4 &SCOPED-DEFINE MinimumBrightness 0.5
&SCOPED-DEFINE OneColorHue 220 /* blue */
SUPER ().
ASSIGN
iNumHueSteps = MINIMUM({&MaxNumHueSteps}, piNumUniqueColorsToGenerate)
EXTENT(UniqueColor) = piNumUniqueColorsToGenerate
deColorSpacing = SQRT((piNumUniqueColorsToGenerate / iNumHueSteps )) /* split the rest between Saturation and Brightness */
iNumSaturationSteps = System.Math:Ceiling(deColorSpacing)
iNumBrightnessSteps = iNumSaturationSteps
EXTENT(deExtHueSteps) = iNumHueSteps
EXTENT(deExtBrightnessSteps) = iNumBrightnessSteps
EXTENT(deExtSaturationSteps) = iNumSaturationSteps
.
IF iNumHueSteps > 1
THEN DO iHueStepIndex = 1 TO iNumHueSteps:
deExtHueSteps[iHueStepIndex] = (360 / iNumHueSteps) * (iHueStepIndex - 1).
END. /* DO iHueStepIndex = 1 TO iNumHueSteps */
ELSE deExtHueSteps[1] = {&OneColorHue}.
IF iNumSaturationSteps > 1
THEN DO iSaturationStepIndex = 1 TO iNumSaturationSteps:
deExtSaturationSteps[iSaturationStepIndex] = 1 - (((1 - {&MinimumSaturation}) / (iNumSaturationSteps - 1)) * (iSaturationStepIndex - 1)). /* the first will be the minimum, the others will be spaced evenly in the remaining available space */
END. /* THEN DO iSaturationStepIndex = 1 TO iNumSaturationSteps: */
ELSE deExtSaturationSteps[1] = 1.
IF iNumBrightnessSteps > 1
THEN DO iBrightnessStepIndex = 1 TO iNumBrightnessSteps:
deExtBrightnessSteps[iBrightnessStepIndex] = 1 - (((1 - {&MinimumBrightness}) / (iNumBrightnessSteps - 1)) * (iBrightnessStepIndex - 1)). /* the first will be the minimum, the others will be spaced evenly in the remaining available space */
END. /* THEN DO iBrightnessStepIndex = 1 TO iNumBrightnessSteps: */
ELSE deExtBrightnessSteps[1] = 1.
ASSIGN
iHueStepIndex = 1
iSaturationStepIndex = 1
iBrightnessStepIndex = 1
.
DO iColorIndex = 1 TO piNumUniqueColorsToGenerate:
UniqueColor[iColorIndex] = GetRGBColorFromHSB(deExtHueSteps[iHueStepIndex],
deExtSaturationSteps[iSaturationStepIndex],
deExtBrightnessSteps[iBrightnessStepIndex]).
IF iHueStepIndex < iNumHueSteps
THEN iHueStepIndex = iHueStepIndex + 1.
ELSE IF iSaturationStepIndex < iNumSaturationSteps
THEN ASSIGN
iSaturationStepIndex = iSaturationStepIndex + 1
iHueStepIndex = 1
.
ELSE IF iBrightnessStepIndex < iNumBrightnessSteps
THEN ASSIGN
iBrightnessStepIndex = iBrightnessStepIndex + 1
iSaturationStepIndex = 1
iHueStepIndex = 1
.
END. /* DO iColorIndex = 1 TO piNumUniqueColorsToGenerate */
END CONSTRUCTOR.
METHOD PUBLIC System.Drawing.Color GetRGBColorFromHSB(
deHue AS DECIMAL, /* 0 to 360 degrees on the HSB cone, the color type (such as red, blue, or yellow), each value corresponds to one color : 0 is red, 45 is a shade of orange and 55 is a shade of yellow */
deSaturation AS DECIMAL, /* 0 to 1 saturation, the intensity of the color, 0 means no color, that is a shade of grey between black and white; 1 means intense color */
deBrightness AS DECIMAL /* also called Value, the brightness of the color, 0 is always black; depending on the saturation, 1 may be white or a more or less saturated color */
):
DEFINE VARIABLE deRed AS DECIMAL NO-UNDO INITIAL 0.
DEFINE VARIABLE deGreen AS DECIMAL NO-UNDO INITIAL 0.
DEFINE VARIABLE deBlue AS DECIMAL NO-UNDO INITIAL 0.
DEFINE VARIABLE deSectorPosition AS DECIMAL NO-UNDO.
DEFINE VARIABLE deFractionOfSector AS DECIMAL NO-UNDO.
DEFINE VARIABLE iSectorNumber AS INTEGER NO-UNDO.
DEFINE VARIABLE dePAxis AS DECIMAL NO-UNDO.
DEFINE VARIABLE deQAxis AS DECIMAL NO-UNDO.
DEFINE VARIABLE deTAxis AS DECIMAL NO-UNDO.
IF deSaturation > 0
THEN DO:
ASSIGN
deHue = MIN(deHue, 360)
deSectorPosition = deHue / 60.0
iSectorNumber = TRUNCATE(deSectorPosition, 0)
deFractionOfSector = deSectorPosition - iSectorNumber
dePAxis = deBrightness * (1 - deSaturation)
deQAxis = deBrightness * (1 - (deSaturation * deFractionOfSector))
deTAxis = deBrightness * (1 - (deSaturation * (1 - deFractionOfSector)))
.
CASE iSectorNumber:
WHEN 0
THEN ASSIGN
deRed = deBrightness
deGreen = deTAxis
deBlue = dePAxis
.
WHEN 1
THEN ASSIGN
deRed = deQAxis
deGreen = deBrightness
deBlue = dePAxis
.
WHEN 2
THEN ASSIGN
deRed = dePAxis
deGreen = deBrightness
deBlue = deTAxis
.
WHEN 3
THEN ASSIGN
deRed = dePAxis
deGreen = deQAxis
deBlue = deBrightness
.
WHEN 4
THEN ASSIGN
deRed = deTAxis
deGreen = dePAxis
deBlue = deBrightness
.
WHEN 5
THEN ASSIGN
deRed = deBrightness
deGreen = dePAxis
deBlue = deQAxis
.
END CASE. /* CASE iSectorNumber */
END. /* IF deSaturation > 0 */
RETURN System.Drawing.Color:FromArgb(INTEGER(deRed * 255), INTEGER(deGreen * 255), INTEGER(deBlue * 255)).
END METHOD.
END CLASS.One sample usage:
DEFINE VARIABLE objPaintElement AS CLASS Infragistics.UltraChart.Resources.Appearance.PaintElement NO-UNDO. DEFINE VARIABLE iStopColorShift AS INTEGER NO-UNDO INITIAL 1. ASSIGN objPaintElement = NEW Infragistics.UltraChart.Resources.Appearance.PaintElement() objPaintElement:ElementType = Infragistics.UltraChart.Shared.Styles.PaintElementType:Gradient objPaintElement:FillGradientStyle = Infragistics.UltraChart.Shared.Styles.GradientStyle:Vertical objPaintElement:Fill = pObjColorGenerator:UniqueColor[piInfoIndex] objPaintElement:FillStopColor = pObjColorGenerator:UniqueColor[(piInfoIndex + (iStopColorShift * EXP(piInfoIndex,2))) MOD THIS-OBJECT:iNumUniqueInfo] iStopColorShift = - iStopColorShift /* we alternate the color shift so that adjacent colors will have a very different stop color */ .