Generate distinctly different RGB colors in graphs

前端 未结 12 1178
故里飘歌
故里飘歌 2020-11-30 16:51

When generating graphs and showing different sets of data it usually a good idea to difference the sets by color. So one line is red and the next is green and so on. The pro

相关标签:
12条回答
  • 2020-11-30 17:25

    I implemented this algorithm in a shorter way

    void ColorValue::SetColorValue( double r, double g, double b, ColorType myType )
    {
       this->c[0] = r;
       this->c[1] = g;
       this->c[2] = b;
    
       this->type = myType;
    }
    
    
    DistinctColorGenerator::DistinctColorGenerator()
    {
       mFactor = 255;
       mColorsGenerated = 0;
       mpColorCycle = new ColorValue[6];
       mpColorCycle[0].SetColorValue( 1.0, 0.0, 0.0, TYPE_RGB);
       mpColorCycle[1].SetColorValue( 0.0, 1.0, 0.0, TYPE_RGB);
       mpColorCycle[2].SetColorValue( 0.0, 0.0, 1.0, TYPE_RGB);
       mpColorCycle[3].SetColorValue( 1.0, 1.0, 0.0, TYPE_RGB);
       mpColorCycle[4].SetColorValue( 1.0, 0.0, 1.0, TYPE_RGB);
       mpColorCycle[5].SetColorValue( 0.0, 1.0, 1.0, TYPE_RGB);
    }
    
    //----------------------------------------------------------
    
    ColorValue DistinctColorGenerator::GenerateNewColor()
    {
       int innerCycleNr = mColorsGenerated % 6;
       int outerCycleNr = mColorsGenerated / 6;
       int cycleSize = pow( 2, (int)(log((double)(outerCycleNr)) / log( 2.0 ) ) );
       int insideCycleCounter = outerCycleNr % cyclesize;
    
       if ( outerCycleNr == 0)
       {
          mFactor = 255;
       }
       else
       {
          mFactor = ( 256 / ( 2 * cycleSize ) ) + ( insideCycleCounter * ( 256 / cycleSize ) );
       }
    
       ColorValue newColor = mpColorCycle[innerCycleNr] * mFactor;
    
       mColorsGenerated++;
       return newColor;
    }
    
    0 讨论(0)
  • 2020-11-30 17:26

    I have put up a page online for procedurally generating visually distinct colors:
    http://phrogz.net/css/distinct-colors.html

    Unlike other answers here that evenly step across RGB or HSV space (where there is a nonlinear relationship between the axis values and the perceptual differences), my page uses the standard CMI(I:c) color distance algorithm to prevent two colors from being too visually close.

    The final tab of the page allows you to sort the values in several ways, and then interleave them (ordered shuffle) so that you get very distinct colors placed next to one another.

    As of this writing, it only works well in Chrome and Safari, with a shim for Firefox; it uses HTML5 range input sliders in the interface, which IE9 and Firefox do not yet support natively.

    0 讨论(0)
  • 2020-11-30 17:33

    You could also think of the color space as all combinations of three numbers from 0 to 255, inclusive. That's the base-255 representation of a number between 0 and 255^3, forced to have three decimal places (add zeros on to the end if need be.)

    So to generate x number of colors, you'd calculate x evenly spaced percentages, 0 to 100. Get numbers by multiplying those percentages by 255^3, convert those numbers to base 255, and add zeros as previously mentioned.

    Base conversion algorithm, for reference (in pseudocode that's quite close to C#):

    int num = (number to convert);
    int baseConvert = (desired base, 255 in this case);
    (array of ints) nums = new (array of ints);
    int x = num;
    double digits = Math.Log(num, baseConvert); //or ln(num) / ln(baseConvert)
    int numDigits = (digits - Math.Ceiling(digits) == 0 ? (int)(digits + 1) : (int)Math.Ceiling(digits)); //go up one if it turns out even
    for (int i = 0; i < numDigits; i++)
    {
      int toAdd = ((int)Math.Floor(x / Math.Pow((double)convertBase, (double)(numDigits - i - 1))));
      //Formula for 0th digit: d = num / (convertBase^(numDigits - 1))
      //Then subtract (d * convertBase^(numDigits - 1)) from the num and continue
      nums.Add(toAdd);
      x -= toAdd * (int)Math.Pow((double)convertBase, (double)(numDigits - i - 1));
    }
    return nums;
    

    You might also have to do something to bring the range in a little bit, to avoid having white and black, if you want. Those numbers aren't actually a smooth color scale, but they'll generate separate colors if you don't have too many.

    This question has more on base conversion in .NET.

    0 讨论(0)
  • 2020-11-30 17:34

    You have three colour channels 0 to 255 R, G and B.

    First go through

    0, 0, 255
    0, 255, 0
    255, 0, 0
    

    Then go through

    0, 255, 255
    255, 0, 255
    255, 255, 0
    

    Then divide by 2 => 128 and start again:

    0, 0, 128
    0, 128, 0
    128, 0, 0
    0, 128, 128
    128, 0, 128
    128, 128, 0
    

    Divide by 2 => 64

    Next time add 64 to 128 => 192

    follow the pattern.

    Straightforward to program and gives you fairly distinct colours.

    EDIT: Request for code sample

    Also - adding in the additional pattern as below if gray is an acceptable colour:

    255, 255, 255
    128, 128, 128 
    

    There are a number of ways you can handle generating these in code.

    The Easy Way

    If you can guarantee that you will never need more than a fixed number of colours, just generate an array of colours following this pattern and use those:

        static string[] ColourValues = new string[] { 
            "FF0000", "00FF00", "0000FF", "FFFF00", "FF00FF", "00FFFF", "000000", 
            "800000", "008000", "000080", "808000", "800080", "008080", "808080", 
            "C00000", "00C000", "0000C0", "C0C000", "C000C0", "00C0C0", "C0C0C0", 
            "400000", "004000", "000040", "404000", "400040", "004040", "404040", 
            "200000", "002000", "000020", "202000", "200020", "002020", "202020", 
            "600000", "006000", "000060", "606000", "600060", "006060", "606060", 
            "A00000", "00A000", "0000A0", "A0A000", "A000A0", "00A0A0", "A0A0A0", 
            "E00000", "00E000", "0000E0", "E0E000", "E000E0", "00E0E0", "E0E0E0", 
        };
    

    The Hard Way

    If you don't know how many colours you are going to need, the code below will generate up to 896 colours using this pattern. (896 = 256 * 7 / 2) 256 is the colour space per channel, we have 7 patterns and we stop before we get to colours separated by only 1 colour value.

    I've probably made harder work of this code than I needed to. First, there is an intensity generator which starts at 255, then generates the values as per the pattern described above. The pattern generator just loops through the seven colour patterns.

    using System;
    
    class Program {
        static void Main(string[] args) {
            ColourGenerator generator = new ColourGenerator();
            for (int i = 0; i < 896; i++) {
                Console.WriteLine(string.Format("{0}: {1}", i, generator.NextColour()));
            }
        }
    }
    
    public class ColourGenerator {
    
        private int index = 0;
        private IntensityGenerator intensityGenerator = new IntensityGenerator();
    
        public string NextColour() {
            string colour = string.Format(PatternGenerator.NextPattern(index),
                intensityGenerator.NextIntensity(index));
            index++;
            return colour;
        }
    }
    
    public class PatternGenerator {
        public static string NextPattern(int index) {
            switch (index % 7) {
            case 0: return "{0}0000";
            case 1: return "00{0}00";
            case 2: return "0000{0}";
            case 3: return "{0}{0}00";
            case 4: return "{0}00{0}";
            case 5: return "00{0}{0}";
            case 6: return "{0}{0}{0}";
            default: throw new Exception("Math error");
            }
        }
    }
    
    public class IntensityGenerator {
        private IntensityValueWalker walker;
        private int current;
    
        public string NextIntensity(int index) {
            if (index == 0) {
                current = 255;
            }
            else if (index % 7 == 0) {
                if (walker == null) {
                    walker = new IntensityValueWalker();
                }
                else {
                    walker.MoveNext();
                }
                current = walker.Current.Value;
            }
            string currentText = current.ToString("X");
            if (currentText.Length == 1) currentText = "0" + currentText;
            return currentText;
        }
    }
    
    public class IntensityValue {
    
        private IntensityValue mChildA;
        private IntensityValue mChildB;
    
        public IntensityValue(IntensityValue parent, int value, int level) {
            if (level > 7) throw new Exception("There are no more colours left");
            Value = value;
            Parent = parent;
            Level = level;
        }
    
        public int Level { get; set; }
        public int Value { get; set; }
        public IntensityValue Parent { get; set; }
    
        public IntensityValue ChildA {
            get {
                return mChildA ?? (mChildA = new IntensityValue(this, this.Value - (1<<(7-Level)), Level+1));
            }
        }
    
        public IntensityValue ChildB {
            get {
                return mChildB ?? (mChildB = new IntensityValue(this, Value + (1<<(7-Level)), Level+1));
            }
        }
    }
    
    public class IntensityValueWalker {
    
        public IntensityValueWalker() {
            Current = new IntensityValue(null, 1<<7, 1);
        }
    
        public IntensityValue Current { get; set; }
    
        public void MoveNext() {
            if (Current.Parent == null) {
                Current = Current.ChildA;
            }
            else if (Current.Parent.ChildA == Current) {
                Current = Current.Parent.ChildB;
            }
            else {
                int levelsUp = 1;
                Current = Current.Parent;
                while (Current.Parent != null && Current == Current.Parent.ChildB) {
                    Current = Current.Parent;
                    levelsUp++;
                }
                if (Current.Parent != null) {
                    Current = Current.Parent.ChildB;
                }
                else {
                    levelsUp++;
                }
                for (int i = 0; i < levelsUp; i++) {
                    Current = Current.ChildA;
                }
    
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-30 17:41

    I think the HSV (or HSL) space has more opportunities here. If you don't mind the extra conversion, it's pretty easy to go through all the colors by just rotating the Hue value. If that's not enough, you can change the Saturation/Value/Lightness values and go through the rotation again. Or, you can always shift the Hue values or change your "stepping" angle and rotate more times.

    0 讨论(0)
  • 2020-11-30 17:41

    You could get a random set of your 3 255 values and check it against the last set of 3 values, making sure they are each at least X away from the old values before using them.

    OLD: 190, 120, 100

    NEW: 180, 200, 30

    If X = 20, then the new set would be regenerated again.

    0 讨论(0)
提交回复
热议问题