Description
Bug summary
In matplotlib (tested with version 3.2.2), the first and last minor ticks are sometimes not plotted on the x- or y-axis although they should. I have also created a related StackOverflow post under https://stackoverflow.com/q/70861773/5269892.
Code for reproduction
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import AutoMinorLocator
plt.ion()
fig = plt.figure()
ax = plt.gca()
ax.set_xlim(-1.9,1.9)
# ax.minorticks_on(), for linear axis scales, internally calls set_minor_locator(AutoMinorLocator()),
# for both the x- and y-axis
# ax.minorticks_on()
ax.xaxis.set_minor_locator(AutoMinorLocator())
print('minor xtick locs for x-axis limits -1.9 to 1.9):', ax.xaxis.get_ticklocs(minor=True))
fig.savefig('/home/proxauf/Documents/so_minor_ticks_first_last_1.png')
ax.set_xlim(-5,5)
print('minor xtick locs for x-axis limits -5 to 5):', ax.xaxis.get_ticklocs(minor=True))
fig.savefig('/home/proxauf/Documents/so_minor_ticks_first_last_2.png')
ax.set_xlim(-1.9,1.9)
plt.close()
def print_auto_minor_explicit(ax):
# a large part of this code is copied the AutoMinorLocator class for matplotlib 3.5.1 (the latest version), taken from https://github.com/matplotlib/matplotlib/blob/v3.5.1/lib/matplotlib/ticker.py#L2754-L2812
majorlocs = ax.xaxis.get_majorticklocs()
majorstep = majorlocs[1] - majorlocs[0]
majorstep_no_exponent = 10 ** (np.log10(majorstep) % 1)
if np.isclose(majorstep_no_exponent, [1.0, 2.5, 5.0, 10.0]).any():
ndivs = 5
else:
ndivs = 4
minorstep = majorstep / ndivs
vmin, vmax = ax.xaxis.get_view_interval()
if vmin > vmax:
vmin, vmax = vmax, vmin
t0 = majorlocs[0]
tmin = ((vmin - t0) // minorstep + 1) * minorstep
tmax = ((vmax - t0) // minorstep + 1) * minorstep
locs = np.arange(tmin, tmax, minorstep) + t0
kw_minorticks = {}
for key, val in [('majorlocs', majorlocs), ('majorstep', majorstep), ('majorstep_no_exponent', majorstep_no_exponent),
('ndivs', ndivs), ('minorstep', minorstep), ('vmin',vmin), ('vmax', vmax),
('t0', t0), ('tmin', tmin), ('tmax', tmax), ('locs', locs)]:
kw_minorticks[key] = val
print('%s:' % key, val)
print('-------------------------')
tmin_temp = (vmin - t0) // minorstep
tmax_temp = (vmax - t0) // minorstep
print('tmin_temp:', tmin_temp)
print('tmax_temp:', tmax_temp)
print('vmax - t0:', (vmax - t0))
print('(vmax - t0) // minorstep:', (vmax - t0) // minorstep)
print('(vmax - t0) / minorstep:', (vmax - t0) / minorstep)
print('int((vmax - t0) / minorstep):', int((vmax - t0) / minorstep))
print('-------------------------')
tmin_mod = round((vmin - t0) / minorstep)
tmax_mod = round((vmax - t0) / minorstep) + 1
locs_mod = (np.arange(tmin_mod, tmax_mod) * minorstep) + t0
print('tmin_mod:', tmin_mod)
print('tmax_mod:', tmax_mod)
print('locs_mod:', locs_mod)
return kw_minorticks
print('\n-------------------------\n')
kw_minorticks = print_auto_minor_explicit(ax)
# Issues (for example x-axis limits -1.9 to 1.9):
#
# (1): tmin_temp is 1.0 (fine), but tmin should be 0.1 instead of 0.2;
# I guess t_min should be ((vmin - t0) // minorstep) * minorstep (so no +1 inside brackets);
#
# (2): tmax_temp is 38.0, but should be 39.0;
# this is because (vmax - t0) is 3.9 (fine), but (vmax - t0) // minorstep is 38.0 instead of 39.0;
# it looks like there are floating point issues;
# should be avoided using round() or np.around() on (vmax - t0) // minorstep;
#
# Suggested changes in computation:
#
# tmin_mod = round((vmin - t0) / minorstep)
# tmax_mod = round((vmax - t0) / minorstep) + 1
# locs_mod = (np.arange(tmin_mod, tmax_mod) * minorstep) + t0
Actual outcome
minor xtick locs for x-axis limits -1.9 to 1.9): [-1.8, -1.7, -1.5999999999999999, -1.4, -1.2999999999999998, -1.1999999999999997, -1.0999999999999999, -0.8999999999999997, -0.7999999999999996, -0.6999999999999997, -0.5999999999999996, -0.3999999999999997, -0.2999999999999996, -0.1999999999999995, -0.09999999999999942, 0.10000000000000053, 0.20000000000000107, 0.3000000000000007, 0.4000000000000008, 0.600000000000001, 0.7000000000000011, 0.8000000000000012, 0.9000000000000012, 1.100000000000001, 1.200000000000001, 1.3000000000000012, 1.4000000000000012, 1.6000000000000014, 1.7000000000000015, 1.8000000000000016]
minor xtick locs for x-axis limits -5 to 5): [-4.5, -3.5, -3.0, -2.5, -1.5, -1.0, -0.5, 0.5, 1.0, 1.5, 2.5, 3.0, 3.5, 4.5, 5.0]
-------------------------
majorlocs: [-2. -1.5 -1. -0.5 0. 0.5 1. 1.5 2. ]
majorstep: 0.5
majorstep_no_exponent: 4.999999999999999
ndivs: 5
minorstep: 0.1
vmin: -1.9
vmax: 1.9
t0: -2.0
tmin: 0.2
tmax: 3.9000000000000004
locs: [-1.8000000e+00 -1.7000000e+00 -1.6000000e+00 -1.5000000e+00
-1.4000000e+00 -1.3000000e+00 -1.2000000e+00 -1.1000000e+00
-1.0000000e+00 -9.0000000e-01 -8.0000000e-01 -7.0000000e-01
-6.0000000e-01 -5.0000000e-01 -4.0000000e-01 -3.0000000e-01
-2.0000000e-01 -1.0000000e-01 8.8817842e-16 1.0000000e-01
2.0000000e-01 3.0000000e-01 4.0000000e-01 5.0000000e-01
6.0000000e-01 7.0000000e-01 8.0000000e-01 9.0000000e-01
1.0000000e+00 1.1000000e+00 1.2000000e+00 1.3000000e+00
1.4000000e+00 1.5000000e+00 1.6000000e+00 1.7000000e+00
1.8000000e+00]
-------------------------
tmin_temp: 1.0
tmax_temp: 38.0
vmax - t0: 3.9
(vmax - t0) // minorstep: 38.0
(vmax - t0) / minorstep: 39.0
int((vmax - t0) / minorstep): 39
-------------------------
tmin_mod: 1
tmax_mod: 40
locs_mod: [-1.9 -1.8 -1.7 -1.6 -1.5 -1.4 -1.3 -1.2 -1.1 -1. -0.9 -0.8 -0.7 -0.6
-0.5 -0.4 -0.3 -0.2 -0.1 0. 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8
0.9 1. 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9]
Expected outcome
In the above minimum example, the tick locations should include -1.9 and 1.9 (for x-axis limits -1.9 to 1.9) and -5.0 (for x-axis limits -5 to 5).
Additional information
The first minor tick is sometimes not included due to an incorrect +1 being added in the computation of tmin
. Only when a floating point arithmetic error occurs ((vmin - t0) // minorstep
too small by 1), the +1 coincidentally corrects the value such that the first tick location is included. The second tick is sometimes not included due to a similar floating point arithmetic error ((vmax - t0) // minorstep
too small by 1) that may occur. A suggestion for changes to the computation of the tick locations is given in the above code as well as below.
# tmin_mod = round((vmin - t0) / minorstep)
# tmax_mod = round((vmax - t0) / minorstep) + 1
# locs_mod = (np.arange(tmin_mod, tmax_mod) * minorstep) + t0
Operating system
No response
Matplotlib Version
3.2.2
Matplotlib Backend
Qt5Agg
Python version
3.7.6
Jupyter version
No response
Installation
conda