How does ROOT color system works?

Hi rooters,
I am confused about how the color system works in ROOT.
I have read other posts (like TColor -> Color_t, or How to form a Color_t from a TColor) on the forum which didn’t help me to understand very much how ROOT manages to convert an RGB into a unique color index (also the documentation is not very verbose on this regard).

Intuitively, I would think that something like this should work

auto myColor = TColor(r,g,b); //stack allocation
myGraph->SetLineColor(myColor); //this does not work

however, it seems that all the SetXxxxColor() methods in ROOT do not accept a TColor object. Instead, they accept a short int (Color_t) which I would guess is the color index that ROOT decides to attribute to that specific RGB combination at some point during execution (or during compile time?). For example, 2 corresponds to red.

On the one hand, this is useful as it allows to have all the enum kColorName and color wheel scheme working and also this allows for the main colors to be represented by a color integer from 1 to 9 thereby making them easily accessible.

However, this color index strategy becomes very confusing when I want to add new RGB combinations to an already existing infrastructure of code. For example, if I am understanding correctly, the correct version of the previous lines of code should be something like

//notice the heap allocation
auto myColor = new TColor(r,g,b);  //ROOT will automatically add an index if the combination does not exist. I can force it to be as I want calling TColor(my_index,r,g,b)
Color_t color = myColor->GetNumber();
myGraph->SetLineColor(myColor);

however, I do not understand what ROOT does under the hood. What happens if I do something like

Color_t color = myColor->GetNumber();
delete myColor;
myGraph->SetLineColor(myColor); //seems that it just becomes grey-ish or white whatever the color combination

Or, more realistically, consider the following example:

int GetPantoneColorOfTheYear(int year){
        if (year<2000 or year>2023)
                throw std::out_of_range((const char*)Form("Pantone Color of the year not defined for year %d",year));
        std::map<int, TColor> pantoneMap = {
          {2000, TColor(155./255, 183./255, 212./255)},
          {2001, TColor(199./255, 67./255, 117./255)},
          {2002, TColor(191./255, 25./255, 50./255)},
          //...
          {2022, TColor(186./255, 38./255, 73./255)},
          {2023, TColor(102./255, 103./255, 171./255)},
        };
        return pantoneMap[year].GetNumber();
}

If I call

myGraph->SetLineColor(GetPantoneColorOfTheYear(2023))

I just get a white color because the TColor has been allocated on the stack and gets deleted after the function successfully returns the color number (color index?), which however is not meaningful anymore.

To make this work, I had to do a heap allocation of all the colors:

int GetPantoneColorOfTheYear(int year){
        if (year<2000 or year>2023)
                throw std::out_of_range((const char*)Form("Pantone Color of the year not defined for year %d",year));
        std::map<int, TColor*> pantoneMap = {
          {2000, new TColor(155./255, 183./255, 212./255)},
          {2001, new TColor(199./255, 67./255, 117./255)},
          {2002, new TColor(191./255, 25./255, 50./255)},
          //...
          {2022, new TColor(186./255, 38./255, 73./255)},
          {2023, new TColor(102./255, 103./255, 171./255)},
        };
        return pantoneMap[year]->GetNumber();
}

//in this way the following code works in main() and produces the correct color
myGraph->SetLineColor(GetPantoneColorOfTheYear(2023))

So I do not understand what ROOT does under the hood. Is there something like a global color indexing system that I can overwrite by creating new indices? And what happens if, during execution, I manipulate one color index in one part of my codebase, and then accidentally I call the same color index in another part? Will the actual color be modified?

//class1.cc
//in constructor
auto c = new TColor(2000,0.31,0.21,0.79)
//in a member function (myMethod())
myGraph->SetLineColor(2000);

//class2.cc
//in constructor
auto c = new TColor(2000,0.1,0.1,0.6) //accidentally use the same (global?) index

int main()
  class1 A; //constructor gets called (color is defined)
  class1 B; //constructor gets called (color is defined)
  A.myMethod(); //A calls ->SetLineColor(2000) //right behavior
  class2 Evil; //global color index gets redefined?
  B.myMethod() //B calls ->SetLineColor(2000) but now the index does not represent the same color anymore

And this leads me to two final questions:

  1. Is there a way to manipulate the default colors of ROOT on a global level for a particular root installation on my system? For example, I would like to add the mentioned GetPantoneColorOfTheYear() function available for all the users pointing to a particular install of ROOT on the machine, is there a way to make this function globally available?

  2. Is it possible to overwrite the default color root uses (i.e., the color that gets automatically chosen when I do NOT call any of the SetXxxxColor() methods) at a global level? For example, the default behavior for lines in TGraphs is kBlack. Can I change this to be kRed, or to be one particular custom RGB combination for a particular ROOT install on my system?


ROOT Version: any from 5.34 to 6.26/10
Platform: Mac ARM or Linux Intel
Compiler: GCC, Clang


Regarding the TColor management and the color indeces have a look at the TColor doc. The normal TColor ctor has the index as parameter. At the beginning of your post you are using the fast ctor which is explicitly stated to be use internally when creating a color palette.

Thank you very much for your answer.
Yes, I have already read the documentation and I am aware of what the fast ctor does. I am also aware that, to avoid overwriting existing color indexes (without using the fast ctor) the GetFreeColorIndex() method is available. However, from the docs, I am not sure to get how the global nature of this color management system works, and this is especially painful in large codebases. Moreover, I still do not understand how to add global functions for a specific root install or overwrite the default color index definitions on a global level. I am also not sure whether the predefined default color indices are determined at compile time or run time (or maybe Cling is involved so none of these two options is completely true?).

Anyways, this is not essential for the proper analysis workflow, so no problem if it is not worth the time to go into the details.

Thank you anyways for your support :slight_smile:

May be this macro can help you to undesrtand which colors à predefined and what are the free slots in the list of colors:

/// List the defined colors.

void ListColors(int ci1=0, int ci2 = 0) {

   TObjArray *colors = (TObjArray*) gROOT->GetListOfColors();
   Int_t ncolors = colors->GetSize();
   if (ci2==0) ci2 = ncolors;
   TColor *color = 0;
   Int_t nc =0 ;

   printf("   +------+-------+-------+-------+-------+--------------------+--------------------+\n");
   printf("   | Idx  | Red   | Green | Blue  | Alpha |     Color Name     |    Color Title     |\n");
   printf("   +------+-------+-------+-------+-------+--------------------+--------------------+\n");

   for (Int_t i = ci1; i<ci2; i++) {
      color = (TColor*)colors->At(i);
      if (color) {
         printf("   | %4d | %5.3f | %5.3f | %5.3f | %5.3f | %18s | %18s |\n", i,
                                                           color->GetRed(),
                                                           color->GetGreen(),
                                                           color->GetBlue(),
                                                           color->GetAlpha(),
                                                           color->GetName(),
                                                           color->GetTitle());
         nc++;
      } else {
         printf("   | %4d |   ?   |   ?   |   ?   |   ?   |                  ? |                  ? |\n",i);
      }
   }
   printf("   +------+-------+-------+-------+-------+--------------------+--------------------+\n");
   printf("   | Number of possible colors = %4d                                               |\n",ncolors);
   printf("   | Number of defined colors between %4d and %4d = %4d                          |\n",ci1,ci2,nc);
   printf("   | Number of free indeces between %4d and %4d   = %4d                          |\n",ci1,ci2,ci2-ci1-nc);
   printf("   +--------------------------------------------------------------------------------+\n\n");

}

Yes this is very useful, thanks a lot.
So I have tried to create a color and understood that the gROOT fColors (TROOT::fColors attribute) gets updated. This happens both with a heap or stack allocation. However, if for some reason the color is stack allocated and it goes out of scope, or if I do a heap allocation and then delete the new color, it gets removed from the gROOT fColors TSeqCollection.

So, is there a way to change the default gROOT behavior on a local install to make new custom colors globally available for all the users and macros pointing to that particular install?

No, the list of colours is unique.

You can “ensure” that people get the same settings;