Description
Problem
There is no direct, user-friendly method to add custom/special ticks. Existing methods can lead to surprising results and I have found little official guidance in the documentation that would account for this.
From what I have seen in the examples gallery and on the Internet, most solutions rely on Axes.set_xticks
or Axes.set_yticks
. These methods can fail silently (and very frustratingly) when attempting to work with e.g. a semilog plot.
Example of what would be expected behaviour with user set ticks (using ordinary plot
):
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
# Constants
L = 6
D = 1
gamma = 1
m_0 = 1
Delta_m = 0.05
q = gamma / D
r = np.sqrt(q)
Samples = 601
# Functions that should work with numbers and ndarrays just as well
def dervLin(xs):
return xs * 0 -m_0 / L # Trick to make this ndarray-compatible (`xs * 0 +` forces array broadcasting, but doesn't change the value)
def dervExp(xs):
return -m_0 * r * np.exp(-r * xs)
def dervPow(xs):
return -12 * D / gamma / (xs + np.sqrt((6 * D / gamma) / m_0))**3
xs = np.linspace(0, L, Samples)
ys1 = Delta_m / np.abs(dervLin(xs))
ys2 = Delta_m / np.abs(dervExp(xs))
ys3 = Delta_m / np.abs(dervPow(xs))
fig, ax = plt.subplots(1, 1)
ax.set_xlim(0, L)
ax.plot(xs, ys1, xs, ys2, xs, ys3)
ax.hlines([ys1[-1], ys2[-1], ys3[-1]], 0, L, linewidth=0.5, linestyle="dashed", color="grey")
ax.set_yticks([ys1[-1], ys2[-1], ys3[-1]])
Now, trying the same with Axes.semilogy
instead of Axes.plot
:
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
# Constants
L = 6
D = 1
gamma = 1
m_0 = 1
Delta_m = 0.05
q = gamma / D
r = np.sqrt(q)
Samples = 601
# Functions that should work with numbers and ndarrays just as well
def dervLin(xs):
return xs * 0 -m_0 / L # Trick to make this ndarray-compatible (`xs * 0 +` forces array broadcasting, but doesn't change the value)
def dervExp(xs):
return -m_0 * r * np.exp(-r * xs)
def dervPow(xs):
return -12 * D / gamma / (xs + np.sqrt((6 * D / gamma) / m_0))**3
xs = np.linspace(0, L, Samples)
ys1 = Delta_m / np.abs(dervLin(xs))
ys2 = Delta_m / np.abs(dervExp(xs))
ys3 = Delta_m / np.abs(dervPow(xs))
fig, ax = plt.subplots(1, 1)
ax.set_xlim(0, L)
ax.semilogy(xs, ys1, xs, ys2, xs, ys3)
ax.hlines([ys1[-1], ys2[-1], ys3[-1]], 0, L, linewidth=0.5, linestyle="dashed", color="grey")
ax.set_yticks([ys1[-1], ys2[-1], ys3[-1]])
silently fails to produce labelled ticks:
I have deduced that the problem lies with LogFormatterSciNotation
, which doesn't produce a tick label when presented with user-provided values.
fmt = ax.yaxis.get_major_formatter()
>>> fmt
<matplotlib.ticker.LogFormatterSciNotation at 0x1d7a95a1940>
>>> fmt(0.3)
''
>>> fmt(10)
'$\\mathdefault{10^{1}}$'
My workaround includes subclassing and substituting the formatter:
class SpecialLogFmt(mpl.ticker.LogFormatterSciNotation):
def __call__(self, x, pos):
s = super().__call__(x, pos)
if len(s) == 0:
s = "%.2f" % x
return s
sfmt = SpecialLogFmt()
ax.yaxis.set_major_formatter(sfmt)
Given these complications that necessitate a not completely trivial workaround, I believe it would be helpful to introduce explicit support for user-provided special ticks (in parallel to automatically-generated ticks). Other software e.g. OriginLab/OriginPro offers such a feature out of the box.
Proposed solution
At the very least, amend documentation of Axes.set_xticks
/Axes.set_yticks
to state that the results also depend on the Formatter
of the axis, which may ignore the ticks which were added manually. Current documentation claiming that If not set, the labels show the data value.
is misleading when this isn't mentioned.
Add a gallery example that shows how to use special ticks, with more emphasis on such edge cases as can occur with e.g. log plots.
If possible, create new API methods that would explicitly support manually-added special ticks, so that the user does not have to worry about making the axis formatter work with the custom ticks. This API could be connected with the hlines
/vlines
method to give horizontal/vertical lines ending in a special tick, which may be very helpful in some contexts.