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

Skip to content

Commit 463349a

Browse files
authored
Merge pull request #14998 from anntzer/spines
FIX: nonlinear spine positions & inline Spine._calc_offset_transform into get_spine_transform.
2 parents 8ed827e + e2786a4 commit 463349a

File tree

2 files changed

+78
-111
lines changed

2 files changed

+78
-111
lines changed

lib/matplotlib/spines.py

Lines changed: 56 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010

1111

1212
class Spine(mpatches.Patch):
13-
"""an axis spine -- the line noting the data area boundaries
13+
"""
14+
An axis spine -- the line noting the data area boundaries
1415
1516
Spines are the lines connecting the axis tick marks and noting the
1617
boundaries of the data area. They can be placed at arbitrary
@@ -166,10 +167,8 @@ def get_window_extent(self, renderer=None):
166167
--------
167168
matplotlib.axes.Axes.get_tightbbox
168169
matplotlib.axes.Axes.get_window_extent
169-
170170
"""
171-
# make sure the location is updated so that transforms etc are
172-
# correct:
171+
# make sure the location is updated so that transforms etc are correct:
173172
self._adjust_location()
174173
bb = super().get_window_extent(renderer=renderer)
175174
if self.axis is None:
@@ -197,18 +196,18 @@ def get_window_extent(self, renderer=None):
197196
padin = padin * tickl / 72 * self.figure.dpi
198197

199198
if tick.tick1line.get_visible():
200-
if self.spine_type in ['left']:
199+
if self.spine_type == 'left':
201200
bb0.x0 = bb0.x0 - padout
202201
bb0.x1 = bb0.x1 + padin
203-
elif self.spine_type in ['bottom']:
202+
elif self.spine_type == 'bottom':
204203
bb0.y0 = bb0.y0 - padout
205204
bb0.y1 = bb0.y1 + padin
206205

207206
if tick.tick2line.get_visible():
208-
if self.spine_type in ['right']:
207+
if self.spine_type == 'right':
209208
bb0.x1 = bb0.x1 + padout
210209
bb0.x0 = bb0.x0 - padin
211-
elif self.spine_type in ['top']:
210+
elif self.spine_type == 'top':
212211
bb0.y1 = bb0.y1 + padout
213212
bb0.y0 = bb0.y0 - padout
214213
bboxes.append(bb0)
@@ -379,80 +378,6 @@ def draw(self, renderer):
379378
self.stale = False
380379
return ret
381380

382-
def _calc_offset_transform(self):
383-
"""Calculate the offset transform performed by the spine."""
384-
self._ensure_position_is_set()
385-
position = self._position
386-
if isinstance(position, str):
387-
if position == 'center':
388-
position = ('axes', 0.5)
389-
elif position == 'zero':
390-
position = ('data', 0)
391-
assert len(position) == 2, "position should be 2-tuple"
392-
position_type, amount = position
393-
assert position_type in ('axes', 'outward', 'data')
394-
if position_type == 'outward':
395-
if amount == 0:
396-
# short circuit commonest case
397-
self._spine_transform = ('identity',
398-
mtransforms.IdentityTransform())
399-
elif self.spine_type in ['left', 'right', 'top', 'bottom']:
400-
offset_vec = {'left': (-1, 0),
401-
'right': (1, 0),
402-
'bottom': (0, -1),
403-
'top': (0, 1),
404-
}[self.spine_type]
405-
# calculate x and y offset in dots
406-
offset_x = amount * offset_vec[0] / 72.0
407-
offset_y = amount * offset_vec[1] / 72.0
408-
self._spine_transform = ('post',
409-
mtransforms.ScaledTranslation(
410-
offset_x,
411-
offset_y,
412-
self.figure.dpi_scale_trans))
413-
else:
414-
cbook._warn_external('unknown spine type "%s": no spine '
415-
'offset performed' % self.spine_type)
416-
self._spine_transform = ('identity',
417-
mtransforms.IdentityTransform())
418-
elif position_type == 'axes':
419-
if self.spine_type in ('left', 'right'):
420-
self._spine_transform = ('pre',
421-
mtransforms.Affine2D.from_values(
422-
# keep y unchanged, fix x at
423-
# amount
424-
0, 0, 0, 1, amount, 0))
425-
elif self.spine_type in ('bottom', 'top'):
426-
self._spine_transform = ('pre',
427-
mtransforms.Affine2D.from_values(
428-
# keep x unchanged, fix y at
429-
# amount
430-
1, 0, 0, 0, 0, amount))
431-
else:
432-
cbook._warn_external('unknown spine type "%s": no spine '
433-
'offset performed' % self.spine_type)
434-
self._spine_transform = ('identity',
435-
mtransforms.IdentityTransform())
436-
elif position_type == 'data':
437-
if self.spine_type in ('right', 'top'):
438-
# The right and top spines have a default position of 1 in
439-
# axes coordinates. When specifying the position in data
440-
# coordinates, we need to calculate the position relative to 0.
441-
amount -= 1
442-
if self.spine_type in ('left', 'right'):
443-
self._spine_transform = ('data',
444-
mtransforms.Affine2D().translate(
445-
amount, 0))
446-
elif self.spine_type in ('bottom', 'top'):
447-
self._spine_transform = ('data',
448-
mtransforms.Affine2D().translate(
449-
0, amount))
450-
else:
451-
cbook._warn_external('unknown spine type "%s": no spine '
452-
'offset performed' % self.spine_type)
453-
self._spine_transform = ('identity',
454-
mtransforms.IdentityTransform())
455-
456381
def set_position(self, position):
457382
"""Set the position of the spine.
458383
@@ -484,7 +409,6 @@ def set_position(self, position):
484409
raise ValueError("position[0] should be one of 'outward', "
485410
"'axes', or 'data' ")
486411
self._position = position
487-
self._calc_offset_transform()
488412

489413
self.set_transform(self.get_spine_transform())
490414

@@ -500,39 +424,61 @@ def get_position(self):
500424
def get_spine_transform(self):
501425
"""Return the spine transform."""
502426
self._ensure_position_is_set()
503-
what, how = self._spine_transform
504-
505-
if what == 'data':
506-
# special case data based spine locations
507-
data_xform = self.axes.transScale + \
508-
(how + self.axes.transLimits + self.axes.transAxes)
509-
if self.spine_type in ['left', 'right']:
510-
result = mtransforms.blended_transform_factory(
511-
data_xform, self.axes.transData)
512-
elif self.spine_type in ['top', 'bottom']:
513-
result = mtransforms.blended_transform_factory(
514-
self.axes.transData, data_xform)
515-
else:
516-
raise ValueError('unknown spine spine_type: %s' %
517-
self.spine_type)
518-
return result
519427

428+
position = self._position
429+
if isinstance(position, str):
430+
if position == 'center':
431+
position = ('axes', 0.5)
432+
elif position == 'zero':
433+
position = ('data', 0)
434+
assert len(position) == 2, 'position should be 2-tuple'
435+
position_type, amount = position
436+
cbook._check_in_list(['axes', 'outward', 'data'],
437+
position_type=position_type)
520438
if self.spine_type in ['left', 'right']:
521439
base_transform = self.axes.get_yaxis_transform(which='grid')
522440
elif self.spine_type in ['top', 'bottom']:
523441
base_transform = self.axes.get_xaxis_transform(which='grid')
524442
else:
525-
raise ValueError('unknown spine spine_type: %s' %
526-
self.spine_type)
527-
528-
if what == 'identity':
529-
return base_transform
530-
elif what == 'post':
531-
return base_transform + how
532-
elif what == 'pre':
533-
return how + base_transform
534-
else:
535-
raise ValueError("unknown spine_transform type: %s" % what)
443+
raise ValueError(f'unknown spine spine_type: {self.spine_type!r}')
444+
445+
if position_type == 'outward':
446+
if amount == 0: # short circuit commonest case
447+
return base_transform
448+
else:
449+
offset_vec = {'left': (-1, 0), 'right': (1, 0),
450+
'bottom': (0, -1), 'top': (0, 1),
451+
}[self.spine_type]
452+
# calculate x and y offset in dots
453+
offset_dots = amount * np.array(offset_vec) / 72
454+
return (base_transform
455+
+ mtransforms.ScaledTranslation(
456+
*offset_dots, self.figure.dpi_scale_trans))
457+
elif position_type == 'axes':
458+
if self.spine_type in ['left', 'right']:
459+
# keep y unchanged, fix x at amount
460+
return (mtransforms.Affine2D.from_values(0, 0, 0, 1, amount, 0)
461+
+ base_transform)
462+
elif self.spine_type in ['bottom', 'top']:
463+
# keep x unchanged, fix y at amount
464+
return (mtransforms.Affine2D.from_values(1, 0, 0, 0, 0, amount)
465+
+ base_transform)
466+
elif position_type == 'data':
467+
if self.spine_type in ('right', 'top'):
468+
# The right and top spines have a default position of 1 in
469+
# axes coordinates. When specifying the position in data
470+
# coordinates, we need to calculate the position relative to 0.
471+
amount -= 1
472+
if self.spine_type in ('left', 'right'):
473+
return mtransforms.blended_transform_factory(
474+
mtransforms.Affine2D().translate(amount, 0)
475+
+ self.axes.transData,
476+
self.axes.transData)
477+
elif self.spine_type in ('bottom', 'top'):
478+
return mtransforms.blended_transform_factory(
479+
self.axes.transData,
480+
mtransforms.Affine2D().translate(0, amount)
481+
+ self.axes.transData)
536482

537483
def set_bounds(self, low=None, high=None):
538484
"""

lib/matplotlib/tests/test_spines.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import numpy as np
22

33
import matplotlib.pyplot as plt
4-
from matplotlib.testing.decorators import image_comparison
4+
from matplotlib.testing.decorators import check_figures_equal, image_comparison
55

66

77
@image_comparison(['spines_axes_positions'])
@@ -33,6 +33,27 @@ def test_spines_data_positions():
3333
ax.set_ylim([-2, 2])
3434

3535

36+
@check_figures_equal(extensions=["png"])
37+
def test_spine_nonlinear_data_positions(fig_test, fig_ref):
38+
plt.style.use("default")
39+
40+
ax = fig_test.add_subplot()
41+
ax.set(xscale="log", xlim=(.1, 1))
42+
# Use position="data" to visually swap the left and right spines, using
43+
# linewidth to distinguish them. The calls to tick_params removes labels
44+
# (for image comparison purposes) and harmonizes tick positions with the
45+
# reference).
46+
ax.spines["left"].set_position(("data", 1))
47+
ax.spines["left"].set_linewidth(2)
48+
ax.spines["right"].set_position(("data", .1))
49+
ax.tick_params(axis="y", labelleft=False, direction="in")
50+
51+
ax = fig_ref.add_subplot()
52+
ax.set(xscale="log", xlim=(.1, 1))
53+
ax.spines["right"].set_linewidth(2)
54+
ax.tick_params(axis="y", labelleft=False, left=False, right=True)
55+
56+
3657
@image_comparison(['spines_capstyle'])
3758
def test_spines_capstyle():
3859
# issue 2542

0 commit comments

Comments
 (0)