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

Skip to content

Commit 4d2aae6

Browse files
authored
Merge pull request #16959 from anntzer/connpatch
Simplify and robustify ConnectionPatch coordinates conversion.
2 parents f843ffd + 952a322 commit 4d2aae6

File tree

2 files changed

+61
-115
lines changed

2 files changed

+61
-115
lines changed

lib/matplotlib/patches.py

Lines changed: 40 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -4243,108 +4243,54 @@ def __init__(self, xyA, xyB, coordsA, coordsB=None,
42434243
# if True, draw annotation only if self.xy is inside the axes
42444244
self._annotation_clip = None
42454245

4246-
def _get_xy(self, x, y, s, axes=None):
4246+
def _get_xy(self, xy, s, axes=None):
42474247
"""Calculate the pixel position of given point."""
4248+
s0 = s # For the error message, if needed.
42484249
if axes is None:
42494250
axes = self.axes
4251+
xy = np.array(xy)
4252+
if s in ["figure points", "axes points"]:
4253+
xy *= self.figure.dpi / 72
4254+
s = s.replace("points", "pixels")
4255+
elif s == "figure fraction":
4256+
s = self.figure.transFigure
4257+
elif s == "axes fraction":
4258+
s = axes.transAxes
4259+
x, y = xy
42504260

42514261
if s == 'data':
42524262
trans = axes.transData
42534263
x = float(self.convert_xunits(x))
42544264
y = float(self.convert_yunits(y))
42554265
return trans.transform((x, y))
42564266
elif s == 'offset points':
4257-
# convert the data point
4258-
dx, dy = self.xy
4259-
4260-
# prevent recursion
4261-
if self.xycoords == 'offset points':
4262-
return self._get_xy(dx, dy, 'data')
4263-
4264-
dx, dy = self._get_xy(dx, dy, self.xycoords)
4265-
4266-
# convert the offset
4267-
dpi = self.figure.get_dpi()
4268-
x *= dpi / 72.
4269-
y *= dpi / 72.
4270-
4271-
# add the offset to the data point
4272-
x += dx
4273-
y += dy
4274-
4275-
return x, y
4267+
if self.xycoords == 'offset points': # prevent recursion
4268+
return self._get_xy(self.xy, 'data')
4269+
return (
4270+
self._get_xy(self.xy, self.xycoords) # converted data point
4271+
+ xy * self.figure.dpi / 72) # converted offset
42764272
elif s == 'polar':
42774273
theta, r = x, y
42784274
x = r * np.cos(theta)
42794275
y = r * np.sin(theta)
42804276
trans = axes.transData
42814277
return trans.transform((x, y))
4282-
elif s == 'figure points':
4283-
# points from the lower left corner of the figure
4284-
dpi = self.figure.dpi
4285-
l, b, w, h = self.figure.bbox.bounds
4286-
r = l + w
4287-
t = b + h
4288-
4289-
x *= dpi / 72.
4290-
y *= dpi / 72.
4291-
if x < 0:
4292-
x = r + x
4293-
if y < 0:
4294-
y = t + y
4295-
return x, y
42964278
elif s == 'figure pixels':
42974279
# pixels from the lower left corner of the figure
4298-
l, b, w, h = self.figure.bbox.bounds
4299-
r = l + w
4300-
t = b + h
4301-
if x < 0:
4302-
x = r + x
4303-
if y < 0:
4304-
y = t + y
4305-
return x, y
4306-
elif s == 'figure fraction':
4307-
# (0, 0) is lower left, (1, 1) is upper right of figure
4308-
trans = self.figure.transFigure
4309-
return trans.transform((x, y))
4310-
elif s == 'axes points':
4311-
# points from the lower left corner of the axes
4312-
dpi = self.figure.dpi
4313-
l, b, w, h = axes.bbox.bounds
4314-
r = l + w
4315-
t = b + h
4316-
if x < 0:
4317-
x = r + x * dpi / 72.
4318-
else:
4319-
x = l + x * dpi / 72.
4320-
if y < 0:
4321-
y = t + y * dpi / 72.
4322-
else:
4323-
y = b + y * dpi / 72.
4280+
bb = self.figure.bbox
4281+
x = bb.x0 + x if x >= 0 else bb.x1 + x
4282+
y = bb.y0 + y if y >= 0 else bb.y1 + y
43244283
return x, y
43254284
elif s == 'axes pixels':
43264285
# pixels from the lower left corner of the axes
4327-
l, b, w, h = axes.bbox.bounds
4328-
r = l + w
4329-
t = b + h
4330-
if x < 0:
4331-
x = r + x
4332-
else:
4333-
x = l + x
4334-
if y < 0:
4335-
y = t + y
4336-
else:
4337-
y = b + y
4286+
bb = axes.bbox
4287+
x = bb.x0 + x if x >= 0 else bb.x1 + x
4288+
y = bb.y0 + y if y >= 0 else bb.y1 + y
43384289
return x, y
4339-
elif s == 'axes fraction':
4340-
# (0, 0) is lower left, (1, 1) is upper right of axes
4341-
trans = axes.transAxes
4342-
return trans.transform((x, y))
43434290
elif isinstance(s, transforms.Transform):
4344-
return s.transform((x, y))
4291+
return s.transform(xy)
43454292
else:
4346-
raise ValueError("{} is not a valid coordinate "
4347-
"transformation.".format(s))
4293+
raise ValueError(f"{s0} is not a valid coordinate transformation")
43484294

43494295
def set_annotation_clip(self, b):
43504296
"""
@@ -4374,39 +4320,29 @@ def get_annotation_clip(self):
43744320

43754321
def get_path_in_displaycoord(self):
43764322
"""Return the mutated path of the arrow in display coordinates."""
4377-
43784323
dpi_cor = self.get_dpi_cor()
4379-
4380-
x, y = self.xy1
4381-
posA = self._get_xy(x, y, self.coords1, self.axesA)
4382-
4383-
x, y = self.xy2
4384-
posB = self._get_xy(x, y, self.coords2, self.axesB)
4385-
4386-
_path = self.get_connectionstyle()(posA, posB,
4387-
patchA=self.patchA,
4388-
patchB=self.patchB,
4389-
shrinkA=self.shrinkA * dpi_cor,
4390-
shrinkB=self.shrinkB * dpi_cor
4391-
)
4392-
4393-
_path, fillable = self.get_arrowstyle()(
4394-
_path,
4395-
self.get_mutation_scale() * dpi_cor,
4396-
self.get_linewidth() * dpi_cor,
4397-
self.get_mutation_aspect()
4398-
)
4399-
4400-
return _path, fillable
4324+
posA = self._get_xy(self.xy1, self.coords1, self.axesA)
4325+
posB = self._get_xy(self.xy2, self.coords2, self.axesB)
4326+
path = self.get_connectionstyle()(
4327+
posA, posB,
4328+
patchA=self.patchA, patchB=self.patchB,
4329+
shrinkA=self.shrinkA * dpi_cor, shrinkB=self.shrinkB * dpi_cor,
4330+
)
4331+
path, fillable = self.get_arrowstyle()(
4332+
path,
4333+
self.get_mutation_scale() * dpi_cor,
4334+
self.get_linewidth() * dpi_cor,
4335+
self.get_mutation_aspect()
4336+
)
4337+
return path, fillable
44014338

44024339
def _check_xy(self, renderer):
44034340
"""Check whether the annotation needs to be drawn."""
44044341

44054342
b = self.get_annotation_clip()
44064343

44074344
if b or (b is None and self.coords1 == "data"):
4408-
x, y = self.xy1
4409-
xy_pixel = self._get_xy(x, y, self.coords1, self.axesA)
4345+
xy_pixel = self._get_xy(self.xy1, self.coords1, self.axesA)
44104346
if self.axesA is None:
44114347
axes = self.axes
44124348
else:
@@ -4415,8 +4351,7 @@ def _check_xy(self, renderer):
44154351
return False
44164352

44174353
if b or (b is None and self.coords2 == "data"):
4418-
x, y = self.xy2
4419-
xy_pixel = self._get_xy(x, y, self.coords2, self.axesB)
4354+
xy_pixel = self._get_xy(self.xy2, self.coords2, self.axesB)
44204355
if self.axesB is None:
44214356
axes = self.axes
44224357
else:

lib/matplotlib/tests/test_patches.py

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -394,16 +394,27 @@ def test_connection_patch():
394394
ax2.add_artist(con)
395395

396396

397-
def test_connection_patch_fig():
398-
# Test that connection patch can be added as figure artist
399-
fig, (ax1, ax2) = plt.subplots(1, 2)
400-
xy = (0.3, 0.2)
401-
con = mpatches.ConnectionPatch(xyA=xy, xyB=xy,
402-
coordsA="data", coordsB="data",
403-
axesA=ax1, axesB=ax2,
404-
arrowstyle="->", shrinkB=5)
405-
fig.add_artist(con)
406-
fig.canvas.draw()
397+
@check_figures_equal(extensions=["png"])
398+
def test_connection_patch_fig(fig_test, fig_ref):
399+
# Test that connection patch can be added as figure artist, and that figure
400+
# pixels count negative values from the top right corner (this API may be
401+
# changed in the future).
402+
ax1, ax2 = fig_test.subplots(1, 2)
403+
con = mpatches.ConnectionPatch(
404+
xyA=(.3, .2), coordsA="data", axesA=ax1,
405+
xyB=(-30, -20), coordsB="figure pixels",
406+
arrowstyle="->", shrinkB=5)
407+
fig_test.add_artist(con)
408+
409+
ax1, ax2 = fig_ref.subplots(1, 2)
410+
bb = fig_ref.bbox
411+
# Necessary so that pixel counts match on both sides.
412+
plt.rcParams["savefig.dpi"] = plt.rcParams["figure.dpi"]
413+
con = mpatches.ConnectionPatch(
414+
xyA=(.3, .2), coordsA="data", axesA=ax1,
415+
xyB=(bb.width - 30, bb.height - 20), coordsB="figure pixels",
416+
arrowstyle="->", shrinkB=5)
417+
fig_ref.add_artist(con)
407418

408419

409420
def test_datetime_rectangle():

0 commit comments

Comments
 (0)