1
1
"""Contains classes for generating hatch patterns."""
2
+ from collections .abc import Iterable
2
3
3
4
import numpy as np
4
5
5
- from matplotlib import cbook
6
+ from matplotlib import _api , docstring
6
7
from matplotlib .path import Path
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
- if hatch is not None :
192
- invalids = set (hatch ).difference (valid_hatch_patterns )
193
- if invalids :
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
+ class Hatch :
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_density if density is None else density
272
+ self ._pattern_spec = pattern_spec
273
+ self .patterns = self ._validate_hatch_pattern (pattern_spec )
274
+ self ._build_path ()
275
+
276
+ @classmethod
277
+ def from_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_vertices for pattern in self .patterns ])
288
+
289
+ if num_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
+ for pattern in self .patterns :
297
+ if pattern .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
+ if isinstance (patterns , Hatch ):
307
+ patterns = patterns ._pattern_spec
308
+ if patterns is None or patterns is []:
309
+ return []
310
+ elif isinstance (patterns , str ):
311
+ invalids = set (patterns ).difference (Hatch ._valid_hatch_patterns )
312
+ if invalids :
313
+ Hatch ._warn_invalid_hatch (invalids )
314
+ return [hatch_type (patterns , self .density )
315
+ for hatch_type in _hatch_types ]
316
+ elif isinstance (patterns , Iterable ) and np .all ([
317
+ isinstance (p , HatchPatternBase ) for p in patterns ]):
318
+ return patterns
319
+ else :
320
+ raise ValueError (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
+ def demo (density = 6 ):
335
+ import matplotlib .pyplot as plt
336
+ from matplotlib .patches import Rectangle
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
+ for i , hatch in enumerate (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 } '" for p in Hatch ._valid_hatch_patterns ]) \
378
+ + "}"
379
+
380
+
381
+ docstring .interpd .update ({'Hatch' : Hatch .input_description })
382
+
383
+
384
+ @_api .deprecated ("3.4" )
205
385
def get_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
- for hatch_type in _hatch_types ]
215
- num_vertices = sum ([pattern .num_vertices for pattern in patterns ])
216
-
217
- if num_vertices == 0 :
218
- return Path (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
- for pattern in patterns :
225
- if pattern .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
+ return Hatch (hatchpattern ).path
230
392
231
- return Path ( vertices , codes )
393
+ docstring
0 commit comments