MoAly98 / Pythium

ROOT Killa'
2 stars 5 forks source link

Plotting class structure #18

Open FlavioRyu opened 2 years ago

FlavioRyu commented 2 years ago

I was just brainstorming about the use case of our classes before even starting to have a skeleton and I came up with this:

"""
the desired use case:

                        ┌--> this is a boost-histogram object for now
                        |
histo1 = Hist1D(histogram-object, title, fill=True, color)
histo1.make_grid()
histo1.axis.set_xaxis(hide=True)
histo1.axis.set_yaxis(axis-name)
histo1.title_loc(where)
histo1.display() -> this will draw the plot using mplhep

histo2 = PullPlotHist(histogram-object, title, color1, color2) -> the pull plot will be a sideways histogram
histo1.make_grid()
histo2.line.set_linewidth(number)
histo2.marker.set_markersize(number)
histo2.title_loc(where)
histo2.display()

histo3 = CorrMatrix(histogram-object, title, color_palette)
histo3.set_fontsize(number) -> size of numbers inside the corr matrix
histo3.line.set_style('--') -> style of lines that separate each element in the matrix
histo3.display()
"""

This a brief example of some functionalities that I thought might be useful to have for some histogram types. These classes will all inherit from a more general BasicHisto (which contains methods such as make_grid() or display() and attributes like line or axis) class which will take in the histogram object (which is a boost-hist for now) and extrapolate and store all relevant information about it. The idea is to create a subclass for each histogram type.

It is a very basic scratch so let me know if there is anything that I got wrong or that should be changed about the structure in general.

Edit: this does not make use of any canvas or pad structures.

MoAly98 commented 2 years ago

Can you update this issue @FlavioRyu

FlavioRyu commented 2 years ago

Yes, I've changed a lot since I posted this original issue so I will try to give a summary (otherwise this would become a documentation) of what is the current situation.

There are 5 classes: EmptyPlot, PythRatio, PythCMatrix, PythPull and PythProjection, where the first is the mother class of the others (can be considered a virtual class). Each individual plot classes store specific rcParams values in form of a dictionary; the user can pass in the constructor (under the argument rcp_kw) a dictionary containing rcParams keys and this will be used to internally update the plot default rcParams.

The most important function is the display() function which is present in all child classes. This effectively draws the entire plot by calling plotting functions such as matpltolib.errorbar(), matpltolib.imshow() and mplhep.histplot() (where, btw, the last one is a helper for matpltolib.stairs() and matpltolib.errorbar()).

EmptyPlot

Class storing basic attributes such as figure size, master title (or figure title), positioning of subplots present in the figure, etc. Member functioins are functions that are common to all other plot classes such as create_canvas(), make_subplot(), set_color(), save_image() etc. to name a few.

Titling system

The base class also stores two dictionaries (called xtitles_dict and xtitles_dict) to store x and y titles (or labels) of any subplot that might be present in the plot. These titles are set by the user calling the function axes_labels(). Below is an explanation of these two dictionaries (taken from source code)

        # set default dict of axes titles
        self.xtitles_dict = {
            "xmain" : '',
            "xtop"  : '',
            "xbot"  : '',
            "xleft" : '', # not used
            "xright": ''
        }
        self.ytitles_dict = {
            "ymain" : '',
            "ytop"  : '',
            "ybot"  : '',
            "yleft" : '', # not used
            "yright": ''
        }
        """
        The terms 'main', 'top', 'bot' etc. refer to the follownig subplot scheme:

                 -------- 
                |  top   |
                 --------        
         -----   --------   -----
        |     | |        | |     |
        |left | |  main  | |right|
        |     | |        | |     |
         -----   --------   -----
                 -------- 
                |  bot   |
                 --------

        And each plot type (ratio, pull, projection, corrm) will be a combination of
        these subplots (Ax objects). For example, the ratio plot will be made of main
        and bot subplots and the projection plot will be made of main, top and right
        subplots. The left subplot is currently not used and might be deleted in the 
        future if no use cases are found.

        The space between the subplots is called 'spacing' throughout all classes
        """

Font sizes

As matplotlib makes a lot of confusion in the rcParams that control font sizes of each element in the plot, I decided to standardise them in the following way (example from pull plot):

PythRatio

Use case example:

my_dict={
    'axes.titlesize': 20,
    'font.size': 11
}

obj = PythRatio(hist_list, size=(6,8), title='Master Title', spacing=0, rcp_kw=my_dict)
obj.set_stack_color(reverse=True, colormap='gist_rainbow')
obj.axes_labels(15, xbot=xaxis_label, ymain='Events', ybot='Data/Pred.')
obj.set_bot_yaxis([0.5, 1.5], 0.25, edges=False) # strongly recommend user to do this
obj.plot_errors(hatch='/////', alpha=0.2)
obj.display()

set_bot_yaxis is recommended because if user doesn't call this, the y axis of the ratio plot (the bottom subplot) will be drawn automatically by matplotlib but this might not always be the desired output. For instance, axis tick labels might overlap to other parts of the plot but this can be avoided by specifying a custom y axis range and the edges boolean.

Output of example:

PythCMatrix

Use case example:

obj = PythCMatrix(data, 0.001, title='Master Title')
obj.set_color(colormap='bwr')
obj.display(set_cbar=False)

In the constructor, the float after the data is the threshold value for which the data will be cut. In the display() function, it is possible to specify whether a colorbar on the side is wanted or not. The figure size changed automatically: figure side length (in inches) is len(data)/3.

Output of example:

PythPull

Use case example:

my_dict={
    'axes.titlesize': 15
}

hist1 = PythPullPlot(data0.iloc[0:40], title='Master Title', rcp_kw=my_dict)
hist1.set_rangex([-6, 11])
hist1.axes_labels(12, xmain=r'$(\hat{\theta}-\theta)/\Delta\theta$')
hist1.display()

set_rangex() can be called by user to impose a custom x range for the plot. The figure size of the pull plot changes according to the length of nuisance parameters (n) to be plotted: figsize = (2, 0.4012*n**(0.763)) (this formula was empirically derived).

Output of example:

PythProjection

Use case example:

my_dict={
    'axes.titlesize': 20,
    'xaxis.labellocation': 'right',
    'yaxis.labellocation': 'top',
}

plot = PythProjection(size=(6,6), title='Master Title', spacing=0.08, rcp_kw=my_dict)
plot.fill(hist)
plot.axes_labels(15, ymain=r'$p_y$ [MeV]', xmain=r'$p_x$ [MeV]', xright='righttitle', ytop='toptitle')
plot.put_grid()
plot.set_color(colormap='binary')
plot.display()

Output of example:

MoAly98 commented 2 years ago

@jkwinter Can you review this issue and give your updates on what changed/what needs to change?

MoAly98 commented 2 years ago

How will we provide plotting classes to users? TODO:

jkwinter commented 2 years ago

In terms of structure, Flavio has documented this in the plotting readme. In terms of functionality

Open points:

jkwinter commented 2 years ago

Plotting to-do list General

Hist1D

RatioPlot

PullPlot

ProjectionPlot

CMatrixPlot