|
1 | 1 | """Contains classes for generating hatch patterns."""
|
| 2 | +fromcollections.abcimportIterable |
2 | 3 |
|
3 | 4 | importnumpyasnp
|
4 | 5 |
|
5 |
| -frommatplotlibimportcbook |
| 6 | +frommatplotlibimport_api,docstring |
6 | 7 | frommatplotlib.pathimportPath
|
7 | 8 |
|
8 | 9 |
|
@@ -186,46 +187,207 @@ def __init__(self, hatch, density):
|
186 | 187 | ]
|
187 | 188 |
|
188 | 189 |
|
189 |
| -def_validate_hatch_pattern(hatch): |
190 |
| -valid_hatch_patterns=set(r'-+|/\xXoO.*') |
191 |
| -ifhatchisnotNone: |
192 |
| -invalids=set(hatch).difference(valid_hatch_patterns) |
193 |
| -ifinvalids: |
194 |
| -valid=''.join(sorted(valid_hatch_patterns)) |
195 |
| -invalids=''.join(sorted(invalids)) |
196 |
| -cbook.warn_deprecated( |
197 |
| -'3.4', |
198 |
| -message=f'hatch must consist of a string of "{valid}" or ' |
199 |
| -'None, but found the following invalid values ' |
200 |
| -f'"{invalids}". Passing invalid values is deprecated ' |
201 |
| -'since %(since)s and will become an error %(removal)s.' |
202 |
| - ) |
| 190 | +classHatch: |
| 191 | +r""" |
| 192 | + Pattern to be tiled within a filled area. |
| 193 | +
|
| 194 | + For a visual example of the available *Hatch* patterns, `view these docs |
| 195 | + online <Hatch>` or run `Hatch.demo`. |
| 196 | +
|
| 197 | + When making plots that contain multiple filled shapes, like :doc:`bar |
| 198 | + charts </gallery/lines_bars_and_markers/bar_stacked>` or filled |
| 199 | + :doc:`countour plots </images_contours_and_fields/contourf_hatching>`, it |
| 200 | + is common to use :doc:`color </tutorials/colors/colors>` to distinguish |
| 201 | + between areas. However, if color is not available, such as when printing in |
| 202 | + black and white, Matplotlib also supports hatching (i.e. filling each |
| 203 | + area with a unique repeating pattern or lines or shapes) in order to make |
| 204 | + it easy to refer to a specific filled bar, shape, or similar. |
| 205 | +
|
| 206 | + .. warning:: |
| 207 | + Hatching is currently only supported in the Agg, PostScript, PDF, and |
| 208 | + SVG backends. |
| 209 | +
|
| 210 | + **Hatching patterns** |
| 211 | +
|
| 212 | + There hatching primitives built into Matplotlib are: |
| 213 | +
|
| 214 | + .. rst-class:: value-list |
| 215 | +
|
| 216 | + '-' |
| 217 | + Horizontal lines. |
| 218 | + '|' |
| 219 | + Vertical lines. |
| 220 | + '+' |
| 221 | + Crossed lines. ``'+'`` is equivalent to ``'-|'``. |
| 222 | + '\' |
| 223 | + Diagonal lines running northwest to southeast. |
| 224 | + '/' |
| 225 | + Diagonal lines running southwest to northeast. |
| 226 | + 'x' |
| 227 | + Crossed diagonal lines. Equivalent to ``r'\/'``. |
| 228 | + 'X' |
| 229 | + Synonym for ``'x'``. |
| 230 | + '.' |
| 231 | + Dots (i.e. very small, filled circles). |
| 232 | + 'o' |
| 233 | + Small, unfilled circles. |
| 234 | + 'O' |
| 235 | + Large, unfilled circles. |
| 236 | + '*' |
| 237 | + Filled star shape. |
| 238 | +
|
| 239 | + Hatching primitives can be combined to make more complicated patterns. For |
| 240 | + example, a hatch pattern of ``'*/|'`` would fill the area with vertical and |
| 241 | + diagonal lines as well as stars. |
| 242 | +
|
| 243 | + **Hatching Density** |
| 244 | +
|
| 245 | + By default, the hatching pattern is tiled so that there are **6** lines per |
| 246 | + inch (in display space), but this can be tuned (in integer increments) |
| 247 | + using the *density* kwarg to *Hatch*. |
| 248 | +
|
| 249 | + For convenience, the same symbol can also be repeated to request a higher |
| 250 | + hatching density. For example, ``'||-'`` will have twice as many vertical |
| 251 | + lines as ``'|-'``. Notice that since ``'|-'`` can also be written as |
| 252 | + ``'+'``, we can also write ``'||-'`` as ``'|+'``. |
| 253 | +
|
| 254 | + Examples |
| 255 | + -------- |
| 256 | + For more examples of how to use hatching, see `the hatching demo |
| 257 | + </gallery/shapes_and_collections/hatch_demo>` and `the contourf hatching |
| 258 | + demo </gallery/images_contours_and_fields/contourf_hatching>`. |
| 259 | +
|
| 260 | + .. plot:: |
| 261 | + :alt: Demo showing each hatching primitive at its default density. |
| 262 | +
|
| 263 | + from matplotlib.hatch import Hatch |
| 264 | + Hatch.demo() |
| 265 | + """ |
| 266 | + |
| 267 | +_default_density=6 |
| 268 | +_valid_hatch_patterns=set(r'-|+/\xX.oO*') |
| 269 | + |
| 270 | +def__init__(self,pattern_spec,density=None): |
| 271 | +self.density=Hatch._default_densityifdensityisNoneelsedensity |
| 272 | +self._pattern_spec=pattern_spec |
| 273 | +self.patterns=self._validate_hatch_pattern(pattern_spec) |
| 274 | +self._build_path() |
| 275 | + |
| 276 | +@classmethod |
| 277 | +deffrom_path(cls,path): |
| 278 | +hatch=cls(None,0) |
| 279 | +hatch.path=path |
| 280 | + |
| 281 | +def_build_path(self): |
| 282 | +# the API of HatchPatternBase was architected before Hatch, so instead |
| 283 | +# of simply returning Path's that we can concatenate using |
| 284 | +# Path.make_compound_path, we must pre-allocate the vertices array for |
| 285 | +# the final path up front. (The performance gain from this |
| 286 | +# preallocation is untested). |
| 287 | +num_vertices=sum([pattern.num_verticesforpatterninself.patterns]) |
| 288 | + |
| 289 | +ifnum_vertices==0: |
| 290 | +self.path=Path(np.empty((0,2))) |
203 | 291 |
|
| 292 | +vertices=np.empty((num_vertices,2)) |
| 293 | +codes=np.empty(num_vertices,Path.code_type) |
204 | 294 |
|
| 295 | +cursor=0 |
| 296 | +forpatterninself.patterns: |
| 297 | +ifpattern.num_vertices!=0: |
| 298 | +vertices_chunk=vertices[cursor:cursor+pattern.num_vertices] |
| 299 | +codes_chunk=codes[cursor:cursor+pattern.num_vertices] |
| 300 | +pattern.set_vertices_and_codes(vertices_chunk,codes_chunk) |
| 301 | +cursor+=pattern.num_vertices |
| 302 | + |
| 303 | +self.path=Path(vertices,codes) |
| 304 | + |
| 305 | +def_validate_hatch_pattern(self,patterns): |
| 306 | +ifisinstance(patterns,Hatch): |
| 307 | +patterns=patterns._pattern_spec |
| 308 | +ifpatternsisNoneorpatternsis []: |
| 309 | +return [] |
| 310 | +elifisinstance(patterns,str): |
| 311 | +invalids=set(patterns).difference(Hatch._valid_hatch_patterns) |
| 312 | +ifinvalids: |
| 313 | +Hatch._warn_invalid_hatch(invalids) |
| 314 | +return [hatch_type(patterns,self.density) |
| 315 | +forhatch_typein_hatch_types] |
| 316 | +elifisinstance(patterns,Iterable)andnp.all([ |
| 317 | +isinstance(p,HatchPatternBase)forpinpatterns]): |
| 318 | +returnpatterns |
| 319 | +else: |
| 320 | +raiseValueError(f"Cannot construct hatch pattern from{patterns}") |
| 321 | + |
| 322 | +def_warn_invalid_hatch(invalids): |
| 323 | +valid=''.join(sorted(Hatch._valid_hatch_patterns)) |
| 324 | +invalids=''.join(sorted(invalids)) |
| 325 | +_api.warn_deprecated( |
| 326 | +'3.4', |
| 327 | +message=f'hatch must consist of a string of "{valid}" or None, ' |
| 328 | +f'but found the following invalid values "{invalids}". ' |
| 329 | +'Passing invalid values is deprecated since %(since)s and ' |
| 330 | +'will become an error %(removal)s.' |
| 331 | + ) |
| 332 | + |
| 333 | +@staticmethod |
| 334 | +defdemo(density=6): |
| 335 | +importmatplotlib.pyplotasplt |
| 336 | +frommatplotlib.patchesimportRectangle |
| 337 | +fig=plt.figure() |
| 338 | +ax=fig.add_axes([0,0,1,1]) |
| 339 | +ax.set_axis_off() |
| 340 | +num_patches=len(Hatch._valid_hatch_patterns) |
| 341 | + |
| 342 | +spacing=0.1# percent of width |
| 343 | +boxes_per_row=4 |
| 344 | +num_rows=np.ceil(num_patches/boxes_per_row) |
| 345 | +inter_box_dist_y=1/num_rows |
| 346 | +posts=np.linspace(0,1,boxes_per_row+1) |
| 347 | +inter_box_dist_x=posts[1]-posts[0] |
| 348 | +font_size=12 |
| 349 | +fig_size= (4,4) |
| 350 | +text_pad=0.2# fraction of text height |
| 351 | +text_height= (1+text_pad)*( |
| 352 | +fig.dpi_scale_trans+ax.transAxes.inverted() |
| 353 | + ).transform([1,1])[1] |
| 354 | +# half of text pad |
| 355 | +bottom_padding=text_height*(1- (1/(1+text_pad)))/2 |
| 356 | + |
| 357 | +fori,hatchinenumerate(Hatch._valid_hatch_patterns): |
| 358 | +row=int(i/boxes_per_row) |
| 359 | +col=i-row*boxes_per_row |
| 360 | +ax.add_patch(Rectangle( |
| 361 | +xy=[(col+spacing/2)*inter_box_dist_x, |
| 362 | +bottom_padding+row*inter_box_dist_y], |
| 363 | +width=inter_box_dist_x*(1-spacing), |
| 364 | +height=inter_box_dist_y*(1-text_height), |
| 365 | +transform=ax.transAxes, |
| 366 | +hatch=hatch, |
| 367 | +label="'"+hatch+"'" |
| 368 | + )) |
| 369 | +ax.text((col+1/2)*inter_box_dist_x, |
| 370 | +bottom_padding+ (-text_height*(1/(1+text_pad))+row |
| 371 | ++1)*inter_box_dist_y, |
| 372 | +"'"+hatch+"'",horizontalalignment='center', |
| 373 | +fontsize=font_size) |
| 374 | + |
| 375 | + |
| 376 | +Hatch.input_description="{" \ |
| 377 | ++", ".join([f"'{p}'"forpinHatch._valid_hatch_patterns]) \ |
| 378 | ++"}" |
| 379 | + |
| 380 | + |
| 381 | +docstring.interpd.update({'Hatch':Hatch.input_description}) |
| 382 | + |
| 383 | + |
| 384 | +@_api.deprecated("3.4") |
205 | 385 | defget_path(hatchpattern,density=6):
|
206 | 386 | """
|
207 | 387 | Given a hatch specifier, *hatchpattern*, generates Path to render
|
208 | 388 | the hatch in a unit square. *density* is the number of lines per
|
209 | 389 | unit square.
|
210 | 390 | """
|
211 |
| -density=int(density) |
212 |
| - |
213 |
| -patterns= [hatch_type(hatchpattern,density) |
214 |
| -forhatch_typein_hatch_types] |
215 |
| -num_vertices=sum([pattern.num_verticesforpatterninpatterns]) |
216 |
| - |
217 |
| -ifnum_vertices==0: |
218 |
| -returnPath(np.empty((0,2))) |
219 |
| - |
220 |
| -vertices=np.empty((num_vertices,2)) |
221 |
| -codes=np.empty(num_vertices,Path.code_type) |
222 |
| - |
223 |
| -cursor=0 |
224 |
| -forpatterninpatterns: |
225 |
| -ifpattern.num_vertices!=0: |
226 |
| -vertices_chunk=vertices[cursor:cursor+pattern.num_vertices] |
227 |
| -codes_chunk=codes[cursor:cursor+pattern.num_vertices] |
228 |
| -pattern.set_vertices_and_codes(vertices_chunk,codes_chunk) |
229 |
| -cursor+=pattern.num_vertices |
| 391 | +returnHatch(hatchpattern).path |
230 | 392 |
|
231 |
| -returnPath(vertices,codes) |
| 393 | +docstring |