I use the symlog scale of matplotlib to cover a large ranges of parameters extending both into positive and negative direction. Unfortunately, the symlog-scale is not very intuitive and probably also not very commonly used. Therefore, I'd like to make the used scaling more obvious by placing minor ticks between the major ticks. On the log part of the scale, I want to place ticks at [2,3,…,9]*10^e where e
is the nearby major tick. Additionally, the range between 0 and 0.1 should be covered with evenly placed minor ticks, which would be 0.01 apart. I tried using the matplotlib.ticker API to arrive at such ticks using the following code:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import LogLocator, AutoLocator
x = np.linspace(-5, 5, 100)
y = x
plt.plot(x, y)
plt.yscale('symlog', linthreshy=1e-1)
yaxis = plt.gca().yaxis
yaxis.set_minor_locator(LogLocator(subs=np.arange(2, 10)))
plt.show()
Unfortunately, this does not produce what I want:
Note that there are way to many minor ticks around 0, which are probably due to the LogLocator
. Furthermore, there are no minor ticks on the negative axis.
No minor ticks appear if I use and AutoLocator
instead. The AutoMinorLocator
does only support evenly scaled axes. My question thus is how to achieve the desired tick placement?
Digging a little bit deeper into the issue, I noticed that it will be hard to come up with a general solution. Luckily, I can assume some constraints on my data and a custom made class was therefore enough to solve the problem:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import Locator
class MinorSymLogLocator(Locator):
"""
Dynamically find minor tick positions based on the positions of
major ticks for a symlog scaling.
"""
def __init__(self, linthresh):
"""
Ticks will be placed between the major ticks.
The placement is linear for x between -linthresh and linthresh,
otherwise its logarithmically
"""
self.linthresh = linthresh
def __call__(self):
'Return the locations of the ticks'
majorlocs = self.axis.get_majorticklocs()
# iterate through minor locs
minorlocs = []
# handle the lowest part
for i in xrange(1, len(majorlocs)):
majorstep = majorlocs[i] - majorlocs[i-1]
if abs(majorlocs[i-1] + majorstep/2) < self.linthresh:
ndivs = 10
else:
ndivs = 9
minorstep = majorstep / ndivs
locs = np.arange(majorlocs[i-1], majorlocs[i], minorstep)[1:]
minorlocs.extend(locs)
return self.raise_if_exceeds(np.array(minorlocs))
def tick_values(self, vmin, vmax):
raise NotImplementedError('Cannot get tick locations for a '
'%s type.' % type(self))
x = np.linspace(-5, 5, 100)
y = x
plt.plot(x, y)
plt.yscale('symlog', linthreshy=1e-1)
yaxis = plt.gca().yaxis
yaxis.set_minor_locator(MinorSymLogLocator(1e-1))
plt.show()
This produces
Note that this method only places ticks between major ticks. This will become noticeable if you zoom and pan into the image. Furthermore, the linear threshold has to be supplied to the class explicitly, since I found no way to easily and robustly read it off the axis itself.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With