123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214 |
- """
- Contains a classes for generating hatch patterns.
- """
- import numpy as np
- from matplotlib.path import Path
- class HatchPatternBase:
- """
- The base class for a hatch pattern.
- """
- pass
- class HorizontalHatch(HatchPatternBase):
- def __init__(self, hatch, density):
- self.num_lines = int((hatch.count('-') + hatch.count('+')) * density)
- self.num_vertices = self.num_lines * 2
- def set_vertices_and_codes(self, vertices, codes):
- steps, stepsize = np.linspace(0.0, 1.0, self.num_lines, False,
- retstep=True)
- steps += stepsize / 2.
- vertices[0::2, 0] = 0.0
- vertices[0::2, 1] = steps
- vertices[1::2, 0] = 1.0
- vertices[1::2, 1] = steps
- codes[0::2] = Path.MOVETO
- codes[1::2] = Path.LINETO
- class VerticalHatch(HatchPatternBase):
- def __init__(self, hatch, density):
- self.num_lines = int((hatch.count('|') + hatch.count('+')) * density)
- self.num_vertices = self.num_lines * 2
- def set_vertices_and_codes(self, vertices, codes):
- steps, stepsize = np.linspace(0.0, 1.0, self.num_lines, False,
- retstep=True)
- steps += stepsize / 2.
- vertices[0::2, 0] = steps
- vertices[0::2, 1] = 0.0
- vertices[1::2, 0] = steps
- vertices[1::2, 1] = 1.0
- codes[0::2] = Path.MOVETO
- codes[1::2] = Path.LINETO
- class NorthEastHatch(HatchPatternBase):
- def __init__(self, hatch, density):
- self.num_lines = int((hatch.count('/') + hatch.count('x') +
- hatch.count('X')) * density)
- if self.num_lines:
- self.num_vertices = (self.num_lines + 1) * 2
- else:
- self.num_vertices = 0
- def set_vertices_and_codes(self, vertices, codes):
- steps = np.linspace(-0.5, 0.5, self.num_lines + 1)
- vertices[0::2, 0] = 0.0 + steps
- vertices[0::2, 1] = 0.0 - steps
- vertices[1::2, 0] = 1.0 + steps
- vertices[1::2, 1] = 1.0 - steps
- codes[0::2] = Path.MOVETO
- codes[1::2] = Path.LINETO
- class SouthEastHatch(HatchPatternBase):
- def __init__(self, hatch, density):
- self.num_lines = int((hatch.count('\\') + hatch.count('x') +
- hatch.count('X')) * density)
- self.num_vertices = (self.num_lines + 1) * 2
- if self.num_lines:
- self.num_vertices = (self.num_lines + 1) * 2
- else:
- self.num_vertices = 0
- def set_vertices_and_codes(self, vertices, codes):
- steps = np.linspace(-0.5, 0.5, self.num_lines + 1)
- vertices[0::2, 0] = 0.0 + steps
- vertices[0::2, 1] = 1.0 + steps
- vertices[1::2, 0] = 1.0 + steps
- vertices[1::2, 1] = 0.0 + steps
- codes[0::2] = Path.MOVETO
- codes[1::2] = Path.LINETO
- class Shapes(HatchPatternBase):
- filled = False
- def __init__(self, hatch, density):
- if self.num_rows == 0:
- self.num_shapes = 0
- self.num_vertices = 0
- else:
- self.num_shapes = ((self.num_rows // 2 + 1) * (self.num_rows + 1) +
- (self.num_rows // 2) * (self.num_rows))
- self.num_vertices = (self.num_shapes *
- len(self.shape_vertices) *
- (1 if self.filled else 2))
- def set_vertices_and_codes(self, vertices, codes):
- offset = 1.0 / self.num_rows
- shape_vertices = self.shape_vertices * offset * self.size
- if not self.filled:
- inner_vertices = shape_vertices[::-1] * 0.9
- shape_codes = self.shape_codes
- shape_size = len(shape_vertices)
- cursor = 0
- for row in range(self.num_rows + 1):
- if row % 2 == 0:
- cols = np.linspace(0, 1, self.num_rows + 1)
- else:
- cols = np.linspace(offset / 2, 1 - offset / 2, self.num_rows)
- row_pos = row * offset
- for col_pos in cols:
- vertices[cursor:cursor + shape_size] = (shape_vertices +
- (col_pos, row_pos))
- codes[cursor:cursor + shape_size] = shape_codes
- cursor += shape_size
- if not self.filled:
- vertices[cursor:cursor + shape_size] = (inner_vertices +
- (col_pos, row_pos))
- codes[cursor:cursor + shape_size] = shape_codes
- cursor += shape_size
- class Circles(Shapes):
- def __init__(self, hatch, density):
- path = Path.unit_circle()
- self.shape_vertices = path.vertices
- self.shape_codes = path.codes
- Shapes.__init__(self, hatch, density)
- class SmallCircles(Circles):
- size = 0.2
- def __init__(self, hatch, density):
- self.num_rows = (hatch.count('o')) * density
- Circles.__init__(self, hatch, density)
- class LargeCircles(Circles):
- size = 0.35
- def __init__(self, hatch, density):
- self.num_rows = (hatch.count('O')) * density
- Circles.__init__(self, hatch, density)
- class SmallFilledCircles(SmallCircles):
- size = 0.1
- filled = True
- def __init__(self, hatch, density):
- self.num_rows = (hatch.count('.')) * density
- Circles.__init__(self, hatch, density)
- class Stars(Shapes):
- size = 1.0 / 3.0
- filled = True
- def __init__(self, hatch, density):
- self.num_rows = (hatch.count('*')) * density
- path = Path.unit_regular_star(5)
- self.shape_vertices = path.vertices
- self.shape_codes = np.full(len(self.shape_vertices), Path.LINETO,
- dtype=Path.code_type)
- self.shape_codes[0] = Path.MOVETO
- Shapes.__init__(self, hatch, density)
- _hatch_types = [
- HorizontalHatch,
- VerticalHatch,
- NorthEastHatch,
- SouthEastHatch,
- SmallCircles,
- LargeCircles,
- SmallFilledCircles,
- Stars
- ]
- def get_path(hatchpattern, density=6):
- """
- Given a hatch specifier, *hatchpattern*, generates Path to render
- the hatch in a unit square. *density* is the number of lines per
- unit square.
- """
- density = int(density)
- patterns = [hatch_type(hatchpattern, density)
- for hatch_type in _hatch_types]
- num_vertices = sum([pattern.num_vertices for pattern in patterns])
- if num_vertices == 0:
- return Path(np.empty((0, 2)))
- vertices = np.empty((num_vertices, 2))
- codes = np.empty(num_vertices, Path.code_type)
- cursor = 0
- for pattern in patterns:
- if pattern.num_vertices != 0:
- vertices_chunk = vertices[cursor:cursor + pattern.num_vertices]
- codes_chunk = codes[cursor:cursor + pattern.num_vertices]
- pattern.set_vertices_and_codes(vertices_chunk, codes_chunk)
- cursor += pattern.num_vertices
- return Path(vertices, codes)
|