New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix manual contour label positions on sparse contours #1865
Changes from 6 commits
283f358
d6a70a7
2cfb857
4ab3dd9
438c779
3cd97ea
0b72b18
b2c0352
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -572,6 +572,18 @@ def add_label_near(self, x, y, inline=True, inline_spacing=5, | |
conmin, segmin, imin, xmin, ymin = self.find_nearest_contour( | ||
x, y, self.labelIndiceList)[:5] | ||
|
||
# The calc_label_rot_and_inline routine requires that (xmin,ymin) | ||
# be a vertex in the path. So, if it isn't, add a vertex here | ||
paths = self.collections[conmin].get_paths() | ||
lc = paths[segmin].vertices | ||
if transform: | ||
xcmin = transform.inverted().transform([xmin, ymin]) | ||
else: | ||
xcmin = np.array([xmin, ymin]) | ||
if not np.allclose(xcmin, lc[imin]): | ||
lc = np.r_[lc[:imin], np.array(xcmin)[None, :], lc[imin:]] | ||
paths[segmin] = mpath.Path(lc) | ||
|
||
# Get index of nearest level in subset of levels used for labeling | ||
lmin = self.labelIndiceList.index(conmin) | ||
|
||
|
@@ -621,8 +633,8 @@ def labels(self, inline, inline_spacing): | |
add_label = self.add_label | ||
|
||
for icon, lev, fsize, cvalue in zip( | ||
self.labelIndiceList, self.labelLevelList, self.labelFontSizeList, | ||
self.labelCValueList): | ||
self.labelIndiceList, self.labelLevelList, | ||
self.labelFontSizeList, self.labelCValueList): | ||
|
||
con = self.collections[icon] | ||
trans = con.get_transform() | ||
|
@@ -674,6 +686,64 @@ def labels(self, inline, inline_spacing): | |
paths.extend(additions) | ||
|
||
|
||
def _find_closest_point_on_leg(p1, p2, p0): | ||
'''find closest point to p0 on line segment connecting p1 and p2''' | ||
|
||
# handle degenerate case | ||
if np.all(p2 == p1): | ||
d = np.sum((p0 - p1)**2) | ||
return d, p1 | ||
|
||
d21 = p2 - p1 | ||
d01 = p0 - p1 | ||
|
||
# project on to line segment to find closest point | ||
proj = np.dot(d01, d21) / np.dot(d21, d21) | ||
if proj < 0: | ||
proj = 0 | ||
if proj > 1: | ||
proj = 1 | ||
pc = p1 + proj * d21 | ||
|
||
# find squared distance | ||
d = np.sum((pc-p0)**2) | ||
|
||
return d, pc | ||
|
||
|
||
def _find_closest_point_on_path(lc, point): | ||
''' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And here. |
||
lc: coordinates of vertices | ||
point: coordinates of test point | ||
''' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ditto. |
||
|
||
# find index of closest vertex for this segment | ||
ds = np.sum((lc - point[None, :])**2, 1) | ||
imin = np.argmin(ds) | ||
|
||
dmin = 1e10 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we use numpy's infinity here, just to be safe? >>> import numpy as np
>>> 1e10 < np.inf
True |
||
xcmin = None | ||
legmin = (None, None) | ||
|
||
closed = mlab.is_closed_polygon(lc) | ||
|
||
# build list of legs before and after this vertex | ||
legs = [] | ||
if imin > 0 or closed: | ||
legs.append(((imin-1) % len(lc), imin)) | ||
if imin < len(lc) - 1 or closed: | ||
legs.append((imin, (imin+1) % len(lc))) | ||
|
||
for leg in legs: | ||
d, xc = _find_closest_point_on_leg(lc[leg[0]], lc[leg[1]], point) | ||
if d < dmin: | ||
dmin = d | ||
xcmin = xc | ||
legmin = leg | ||
|
||
return (dmin, xcmin, legmin) | ||
|
||
|
||
class ContourSet(cm.ScalarMappable, ContourLabeler): | ||
""" | ||
Store a set of contour lines or filled regions. | ||
|
@@ -832,12 +902,13 @@ def __init__(self, ax, *args, **kwargs): | |
paths = self._make_paths(segs, kinds) | ||
# Default zorder taken from Collection | ||
zorder = kwargs.get('zorder', 1) | ||
col = mcoll.PathCollection(paths, | ||
antialiaseds=(self.antialiased,), | ||
edgecolors='none', | ||
alpha=self.alpha, | ||
transform=self.get_transform(), | ||
zorder=zorder) | ||
col = mcoll.PathCollection( | ||
paths, | ||
antialiaseds=(self.antialiased,), | ||
edgecolors='none', | ||
alpha=self.alpha, | ||
transform=self.get_transform(), | ||
zorder=zorder) | ||
self.ax.add_collection(col) | ||
self.collections.append(col) | ||
else: | ||
|
@@ -851,13 +922,14 @@ def __init__(self, ax, *args, **kwargs): | |
zip(self.levels, tlinewidths, tlinestyles, self.allsegs): | ||
# Default zorder taken from LineCollection | ||
zorder = kwargs.get('zorder', 2) | ||
col = mcoll.LineCollection(segs, | ||
antialiaseds=aa, | ||
linewidths=width, | ||
linestyle=[lstyle], | ||
alpha=self.alpha, | ||
transform=self.get_transform(), | ||
zorder=zorder) | ||
col = mcoll.LineCollection( | ||
segs, | ||
antialiaseds=aa, | ||
linewidths=width, | ||
linestyle=[lstyle], | ||
alpha=self.alpha, | ||
transform=self.get_transform(), | ||
zorder=zorder) | ||
col.set_label('_nolegend_') | ||
self.ax.add_collection(col, False) | ||
self.collections.append(col) | ||
|
@@ -902,29 +974,27 @@ def legend_elements(self, variable_name='x', str_format=str): | |
n_levels = len(self.collections) | ||
|
||
for i, (collection, lower, upper) in enumerate( | ||
zip(self.collections, | ||
lowers, uppers)): | ||
patch = mpatches.Rectangle( | ||
(0, 0), 1, 1, | ||
facecolor=collection.get_facecolor()[0], | ||
hatch=collection.get_hatch(), | ||
alpha=collection.get_alpha(), | ||
) | ||
artists.append(patch) | ||
|
||
lower = str_format(lower) | ||
upper = str_format(upper) | ||
|
||
if i == 0 and self.extend in ('min', 'both'): | ||
labels.append(r'$%s \leq %s$' % (variable_name, | ||
lower)) | ||
elif i == n_levels - 1 and self.extend in ('max', 'both'): | ||
labels.append(r'$%s > %s$' % (variable_name, | ||
upper)) | ||
else: | ||
labels.append(r'$%s < %s \leq %s$' % (lower, | ||
variable_name, | ||
upper)) | ||
zip(self.collections, lowers, uppers)): | ||
patch = mpatches.Rectangle( | ||
(0, 0), 1, 1, | ||
facecolor=collection.get_facecolor()[0], | ||
hatch=collection.get_hatch(), | ||
alpha=collection.get_alpha()) | ||
artists.append(patch) | ||
|
||
lower = str_format(lower) | ||
upper = str_format(upper) | ||
|
||
if i == 0 and self.extend in ('min', 'both'): | ||
labels.append(r'$%s \leq %s$' % (variable_name, | ||
lower)) | ||
elif i == n_levels - 1 and self.extend in ('max', 'both'): | ||
labels.append(r'$%s > %s$' % (variable_name, | ||
upper)) | ||
else: | ||
labels.append(r'$%s < %s \leq %s$' % (lower, | ||
variable_name, | ||
upper)) | ||
else: | ||
for collection, level in zip(self.collections, self.levels): | ||
|
||
|
@@ -963,7 +1033,7 @@ def _process_args(self, *args, **kwargs): | |
|
||
# Check length of allkinds. | ||
if (self.allkinds is not None and | ||
len(self.allkinds) != len(self.allsegs)): | ||
len(self.allkinds) != len(self.allsegs)): | ||
raise ValueError('allkinds has different length to allsegs') | ||
|
||
# Determine x,y bounds and update axes data limits. | ||
|
@@ -1262,26 +1332,27 @@ def find_nearest_contour(self, x, y, indices=None, pixel=True): | |
xmin = None | ||
ymin = None | ||
|
||
point = np.array([x, y]) | ||
|
||
for icon in indices: | ||
con = self.collections[icon] | ||
trans = con.get_transform() | ||
paths = con.get_paths() | ||
|
||
for segNum, linepath in enumerate(paths): | ||
lc = linepath.vertices | ||
|
||
# transfer all data points to screen coordinates if desired | ||
if pixel: | ||
lc = trans.transform(lc) | ||
|
||
ds = (lc[:, 0] - x) ** 2 + (lc[:, 1] - y) ** 2 | ||
d = min(ds) | ||
d, xc, leg = _find_closest_point_on_path(lc, point) | ||
if d < dmin: | ||
dmin = d | ||
conmin = icon | ||
segmin = segNum | ||
imin = mpl.mlab.find(ds == d)[0] | ||
xmin = lc[imin, 0] | ||
ymin = lc[imin, 1] | ||
imin = leg[1] | ||
xmin = xc[0] | ||
ymin = xc[1] | ||
|
||
return (conmin, segmin, imin, xmin, ymin, dmin) | ||
|
||
|
@@ -1340,7 +1411,7 @@ def _process_args(self, *args, **kwargs): | |
# if the transform is not trans data, and some part of it | ||
# contains transData, transform the xs and ys to data coordinates | ||
if (t != self.ax.transData and | ||
any(t.contains_branch_seperately(self.ax.transData))): | ||
any(t.contains_branch_seperately(self.ax.transData))): | ||
trans_to_data = t - self.ax.transData | ||
pts = (np.vstack([x.flat, y.flat]).T) | ||
transformed_pts = trans_to_data.transform(pts) | ||
|
@@ -1450,11 +1521,11 @@ def _check_xyz(self, args, kwargs): | |
|
||
if x.shape != z.shape: | ||
raise TypeError("Shape of x does not match that of z: found " | ||
"{0} instead of {1}.".format(x.shape, z.shape)) | ||
"{0} instead of {1}.".format(x.shape, z.shape)) | ||
|
||
if y.shape != z.shape: | ||
raise TypeError("Shape of y does not match that of z: found " | ||
"{0} instead of {1}.".format(y.shape, z.shape)) | ||
"{0} instead of {1}.".format(y.shape, z.shape)) | ||
|
||
else: | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you make these triple double quotes,
"""
?