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:
-
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? -
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