Skip to content

Commit cc70dcf

Browse files
Implement 3d axis positions
1 parent 9965954 commit cc70dcf

File tree

1 file changed

+64
-38
lines changed

1 file changed

+64
-38
lines changed

lib/mpl_toolkits/mplot3d/axis3d.py

Lines changed: 64 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ def __init__(self, *args, **kwargs):
7676

7777
name = self.axis_name
7878

79+
self.position = 'auto'
80+
7981
# This is a temporary member variable.
8082
# Do not depend on this existing in future releases!
8183
self._axinfo = self._AXINFO[name].copy()
@@ -225,8 +227,7 @@ def _get_coord_info(self, renderer):
225227
# Get the mean value for each bound:
226228
centers = 0.5 * (maxs + mins)
227229

228-
# Add a small offset between min/max point and the edge of the
229-
# plot:
230+
# Add a small offset between min/max point and the edge of the plot:
230231
deltas = (maxs - mins) / 12
231232
mins -= 0.25 * deltas
232233
maxs += 0.25 * deltas
@@ -256,19 +257,26 @@ def _get_coord_info(self, renderer):
256257

257258
return mins, maxs, centers, deltas, bounds_proj, highs
258259

259-
def _get_axis_line_edge_points(self, minmax, maxmin):
260+
def _get_axis_line_edge_points(self, minmax, maxmin, position=None):
260261
"""Get the edge points for the black bolded axis line."""
261262
# When changing vertical axis some of the axes has to be
262263
# moved to the other plane so it looks the same as if the z-axis
263264
# was the vertical axis.
264-
mb = [minmax, maxmin]
265+
mb = [minmax, maxmin] # line from origin to near invisible corner
265266
mb_rev = mb[::-1]
266267
mm = [[mb, mb_rev, mb_rev], [mb_rev, mb_rev, mb], [mb, mb, mb]]
267268
mm = mm[self.axes._vertical_axis][self._axinfo["i"]]
268269

269270
juggled = self._axinfo["juggled"]
270-
edge_point_0 = mm[0].copy()
271-
edge_point_0[juggled[0]] = mm[1][juggled[0]]
271+
edge_point_0 = mm[0].copy() # origin point
272+
273+
if ( (position == 'lower'
274+
and mm[1][juggled[-1]] < mm[0][juggled[-1]])
275+
or (position == 'upper'
276+
and mm[1][juggled[-1]] > mm[0][juggled[-1]])):
277+
edge_point_0[juggled[-1]] = mm[1][juggled[-1]]
278+
else:
279+
edge_point_0[juggled[0]] = mm[1][juggled[0]]
272280

273281
edge_point_1 = edge_point_0.copy()
274282
edge_point_1[juggled[1]] = mm[1][juggled[1]]
@@ -372,7 +380,8 @@ def _draw_ticks(self, renderer, edgep1, deltas_per_point):
372380
tick.draw(renderer)
373381

374382

375-
def _draw_offset_text(self, renderer, edgep1, edgep2, labeldeltas, pep, dx, dy):
383+
def _draw_offset_text(self, renderer, edgep1, edgep2, labeldeltas, pep,
384+
dx, dy):
376385
mins, maxs, centers, deltas, tc, highs = self._get_coord_info(renderer)
377386

378387
# Get general axis information:
@@ -389,7 +398,8 @@ def _draw_offset_text(self, renderer, edgep1, edgep2, labeldeltas, pep, dx, dy):
389398
outeredgep = edgep2
390399
outerindex = 1
391400

392-
pos = _move_from_center(outeredgep, centers, labeldeltas, self._axmask())
401+
pos = _move_from_center(outeredgep, centers, labeldeltas,
402+
self._axmask())
393403
olx, oly, olz = proj3d.proj_transform(*pos, self.axes.M)
394404
self.offsetText.set_text(self.major.formatter.get_offset())
395405
self.offsetText.set_position((olx, oly))
@@ -470,19 +480,6 @@ def draw(self, renderer):
470480
# Get general axis information:
471481
mins, maxs, centers, deltas, tc, highs = self._get_coord_info(renderer)
472482

473-
minmax = np.where(highs, maxs, mins)
474-
maxmin = np.where(~highs, maxs, mins)
475-
476-
# Create edge points for the black bolded axis line:
477-
edgep1, edgep2 = self._get_axis_line_edge_points(minmax, maxmin)
478-
479-
# Draw the lines
480-
# Project the edge points along the current position
481-
pep = proj3d._proj_trans_points([edgep1, edgep2], self.axes.M)
482-
pep = np.asarray(pep)
483-
self.line.set_data(pep[0], pep[1])
484-
self.line.draw(renderer)
485-
486483
# Calculate offset distances
487484
# A rough estimate; points are ambiguous since 3D plots rotate
488485
reltoinches = self.figure.dpi_scale_trans.inverted()
@@ -492,24 +489,53 @@ def draw(self, renderer):
492489
default_offset = 21.
493490
labeldeltas = (
494491
(self.labelpad + default_offset) * deltas_per_point * deltas)
495-
# The transAxes transform is used because the Text object
496-
# rotates the text relative to the display coordinate system.
497-
# Therefore, if we want the labels to remain parallel to the
498-
# axis regardless of the aspect ratio, we need to convert the
499-
# edge points of the plane to display coordinates and calculate
500-
# an angle from that.
501-
# TODO: Maybe Text objects should handle this themselves?
502-
dx, dy = (self.axes.transAxes.transform([pep[0:2, 1]]) -
503-
self.axes.transAxes.transform([pep[0:2, 0]]))[0]
504-
505-
# Draw labels
506-
self._draw_labels(renderer, edgep1, edgep2, labeldeltas, dx, dy)
507492

508-
# Draw Offset text
509-
self._draw_offset_text(renderer, edgep1, edgep2, labeldeltas, pep, dx, dy)
510-
511-
# Draw ticks
512-
self._draw_ticks(renderer, edgep1, deltas_per_point)
493+
# Determine edge points for the axis lines
494+
edgep1s = []
495+
edgep2s = []
496+
minmax = np.where(highs, maxs, mins) # "origin" point
497+
maxmin = np.where(~highs, maxs, mins) # "opposite" corner near camera
498+
if self.position == 'auto':
499+
edgep1, edgep2 = self._get_axis_line_edge_points(minmax, maxmin)
500+
edgep1s = [edgep1]
501+
edgep2s = [edgep2]
502+
else:
503+
edgep1_l, edgep2_l = self._get_axis_line_edge_points(minmax, maxmin, position='lower')
504+
edgep1_u, edgep2_u = self._get_axis_line_edge_points(minmax, maxmin, position='upper')
505+
if self.position in ('lower', 'both'):
506+
edgep1s.append(edgep1_l)
507+
edgep2s.append(edgep2_l)
508+
if self.position in ('upper', 'both'):
509+
edgep1s.append(edgep1_u)
510+
edgep2s.append(edgep2_u)
511+
512+
for edgep1, edgep2 in zip(edgep1s, edgep2s):
513+
# Draw the lines
514+
# Project the edge points along the current position
515+
pep = proj3d._proj_trans_points([edgep1, edgep2], self.axes.M)
516+
pep = np.asarray(pep)
517+
self.line.set_data(pep[0], pep[1])
518+
self.line.draw(renderer)
519+
520+
# The transAxes transform is used because the Text object
521+
# rotates the text relative to the display coordinate system.
522+
# Therefore, if we want the labels to remain parallel to the
523+
# axis regardless of the aspect ratio, we need to convert the
524+
# edge points of the plane to display coordinates and calculate
525+
# an angle from that.
526+
# TODO: Maybe Text objects should handle this themselves?
527+
dx, dy = (self.axes.transAxes.transform([pep[0:2, 1]]) -
528+
self.axes.transAxes.transform([pep[0:2, 0]]))[0]
529+
530+
# Draw labels
531+
self._draw_labels(renderer, edgep1, edgep2, labeldeltas, dx, dy)
532+
533+
# Draw Offset text
534+
self._draw_offset_text(renderer, edgep1, edgep2, labeldeltas, pep,
535+
dx, dy)
536+
537+
# Draw ticks
538+
self._draw_ticks(renderer, edgep1, deltas_per_point)
513539

514540
renderer.close_group('axis3d')
515541
self.stale = False

0 commit comments

Comments
 (0)