HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonMinor

Subclassing `int` to make colors

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
colorsintmakesubclassing

Problem

In the ROOT framework (originally a C++ framework, but python bindings are also supplied), colors are objects (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:

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 self


Or 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 self
class 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.