|
| 1 | +""" |
| 2 | +Enums representing sets of strings that Matplotlib uses as input parameters. |
| 3 | +
|
| 4 | +Matplotlib often uses simple data types like strings or tuples to define a |
| 5 | +concept; e.g. the line capstyle can be specified as one of 'butt', 'round', |
| 6 | +or 'projecting'. The classes in this module are used internally and serve to |
| 7 | +document these concepts formally. |
| 8 | +
|
| 9 | +As an end-user you will not use these classes directly, but only the values |
| 10 | +they define. |
| 11 | +""" |
| 12 | + |
| 13 | +from enum import Enum, auto |
| 14 | +from matplotlib import cbook, docstring |
| 15 | + |
| 16 | + |
| 17 | +class _AutoStringNameEnum(Enum): |
| 18 | + """Automate the ``name = 'name'`` part of making a (str, Enum).""" |
| 19 | + |
| 20 | + def _generate_next_value_(name, start, count, last_values): |
| 21 | + return name |
| 22 | + |
| 23 | + def __hash__(self): |
| 24 | + return str(self).__hash__() |
| 25 | + |
| 26 | + |
| 27 | +def _deprecate_case_insensitive_join_cap(s): |
| 28 | + s_low = s.lower() |
| 29 | + if s != s_low: |
| 30 | + if s_low in ['miter', 'round', 'bevel']: |
| 31 | + cbook.warn_deprecated( |
| 32 | + "3.3", message="Case-insensitive capstyles are deprecated " |
| 33 | + "since %(since)s and support for them will be removed " |
| 34 | + "%(removal)s; please pass them in lowercase.") |
| 35 | + elif s_low in ['butt', 'round', 'projecting']: |
| 36 | + cbook.warn_deprecated( |
| 37 | + "3.3", message="Case-insensitive joinstyles are deprecated " |
| 38 | + "since %(since)s and support for them will be removed " |
| 39 | + "%(removal)s; please pass them in lowercase.") |
| 40 | + # Else, error out at the check_in_list stage. |
| 41 | + return s_low |
| 42 | + |
| 43 | + |
| 44 | +class JoinStyle(str, _AutoStringNameEnum): |
| 45 | + """ |
| 46 | + Define how the connection between two line segments is drawn. |
| 47 | +
|
| 48 | + For a visual impression of each *JoinStyle*, `view these docs online |
| 49 | + <JoinStyle>`, or run `JoinStyle.demo`. |
| 50 | +
|
| 51 | + Lines in Matplotlib are typically defined by a 1D `~.path.Path` and a |
| 52 | + finite ``linewidth``, where the underlying 1D `~.path.Path` represents the |
| 53 | + center of the stroked line. |
| 54 | +
|
| 55 | + By default, `~.backend_bases.GraphicsContextBase` defines the boundaries of |
| 56 | + a stroked line to simply be every point within some radius, |
| 57 | + ``linewidth/2``, away from any point of the center line. However, this |
| 58 | + results in corners appearing "rounded", which may not be the desired |
| 59 | + behavior if you are drawing, for example, a polygon or pointed star. |
| 60 | +
|
| 61 | + **Supported values:** |
| 62 | +
|
| 63 | + .. rst-class:: value-list |
| 64 | +
|
| 65 | + 'miter' |
| 66 | + the "arrow-tip" style. Each boundary of the filled-in area will |
| 67 | + extend in a straight line parallel to the tangent vector of the |
| 68 | + centerline at the point it meets the corner, until they meet in a |
| 69 | + sharp point. |
| 70 | + 'round' |
| 71 | + stokes every point within a radius of ``linewidth/2`` of the center |
| 72 | + lines. |
| 73 | + 'bevel' |
| 74 | + the "squared-off" style. It can be thought of as a rounded corner |
| 75 | + where the "circular" part of the corner has been cut off. |
| 76 | +
|
| 77 | + .. note:: |
| 78 | +
|
| 79 | + Very long miter tips are cut off (to form a *bevel*) after a |
| 80 | + backend-dependent limit called the "miter limit", which specifies the |
| 81 | + maximum allowed ratio of miter length to line width. For example, the |
| 82 | + PDF backend uses the default value of 10 specified by the PDF standard, |
| 83 | + while the SVG backend does not even specify the miter limit, resulting |
| 84 | + in a default value of 4 per the SVG specification. Matplotlib does not |
| 85 | + currently allow the user to adjust this parameter. |
| 86 | +
|
| 87 | + A more detailed description of the effect of a miter limit can be found |
| 88 | + in the `Mozilla Developer Docs |
| 89 | + <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-miterlimit>`_ |
| 90 | +
|
| 91 | + .. plot:: |
| 92 | + :alt: Demo of possible JoinStyle's |
| 93 | +
|
| 94 | + from matplotlib._enums import JoinStyle |
| 95 | + JoinStyle.demo() |
| 96 | +
|
| 97 | + """ |
| 98 | + |
| 99 | + miter = auto() |
| 100 | + round = auto() |
| 101 | + bevel = auto() |
| 102 | + |
| 103 | + def __init__(self, s): |
| 104 | + s = _deprecate_case_insensitive_join_cap(s) |
| 105 | + Enum.__init__(self) |
| 106 | + |
| 107 | + @staticmethod |
| 108 | + def demo(): |
| 109 | + """Demonstrate how each JoinStyle looks for various join angles.""" |
| 110 | + import numpy as np |
| 111 | + import matplotlib.pyplot as plt |
| 112 | + |
| 113 | + def plot_angle(ax, x, y, angle, style): |
| 114 | + phi = np.radians(angle) |
| 115 | + xx = [x + .5, x, x + .5*np.cos(phi)] |
| 116 | + yy = [y, y, y + .5*np.sin(phi)] |
| 117 | + ax.plot(xx, yy, lw=12, color='tab:blue', solid_joinstyle=style) |
| 118 | + ax.plot(xx, yy, lw=1, color='black') |
| 119 | + ax.plot(xx[1], yy[1], 'o', color='tab:red', markersize=3) |
| 120 | + |
| 121 | + fig, ax = plt.subplots(figsize=(5, 4), constrained_layout=True) |
| 122 | + ax.set_title('Join style') |
| 123 | + for x, style in enumerate(['miter', 'round', 'bevel']): |
| 124 | + ax.text(x, 5, style) |
| 125 | + for y, angle in enumerate([20, 45, 60, 90, 120]): |
| 126 | + plot_angle(ax, x, y, angle, style) |
| 127 | + if x == 0: |
| 128 | + ax.text(-1.3, y, f'{angle} degrees') |
| 129 | + ax.set_xlim(-1.5, 2.75) |
| 130 | + ax.set_ylim(-.5, 5.5) |
| 131 | + ax.set_axis_off() |
| 132 | + fig.show() |
| 133 | + |
| 134 | + |
| 135 | +JoinStyle.input_description = "{" \ |
| 136 | + + ", ".join([f"'{js.name}'" for js in JoinStyle]) \ |
| 137 | + + "}" |
| 138 | + |
| 139 | + |
| 140 | +class CapStyle(str, _AutoStringNameEnum): |
| 141 | + r""" |
| 142 | + Define how the two endpoints (caps) of an unclosed line are drawn. |
| 143 | +
|
| 144 | + How to draw the start and end points of lines that represent a closed curve |
| 145 | + (i.e. that end in a `~.path.Path.CLOSEPOLY`) is controlled by the line's |
| 146 | + `JoinStyle`. For all other lines, how the start and end points are drawn is |
| 147 | + controlled by the *CapStyle*. |
| 148 | +
|
| 149 | + For a visual impression of each *CapStyle*, `view these docs online |
| 150 | + <CapStyle>` or run `CapStyle.demo`. |
| 151 | +
|
| 152 | + **Supported values:** |
| 153 | +
|
| 154 | + .. rst-class:: value-list |
| 155 | +
|
| 156 | + 'butt' |
| 157 | + the line is squared off at its endpoint. |
| 158 | + 'projecting' |
| 159 | + the line is squared off as in *butt*, but the filled in area |
| 160 | + extends beyond the endpoint a distance of ``linewidth/2``. |
| 161 | + 'round' |
| 162 | + like *butt*, but a semicircular cap is added to the end of the |
| 163 | + line, of radius ``linewidth/2``. |
| 164 | +
|
| 165 | + .. plot:: |
| 166 | + :alt: Demo of possible CapStyle's |
| 167 | +
|
| 168 | + from matplotlib._enums import CapStyle |
| 169 | + CapStyle.demo() |
| 170 | +
|
| 171 | + """ |
| 172 | + butt = 'butt' |
| 173 | + projecting = 'projecting' |
| 174 | + round = 'round' |
| 175 | + |
| 176 | + def __init__(self, s): |
| 177 | + s = _deprecate_case_insensitive_join_cap(s) |
| 178 | + Enum.__init__(self) |
| 179 | + |
| 180 | + @staticmethod |
| 181 | + def demo(): |
| 182 | + """Demonstrate how each CapStyle looks for a thick line segment.""" |
| 183 | + import matplotlib.pyplot as plt |
| 184 | + |
| 185 | + fig = plt.figure(figsize=(4, 1.2)) |
| 186 | + ax = fig.add_axes([0, 0, 1, 0.8]) |
| 187 | + ax.set_title('Cap style') |
| 188 | + |
| 189 | + for x, style in enumerate(['butt', 'round', 'projecting']): |
| 190 | + ax.text(x+0.25, 0.85, style, ha='center') |
| 191 | + xx = [x, x+0.5] |
| 192 | + yy = [0, 0] |
| 193 | + ax.plot(xx, yy, lw=12, color='tab:blue', solid_capstyle=style) |
| 194 | + ax.plot(xx, yy, lw=1, color='black') |
| 195 | + ax.plot(xx, yy, 'o', color='tab:red', markersize=3) |
| 196 | + ax.text(2.25, 0.55, '(default)', ha='center') |
| 197 | + |
| 198 | + ax.set_ylim(-.5, 1.5) |
| 199 | + ax.set_axis_off() |
| 200 | + fig.show() |
| 201 | + |
| 202 | + |
| 203 | +CapStyle.input_description = "{" \ |
| 204 | + + ", ".join([f"'{cs.name}'" for cs in CapStyle]) \ |
| 205 | + + "}" |
| 206 | + |
| 207 | +docstring.interpd.update({'JoinStyle': JoinStyle.input_description, |
| 208 | + 'CapStyle': CapStyle.input_description}) |
0 commit comments