Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/matplotlib/hatch.py: 28%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

143 statements  

1"""Contains classes for generating hatch patterns.""" 

2 

3import numpy as np 

4 

5from matplotlib import _api 

6from matplotlib.path import Path 

7 

8 

9class HatchPatternBase: 

10 """The base class for a hatch pattern.""" 

11 pass 

12 

13 

14class HorizontalHatch(HatchPatternBase): 

15 def __init__(self, hatch, density): 

16 self.num_lines = int((hatch.count('-') + hatch.count('+')) * density) 

17 self.num_vertices = self.num_lines * 2 

18 

19 def set_vertices_and_codes(self, vertices, codes): 

20 steps, stepsize = np.linspace(0.0, 1.0, self.num_lines, False, 

21 retstep=True) 

22 steps += stepsize / 2. 

23 vertices[0::2, 0] = 0.0 

24 vertices[0::2, 1] = steps 

25 vertices[1::2, 0] = 1.0 

26 vertices[1::2, 1] = steps 

27 codes[0::2] = Path.MOVETO 

28 codes[1::2] = Path.LINETO 

29 

30 

31class VerticalHatch(HatchPatternBase): 

32 def __init__(self, hatch, density): 

33 self.num_lines = int((hatch.count('|') + hatch.count('+')) * density) 

34 self.num_vertices = self.num_lines * 2 

35 

36 def set_vertices_and_codes(self, vertices, codes): 

37 steps, stepsize = np.linspace(0.0, 1.0, self.num_lines, False, 

38 retstep=True) 

39 steps += stepsize / 2. 

40 vertices[0::2, 0] = steps 

41 vertices[0::2, 1] = 0.0 

42 vertices[1::2, 0] = steps 

43 vertices[1::2, 1] = 1.0 

44 codes[0::2] = Path.MOVETO 

45 codes[1::2] = Path.LINETO 

46 

47 

48class NorthEastHatch(HatchPatternBase): 

49 def __init__(self, hatch, density): 

50 self.num_lines = int( 

51 (hatch.count('/') + hatch.count('x') + hatch.count('X')) * density) 

52 if self.num_lines: 

53 self.num_vertices = (self.num_lines + 1) * 2 

54 else: 

55 self.num_vertices = 0 

56 

57 def set_vertices_and_codes(self, vertices, codes): 

58 steps = np.linspace(-0.5, 0.5, self.num_lines + 1) 

59 vertices[0::2, 0] = 0.0 + steps 

60 vertices[0::2, 1] = 0.0 - steps 

61 vertices[1::2, 0] = 1.0 + steps 

62 vertices[1::2, 1] = 1.0 - steps 

63 codes[0::2] = Path.MOVETO 

64 codes[1::2] = Path.LINETO 

65 

66 

67class SouthEastHatch(HatchPatternBase): 

68 def __init__(self, hatch, density): 

69 self.num_lines = int( 

70 (hatch.count('\\') + hatch.count('x') + hatch.count('X')) 

71 * density) 

72 if self.num_lines: 

73 self.num_vertices = (self.num_lines + 1) * 2 

74 else: 

75 self.num_vertices = 0 

76 

77 def set_vertices_and_codes(self, vertices, codes): 

78 steps = np.linspace(-0.5, 0.5, self.num_lines + 1) 

79 vertices[0::2, 0] = 0.0 + steps 

80 vertices[0::2, 1] = 1.0 + steps 

81 vertices[1::2, 0] = 1.0 + steps 

82 vertices[1::2, 1] = 0.0 + steps 

83 codes[0::2] = Path.MOVETO 

84 codes[1::2] = Path.LINETO 

85 

86 

87class Shapes(HatchPatternBase): 

88 filled = False 

89 

90 def __init__(self, hatch, density): 

91 if self.num_rows == 0: 

92 self.num_shapes = 0 

93 self.num_vertices = 0 

94 else: 

95 self.num_shapes = ((self.num_rows // 2 + 1) * (self.num_rows + 1) + 

96 (self.num_rows // 2) * self.num_rows) 

97 self.num_vertices = (self.num_shapes * 

98 len(self.shape_vertices) * 

99 (1 if self.filled else 2)) 

100 

101 def set_vertices_and_codes(self, vertices, codes): 

102 offset = 1.0 / self.num_rows 

103 shape_vertices = self.shape_vertices * offset * self.size 

104 shape_codes = self.shape_codes 

105 if not self.filled: 

106 shape_vertices = np.concatenate( # Forward, then backward. 

107 [shape_vertices, shape_vertices[::-1] * 0.9]) 

108 shape_codes = np.concatenate([shape_codes, shape_codes]) 

109 vertices_parts = [] 

110 codes_parts = [] 

111 for row in range(self.num_rows + 1): 

112 if row % 2 == 0: 

113 cols = np.linspace(0, 1, self.num_rows + 1) 

114 else: 

115 cols = np.linspace(offset / 2, 1 - offset / 2, self.num_rows) 

116 row_pos = row * offset 

117 for col_pos in cols: 

118 vertices_parts.append(shape_vertices + [col_pos, row_pos]) 

119 codes_parts.append(shape_codes) 

120 np.concatenate(vertices_parts, out=vertices) 

121 np.concatenate(codes_parts, out=codes) 

122 

123 

124class Circles(Shapes): 

125 def __init__(self, hatch, density): 

126 path = Path.unit_circle() 

127 self.shape_vertices = path.vertices 

128 self.shape_codes = path.codes 

129 super().__init__(hatch, density) 

130 

131 

132class SmallCircles(Circles): 

133 size = 0.2 

134 

135 def __init__(self, hatch, density): 

136 self.num_rows = (hatch.count('o')) * density 

137 super().__init__(hatch, density) 

138 

139 

140class LargeCircles(Circles): 

141 size = 0.35 

142 

143 def __init__(self, hatch, density): 

144 self.num_rows = (hatch.count('O')) * density 

145 super().__init__(hatch, density) 

146 

147 

148class SmallFilledCircles(Circles): 

149 size = 0.1 

150 filled = True 

151 

152 def __init__(self, hatch, density): 

153 self.num_rows = (hatch.count('.')) * density 

154 super().__init__(hatch, density) 

155 

156 

157class Stars(Shapes): 

158 size = 1.0 / 3.0 

159 filled = True 

160 

161 def __init__(self, hatch, density): 

162 self.num_rows = (hatch.count('*')) * density 

163 path = Path.unit_regular_star(5) 

164 self.shape_vertices = path.vertices 

165 self.shape_codes = np.full(len(self.shape_vertices), Path.LINETO, 

166 dtype=Path.code_type) 

167 self.shape_codes[0] = Path.MOVETO 

168 super().__init__(hatch, density) 

169 

170_hatch_types = [ 

171 HorizontalHatch, 

172 VerticalHatch, 

173 NorthEastHatch, 

174 SouthEastHatch, 

175 SmallCircles, 

176 LargeCircles, 

177 SmallFilledCircles, 

178 Stars 

179 ] 

180 

181 

182def _validate_hatch_pattern(hatch): 

183 valid_hatch_patterns = set(r'-+|/\xXoO.*') 

184 if hatch is not None: 

185 invalids = set(hatch).difference(valid_hatch_patterns) 

186 if invalids: 

187 valid = ''.join(sorted(valid_hatch_patterns)) 

188 invalids = ''.join(sorted(invalids)) 

189 _api.warn_deprecated( 

190 '3.4', 

191 removal='3.11', # one release after custom hatches (#20690) 

192 message=f'hatch must consist of a string of "{valid}" or ' 

193 'None, but found the following invalid values ' 

194 f'"{invalids}". Passing invalid values is deprecated ' 

195 'since %(since)s and will become an error %(removal)s.' 

196 ) 

197 

198 

199def get_path(hatchpattern, density=6): 

200 """ 

201 Given a hatch specifier, *hatchpattern*, generates Path to render 

202 the hatch in a unit square. *density* is the number of lines per 

203 unit square. 

204 """ 

205 density = int(density) 

206 

207 patterns = [hatch_type(hatchpattern, density) 

208 for hatch_type in _hatch_types] 

209 num_vertices = sum([pattern.num_vertices for pattern in patterns]) 

210 

211 if num_vertices == 0: 

212 return Path(np.empty((0, 2))) 

213 

214 vertices = np.empty((num_vertices, 2)) 

215 codes = np.empty(num_vertices, Path.code_type) 

216 

217 cursor = 0 

218 for pattern in patterns: 

219 if pattern.num_vertices != 0: 

220 vertices_chunk = vertices[cursor:cursor + pattern.num_vertices] 

221 codes_chunk = codes[cursor:cursor + pattern.num_vertices] 

222 pattern.set_vertices_and_codes(vertices_chunk, codes_chunk) 

223 cursor += pattern.num_vertices 

224 

225 return Path(vertices, codes)