| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,7 @@ | |||
| 1 | + Fixed errorbars in polar plots | ||
| 2 | + ------------------------------ | ||
| 3 | + Caps and error lines are now drawn with respect to polar coordinates, | ||
| 4 | + when plotting errorbars on polar plots. | ||
| 5 | + | ||
| 6 | + .. figure:: /gallery/pie_and_polar_charts/images/sphx_glr_polar_error_caps_001.png | ||
| 7 | + :target: ../../gallery/pie_and_polar_charts/polar_error_caps.html | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,53 @@ | |||
| 1 | + """ | ||
| 2 | + ================================= | ||
| 3 | + Error bar rendering on polar axis | ||
| 4 | + ================================= | ||
| 5 | + | ||
| 6 | + Demo of error bar plot in polar coordinates. | ||
| 7 | + Theta error bars are curved lines ended with caps oriented towards the | ||
| 8 | + center. | ||
| 9 | + Radius error bars are straight lines oriented towards center with | ||
| 10 | + perpendicular caps. | ||
| 11 | + """ | ||
| 12 | + import numpy as np | ||
| 13 | + import matplotlib.pyplot as plt | ||
| 14 | + | ||
| 15 | + theta = np.arange(0, 2 * np.pi, np.pi / 4) | ||
| 16 | + r = theta / np.pi / 2 + 0.5 | ||
| 17 | + | ||
| 18 | + fig = plt.figure(figsize=(10, 10)) | ||
| 19 | + ax = fig.add_subplot(projection='polar') | ||
| 20 | + ax.errorbar(theta, r, xerr=0.25, yerr=0.1, capsize=7, fmt="o", c="seagreen") | ||
| 21 | + ax.set_title("Pretty polar error bars") | ||
| 22 | + plt.show() | ||
| 23 | + | ||
| 24 | + ############################################################################# | ||
| 25 | + # Please acknowledge that large theta error bars will be overlapping. | ||
| 26 | + # This may reduce readability of the output plot. See example figure below: | ||
| 27 | + | ||
| 28 | + fig = plt.figure(figsize=(10, 10)) | ||
| 29 | + ax = fig.add_subplot(projection='polar') | ||
| 30 | + ax.errorbar(theta, r, xerr=5.25, yerr=0.1, capsize=7, fmt="o", c="darkred") | ||
| 31 | + ax.set_title("Overlapping theta error bars") | ||
| 32 | + plt.show() | ||
| 33 | + | ||
| 34 | + ############################################################################# | ||
| 35 | + # On the other hand, large radius error bars will never overlap, they just | ||
| 36 | + # lead to unwanted scale in the data, reducing the displayed range. | ||
| 37 | + | ||
| 38 | + fig = plt.figure(figsize=(10, 10)) | ||
| 39 | + ax = fig.add_subplot(projection='polar') | ||
| 40 | + ax.errorbar(theta, r, xerr=0.25, yerr=10.1, capsize=7, fmt="o", c="orangered") | ||
| 41 | + ax.set_title("Large radius error bars") | ||
| 42 | + plt.show() | ||
| 43 | + | ||
| 44 | + | ||
| 45 | + ############################################################################# | ||
| 46 | + # | ||
| 47 | + # .. admonition:: References | ||
| 48 | + # | ||
| 49 | + # The use of the following functions, methods, classes and modules is shown | ||
| 50 | + # in this example: | ||
| 51 | + # | ||
| 52 | + # - `matplotlib.axes.Axes.errorbar` / `matplotlib.pyplot.errorbar` | ||
| 53 | + # - `matplotlib.projections.polar` | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -3573,10 +3573,11 @@ def _upcast_err(err): | |||
| 3573 | 3573 | eb_cap_style['color'] = ecolor | |
| 3574 | 3574 | ||
| 3575 | 3575 | barcols = [] | |
| 3576 | - caplines = [] | ||
| 3576 | + caplines = {'x': [], 'y': []} | ||
| 3577 | 3577 | ||
| 3578 | 3578 | # Vectorized fancy-indexer. | |
| 3579 | - def apply_mask(arrays, mask): return [array[mask] for array in arrays] | ||
| 3579 | + def apply_mask(arrays, mask): | ||
| 3580 | + return [array[mask] for array in arrays] | ||
| 3580 | 3581 | ||
| 3581 | 3582 | # dep: dependent dataset, indep: independent dataset | |
| 3582 | 3583 | for (dep_axis, dep, err, lolims, uplims, indep, lines_func, | |
@@ -3607,9 +3608,12 @@ def apply_mask(arrays, mask): return [array[mask] for array in arrays] | |||
| 3607 | 3608 | # return dep - elow * ~lolims, dep + ehigh * ~uplims | |
| 3608 | 3609 | # except that broadcast_to would strip units. | |
| 3609 | 3610 | low, high = dep + np.row_stack([-(1 - lolims), 1 - uplims]) * err | |
| 3610 | - | ||
| 3611 | 3611 | barcols.append(lines_func( | |
| 3612 | 3612 | *apply_mask([indep, low, high], everymask), **eb_lines_style)) | |
| 3613 | + if self.name == "polar" and dep_axis == "x": | ||
| 3614 | + for b in barcols: | ||
| 3615 | + for p in b.get_paths(): | ||
| 3616 | + p._interpolation_steps = 2 | ||
| 3613 | 3617 | # Normal errorbars for points without upper/lower limits. | |
| 3614 | 3618 | nolims = ~(lolims | uplims) | |
| 3615 | 3619 | if nolims.any() and capsize > 0: | |
@@ -3622,7 +3626,7 @@ def apply_mask(arrays, mask): return [array[mask] for array in arrays] | |||
| 3622 | 3626 | line = mlines.Line2D(indep_masked, indep_masked, | |
| 3623 | 3627 | marker=marker, **eb_cap_style) | |
| 3624 | 3628 | line.set(**{f"{dep_axis}data": lh_masked}) | |
| 3625 | - caplines.append(line) | ||
| 3629 | + caplines[dep_axis].append(line) | ||
| 3626 | 3630 | for idx, (lims, hl) in enumerate([(lolims, high), (uplims, low)]): | |
| 3627 | 3631 | if not lims.any(): | |
| 3628 | 3632 | continue | |
@@ -3636,15 +3640,29 @@ def apply_mask(arrays, mask): return [array[mask] for array in arrays] | |||
| 3636 | 3640 | line = mlines.Line2D(x_masked, y_masked, | |
| 3637 | 3641 | marker=hlmarker, **eb_cap_style) | |
| 3638 | 3642 | line.set(**{f"{dep_axis}data": hl_masked}) | |
| 3639 | - caplines.append(line) | ||
| 3643 | + caplines[dep_axis].append(line) | ||
| 3640 | 3644 | if capsize > 0: | |
| 3641 | - caplines.append(mlines.Line2D( | ||
| 3645 | + caplines[dep_axis].append(mlines.Line2D( | ||
| 3642 | 3646 | x_masked, y_masked, marker=marker, **eb_cap_style)) | |
| 3643 | - | ||
| 3644 | - for l in caplines: | ||
| 3645 | - self.add_line(l) | ||
| 3647 | + if self.name == 'polar': | ||
| 3648 | + for axis in caplines: | ||
| 3649 | + for l in caplines[axis]: | ||
| 3650 | + # Rotate caps to be perpendicular to the error bars | ||
| 3651 | + for theta, r in zip(l.get_xdata(), l.get_ydata()): | ||
| 3652 | + rotation = mtransforms.Affine2D().rotate(theta) | ||
| 3653 | + if axis == 'y': | ||
| 3654 | + rotation.rotate(-np.pi / 2) | ||
| 3655 | + ms = mmarkers.MarkerStyle(marker=marker, | ||
| 3656 | + transform=rotation) | ||
| 3657 | + self.add_line(mlines.Line2D([theta], [r], marker=ms, | ||
| 3658 | + **eb_cap_style)) | ||
| 3659 | + else: | ||
| 3660 | + for axis in caplines: | ||
| 3661 | + for l in caplines[axis]: | ||
| 3662 | + self.add_line(l) | ||
| 3646 | 3663 | ||
| 3647 | 3664 | self._request_autoscale_view() | |
| 3665 | + caplines = caplines['x'] + caplines['y'] | ||
| 3648 | 3666 | errorbar_container = ErrorbarContainer( | |
| 3649 | 3667 | (data_line, tuple(caplines), tuple(barcols)), | |
| 3650 | 3668 | has_xerr=(xerr is not None), has_yerr=(yerr is not None), | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -3687,6 +3687,41 @@ def test_errorbar(): | |||
| 3687 | 3687 | ax.set_title("Simplest errorbars, 0.2 in x, 0.4 in y") | |
| 3688 | 3688 | ||
| 3689 | 3689 | ||
| 3690 | + @image_comparison(['mixed_errorbar_polar_caps'], extensions=['png'], | ||
| 3691 | + remove_text=True) | ||
| 3692 | + def test_mixed_errorbar_polar_caps(): | ||
| 3693 | + """ | ||
| 3694 | + Mix several polar errorbar use cases in a single test figure. | ||
| 3695 | + | ||
| 3696 | + It is advisable to position individual points off the grid. If there are | ||
| 3697 | + problems with reproducibility of this test, consider removing grid. | ||
| 3698 | + """ | ||
| 3699 | + fig = plt.figure() | ||
| 3700 | + ax = plt.subplot(111, projection='polar') | ||
| 3701 | + | ||
| 3702 | + # symmetric errorbars | ||
| 3703 | + th_sym = [1, 2, 3] | ||
| 3704 | + r_sym = [0.9]*3 | ||
| 3705 | + ax.errorbar(th_sym, r_sym, xerr=0.35, yerr=0.2, fmt="o") | ||
| 3706 | + | ||
| 3707 | + # long errorbars | ||
| 3708 | + th_long = [np.pi/2 + .1, np.pi + .1] | ||
| 3709 | + r_long = [1.8, 2.2] | ||
| 3710 | + ax.errorbar(th_long, r_long, xerr=0.8 * np.pi, yerr=0.15, fmt="o") | ||
| 3711 | + | ||
| 3712 | + # asymmetric errorbars | ||
| 3713 | + th_asym = [4*np.pi/3 + .1, 5*np.pi/3 + .1, 2*np.pi-0.1] | ||
| 3714 | + r_asym = [1.1]*3 | ||
| 3715 | + xerr = [[.3, .3, .2], [.2, .3, .3]] | ||
| 3716 | + yerr = [[.35, .5, .5], [.5, .35, .5]] | ||
| 3717 | + ax.errorbar(th_asym, r_asym, xerr=xerr, yerr=yerr, fmt="o") | ||
| 3718 | + | ||
| 3719 | + # overlapping errorbar | ||
| 3720 | + th_over = [2.1] | ||
| 3721 | + r_over = [3.1] | ||
| 3722 | + ax.errorbar(th_over, r_over, xerr=10, yerr=.2, fmt="o") | ||
| 3723 | + | ||
| 3724 | + | ||
| 3690 | 3725 | def test_errorbar_colorcycle(): | |
| 3691 | 3726 | ||
| 3692 | 3727 | f, ax = plt.subplots() | |
| Back | FazBrowse Home | New Git URL |
0 commit comments