Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[Bug]: First and or last minor ticks sometimes not plotted #22331

Closed
@silenceOfTheLambda

Description

@silenceOfTheLambda

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

so_minor_ticks_first_last_1
so_minor_ticks_first_last_2

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions