Coverage for /home/deng/Projects/ete4/hackathon/ete4/ete4/smartview/renderer/draw_helpers.py: 29%

109 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2024-03-21 09:19 +0100

1from collections import OrderedDict, namedtuple 

2from math import sin, cos, pi, sqrt, atan2 

3 

4Box = namedtuple('Box', 'x y dx dy') # corner and size of a 2D shape 

5 

6Padding = namedtuple('Padding', 'x y') 

7 

8 

9def clip_angles(a1, a2): 

10 "Return the angles such that a1 to a2 extend at maximum from -pi to pi" 

11 EPSILON = 1e-8 # without it, p1 can be == p2 and svg arcs are not drawn 

12 return max(-pi + EPSILON, a1), min(pi - EPSILON, a2) 

13 

14 

15def cartesian(point): 

16 r, a = point 

17 return r * cos(a), r * sin(a) 

18 

19 

20def summary(nodes, prop="name"): 

21 "Return a list of names summarizing the given list of nodes" 

22 return list(OrderedDict((first_value(node, prop), None) for node in nodes).keys()) 

23 

24 

25def first_value(tree, prop): 

26 "Return the value of the requested property for the first node that has it" 

27 return next((node.props.get(prop) for node in tree.traverse('preorder') 

28 if node.props.get(prop)), '') 

29 

30 

31def get_xs(box): 

32 x, _, dx, _ = box 

33 return x, x + dx 

34 

35def get_ys(box): 

36 _, y, _, dy = box 

37 return y, y + dy 

38 

39def intersects_box(b1, b2): 

40 "Return True if the boxes b1 and b2 (of the same kind) intersect" 

41 return (intersects_segment(get_xs(b1), get_xs(b2)) and 

42 intersects_segment(get_ys(b1), get_ys(b2))) 

43 

44 

45def intersects_segment(s1, s2): 

46 "Return True if the segments s1 and s2 intersect" 

47 s1min, s1max = s1 

48 s2min, s2max = s2 

49 return s1min <= s2max and s2min <= s1max 

50 

51 

52def intersects_angles(rect, asec): 

53 "Return True if any part of rect is contained within the angles of the asec" 

54 return any(intersects_segment(get_ys(circumasec(r)), get_ys(asec)) 

55 for r in split_thru_negative_xaxis(rect)) 

56 # We divide rect in two if it passes thru the -x axis, because then its 

57 # circumbscribing asec goes from -pi to +pi and (wrongly) always intersects. 

58 

59 

60def split_thru_negative_xaxis(rect): 

61 "Return a list of rectangles resulting from cutting the given one" 

62 x, y, dx, dy = rect 

63 if x >= 0 or y > 0 or y + dy < 0: 

64 return [rect] 

65 else: 

66 EPSILON = 1e-8 

67 return [Box(x, y, dx, -y-EPSILON), Box(x, EPSILON, dx, dy + y)] 

68 

69 

70def circumrect(asec): 

71 "Return the rectangle that circumscribes the given annular sector" 

72 if asec is None: 

73 return None 

74 

75 rmin, amin, dr, da = asec 

76 rmax, amax = rmin + dr, amin + da 

77 

78 amin, amax = clip_angles(amin, amax) 

79 

80 points = [(rmin, amin), (rmin, amax), (rmax, amin), (rmax, amax)] 

81 xs = [r * cos(a) for r,a in points] 

82 ys = [r * sin(a) for r,a in points] 

83 xmin, ymin = min(xs), min(ys) 

84 xmax, ymax = max(xs), max(ys) 

85 

86 if amin < -pi/2 < amax: # asec traverses the -y axis 

87 ymin = -rmax 

88 if amin < 0 < amax: # asec traverses the +x axis 

89 xmax = rmax 

90 if amin < pi/2 < amax: # asec traverses the +y axis 

91 ymax = rmax 

92 # NOTE: the annular sectors we consider never traverse the -x axis. 

93 

94 return Box(xmin, ymin, xmax - xmin, ymax - ymin) 

95 

96 

97def circumasec(rect): 

98 "Return the annular sector that circumscribes the given rectangle" 

99 if rect is None: 

100 return None 

101 x, y, dx, dy = rect 

102 points = [(x, y), (x, y+dy), (x+dx, y), (x+dx, y+dy)] 

103 radius2 = [x*x + y*y for x,y in points] 

104 if x <= 0 and x+dx >= 0 and y <= 0 and y+dy >= 0: 

105 return Box(0, -pi, sqrt(max(radius2)), 2*pi) 

106 else: 

107 angles = [atan2(y, x) for x,y in points] 

108 rmin, amin = sqrt(min(radius2)), min(angles) 

109 return Box(rmin, amin, sqrt(max(radius2)) - rmin, max(angles) - amin) 

110 

111# Basic drawing elements. 

112def draw_nodebox(box, name='', properties=None, 

113 node_id=None, searched_by=None, style=None): 

114 properties = { k:v for k,v in (properties or {}).items() \ 

115 if not (k.startswith('_') or k == 'seq')} 

116 return ['nodebox', box, name, 

117 properties, node_id or [], 

118 searched_by or [], style or {}] 

119 

120def draw_outline(box, style=None): 

121 return ['outline', box, style or {}] 

122 

123def get_line_type(style): 

124 types = ['solid', 'dotted', 'dashed'] 

125 if style.get('type'): 

126 style['type'] = types[int(style['type'])] 

127 else: 

128 style['type'] = types[0] 

129 return style 

130 

131def draw_line(p1, p2, line_type='', parent_of=None, style=None): 

132 style = get_line_type(style or {}) 

133 return ['line', p1, p2, line_type, parent_of or [], style] 

134 

135def draw_arc(p1, p2, large=False, arc_type='', style=None): 

136 style = get_line_type(style or {}) 

137 return ['arc', p1, p2, int(large), arc_type, style] 

138 

139def draw_circle(center, radius, circle_type='', style=None, tooltip=None): 

140 return ['circle', center, radius, circle_type, style or {}, tooltip or ''] 

141 

142def draw_ellipse(center, rx, ry, ellipse_type='', style=None, tooltip=None): 

143 return ['ellipse', center, rx, ry, ellipse_type, style or {}, tooltip or ''] 

144 

145def draw_slice(center, r, a, da, slice_type='', style=None, tooltip=None): 

146 return ['slice', (center, r, a, da), slice_type, style or {}, tooltip or ''] 

147 

148def draw_triangle(box, tip, triangle_type='', style=None, tooltip=None): 

149 """Returns array with all the information needed to draw a triangle 

150 in front end. 

151 :box: bounds triangle 

152 :tip: defines tip orientation 'top', 'left' or 'right'. 

153 :triangle_type: will label triangle in front end (class) 

154 """ 

155 return ['triangle', box, tip, triangle_type, style or {}, tooltip or ''] 

156 

157def draw_text(box, text, text_type='', rotation=0, anchor=None, style=None): 

158 return ['text', box, text, text_type, rotation, anchor or "", style or {}] 

159 

160def draw_rect(box, rect_type, style=None, tooltip=None): 

161 return ['rect', box, rect_type, style or {}, tooltip or ''] 

162 

163def draw_rhombus(box, rhombus_type='', style=None, tooltip=None): 

164 """ Create rhombus provided a bounding box """ 

165 # Rotate the box to provide a rhombus (points) to drawing engine 

166 x, y, dx, dy = box 

167 rhombus = ((x + dx / 2, y), # top 

168 (x + dx, y + dy / 2), # right 

169 (x + dx / 2, y + dy), # bottom 

170 (x, y + dy / 2)) # left 

171 return ['rhombus', rhombus, rhombus_type, style or {}, tooltip or ''] 

172 

173def draw_arrow(box, tip, orientation='right', arrow_type='', 

174 style=None, tooltip=None): 

175 """ Create arrow provided a bounding box """ 

176 x, y, dx, dy = box 

177 

178 if orientation == 'right': 

179 arrow = ((x, y), 

180 (x + dx - tip, y), 

181 (x + dx, y + dy / 2), 

182 (x + dx - tip, y + dy), 

183 (x, y + dy)) 

184 elif orientation == 'left': 

185 arrow = ((x, y + dy / 2), 

186 (x + tip, y), 

187 (x + dx, y), 

188 (x + dx, y + dy), 

189 (x + tip, y + dy)) 

190 return ['polygon', arrow, arrow_type, style or {}, tooltip or ''] 

191 

192def draw_array(box, a, tooltip=None): 

193 return ['array', box, a, tooltip or ''] 

194 

195def draw_html(box, html, html_type='', style=None): 

196 return ['html', box, html, html_type, style or {}] 

197 

198def draw_img(box, img, img_type='', style=None): 

199 return ['img', box, img, img_type, style or {}]