Distinct color generator

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 */
.