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