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

Skip to content

Commit d3b5623

Browse files
committed
Sync caps of errorbar3d with 2d.
There are a few differences that cause some image changes: * When both upper and lower limits are True, `errorbar3d` incorrectly used full errorbar length for them. They should both have 0 errorbar with the arrow-head cap. * The arrow-head cap should use `eb_cap_style`, not `eb_lines_style`. This meant that the capsize defaulted to 0, so this was made explicitly non-zero in the test. * The baseline of the triangle (bottom/top for caps above/below a line) in 2D is aligned with the end of the errorbar, *not* the tip of the triangle, so all quivers in 3D shifted outward to match. * The quiver would preferably not overlap the existing errorbar, which I've hopefully achieved by setting the length based on `capsize`, and using the above positioning. Consequently, `arrow_length_ratio` is no longer exposed.
1 parent cf7e6fb commit d3b5623

File tree

4 files changed

+40
-37
lines changed

4 files changed

+40
-37
lines changed

lib/mpl_toolkits/mplot3d/axes3d.py

Lines changed: 39 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2997,7 +2997,7 @@ def errorbar(self, x, y, z, zerr=None, yerr=None, xerr=None, fmt='',
29972997
barsabove=False, errorevery=1, ecolor=None, elinewidth=None,
29982998
capsize=None, capthick=None, xlolims=False, xuplims=False,
29992999
ylolims=False, yuplims=False, zlolims=False, zuplims=False,
3000-
arrow_length_ratio=.4, **kwargs):
3000+
**kwargs):
30013001
"""
30023002
Plot lines and/or markers with errorbars around them.
30033003
@@ -3074,10 +3074,6 @@ def errorbar(self, x, y, z, zerr=None, yerr=None, xerr=None, fmt='',
30743074
Used to avoid overlapping error bars when two series share x-axis
30753075
values.
30763076
3077-
arrow_length_ratio : float, default: 0.4
3078-
Passed to :meth:`quiver`, the ratio of the arrow head with respect
3079-
to the quiver.
3080-
30813077
Returns
30823078
-------
30833079
errlines : list
@@ -3183,7 +3179,7 @@ def errorbar(self, x, y, z, zerr=None, yerr=None, xerr=None, fmt='',
31833179
eb_lines_style[key] = kwargs[key]
31843180

31853181
# Make the style dict for caps (the "hats").
3186-
eb_cap_style = {**base_style, 'linestyle': 'none'}
3182+
eb_cap_style = {**base_style, 'linestyle': 'None'}
31873183
if capsize is None:
31883184
capsize = rcParams["errorbar.capsize"]
31893185
if capsize > 0:
@@ -3207,14 +3203,8 @@ def _extract_errs(err, data, lomask, himask):
32073203
else:
32083204
low_err, high_err = err, err
32093205

3210-
# for compatibility with the 2d errorbar function, when both upper
3211-
# and lower limits specified, we need to draw the markers / line
3212-
common_mask = (lomask == himask) & everymask
3213-
_lomask = lomask | common_mask
3214-
_himask = himask | common_mask
3215-
3216-
lows = np.where(_lomask, data - low_err, data)
3217-
highs = np.where(_himask, data + high_err, data)
3206+
lows = np.where(lomask | ~everymask, data, data - low_err)
3207+
highs = np.where(himask | ~everymask, data, data + high_err)
32183208

32193209
return lows, highs
32203210

@@ -3229,6 +3219,33 @@ def _extract_errs(err, data, lomask, himask):
32293219
capmarker = {0: '|', 1: '|', 2: '_'}
32303220
i_xyz = {'x': 0, 'y': 1, 'z': 2}
32313221

3222+
# Calculate marker size from points to quiver length. Because these are
3223+
# not markers, and 3D Axes do not use the normal transform stack, this
3224+
# is a bit involved. Since the quiver arrows will change size as the
3225+
# scene is rotated, they are given a standard size based on viewing
3226+
# them directly in planar form.
3227+
quiversize = eb_cap_style.get('markersize',
3228+
rcParams['lines.markersize']) ** 2
3229+
quiversize *= self.figure.dpi / 72
3230+
quiversize = self.transAxes.inverted().transform([
3231+
(0, 0), (quiversize, quiversize)])
3232+
quiversize = np.mean(np.diff(quiversize, axis=0))
3233+
# quiversize is now in Axes coordinates, and to convert back to data
3234+
# coordinates, we need to run it through the inverse 3D transform. For
3235+
# consistency, this uses a fixed azimuth and elevation.
3236+
with cbook._setattr_cm(self, azim=0, elev=0):
3237+
invM = np.linalg.inv(self.get_proj())
3238+
# azim=elev=0 produces the Y-Z plane, so quiversize in 2D 'x' is 'y' in
3239+
# 3D, hence the 1 index.
3240+
quiversize = np.dot(invM, np.array([quiversize, 0, 0, 0]))[1]
3241+
# Quivers use a fixed 15-degree arrow head, so scale up the length so
3242+
# that the size corresponds to the base. In other words, this constant
3243+
# corresponds to the equation tan(15) = (base / 2) / (arrow length).
3244+
quiversize *= 1.8660254037844388
3245+
eb_quiver_style = {**eb_cap_style,
3246+
'length': quiversize, 'arrow_length_ratio': 1}
3247+
eb_quiver_style.pop('markersize', None)
3248+
32323249
# loop over x-, y-, and z-direction and draw relevant elements
32333250
for zdir, data, err, lolims, uplims in zip(
32343251
['x', 'y', 'z'], [x, y, z], [xerr, yerr, zerr],
@@ -3249,18 +3266,16 @@ def _extract_errs(err, data, lomask, himask):
32493266
lolims = np.broadcast_to(lolims, len(data)).astype(bool)
32503267
uplims = np.broadcast_to(uplims, len(data)).astype(bool)
32513268

3252-
nolims = ~(lolims | uplims)
3253-
32543269
# a nested list structure that expands to (xl,xh),(yl,yh),(zl,zh),
32553270
# where x/y/z and l/h correspond to dimensions and low/high
32563271
# positions of errorbars in a dimension we're looping over
32573272
coorderr = [
3258-
_extract_errs(err * dir_vector[i], coord,
3259-
~lolims & everymask, ~uplims & everymask)
3273+
_extract_errs(err * dir_vector[i], coord, lolims, uplims)
32603274
for i, coord in enumerate([x, y, z])]
32613275
(xl, xh), (yl, yh), (zl, zh) = coorderr
32623276

32633277
# draws capmarkers - flat caps orthogonal to the error bars
3278+
nolims = ~(lolims | uplims)
32643279
if nolims.any() and capsize > 0:
32653280
lo_caps_xyz = _apply_mask([xl, yl, zl], nolims & everymask)
32663281
hi_caps_xyz = _apply_mask([xh, yh, zh], nolims & everymask)
@@ -3278,24 +3293,12 @@ def _extract_errs(err, data, lomask, himask):
32783293
caplines.append(cap_lo)
32793294
caplines.append(cap_hi)
32803295

3281-
if (lolims | uplims).any():
3282-
limits = [
3283-
_extract_errs(err*dir_vector[i], coord, uplims, lolims)
3284-
for i, coord in enumerate([x, y, z])]
3285-
3286-
(xlo, xup), (ylo, yup), (zlo, zup) = limits
3287-
lomask = lolims & everymask
3288-
upmask = uplims & everymask
3289-
lolims_xyz = np.array(_apply_mask([xlo, ylo, zlo], upmask))
3290-
uplims_xyz = np.array(_apply_mask([xup, yup, zup], lomask))
3291-
lo_xyz = np.array(_apply_mask([x, y, z], upmask))
3292-
up_xyz = np.array(_apply_mask([x, y, z], lomask))
3293-
x0, y0, z0 = np.concatenate([lo_xyz, up_xyz], axis=-1)
3294-
dx, dy, dz = np.concatenate([lolims_xyz - lo_xyz,
3295-
uplims_xyz - up_xyz], axis=-1)
3296-
self.quiver(x0, y0, z0, dx, dy, dz,
3297-
arrow_length_ratio=arrow_length_ratio,
3298-
**eb_lines_style)
3296+
if lolims.any():
3297+
xh0, yh0, zh0 = _apply_mask([xh, yh, zh], lolims & everymask)
3298+
self.quiver(xh0, yh0, zh0, *dir_vector, **eb_quiver_style)
3299+
if uplims.any():
3300+
xl0, yl0, zl0 = _apply_mask([xl, yl, zl], uplims & everymask)
3301+
self.quiver(xl0, yl0, zl0, *-dir_vector, **eb_quiver_style)
32993302

33003303
errline = art3d.Line3DCollection(np.array(coorderr).T,
33013304
**eb_lines_style)

lib/mpl_toolkits/tests/test_mplot3d.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1245,7 +1245,7 @@ def test_errorbar3d_errorevery():
12451245
zlolims = (i % estep == 0) & (i // estep % 3 == 2)
12461246

12471247
ax.errorbar(x, y, z, 0.2, zuplims=zuplims, zlolims=zlolims,
1248-
errorevery=estep)
1248+
errorevery=estep, capsize=2)
12491249

12501250

12511251
@mpl3d_image_comparison(['errorbar3d.png'])

0 commit comments

Comments
 (0)