aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/matplotlib/py3/mpl_toolkits/mplot3d/axis3d.py
diff options
context:
space:
mode:
authorshumkovnd <shumkovnd@yandex-team.com>2023-11-10 14:39:34 +0300
committershumkovnd <shumkovnd@yandex-team.com>2023-11-10 16:42:24 +0300
commit77eb2d3fdcec5c978c64e025ced2764c57c00285 (patch)
treec51edb0748ca8d4a08d7c7323312c27ba1a8b79a /contrib/python/matplotlib/py3/mpl_toolkits/mplot3d/axis3d.py
parentdd6d20cadb65582270ac23f4b3b14ae189704b9d (diff)
downloadydb-77eb2d3fdcec5c978c64e025ced2764c57c00285.tar.gz
KIKIMR-19287: add task_stats_drawing script
Diffstat (limited to 'contrib/python/matplotlib/py3/mpl_toolkits/mplot3d/axis3d.py')
-rw-r--r--contrib/python/matplotlib/py3/mpl_toolkits/mplot3d/axis3d.py753
1 files changed, 753 insertions, 0 deletions
diff --git a/contrib/python/matplotlib/py3/mpl_toolkits/mplot3d/axis3d.py b/contrib/python/matplotlib/py3/mpl_toolkits/mplot3d/axis3d.py
new file mode 100644
index 0000000000..4c5fa8a9c9
--- /dev/null
+++ b/contrib/python/matplotlib/py3/mpl_toolkits/mplot3d/axis3d.py
@@ -0,0 +1,753 @@
+# axis3d.py, original mplot3d version by John Porter
+# Created: 23 Sep 2005
+# Parts rewritten by Reinier Heeres <reinier@heeres.eu>
+
+import inspect
+
+import numpy as np
+
+import matplotlib as mpl
+from matplotlib import (
+ _api, artist, lines as mlines, axis as maxis, patches as mpatches,
+ transforms as mtransforms, colors as mcolors)
+from . import art3d, proj3d
+
+
+def _move_from_center(coord, centers, deltas, axmask=(True, True, True)):
+ """
+ For each coordinate where *axmask* is True, move *coord* away from
+ *centers* by *deltas*.
+ """
+ coord = np.asarray(coord)
+ return coord + axmask * np.copysign(1, coord - centers) * deltas
+
+
+def _tick_update_position(tick, tickxs, tickys, labelpos):
+ """Update tick line and label position and style."""
+
+ tick.label1.set_position(labelpos)
+ tick.label2.set_position(labelpos)
+ tick.tick1line.set_visible(True)
+ tick.tick2line.set_visible(False)
+ tick.tick1line.set_linestyle('-')
+ tick.tick1line.set_marker('')
+ tick.tick1line.set_data(tickxs, tickys)
+ tick.gridline.set_data([0], [0])
+
+
+class Axis(maxis.XAxis):
+ """An Axis class for the 3D plots."""
+ # These points from the unit cube make up the x, y and z-planes
+ _PLANES = (
+ (0, 3, 7, 4), (1, 2, 6, 5), # yz planes
+ (0, 1, 5, 4), (3, 2, 6, 7), # xz planes
+ (0, 1, 2, 3), (4, 5, 6, 7), # xy planes
+ )
+
+ # Some properties for the axes
+ _AXINFO = {
+ 'x': {'i': 0, 'tickdir': 1, 'juggled': (1, 0, 2)},
+ 'y': {'i': 1, 'tickdir': 0, 'juggled': (0, 1, 2)},
+ 'z': {'i': 2, 'tickdir': 0, 'juggled': (0, 2, 1)},
+ }
+
+ def _old_init(self, adir, v_intervalx, d_intervalx, axes, *args,
+ rotate_label=None, **kwargs):
+ return locals()
+
+ def _new_init(self, axes, *, rotate_label=None, **kwargs):
+ return locals()
+
+ def __init__(self, *args, **kwargs):
+ params = _api.select_matching_signature(
+ [self._old_init, self._new_init], *args, **kwargs)
+ if "adir" in params:
+ _api.warn_deprecated(
+ "3.6", message=f"The signature of 3D Axis constructors has "
+ f"changed in %(since)s; the new signature is "
+ f"{inspect.signature(type(self).__init__)}", pending=True)
+ if params["adir"] != self.axis_name:
+ raise ValueError(f"Cannot instantiate {type(self).__name__} "
+ f"with adir={params['adir']!r}")
+ axes = params["axes"]
+ rotate_label = params["rotate_label"]
+ args = params.get("args", ())
+ kwargs = params["kwargs"]
+
+ name = self.axis_name
+
+ self._label_position = 'default'
+ self._tick_position = 'default'
+
+ # This is a temporary member variable.
+ # Do not depend on this existing in future releases!
+ self._axinfo = self._AXINFO[name].copy()
+ # Common parts
+ self._axinfo.update({
+ 'label': {'va': 'center', 'ha': 'center',
+ 'rotation_mode': 'anchor'},
+ 'color': mpl.rcParams[f'axes3d.{name}axis.panecolor'],
+ 'tick': {
+ 'inward_factor': 0.2,
+ 'outward_factor': 0.1,
+ },
+ })
+
+ if mpl.rcParams['_internal.classic_mode']:
+ self._axinfo.update({
+ 'axisline': {'linewidth': 0.75, 'color': (0, 0, 0, 1)},
+ 'grid': {
+ 'color': (0.9, 0.9, 0.9, 1),
+ 'linewidth': 1.0,
+ 'linestyle': '-',
+ },
+ })
+ self._axinfo['tick'].update({
+ 'linewidth': {
+ True: mpl.rcParams['lines.linewidth'], # major
+ False: mpl.rcParams['lines.linewidth'], # minor
+ }
+ })
+ else:
+ self._axinfo.update({
+ 'axisline': {
+ 'linewidth': mpl.rcParams['axes.linewidth'],
+ 'color': mpl.rcParams['axes.edgecolor'],
+ },
+ 'grid': {
+ 'color': mpl.rcParams['grid.color'],
+ 'linewidth': mpl.rcParams['grid.linewidth'],
+ 'linestyle': mpl.rcParams['grid.linestyle'],
+ },
+ })
+ self._axinfo['tick'].update({
+ 'linewidth': {
+ True: ( # major
+ mpl.rcParams['xtick.major.width'] if name in 'xz'
+ else mpl.rcParams['ytick.major.width']),
+ False: ( # minor
+ mpl.rcParams['xtick.minor.width'] if name in 'xz'
+ else mpl.rcParams['ytick.minor.width']),
+ }
+ })
+
+ super().__init__(axes, *args, **kwargs)
+
+ # data and viewing intervals for this direction
+ if "d_intervalx" in params:
+ self.set_data_interval(*params["d_intervalx"])
+ if "v_intervalx" in params:
+ self.set_view_interval(*params["v_intervalx"])
+ self.set_rotate_label(rotate_label)
+ self._init3d() # Inline after init3d deprecation elapses.
+
+ __init__.__signature__ = inspect.signature(_new_init)
+ adir = _api.deprecated("3.6", pending=True)(
+ property(lambda self: self.axis_name))
+
+ def _init3d(self):
+ self.line = mlines.Line2D(
+ xdata=(0, 0), ydata=(0, 0),
+ linewidth=self._axinfo['axisline']['linewidth'],
+ color=self._axinfo['axisline']['color'],
+ antialiased=True)
+
+ # Store dummy data in Polygon object
+ self.pane = mpatches.Polygon([[0, 0], [0, 1]], closed=False)
+ self.set_pane_color(self._axinfo['color'])
+
+ self.axes._set_artist_props(self.line)
+ self.axes._set_artist_props(self.pane)
+ self.gridlines = art3d.Line3DCollection([])
+ self.axes._set_artist_props(self.gridlines)
+ self.axes._set_artist_props(self.label)
+ self.axes._set_artist_props(self.offsetText)
+ # Need to be able to place the label at the correct location
+ self.label._transform = self.axes.transData
+ self.offsetText._transform = self.axes.transData
+
+ @_api.deprecated("3.6", pending=True)
+ def init3d(self): # After deprecation elapses, inline _init3d to __init__.
+ self._init3d()
+
+ def get_major_ticks(self, numticks=None):
+ ticks = super().get_major_ticks(numticks)
+ for t in ticks:
+ for obj in [
+ t.tick1line, t.tick2line, t.gridline, t.label1, t.label2]:
+ obj.set_transform(self.axes.transData)
+ return ticks
+
+ def get_minor_ticks(self, numticks=None):
+ ticks = super().get_minor_ticks(numticks)
+ for t in ticks:
+ for obj in [
+ t.tick1line, t.tick2line, t.gridline, t.label1, t.label2]:
+ obj.set_transform(self.axes.transData)
+ return ticks
+
+ def set_ticks_position(self, position):
+ """
+ Set the ticks position.
+
+ Parameters
+ ----------
+ position : {'lower', 'upper', 'both', 'default', 'none'}
+ The position of the bolded axis lines, ticks, and tick labels.
+ """
+ if position in ['top', 'bottom']:
+ _api.warn_deprecated('3.8', name=f'{position=}',
+ obj_type='argument value',
+ alternative="'upper' or 'lower'")
+ return
+ _api.check_in_list(['lower', 'upper', 'both', 'default', 'none'],
+ position=position)
+ self._tick_position = position
+
+ def get_ticks_position(self):
+ """
+ Get the ticks position.
+
+ Returns
+ -------
+ str : {'lower', 'upper', 'both', 'default', 'none'}
+ The position of the bolded axis lines, ticks, and tick labels.
+ """
+ return self._tick_position
+
+ def set_label_position(self, position):
+ """
+ Set the label position.
+
+ Parameters
+ ----------
+ position : {'lower', 'upper', 'both', 'default', 'none'}
+ The position of the axis label.
+ """
+ if position in ['top', 'bottom']:
+ _api.warn_deprecated('3.8', name=f'{position=}',
+ obj_type='argument value',
+ alternative="'upper' or 'lower'")
+ return
+ _api.check_in_list(['lower', 'upper', 'both', 'default', 'none'],
+ position=position)
+ self._label_position = position
+
+ def get_label_position(self):
+ """
+ Get the label position.
+
+ Returns
+ -------
+ str : {'lower', 'upper', 'both', 'default', 'none'}
+ The position of the axis label.
+ """
+ return self._label_position
+
+ def set_pane_color(self, color, alpha=None):
+ """
+ Set pane color.
+
+ Parameters
+ ----------
+ color : color
+ Color for axis pane.
+ alpha : float, optional
+ Alpha value for axis pane. If None, base it on *color*.
+ """
+ color = mcolors.to_rgba(color, alpha)
+ self._axinfo['color'] = color
+ self.pane.set_edgecolor(color)
+ self.pane.set_facecolor(color)
+ self.pane.set_alpha(color[-1])
+ self.stale = True
+
+ def set_rotate_label(self, val):
+ """
+ Whether to rotate the axis label: True, False or None.
+ If set to None the label will be rotated if longer than 4 chars.
+ """
+ self._rotate_label = val
+ self.stale = True
+
+ def get_rotate_label(self, text):
+ if self._rotate_label is not None:
+ return self._rotate_label
+ else:
+ return len(text) > 4
+
+ def _get_coord_info(self, renderer):
+ mins, maxs = np.array([
+ self.axes.get_xbound(),
+ self.axes.get_ybound(),
+ self.axes.get_zbound(),
+ ]).T
+
+ # Get the mean value for each bound:
+ centers = 0.5 * (maxs + mins)
+
+ # Add a small offset between min/max point and the edge of the plot:
+ deltas = (maxs - mins) / 12
+ mins -= 0.25 * deltas
+ maxs += 0.25 * deltas
+
+ # Project the bounds along the current position of the cube:
+ bounds = mins[0], maxs[0], mins[1], maxs[1], mins[2], maxs[2]
+ bounds_proj = self.axes._tunit_cube(bounds, self.axes.M)
+
+ # Determine which one of the parallel planes are higher up:
+ means_z0 = np.zeros(3)
+ means_z1 = np.zeros(3)
+ for i in range(3):
+ means_z0[i] = np.mean(bounds_proj[self._PLANES[2 * i], 2])
+ means_z1[i] = np.mean(bounds_proj[self._PLANES[2 * i + 1], 2])
+ highs = means_z0 < means_z1
+
+ # Special handling for edge-on views
+ equals = np.abs(means_z0 - means_z1) <= np.finfo(float).eps
+ if np.sum(equals) == 2:
+ vertical = np.where(~equals)[0][0]
+ if vertical == 2: # looking at XY plane
+ highs = np.array([True, True, highs[2]])
+ elif vertical == 1: # looking at XZ plane
+ highs = np.array([True, highs[1], False])
+ elif vertical == 0: # looking at YZ plane
+ highs = np.array([highs[0], False, False])
+
+ return mins, maxs, centers, deltas, bounds_proj, highs
+
+ def _get_axis_line_edge_points(self, minmax, maxmin, position=None):
+ """Get the edge points for the black bolded axis line."""
+ # When changing vertical axis some of the axes has to be
+ # moved to the other plane so it looks the same as if the z-axis
+ # was the vertical axis.
+ mb = [minmax, maxmin] # line from origin to nearest corner to camera
+ mb_rev = mb[::-1]
+ mm = [[mb, mb_rev, mb_rev], [mb_rev, mb_rev, mb], [mb, mb, mb]]
+ mm = mm[self.axes._vertical_axis][self._axinfo["i"]]
+
+ juggled = self._axinfo["juggled"]
+ edge_point_0 = mm[0].copy() # origin point
+
+ if ((position == 'lower' and mm[1][juggled[-1]] < mm[0][juggled[-1]]) or
+ (position == 'upper' and mm[1][juggled[-1]] > mm[0][juggled[-1]])):
+ edge_point_0[juggled[-1]] = mm[1][juggled[-1]]
+ else:
+ edge_point_0[juggled[0]] = mm[1][juggled[0]]
+
+ edge_point_1 = edge_point_0.copy()
+ edge_point_1[juggled[1]] = mm[1][juggled[1]]
+
+ return edge_point_0, edge_point_1
+
+ def _get_all_axis_line_edge_points(self, minmax, maxmin, axis_position=None):
+ # Determine edge points for the axis lines
+ edgep1s = []
+ edgep2s = []
+ position = []
+ if axis_position in (None, 'default'):
+ edgep1, edgep2 = self._get_axis_line_edge_points(minmax, maxmin)
+ edgep1s = [edgep1]
+ edgep2s = [edgep2]
+ position = ['default']
+ else:
+ edgep1_l, edgep2_l = self._get_axis_line_edge_points(minmax, maxmin,
+ position='lower')
+ edgep1_u, edgep2_u = self._get_axis_line_edge_points(minmax, maxmin,
+ position='upper')
+ if axis_position in ('lower', 'both'):
+ edgep1s.append(edgep1_l)
+ edgep2s.append(edgep2_l)
+ position.append('lower')
+ if axis_position in ('upper', 'both'):
+ edgep1s.append(edgep1_u)
+ edgep2s.append(edgep2_u)
+ position.append('upper')
+ return edgep1s, edgep2s, position
+
+ def _get_tickdir(self, position):
+ """
+ Get the direction of the tick.
+
+ Parameters
+ ----------
+ position : str, optional : {'upper', 'lower', 'default'}
+ The position of the axis.
+
+ Returns
+ -------
+ tickdir : int
+ Index which indicates which coordinate the tick line will
+ align with.
+ """
+ _api.check_in_list(('upper', 'lower', 'default'), position=position)
+
+ # TODO: Move somewhere else where it's triggered less:
+ tickdirs_base = [v["tickdir"] for v in self._AXINFO.values()] # default
+ elev_mod = np.mod(self.axes.elev + 180, 360) - 180
+ azim_mod = np.mod(self.axes.azim, 360)
+ if position == 'upper':
+ if elev_mod >= 0:
+ tickdirs_base = [2, 2, 0]
+ else:
+ tickdirs_base = [1, 0, 0]
+ if 0 <= azim_mod < 180:
+ tickdirs_base[2] = 1
+ elif position == 'lower':
+ if elev_mod >= 0:
+ tickdirs_base = [1, 0, 1]
+ else:
+ tickdirs_base = [2, 2, 1]
+ if 0 <= azim_mod < 180:
+ tickdirs_base[2] = 0
+ info_i = [v["i"] for v in self._AXINFO.values()]
+
+ i = self._axinfo["i"]
+ vert_ax = self.axes._vertical_axis
+ j = vert_ax - 2
+ # default: tickdir = [[1, 2, 1], [2, 2, 0], [1, 0, 0]][vert_ax][i]
+ tickdir = np.roll(info_i, -j)[np.roll(tickdirs_base, j)][i]
+ return tickdir
+
+ def active_pane(self, renderer):
+ mins, maxs, centers, deltas, tc, highs = self._get_coord_info(renderer)
+ info = self._axinfo
+ index = info['i']
+ if not highs[index]:
+ loc = mins[index]
+ plane = self._PLANES[2 * index]
+ else:
+ loc = maxs[index]
+ plane = self._PLANES[2 * index + 1]
+ xys = np.array([tc[p] for p in plane])
+ return xys, loc
+
+ def draw_pane(self, renderer):
+ """
+ Draw pane.
+
+ Parameters
+ ----------
+ renderer : `~matplotlib.backend_bases.RendererBase` subclass
+ """
+ renderer.open_group('pane3d', gid=self.get_gid())
+ xys, loc = self.active_pane(renderer)
+ self.pane.xy = xys[:, :2]
+ self.pane.draw(renderer)
+ renderer.close_group('pane3d')
+
+ def _axmask(self):
+ axmask = [True, True, True]
+ axmask[self._axinfo["i"]] = False
+ return axmask
+
+ def _draw_ticks(self, renderer, edgep1, centers, deltas, highs,
+ deltas_per_point, pos):
+ ticks = self._update_ticks()
+ info = self._axinfo
+ index = info["i"]
+
+ # Draw ticks:
+ tickdir = self._get_tickdir(pos)
+ tickdelta = deltas[tickdir] if highs[tickdir] else -deltas[tickdir]
+
+ tick_info = info['tick']
+ tick_out = tick_info['outward_factor'] * tickdelta
+ tick_in = tick_info['inward_factor'] * tickdelta
+ tick_lw = tick_info['linewidth']
+ edgep1_tickdir = edgep1[tickdir]
+ out_tickdir = edgep1_tickdir + tick_out
+ in_tickdir = edgep1_tickdir - tick_in
+
+ default_label_offset = 8. # A rough estimate
+ points = deltas_per_point * deltas
+ for tick in ticks:
+ # Get tick line positions
+ pos = edgep1.copy()
+ pos[index] = tick.get_loc()
+ pos[tickdir] = out_tickdir
+ x1, y1, z1 = proj3d.proj_transform(*pos, self.axes.M)
+ pos[tickdir] = in_tickdir
+ x2, y2, z2 = proj3d.proj_transform(*pos, self.axes.M)
+
+ # Get position of label
+ labeldeltas = (tick.get_pad() + default_label_offset) * points
+
+ pos[tickdir] = edgep1_tickdir
+ pos = _move_from_center(pos, centers, labeldeltas, self._axmask())
+ lx, ly, lz = proj3d.proj_transform(*pos, self.axes.M)
+
+ _tick_update_position(tick, (x1, x2), (y1, y2), (lx, ly))
+ tick.tick1line.set_linewidth(tick_lw[tick._major])
+ tick.draw(renderer)
+
+ def _draw_offset_text(self, renderer, edgep1, edgep2, labeldeltas, centers,
+ highs, pep, dx, dy):
+ # Get general axis information:
+ info = self._axinfo
+ index = info["i"]
+ juggled = info["juggled"]
+ tickdir = info["tickdir"]
+
+ # Which of the two edge points do we want to
+ # use for locating the offset text?
+ if juggled[2] == 2:
+ outeredgep = edgep1
+ outerindex = 0
+ else:
+ outeredgep = edgep2
+ outerindex = 1
+
+ pos = _move_from_center(outeredgep, centers, labeldeltas,
+ self._axmask())
+ olx, oly, olz = proj3d.proj_transform(*pos, self.axes.M)
+ self.offsetText.set_text(self.major.formatter.get_offset())
+ self.offsetText.set_position((olx, oly))
+ angle = art3d._norm_text_angle(np.rad2deg(np.arctan2(dy, dx)))
+ self.offsetText.set_rotation(angle)
+ # Must set rotation mode to "anchor" so that
+ # the alignment point is used as the "fulcrum" for rotation.
+ self.offsetText.set_rotation_mode('anchor')
+
+ # ----------------------------------------------------------------------
+ # Note: the following statement for determining the proper alignment of
+ # the offset text. This was determined entirely by trial-and-error
+ # and should not be in any way considered as "the way". There are
+ # still some edge cases where alignment is not quite right, but this
+ # seems to be more of a geometry issue (in other words, I might be
+ # using the wrong reference points).
+ #
+ # (TT, FF, TF, FT) are the shorthand for the tuple of
+ # (centpt[tickdir] <= pep[tickdir, outerindex],
+ # centpt[index] <= pep[index, outerindex])
+ #
+ # Three-letters (e.g., TFT, FTT) are short-hand for the array of bools
+ # from the variable 'highs'.
+ # ---------------------------------------------------------------------
+ centpt = proj3d.proj_transform(*centers, self.axes.M)
+ if centpt[tickdir] > pep[tickdir, outerindex]:
+ # if FT and if highs has an even number of Trues
+ if (centpt[index] <= pep[index, outerindex]
+ and np.count_nonzero(highs) % 2 == 0):
+ # Usually, this means align right, except for the FTT case,
+ # in which offset for axis 1 and 2 are aligned left.
+ if highs.tolist() == [False, True, True] and index in (1, 2):
+ align = 'left'
+ else:
+ align = 'right'
+ else:
+ # The FF case
+ align = 'left'
+ else:
+ # if TF and if highs has an even number of Trues
+ if (centpt[index] > pep[index, outerindex]
+ and np.count_nonzero(highs) % 2 == 0):
+ # Usually mean align left, except if it is axis 2
+ align = 'right' if index == 2 else 'left'
+ else:
+ # The TT case
+ align = 'right'
+
+ self.offsetText.set_va('center')
+ self.offsetText.set_ha(align)
+ self.offsetText.draw(renderer)
+
+ def _draw_labels(self, renderer, edgep1, edgep2, labeldeltas, centers, dx, dy):
+ label = self._axinfo["label"]
+
+ # Draw labels
+ lxyz = 0.5 * (edgep1 + edgep2)
+ lxyz = _move_from_center(lxyz, centers, labeldeltas, self._axmask())
+ tlx, tly, tlz = proj3d.proj_transform(*lxyz, self.axes.M)
+ self.label.set_position((tlx, tly))
+ if self.get_rotate_label(self.label.get_text()):
+ angle = art3d._norm_text_angle(np.rad2deg(np.arctan2(dy, dx)))
+ self.label.set_rotation(angle)
+ self.label.set_va(label['va'])
+ self.label.set_ha(label['ha'])
+ self.label.set_rotation_mode(label['rotation_mode'])
+ self.label.draw(renderer)
+
+ @artist.allow_rasterization
+ def draw(self, renderer):
+ self.label._transform = self.axes.transData
+ self.offsetText._transform = self.axes.transData
+ renderer.open_group("axis3d", gid=self.get_gid())
+
+ # Get general axis information:
+ mins, maxs, centers, deltas, tc, highs = self._get_coord_info(renderer)
+
+ # Calculate offset distances
+ # A rough estimate; points are ambiguous since 3D plots rotate
+ reltoinches = self.figure.dpi_scale_trans.inverted()
+ ax_inches = reltoinches.transform(self.axes.bbox.size)
+ ax_points_estimate = sum(72. * ax_inches)
+ deltas_per_point = 48 / ax_points_estimate
+ default_offset = 21.
+ labeldeltas = (self.labelpad + default_offset) * deltas_per_point * deltas
+
+ # Determine edge points for the axis lines
+ minmax = np.where(highs, maxs, mins) # "origin" point
+ maxmin = np.where(~highs, maxs, mins) # "opposite" corner near camera
+
+ for edgep1, edgep2, pos in zip(*self._get_all_axis_line_edge_points(
+ minmax, maxmin, self._tick_position)):
+ # Project the edge points along the current position
+ pep = proj3d._proj_trans_points([edgep1, edgep2], self.axes.M)
+ pep = np.asarray(pep)
+
+ # The transAxes transform is used because the Text object
+ # rotates the text relative to the display coordinate system.
+ # Therefore, if we want the labels to remain parallel to the
+ # axis regardless of the aspect ratio, we need to convert the
+ # edge points of the plane to display coordinates and calculate
+ # an angle from that.
+ # TODO: Maybe Text objects should handle this themselves?
+ dx, dy = (self.axes.transAxes.transform([pep[0:2, 1]]) -
+ self.axes.transAxes.transform([pep[0:2, 0]]))[0]
+
+ # Draw the lines
+ self.line.set_data(pep[0], pep[1])
+ self.line.draw(renderer)
+
+ # Draw ticks
+ self._draw_ticks(renderer, edgep1, centers, deltas, highs,
+ deltas_per_point, pos)
+
+ # Draw Offset text
+ self._draw_offset_text(renderer, edgep1, edgep2, labeldeltas,
+ centers, highs, pep, dx, dy)
+
+ for edgep1, edgep2, pos in zip(*self._get_all_axis_line_edge_points(
+ minmax, maxmin, self._label_position)):
+ # See comments above
+ pep = proj3d._proj_trans_points([edgep1, edgep2], self.axes.M)
+ pep = np.asarray(pep)
+ dx, dy = (self.axes.transAxes.transform([pep[0:2, 1]]) -
+ self.axes.transAxes.transform([pep[0:2, 0]]))[0]
+
+ # Draw labels
+ self._draw_labels(renderer, edgep1, edgep2, labeldeltas, centers, dx, dy)
+
+ renderer.close_group('axis3d')
+ self.stale = False
+
+ @artist.allow_rasterization
+ def draw_grid(self, renderer):
+ if not self.axes._draw_grid:
+ return
+
+ renderer.open_group("grid3d", gid=self.get_gid())
+
+ ticks = self._update_ticks()
+ if len(ticks):
+ # Get general axis information:
+ info = self._axinfo
+ index = info["i"]
+
+ mins, maxs, _, _, _, highs = self._get_coord_info(renderer)
+
+ minmax = np.where(highs, maxs, mins)
+ maxmin = np.where(~highs, maxs, mins)
+
+ # Grid points where the planes meet
+ xyz0 = np.tile(minmax, (len(ticks), 1))
+ xyz0[:, index] = [tick.get_loc() for tick in ticks]
+
+ # Grid lines go from the end of one plane through the plane
+ # intersection (at xyz0) to the end of the other plane. The first
+ # point (0) differs along dimension index-2 and the last (2) along
+ # dimension index-1.
+ lines = np.stack([xyz0, xyz0, xyz0], axis=1)
+ lines[:, 0, index - 2] = maxmin[index - 2]
+ lines[:, 2, index - 1] = maxmin[index - 1]
+ self.gridlines.set_segments(lines)
+ gridinfo = info['grid']
+ self.gridlines.set_color(gridinfo['color'])
+ self.gridlines.set_linewidth(gridinfo['linewidth'])
+ self.gridlines.set_linestyle(gridinfo['linestyle'])
+ self.gridlines.do_3d_projection()
+ self.gridlines.draw(renderer)
+
+ renderer.close_group('grid3d')
+
+ # TODO: Get this to work (more) properly when mplot3d supports the
+ # transforms framework.
+ def get_tightbbox(self, renderer=None, *, for_layout_only=False):
+ # docstring inherited
+ if not self.get_visible():
+ return
+ # We have to directly access the internal data structures
+ # (and hope they are up to date) because at draw time we
+ # shift the ticks and their labels around in (x, y) space
+ # based on the projection, the current view port, and their
+ # position in 3D space. If we extend the transforms framework
+ # into 3D we would not need to do this different book keeping
+ # than we do in the normal axis
+ major_locs = self.get_majorticklocs()
+ minor_locs = self.get_minorticklocs()
+
+ ticks = [*self.get_minor_ticks(len(minor_locs)),
+ *self.get_major_ticks(len(major_locs))]
+ view_low, view_high = self.get_view_interval()
+ if view_low > view_high:
+ view_low, view_high = view_high, view_low
+ interval_t = self.get_transform().transform([view_low, view_high])
+
+ ticks_to_draw = []
+ for tick in ticks:
+ try:
+ loc_t = self.get_transform().transform(tick.get_loc())
+ except AssertionError:
+ # Transform.transform doesn't allow masked values but
+ # some scales might make them, so we need this try/except.
+ pass
+ else:
+ if mtransforms._interval_contains_close(interval_t, loc_t):
+ ticks_to_draw.append(tick)
+
+ ticks = ticks_to_draw
+
+ bb_1, bb_2 = self._get_ticklabel_bboxes(ticks, renderer)
+ other = []
+
+ if self.line.get_visible():
+ other.append(self.line.get_window_extent(renderer))
+ if (self.label.get_visible() and not for_layout_only and
+ self.label.get_text()):
+ other.append(self.label.get_window_extent(renderer))
+
+ return mtransforms.Bbox.union([*bb_1, *bb_2, *other])
+
+ d_interval = _api.deprecated(
+ "3.6", alternative="get_data_interval", pending=True)(
+ property(lambda self: self.get_data_interval(),
+ lambda self, minmax: self.set_data_interval(*minmax)))
+ v_interval = _api.deprecated(
+ "3.6", alternative="get_view_interval", pending=True)(
+ property(lambda self: self.get_view_interval(),
+ lambda self, minmax: self.set_view_interval(*minmax)))
+
+
+class XAxis(Axis):
+ axis_name = "x"
+ get_view_interval, set_view_interval = maxis._make_getset_interval(
+ "view", "xy_viewLim", "intervalx")
+ get_data_interval, set_data_interval = maxis._make_getset_interval(
+ "data", "xy_dataLim", "intervalx")
+
+
+class YAxis(Axis):
+ axis_name = "y"
+ get_view_interval, set_view_interval = maxis._make_getset_interval(
+ "view", "xy_viewLim", "intervaly")
+ get_data_interval, set_data_interval = maxis._make_getset_interval(
+ "data", "xy_dataLim", "intervaly")
+
+
+class ZAxis(Axis):
+ axis_name = "z"
+ get_view_interval, set_view_interval = maxis._make_getset_interval(
+ "view", "zz_viewLim", "intervalx")
+ get_data_interval, set_data_interval = maxis._make_getset_interval(
+ "data", "zz_dataLim", "intervalx")