|
| 1 | +""" |
| 2 | +Style desription information that is shared across unrelated classses. |
| 3 | +""" |
| 4 | + |
| 5 | +from enum import Enum |
| 6 | +from matplotlib import cbook |
| 7 | + |
| 8 | + |
| 9 | +def _deprecate_case_insensitive_join_cap(s): |
| 10 | + s_low = s.lower() |
| 11 | + if s != s_low: |
| 12 | + if s_low in ['miter', 'round', 'bevel']: |
| 13 | + cbook.warn_deprecated( |
| 14 | + "3.3", message="Case-insensitive capstyles are deprecated " |
| 15 | + "since %(since)s and support for them will be removed " |
| 16 | + "%(removal)s; please pass them in lowercase.") |
| 17 | + elif s_low in ['butt', 'round', 'projecting']: |
| 18 | + cbook.warn_deprecated( |
| 19 | + "3.3", message="Case-insensitive joinstyles are deprecated " |
| 20 | + "since %(since)s and support for them will be removed " |
| 21 | + "%(removal)s; please pass them in lowercase.") |
| 22 | + # Else, error out at the check_in_list stage. |
| 23 | + return s_low |
| 24 | + |
| 25 | + |
| 26 | +class JoinStyle(Enum): |
| 27 | + """ |
| 28 | + Define how the connection between two line segments is drawn. |
| 29 | +
|
| 30 | + For a simple visual description of each *JoinStyle*, `view these docs |
| 31 | + online <JoinStyle>`, or simply run `JoinStyle.demo`. |
| 32 | +
|
| 33 | + .. plot:: |
| 34 | + :alt: Demo of possible JoinStyle's |
| 35 | +
|
| 36 | + from matplotlib._types import JoinStyle |
| 37 | + JoinStyle.demo() |
| 38 | +
|
| 39 | + For a more precise description, we first recall that lines in Matplotlib |
| 40 | + are typically defined by a 1D `~.path.Path` and a finite ``linewidth``, |
| 41 | + where the underlying 1D `~.path.Path` represents the center of the stroked |
| 42 | + line. |
| 43 | +
|
| 44 | + By default, `~.backend_bases.GraphicsContextBase` defines the boundaries of |
| 45 | + a stroked line to simply be every point within some radius, |
| 46 | + ``linewidth/2``, away from any point of the center line. However, this |
| 47 | + results in corners appearing "rounded", which may not be the desired |
| 48 | + behavior if you are drawing, for example, a polygon or pointed star. |
| 49 | +
|
| 50 | + Matplotlib provides three options for defining how the corners where two |
| 51 | + segments meet should be drawn. In short: |
| 52 | +
|
| 53 | + - *miter* is the "arrow-tip" style. Each boundary of the filled-in area |
| 54 | + will extend in a straight line parallel to the tangent vector of the |
| 55 | + centerline at the point it meets the corner, until they meet in a |
| 56 | + sharp point. |
| 57 | + - *round* stokes every point within a radius of ``linewidth/2`` of the |
| 58 | + center lines. |
| 59 | + - *bevel* is the "squared-off" style. It can be thought of as a rounded |
| 60 | + corner where the "circular" part of the corner has been cut off. |
| 61 | +
|
| 62 | + .. note:: |
| 63 | +
|
| 64 | + The *miter* option can be controller further by specifying a "miter |
| 65 | + limit", which specifies how long a miter tip can get before it is |
| 66 | + automatically "bevel"ed off. Matplotlib does not currently expose a |
| 67 | + ``miterlimit`` parameter to the user, and most backends simply use the |
| 68 | + upstream default value. For example, the PDF backend assumes the |
| 69 | + default value of 10 specified by the PDF standard, while the SVG |
| 70 | + backend does not even specify the miter limit, resulting in a default |
| 71 | + value of 4 per the SVG specification. |
| 72 | +
|
| 73 | + A more detailed description of the effect of a miter limit can be found |
| 74 | + in the `Mozilla Developer Docs |
| 75 | + <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-miterlimit>`_ |
| 76 | + """ |
| 77 | + |
| 78 | + miter = 'miter' |
| 79 | + round = 'round' |
| 80 | + bevel = 'bevel' |
| 81 | + |
| 82 | + def __init__(self, s): |
| 83 | + s = _deprecate_case_insensitive_join_cap(s) |
| 84 | + Enum.__init__(self) |
| 85 | + |
| 86 | + @staticmethod |
| 87 | + def demo(): |
| 88 | + import numpy as np |
| 89 | + import matplotlib.pyplot as plt |
| 90 | + |
| 91 | + def plot_angle(ax, x, y, angle, style): |
| 92 | + phi = np.radians(angle) |
| 93 | + xx = [x + .5, x, x + .5*np.cos(phi)] |
| 94 | + yy = [y, y, y + .5*np.sin(phi)] |
| 95 | + ax.plot(xx, yy, lw=12, color='tab:blue', solid_joinstyle=style) |
| 96 | + ax.plot(xx, yy, lw=1, color='black') |
| 97 | + ax.plot(xx[1], yy[1], 'o', color='tab:red', markersize=3) |
| 98 | + |
| 99 | + fig, ax = plt.subplots(figsize=(8, 6)) |
| 100 | + ax.set_title('Join style') |
| 101 | + for x, style in enumerate(['miter', 'round', 'bevel']): |
| 102 | + ax.text(x, 5, style) |
| 103 | + for y, angle in enumerate([20, 45, 60, 90, 120]): |
| 104 | + plot_angle(ax, x, y, angle, style) |
| 105 | + if x == 0: |
| 106 | + ax.text(-1.3, y, f'{angle} degrees') |
| 107 | + ax.set_xlim(-1.5, 2.75) |
| 108 | + ax.set_ylim(-.5, 5.5) |
| 109 | + ax.set_axis_off() |
| 110 | + |
| 111 | + plt.show() |
| 112 | + |
| 113 | + |
| 114 | +class CapStyle(Enum): |
| 115 | + """ |
| 116 | + Define how the the end of a line is drawn. |
| 117 | +
|
| 118 | + How to draw the start and end points of lines that represent a closed curve |
| 119 | + (i.e. that end in a `~.path.Path.CLOSEPOLY`) is controlled by the line's |
| 120 | + `JoinStyle`. For all other lines, how the start and end points are drawn is |
| 121 | + controlled by the *CapStyle*. |
| 122 | +
|
| 123 | + For a simple visual description of each *CapStyle*, `view these docs |
| 124 | + online <CapStyle>` or simply run `CapStyle.demo`. |
| 125 | +
|
| 126 | + .. plot:: |
| 127 | + :alt: Demo of possible CapStyle's |
| 128 | +
|
| 129 | + from matplotlib._types import CapStyle |
| 130 | + CapStyle.demo() |
| 131 | +
|
| 132 | + Briefly, the three options available are: |
| 133 | +
|
| 134 | + - *butt*: the line is squared off at its endpoint. |
| 135 | + - *projecting*: the line is squared off as in *butt*, but the filled in |
| 136 | + area extends beyond the endpoint a distance of ``linewidth/2``. |
| 137 | + - *round*: like *butt*, but a semicircular cap is added to the end of |
| 138 | + the line, of radius ``linewidth/2``. |
| 139 | + """ |
| 140 | + butt = 'butt' |
| 141 | + projecting = 'projecting' |
| 142 | + round = 'round' |
| 143 | + |
| 144 | + def __init__(self, s): |
| 145 | + s = _deprecate_case_insensitive_join_cap(s) |
| 146 | + Enum.__init__(self) |
| 147 | + |
| 148 | + @staticmethod |
| 149 | + def demo(): |
| 150 | + import matplotlib.pyplot as plt |
| 151 | + |
| 152 | + fig, ax = plt.subplots(figsize=(8, 2)) |
| 153 | + ax.set_title('Cap style') |
| 154 | + |
| 155 | + for x, style in enumerate(['butt', 'round', 'projecting']): |
| 156 | + ax.text(x+0.25, 1, style, ha='center') |
| 157 | + xx = [x, x+0.5] |
| 158 | + yy = [0, 0] |
| 159 | + ax.plot(xx, yy, lw=12, color='tab:blue', solid_capstyle=style) |
| 160 | + ax.plot(xx, yy, lw=1, color='black') |
| 161 | + ax.plot(xx, yy, 'o', color='tab:red', markersize=3) |
| 162 | + ax.text(2.25, 0.7, '(default)', ha='center') |
| 163 | + |
| 164 | + ax.set_ylim(-.5, 1.5) |
| 165 | + ax.set_axis_off() |
0 commit comments