Fixed database typo and removed unnecessary class identifier.
This commit is contained in:
parent
00ad49a143
commit
45fb349a7d
5098 changed files with 952558 additions and 85 deletions
15
venv/Lib/site-packages/mpl_toolkits/axisartist/__init__.py
Normal file
15
venv/Lib/site-packages/mpl_toolkits/axisartist/__init__.py
Normal file
|
@ -0,0 +1,15 @@
|
|||
from .axislines import (
|
||||
Axes, AxesZero, AxisArtistHelper, AxisArtistHelperRectlinear,
|
||||
GridHelperBase, GridHelperRectlinear, Subplot, SubplotZero)
|
||||
from .axis_artist import AxisArtist, GridlinesCollection
|
||||
from .grid_helper_curvelinear import GridHelperCurveLinear
|
||||
from .floating_axes import FloatingAxes, FloatingSubplot
|
||||
from mpl_toolkits.axes_grid1.parasite_axes import (
|
||||
host_axes_class_factory, parasite_axes_class_factory,
|
||||
parasite_axes_auxtrans_class_factory, subplot_class_factory)
|
||||
|
||||
|
||||
ParasiteAxes = parasite_axes_class_factory(Axes)
|
||||
ParasiteAxesAuxTrans = parasite_axes_auxtrans_class_factory(ParasiteAxes)
|
||||
HostAxes = host_axes_class_factory(Axes)
|
||||
SubplotHost = subplot_class_factory(HostAxes)
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
405
venv/Lib/site-packages/mpl_toolkits/axisartist/angle_helper.py
Normal file
405
venv/Lib/site-packages/mpl_toolkits/axisartist/angle_helper.py
Normal file
|
@ -0,0 +1,405 @@
|
|||
import numpy as np
|
||||
import math
|
||||
|
||||
from matplotlib import cbook
|
||||
from mpl_toolkits.axisartist.grid_finder import ExtremeFinderSimple
|
||||
|
||||
|
||||
def select_step_degree(dv):
|
||||
|
||||
degree_limits_ = [1.5, 3, 7, 13, 20, 40, 70, 120, 270, 520]
|
||||
degree_steps_ = [1, 2, 5, 10, 15, 30, 45, 90, 180, 360]
|
||||
degree_factors = [1.] * len(degree_steps_)
|
||||
|
||||
minsec_limits_ = [1.5, 2.5, 3.5, 8, 11, 18, 25, 45]
|
||||
minsec_steps_ = [1, 2, 3, 5, 10, 15, 20, 30]
|
||||
|
||||
minute_limits_ = np.array(minsec_limits_) / 60
|
||||
minute_factors = [60.] * len(minute_limits_)
|
||||
|
||||
second_limits_ = np.array(minsec_limits_) / 3600
|
||||
second_factors = [3600.] * len(second_limits_)
|
||||
|
||||
degree_limits = [*second_limits_, *minute_limits_, *degree_limits_]
|
||||
degree_steps = [*minsec_steps_, *minsec_steps_, *degree_steps_]
|
||||
degree_factors = [*second_factors, *minute_factors, *degree_factors]
|
||||
|
||||
n = np.searchsorted(degree_limits, dv)
|
||||
step = degree_steps[n]
|
||||
factor = degree_factors[n]
|
||||
|
||||
return step, factor
|
||||
|
||||
|
||||
def select_step_hour(dv):
|
||||
|
||||
hour_limits_ = [1.5, 2.5, 3.5, 5, 7, 10, 15, 21, 36]
|
||||
hour_steps_ = [1, 2, 3, 4, 6, 8, 12, 18, 24]
|
||||
hour_factors = [1.] * len(hour_steps_)
|
||||
|
||||
minsec_limits_ = [1.5, 2.5, 3.5, 4.5, 5.5, 8, 11, 14, 18, 25, 45]
|
||||
minsec_steps_ = [1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30]
|
||||
|
||||
minute_limits_ = np.array(minsec_limits_) / 60
|
||||
minute_factors = [60.] * len(minute_limits_)
|
||||
|
||||
second_limits_ = np.array(minsec_limits_) / 3600
|
||||
second_factors = [3600.] * len(second_limits_)
|
||||
|
||||
hour_limits = [*second_limits_, *minute_limits_, *hour_limits_]
|
||||
hour_steps = [*minsec_steps_, *minsec_steps_, *hour_steps_]
|
||||
hour_factors = [*second_factors, *minute_factors, *hour_factors]
|
||||
|
||||
n = np.searchsorted(hour_limits, dv)
|
||||
step = hour_steps[n]
|
||||
factor = hour_factors[n]
|
||||
|
||||
return step, factor
|
||||
|
||||
|
||||
def select_step_sub(dv):
|
||||
|
||||
# subarcsec or degree
|
||||
tmp = 10.**(int(math.log10(dv))-1.)
|
||||
|
||||
factor = 1./tmp
|
||||
|
||||
if 1.5*tmp >= dv:
|
||||
step = 1
|
||||
elif 3.*tmp >= dv:
|
||||
step = 2
|
||||
elif 7.*tmp >= dv:
|
||||
step = 5
|
||||
else:
|
||||
step = 1
|
||||
factor = 0.1*factor
|
||||
|
||||
return step, factor
|
||||
|
||||
|
||||
def select_step(v1, v2, nv, hour=False, include_last=True,
|
||||
threshold_factor=3600.):
|
||||
|
||||
if v1 > v2:
|
||||
v1, v2 = v2, v1
|
||||
|
||||
dv = (v2 - v1) / nv
|
||||
|
||||
if hour:
|
||||
_select_step = select_step_hour
|
||||
cycle = 24.
|
||||
else:
|
||||
_select_step = select_step_degree
|
||||
cycle = 360.
|
||||
|
||||
# for degree
|
||||
if dv > 1 / threshold_factor:
|
||||
step, factor = _select_step(dv)
|
||||
else:
|
||||
step, factor = select_step_sub(dv*threshold_factor)
|
||||
|
||||
factor = factor * threshold_factor
|
||||
|
||||
levs = np.arange(np.floor(v1 * factor / step),
|
||||
np.ceil(v2 * factor / step) + 0.5,
|
||||
dtype=int) * step
|
||||
|
||||
# n : number of valid levels. If there is a cycle, e.g., [0, 90, 180,
|
||||
# 270, 360], the grid line needs to be extended from 0 to 360, so
|
||||
# we need to return the whole array. However, the last level (360)
|
||||
# needs to be ignored often. In this case, so we return n=4.
|
||||
|
||||
n = len(levs)
|
||||
|
||||
# we need to check the range of values
|
||||
# for example, -90 to 90, 0 to 360,
|
||||
|
||||
if factor == 1. and levs[-1] >= levs[0] + cycle: # check for cycle
|
||||
nv = int(cycle / step)
|
||||
if include_last:
|
||||
levs = levs[0] + np.arange(0, nv+1, 1) * step
|
||||
else:
|
||||
levs = levs[0] + np.arange(0, nv, 1) * step
|
||||
|
||||
n = len(levs)
|
||||
|
||||
return np.array(levs), n, factor
|
||||
|
||||
|
||||
def select_step24(v1, v2, nv, include_last=True, threshold_factor=3600):
|
||||
v1, v2 = v1 / 15, v2 / 15
|
||||
levs, n, factor = select_step(v1, v2, nv, hour=True,
|
||||
include_last=include_last,
|
||||
threshold_factor=threshold_factor)
|
||||
return levs * 15, n, factor
|
||||
|
||||
|
||||
def select_step360(v1, v2, nv, include_last=True, threshold_factor=3600):
|
||||
return select_step(v1, v2, nv, hour=False,
|
||||
include_last=include_last,
|
||||
threshold_factor=threshold_factor)
|
||||
|
||||
|
||||
class LocatorBase:
|
||||
@cbook._rename_parameter("3.3", "den", "nbins")
|
||||
def __init__(self, nbins, include_last=True):
|
||||
self.nbins = nbins
|
||||
self._include_last = include_last
|
||||
|
||||
@cbook.deprecated("3.3", alternative="nbins")
|
||||
@property
|
||||
def den(self):
|
||||
return self.nbins
|
||||
|
||||
@den.setter
|
||||
def den(self, v):
|
||||
self.nbins = v
|
||||
|
||||
def set_params(self, nbins=None):
|
||||
if nbins is not None:
|
||||
self.nbins = int(nbins)
|
||||
|
||||
|
||||
class LocatorHMS(LocatorBase):
|
||||
def __call__(self, v1, v2):
|
||||
return select_step24(v1, v2, self.nbins, self._include_last)
|
||||
|
||||
|
||||
class LocatorHM(LocatorBase):
|
||||
def __call__(self, v1, v2):
|
||||
return select_step24(v1, v2, self.nbins, self._include_last,
|
||||
threshold_factor=60)
|
||||
|
||||
|
||||
class LocatorH(LocatorBase):
|
||||
def __call__(self, v1, v2):
|
||||
return select_step24(v1, v2, self.nbins, self._include_last,
|
||||
threshold_factor=1)
|
||||
|
||||
|
||||
class LocatorDMS(LocatorBase):
|
||||
def __call__(self, v1, v2):
|
||||
return select_step360(v1, v2, self.nbins, self._include_last)
|
||||
|
||||
|
||||
class LocatorDM(LocatorBase):
|
||||
def __call__(self, v1, v2):
|
||||
return select_step360(v1, v2, self.nbins, self._include_last,
|
||||
threshold_factor=60)
|
||||
|
||||
|
||||
class LocatorD(LocatorBase):
|
||||
def __call__(self, v1, v2):
|
||||
return select_step360(v1, v2, self.nbins, self._include_last,
|
||||
threshold_factor=1)
|
||||
|
||||
|
||||
class FormatterDMS:
|
||||
deg_mark = r"^{\circ}"
|
||||
min_mark = r"^{\prime}"
|
||||
sec_mark = r"^{\prime\prime}"
|
||||
|
||||
fmt_d = "$%d" + deg_mark + "$"
|
||||
fmt_ds = r"$%d.%s" + deg_mark + "$"
|
||||
|
||||
# %s for sign
|
||||
fmt_d_m = r"$%s%d" + deg_mark + r"\,%02d" + min_mark + "$"
|
||||
fmt_d_ms = r"$%s%d" + deg_mark + r"\,%02d.%s" + min_mark + "$"
|
||||
|
||||
fmt_d_m_partial = "$%s%d" + deg_mark + r"\,%02d" + min_mark + r"\,"
|
||||
fmt_s_partial = "%02d" + sec_mark + "$"
|
||||
fmt_ss_partial = "%02d.%s" + sec_mark + "$"
|
||||
|
||||
def _get_number_fraction(self, factor):
|
||||
## check for fractional numbers
|
||||
number_fraction = None
|
||||
# check for 60
|
||||
|
||||
for threshold in [1, 60, 3600]:
|
||||
if factor <= threshold:
|
||||
break
|
||||
|
||||
d = factor // threshold
|
||||
int_log_d = int(np.floor(np.log10(d)))
|
||||
if 10**int_log_d == d and d != 1:
|
||||
number_fraction = int_log_d
|
||||
factor = factor // 10**int_log_d
|
||||
return factor, number_fraction
|
||||
|
||||
return factor, number_fraction
|
||||
|
||||
def __call__(self, direction, factor, values):
|
||||
if len(values) == 0:
|
||||
return []
|
||||
|
||||
ss = np.sign(values)
|
||||
signs = ["-" if v < 0 else "" for v in values]
|
||||
|
||||
factor, number_fraction = self._get_number_fraction(factor)
|
||||
|
||||
values = np.abs(values)
|
||||
|
||||
if number_fraction is not None:
|
||||
values, frac_part = divmod(values, 10 ** number_fraction)
|
||||
frac_fmt = "%%0%dd" % (number_fraction,)
|
||||
frac_str = [frac_fmt % (f1,) for f1 in frac_part]
|
||||
|
||||
if factor == 1:
|
||||
if number_fraction is None:
|
||||
return [self.fmt_d % (s * int(v),) for s, v in zip(ss, values)]
|
||||
else:
|
||||
return [self.fmt_ds % (s * int(v), f1)
|
||||
for s, v, f1 in zip(ss, values, frac_str)]
|
||||
elif factor == 60:
|
||||
deg_part, min_part = divmod(values, 60)
|
||||
if number_fraction is None:
|
||||
return [self.fmt_d_m % (s1, d1, m1)
|
||||
for s1, d1, m1 in zip(signs, deg_part, min_part)]
|
||||
else:
|
||||
return [self.fmt_d_ms % (s, d1, m1, f1)
|
||||
for s, d1, m1, f1
|
||||
in zip(signs, deg_part, min_part, frac_str)]
|
||||
|
||||
elif factor == 3600:
|
||||
if ss[-1] == -1:
|
||||
inverse_order = True
|
||||
values = values[::-1]
|
||||
signs = signs[::-1]
|
||||
else:
|
||||
inverse_order = False
|
||||
|
||||
l_hm_old = ""
|
||||
r = []
|
||||
|
||||
deg_part, min_part_ = divmod(values, 3600)
|
||||
min_part, sec_part = divmod(min_part_, 60)
|
||||
|
||||
if number_fraction is None:
|
||||
sec_str = [self.fmt_s_partial % (s1,) for s1 in sec_part]
|
||||
else:
|
||||
sec_str = [self.fmt_ss_partial % (s1, f1)
|
||||
for s1, f1 in zip(sec_part, frac_str)]
|
||||
|
||||
for s, d1, m1, s1 in zip(signs, deg_part, min_part, sec_str):
|
||||
l_hm = self.fmt_d_m_partial % (s, d1, m1)
|
||||
if l_hm != l_hm_old:
|
||||
l_hm_old = l_hm
|
||||
l = l_hm + s1
|
||||
else:
|
||||
l = "$" + s + s1
|
||||
r.append(l)
|
||||
|
||||
if inverse_order:
|
||||
return r[::-1]
|
||||
else:
|
||||
return r
|
||||
|
||||
else: # factor > 3600.
|
||||
return [r"$%s^{\circ}$" % (str(v),) for v in ss*values]
|
||||
|
||||
|
||||
class FormatterHMS(FormatterDMS):
|
||||
deg_mark = r"^\mathrm{h}"
|
||||
min_mark = r"^\mathrm{m}"
|
||||
sec_mark = r"^\mathrm{s}"
|
||||
|
||||
fmt_d = "$%d" + deg_mark + "$"
|
||||
fmt_ds = r"$%d.%s" + deg_mark + "$"
|
||||
|
||||
# %s for sign
|
||||
fmt_d_m = r"$%s%d" + deg_mark + r"\,%02d" + min_mark+"$"
|
||||
fmt_d_ms = r"$%s%d" + deg_mark + r"\,%02d.%s" + min_mark+"$"
|
||||
|
||||
fmt_d_m_partial = "$%s%d" + deg_mark + r"\,%02d" + min_mark + r"\,"
|
||||
fmt_s_partial = "%02d" + sec_mark + "$"
|
||||
fmt_ss_partial = "%02d.%s" + sec_mark + "$"
|
||||
|
||||
def __call__(self, direction, factor, values): # hour
|
||||
return super().__call__(direction, factor, np.asarray(values) / 15)
|
||||
|
||||
|
||||
class ExtremeFinderCycle(ExtremeFinderSimple):
|
||||
# docstring inherited
|
||||
|
||||
def __init__(self, nx, ny,
|
||||
lon_cycle=360., lat_cycle=None,
|
||||
lon_minmax=None, lat_minmax=(-90, 90)):
|
||||
"""
|
||||
This subclass handles the case where one or both coordinates should be
|
||||
taken modulo 360, or be restricted to not exceed a specific range.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
nx, ny : int
|
||||
The number of samples in each direction.
|
||||
|
||||
lon_cycle, lat_cycle : 360 or None
|
||||
If not None, values in the corresponding direction are taken modulo
|
||||
*lon_cycle* or *lat_cycle*; in theory this can be any number but
|
||||
the implementation actually assumes that it is 360 (if not None);
|
||||
other values give nonsensical results.
|
||||
|
||||
This is done by "unwrapping" the transformed grid coordinates so
|
||||
that jumps are less than a half-cycle; then normalizing the span to
|
||||
no more than a full cycle.
|
||||
|
||||
For example, if values are in the union of the [0, 2] and
|
||||
[358, 360] intervals (typically, angles measured modulo 360), the
|
||||
values in the second interval are normalized to [-2, 0] instead so
|
||||
that the values now cover [-2, 2]. If values are in a range of
|
||||
[5, 1000], this gets normalized to [5, 365].
|
||||
|
||||
lon_minmax, lat_minmax : (float, float) or None
|
||||
If not None, the computed bounding box is clipped to the given
|
||||
range in the corresponding direction.
|
||||
"""
|
||||
self.nx, self.ny = nx, ny
|
||||
self.lon_cycle, self.lat_cycle = lon_cycle, lat_cycle
|
||||
self.lon_minmax = lon_minmax
|
||||
self.lat_minmax = lat_minmax
|
||||
|
||||
def __call__(self, transform_xy, x1, y1, x2, y2):
|
||||
# docstring inherited
|
||||
x, y = np.meshgrid(
|
||||
np.linspace(x1, x2, self.nx), np.linspace(y1, y2, self.ny))
|
||||
lon, lat = transform_xy(np.ravel(x), np.ravel(y))
|
||||
|
||||
# iron out jumps, but algorithm should be improved.
|
||||
# This is just naive way of doing and my fail for some cases.
|
||||
# Consider replacing this with numpy.unwrap
|
||||
# We are ignoring invalid warnings. They are triggered when
|
||||
# comparing arrays with NaNs using > We are already handling
|
||||
# that correctly using np.nanmin and np.nanmax
|
||||
with np.errstate(invalid='ignore'):
|
||||
if self.lon_cycle is not None:
|
||||
lon0 = np.nanmin(lon)
|
||||
lon -= 360. * ((lon - lon0) > 180.)
|
||||
if self.lat_cycle is not None:
|
||||
lat0 = np.nanmin(lat)
|
||||
lat -= 360. * ((lat - lat0) > 180.)
|
||||
|
||||
lon_min, lon_max = np.nanmin(lon), np.nanmax(lon)
|
||||
lat_min, lat_max = np.nanmin(lat), np.nanmax(lat)
|
||||
|
||||
lon_min, lon_max, lat_min, lat_max = \
|
||||
self._add_pad(lon_min, lon_max, lat_min, lat_max)
|
||||
|
||||
# check cycle
|
||||
if self.lon_cycle:
|
||||
lon_max = min(lon_max, lon_min + self.lon_cycle)
|
||||
if self.lat_cycle:
|
||||
lat_max = min(lat_max, lat_min + self.lat_cycle)
|
||||
|
||||
if self.lon_minmax is not None:
|
||||
min0 = self.lon_minmax[0]
|
||||
lon_min = max(min0, lon_min)
|
||||
max0 = self.lon_minmax[1]
|
||||
lon_max = min(max0, lon_max)
|
||||
|
||||
if self.lat_minmax is not None:
|
||||
min0 = self.lat_minmax[0]
|
||||
lat_min = max(min0, lat_min)
|
||||
max0 = self.lat_minmax[1]
|
||||
lat_max = min(max0, lat_max)
|
||||
|
||||
return lon_min, lon_max, lat_min, lat_max
|
|
@ -0,0 +1,2 @@
|
|||
from mpl_toolkits.axes_grid1.axes_divider import (
|
||||
Divider, AxesLocator, SubplotDivider, AxesDivider, make_axes_locatable)
|
18
venv/Lib/site-packages/mpl_toolkits/axisartist/axes_grid.py
Normal file
18
venv/Lib/site-packages/mpl_toolkits/axisartist/axes_grid.py
Normal file
|
@ -0,0 +1,18 @@
|
|||
import mpl_toolkits.axes_grid1.axes_grid as axes_grid_orig
|
||||
from .axislines import Axes
|
||||
|
||||
|
||||
class CbarAxes(axes_grid_orig.CbarAxesBase, Axes):
|
||||
pass
|
||||
|
||||
|
||||
class Grid(axes_grid_orig.Grid):
|
||||
_defaultAxesClass = Axes
|
||||
|
||||
|
||||
class ImageGrid(axes_grid_orig.ImageGrid):
|
||||
_defaultAxesClass = Axes
|
||||
_defaultCbarAxesClass = CbarAxes
|
||||
|
||||
|
||||
AxesGrid = ImageGrid
|
|
@ -0,0 +1,7 @@
|
|||
from mpl_toolkits.axes_grid1.axes_rgb import (
|
||||
make_rgb_axes, imshow_rgb, RGBAxes as _RGBAxes)
|
||||
from .axislines import Axes
|
||||
|
||||
|
||||
class RGBAxes(_RGBAxes):
|
||||
_defaultAxesClass = Axes
|
1229
venv/Lib/site-packages/mpl_toolkits/axisartist/axis_artist.py
Normal file
1229
venv/Lib/site-packages/mpl_toolkits/axisartist/axis_artist.py
Normal file
File diff suppressed because it is too large
Load diff
153
venv/Lib/site-packages/mpl_toolkits/axisartist/axisline_style.py
Normal file
153
venv/Lib/site-packages/mpl_toolkits/axisartist/axisline_style.py
Normal file
|
@ -0,0 +1,153 @@
|
|||
from matplotlib.patches import _Style, FancyArrowPatch
|
||||
from matplotlib.transforms import IdentityTransform
|
||||
from matplotlib.path import Path
|
||||
import numpy as np
|
||||
|
||||
|
||||
class _FancyAxislineStyle:
|
||||
class SimpleArrow(FancyArrowPatch):
|
||||
"""
|
||||
The artist class that will be returned for SimpleArrow style.
|
||||
"""
|
||||
_ARROW_STYLE = "->"
|
||||
|
||||
def __init__(self, axis_artist, line_path, transform,
|
||||
line_mutation_scale):
|
||||
self._axis_artist = axis_artist
|
||||
self._line_transform = transform
|
||||
self._line_path = line_path
|
||||
self._line_mutation_scale = line_mutation_scale
|
||||
|
||||
FancyArrowPatch.__init__(self,
|
||||
path=self._line_path,
|
||||
arrowstyle=self._ARROW_STYLE,
|
||||
patchA=None,
|
||||
patchB=None,
|
||||
shrinkA=0.,
|
||||
shrinkB=0.,
|
||||
mutation_scale=line_mutation_scale,
|
||||
mutation_aspect=None,
|
||||
transform=IdentityTransform(),
|
||||
)
|
||||
|
||||
def set_line_mutation_scale(self, scale):
|
||||
self.set_mutation_scale(scale*self._line_mutation_scale)
|
||||
|
||||
def _extend_path(self, path, mutation_size=10):
|
||||
"""
|
||||
Extend the path to make a room for drawing arrow.
|
||||
"""
|
||||
from matplotlib.bezier import get_cos_sin
|
||||
|
||||
x0, y0 = path.vertices[-2]
|
||||
x1, y1 = path.vertices[-1]
|
||||
cost, sint = get_cos_sin(x0, y0, x1, y1)
|
||||
|
||||
d = mutation_size * 1.
|
||||
x2, y2 = x1 + cost*d, y1+sint*d
|
||||
|
||||
if path.codes is None:
|
||||
_path = Path(np.concatenate([path.vertices, [[x2, y2]]]))
|
||||
else:
|
||||
_path = Path(np.concatenate([path.vertices, [[x2, y2]]]),
|
||||
np.concatenate([path.codes, [Path.LINETO]]))
|
||||
|
||||
return _path
|
||||
|
||||
def set_path(self, path):
|
||||
self._line_path = path
|
||||
|
||||
def draw(self, renderer):
|
||||
"""
|
||||
Draw the axis line.
|
||||
1) transform the path to the display coordinate.
|
||||
2) extend the path to make a room for arrow
|
||||
3) update the path of the FancyArrowPatch.
|
||||
4) draw
|
||||
"""
|
||||
path_in_disp = self._line_transform.transform_path(self._line_path)
|
||||
mutation_size = self.get_mutation_scale() # line_mutation_scale()
|
||||
extended_path = self._extend_path(path_in_disp,
|
||||
mutation_size=mutation_size)
|
||||
self._path_original = extended_path
|
||||
FancyArrowPatch.draw(self, renderer)
|
||||
|
||||
class FilledArrow(SimpleArrow):
|
||||
"""
|
||||
The artist class that will be returned for SimpleArrow style.
|
||||
"""
|
||||
_ARROW_STYLE = "-|>"
|
||||
|
||||
|
||||
class AxislineStyle(_Style):
|
||||
"""
|
||||
A container class which defines style classes for AxisArtists.
|
||||
|
||||
An instance of any axisline style class is an callable object,
|
||||
whose call signature is ::
|
||||
|
||||
__call__(self, axis_artist, path, transform)
|
||||
|
||||
When called, this should return an `.Artist` with the following methods::
|
||||
|
||||
def set_path(self, path):
|
||||
# set the path for axisline.
|
||||
|
||||
def set_line_mutation_scale(self, scale):
|
||||
# set the scale
|
||||
|
||||
def draw(self, renderer):
|
||||
# draw
|
||||
"""
|
||||
|
||||
_style_list = {}
|
||||
|
||||
class _Base:
|
||||
# The derived classes are required to be able to be initialized
|
||||
# w/o arguments, i.e., all its argument (except self) must have
|
||||
# the default values.
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
initialization.
|
||||
"""
|
||||
super().__init__()
|
||||
|
||||
def __call__(self, axis_artist, transform):
|
||||
"""
|
||||
Given the AxisArtist instance, and transform for the path (set_path
|
||||
method), return the Matplotlib artist for drawing the axis line.
|
||||
"""
|
||||
return self.new_line(axis_artist, transform)
|
||||
|
||||
class SimpleArrow(_Base):
|
||||
"""
|
||||
A simple arrow.
|
||||
"""
|
||||
|
||||
ArrowAxisClass = _FancyAxislineStyle.SimpleArrow
|
||||
|
||||
def __init__(self, size=1):
|
||||
"""
|
||||
Parameters
|
||||
----------
|
||||
size : float
|
||||
Size of the arrow as a fraction of the ticklabel size.
|
||||
"""
|
||||
|
||||
self.size = size
|
||||
super().__init__()
|
||||
|
||||
def new_line(self, axis_artist, transform):
|
||||
|
||||
linepath = Path([(0, 0), (0, 1)])
|
||||
axisline = self.ArrowAxisClass(axis_artist, linepath, transform,
|
||||
line_mutation_scale=self.size)
|
||||
return axisline
|
||||
|
||||
_style_list["->"] = SimpleArrow
|
||||
|
||||
class FilledArrow(SimpleArrow):
|
||||
ArrowAxisClass = _FancyAxislineStyle.FilledArrow
|
||||
|
||||
_style_list["-|>"] = FilledArrow
|
607
venv/Lib/site-packages/mpl_toolkits/axisartist/axislines.py
Normal file
607
venv/Lib/site-packages/mpl_toolkits/axisartist/axislines.py
Normal file
|
@ -0,0 +1,607 @@
|
|||
"""
|
||||
Axislines includes modified implementation of the Axes class. The
|
||||
biggest difference is that the artists responsible for drawing the axis spine,
|
||||
ticks, ticklabels and axis labels are separated out from Matplotlib's Axis
|
||||
class. Originally, this change was motivated to support curvilinear
|
||||
grid. Here are a few reasons that I came up with a new axes class:
|
||||
|
||||
* "top" and "bottom" x-axis (or "left" and "right" y-axis) can have
|
||||
different ticks (tick locations and labels). This is not possible
|
||||
with the current Matplotlib, although some twin axes trick can help.
|
||||
|
||||
* Curvilinear grid.
|
||||
|
||||
* angled ticks.
|
||||
|
||||
In the new axes class, xaxis and yaxis is set to not visible by
|
||||
default, and new set of artist (AxisArtist) are defined to draw axis
|
||||
line, ticks, ticklabels and axis label. Axes.axis attribute serves as
|
||||
a dictionary of these artists, i.e., ax.axis["left"] is a AxisArtist
|
||||
instance responsible to draw left y-axis. The default Axes.axis contains
|
||||
"bottom", "left", "top" and "right".
|
||||
|
||||
AxisArtist can be considered as a container artist and
|
||||
has following children artists which will draw ticks, labels, etc.
|
||||
|
||||
* line
|
||||
* major_ticks, major_ticklabels
|
||||
* minor_ticks, minor_ticklabels
|
||||
* offsetText
|
||||
* label
|
||||
|
||||
Note that these are separate artists from `matplotlib.axis.Axis`, thus most
|
||||
tick-related functions in Matplotlib won't work. For example, color and
|
||||
markerwidth of the ``ax.axis["bottom"].major_ticks`` will follow those of
|
||||
Axes.xaxis unless explicitly specified.
|
||||
|
||||
In addition to AxisArtist, the Axes will have *gridlines* attribute,
|
||||
which obviously draws grid lines. The gridlines needs to be separated
|
||||
from the axis as some gridlines can never pass any axis.
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
|
||||
from matplotlib import cbook, rcParams
|
||||
import matplotlib.axes as maxes
|
||||
from matplotlib.path import Path
|
||||
from mpl_toolkits.axes_grid1 import mpl_axes
|
||||
from .axisline_style import AxislineStyle
|
||||
from .axis_artist import AxisArtist, GridlinesCollection
|
||||
|
||||
|
||||
class AxisArtistHelper:
|
||||
"""
|
||||
AxisArtistHelper should define
|
||||
following method with given APIs. Note that the first axes argument
|
||||
will be axes attribute of the caller artist.::
|
||||
|
||||
|
||||
# LINE (spinal line?)
|
||||
|
||||
def get_line(self, axes):
|
||||
# path : Path
|
||||
return path
|
||||
|
||||
def get_line_transform(self, axes):
|
||||
# ...
|
||||
# trans : transform
|
||||
return trans
|
||||
|
||||
# LABEL
|
||||
|
||||
def get_label_pos(self, axes):
|
||||
# x, y : position
|
||||
return (x, y), trans
|
||||
|
||||
|
||||
def get_label_offset_transform(self,
|
||||
axes,
|
||||
pad_points, fontprops, renderer,
|
||||
bboxes,
|
||||
):
|
||||
# va : vertical alignment
|
||||
# ha : horizontal alignment
|
||||
# a : angle
|
||||
return trans, va, ha, a
|
||||
|
||||
# TICK
|
||||
|
||||
def get_tick_transform(self, axes):
|
||||
return trans
|
||||
|
||||
def get_tick_iterators(self, axes):
|
||||
# iter : iterable object that yields (c, angle, l) where
|
||||
# c, angle, l is position, tick angle, and label
|
||||
|
||||
return iter_major, iter_minor
|
||||
"""
|
||||
|
||||
class _Base:
|
||||
"""Base class for axis helper."""
|
||||
def __init__(self):
|
||||
self.delta1, self.delta2 = 0.00001, 0.00001
|
||||
|
||||
def update_lim(self, axes):
|
||||
pass
|
||||
|
||||
class Fixed(_Base):
|
||||
"""Helper class for a fixed (in the axes coordinate) axis."""
|
||||
|
||||
_default_passthru_pt = dict(left=(0, 0),
|
||||
right=(1, 0),
|
||||
bottom=(0, 0),
|
||||
top=(0, 1))
|
||||
|
||||
def __init__(self, loc, nth_coord=None):
|
||||
"""
|
||||
nth_coord = along which coordinate value varies
|
||||
in 2d, nth_coord = 0 -> x axis, nth_coord = 1 -> y axis
|
||||
"""
|
||||
cbook._check_in_list(["left", "right", "bottom", "top"], loc=loc)
|
||||
self._loc = loc
|
||||
|
||||
if nth_coord is None:
|
||||
if loc in ["left", "right"]:
|
||||
nth_coord = 1
|
||||
elif loc in ["bottom", "top"]:
|
||||
nth_coord = 0
|
||||
|
||||
self.nth_coord = nth_coord
|
||||
|
||||
super().__init__()
|
||||
|
||||
self.passthru_pt = self._default_passthru_pt[loc]
|
||||
|
||||
_verts = np.array([[0., 0.],
|
||||
[1., 1.]])
|
||||
fixed_coord = 1 - nth_coord
|
||||
_verts[:, fixed_coord] = self.passthru_pt[fixed_coord]
|
||||
|
||||
# axis line in transAxes
|
||||
self._path = Path(_verts)
|
||||
|
||||
def get_nth_coord(self):
|
||||
return self.nth_coord
|
||||
|
||||
# LINE
|
||||
|
||||
def get_line(self, axes):
|
||||
return self._path
|
||||
|
||||
def get_line_transform(self, axes):
|
||||
return axes.transAxes
|
||||
|
||||
# LABEL
|
||||
|
||||
def get_axislabel_transform(self, axes):
|
||||
return axes.transAxes
|
||||
|
||||
def get_axislabel_pos_angle(self, axes):
|
||||
"""
|
||||
Return the label reference position in transAxes.
|
||||
|
||||
get_label_transform() returns a transform of (transAxes+offset)
|
||||
"""
|
||||
return dict(left=((0., 0.5), 90), # (position, angle_tangent)
|
||||
right=((1., 0.5), 90),
|
||||
bottom=((0.5, 0.), 0),
|
||||
top=((0.5, 1.), 0))[self._loc]
|
||||
|
||||
# TICK
|
||||
|
||||
def get_tick_transform(self, axes):
|
||||
return [axes.get_xaxis_transform(),
|
||||
axes.get_yaxis_transform()][self.nth_coord]
|
||||
|
||||
class Floating(_Base):
|
||||
|
||||
def __init__(self, nth_coord, value):
|
||||
self.nth_coord = nth_coord
|
||||
self._value = value
|
||||
super().__init__()
|
||||
|
||||
def get_nth_coord(self):
|
||||
return self.nth_coord
|
||||
|
||||
def get_line(self, axes):
|
||||
raise RuntimeError(
|
||||
"get_line method should be defined by the derived class")
|
||||
|
||||
|
||||
class AxisArtistHelperRectlinear:
|
||||
|
||||
class Fixed(AxisArtistHelper.Fixed):
|
||||
|
||||
def __init__(self, axes, loc, nth_coord=None):
|
||||
"""
|
||||
nth_coord = along which coordinate value varies
|
||||
in 2d, nth_coord = 0 -> x axis, nth_coord = 1 -> y axis
|
||||
"""
|
||||
super().__init__(loc, nth_coord)
|
||||
self.axis = [axes.xaxis, axes.yaxis][self.nth_coord]
|
||||
|
||||
# TICK
|
||||
|
||||
def get_tick_iterators(self, axes):
|
||||
"""tick_loc, tick_angle, tick_label"""
|
||||
|
||||
loc = self._loc
|
||||
|
||||
if loc in ["bottom", "top"]:
|
||||
angle_normal, angle_tangent = 90, 0
|
||||
else:
|
||||
angle_normal, angle_tangent = 0, 90
|
||||
|
||||
major = self.axis.major
|
||||
majorLocs = major.locator()
|
||||
majorLabels = major.formatter.format_ticks(majorLocs)
|
||||
|
||||
minor = self.axis.minor
|
||||
minorLocs = minor.locator()
|
||||
minorLabels = minor.formatter.format_ticks(minorLocs)
|
||||
|
||||
tick_to_axes = self.get_tick_transform(axes) - axes.transAxes
|
||||
|
||||
def _f(locs, labels):
|
||||
for x, l in zip(locs, labels):
|
||||
|
||||
c = list(self.passthru_pt) # copy
|
||||
c[self.nth_coord] = x
|
||||
|
||||
# check if the tick point is inside axes
|
||||
c2 = tick_to_axes.transform(c)
|
||||
if (0 - self.delta1
|
||||
<= c2[self.nth_coord]
|
||||
<= 1 + self.delta2):
|
||||
yield c, angle_normal, angle_tangent, l
|
||||
|
||||
return _f(majorLocs, majorLabels), _f(minorLocs, minorLabels)
|
||||
|
||||
class Floating(AxisArtistHelper.Floating):
|
||||
def __init__(self, axes, nth_coord,
|
||||
passingthrough_point, axis_direction="bottom"):
|
||||
super().__init__(nth_coord, passingthrough_point)
|
||||
self._axis_direction = axis_direction
|
||||
self.axis = [axes.xaxis, axes.yaxis][self.nth_coord]
|
||||
|
||||
def get_line(self, axes):
|
||||
_verts = np.array([[0., 0.],
|
||||
[1., 1.]])
|
||||
|
||||
fixed_coord = 1 - self.nth_coord
|
||||
data_to_axes = axes.transData - axes.transAxes
|
||||
p = data_to_axes.transform([self._value, self._value])
|
||||
_verts[:, fixed_coord] = p[fixed_coord]
|
||||
|
||||
return Path(_verts)
|
||||
|
||||
def get_line_transform(self, axes):
|
||||
return axes.transAxes
|
||||
|
||||
def get_axislabel_transform(self, axes):
|
||||
return axes.transAxes
|
||||
|
||||
def get_axislabel_pos_angle(self, axes):
|
||||
"""
|
||||
Return the label reference position in transAxes.
|
||||
|
||||
get_label_transform() returns a transform of (transAxes+offset)
|
||||
"""
|
||||
angle = [0, 90][self.nth_coord]
|
||||
_verts = [0.5, 0.5]
|
||||
fixed_coord = 1 - self.nth_coord
|
||||
data_to_axes = axes.transData - axes.transAxes
|
||||
p = data_to_axes.transform([self._value, self._value])
|
||||
_verts[fixed_coord] = p[fixed_coord]
|
||||
if 0 <= _verts[fixed_coord] <= 1:
|
||||
return _verts, angle
|
||||
else:
|
||||
return None, None
|
||||
|
||||
def get_tick_transform(self, axes):
|
||||
return axes.transData
|
||||
|
||||
def get_tick_iterators(self, axes):
|
||||
"""tick_loc, tick_angle, tick_label"""
|
||||
if self.nth_coord == 0:
|
||||
angle_normal, angle_tangent = 90, 0
|
||||
else:
|
||||
angle_normal, angle_tangent = 0, 90
|
||||
|
||||
major = self.axis.major
|
||||
majorLocs = major.locator()
|
||||
majorLabels = major.formatter.format_ticks(majorLocs)
|
||||
|
||||
minor = self.axis.minor
|
||||
minorLocs = minor.locator()
|
||||
minorLabels = minor.formatter.format_ticks(minorLocs)
|
||||
|
||||
data_to_axes = axes.transData - axes.transAxes
|
||||
|
||||
def _f(locs, labels):
|
||||
for x, l in zip(locs, labels):
|
||||
c = [self._value, self._value]
|
||||
c[self.nth_coord] = x
|
||||
c1, c2 = data_to_axes.transform(c)
|
||||
if (0 <= c1 <= 1 and 0 <= c2 <= 1
|
||||
and 0 - self.delta1
|
||||
<= [c1, c2][self.nth_coord]
|
||||
<= 1 + self.delta2):
|
||||
yield c, angle_normal, angle_tangent, l
|
||||
|
||||
return _f(majorLocs, majorLabels), _f(minorLocs, minorLabels)
|
||||
|
||||
|
||||
class GridHelperBase:
|
||||
|
||||
def __init__(self):
|
||||
self._force_update = True
|
||||
self._old_limits = None
|
||||
super().__init__()
|
||||
|
||||
def update_lim(self, axes):
|
||||
x1, x2 = axes.get_xlim()
|
||||
y1, y2 = axes.get_ylim()
|
||||
|
||||
if self._force_update or self._old_limits != (x1, x2, y1, y2):
|
||||
self._update(x1, x2, y1, y2)
|
||||
self._force_update = False
|
||||
self._old_limits = (x1, x2, y1, y2)
|
||||
|
||||
def _update(self, x1, x2, y1, y2):
|
||||
pass
|
||||
|
||||
def invalidate(self):
|
||||
self._force_update = True
|
||||
|
||||
def valid(self):
|
||||
return not self._force_update
|
||||
|
||||
def get_gridlines(self, which, axis):
|
||||
"""
|
||||
Return list of grid lines as a list of paths (list of points).
|
||||
|
||||
*which* : "major" or "minor"
|
||||
*axis* : "both", "x" or "y"
|
||||
"""
|
||||
return []
|
||||
|
||||
def new_gridlines(self, ax):
|
||||
"""
|
||||
Create and return a new GridlineCollection instance.
|
||||
|
||||
*which* : "major" or "minor"
|
||||
*axis* : "both", "x" or "y"
|
||||
|
||||
"""
|
||||
gridlines = GridlinesCollection(None, transform=ax.transData,
|
||||
colors=rcParams['grid.color'],
|
||||
linestyles=rcParams['grid.linestyle'],
|
||||
linewidths=rcParams['grid.linewidth'])
|
||||
ax._set_artist_props(gridlines)
|
||||
gridlines.set_grid_helper(self)
|
||||
|
||||
ax.axes._set_artist_props(gridlines)
|
||||
# gridlines.set_clip_path(self.axes.patch)
|
||||
# set_clip_path need to be deferred after Axes.cla is completed.
|
||||
# It is done inside the cla.
|
||||
|
||||
return gridlines
|
||||
|
||||
|
||||
class GridHelperRectlinear(GridHelperBase):
|
||||
|
||||
def __init__(self, axes):
|
||||
super().__init__()
|
||||
self.axes = axes
|
||||
|
||||
def new_fixed_axis(self, loc,
|
||||
nth_coord=None,
|
||||
axis_direction=None,
|
||||
offset=None,
|
||||
axes=None,
|
||||
):
|
||||
|
||||
if axes is None:
|
||||
cbook._warn_external(
|
||||
"'new_fixed_axis' explicitly requires the axes keyword.")
|
||||
axes = self.axes
|
||||
|
||||
_helper = AxisArtistHelperRectlinear.Fixed(axes, loc, nth_coord)
|
||||
|
||||
if axis_direction is None:
|
||||
axis_direction = loc
|
||||
axisline = AxisArtist(axes, _helper, offset=offset,
|
||||
axis_direction=axis_direction,
|
||||
)
|
||||
|
||||
return axisline
|
||||
|
||||
def new_floating_axis(self, nth_coord, value,
|
||||
axis_direction="bottom",
|
||||
axes=None,
|
||||
):
|
||||
|
||||
if axes is None:
|
||||
cbook._warn_external(
|
||||
"'new_floating_axis' explicitly requires the axes keyword.")
|
||||
axes = self.axes
|
||||
|
||||
_helper = AxisArtistHelperRectlinear.Floating(
|
||||
axes, nth_coord, value, axis_direction)
|
||||
|
||||
axisline = AxisArtist(axes, _helper)
|
||||
|
||||
axisline.line.set_clip_on(True)
|
||||
axisline.line.set_clip_box(axisline.axes.bbox)
|
||||
return axisline
|
||||
|
||||
def get_gridlines(self, which="major", axis="both"):
|
||||
"""
|
||||
Return list of gridline coordinates in data coordinates.
|
||||
|
||||
*which* : "major" or "minor"
|
||||
*axis* : "both", "x" or "y"
|
||||
"""
|
||||
gridlines = []
|
||||
|
||||
if axis in ["both", "x"]:
|
||||
locs = []
|
||||
y1, y2 = self.axes.get_ylim()
|
||||
if which in ["both", "major"]:
|
||||
locs.extend(self.axes.xaxis.major.locator())
|
||||
if which in ["both", "minor"]:
|
||||
locs.extend(self.axes.xaxis.minor.locator())
|
||||
|
||||
for x in locs:
|
||||
gridlines.append([[x, x], [y1, y2]])
|
||||
|
||||
if axis in ["both", "y"]:
|
||||
x1, x2 = self.axes.get_xlim()
|
||||
locs = []
|
||||
if self.axes.yaxis._gridOnMajor:
|
||||
locs.extend(self.axes.yaxis.major.locator())
|
||||
if self.axes.yaxis._gridOnMinor:
|
||||
locs.extend(self.axes.yaxis.minor.locator())
|
||||
|
||||
for y in locs:
|
||||
gridlines.append([[x1, x2], [y, y]])
|
||||
|
||||
return gridlines
|
||||
|
||||
|
||||
class Axes(maxes.Axes):
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
return maxes.Axes.axis(self.axes, *args, **kwargs)
|
||||
|
||||
def __init__(self, *args, grid_helper=None, **kwargs):
|
||||
self._axisline_on = True
|
||||
self._grid_helper = (grid_helper if grid_helper
|
||||
else GridHelperRectlinear(self))
|
||||
super().__init__(*args, **kwargs)
|
||||
self.toggle_axisline(True)
|
||||
|
||||
def toggle_axisline(self, b=None):
|
||||
if b is None:
|
||||
b = not self._axisline_on
|
||||
if b:
|
||||
self._axisline_on = True
|
||||
for s in self.spines.values():
|
||||
s.set_visible(False)
|
||||
self.xaxis.set_visible(False)
|
||||
self.yaxis.set_visible(False)
|
||||
else:
|
||||
self._axisline_on = False
|
||||
for s in self.spines.values():
|
||||
s.set_visible(True)
|
||||
self.xaxis.set_visible(True)
|
||||
self.yaxis.set_visible(True)
|
||||
|
||||
def _init_axis_artists(self, axes=None):
|
||||
if axes is None:
|
||||
axes = self
|
||||
|
||||
self._axislines = mpl_axes.Axes.AxisDict(self)
|
||||
new_fixed_axis = self.get_grid_helper().new_fixed_axis
|
||||
for loc in ["bottom", "top", "left", "right"]:
|
||||
self._axislines[loc] = new_fixed_axis(loc=loc, axes=axes,
|
||||
axis_direction=loc)
|
||||
|
||||
for axisline in [self._axislines["top"], self._axislines["right"]]:
|
||||
axisline.label.set_visible(False)
|
||||
axisline.major_ticklabels.set_visible(False)
|
||||
axisline.minor_ticklabels.set_visible(False)
|
||||
|
||||
@property
|
||||
def axis(self):
|
||||
return self._axislines
|
||||
|
||||
def new_gridlines(self, grid_helper=None):
|
||||
"""
|
||||
Create and return a new GridlineCollection instance.
|
||||
|
||||
*which* : "major" or "minor"
|
||||
*axis* : "both", "x" or "y"
|
||||
|
||||
"""
|
||||
if grid_helper is None:
|
||||
grid_helper = self.get_grid_helper()
|
||||
|
||||
gridlines = grid_helper.new_gridlines(self)
|
||||
return gridlines
|
||||
|
||||
def _init_gridlines(self, grid_helper=None):
|
||||
# It is done inside the cla.
|
||||
self.gridlines = self.new_gridlines(grid_helper)
|
||||
|
||||
def cla(self):
|
||||
# gridlines need to b created before cla() since cla calls grid()
|
||||
self._init_gridlines()
|
||||
super().cla()
|
||||
|
||||
# the clip_path should be set after Axes.cla() since that's
|
||||
# when a patch is created.
|
||||
self.gridlines.set_clip_path(self.axes.patch)
|
||||
|
||||
self._init_axis_artists()
|
||||
|
||||
def get_grid_helper(self):
|
||||
return self._grid_helper
|
||||
|
||||
def grid(self, b=None, which='major', axis="both", **kwargs):
|
||||
"""
|
||||
Toggle the gridlines, and optionally set the properties of the lines.
|
||||
"""
|
||||
# their are some discrepancy between the behavior of grid in
|
||||
# axes_grid and the original mpl's grid, because axes_grid
|
||||
# explicitly set the visibility of the gridlines.
|
||||
super().grid(b, which=which, axis=axis, **kwargs)
|
||||
if not self._axisline_on:
|
||||
return
|
||||
if b is None:
|
||||
b = (self.axes.xaxis._gridOnMinor
|
||||
or self.axes.xaxis._gridOnMajor
|
||||
or self.axes.yaxis._gridOnMinor
|
||||
or self.axes.yaxis._gridOnMajor)
|
||||
self.gridlines.set(which=which, axis=axis, visible=b)
|
||||
self.gridlines.set(**kwargs)
|
||||
|
||||
def get_children(self):
|
||||
if self._axisline_on:
|
||||
children = [*self._axislines.values(), self.gridlines]
|
||||
else:
|
||||
children = []
|
||||
children.extend(super().get_children())
|
||||
return children
|
||||
|
||||
def invalidate_grid_helper(self):
|
||||
self._grid_helper.invalidate()
|
||||
|
||||
def new_fixed_axis(self, loc, offset=None):
|
||||
gh = self.get_grid_helper()
|
||||
axis = gh.new_fixed_axis(loc,
|
||||
nth_coord=None,
|
||||
axis_direction=None,
|
||||
offset=offset,
|
||||
axes=self,
|
||||
)
|
||||
return axis
|
||||
|
||||
def new_floating_axis(self, nth_coord, value, axis_direction="bottom"):
|
||||
gh = self.get_grid_helper()
|
||||
axis = gh.new_floating_axis(nth_coord, value,
|
||||
axis_direction=axis_direction,
|
||||
axes=self)
|
||||
return axis
|
||||
|
||||
|
||||
Subplot = maxes.subplot_class_factory(Axes)
|
||||
|
||||
|
||||
class AxesZero(Axes):
|
||||
|
||||
def _init_axis_artists(self):
|
||||
super()._init_axis_artists()
|
||||
|
||||
new_floating_axis = self._grid_helper.new_floating_axis
|
||||
xaxis_zero = new_floating_axis(nth_coord=0,
|
||||
value=0.,
|
||||
axis_direction="bottom",
|
||||
axes=self)
|
||||
|
||||
xaxis_zero.line.set_clip_path(self.patch)
|
||||
xaxis_zero.set_visible(False)
|
||||
self._axislines["xzero"] = xaxis_zero
|
||||
|
||||
yaxis_zero = new_floating_axis(nth_coord=1,
|
||||
value=0.,
|
||||
axis_direction="left",
|
||||
axes=self)
|
||||
|
||||
yaxis_zero.line.set_clip_path(self.patch)
|
||||
yaxis_zero.set_visible(False)
|
||||
self._axislines["yzero"] = yaxis_zero
|
||||
|
||||
|
||||
SubplotZero = maxes.subplot_class_factory(AxesZero)
|
118
venv/Lib/site-packages/mpl_toolkits/axisartist/clip_path.py
Normal file
118
venv/Lib/site-packages/mpl_toolkits/axisartist/clip_path.py
Normal file
|
@ -0,0 +1,118 @@
|
|||
import numpy as np
|
||||
from math import degrees
|
||||
from matplotlib import cbook
|
||||
import math
|
||||
|
||||
|
||||
def atan2(dy, dx):
|
||||
if dx == 0 and dy == 0:
|
||||
cbook._warn_external("dx and dy are 0")
|
||||
return 0
|
||||
else:
|
||||
return math.atan2(dy, dx)
|
||||
|
||||
|
||||
# FIXME : The current algorithm seems to return incorrect angle when the line
|
||||
# ends at the boundary.
|
||||
def clip(xlines, ylines, x0, clip="right", xdir=True, ydir=True):
|
||||
|
||||
clipped_xlines = []
|
||||
clipped_ylines = []
|
||||
|
||||
_pos_angles = []
|
||||
|
||||
xsign = 1 if xdir else -1
|
||||
ysign = 1 if ydir else -1
|
||||
|
||||
for x, y in zip(xlines, ylines):
|
||||
|
||||
if clip in ["up", "right"]:
|
||||
b = (x < x0).astype("i")
|
||||
db = b[1:] - b[:-1]
|
||||
else:
|
||||
b = (x > x0).astype("i")
|
||||
db = b[1:] - b[:-1]
|
||||
|
||||
if b[0]:
|
||||
ns = 0
|
||||
else:
|
||||
ns = -1
|
||||
segx, segy = [], []
|
||||
for (i,) in np.argwhere(db):
|
||||
c = db[i]
|
||||
if c == -1:
|
||||
dx = (x0 - x[i])
|
||||
dy = (y[i+1] - y[i]) * (dx / (x[i+1] - x[i]))
|
||||
y0 = y[i] + dy
|
||||
clipped_xlines.append(np.concatenate([segx, x[ns:i+1], [x0]]))
|
||||
clipped_ylines.append(np.concatenate([segy, y[ns:i+1], [y0]]))
|
||||
ns = -1
|
||||
segx, segy = [], []
|
||||
|
||||
if dx == 0. and dy == 0:
|
||||
dx = x[i+1] - x[i]
|
||||
dy = y[i+1] - y[i]
|
||||
|
||||
a = degrees(atan2(ysign*dy, xsign*dx))
|
||||
_pos_angles.append((x0, y0, a))
|
||||
|
||||
elif c == 1:
|
||||
dx = (x0 - x[i])
|
||||
dy = (y[i+1] - y[i]) * (dx / (x[i+1] - x[i]))
|
||||
y0 = y[i] + dy
|
||||
segx, segy = [x0], [y0]
|
||||
ns = i+1
|
||||
|
||||
if dx == 0. and dy == 0:
|
||||
dx = x[i+1] - x[i]
|
||||
dy = y[i+1] - y[i]
|
||||
|
||||
a = degrees(atan2(ysign*dy, xsign*dx))
|
||||
_pos_angles.append((x0, y0, a))
|
||||
|
||||
if ns != -1:
|
||||
clipped_xlines.append(np.concatenate([segx, x[ns:]]))
|
||||
clipped_ylines.append(np.concatenate([segy, y[ns:]]))
|
||||
|
||||
return clipped_xlines, clipped_ylines, _pos_angles
|
||||
|
||||
|
||||
def clip_line_to_rect(xline, yline, bbox):
|
||||
|
||||
x0, y0, x1, y1 = bbox.extents
|
||||
|
||||
xdir = x1 > x0
|
||||
ydir = y1 > y0
|
||||
|
||||
if x1 > x0:
|
||||
lx1, ly1, c_right_ = clip([xline], [yline], x1,
|
||||
clip="right", xdir=xdir, ydir=ydir)
|
||||
lx2, ly2, c_left_ = clip(lx1, ly1, x0,
|
||||
clip="left", xdir=xdir, ydir=ydir)
|
||||
else:
|
||||
lx1, ly1, c_right_ = clip([xline], [yline], x0,
|
||||
clip="right", xdir=xdir, ydir=ydir)
|
||||
lx2, ly2, c_left_ = clip(lx1, ly1, x1,
|
||||
clip="left", xdir=xdir, ydir=ydir)
|
||||
|
||||
if y1 > y0:
|
||||
ly3, lx3, c_top_ = clip(ly2, lx2, y1,
|
||||
clip="right", xdir=ydir, ydir=xdir)
|
||||
ly4, lx4, c_bottom_ = clip(ly3, lx3, y0,
|
||||
clip="left", xdir=ydir, ydir=xdir)
|
||||
else:
|
||||
ly3, lx3, c_top_ = clip(ly2, lx2, y0,
|
||||
clip="right", xdir=ydir, ydir=xdir)
|
||||
ly4, lx4, c_bottom_ = clip(ly3, lx3, y1,
|
||||
clip="left", xdir=ydir, ydir=xdir)
|
||||
|
||||
c_left = [((x, y), (a + 90) % 180 - 90) for x, y, a in c_left_
|
||||
if bbox.containsy(y)]
|
||||
c_bottom = [((x, y), (90 - a) % 180) for y, x, a in c_bottom_
|
||||
if bbox.containsx(x)]
|
||||
c_right = [((x, y), (a + 90) % 180 + 90) for x, y, a in c_right_
|
||||
if bbox.containsy(y)]
|
||||
c_top = [((x, y), (90 - a) % 180 + 180) for y, x, a in c_top_
|
||||
if bbox.containsx(x)]
|
||||
|
||||
return list(zip(lx4, ly4)), [c_left, c_bottom, c_right, c_top]
|
372
venv/Lib/site-packages/mpl_toolkits/axisartist/floating_axes.py
Normal file
372
venv/Lib/site-packages/mpl_toolkits/axisartist/floating_axes.py
Normal file
|
@ -0,0 +1,372 @@
|
|||
"""
|
||||
An experimental support for curvilinear grid.
|
||||
"""
|
||||
|
||||
# TODO :
|
||||
# see if tick_iterator method can be simplified by reusing the parent method.
|
||||
|
||||
import functools
|
||||
|
||||
import numpy as np
|
||||
|
||||
import matplotlib.patches as mpatches
|
||||
from matplotlib.path import Path
|
||||
import matplotlib.axes as maxes
|
||||
|
||||
from mpl_toolkits.axes_grid1.parasite_axes import host_axes_class_factory
|
||||
|
||||
from . import axislines, grid_helper_curvelinear
|
||||
from .axis_artist import AxisArtist
|
||||
from .grid_finder import ExtremeFinderSimple
|
||||
|
||||
|
||||
class FloatingAxisArtistHelper(
|
||||
grid_helper_curvelinear.FloatingAxisArtistHelper):
|
||||
pass
|
||||
|
||||
|
||||
class FixedAxisArtistHelper(grid_helper_curvelinear.FloatingAxisArtistHelper):
|
||||
|
||||
def __init__(self, grid_helper, side, nth_coord_ticks=None):
|
||||
"""
|
||||
nth_coord = along which coordinate value varies.
|
||||
nth_coord = 0 -> x axis, nth_coord = 1 -> y axis
|
||||
"""
|
||||
value, nth_coord = grid_helper.get_data_boundary(side)
|
||||
super().__init__(grid_helper, nth_coord, value, axis_direction=side)
|
||||
if nth_coord_ticks is None:
|
||||
nth_coord_ticks = nth_coord
|
||||
self.nth_coord_ticks = nth_coord_ticks
|
||||
|
||||
self.value = value
|
||||
self.grid_helper = grid_helper
|
||||
self._side = side
|
||||
|
||||
def update_lim(self, axes):
|
||||
self.grid_helper.update_lim(axes)
|
||||
self.grid_info = self.grid_helper.grid_info
|
||||
|
||||
def get_tick_iterators(self, axes):
|
||||
"""tick_loc, tick_angle, tick_label, (optionally) tick_label"""
|
||||
|
||||
grid_finder = self.grid_helper.grid_finder
|
||||
|
||||
lat_levs, lat_n, lat_factor = self.grid_info["lat_info"]
|
||||
lon_levs, lon_n, lon_factor = self.grid_info["lon_info"]
|
||||
|
||||
lon_levs, lat_levs = np.asarray(lon_levs), np.asarray(lat_levs)
|
||||
if lat_factor is not None:
|
||||
yy0 = lat_levs / lat_factor
|
||||
dy = 0.001 / lat_factor
|
||||
else:
|
||||
yy0 = lat_levs
|
||||
dy = 0.001
|
||||
|
||||
if lon_factor is not None:
|
||||
xx0 = lon_levs / lon_factor
|
||||
dx = 0.001 / lon_factor
|
||||
else:
|
||||
xx0 = lon_levs
|
||||
dx = 0.001
|
||||
|
||||
extremes = self.grid_helper._extremes
|
||||
xmin, xmax = sorted(extremes[:2])
|
||||
ymin, ymax = sorted(extremes[2:])
|
||||
|
||||
def transform_xy(x, y):
|
||||
x1, y1 = grid_finder.transform_xy(x, y)
|
||||
x2, y2 = axes.transData.transform(np.array([x1, y1]).T).T
|
||||
return x2, y2
|
||||
|
||||
if self.nth_coord == 0:
|
||||
mask = (ymin <= yy0) & (yy0 <= ymax)
|
||||
yy0 = yy0[mask]
|
||||
xx0 = np.full_like(yy0, self.value)
|
||||
xx1, yy1 = transform_xy(xx0, yy0)
|
||||
|
||||
xx00 = xx0.astype(float, copy=True)
|
||||
xx00[xx0 + dx > xmax] -= dx
|
||||
xx1a, yy1a = transform_xy(xx00, yy0)
|
||||
xx1b, yy1b = transform_xy(xx00 + dx, yy0)
|
||||
|
||||
yy00 = yy0.astype(float, copy=True)
|
||||
yy00[yy0 + dy > ymax] -= dy
|
||||
xx2a, yy2a = transform_xy(xx0, yy00)
|
||||
xx2b, yy2b = transform_xy(xx0, yy00 + dy)
|
||||
|
||||
labels = self.grid_info["lat_labels"]
|
||||
labels = [l for l, m in zip(labels, mask) if m]
|
||||
|
||||
elif self.nth_coord == 1:
|
||||
mask = (xmin <= xx0) & (xx0 <= xmax)
|
||||
xx0 = xx0[mask]
|
||||
yy0 = np.full_like(xx0, self.value)
|
||||
xx1, yy1 = transform_xy(xx0, yy0)
|
||||
|
||||
yy00 = yy0.astype(float, copy=True)
|
||||
yy00[yy0 + dy > ymax] -= dy
|
||||
xx1a, yy1a = transform_xy(xx0, yy00)
|
||||
xx1b, yy1b = transform_xy(xx0, yy00 + dy)
|
||||
|
||||
xx00 = xx0.astype(float, copy=True)
|
||||
xx00[xx0 + dx > xmax] -= dx
|
||||
xx2a, yy2a = transform_xy(xx00, yy0)
|
||||
xx2b, yy2b = transform_xy(xx00 + dx, yy0)
|
||||
|
||||
labels = self.grid_info["lon_labels"]
|
||||
labels = [l for l, m in zip(labels, mask) if m]
|
||||
|
||||
def f1():
|
||||
dd = np.arctan2(yy1b - yy1a, xx1b - xx1a) # angle normal
|
||||
dd2 = np.arctan2(yy2b - yy2a, xx2b - xx2a) # angle tangent
|
||||
mm = (yy1b - yy1a == 0) & (xx1b - xx1a == 0) # mask not defined dd
|
||||
dd[mm] = dd2[mm] + np.pi / 2
|
||||
|
||||
tick_to_axes = self.get_tick_transform(axes) - axes.transAxes
|
||||
for x, y, d, d2, lab in zip(xx1, yy1, dd, dd2, labels):
|
||||
c2 = tick_to_axes.transform((x, y))
|
||||
delta = 0.00001
|
||||
if 0-delta <= c2[0] <= 1+delta and 0-delta <= c2[1] <= 1+delta:
|
||||
d1, d2 = np.rad2deg([d, d2])
|
||||
yield [x, y], d1, d2, lab
|
||||
|
||||
return f1(), iter([])
|
||||
|
||||
def get_line(self, axes):
|
||||
self.update_lim(axes)
|
||||
k, v = dict(left=("lon_lines0", 0),
|
||||
right=("lon_lines0", 1),
|
||||
bottom=("lat_lines0", 0),
|
||||
top=("lat_lines0", 1))[self._side]
|
||||
xx, yy = self.grid_info[k][v]
|
||||
return Path(np.column_stack([xx, yy]))
|
||||
|
||||
|
||||
class ExtremeFinderFixed(ExtremeFinderSimple):
|
||||
# docstring inherited
|
||||
|
||||
def __init__(self, extremes):
|
||||
"""
|
||||
This subclass always returns the same bounding box.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
extremes : (float, float, float, float)
|
||||
The bounding box that this helper always returns.
|
||||
"""
|
||||
self._extremes = extremes
|
||||
|
||||
def __call__(self, transform_xy, x1, y1, x2, y2):
|
||||
# docstring inherited
|
||||
return self._extremes
|
||||
|
||||
|
||||
class GridHelperCurveLinear(grid_helper_curvelinear.GridHelperCurveLinear):
|
||||
|
||||
def __init__(self, aux_trans, extremes,
|
||||
grid_locator1=None,
|
||||
grid_locator2=None,
|
||||
tick_formatter1=None,
|
||||
tick_formatter2=None):
|
||||
# docstring inherited
|
||||
self._extremes = extremes
|
||||
extreme_finder = ExtremeFinderFixed(extremes)
|
||||
super().__init__(aux_trans,
|
||||
extreme_finder,
|
||||
grid_locator1=grid_locator1,
|
||||
grid_locator2=grid_locator2,
|
||||
tick_formatter1=tick_formatter1,
|
||||
tick_formatter2=tick_formatter2)
|
||||
|
||||
def get_data_boundary(self, side):
|
||||
"""
|
||||
Return v=0, nth=1.
|
||||
"""
|
||||
lon1, lon2, lat1, lat2 = self._extremes
|
||||
return dict(left=(lon1, 0),
|
||||
right=(lon2, 0),
|
||||
bottom=(lat1, 1),
|
||||
top=(lat2, 1))[side]
|
||||
|
||||
def new_fixed_axis(self, loc,
|
||||
nth_coord=None,
|
||||
axis_direction=None,
|
||||
offset=None,
|
||||
axes=None):
|
||||
if axes is None:
|
||||
axes = self.axes
|
||||
if axis_direction is None:
|
||||
axis_direction = loc
|
||||
# This is not the same as the FixedAxisArtistHelper class used by
|
||||
# grid_helper_curvelinear.GridHelperCurveLinear.new_fixed_axis!
|
||||
_helper = FixedAxisArtistHelper(
|
||||
self, loc, nth_coord_ticks=nth_coord)
|
||||
axisline = AxisArtist(axes, _helper, axis_direction=axis_direction)
|
||||
# Perhaps should be moved to the base class?
|
||||
axisline.line.set_clip_on(True)
|
||||
axisline.line.set_clip_box(axisline.axes.bbox)
|
||||
return axisline
|
||||
|
||||
# new_floating_axis will inherit the grid_helper's extremes.
|
||||
|
||||
# def new_floating_axis(self, nth_coord,
|
||||
# value,
|
||||
# axes=None,
|
||||
# axis_direction="bottom"
|
||||
# ):
|
||||
|
||||
# axis = super(GridHelperCurveLinear,
|
||||
# self).new_floating_axis(nth_coord,
|
||||
# value, axes=axes,
|
||||
# axis_direction=axis_direction)
|
||||
|
||||
# # set extreme values of the axis helper
|
||||
# if nth_coord == 1:
|
||||
# axis.get_helper().set_extremes(*self._extremes[:2])
|
||||
# elif nth_coord == 0:
|
||||
# axis.get_helper().set_extremes(*self._extremes[2:])
|
||||
|
||||
# return axis
|
||||
|
||||
def _update_grid(self, x1, y1, x2, y2):
|
||||
if self.grid_info is None:
|
||||
self.grid_info = dict()
|
||||
|
||||
grid_info = self.grid_info
|
||||
|
||||
grid_finder = self.grid_finder
|
||||
extremes = grid_finder.extreme_finder(grid_finder.inv_transform_xy,
|
||||
x1, y1, x2, y2)
|
||||
|
||||
lon_min, lon_max = sorted(extremes[:2])
|
||||
lat_min, lat_max = sorted(extremes[2:])
|
||||
lon_levs, lon_n, lon_factor = \
|
||||
grid_finder.grid_locator1(lon_min, lon_max)
|
||||
lat_levs, lat_n, lat_factor = \
|
||||
grid_finder.grid_locator2(lat_min, lat_max)
|
||||
grid_info["extremes"] = lon_min, lon_max, lat_min, lat_max # extremes
|
||||
|
||||
grid_info["lon_info"] = lon_levs, lon_n, lon_factor
|
||||
grid_info["lat_info"] = lat_levs, lat_n, lat_factor
|
||||
|
||||
grid_info["lon_labels"] = grid_finder.tick_formatter1("bottom",
|
||||
lon_factor,
|
||||
lon_levs)
|
||||
|
||||
grid_info["lat_labels"] = grid_finder.tick_formatter2("bottom",
|
||||
lat_factor,
|
||||
lat_levs)
|
||||
|
||||
if lon_factor is None:
|
||||
lon_values = np.asarray(lon_levs[:lon_n])
|
||||
else:
|
||||
lon_values = np.asarray(lon_levs[:lon_n]/lon_factor)
|
||||
if lat_factor is None:
|
||||
lat_values = np.asarray(lat_levs[:lat_n])
|
||||
else:
|
||||
lat_values = np.asarray(lat_levs[:lat_n]/lat_factor)
|
||||
|
||||
lon_lines, lat_lines = grid_finder._get_raw_grid_lines(
|
||||
lon_values[(lon_min < lon_values) & (lon_values < lon_max)],
|
||||
lat_values[(lat_min < lat_values) & (lat_values < lat_max)],
|
||||
lon_min, lon_max, lat_min, lat_max)
|
||||
|
||||
grid_info["lon_lines"] = lon_lines
|
||||
grid_info["lat_lines"] = lat_lines
|
||||
|
||||
lon_lines, lat_lines = grid_finder._get_raw_grid_lines(
|
||||
# lon_min, lon_max, lat_min, lat_max)
|
||||
extremes[:2], extremes[2:], *extremes)
|
||||
|
||||
grid_info["lon_lines0"] = lon_lines
|
||||
grid_info["lat_lines0"] = lat_lines
|
||||
|
||||
def get_gridlines(self, which="major", axis="both"):
|
||||
grid_lines = []
|
||||
if axis in ["both", "x"]:
|
||||
grid_lines.extend(self.grid_info["lon_lines"])
|
||||
if axis in ["both", "y"]:
|
||||
grid_lines.extend(self.grid_info["lat_lines"])
|
||||
return grid_lines
|
||||
|
||||
def get_boundary(self):
|
||||
"""
|
||||
Return (N, 2) array of (x, y) coordinate of the boundary.
|
||||
"""
|
||||
x0, x1, y0, y1 = self._extremes
|
||||
tr = self._aux_trans
|
||||
|
||||
xx = np.linspace(x0, x1, 100)
|
||||
yy0 = np.full_like(xx, y0)
|
||||
yy1 = np.full_like(xx, y1)
|
||||
yy = np.linspace(y0, y1, 100)
|
||||
xx0 = np.full_like(yy, x0)
|
||||
xx1 = np.full_like(yy, x1)
|
||||
|
||||
xxx = np.concatenate([xx[:-1], xx1[:-1], xx[-1:0:-1], xx0])
|
||||
yyy = np.concatenate([yy0[:-1], yy[:-1], yy1[:-1], yy[::-1]])
|
||||
t = tr.transform(np.array([xxx, yyy]).transpose())
|
||||
|
||||
return t
|
||||
|
||||
|
||||
class FloatingAxesBase:
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
grid_helper = kwargs.get("grid_helper", None)
|
||||
if grid_helper is None:
|
||||
raise ValueError("FloatingAxes requires grid_helper argument")
|
||||
if not hasattr(grid_helper, "get_boundary"):
|
||||
raise ValueError("grid_helper must implement get_boundary method")
|
||||
|
||||
self._axes_class_floating.__init__(self, *args, **kwargs)
|
||||
|
||||
self.set_aspect(1.)
|
||||
self.adjust_axes_lim()
|
||||
|
||||
def _gen_axes_patch(self):
|
||||
# docstring inherited
|
||||
grid_helper = self.get_grid_helper()
|
||||
t = grid_helper.get_boundary()
|
||||
return mpatches.Polygon(t)
|
||||
|
||||
def cla(self):
|
||||
self._axes_class_floating.cla(self)
|
||||
# HostAxes.cla(self)
|
||||
self.patch.set_transform(self.transData)
|
||||
|
||||
patch = self._axes_class_floating._gen_axes_patch(self)
|
||||
patch.set_figure(self.figure)
|
||||
patch.set_visible(False)
|
||||
patch.set_transform(self.transAxes)
|
||||
|
||||
self.patch.set_clip_path(patch)
|
||||
self.gridlines.set_clip_path(patch)
|
||||
|
||||
self._original_patch = patch
|
||||
|
||||
def adjust_axes_lim(self):
|
||||
grid_helper = self.get_grid_helper()
|
||||
t = grid_helper.get_boundary()
|
||||
x, y = t[:, 0], t[:, 1]
|
||||
|
||||
xmin, xmax = min(x), max(x)
|
||||
ymin, ymax = min(y), max(y)
|
||||
|
||||
dx = (xmax-xmin) / 100
|
||||
dy = (ymax-ymin) / 100
|
||||
|
||||
self.set_xlim(xmin-dx, xmax+dx)
|
||||
self.set_ylim(ymin-dy, ymax+dy)
|
||||
|
||||
|
||||
@functools.lru_cache(None)
|
||||
def floatingaxes_class_factory(axes_class):
|
||||
return type("Floating %s" % axes_class.__name__,
|
||||
(FloatingAxesBase, axes_class),
|
||||
{'_axes_class_floating': axes_class})
|
||||
|
||||
|
||||
FloatingAxes = floatingaxes_class_factory(
|
||||
host_axes_class_factory(axislines.Axes))
|
||||
FloatingSubplot = maxes.subplot_class_factory(FloatingAxes)
|
304
venv/Lib/site-packages/mpl_toolkits/axisartist/grid_finder.py
Normal file
304
venv/Lib/site-packages/mpl_toolkits/axisartist/grid_finder.py
Normal file
|
@ -0,0 +1,304 @@
|
|||
import numpy as np
|
||||
|
||||
from matplotlib import cbook, ticker as mticker
|
||||
from matplotlib.transforms import Bbox, Transform
|
||||
from .clip_path import clip_line_to_rect
|
||||
|
||||
|
||||
def _deprecate_factor_none(factor):
|
||||
# After the deprecation period, calls to _deprecate_factor_none can just be
|
||||
# removed.
|
||||
if factor is None:
|
||||
cbook.warn_deprecated(
|
||||
"3.2", message="factor=None is deprecated since %(since)s and "
|
||||
"support will be removed %(removal)s; use/return factor=1 instead")
|
||||
factor = 1
|
||||
return factor
|
||||
|
||||
|
||||
class ExtremeFinderSimple:
|
||||
"""
|
||||
A helper class to figure out the range of grid lines that need to be drawn.
|
||||
"""
|
||||
|
||||
def __init__(self, nx, ny):
|
||||
"""
|
||||
Parameters
|
||||
----------
|
||||
nx, ny : int
|
||||
The number of samples in each direction.
|
||||
"""
|
||||
self.nx = nx
|
||||
self.ny = ny
|
||||
|
||||
def __call__(self, transform_xy, x1, y1, x2, y2):
|
||||
"""
|
||||
Compute an approximation of the bounding box obtained by applying
|
||||
*transform_xy* to the box delimited by ``(x1, y1, x2, y2)``.
|
||||
|
||||
The intended use is to have ``(x1, y1, x2, y2)`` in axes coordinates,
|
||||
and have *transform_xy* be the transform from axes coordinates to data
|
||||
coordinates; this method then returns the range of data coordinates
|
||||
that span the actual axes.
|
||||
|
||||
The computation is done by sampling ``nx * ny`` equispaced points in
|
||||
the ``(x1, y1, x2, y2)`` box and finding the resulting points with
|
||||
extremal coordinates; then adding some padding to take into account the
|
||||
finite sampling.
|
||||
|
||||
As each sampling step covers a relative range of *1/nx* or *1/ny*,
|
||||
the padding is computed by expanding the span covered by the extremal
|
||||
coordinates by these fractions.
|
||||
"""
|
||||
x, y = np.meshgrid(
|
||||
np.linspace(x1, x2, self.nx), np.linspace(y1, y2, self.ny))
|
||||
xt, yt = transform_xy(np.ravel(x), np.ravel(y))
|
||||
return self._add_pad(xt.min(), xt.max(), yt.min(), yt.max())
|
||||
|
||||
def _add_pad(self, x_min, x_max, y_min, y_max):
|
||||
"""Perform the padding mentioned in `__call__`."""
|
||||
dx = (x_max - x_min) / self.nx
|
||||
dy = (y_max - y_min) / self.ny
|
||||
return x_min - dx, x_max + dx, y_min - dy, y_max + dy
|
||||
|
||||
|
||||
class GridFinder:
|
||||
def __init__(self,
|
||||
transform,
|
||||
extreme_finder=None,
|
||||
grid_locator1=None,
|
||||
grid_locator2=None,
|
||||
tick_formatter1=None,
|
||||
tick_formatter2=None):
|
||||
"""
|
||||
transform : transform from the image coordinate (which will be
|
||||
the transData of the axes to the world coordinate.
|
||||
|
||||
or transform = (transform_xy, inv_transform_xy)
|
||||
|
||||
locator1, locator2 : grid locator for 1st and 2nd axis.
|
||||
"""
|
||||
if extreme_finder is None:
|
||||
extreme_finder = ExtremeFinderSimple(20, 20)
|
||||
if grid_locator1 is None:
|
||||
grid_locator1 = MaxNLocator()
|
||||
if grid_locator2 is None:
|
||||
grid_locator2 = MaxNLocator()
|
||||
if tick_formatter1 is None:
|
||||
tick_formatter1 = FormatterPrettyPrint()
|
||||
if tick_formatter2 is None:
|
||||
tick_formatter2 = FormatterPrettyPrint()
|
||||
self.extreme_finder = extreme_finder
|
||||
self.grid_locator1 = grid_locator1
|
||||
self.grid_locator2 = grid_locator2
|
||||
self.tick_formatter1 = tick_formatter1
|
||||
self.tick_formatter2 = tick_formatter2
|
||||
self.update_transform(transform)
|
||||
|
||||
def get_grid_info(self, x1, y1, x2, y2):
|
||||
"""
|
||||
lon_values, lat_values : list of grid values. if integer is given,
|
||||
rough number of grids in each direction.
|
||||
"""
|
||||
|
||||
extremes = self.extreme_finder(self.inv_transform_xy, x1, y1, x2, y2)
|
||||
|
||||
# min & max rage of lat (or lon) for each grid line will be drawn.
|
||||
# i.e., gridline of lon=0 will be drawn from lat_min to lat_max.
|
||||
|
||||
lon_min, lon_max, lat_min, lat_max = extremes
|
||||
lon_levs, lon_n, lon_factor = self.grid_locator1(lon_min, lon_max)
|
||||
lat_levs, lat_n, lat_factor = self.grid_locator2(lat_min, lat_max)
|
||||
|
||||
lon_values = lon_levs[:lon_n] / _deprecate_factor_none(lon_factor)
|
||||
lat_values = lat_levs[:lat_n] / _deprecate_factor_none(lat_factor)
|
||||
|
||||
lon_lines, lat_lines = self._get_raw_grid_lines(lon_values,
|
||||
lat_values,
|
||||
lon_min, lon_max,
|
||||
lat_min, lat_max)
|
||||
|
||||
ddx = (x2-x1)*1.e-10
|
||||
ddy = (y2-y1)*1.e-10
|
||||
bb = Bbox.from_extents(x1-ddx, y1-ddy, x2+ddx, y2+ddy)
|
||||
|
||||
grid_info = {
|
||||
"extremes": extremes,
|
||||
"lon_lines": lon_lines,
|
||||
"lat_lines": lat_lines,
|
||||
"lon": self._clip_grid_lines_and_find_ticks(
|
||||
lon_lines, lon_values, lon_levs, bb),
|
||||
"lat": self._clip_grid_lines_and_find_ticks(
|
||||
lat_lines, lat_values, lat_levs, bb),
|
||||
}
|
||||
|
||||
tck_labels = grid_info["lon"]["tick_labels"] = {}
|
||||
for direction in ["left", "bottom", "right", "top"]:
|
||||
levs = grid_info["lon"]["tick_levels"][direction]
|
||||
tck_labels[direction] = self.tick_formatter1(
|
||||
direction, lon_factor, levs)
|
||||
|
||||
tck_labels = grid_info["lat"]["tick_labels"] = {}
|
||||
for direction in ["left", "bottom", "right", "top"]:
|
||||
levs = grid_info["lat"]["tick_levels"][direction]
|
||||
tck_labels[direction] = self.tick_formatter2(
|
||||
direction, lat_factor, levs)
|
||||
|
||||
return grid_info
|
||||
|
||||
def _get_raw_grid_lines(self,
|
||||
lon_values, lat_values,
|
||||
lon_min, lon_max, lat_min, lat_max):
|
||||
|
||||
lons_i = np.linspace(lon_min, lon_max, 100) # for interpolation
|
||||
lats_i = np.linspace(lat_min, lat_max, 100)
|
||||
|
||||
lon_lines = [self.transform_xy(np.full_like(lats_i, lon), lats_i)
|
||||
for lon in lon_values]
|
||||
lat_lines = [self.transform_xy(lons_i, np.full_like(lons_i, lat))
|
||||
for lat in lat_values]
|
||||
|
||||
return lon_lines, lat_lines
|
||||
|
||||
def _clip_grid_lines_and_find_ticks(self, lines, values, levs, bb):
|
||||
gi = {
|
||||
"values": [],
|
||||
"levels": [],
|
||||
"tick_levels": dict(left=[], bottom=[], right=[], top=[]),
|
||||
"tick_locs": dict(left=[], bottom=[], right=[], top=[]),
|
||||
"lines": [],
|
||||
}
|
||||
|
||||
tck_levels = gi["tick_levels"]
|
||||
tck_locs = gi["tick_locs"]
|
||||
for (lx, ly), v, lev in zip(lines, values, levs):
|
||||
xy, tcks = clip_line_to_rect(lx, ly, bb)
|
||||
if not xy:
|
||||
continue
|
||||
gi["levels"].append(v)
|
||||
gi["lines"].append(xy)
|
||||
|
||||
for tck, direction in zip(tcks,
|
||||
["left", "bottom", "right", "top"]):
|
||||
for t in tck:
|
||||
tck_levels[direction].append(lev)
|
||||
tck_locs[direction].append(t)
|
||||
|
||||
return gi
|
||||
|
||||
def update_transform(self, aux_trans):
|
||||
if isinstance(aux_trans, Transform):
|
||||
def transform_xy(x, y):
|
||||
ll1 = np.column_stack([x, y])
|
||||
ll2 = aux_trans.transform(ll1)
|
||||
lon, lat = ll2[:, 0], ll2[:, 1]
|
||||
return lon, lat
|
||||
|
||||
def inv_transform_xy(x, y):
|
||||
ll1 = np.column_stack([x, y])
|
||||
ll2 = aux_trans.inverted().transform(ll1)
|
||||
lon, lat = ll2[:, 0], ll2[:, 1]
|
||||
return lon, lat
|
||||
|
||||
else:
|
||||
transform_xy, inv_transform_xy = aux_trans
|
||||
|
||||
self.transform_xy = transform_xy
|
||||
self.inv_transform_xy = inv_transform_xy
|
||||
|
||||
def update(self, **kw):
|
||||
for k in kw:
|
||||
if k in ["extreme_finder",
|
||||
"grid_locator1",
|
||||
"grid_locator2",
|
||||
"tick_formatter1",
|
||||
"tick_formatter2"]:
|
||||
setattr(self, k, kw[k])
|
||||
else:
|
||||
raise ValueError("Unknown update property '%s'" % k)
|
||||
|
||||
|
||||
@cbook.deprecated("3.2")
|
||||
class GridFinderBase(GridFinder):
|
||||
def __init__(self,
|
||||
extreme_finder,
|
||||
grid_locator1=None,
|
||||
grid_locator2=None,
|
||||
tick_formatter1=None,
|
||||
tick_formatter2=None):
|
||||
super().__init__((None, None), extreme_finder,
|
||||
grid_locator1, grid_locator2,
|
||||
tick_formatter1, tick_formatter2)
|
||||
|
||||
|
||||
class MaxNLocator(mticker.MaxNLocator):
|
||||
def __init__(self, nbins=10, steps=None,
|
||||
trim=True,
|
||||
integer=False,
|
||||
symmetric=False,
|
||||
prune=None):
|
||||
# trim argument has no effect. It has been left for API compatibility
|
||||
mticker.MaxNLocator.__init__(self, nbins, steps=steps,
|
||||
integer=integer,
|
||||
symmetric=symmetric, prune=prune)
|
||||
self.create_dummy_axis()
|
||||
self._factor = 1
|
||||
|
||||
def __call__(self, v1, v2):
|
||||
self.set_bounds(v1 * self._factor, v2 * self._factor)
|
||||
locs = mticker.MaxNLocator.__call__(self)
|
||||
return np.array(locs), len(locs), self._factor
|
||||
|
||||
@cbook.deprecated("3.3")
|
||||
def set_factor(self, f):
|
||||
self._factor = _deprecate_factor_none(f)
|
||||
|
||||
|
||||
class FixedLocator:
|
||||
def __init__(self, locs):
|
||||
self._locs = locs
|
||||
self._factor = 1
|
||||
|
||||
def __call__(self, v1, v2):
|
||||
v1, v2 = sorted([v1 * self._factor, v2 * self._factor])
|
||||
locs = np.array([l for l in self._locs if v1 <= l <= v2])
|
||||
return locs, len(locs), self._factor
|
||||
|
||||
@cbook.deprecated("3.3")
|
||||
def set_factor(self, f):
|
||||
self._factor = _deprecate_factor_none(f)
|
||||
|
||||
|
||||
# Tick Formatter
|
||||
|
||||
class FormatterPrettyPrint:
|
||||
def __init__(self, useMathText=True):
|
||||
self._fmt = mticker.ScalarFormatter(
|
||||
useMathText=useMathText, useOffset=False)
|
||||
self._fmt.create_dummy_axis()
|
||||
|
||||
def __call__(self, direction, factor, values):
|
||||
return self._fmt.format_ticks(values)
|
||||
|
||||
|
||||
class DictFormatter:
|
||||
def __init__(self, format_dict, formatter=None):
|
||||
"""
|
||||
format_dict : dictionary for format strings to be used.
|
||||
formatter : fall-back formatter
|
||||
"""
|
||||
super().__init__()
|
||||
self._format_dict = format_dict
|
||||
self._fallback_formatter = formatter
|
||||
|
||||
def __call__(self, direction, factor, values):
|
||||
"""
|
||||
factor is ignored if value is found in the dictionary
|
||||
"""
|
||||
if self._fallback_formatter:
|
||||
fallback_strings = self._fallback_formatter(
|
||||
direction, factor, values)
|
||||
else:
|
||||
fallback_strings = [""] * len(values)
|
||||
return [self._format_dict.get(k, v)
|
||||
for k, v in zip(values, fallback_strings)]
|
|
@ -0,0 +1,398 @@
|
|||
"""
|
||||
An experimental support for curvilinear grid.
|
||||
"""
|
||||
from itertools import chain
|
||||
|
||||
import numpy as np
|
||||
|
||||
from matplotlib.path import Path
|
||||
from matplotlib.transforms import Affine2D, IdentityTransform
|
||||
from .axislines import AxisArtistHelper, GridHelperBase
|
||||
from .axis_artist import AxisArtist
|
||||
from .grid_finder import GridFinder, _deprecate_factor_none
|
||||
|
||||
|
||||
class FixedAxisArtistHelper(AxisArtistHelper.Fixed):
|
||||
"""
|
||||
Helper class for a fixed axis.
|
||||
"""
|
||||
|
||||
def __init__(self, grid_helper, side, nth_coord_ticks=None):
|
||||
"""
|
||||
nth_coord = along which coordinate value varies.
|
||||
nth_coord = 0 -> x axis, nth_coord = 1 -> y axis
|
||||
"""
|
||||
|
||||
super().__init__(loc=side)
|
||||
|
||||
self.grid_helper = grid_helper
|
||||
if nth_coord_ticks is None:
|
||||
nth_coord_ticks = self.nth_coord
|
||||
self.nth_coord_ticks = nth_coord_ticks
|
||||
|
||||
self.side = side
|
||||
self._limits_inverted = False
|
||||
|
||||
def update_lim(self, axes):
|
||||
self.grid_helper.update_lim(axes)
|
||||
|
||||
if self.nth_coord == 0:
|
||||
xy1, xy2 = axes.get_ylim()
|
||||
else:
|
||||
xy1, xy2 = axes.get_xlim()
|
||||
|
||||
if xy1 > xy2:
|
||||
self._limits_inverted = True
|
||||
else:
|
||||
self._limits_inverted = False
|
||||
|
||||
def change_tick_coord(self, coord_number=None):
|
||||
if coord_number is None:
|
||||
self.nth_coord_ticks = 1 - self.nth_coord_ticks
|
||||
elif coord_number in [0, 1]:
|
||||
self.nth_coord_ticks = coord_number
|
||||
else:
|
||||
raise Exception("wrong coord number")
|
||||
|
||||
def get_tick_transform(self, axes):
|
||||
return axes.transData
|
||||
|
||||
def get_tick_iterators(self, axes):
|
||||
"""tick_loc, tick_angle, tick_label"""
|
||||
|
||||
g = self.grid_helper
|
||||
|
||||
if self._limits_inverted:
|
||||
side = {"left": "right", "right": "left",
|
||||
"top": "bottom", "bottom": "top"}[self.side]
|
||||
else:
|
||||
side = self.side
|
||||
|
||||
ti1 = g.get_tick_iterator(self.nth_coord_ticks, side)
|
||||
ti2 = g.get_tick_iterator(1-self.nth_coord_ticks, side, minor=True)
|
||||
|
||||
return chain(ti1, ti2), iter([])
|
||||
|
||||
|
||||
class FloatingAxisArtistHelper(AxisArtistHelper.Floating):
|
||||
|
||||
def __init__(self, grid_helper, nth_coord, value, axis_direction=None):
|
||||
"""
|
||||
nth_coord = along which coordinate value varies.
|
||||
nth_coord = 0 -> x axis, nth_coord = 1 -> y axis
|
||||
"""
|
||||
|
||||
super().__init__(nth_coord, value)
|
||||
self.value = value
|
||||
self.grid_helper = grid_helper
|
||||
self._extremes = -np.inf, np.inf
|
||||
|
||||
self._get_line_path = None # a method that returns a Path.
|
||||
self._line_num_points = 100 # number of points to create a line
|
||||
|
||||
def set_extremes(self, e1, e2):
|
||||
if e1 is None:
|
||||
e1 = -np.inf
|
||||
if e2 is None:
|
||||
e2 = np.inf
|
||||
self._extremes = e1, e2
|
||||
|
||||
def update_lim(self, axes):
|
||||
self.grid_helper.update_lim(axes)
|
||||
|
||||
x1, x2 = axes.get_xlim()
|
||||
y1, y2 = axes.get_ylim()
|
||||
grid_finder = self.grid_helper.grid_finder
|
||||
extremes = grid_finder.extreme_finder(grid_finder.inv_transform_xy,
|
||||
x1, y1, x2, y2)
|
||||
|
||||
lon_min, lon_max, lat_min, lat_max = extremes
|
||||
e_min, e_max = self._extremes # ranges of other coordinates
|
||||
if self.nth_coord == 0:
|
||||
lat_min = max(e_min, lat_min)
|
||||
lat_max = min(e_max, lat_max)
|
||||
elif self.nth_coord == 1:
|
||||
lon_min = max(e_min, lon_min)
|
||||
lon_max = min(e_max, lon_max)
|
||||
|
||||
lon_levs, lon_n, lon_factor = \
|
||||
grid_finder.grid_locator1(lon_min, lon_max)
|
||||
lat_levs, lat_n, lat_factor = \
|
||||
grid_finder.grid_locator2(lat_min, lat_max)
|
||||
|
||||
if self.nth_coord == 0:
|
||||
xx0 = np.full(self._line_num_points, self.value, type(self.value))
|
||||
yy0 = np.linspace(lat_min, lat_max, self._line_num_points)
|
||||
xx, yy = grid_finder.transform_xy(xx0, yy0)
|
||||
elif self.nth_coord == 1:
|
||||
xx0 = np.linspace(lon_min, lon_max, self._line_num_points)
|
||||
yy0 = np.full(self._line_num_points, self.value, type(self.value))
|
||||
xx, yy = grid_finder.transform_xy(xx0, yy0)
|
||||
|
||||
self.grid_info = {
|
||||
"extremes": (lon_min, lon_max, lat_min, lat_max),
|
||||
"lon_info": (lon_levs, lon_n, _deprecate_factor_none(lon_factor)),
|
||||
"lat_info": (lat_levs, lat_n, _deprecate_factor_none(lat_factor)),
|
||||
"lon_labels": grid_finder.tick_formatter1(
|
||||
"bottom", _deprecate_factor_none(lon_factor), lon_levs),
|
||||
"lat_labels": grid_finder.tick_formatter2(
|
||||
"bottom", _deprecate_factor_none(lat_factor), lat_levs),
|
||||
"line_xy": (xx, yy),
|
||||
}
|
||||
|
||||
def get_axislabel_transform(self, axes):
|
||||
return Affine2D() # axes.transData
|
||||
|
||||
def get_axislabel_pos_angle(self, axes):
|
||||
|
||||
extremes = self.grid_info["extremes"]
|
||||
|
||||
if self.nth_coord == 0:
|
||||
xx0 = self.value
|
||||
yy0 = (extremes[2] + extremes[3]) / 2
|
||||
dxx = 0
|
||||
dyy = abs(extremes[2] - extremes[3]) / 1000
|
||||
elif self.nth_coord == 1:
|
||||
xx0 = (extremes[0] + extremes[1]) / 2
|
||||
yy0 = self.value
|
||||
dxx = abs(extremes[0] - extremes[1]) / 1000
|
||||
dyy = 0
|
||||
|
||||
grid_finder = self.grid_helper.grid_finder
|
||||
(xx1,), (yy1,) = grid_finder.transform_xy([xx0], [yy0])
|
||||
|
||||
data_to_axes = axes.transData - axes.transAxes
|
||||
p = data_to_axes.transform([xx1, yy1])
|
||||
|
||||
if 0 <= p[0] <= 1 and 0 <= p[1] <= 1:
|
||||
xx1c, yy1c = axes.transData.transform([xx1, yy1])
|
||||
(xx2,), (yy2,) = grid_finder.transform_xy([xx0 + dxx], [yy0 + dyy])
|
||||
xx2c, yy2c = axes.transData.transform([xx2, yy2])
|
||||
return (xx1c, yy1c), np.rad2deg(np.arctan2(yy2c-yy1c, xx2c-xx1c))
|
||||
else:
|
||||
return None, None
|
||||
|
||||
def get_tick_transform(self, axes):
|
||||
return IdentityTransform() # axes.transData
|
||||
|
||||
def get_tick_iterators(self, axes):
|
||||
"""tick_loc, tick_angle, tick_label, (optionally) tick_label"""
|
||||
|
||||
grid_finder = self.grid_helper.grid_finder
|
||||
|
||||
lat_levs, lat_n, lat_factor = self.grid_info["lat_info"]
|
||||
lat_levs = np.asarray(lat_levs)
|
||||
yy0 = lat_levs / _deprecate_factor_none(lat_factor)
|
||||
dy = 0.01 / _deprecate_factor_none(lat_factor)
|
||||
|
||||
lon_levs, lon_n, lon_factor = self.grid_info["lon_info"]
|
||||
lon_levs = np.asarray(lon_levs)
|
||||
xx0 = lon_levs / _deprecate_factor_none(lon_factor)
|
||||
dx = 0.01 / _deprecate_factor_none(lon_factor)
|
||||
|
||||
if None in self._extremes:
|
||||
e0, e1 = self._extremes
|
||||
else:
|
||||
e0, e1 = sorted(self._extremes)
|
||||
if e0 is None:
|
||||
e0 = -np.inf
|
||||
if e1 is None:
|
||||
e1 = np.inf
|
||||
|
||||
if self.nth_coord == 0:
|
||||
mask = (e0 <= yy0) & (yy0 <= e1)
|
||||
#xx0, yy0 = xx0[mask], yy0[mask]
|
||||
yy0 = yy0[mask]
|
||||
elif self.nth_coord == 1:
|
||||
mask = (e0 <= xx0) & (xx0 <= e1)
|
||||
#xx0, yy0 = xx0[mask], yy0[mask]
|
||||
xx0 = xx0[mask]
|
||||
|
||||
def transform_xy(x, y):
|
||||
x1, y1 = grid_finder.transform_xy(x, y)
|
||||
x2y2 = axes.transData.transform(np.array([x1, y1]).transpose())
|
||||
x2, y2 = x2y2.transpose()
|
||||
return x2, y2
|
||||
|
||||
# find angles
|
||||
if self.nth_coord == 0:
|
||||
xx0 = np.full_like(yy0, self.value)
|
||||
|
||||
xx1, yy1 = transform_xy(xx0, yy0)
|
||||
|
||||
xx00 = xx0.copy()
|
||||
xx00[xx0 + dx > e1] -= dx
|
||||
xx1a, yy1a = transform_xy(xx00, yy0)
|
||||
xx1b, yy1b = transform_xy(xx00+dx, yy0)
|
||||
|
||||
xx2a, yy2a = transform_xy(xx0, yy0)
|
||||
xx2b, yy2b = transform_xy(xx0, yy0+dy)
|
||||
|
||||
labels = self.grid_info["lat_labels"]
|
||||
labels = [l for l, m in zip(labels, mask) if m]
|
||||
|
||||
elif self.nth_coord == 1:
|
||||
yy0 = np.full_like(xx0, self.value)
|
||||
|
||||
xx1, yy1 = transform_xy(xx0, yy0)
|
||||
|
||||
xx1a, yy1a = transform_xy(xx0, yy0)
|
||||
xx1b, yy1b = transform_xy(xx0, yy0+dy)
|
||||
|
||||
xx00 = xx0.copy()
|
||||
xx00[xx0 + dx > e1] -= dx
|
||||
xx2a, yy2a = transform_xy(xx00, yy0)
|
||||
xx2b, yy2b = transform_xy(xx00+dx, yy0)
|
||||
|
||||
labels = self.grid_info["lon_labels"]
|
||||
labels = [l for l, m in zip(labels, mask) if m]
|
||||
|
||||
def f1():
|
||||
dd = np.arctan2(yy1b-yy1a, xx1b-xx1a) # angle normal
|
||||
dd2 = np.arctan2(yy2b-yy2a, xx2b-xx2a) # angle tangent
|
||||
mm = (yy1b == yy1a) & (xx1b == xx1a) # mask where dd not defined
|
||||
dd[mm] = dd2[mm] + np.pi / 2
|
||||
|
||||
tick_to_axes = self.get_tick_transform(axes) - axes.transAxes
|
||||
for x, y, d, d2, lab in zip(xx1, yy1, dd, dd2, labels):
|
||||
c2 = tick_to_axes.transform((x, y))
|
||||
delta = 0.00001
|
||||
if 0-delta <= c2[0] <= 1+delta and 0-delta <= c2[1] <= 1+delta:
|
||||
d1, d2 = np.rad2deg([d, d2])
|
||||
yield [x, y], d1, d2, lab
|
||||
|
||||
return f1(), iter([])
|
||||
|
||||
def get_line_transform(self, axes):
|
||||
return axes.transData
|
||||
|
||||
def get_line(self, axes):
|
||||
self.update_lim(axes)
|
||||
x, y = self.grid_info["line_xy"]
|
||||
|
||||
if self._get_line_path is None:
|
||||
return Path(np.column_stack([x, y]))
|
||||
else:
|
||||
return self._get_line_path(axes, x, y)
|
||||
|
||||
|
||||
class GridHelperCurveLinear(GridHelperBase):
|
||||
|
||||
def __init__(self, aux_trans,
|
||||
extreme_finder=None,
|
||||
grid_locator1=None,
|
||||
grid_locator2=None,
|
||||
tick_formatter1=None,
|
||||
tick_formatter2=None):
|
||||
"""
|
||||
aux_trans : a transform from the source (curved) coordinate to
|
||||
target (rectilinear) coordinate. An instance of MPL's Transform
|
||||
(inverse transform should be defined) or a tuple of two callable
|
||||
objects which defines the transform and its inverse. The callables
|
||||
need take two arguments of array of source coordinates and
|
||||
should return two target coordinates.
|
||||
|
||||
e.g., ``x2, y2 = trans(x1, y1)``
|
||||
"""
|
||||
super().__init__()
|
||||
self.grid_info = None
|
||||
self._old_values = None
|
||||
self._aux_trans = aux_trans
|
||||
self.grid_finder = GridFinder(aux_trans,
|
||||
extreme_finder,
|
||||
grid_locator1,
|
||||
grid_locator2,
|
||||
tick_formatter1,
|
||||
tick_formatter2)
|
||||
|
||||
def update_grid_finder(self, aux_trans=None, **kw):
|
||||
if aux_trans is not None:
|
||||
self.grid_finder.update_transform(aux_trans)
|
||||
self.grid_finder.update(**kw)
|
||||
self.invalidate()
|
||||
|
||||
def _update(self, x1, x2, y1, y2):
|
||||
"""bbox in 0-based image coordinates"""
|
||||
# update wcsgrid
|
||||
if self.valid() and self._old_values == (x1, x2, y1, y2):
|
||||
return
|
||||
self._update_grid(x1, y1, x2, y2)
|
||||
self._old_values = (x1, x2, y1, y2)
|
||||
self._force_update = False
|
||||
|
||||
def new_fixed_axis(self, loc,
|
||||
nth_coord=None,
|
||||
axis_direction=None,
|
||||
offset=None,
|
||||
axes=None):
|
||||
if axes is None:
|
||||
axes = self.axes
|
||||
if axis_direction is None:
|
||||
axis_direction = loc
|
||||
_helper = FixedAxisArtistHelper(self, loc, nth_coord_ticks=nth_coord)
|
||||
axisline = AxisArtist(axes, _helper, axis_direction=axis_direction)
|
||||
# Why is clip not set on axisline, unlike in new_floating_axis or in
|
||||
# the floating_axig.GridHelperCurveLinear subclass?
|
||||
return axisline
|
||||
|
||||
def new_floating_axis(self, nth_coord,
|
||||
value,
|
||||
axes=None,
|
||||
axis_direction="bottom"
|
||||
):
|
||||
|
||||
if axes is None:
|
||||
axes = self.axes
|
||||
|
||||
_helper = FloatingAxisArtistHelper(
|
||||
self, nth_coord, value, axis_direction)
|
||||
|
||||
axisline = AxisArtist(axes, _helper)
|
||||
|
||||
# _helper = FloatingAxisArtistHelper(self, nth_coord,
|
||||
# value,
|
||||
# label_direction=label_direction,
|
||||
# )
|
||||
|
||||
# axisline = AxisArtistFloating(axes, _helper,
|
||||
# axis_direction=axis_direction)
|
||||
axisline.line.set_clip_on(True)
|
||||
axisline.line.set_clip_box(axisline.axes.bbox)
|
||||
# axisline.major_ticklabels.set_visible(True)
|
||||
# axisline.minor_ticklabels.set_visible(False)
|
||||
|
||||
return axisline
|
||||
|
||||
def _update_grid(self, x1, y1, x2, y2):
|
||||
self.grid_info = self.grid_finder.get_grid_info(x1, y1, x2, y2)
|
||||
|
||||
def get_gridlines(self, which="major", axis="both"):
|
||||
grid_lines = []
|
||||
if axis in ["both", "x"]:
|
||||
for gl in self.grid_info["lon"]["lines"]:
|
||||
grid_lines.extend(gl)
|
||||
if axis in ["both", "y"]:
|
||||
for gl in self.grid_info["lat"]["lines"]:
|
||||
grid_lines.extend(gl)
|
||||
return grid_lines
|
||||
|
||||
def get_tick_iterator(self, nth_coord, axis_side, minor=False):
|
||||
|
||||
# axisnr = dict(left=0, bottom=1, right=2, top=3)[axis_side]
|
||||
angle_tangent = dict(left=90, right=90, bottom=0, top=0)[axis_side]
|
||||
# angle = [0, 90, 180, 270][axisnr]
|
||||
lon_or_lat = ["lon", "lat"][nth_coord]
|
||||
if not minor: # major ticks
|
||||
for (xy, a), l in zip(
|
||||
self.grid_info[lon_or_lat]["tick_locs"][axis_side],
|
||||
self.grid_info[lon_or_lat]["tick_labels"][axis_side]):
|
||||
angle_normal = a
|
||||
yield xy, angle_normal, angle_tangent, l
|
||||
else:
|
||||
for (xy, a), l in zip(
|
||||
self.grid_info[lon_or_lat]["tick_locs"][axis_side],
|
||||
self.grid_info[lon_or_lat]["tick_labels"][axis_side]):
|
||||
angle_normal = a
|
||||
yield xy, angle_normal, angle_tangent, ""
|
||||
# for xy, a, l in self.grid_info[lon_or_lat]["ticks"][axis_side]:
|
||||
# yield xy, a, ""
|
|
@ -0,0 +1,10 @@
|
|||
from mpl_toolkits.axes_grid1.parasite_axes import (
|
||||
host_axes_class_factory, parasite_axes_class_factory,
|
||||
parasite_axes_auxtrans_class_factory, subplot_class_factory)
|
||||
from .axislines import Axes
|
||||
|
||||
|
||||
ParasiteAxes = parasite_axes_class_factory(Axes)
|
||||
ParasiteAxesAuxTrans = parasite_axes_auxtrans_class_factory(ParasiteAxes)
|
||||
HostAxes = host_axes_class_factory(Axes)
|
||||
SubplotHost = subplot_class_factory(HostAxes)
|
Loading…
Add table
Add a link
Reference in a new issue