1350 lines
50 KiB
Python
1350 lines
50 KiB
Python
|
try:
|
||
|
from contextlib import nullcontext
|
||
|
except ImportError:
|
||
|
from contextlib import ExitStack as nullcontext # Py 3.6.
|
||
|
import re
|
||
|
import itertools
|
||
|
|
||
|
import numpy as np
|
||
|
from numpy.testing import assert_almost_equal, assert_array_equal
|
||
|
import pytest
|
||
|
|
||
|
import matplotlib as mpl
|
||
|
from matplotlib import cbook
|
||
|
import matplotlib.pyplot as plt
|
||
|
import matplotlib.ticker as mticker
|
||
|
|
||
|
|
||
|
class TestMaxNLocator:
|
||
|
basic_data = [
|
||
|
(20, 100, np.array([20., 40., 60., 80., 100.])),
|
||
|
(0.001, 0.0001, np.array([0., 0.0002, 0.0004, 0.0006, 0.0008, 0.001])),
|
||
|
(-1e15, 1e15, np.array([-1.0e+15, -5.0e+14, 0e+00, 5e+14, 1.0e+15])),
|
||
|
(0, 0.85e-50, np.arange(6) * 2e-51),
|
||
|
(-0.85e-50, 0, np.arange(-5, 1) * 2e-51),
|
||
|
]
|
||
|
|
||
|
integer_data = [
|
||
|
(-0.1, 1.1, None, np.array([-1, 0, 1, 2])),
|
||
|
(-0.1, 0.95, None, np.array([-0.25, 0, 0.25, 0.5, 0.75, 1.0])),
|
||
|
(1, 55, [1, 1.5, 5, 6, 10], np.array([0, 15, 30, 45, 60])),
|
||
|
]
|
||
|
|
||
|
@pytest.mark.parametrize('vmin, vmax, expected', basic_data)
|
||
|
def test_basic(self, vmin, vmax, expected):
|
||
|
loc = mticker.MaxNLocator(nbins=5)
|
||
|
assert_almost_equal(loc.tick_values(vmin, vmax), expected)
|
||
|
|
||
|
@pytest.mark.parametrize('vmin, vmax, steps, expected', integer_data)
|
||
|
def test_integer(self, vmin, vmax, steps, expected):
|
||
|
loc = mticker.MaxNLocator(nbins=5, integer=True, steps=steps)
|
||
|
assert_almost_equal(loc.tick_values(vmin, vmax), expected)
|
||
|
|
||
|
|
||
|
class TestLinearLocator:
|
||
|
def test_basic(self):
|
||
|
loc = mticker.LinearLocator(numticks=3)
|
||
|
test_value = np.array([-0.8, -0.3, 0.2])
|
||
|
assert_almost_equal(loc.tick_values(-0.8, 0.2), test_value)
|
||
|
|
||
|
def test_set_params(self):
|
||
|
"""
|
||
|
Create linear locator with presets={}, numticks=2 and change it to
|
||
|
something else. See if change was successful. Should not exception.
|
||
|
"""
|
||
|
loc = mticker.LinearLocator(numticks=2)
|
||
|
loc.set_params(numticks=8, presets={(0, 1): []})
|
||
|
assert loc.numticks == 8
|
||
|
assert loc.presets == {(0, 1): []}
|
||
|
|
||
|
|
||
|
class TestMultipleLocator:
|
||
|
def test_basic(self):
|
||
|
loc = mticker.MultipleLocator(base=3.147)
|
||
|
test_value = np.array([-9.441, -6.294, -3.147, 0., 3.147, 6.294,
|
||
|
9.441, 12.588])
|
||
|
assert_almost_equal(loc.tick_values(-7, 10), test_value)
|
||
|
|
||
|
def test_view_limits(self):
|
||
|
"""
|
||
|
Test basic behavior of view limits.
|
||
|
"""
|
||
|
with mpl.rc_context({'axes.autolimit_mode': 'data'}):
|
||
|
loc = mticker.MultipleLocator(base=3.147)
|
||
|
assert_almost_equal(loc.view_limits(-5, 5), (-5, 5))
|
||
|
|
||
|
def test_view_limits_round_numbers(self):
|
||
|
"""
|
||
|
Test that everything works properly with 'round_numbers' for auto
|
||
|
limit.
|
||
|
"""
|
||
|
with mpl.rc_context({'axes.autolimit_mode': 'round_numbers'}):
|
||
|
loc = mticker.MultipleLocator(base=3.147)
|
||
|
assert_almost_equal(loc.view_limits(-4, 4), (-6.294, 6.294))
|
||
|
|
||
|
def test_set_params(self):
|
||
|
"""
|
||
|
Create multiple locator with 0.7 base, and change it to something else.
|
||
|
See if change was successful.
|
||
|
"""
|
||
|
mult = mticker.MultipleLocator(base=0.7)
|
||
|
mult.set_params(base=1.7)
|
||
|
assert mult._edge.step == 1.7
|
||
|
|
||
|
|
||
|
class TestAutoMinorLocator:
|
||
|
def test_basic(self):
|
||
|
fig, ax = plt.subplots()
|
||
|
ax.set_xlim(0, 1.39)
|
||
|
ax.minorticks_on()
|
||
|
test_value = np.array([0.05, 0.1, 0.15, 0.25, 0.3, 0.35, 0.45,
|
||
|
0.5, 0.55, 0.65, 0.7, 0.75, 0.85, 0.9,
|
||
|
0.95, 1.05, 1.1, 1.15, 1.25, 1.3, 1.35])
|
||
|
assert_almost_equal(ax.xaxis.get_ticklocs(minor=True), test_value)
|
||
|
|
||
|
# NB: the following values are assuming that *xlim* is [0, 5]
|
||
|
params = [
|
||
|
(0, 0), # no major tick => no minor tick either
|
||
|
(1, 0) # a single major tick => no minor tick
|
||
|
]
|
||
|
|
||
|
@pytest.mark.parametrize('nb_majorticks, expected_nb_minorticks', params)
|
||
|
def test_low_number_of_majorticks(
|
||
|
self, nb_majorticks, expected_nb_minorticks):
|
||
|
# This test is related to issue #8804
|
||
|
fig, ax = plt.subplots()
|
||
|
xlims = (0, 5) # easier to test the different code paths
|
||
|
ax.set_xlim(*xlims)
|
||
|
ax.set_xticks(np.linspace(xlims[0], xlims[1], nb_majorticks))
|
||
|
ax.minorticks_on()
|
||
|
ax.xaxis.set_minor_locator(mticker.AutoMinorLocator())
|
||
|
assert len(ax.xaxis.get_minorticklocs()) == expected_nb_minorticks
|
||
|
|
||
|
majorstep_minordivisions = [(1, 5),
|
||
|
(2, 4),
|
||
|
(2.5, 5),
|
||
|
(5, 5),
|
||
|
(10, 5)]
|
||
|
|
||
|
# This test is meant to verify the parameterization for
|
||
|
# test_number_of_minor_ticks
|
||
|
def test_using_all_default_major_steps(self):
|
||
|
with mpl.rc_context({'_internal.classic_mode': False}):
|
||
|
majorsteps = [x[0] for x in self.majorstep_minordivisions]
|
||
|
np.testing.assert_allclose(majorsteps,
|
||
|
mticker.AutoLocator()._steps)
|
||
|
|
||
|
@pytest.mark.parametrize('major_step, expected_nb_minordivisions',
|
||
|
majorstep_minordivisions)
|
||
|
def test_number_of_minor_ticks(
|
||
|
self, major_step, expected_nb_minordivisions):
|
||
|
fig, ax = plt.subplots()
|
||
|
xlims = (0, major_step)
|
||
|
ax.set_xlim(*xlims)
|
||
|
ax.set_xticks(xlims)
|
||
|
ax.minorticks_on()
|
||
|
ax.xaxis.set_minor_locator(mticker.AutoMinorLocator())
|
||
|
nb_minor_divisions = len(ax.xaxis.get_minorticklocs()) + 1
|
||
|
assert nb_minor_divisions == expected_nb_minordivisions
|
||
|
|
||
|
limits = [(0, 1.39), (0, 0.139),
|
||
|
(0, 0.11e-19), (0, 0.112e-12),
|
||
|
(-2.0e-07, -3.3e-08), (1.20e-06, 1.42e-06),
|
||
|
(-1.34e-06, -1.44e-06), (-8.76e-07, -1.51e-06)]
|
||
|
|
||
|
reference = [
|
||
|
[0.05, 0.1, 0.15, 0.25, 0.3, 0.35, 0.45, 0.5, 0.55, 0.65, 0.7,
|
||
|
0.75, 0.85, 0.9, 0.95, 1.05, 1.1, 1.15, 1.25, 1.3, 1.35],
|
||
|
[0.005, 0.01, 0.015, 0.025, 0.03, 0.035, 0.045, 0.05, 0.055, 0.065,
|
||
|
0.07, 0.075, 0.085, 0.09, 0.095, 0.105, 0.11, 0.115, 0.125, 0.13,
|
||
|
0.135],
|
||
|
[5.00e-22, 1.00e-21, 1.50e-21, 2.50e-21, 3.00e-21, 3.50e-21, 4.50e-21,
|
||
|
5.00e-21, 5.50e-21, 6.50e-21, 7.00e-21, 7.50e-21, 8.50e-21, 9.00e-21,
|
||
|
9.50e-21, 1.05e-20, 1.10e-20],
|
||
|
[5.00e-15, 1.00e-14, 1.50e-14, 2.50e-14, 3.00e-14, 3.50e-14, 4.50e-14,
|
||
|
5.00e-14, 5.50e-14, 6.50e-14, 7.00e-14, 7.50e-14, 8.50e-14, 9.00e-14,
|
||
|
9.50e-14, 1.05e-13, 1.10e-13],
|
||
|
[-1.95e-07, -1.90e-07, -1.85e-07, -1.75e-07, -1.70e-07, -1.65e-07,
|
||
|
-1.55e-07, -1.50e-07, -1.45e-07, -1.35e-07, -1.30e-07, -1.25e-07,
|
||
|
-1.15e-07, -1.10e-07, -1.05e-07, -9.50e-08, -9.00e-08, -8.50e-08,
|
||
|
-7.50e-08, -7.00e-08, -6.50e-08, -5.50e-08, -5.00e-08, -4.50e-08,
|
||
|
-3.50e-08],
|
||
|
[1.21e-06, 1.22e-06, 1.23e-06, 1.24e-06, 1.26e-06, 1.27e-06, 1.28e-06,
|
||
|
1.29e-06, 1.31e-06, 1.32e-06, 1.33e-06, 1.34e-06, 1.36e-06, 1.37e-06,
|
||
|
1.38e-06, 1.39e-06, 1.41e-06, 1.42e-06],
|
||
|
[-1.435e-06, -1.430e-06, -1.425e-06, -1.415e-06, -1.410e-06,
|
||
|
-1.405e-06, -1.395e-06, -1.390e-06, -1.385e-06, -1.375e-06,
|
||
|
-1.370e-06, -1.365e-06, -1.355e-06, -1.350e-06, -1.345e-06],
|
||
|
[-1.48e-06, -1.46e-06, -1.44e-06, -1.42e-06, -1.38e-06, -1.36e-06,
|
||
|
-1.34e-06, -1.32e-06, -1.28e-06, -1.26e-06, -1.24e-06, -1.22e-06,
|
||
|
-1.18e-06, -1.16e-06, -1.14e-06, -1.12e-06, -1.08e-06, -1.06e-06,
|
||
|
-1.04e-06, -1.02e-06, -9.80e-07, -9.60e-07, -9.40e-07, -9.20e-07,
|
||
|
-8.80e-07]]
|
||
|
|
||
|
additional_data = list(zip(limits, reference))
|
||
|
|
||
|
@pytest.mark.parametrize('lim, ref', additional_data)
|
||
|
def test_additional(self, lim, ref):
|
||
|
fig, ax = plt.subplots()
|
||
|
|
||
|
ax.minorticks_on()
|
||
|
ax.grid(True, 'minor', 'y', linewidth=1)
|
||
|
ax.grid(True, 'major', color='k', linewidth=1)
|
||
|
ax.set_ylim(lim)
|
||
|
|
||
|
assert_almost_equal(ax.yaxis.get_ticklocs(minor=True), ref)
|
||
|
|
||
|
|
||
|
class TestLogLocator:
|
||
|
def test_basic(self):
|
||
|
loc = mticker.LogLocator(numticks=5)
|
||
|
with pytest.raises(ValueError):
|
||
|
loc.tick_values(0, 1000)
|
||
|
|
||
|
test_value = np.array([1.00000000e-05, 1.00000000e-03, 1.00000000e-01,
|
||
|
1.00000000e+01, 1.00000000e+03, 1.00000000e+05,
|
||
|
1.00000000e+07, 1.000000000e+09])
|
||
|
assert_almost_equal(loc.tick_values(0.001, 1.1e5), test_value)
|
||
|
|
||
|
loc = mticker.LogLocator(base=2)
|
||
|
test_value = np.array([0.5, 1., 2., 4., 8., 16., 32., 64., 128., 256.])
|
||
|
assert_almost_equal(loc.tick_values(1, 100), test_value)
|
||
|
|
||
|
def test_switch_to_autolocator(self):
|
||
|
loc = mticker.LogLocator(subs="all")
|
||
|
assert_array_equal(loc.tick_values(0.45, 0.55),
|
||
|
[0.44, 0.46, 0.48, 0.5, 0.52, 0.54, 0.56])
|
||
|
# check that we *skip* 1.0, and 10, because this is a minor locator
|
||
|
loc = mticker.LogLocator(subs=np.arange(2, 10))
|
||
|
assert 1.0 not in loc.tick_values(0.9, 20.)
|
||
|
assert 10.0 not in loc.tick_values(0.9, 20.)
|
||
|
|
||
|
def test_set_params(self):
|
||
|
"""
|
||
|
Create log locator with default value, base=10.0, subs=[1.0],
|
||
|
numdecs=4, numticks=15 and change it to something else.
|
||
|
See if change was successful. Should not raise exception.
|
||
|
"""
|
||
|
loc = mticker.LogLocator()
|
||
|
loc.set_params(numticks=7, numdecs=8, subs=[2.0], base=4)
|
||
|
assert loc.numticks == 7
|
||
|
assert loc.numdecs == 8
|
||
|
assert loc._base == 4
|
||
|
assert list(loc._subs) == [2.0]
|
||
|
|
||
|
|
||
|
class TestNullLocator:
|
||
|
def test_set_params(self):
|
||
|
"""
|
||
|
Create null locator, and attempt to call set_params() on it.
|
||
|
Should not exception, and should raise a warning.
|
||
|
"""
|
||
|
loc = mticker.NullLocator()
|
||
|
with pytest.warns(UserWarning):
|
||
|
loc.set_params()
|
||
|
|
||
|
|
||
|
class _LogitHelper:
|
||
|
@staticmethod
|
||
|
def isclose(x, y):
|
||
|
return (np.isclose(-np.log(1/x-1), -np.log(1/y-1))
|
||
|
if 0 < x < 1 and 0 < y < 1 else False)
|
||
|
|
||
|
@staticmethod
|
||
|
def assert_almost_equal(x, y):
|
||
|
ax = np.array(x)
|
||
|
ay = np.array(y)
|
||
|
assert np.all(ax > 0) and np.all(ax < 1)
|
||
|
assert np.all(ay > 0) and np.all(ay < 1)
|
||
|
lx = -np.log(1/ax-1)
|
||
|
ly = -np.log(1/ay-1)
|
||
|
assert_almost_equal(lx, ly)
|
||
|
|
||
|
|
||
|
class TestLogitLocator:
|
||
|
ref_basic_limits = [
|
||
|
(5e-2, 1 - 5e-2),
|
||
|
(5e-3, 1 - 5e-3),
|
||
|
(5e-4, 1 - 5e-4),
|
||
|
(5e-5, 1 - 5e-5),
|
||
|
(5e-6, 1 - 5e-6),
|
||
|
(5e-7, 1 - 5e-7),
|
||
|
(5e-8, 1 - 5e-8),
|
||
|
(5e-9, 1 - 5e-9),
|
||
|
]
|
||
|
|
||
|
ref_basic_major_ticks = [
|
||
|
1 / (10 ** np.arange(1, 3)),
|
||
|
1 / (10 ** np.arange(1, 4)),
|
||
|
1 / (10 ** np.arange(1, 5)),
|
||
|
1 / (10 ** np.arange(1, 6)),
|
||
|
1 / (10 ** np.arange(1, 7)),
|
||
|
1 / (10 ** np.arange(1, 8)),
|
||
|
1 / (10 ** np.arange(1, 9)),
|
||
|
1 / (10 ** np.arange(1, 10)),
|
||
|
]
|
||
|
|
||
|
ref_maxn_limits = [(0.4, 0.6), (5e-2, 2e-1), (1 - 2e-1, 1 - 5e-2)]
|
||
|
|
||
|
@pytest.mark.parametrize(
|
||
|
"lims, expected_low_ticks",
|
||
|
zip(ref_basic_limits, ref_basic_major_ticks),
|
||
|
)
|
||
|
def test_basic_major(self, lims, expected_low_ticks):
|
||
|
"""
|
||
|
Create logit locator with huge number of major, and tests ticks.
|
||
|
"""
|
||
|
expected_ticks = sorted(
|
||
|
[*expected_low_ticks, 0.5, *(1 - expected_low_ticks)]
|
||
|
)
|
||
|
loc = mticker.LogitLocator(nbins=100)
|
||
|
_LogitHelper.assert_almost_equal(
|
||
|
loc.tick_values(*lims),
|
||
|
expected_ticks
|
||
|
)
|
||
|
|
||
|
@pytest.mark.parametrize("lims", ref_maxn_limits)
|
||
|
def test_maxn_major(self, lims):
|
||
|
"""
|
||
|
When the axis is zoomed, the locator must have the same behavior as
|
||
|
MaxNLocator.
|
||
|
"""
|
||
|
loc = mticker.LogitLocator(nbins=100)
|
||
|
maxn_loc = mticker.MaxNLocator(nbins=100, steps=[1, 2, 5, 10])
|
||
|
for nbins in (4, 8, 16):
|
||
|
loc.set_params(nbins=nbins)
|
||
|
maxn_loc.set_params(nbins=nbins)
|
||
|
ticks = loc.tick_values(*lims)
|
||
|
maxn_ticks = maxn_loc.tick_values(*lims)
|
||
|
assert ticks.shape == maxn_ticks.shape
|
||
|
assert (ticks == maxn_ticks).all()
|
||
|
|
||
|
@pytest.mark.parametrize("lims", ref_basic_limits + ref_maxn_limits)
|
||
|
def test_nbins_major(self, lims):
|
||
|
"""
|
||
|
Assert logit locator for respecting nbins param.
|
||
|
"""
|
||
|
|
||
|
basic_needed = int(-np.floor(np.log10(lims[0]))) * 2 + 1
|
||
|
loc = mticker.LogitLocator(nbins=100)
|
||
|
for nbins in range(basic_needed, 2, -1):
|
||
|
loc.set_params(nbins=nbins)
|
||
|
assert len(loc.tick_values(*lims)) <= nbins + 2
|
||
|
|
||
|
@pytest.mark.parametrize(
|
||
|
"lims, expected_low_ticks",
|
||
|
zip(ref_basic_limits, ref_basic_major_ticks),
|
||
|
)
|
||
|
def test_minor(self, lims, expected_low_ticks):
|
||
|
"""
|
||
|
In large scale, test the presence of minor,
|
||
|
and assert no minor when major are subsampled.
|
||
|
"""
|
||
|
|
||
|
expected_ticks = sorted(
|
||
|
[*expected_low_ticks, 0.5, *(1 - expected_low_ticks)]
|
||
|
)
|
||
|
basic_needed = len(expected_ticks)
|
||
|
loc = mticker.LogitLocator(nbins=100)
|
||
|
minor_loc = mticker.LogitLocator(nbins=100, minor=True)
|
||
|
for nbins in range(basic_needed, 2, -1):
|
||
|
loc.set_params(nbins=nbins)
|
||
|
minor_loc.set_params(nbins=nbins)
|
||
|
major_ticks = loc.tick_values(*lims)
|
||
|
minor_ticks = minor_loc.tick_values(*lims)
|
||
|
if len(major_ticks) >= len(expected_ticks):
|
||
|
# no subsample, we must have a lot of minors ticks
|
||
|
assert (len(major_ticks) - 1) * 5 < len(minor_ticks)
|
||
|
else:
|
||
|
# subsample
|
||
|
_LogitHelper.assert_almost_equal(
|
||
|
sorted([*major_ticks, *minor_ticks]), expected_ticks)
|
||
|
|
||
|
def test_minor_attr(self):
|
||
|
loc = mticker.LogitLocator(nbins=100)
|
||
|
assert not loc.minor
|
||
|
loc.minor = True
|
||
|
assert loc.minor
|
||
|
loc.set_params(minor=False)
|
||
|
assert not loc.minor
|
||
|
|
||
|
acceptable_vmin_vmax = [
|
||
|
*(2.5 ** np.arange(-3, 0)),
|
||
|
*(1 - 2.5 ** np.arange(-3, 0)),
|
||
|
]
|
||
|
|
||
|
@pytest.mark.parametrize(
|
||
|
"lims",
|
||
|
[
|
||
|
(a, b)
|
||
|
for (a, b) in itertools.product(acceptable_vmin_vmax, repeat=2)
|
||
|
if a != b
|
||
|
],
|
||
|
)
|
||
|
def test_nonsingular_ok(self, lims):
|
||
|
"""
|
||
|
Create logit locator, and test the nonsingular method for acceptable
|
||
|
value
|
||
|
"""
|
||
|
loc = mticker.LogitLocator()
|
||
|
lims2 = loc.nonsingular(*lims)
|
||
|
assert sorted(lims) == sorted(lims2)
|
||
|
|
||
|
@pytest.mark.parametrize("okval", acceptable_vmin_vmax)
|
||
|
def test_nonsingular_nok(self, okval):
|
||
|
"""
|
||
|
Create logit locator, and test the nonsingular method for non
|
||
|
acceptable value
|
||
|
"""
|
||
|
loc = mticker.LogitLocator()
|
||
|
vmin, vmax = (-1, okval)
|
||
|
vmin2, vmax2 = loc.nonsingular(vmin, vmax)
|
||
|
assert vmax2 == vmax
|
||
|
assert 0 < vmin2 < vmax2
|
||
|
vmin, vmax = (okval, 2)
|
||
|
vmin2, vmax2 = loc.nonsingular(vmin, vmax)
|
||
|
assert vmin2 == vmin
|
||
|
assert vmin2 < vmax2 < 1
|
||
|
|
||
|
|
||
|
class TestFixedLocator:
|
||
|
def test_set_params(self):
|
||
|
"""
|
||
|
Create fixed locator with 5 nbins, and change it to something else.
|
||
|
See if change was successful.
|
||
|
Should not exception.
|
||
|
"""
|
||
|
fixed = mticker.FixedLocator(range(0, 24), nbins=5)
|
||
|
fixed.set_params(nbins=7)
|
||
|
assert fixed.nbins == 7
|
||
|
|
||
|
|
||
|
class TestIndexLocator:
|
||
|
def test_set_params(self):
|
||
|
"""
|
||
|
Create index locator with 3 base, 4 offset. and change it to something
|
||
|
else. See if change was successful.
|
||
|
Should not exception.
|
||
|
"""
|
||
|
index = mticker.IndexLocator(base=3, offset=4)
|
||
|
index.set_params(base=7, offset=7)
|
||
|
assert index._base == 7
|
||
|
assert index.offset == 7
|
||
|
|
||
|
|
||
|
class TestSymmetricalLogLocator:
|
||
|
def test_set_params(self):
|
||
|
"""
|
||
|
Create symmetrical log locator with default subs =[1.0] numticks = 15,
|
||
|
and change it to something else.
|
||
|
See if change was successful.
|
||
|
Should not exception.
|
||
|
"""
|
||
|
sym = mticker.SymmetricalLogLocator(base=10, linthresh=1)
|
||
|
sym.set_params(subs=[2.0], numticks=8)
|
||
|
assert sym._subs == [2.0]
|
||
|
assert sym.numticks == 8
|
||
|
|
||
|
|
||
|
class TestIndexFormatter:
|
||
|
@pytest.mark.parametrize('x, label', [(-2, ''),
|
||
|
(-1, 'label0'),
|
||
|
(0, 'label0'),
|
||
|
(0.5, 'label1'),
|
||
|
(1, 'label1'),
|
||
|
(1.5, 'label2'),
|
||
|
(2, 'label2'),
|
||
|
(2.5, '')])
|
||
|
def test_formatting(self, x, label):
|
||
|
with cbook._suppress_matplotlib_deprecation_warning():
|
||
|
formatter = mticker.IndexFormatter(['label0', 'label1', 'label2'])
|
||
|
assert formatter(x) == label
|
||
|
|
||
|
|
||
|
class TestScalarFormatter:
|
||
|
offset_data = [
|
||
|
(123, 189, 0),
|
||
|
(-189, -123, 0),
|
||
|
(12341, 12349, 12340),
|
||
|
(-12349, -12341, -12340),
|
||
|
(99999.5, 100010.5, 100000),
|
||
|
(-100010.5, -99999.5, -100000),
|
||
|
(99990.5, 100000.5, 100000),
|
||
|
(-100000.5, -99990.5, -100000),
|
||
|
(1233999, 1234001, 1234000),
|
||
|
(-1234001, -1233999, -1234000),
|
||
|
(1, 1, 1),
|
||
|
(123, 123, 0),
|
||
|
# Test cases courtesy of @WeatherGod
|
||
|
(.4538, .4578, .45),
|
||
|
(3789.12, 3783.1, 3780),
|
||
|
(45124.3, 45831.75, 45000),
|
||
|
(0.000721, 0.0007243, 0.00072),
|
||
|
(12592.82, 12591.43, 12590),
|
||
|
(9., 12., 0),
|
||
|
(900., 1200., 0),
|
||
|
(1900., 1200., 0),
|
||
|
(0.99, 1.01, 1),
|
||
|
(9.99, 10.01, 10),
|
||
|
(99.99, 100.01, 100),
|
||
|
(5.99, 6.01, 6),
|
||
|
(15.99, 16.01, 16),
|
||
|
(-0.452, 0.492, 0),
|
||
|
(-0.492, 0.492, 0),
|
||
|
(12331.4, 12350.5, 12300),
|
||
|
(-12335.3, 12335.3, 0),
|
||
|
]
|
||
|
|
||
|
use_offset_data = [True, False]
|
||
|
|
||
|
# (sci_type, scilimits, lim, orderOfMag, fewticks)
|
||
|
scilimits_data = [
|
||
|
(False, (0, 0), (10.0, 20.0), 0, False),
|
||
|
(True, (-2, 2), (-10, 20), 0, False),
|
||
|
(True, (-2, 2), (-20, 10), 0, False),
|
||
|
(True, (-2, 2), (-110, 120), 2, False),
|
||
|
(True, (-2, 2), (-120, 110), 2, False),
|
||
|
(True, (-2, 2), (-.001, 0.002), -3, False),
|
||
|
(True, (-7, 7), (0.18e10, 0.83e10), 9, True),
|
||
|
(True, (0, 0), (-1e5, 1e5), 5, False),
|
||
|
(True, (6, 6), (-1e5, 1e5), 6, False),
|
||
|
]
|
||
|
|
||
|
cursor_data = [
|
||
|
[0., "0.000"],
|
||
|
[0.0123, "0.012"],
|
||
|
[0.123, "0.123"],
|
||
|
[1.23, "1.230"],
|
||
|
[12.3, "12.300"],
|
||
|
]
|
||
|
|
||
|
@pytest.mark.parametrize('unicode_minus, result',
|
||
|
[(True, "\N{MINUS SIGN}1"), (False, "-1")])
|
||
|
def test_unicode_minus(self, unicode_minus, result):
|
||
|
mpl.rcParams['axes.unicode_minus'] = unicode_minus
|
||
|
assert (
|
||
|
plt.gca().xaxis.get_major_formatter().format_data_short(-1).strip()
|
||
|
== result)
|
||
|
|
||
|
@pytest.mark.parametrize('left, right, offset', offset_data)
|
||
|
def test_offset_value(self, left, right, offset):
|
||
|
fig, ax = plt.subplots()
|
||
|
formatter = ax.get_xaxis().get_major_formatter()
|
||
|
|
||
|
with (pytest.warns(UserWarning, match='Attempting to set identical')
|
||
|
if left == right else nullcontext()):
|
||
|
ax.set_xlim(left, right)
|
||
|
ax.get_xaxis()._update_ticks()
|
||
|
assert formatter.offset == offset
|
||
|
|
||
|
with (pytest.warns(UserWarning, match='Attempting to set identical')
|
||
|
if left == right else nullcontext()):
|
||
|
ax.set_xlim(right, left)
|
||
|
ax.get_xaxis()._update_ticks()
|
||
|
assert formatter.offset == offset
|
||
|
|
||
|
@pytest.mark.parametrize('use_offset', use_offset_data)
|
||
|
def test_use_offset(self, use_offset):
|
||
|
with mpl.rc_context({'axes.formatter.useoffset': use_offset}):
|
||
|
tmp_form = mticker.ScalarFormatter()
|
||
|
assert use_offset == tmp_form.get_useOffset()
|
||
|
|
||
|
@pytest.mark.parametrize(
|
||
|
'sci_type, scilimits, lim, orderOfMag, fewticks', scilimits_data)
|
||
|
def test_scilimits(self, sci_type, scilimits, lim, orderOfMag, fewticks):
|
||
|
tmp_form = mticker.ScalarFormatter()
|
||
|
tmp_form.set_scientific(sci_type)
|
||
|
tmp_form.set_powerlimits(scilimits)
|
||
|
fig, ax = plt.subplots()
|
||
|
ax.yaxis.set_major_formatter(tmp_form)
|
||
|
ax.set_ylim(*lim)
|
||
|
if fewticks:
|
||
|
ax.yaxis.set_major_locator(mticker.MaxNLocator(4))
|
||
|
|
||
|
tmp_form.set_locs(ax.yaxis.get_majorticklocs())
|
||
|
assert orderOfMag == tmp_form.orderOfMagnitude
|
||
|
|
||
|
@pytest.mark.parametrize('data, expected', cursor_data)
|
||
|
def test_cursor_precision(self, data, expected):
|
||
|
fig, ax = plt.subplots()
|
||
|
ax.set_xlim(-1, 1) # Pointing precision of 0.001.
|
||
|
fmt = ax.xaxis.get_major_formatter().format_data_short
|
||
|
assert fmt(data) == expected
|
||
|
|
||
|
@pytest.mark.parametrize('data, expected', cursor_data)
|
||
|
def test_cursor_dummy_axis(self, data, expected):
|
||
|
# Issue #17624
|
||
|
sf = mticker.ScalarFormatter()
|
||
|
sf.create_dummy_axis()
|
||
|
sf.set_bounds(0, 10)
|
||
|
fmt = sf.format_data_short
|
||
|
assert fmt(data) == expected
|
||
|
|
||
|
|
||
|
class FakeAxis:
|
||
|
"""Allow Formatter to be called without having a "full" plot set up."""
|
||
|
def __init__(self, vmin=1, vmax=10):
|
||
|
self.vmin = vmin
|
||
|
self.vmax = vmax
|
||
|
|
||
|
def get_view_interval(self):
|
||
|
return self.vmin, self.vmax
|
||
|
|
||
|
|
||
|
class TestLogFormatterExponent:
|
||
|
param_data = [
|
||
|
(True, 4, np.arange(-3, 4.0), np.arange(-3, 4.0),
|
||
|
['-3', '-2', '-1', '0', '1', '2', '3']),
|
||
|
# With labelOnlyBase=False, non-integer powers should be nicely
|
||
|
# formatted.
|
||
|
(False, 10, np.array([0.1, 0.00001, np.pi, 0.2, -0.2, -0.00001]),
|
||
|
range(6), ['0.1', '1e-05', '3.14', '0.2', '-0.2', '-1e-05']),
|
||
|
(False, 50, np.array([3, 5, 12, 42], dtype=float), range(6),
|
||
|
['3', '5', '12', '42']),
|
||
|
]
|
||
|
|
||
|
base_data = [2.0, 5.0, 10.0, np.pi, np.e]
|
||
|
|
||
|
@pytest.mark.parametrize(
|
||
|
'labelOnlyBase, exponent, locs, positions, expected', param_data)
|
||
|
@pytest.mark.parametrize('base', base_data)
|
||
|
def test_basic(self, labelOnlyBase, base, exponent, locs, positions,
|
||
|
expected):
|
||
|
formatter = mticker.LogFormatterExponent(base=base,
|
||
|
labelOnlyBase=labelOnlyBase)
|
||
|
formatter.axis = FakeAxis(1, base**exponent)
|
||
|
vals = base**locs
|
||
|
labels = [formatter(x, pos) for (x, pos) in zip(vals, positions)]
|
||
|
assert labels == expected
|
||
|
|
||
|
def test_blank(self):
|
||
|
# Should be a blank string for non-integer powers if labelOnlyBase=True
|
||
|
formatter = mticker.LogFormatterExponent(base=10, labelOnlyBase=True)
|
||
|
formatter.axis = FakeAxis()
|
||
|
assert formatter(10**0.1) == ''
|
||
|
|
||
|
|
||
|
class TestLogFormatterMathtext:
|
||
|
fmt = mticker.LogFormatterMathtext()
|
||
|
test_data = [
|
||
|
(0, 1, '$\\mathdefault{10^{0}}$'),
|
||
|
(0, 1e-2, '$\\mathdefault{10^{-2}}$'),
|
||
|
(0, 1e2, '$\\mathdefault{10^{2}}$'),
|
||
|
(3, 1, '$\\mathdefault{1}$'),
|
||
|
(3, 1e-2, '$\\mathdefault{0.01}$'),
|
||
|
(3, 1e2, '$\\mathdefault{100}$'),
|
||
|
(3, 1e-3, '$\\mathdefault{10^{-3}}$'),
|
||
|
(3, 1e3, '$\\mathdefault{10^{3}}$'),
|
||
|
]
|
||
|
|
||
|
@pytest.mark.parametrize('min_exponent, value, expected', test_data)
|
||
|
def test_min_exponent(self, min_exponent, value, expected):
|
||
|
with mpl.rc_context({'axes.formatter.min_exponent': min_exponent}):
|
||
|
assert self.fmt(value) == expected
|
||
|
|
||
|
|
||
|
class TestLogFormatterSciNotation:
|
||
|
test_data = [
|
||
|
(2, 0.03125, '$\\mathdefault{2^{-5}}$'),
|
||
|
(2, 1, '$\\mathdefault{2^{0}}$'),
|
||
|
(2, 32, '$\\mathdefault{2^{5}}$'),
|
||
|
(2, 0.0375, '$\\mathdefault{1.2\\times2^{-5}}$'),
|
||
|
(2, 1.2, '$\\mathdefault{1.2\\times2^{0}}$'),
|
||
|
(2, 38.4, '$\\mathdefault{1.2\\times2^{5}}$'),
|
||
|
(10, -1, '$\\mathdefault{-10^{0}}$'),
|
||
|
(10, 1e-05, '$\\mathdefault{10^{-5}}$'),
|
||
|
(10, 1, '$\\mathdefault{10^{0}}$'),
|
||
|
(10, 100000, '$\\mathdefault{10^{5}}$'),
|
||
|
(10, 2e-05, '$\\mathdefault{2\\times10^{-5}}$'),
|
||
|
(10, 2, '$\\mathdefault{2\\times10^{0}}$'),
|
||
|
(10, 200000, '$\\mathdefault{2\\times10^{5}}$'),
|
||
|
(10, 5e-05, '$\\mathdefault{5\\times10^{-5}}$'),
|
||
|
(10, 5, '$\\mathdefault{5\\times10^{0}}$'),
|
||
|
(10, 500000, '$\\mathdefault{5\\times10^{5}}$'),
|
||
|
]
|
||
|
|
||
|
@pytest.mark.style('default')
|
||
|
@pytest.mark.parametrize('base, value, expected', test_data)
|
||
|
def test_basic(self, base, value, expected):
|
||
|
formatter = mticker.LogFormatterSciNotation(base=base)
|
||
|
formatter.sublabel = {1, 2, 5, 1.2}
|
||
|
with mpl.rc_context({'text.usetex': False}):
|
||
|
assert formatter(value) == expected
|
||
|
|
||
|
|
||
|
class TestLogFormatter:
|
||
|
pprint_data = [
|
||
|
(3.141592654e-05, 0.001, '3.142e-5'),
|
||
|
(0.0003141592654, 0.001, '3.142e-4'),
|
||
|
(0.003141592654, 0.001, '3.142e-3'),
|
||
|
(0.03141592654, 0.001, '3.142e-2'),
|
||
|
(0.3141592654, 0.001, '3.142e-1'),
|
||
|
(3.141592654, 0.001, '3.142'),
|
||
|
(31.41592654, 0.001, '3.142e1'),
|
||
|
(314.1592654, 0.001, '3.142e2'),
|
||
|
(3141.592654, 0.001, '3.142e3'),
|
||
|
(31415.92654, 0.001, '3.142e4'),
|
||
|
(314159.2654, 0.001, '3.142e5'),
|
||
|
(1e-05, 0.001, '1e-5'),
|
||
|
(0.0001, 0.001, '1e-4'),
|
||
|
(0.001, 0.001, '1e-3'),
|
||
|
(0.01, 0.001, '1e-2'),
|
||
|
(0.1, 0.001, '1e-1'),
|
||
|
(1, 0.001, '1'),
|
||
|
(10, 0.001, '10'),
|
||
|
(100, 0.001, '100'),
|
||
|
(1000, 0.001, '1000'),
|
||
|
(10000, 0.001, '1e4'),
|
||
|
(100000, 0.001, '1e5'),
|
||
|
(3.141592654e-05, 0.015, '0'),
|
||
|
(0.0003141592654, 0.015, '0'),
|
||
|
(0.003141592654, 0.015, '0.003'),
|
||
|
(0.03141592654, 0.015, '0.031'),
|
||
|
(0.3141592654, 0.015, '0.314'),
|
||
|
(3.141592654, 0.015, '3.142'),
|
||
|
(31.41592654, 0.015, '31.416'),
|
||
|
(314.1592654, 0.015, '314.159'),
|
||
|
(3141.592654, 0.015, '3141.593'),
|
||
|
(31415.92654, 0.015, '31415.927'),
|
||
|
(314159.2654, 0.015, '314159.265'),
|
||
|
(1e-05, 0.015, '0'),
|
||
|
(0.0001, 0.015, '0'),
|
||
|
(0.001, 0.015, '0.001'),
|
||
|
(0.01, 0.015, '0.01'),
|
||
|
(0.1, 0.015, '0.1'),
|
||
|
(1, 0.015, '1'),
|
||
|
(10, 0.015, '10'),
|
||
|
(100, 0.015, '100'),
|
||
|
(1000, 0.015, '1000'),
|
||
|
(10000, 0.015, '10000'),
|
||
|
(100000, 0.015, '100000'),
|
||
|
(3.141592654e-05, 0.5, '0'),
|
||
|
(0.0003141592654, 0.5, '0'),
|
||
|
(0.003141592654, 0.5, '0.003'),
|
||
|
(0.03141592654, 0.5, '0.031'),
|
||
|
(0.3141592654, 0.5, '0.314'),
|
||
|
(3.141592654, 0.5, '3.142'),
|
||
|
(31.41592654, 0.5, '31.416'),
|
||
|
(314.1592654, 0.5, '314.159'),
|
||
|
(3141.592654, 0.5, '3141.593'),
|
||
|
(31415.92654, 0.5, '31415.927'),
|
||
|
(314159.2654, 0.5, '314159.265'),
|
||
|
(1e-05, 0.5, '0'),
|
||
|
(0.0001, 0.5, '0'),
|
||
|
(0.001, 0.5, '0.001'),
|
||
|
(0.01, 0.5, '0.01'),
|
||
|
(0.1, 0.5, '0.1'),
|
||
|
(1, 0.5, '1'),
|
||
|
(10, 0.5, '10'),
|
||
|
(100, 0.5, '100'),
|
||
|
(1000, 0.5, '1000'),
|
||
|
(10000, 0.5, '10000'),
|
||
|
(100000, 0.5, '100000'),
|
||
|
(3.141592654e-05, 5, '0'),
|
||
|
(0.0003141592654, 5, '0'),
|
||
|
(0.003141592654, 5, '0'),
|
||
|
(0.03141592654, 5, '0.03'),
|
||
|
(0.3141592654, 5, '0.31'),
|
||
|
(3.141592654, 5, '3.14'),
|
||
|
(31.41592654, 5, '31.42'),
|
||
|
(314.1592654, 5, '314.16'),
|
||
|
(3141.592654, 5, '3141.59'),
|
||
|
(31415.92654, 5, '31415.93'),
|
||
|
(314159.2654, 5, '314159.27'),
|
||
|
(1e-05, 5, '0'),
|
||
|
(0.0001, 5, '0'),
|
||
|
(0.001, 5, '0'),
|
||
|
(0.01, 5, '0.01'),
|
||
|
(0.1, 5, '0.1'),
|
||
|
(1, 5, '1'),
|
||
|
(10, 5, '10'),
|
||
|
(100, 5, '100'),
|
||
|
(1000, 5, '1000'),
|
||
|
(10000, 5, '10000'),
|
||
|
(100000, 5, '100000'),
|
||
|
(3.141592654e-05, 100, '0'),
|
||
|
(0.0003141592654, 100, '0'),
|
||
|
(0.003141592654, 100, '0'),
|
||
|
(0.03141592654, 100, '0'),
|
||
|
(0.3141592654, 100, '0.3'),
|
||
|
(3.141592654, 100, '3.1'),
|
||
|
(31.41592654, 100, '31.4'),
|
||
|
(314.1592654, 100, '314.2'),
|
||
|
(3141.592654, 100, '3141.6'),
|
||
|
(31415.92654, 100, '31415.9'),
|
||
|
(314159.2654, 100, '314159.3'),
|
||
|
(1e-05, 100, '0'),
|
||
|
(0.0001, 100, '0'),
|
||
|
(0.001, 100, '0'),
|
||
|
(0.01, 100, '0'),
|
||
|
(0.1, 100, '0.1'),
|
||
|
(1, 100, '1'),
|
||
|
(10, 100, '10'),
|
||
|
(100, 100, '100'),
|
||
|
(1000, 100, '1000'),
|
||
|
(10000, 100, '10000'),
|
||
|
(100000, 100, '100000'),
|
||
|
(3.141592654e-05, 1000000.0, '3.1e-5'),
|
||
|
(0.0003141592654, 1000000.0, '3.1e-4'),
|
||
|
(0.003141592654, 1000000.0, '3.1e-3'),
|
||
|
(0.03141592654, 1000000.0, '3.1e-2'),
|
||
|
(0.3141592654, 1000000.0, '3.1e-1'),
|
||
|
(3.141592654, 1000000.0, '3.1'),
|
||
|
(31.41592654, 1000000.0, '3.1e1'),
|
||
|
(314.1592654, 1000000.0, '3.1e2'),
|
||
|
(3141.592654, 1000000.0, '3.1e3'),
|
||
|
(31415.92654, 1000000.0, '3.1e4'),
|
||
|
(314159.2654, 1000000.0, '3.1e5'),
|
||
|
(1e-05, 1000000.0, '1e-5'),
|
||
|
(0.0001, 1000000.0, '1e-4'),
|
||
|
(0.001, 1000000.0, '1e-3'),
|
||
|
(0.01, 1000000.0, '1e-2'),
|
||
|
(0.1, 1000000.0, '1e-1'),
|
||
|
(1, 1000000.0, '1'),
|
||
|
(10, 1000000.0, '10'),
|
||
|
(100, 1000000.0, '100'),
|
||
|
(1000, 1000000.0, '1000'),
|
||
|
(10000, 1000000.0, '1e4'),
|
||
|
(100000, 1000000.0, '1e5'),
|
||
|
]
|
||
|
|
||
|
@pytest.mark.parametrize('value, domain, expected', pprint_data)
|
||
|
def test_pprint(self, value, domain, expected):
|
||
|
fmt = mticker.LogFormatter()
|
||
|
label = fmt._pprint_val(value, domain)
|
||
|
assert label == expected
|
||
|
|
||
|
def _sub_labels(self, axis, subs=()):
|
||
|
"""Test whether locator marks subs to be labeled."""
|
||
|
fmt = axis.get_minor_formatter()
|
||
|
minor_tlocs = axis.get_minorticklocs()
|
||
|
fmt.set_locs(minor_tlocs)
|
||
|
coefs = minor_tlocs / 10**(np.floor(np.log10(minor_tlocs)))
|
||
|
label_expected = [round(c) in subs for c in coefs]
|
||
|
label_test = [fmt(x) != '' for x in minor_tlocs]
|
||
|
assert label_test == label_expected
|
||
|
|
||
|
@pytest.mark.style('default')
|
||
|
def test_sublabel(self):
|
||
|
# test label locator
|
||
|
fig, ax = plt.subplots()
|
||
|
ax.set_xscale('log')
|
||
|
ax.xaxis.set_major_locator(mticker.LogLocator(base=10, subs=[]))
|
||
|
ax.xaxis.set_minor_locator(mticker.LogLocator(base=10,
|
||
|
subs=np.arange(2, 10)))
|
||
|
ax.xaxis.set_major_formatter(mticker.LogFormatter(labelOnlyBase=True))
|
||
|
ax.xaxis.set_minor_formatter(mticker.LogFormatter(labelOnlyBase=False))
|
||
|
# axis range above 3 decades, only bases are labeled
|
||
|
ax.set_xlim(1, 1e4)
|
||
|
fmt = ax.xaxis.get_major_formatter()
|
||
|
fmt.set_locs(ax.xaxis.get_majorticklocs())
|
||
|
show_major_labels = [fmt(x) != ''
|
||
|
for x in ax.xaxis.get_majorticklocs()]
|
||
|
assert np.all(show_major_labels)
|
||
|
self._sub_labels(ax.xaxis, subs=[])
|
||
|
|
||
|
# For the next two, if the numdec threshold in LogFormatter.set_locs
|
||
|
# were 3, then the label sub would be 3 for 2-3 decades and (2, 5)
|
||
|
# for 1-2 decades. With a threshold of 1, subs are not labeled.
|
||
|
# axis range at 2 to 3 decades
|
||
|
ax.set_xlim(1, 800)
|
||
|
self._sub_labels(ax.xaxis, subs=[])
|
||
|
|
||
|
# axis range at 1 to 2 decades
|
||
|
ax.set_xlim(1, 80)
|
||
|
self._sub_labels(ax.xaxis, subs=[])
|
||
|
|
||
|
# axis range at 0.4 to 1 decades, label subs 2, 3, 4, 6
|
||
|
ax.set_xlim(1, 8)
|
||
|
self._sub_labels(ax.xaxis, subs=[2, 3, 4, 6])
|
||
|
|
||
|
# axis range at 0 to 0.4 decades, label all
|
||
|
ax.set_xlim(0.5, 0.9)
|
||
|
self._sub_labels(ax.xaxis, subs=np.arange(2, 10, dtype=int))
|
||
|
|
||
|
@pytest.mark.parametrize('val', [1, 10, 100, 1000])
|
||
|
def test_LogFormatter_call(self, val):
|
||
|
# test _num_to_string method used in __call__
|
||
|
temp_lf = mticker.LogFormatter()
|
||
|
temp_lf.axis = FakeAxis()
|
||
|
assert temp_lf(val) == str(val)
|
||
|
|
||
|
|
||
|
class TestLogitFormatter:
|
||
|
@staticmethod
|
||
|
def logit_deformatter(string):
|
||
|
r"""
|
||
|
Parser to convert string as r'$\mathdefault{1.41\cdot10^{-4}}$' in
|
||
|
float 1.41e-4, as '0.5' or as r'$\mathdefault{\frac{1}{2}}$' in float
|
||
|
0.5,
|
||
|
"""
|
||
|
match = re.match(
|
||
|
r"[^\d]*"
|
||
|
r"(?P<comp>1-)?"
|
||
|
r"(?P<mant>\d*\.?\d*)?"
|
||
|
r"(?:\\cdot)?"
|
||
|
r"(?:10\^\{(?P<expo>-?\d*)})?"
|
||
|
r"[^\d]*$",
|
||
|
string,
|
||
|
)
|
||
|
if match:
|
||
|
comp = match["comp"] is not None
|
||
|
mantissa = float(match["mant"]) if match["mant"] else 1
|
||
|
expo = int(match["expo"]) if match["expo"] is not None else 0
|
||
|
value = mantissa * 10 ** expo
|
||
|
if match["mant"] or match["expo"] is not None:
|
||
|
if comp:
|
||
|
return 1 - value
|
||
|
return value
|
||
|
match = re.match(
|
||
|
r"[^\d]*\\frac\{(?P<num>\d+)\}\{(?P<deno>\d+)\}[^\d]*$", string
|
||
|
)
|
||
|
if match:
|
||
|
num, deno = float(match["num"]), float(match["deno"])
|
||
|
return num / deno
|
||
|
raise ValueError("Not formatted by LogitFormatter")
|
||
|
|
||
|
@pytest.mark.parametrize(
|
||
|
"fx, x",
|
||
|
[
|
||
|
(r"STUFF0.41OTHERSTUFF", 0.41),
|
||
|
(r"STUFF1.41\cdot10^{-2}OTHERSTUFF", 1.41e-2),
|
||
|
(r"STUFF1-0.41OTHERSTUFF", 1 - 0.41),
|
||
|
(r"STUFF1-1.41\cdot10^{-2}OTHERSTUFF", 1 - 1.41e-2),
|
||
|
(r"STUFF", None),
|
||
|
(r"STUFF12.4e-3OTHERSTUFF", None),
|
||
|
],
|
||
|
)
|
||
|
def test_logit_deformater(self, fx, x):
|
||
|
if x is None:
|
||
|
with pytest.raises(ValueError):
|
||
|
TestLogitFormatter.logit_deformatter(fx)
|
||
|
else:
|
||
|
y = TestLogitFormatter.logit_deformatter(fx)
|
||
|
assert _LogitHelper.isclose(x, y)
|
||
|
|
||
|
decade_test = sorted(
|
||
|
[10 ** (-i) for i in range(1, 10)]
|
||
|
+ [1 - 10 ** (-i) for i in range(1, 10)]
|
||
|
+ [1 / 2]
|
||
|
)
|
||
|
|
||
|
@pytest.mark.parametrize("x", decade_test)
|
||
|
def test_basic(self, x):
|
||
|
"""
|
||
|
Test the formatted value correspond to the value for ideal ticks in
|
||
|
logit space.
|
||
|
"""
|
||
|
formatter = mticker.LogitFormatter(use_overline=False)
|
||
|
formatter.set_locs(self.decade_test)
|
||
|
s = formatter(x)
|
||
|
x2 = TestLogitFormatter.logit_deformatter(s)
|
||
|
assert _LogitHelper.isclose(x, x2)
|
||
|
|
||
|
@pytest.mark.parametrize("x", (-1, -0.5, -0.1, 1.1, 1.5, 2))
|
||
|
def test_invalid(self, x):
|
||
|
"""
|
||
|
Test that invalid value are formatted with empty string without
|
||
|
raising exception.
|
||
|
"""
|
||
|
formatter = mticker.LogitFormatter(use_overline=False)
|
||
|
formatter.set_locs(self.decade_test)
|
||
|
s = formatter(x)
|
||
|
assert s == ""
|
||
|
|
||
|
@pytest.mark.parametrize("x", 1 / (1 + np.exp(-np.linspace(-7, 7, 10))))
|
||
|
def test_variablelength(self, x):
|
||
|
"""
|
||
|
The format length should change depending on the neighbor labels.
|
||
|
"""
|
||
|
formatter = mticker.LogitFormatter(use_overline=False)
|
||
|
for N in (10, 20, 50, 100, 200, 1000, 2000, 5000, 10000):
|
||
|
if x + 1 / N < 1:
|
||
|
formatter.set_locs([x - 1 / N, x, x + 1 / N])
|
||
|
sx = formatter(x)
|
||
|
sx1 = formatter(x + 1 / N)
|
||
|
d = (
|
||
|
TestLogitFormatter.logit_deformatter(sx1)
|
||
|
- TestLogitFormatter.logit_deformatter(sx)
|
||
|
)
|
||
|
assert 0 < d < 2 / N
|
||
|
|
||
|
lims_minor_major = [
|
||
|
(True, (5e-8, 1 - 5e-8), ((25, False), (75, False))),
|
||
|
(True, (5e-5, 1 - 5e-5), ((25, False), (75, True))),
|
||
|
(True, (5e-2, 1 - 5e-2), ((25, True), (75, True))),
|
||
|
(False, (0.75, 0.76, 0.77), ((7, True), (25, True), (75, True))),
|
||
|
]
|
||
|
|
||
|
@pytest.mark.parametrize("method, lims, cases", lims_minor_major)
|
||
|
def test_minor_vs_major(self, method, lims, cases):
|
||
|
"""
|
||
|
Test minor/major displays.
|
||
|
"""
|
||
|
|
||
|
if method:
|
||
|
min_loc = mticker.LogitLocator(minor=True)
|
||
|
ticks = min_loc.tick_values(*lims)
|
||
|
else:
|
||
|
ticks = np.array(lims)
|
||
|
min_form = mticker.LogitFormatter(minor=True)
|
||
|
for threshold, has_minor in cases:
|
||
|
min_form.set_minor_threshold(threshold)
|
||
|
formatted = min_form.format_ticks(ticks)
|
||
|
labelled = [f for f in formatted if len(f) > 0]
|
||
|
if has_minor:
|
||
|
assert len(labelled) > 0, (threshold, has_minor)
|
||
|
else:
|
||
|
assert len(labelled) == 0, (threshold, has_minor)
|
||
|
|
||
|
def test_minor_number(self):
|
||
|
"""
|
||
|
Test the parameter minor_number
|
||
|
"""
|
||
|
min_loc = mticker.LogitLocator(minor=True)
|
||
|
min_form = mticker.LogitFormatter(minor=True)
|
||
|
ticks = min_loc.tick_values(5e-2, 1 - 5e-2)
|
||
|
for minor_number in (2, 4, 8, 16):
|
||
|
min_form.set_minor_number(minor_number)
|
||
|
formatted = min_form.format_ticks(ticks)
|
||
|
labelled = [f for f in formatted if len(f) > 0]
|
||
|
assert len(labelled) == minor_number
|
||
|
|
||
|
def test_use_overline(self):
|
||
|
"""
|
||
|
Test the parameter use_overline
|
||
|
"""
|
||
|
x = 1 - 1e-2
|
||
|
fx1 = r"$\mathdefault{1-10^{-2}}$"
|
||
|
fx2 = r"$\mathdefault{\overline{10^{-2}}}$"
|
||
|
form = mticker.LogitFormatter(use_overline=False)
|
||
|
assert form(x) == fx1
|
||
|
form.use_overline(True)
|
||
|
assert form(x) == fx2
|
||
|
form.use_overline(False)
|
||
|
assert form(x) == fx1
|
||
|
|
||
|
def test_one_half(self):
|
||
|
"""
|
||
|
Test the parameter one_half
|
||
|
"""
|
||
|
form = mticker.LogitFormatter()
|
||
|
assert r"\frac{1}{2}" in form(1/2)
|
||
|
form.set_one_half("1/2")
|
||
|
assert "1/2" in form(1/2)
|
||
|
form.set_one_half("one half")
|
||
|
assert "one half" in form(1/2)
|
||
|
|
||
|
@pytest.mark.parametrize("N", (100, 253, 754))
|
||
|
def test_format_data_short(self, N):
|
||
|
locs = np.linspace(0, 1, N)[1:-1]
|
||
|
form = mticker.LogitFormatter()
|
||
|
for x in locs:
|
||
|
fx = form.format_data_short(x)
|
||
|
if fx.startswith("1-"):
|
||
|
x2 = 1 - float(fx[2:])
|
||
|
else:
|
||
|
x2 = float(fx)
|
||
|
assert abs(x - x2) < 1 / N
|
||
|
|
||
|
|
||
|
class TestFormatStrFormatter:
|
||
|
def test_basic(self):
|
||
|
# test % style formatter
|
||
|
tmp_form = mticker.FormatStrFormatter('%05d')
|
||
|
assert '00002' == tmp_form(2)
|
||
|
|
||
|
|
||
|
class TestStrMethodFormatter:
|
||
|
test_data = [
|
||
|
('{x:05d}', (2,), '00002'),
|
||
|
('{x:03d}-{pos:02d}', (2, 1), '002-01'),
|
||
|
]
|
||
|
|
||
|
@pytest.mark.parametrize('format, input, expected', test_data)
|
||
|
def test_basic(self, format, input, expected):
|
||
|
fmt = mticker.StrMethodFormatter(format)
|
||
|
assert fmt(*input) == expected
|
||
|
|
||
|
|
||
|
class TestEngFormatter:
|
||
|
# (unicode_minus, input, expected) where ''expected'' corresponds to the
|
||
|
# outputs respectively returned when (places=None, places=0, places=2)
|
||
|
# unicode_minus is a boolean value for the rcParam['axes.unicode_minus']
|
||
|
raw_format_data = [
|
||
|
(False, -1234.56789, ('-1.23457 k', '-1 k', '-1.23 k')),
|
||
|
(True, -1234.56789, ('\N{MINUS SIGN}1.23457 k', '\N{MINUS SIGN}1 k',
|
||
|
'\N{MINUS SIGN}1.23 k')),
|
||
|
(False, -1.23456789, ('-1.23457', '-1', '-1.23')),
|
||
|
(True, -1.23456789, ('\N{MINUS SIGN}1.23457', '\N{MINUS SIGN}1',
|
||
|
'\N{MINUS SIGN}1.23')),
|
||
|
(False, -0.123456789, ('-123.457 m', '-123 m', '-123.46 m')),
|
||
|
(True, -0.123456789, ('\N{MINUS SIGN}123.457 m', '\N{MINUS SIGN}123 m',
|
||
|
'\N{MINUS SIGN}123.46 m')),
|
||
|
(False, -0.00123456789, ('-1.23457 m', '-1 m', '-1.23 m')),
|
||
|
(True, -0.00123456789, ('\N{MINUS SIGN}1.23457 m', '\N{MINUS SIGN}1 m',
|
||
|
'\N{MINUS SIGN}1.23 m')),
|
||
|
(True, -0.0, ('0', '0', '0.00')),
|
||
|
(True, -0, ('0', '0', '0.00')),
|
||
|
(True, 0, ('0', '0', '0.00')),
|
||
|
(True, 1.23456789e-6, ('1.23457 µ', '1 µ', '1.23 µ')),
|
||
|
(True, 0.123456789, ('123.457 m', '123 m', '123.46 m')),
|
||
|
(True, 0.1, ('100 m', '100 m', '100.00 m')),
|
||
|
(True, 1, ('1', '1', '1.00')),
|
||
|
(True, 1.23456789, ('1.23457', '1', '1.23')),
|
||
|
# places=0: corner-case rounding
|
||
|
(True, 999.9, ('999.9', '1 k', '999.90')),
|
||
|
# corner-case rounding for all
|
||
|
(True, 999.9999, ('1 k', '1 k', '1.00 k')),
|
||
|
# negative corner-case
|
||
|
(False, -999.9999, ('-1 k', '-1 k', '-1.00 k')),
|
||
|
(True, -999.9999, ('\N{MINUS SIGN}1 k', '\N{MINUS SIGN}1 k',
|
||
|
'\N{MINUS SIGN}1.00 k')),
|
||
|
(True, 1000, ('1 k', '1 k', '1.00 k')),
|
||
|
(True, 1001, ('1.001 k', '1 k', '1.00 k')),
|
||
|
(True, 100001, ('100.001 k', '100 k', '100.00 k')),
|
||
|
(True, 987654.321, ('987.654 k', '988 k', '987.65 k')),
|
||
|
# OoR value (> 1000 Y)
|
||
|
(True, 1.23e27, ('1230 Y', '1230 Y', '1230.00 Y'))
|
||
|
]
|
||
|
|
||
|
@pytest.mark.parametrize('unicode_minus, input, expected', raw_format_data)
|
||
|
def test_params(self, unicode_minus, input, expected):
|
||
|
"""
|
||
|
Test the formatting of EngFormatter for various values of the 'places'
|
||
|
argument, in several cases:
|
||
|
0. without a unit symbol but with a (default) space separator;
|
||
|
1. with both a unit symbol and a (default) space separator;
|
||
|
2. with both a unit symbol and some non default separators;
|
||
|
3. without a unit symbol but with some non default separators.
|
||
|
Note that cases 2. and 3. are looped over several separator strings.
|
||
|
"""
|
||
|
|
||
|
plt.rcParams['axes.unicode_minus'] = unicode_minus
|
||
|
UNIT = 's' # seconds
|
||
|
DIGITS = '0123456789' # %timeit showed 10-20% faster search than set
|
||
|
|
||
|
# Case 0: unit='' (default) and sep=' ' (default).
|
||
|
# 'expected' already corresponds to this reference case.
|
||
|
exp_outputs = expected
|
||
|
formatters = (
|
||
|
mticker.EngFormatter(), # places=None (default)
|
||
|
mticker.EngFormatter(places=0),
|
||
|
mticker.EngFormatter(places=2)
|
||
|
)
|
||
|
for _formatter, _exp_output in zip(formatters, exp_outputs):
|
||
|
assert _formatter(input) == _exp_output
|
||
|
|
||
|
# Case 1: unit=UNIT and sep=' ' (default).
|
||
|
# Append a unit symbol to the reference case.
|
||
|
# Beware of the values in [1, 1000), where there is no prefix!
|
||
|
exp_outputs = (_s + " " + UNIT if _s[-1] in DIGITS # case w/o prefix
|
||
|
else _s + UNIT for _s in expected)
|
||
|
formatters = (
|
||
|
mticker.EngFormatter(unit=UNIT), # places=None (default)
|
||
|
mticker.EngFormatter(unit=UNIT, places=0),
|
||
|
mticker.EngFormatter(unit=UNIT, places=2)
|
||
|
)
|
||
|
for _formatter, _exp_output in zip(formatters, exp_outputs):
|
||
|
assert _formatter(input) == _exp_output
|
||
|
|
||
|
# Test several non default separators: no separator, a narrow
|
||
|
# no-break space (unicode character) and an extravagant string.
|
||
|
for _sep in ("", "\N{NARROW NO-BREAK SPACE}", "@_@"):
|
||
|
# Case 2: unit=UNIT and sep=_sep.
|
||
|
# Replace the default space separator from the reference case
|
||
|
# with the tested one `_sep` and append a unit symbol to it.
|
||
|
exp_outputs = (_s + _sep + UNIT if _s[-1] in DIGITS # no prefix
|
||
|
else _s.replace(" ", _sep) + UNIT
|
||
|
for _s in expected)
|
||
|
formatters = (
|
||
|
mticker.EngFormatter(unit=UNIT, sep=_sep), # places=None
|
||
|
mticker.EngFormatter(unit=UNIT, places=0, sep=_sep),
|
||
|
mticker.EngFormatter(unit=UNIT, places=2, sep=_sep)
|
||
|
)
|
||
|
for _formatter, _exp_output in zip(formatters, exp_outputs):
|
||
|
assert _formatter(input) == _exp_output
|
||
|
|
||
|
# Case 3: unit='' (default) and sep=_sep.
|
||
|
# Replace the default space separator from the reference case
|
||
|
# with the tested one `_sep`. Reference case is already unitless.
|
||
|
exp_outputs = (_s.replace(" ", _sep) for _s in expected)
|
||
|
formatters = (
|
||
|
mticker.EngFormatter(sep=_sep), # places=None (default)
|
||
|
mticker.EngFormatter(places=0, sep=_sep),
|
||
|
mticker.EngFormatter(places=2, sep=_sep)
|
||
|
)
|
||
|
for _formatter, _exp_output in zip(formatters, exp_outputs):
|
||
|
assert _formatter(input) == _exp_output
|
||
|
|
||
|
|
||
|
def test_engformatter_usetex_useMathText():
|
||
|
fig, ax = plt.subplots()
|
||
|
ax.plot([0, 500, 1000], [0, 500, 1000])
|
||
|
ax.set_xticks([0, 500, 1000])
|
||
|
for formatter in (mticker.EngFormatter(usetex=True),
|
||
|
mticker.EngFormatter(useMathText=True)):
|
||
|
ax.xaxis.set_major_formatter(formatter)
|
||
|
fig.canvas.draw()
|
||
|
x_tick_label_text = [labl.get_text() for labl in ax.get_xticklabels()]
|
||
|
# Checking if the dollar `$` signs have been inserted around numbers
|
||
|
# in tick labels.
|
||
|
assert x_tick_label_text == ['$0$', '$500$', '$1$ k']
|
||
|
|
||
|
|
||
|
class TestPercentFormatter:
|
||
|
percent_data = [
|
||
|
# Check explicitly set decimals over different intervals and values
|
||
|
(100, 0, '%', 120, 100, '120%'),
|
||
|
(100, 0, '%', 100, 90, '100%'),
|
||
|
(100, 0, '%', 90, 50, '90%'),
|
||
|
(100, 0, '%', -1.7, 40, '-2%'),
|
||
|
(100, 1, '%', 90.0, 100, '90.0%'),
|
||
|
(100, 1, '%', 80.1, 90, '80.1%'),
|
||
|
(100, 1, '%', 70.23, 50, '70.2%'),
|
||
|
# 60.554 instead of 60.55: see https://bugs.python.org/issue5118
|
||
|
(100, 1, '%', -60.554, 40, '-60.6%'),
|
||
|
# Check auto decimals over different intervals and values
|
||
|
(100, None, '%', 95, 1, '95.00%'),
|
||
|
(1.0, None, '%', 3, 6, '300%'),
|
||
|
(17.0, None, '%', 1, 8.5, '6%'),
|
||
|
(17.0, None, '%', 1, 8.4, '5.9%'),
|
||
|
(5, None, '%', -100, 0.000001, '-2000.00000%'),
|
||
|
# Check percent symbol
|
||
|
(1.0, 2, None, 1.2, 100, '120.00'),
|
||
|
(75, 3, '', 50, 100, '66.667'),
|
||
|
(42, None, '^^Foobar$$', 21, 12, '50.0^^Foobar$$'),
|
||
|
]
|
||
|
|
||
|
percent_ids = [
|
||
|
# Check explicitly set decimals over different intervals and values
|
||
|
'decimals=0, x>100%',
|
||
|
'decimals=0, x=100%',
|
||
|
'decimals=0, x<100%',
|
||
|
'decimals=0, x<0%',
|
||
|
'decimals=1, x>100%',
|
||
|
'decimals=1, x=100%',
|
||
|
'decimals=1, x<100%',
|
||
|
'decimals=1, x<0%',
|
||
|
# Check auto decimals over different intervals and values
|
||
|
'autodecimal, x<100%, display_range=1',
|
||
|
'autodecimal, x>100%, display_range=6 (custom xmax test)',
|
||
|
'autodecimal, x<100%, display_range=8.5 (autodecimal test 1)',
|
||
|
'autodecimal, x<100%, display_range=8.4 (autodecimal test 2)',
|
||
|
'autodecimal, x<-100%, display_range=1e-6 (tiny display range)',
|
||
|
# Check percent symbol
|
||
|
'None as percent symbol',
|
||
|
'Empty percent symbol',
|
||
|
'Custom percent symbol',
|
||
|
]
|
||
|
|
||
|
latex_data = [
|
||
|
(False, False, r'50\{t}%'),
|
||
|
(False, True, r'50\\\{t\}\%'),
|
||
|
(True, False, r'50\{t}%'),
|
||
|
(True, True, r'50\{t}%'),
|
||
|
]
|
||
|
|
||
|
@pytest.mark.parametrize(
|
||
|
'xmax, decimals, symbol, x, display_range, expected',
|
||
|
percent_data, ids=percent_ids)
|
||
|
def test_basic(self, xmax, decimals, symbol,
|
||
|
x, display_range, expected):
|
||
|
formatter = mticker.PercentFormatter(xmax, decimals, symbol)
|
||
|
with mpl.rc_context(rc={'text.usetex': False}):
|
||
|
assert formatter.format_pct(x, display_range) == expected
|
||
|
|
||
|
@pytest.mark.parametrize('is_latex, usetex, expected', latex_data)
|
||
|
def test_latex(self, is_latex, usetex, expected):
|
||
|
fmt = mticker.PercentFormatter(symbol='\\{t}%', is_latex=is_latex)
|
||
|
with mpl.rc_context(rc={'text.usetex': usetex}):
|
||
|
assert fmt.format_pct(50, 100) == expected
|
||
|
|
||
|
|
||
|
def test_majformatter_type():
|
||
|
fig, ax = plt.subplots()
|
||
|
with pytest.raises(TypeError):
|
||
|
ax.xaxis.set_major_formatter(mticker.LogLocator())
|
||
|
|
||
|
|
||
|
def test_minformatter_type():
|
||
|
fig, ax = plt.subplots()
|
||
|
with pytest.raises(TypeError):
|
||
|
ax.xaxis.set_minor_formatter(mticker.LogLocator())
|
||
|
|
||
|
|
||
|
def test_majlocator_type():
|
||
|
fig, ax = plt.subplots()
|
||
|
with pytest.raises(TypeError):
|
||
|
ax.xaxis.set_major_locator(mticker.LogFormatter())
|
||
|
|
||
|
|
||
|
def test_minlocator_type():
|
||
|
fig, ax = plt.subplots()
|
||
|
with pytest.raises(TypeError):
|
||
|
ax.xaxis.set_minor_locator(mticker.LogFormatter())
|
||
|
|
||
|
|
||
|
def test_minorticks_rc():
|
||
|
fig = plt.figure()
|
||
|
|
||
|
def minorticksubplot(xminor, yminor, i):
|
||
|
rc = {'xtick.minor.visible': xminor,
|
||
|
'ytick.minor.visible': yminor}
|
||
|
with plt.rc_context(rc=rc):
|
||
|
ax = fig.add_subplot(2, 2, i)
|
||
|
|
||
|
assert (len(ax.xaxis.get_minor_ticks()) > 0) == xminor
|
||
|
assert (len(ax.yaxis.get_minor_ticks()) > 0) == yminor
|
||
|
|
||
|
minorticksubplot(False, False, 1)
|
||
|
minorticksubplot(True, False, 2)
|
||
|
minorticksubplot(False, True, 3)
|
||
|
minorticksubplot(True, True, 4)
|
||
|
|
||
|
|
||
|
@pytest.mark.parametrize('remove_overlapping_locs, expected_num',
|
||
|
((True, 6),
|
||
|
(None, 6), # this tests the default
|
||
|
(False, 9)))
|
||
|
def test_remove_overlap(remove_overlapping_locs, expected_num):
|
||
|
t = np.arange("2018-11-03", "2018-11-06", dtype="datetime64")
|
||
|
x = np.ones(len(t))
|
||
|
|
||
|
fig, ax = plt.subplots()
|
||
|
ax.plot(t, x)
|
||
|
|
||
|
ax.xaxis.set_major_locator(mpl.dates.DayLocator())
|
||
|
ax.xaxis.set_major_formatter(mpl.dates.DateFormatter('\n%a'))
|
||
|
|
||
|
ax.xaxis.set_minor_locator(mpl.dates.HourLocator((0, 6, 12, 18)))
|
||
|
ax.xaxis.set_minor_formatter(mpl.dates.DateFormatter('%H:%M'))
|
||
|
# force there to be extra ticks
|
||
|
ax.xaxis.get_minor_ticks(15)
|
||
|
if remove_overlapping_locs is not None:
|
||
|
ax.xaxis.remove_overlapping_locs = remove_overlapping_locs
|
||
|
|
||
|
# check that getter/setter exists
|
||
|
current = ax.xaxis.remove_overlapping_locs
|
||
|
assert (current == ax.xaxis.get_remove_overlapping_locs())
|
||
|
plt.setp(ax.xaxis, remove_overlapping_locs=current)
|
||
|
new = ax.xaxis.remove_overlapping_locs
|
||
|
assert (new == ax.xaxis.remove_overlapping_locs)
|
||
|
|
||
|
# check that the accessors filter correctly
|
||
|
# this is the method that does the actual filtering
|
||
|
assert len(ax.xaxis.get_minorticklocs()) == expected_num
|
||
|
# these three are derivative
|
||
|
assert len(ax.xaxis.get_minor_ticks()) == expected_num
|
||
|
assert len(ax.xaxis.get_minorticklabels()) == expected_num
|
||
|
assert len(ax.xaxis.get_minorticklines()) == expected_num*2
|
||
|
|
||
|
|
||
|
@pytest.mark.parametrize('sub', [
|
||
|
['hi', 'aardvark'],
|
||
|
np.zeros((2, 2))])
|
||
|
def test_bad_locator_subs(sub):
|
||
|
ll = mticker.LogLocator()
|
||
|
with pytest.raises(ValueError):
|
||
|
ll.subs(sub)
|