patternpythonMinor
Subclassing `int` to make colors
Viewed 0 times
colorsintmakesubclassing
Problem
In the ROOT framework (originally a C++ framework, but python bindings are also supplied), colors are objects (
This short class creates a new such color, given the
Usage example:
I'm looking for comments on the class itself as well as recommendations regarding making the initialization of all the colors easier.
Further explanation:
The need for this class arose, because ROOT cannot convert a
Otherwise it raises errors such as:
```
...
File "test.py", line 43, in plot_2D_stacked
h.SetLineColor(color)
TypeError: void TAttLine::SetLineColor(short lcolor) =>
could not convert argument 1 (short int converi
ROOT.TColor) identified with an integer index.This short class creates a new such color, given the
r, g, b values which stores the object inside an int sub-type (to prevent the gc from deleting it).#colors.py
import ROOT
class Color(int):
"""Create a new ROOT.TColor object with an associated index"""
__slots__ = ["object"]
def __new__(cls, r, g, b):
"""Constructor. r, g, b are in [0, 1]"""
self = int.__new__(cls, ROOT.TColor.GetFreeColorIndex())
cls.set_rgb(self, r, g, b)
return self
def set_rgb(self, r, g, b):
self.object = ROOT.TColor(self, r, g, b)
black = Color(0, 0, 0)
orange = Color(0.9, 0.6, 0)
sky_blue = Color(0.35, 0.7, 0.9)
bluish_green = Color(0, 0.6, 0.5)
yellow = Color(0.95, 0.9, 0.25)
blue = Color(0, 0.45, 0.7)
vermillion = Color(0.8, 0.4, 0)
reddish_purple = Color(0.8, 0.6, 0.7)
colors = [black, orange, sky_blue, bluish_green,
yellow, blue, vermillion, reddish_purple]Usage example:
#draw_histograms.py
from colors import colors
import ROOT
names = "hist 1", "hist 2", "hist 3"
histograms = []
for name, color in zip(names, colors):
hist = ROOT.TH1D(name, name, 100, -50, 50)
hist.FillRandom("gaus", 1000)
hist.SetLineColor(color)
histograms.append(hist)
histograms[1].Draw()
...I'm looking for comments on the class itself as well as recommendations regarding making the initialization of all the colors easier.
Further explanation:
The need for this class arose, because ROOT cannot convert a
ROOT.TColor object directly to an int. It needs the global color index in functions such as hist.SetLineColor(1).Otherwise it raises errors such as:
```
...
File "test.py", line 43, in plot_2D_stacked
h.SetLineColor(color)
TypeError: void TAttLine::SetLineColor(short lcolor) =>
could not convert argument 1 (short int converi
Solution
As suggested by @JoeWallis in the comments, you could use the "fast" TColor constructor. This let you simplify the color creation by not having to manually deal with the color number:
Or you could use plain
If
Depending on the need, it may also be a good idea to define properties to get/set color components, such as:
Change
Now, as regard to style, you define a bunch of constants whose names should be UPPER_SNAKE_CASE. And I would probably use
import ROOT
class Color(int):
"""Create a new ROOT.TColor object with an associated index"""
__slots__ = ["object"]
def __new__(cls, r, g, b, a=1.0):
"""Constructor. r, g, b and a are in [0, 1]"""
# Explicitly use floats here to disambiguate
# between the two TColor constructors
color = ROOT.TColor(float(r), float(g), float(b), float(a))
self = int.__new__(cls, color.GetNumber())
self.object = color
return selfOr you could use plain
ROOT.TColor and use color.GetNumber() whenever you need an int instead of a TColor. An other, untested (and probably not working), would be to define an __int__ method:class Color(ROOT.TColor):
def __int__(self):
return self.GetNumber()If
SetLineColor and others accept that, this would be the best alternative.Depending on the need, it may also be a good idea to define properties to get/set color components, such as:
@property
def red(self):
return self.GetRed()
@red.setter
def red(self, red_value):
self.SetRGB(red_value, self.green, self.blue)Change
self to self.object if you use the int subclass.Now, as regard to style, you define a bunch of constants whose names should be UPPER_SNAKE_CASE. And I would probably use
red, green, blue and alpha instead of one letter variable names, even though in "color" context, these letters are pretty obvious.Code Snippets
import ROOT
class Color(int):
"""Create a new ROOT.TColor object with an associated index"""
__slots__ = ["object"]
def __new__(cls, r, g, b, a=1.0):
"""Constructor. r, g, b and a are in [0, 1]"""
# Explicitly use floats here to disambiguate
# between the two TColor constructors
color = ROOT.TColor(float(r), float(g), float(b), float(a))
self = int.__new__(cls, color.GetNumber())
self.object = color
return selfclass Color(ROOT.TColor):
def __int__(self):
return self.GetNumber()@property
def red(self):
return self.GetRed()
@red.setter
def red(self, red_value):
self.SetRGB(red_value, self.green, self.blue)Context
StackExchange Code Review Q#145850, answer score: 2
Revisions (0)
No revisions yet.