Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to add third level of ticks in python matplotlib

Matplotlib axes have Major and Minor ticks. How do I add a third level of tick below Minor?

For example

import matplotlib.pyplot as plt
import numpy as np
import matplotlib.ticker

t = np.arange(0.0, 100.0, 0.1)
s = np.sin(0.1*np.pi*t)*np.exp(-t*0.01)

fig, ax = plt.subplots()
plt.plot(t, s)

ax1 = ax.twiny()
ax1.plot(t, s)

ax1.xaxis.set_ticks_position('bottom')

majors = np.linspace(0, 100, 6)
minors = np.linspace(0, 100, 11)
thirds = np.linspace(0, 100, 101)

ax.xaxis.set_major_locator(matplotlib.ticker.FixedLocator(majors))
ax.xaxis.set_minor_locator(matplotlib.ticker.FixedLocator(minors))
ax1.xaxis.set_major_locator(matplotlib.ticker.FixedLocator([]))
ax1.xaxis.set_minor_locator(matplotlib.ticker.FixedLocator(thirds))
ax1.tick_params(which='minor', length=2)
ax.tick_params(which='minor', length=4)
ax.tick_params(which='major', length=6)
ax.grid(which='both',axis='x',linestyle='--')

plt.axhline(color='gray')

plt.show()

produces the effect I want using twinned x-axes.

Third level ticks

Is there a better way?

like image 719
pheon Avatar asked Jul 30 '17 21:07

pheon


People also ask

How to set axis ticks in multiples of Pi in Python Matplotlib?

How to set axis ticks in multiples of pi in Python Matplotlib? Initialize a pi variable, create theta and y data points using numpy. Plot theta and y using plot () method.

What are Matplotlib’s default ticks?

It can be a number or a string. Whenever we plot a graph, the axes adjust and take the default ticks. Matplotlib’s default ticks are generally sufficient in common situations but are in no way optimal for every plot. Here, we will see how to customize these ticks as per our need.

How do I Turn on minor tick labels in Python?

Minor tick labels can be turned on by setting the minor formatter. MultipleLocator places ticks on multiples of some base. StrMethodFormatter uses a format string (e.g., ' {x:d}' or ' {x:1.2f}' or ' {x:1.1f} cm') to format the tick labels (the variable in the format string must be 'x' ).

How to set the location of the tick marks from sequence ticks?

The Axis.set_ticks () function in axis module of matplotlib library is used to set the locations of the tick marks from sequence ticks. Parameters: This method accepts the following parameters.


1 Answers

As I stated that you can achieve what you want by deriving from some key classes, I decided to do so (but as I said, it's probably not worth the effort). Anyway, here is what I've got:

from matplotlib import pyplot as plt
from matplotlib import axes as maxes
from matplotlib import axis as maxis
import matplotlib.ticker as mticker
import matplotlib.cbook as cbook
from matplotlib.projections import register_projection


from matplotlib import ticker
import numpy as np

class SubMinorXAxis(maxis.XAxis):
    def __init__(self,*args,**kwargs):
        self.subminor = maxis.Ticker()
        self.subminorTicks = []
        self._subminor_tick_kw = dict()

        super(SubMinorXAxis,self).__init__(*args,**kwargs)


    def reset_ticks(self):
        cbook.popall(self.subminorTicks)
        ##self.subminorTicks.extend([self._get_tick(major=False)])
        self.subminorTicks.extend([maxis.XTick(self.axes, 0, '', major=False, **self._subminor_tick_kw)])
        self._lastNumSubminorTicks = 1
        super(SubMinorXAxis,self).reset_ticks()


    def set_subminor_locator(self, locator):
        """
        Set the locator of the subminor ticker

        ACCEPTS: a :class:`~matplotlib.ticker.Locator` instance
        """
        self.isDefault_minloc = False
        self.subminor.locator = locator
        locator.set_axis(self)
        self.stale = True


    def set_subminor_formatter(self, formatter):
        """
        Set the formatter of the subminor ticker

        ACCEPTS: A :class:`~matplotlib.ticker.Formatter` instance
        """
        self.isDefault_minfmt = False
        self.subminor.formatter = formatter
        formatter.set_axis(self)
        self.stale = True


    def get_subminor_ticks(self, numticks=None):
        'get the subminor tick instances; grow as necessary'
        if numticks is None:
            numticks = len(self.get_subminor_locator()())

        if len(self.subminorTicks) < numticks:
            # update the new tick label properties from the old
            for i in range(numticks - len(self.subminorTicks)):
                ##tick = self._get_tick(major=False)
                tick = maxis.XTick(self.axes, 0, '', major=False, **self._subminor_tick_kw)
                self.subminorTicks.append(tick)

        if self._lastNumSubminorTicks < numticks:
            protoTick = self.subminorTicks[0]
            for i in range(self._lastNumSubminorTicks, len(self.subminorTicks)):
                tick = self.subminorTicks[i]
                tick.gridOn = False
                self._copy_tick_props(protoTick, tick)

        self._lastNumSubminorTicks = numticks
        ticks = self.subminorTicks[:numticks]

        return ticks

    def set_tick_params(self, which='major', reset=False, **kwargs):
        if which == 'subminor':
            kwtrans = self._translate_tick_kw(kwargs, to_init_kw=True)
            if reset:
                self.reset_ticks()
                self._subminor_tick_kw.clear()
            self._subminor_tick_kw.update(kwtrans)

            for tick in self.subminorTicks:
                tick._apply_params(**self._subminor_tick_kw)
        else:
            super(SubMinorXAxis, self).set_tick_params(which=which, reset=reset, **kwargs)

    def cla(self):
        'clear the current axis'
        self.set_subminor_locator(mticker.NullLocator())
        self.set_subminor_formatter(mticker.NullFormatter())

        super(SubMinorXAxis,self).cla()


    def iter_ticks(self):
        """
        Iterate through all of the major and minor ticks.
        ...and through the subminors
        """
        majorLocs = self.major.locator()
        majorTicks = self.get_major_ticks(len(majorLocs))
        self.major.formatter.set_locs(majorLocs)
        majorLabels = [self.major.formatter(val, i)
                       for i, val in enumerate(majorLocs)]

        minorLocs = self.minor.locator()
        minorTicks = self.get_minor_ticks(len(minorLocs))
        self.minor.formatter.set_locs(minorLocs)
        minorLabels = [self.minor.formatter(val, i)
                       for i, val in enumerate(minorLocs)]

        subminorLocs = self.subminor.locator()
        subminorTicks = self.get_subminor_ticks(len(subminorLocs))
        self.subminor.formatter.set_locs(subminorLocs)
        subminorLabels = [self.subminor.formatter(val, i)
                       for i, val in enumerate(subminorLocs)]

        major_minor = [
            (majorTicks, majorLocs, majorLabels),
            (minorTicks, minorLocs, minorLabels),
            (subminorTicks, subminorLocs, subminorLabels),
        ]

        for group in major_minor:
            for tick in zip(*group):
                yield tick


class SubMinorAxes(maxes.Axes):
    name = 'subminor'

    def _init_axis(self):
        self.xaxis = SubMinorXAxis(self)
        self.spines['top'].register_axis(self.xaxis)
        self.spines['bottom'].register_axis(self.xaxis)
        self.yaxis = maxis.YAxis(self)
        self.spines['left'].register_axis(self.yaxis)
        self.spines['right'].register_axis(self.yaxis)

register_projection(SubMinorAxes)




if __name__ == '__main__':
    fig = plt.figure()
    ax = fig.add_subplot(111,projection = 'subminor')

    t = np.arange(0.0, 100.0, 0.1)
    s = np.sin(0.1*np.pi*t)*np.exp(-t*0.01)

    majors = np.linspace(0, 100, 6)
    minors = np.linspace(0, 100, 11)
    thirds = np.linspace(0, 100, 101)

    ax.plot(t, s)

    ax.xaxis.set_ticks_position('bottom')

    ax.xaxis.set_major_locator(ticker.FixedLocator(majors))
    ax.xaxis.set_minor_locator(ticker.FixedLocator(minors))
    ax.xaxis.set_subminor_locator(ticker.FixedLocator(thirds))

    ##some things in set_tick_params are not being set correctly
    ##by default. For instance 'top=False' must be stated
    ##explicitly
    ax.tick_params(which='subminor', length=2, top=False)
    ax.tick_params(which='minor', length=4)
    ax.tick_params(which='major', length=6)
    ax.grid(which='both',axis='x',linestyle='--')

    plt.show()

It's not perfect, but for the use case you provided it's working fine. I drew some ideas from this matplotlib example and by going through the source codes directly. The result looks like this:

sub-minor ticks on x-axis

I tested the code on both Python 2.7 and Python 3.5.

EDIT:

I noticed that the subminor gridlines would always be drawn if the grid is turned on (while I had intended for it not to be drawn at all). I rectified this in the code above, i.e. the subminor ticks should never produce grid lines. If gridlines should be implemented properly, some more work will be needed.

like image 124
Thomas Kühn Avatar answered Oct 09 '22 04:10

Thomas Kühn