Coverage for piemap/geometry.py: 98.62%
142 statements
« prev ^ index » next coverage.py v7.4.1, created at 2024-02-04 21:26:31 +00:00
« prev ^ index » next coverage.py v7.4.1, created at 2024-02-04 21:26:31 +00:00
1import math
2from typing import no_type_check
5@no_type_check
6def exact_top_of_center(angle):
7 """
8 True, if angle leads to point exactly top of center
9 """
10 return True if angle == 270 else False
13@no_type_check
14def exact_bottom_of_center(angle):
15 """
16 True, if angle leads to point exactly bottom of center
17 """
18 return True if angle == 90 else False
21@no_type_check
22def right_bottom_of_center(angle):
23 """
24 True, if angle leads to point right bottom of center
25 """
26 return True if angle < 90 else False
29@no_type_check
30def right_top_of_center(angle):
31 """
32 True, if angle leads to point right top of center
33 """
34 return True if angle > 270 else False
37@no_type_check
38def left_bottom_of_center(angle):
39 """
40 True, if angle leads to point left bottom of center
41 """
42 return True if 90 < angle < 180 else False
45@no_type_check
46def left_top_of_center(angle):
47 """
48 True, if angle leads to point left top of center
49 """
50 return True if 180 <= angle < 270 else False
53@no_type_check
54def octant_of_angle(angle):
55 """
56 derive octant in (N,NW,W,SW,S,SE,E,NE) counterclockwise from angle
57 """
58 try:
59 angle = int(angle)
60 except TypeError:
61 return 'X'
62 if exact_top_of_center(angle):
63 return 'N'
64 elif exact_bottom_of_center(angle):
65 return 'S'
66 elif right_bottom_of_center(angle):
67 return 'E' if angle == 0 else 'SE'
68 elif left_top_of_center(angle):
69 return 'W' if angle == 180 else 'NW'
70 elif left_bottom_of_center(angle):
71 return 'SW'
72 elif right_top_of_center(angle): 72 ↛ exitline 72 didn't return from function 'octant_of_angle', because the condition on line 72 was never false
73 return 'NE'
76@no_type_check
77def axis_name_circle_adjust(angle, font_size_pts, text_angle, font_name, axis_name, axis_name_space_sep=None):
78 """
79 calculate axis name placements from bounding box and angle
80 """
82 def bounding_box_from_font(fnt_size_pts, txt_angle, fnt_name, axs_name):
83 """
84 TODO(sthagen): PHP::imagettfbbox(font_size_pts, text_angle, font_name, axis_name)
85 6,7 upper left corners X,Y-Pos and 4,5 upper right corners X,Y-Pos
86 0,1 lower left corners X,Y-Pos and 2,3 lower right corners X,Y-Pos
88 Returns tuple with:
89 0, 1, 2, 3, 4, 5, 6, 7
90 lo_left_x, lo_left_y, lo_right_x, lo_right_y, hi_right_x, hi_right_y, hi_left_x, hi_left_y
91 """
92 return 100, 200, 300, None, None, None, None, 400 # TODO(sthagen) implement real functionality
94 axis_name_space_sep = True if axis_name_space_sep is None else axis_name_space_sep
96 bbox = bounding_box_from_font(font_size_pts, text_angle, font_name, axis_name)
97 text_width = abs(bbox[2] - bbox[0])
98 text_height = abs(bbox[7] - bbox[1])
100 sep_x = 0
101 sep_y = 0
102 if axis_name_space_sep: 102 ↛ 106line 102 didn't jump to line 106, because the condition on line 102 was never false
103 sep_x = 5
104 sep_y = 5
106 dx_pixel = 0
107 dy_pixel = 0
108 octant = octant_of_angle(angle)
109 if octant == 'SE':
110 dx_pixel = 0 + sep_x
111 dy_pixel = +text_height + sep_y
112 elif octant == 'NE':
113 dx_pixel = 0 + sep_x
114 dy_pixel = 0 - sep_y
115 elif octant == 'SW':
116 dx_pixel = -text_width - sep_x
117 dy_pixel = +text_height + sep_y
118 elif octant == 'NW':
119 dx_pixel = -text_width - sep_x
120 dy_pixel = 0 - sep_y
121 elif octant == 'S':
122 dx_pixel = -text_width / 2
123 dy_pixel = +text_height + 2 * sep_y
124 elif octant == 'N':
125 # exactly on top of center
126 dx_pixel = -text_width / 2
127 dy_pixel = 0 - 2 * sep_y
128 elif octant == 'W':
129 dx_pixel = -text_width - sep_x
130 dy_pixel = +text_height / 2 + 0
131 elif octant == 'E': 131 ↛ 136line 131 didn't jump to line 136, because the condition on line 131 was never false
132 dx_pixel = +text_width + sep_x
133 dy_pixel = +text_height / 2 + 0
135 # Note: Unmatched octant returns (0,0)
136 return dx_pixel, dy_pixel
139@no_type_check
140def xy_point_from_radius_angle(radius, angle, c_x=0, c_y=0):
141 """
142 return point as tuple in (x,y) = (radius,angle/[deg]) possibly shifted (c_x, c_y)
143 """
145 def deg2rad(deg):
146 """TODO(sthagen) refactor later."""
147 return deg * math.pi / 180.0
149 if math.isfinite(angle):
150 angle_in_rad = deg2rad(math.fmod(angle + 360, 360))
151 x = c_x + math.cos(angle_in_rad) * radius
152 y = c_y + math.sin(angle_in_rad) * radius
153 return x, y
154 else:
155 return math.nan, math.nan
158@no_type_check
159def segment_angle_map(needed_number_of_axis):
160 """
161 return map of clockwise allocated angles for the needed axis segments
162 from imagefilledarc: 0 degrees is located at the three-oclock position,
163 ... and the arc is drawn clockwise.
164 but we want one $angleMid allways point to 12 oclock, the rest
165 ... determined by $neededNumberOfAxis
166 and remember, we want the axis to point at 12 oclock, not start-stop
167 so n = 1 gives (0,360,360) in theory 180 full circle to 180
168 n = 2 gives [(270,90,360), (90,270,180)]
169 n = 3 gives [(300,60,360), (60,180,120), (180,300,240)]
170 """
171 closure_guard, norm = 0, 360
173 if needed_number_of_axis == 1:
174 return [(closure_guard, norm, norm)] # early exit for cornercase
176 angle_per_sect = norm / needed_number_of_axis
177 signed_correct_axis_shift = -angle_per_sect / 2
178 triplets = []
179 for i in range(0, needed_number_of_axis):
180 angle_start = math.fmod(i * angle_per_sect + signed_correct_axis_shift + norm, norm)
181 delta = angle_start + angle_per_sect
183 angle_stop = derive_stop(angle_start, closure_guard, delta, norm)
184 angle_mid = derive_mid(angle_start, angle_stop, norm)
186 triplets.append((angle_start, angle_stop, angle_mid))
188 return triplets
191@no_type_check
192def transform_angle_map_ncw_icw(segment_angle_map_ncw):
193 """
194 return map of adjusted angles for imagefilledarcClockWise from nativeCW
195 from imagefilledarc: 0 degrees is located at the three-oclock position,
196 ... and the arc is drawn clockwise.
197 but we want one $angleMid allways point to 12 oclock, the rest
198 ... determined by $neededNumberOfAxis
199 so transform maps 360 deg and 0 deg to 270 deg and 90 deg to 0 deg,
200 ... i.e. d=-90 modulo 360
201 """
202 my_eps = 0.0 # 0.0000000000001 - one zero more and 12+03 oclock for n=1
204 if len(segment_angle_map_ncw) == 1:
205 return [(270 + my_eps, 270 - my_eps, 270)] # early exit for cornercase
207 closure_guard, norm = 0, 360
208 signed_shift_degrees = -90
209 return rotate(closure_guard, norm, segment_angle_map_ncw, signed_shift_degrees)
212@no_type_check
213def transform_angle_map_icw_ncw(segment_angle_map_icw):
214 """
215 return map of canonicalized angles for nativeClockWise from imagefilledarcCW
216 from imagefilledarc: 0 degrees is located at the three-oclock position,
217 ... and the arc is drawn clockwise.
218 but we want one $angleMid allways point to 12 oclock, the rest
219 ... determined by $neededNumberOfAxis
220 so transform maps 360 deg and 0 deg to 270 deg and 90 deg to 0 deg,
221 ... i.e. d=-90 modulo 360
222 """
223 closure_guard, norm = 0, 360
224 # TODO(sthagen) remove after test: my_eps = 0.0000001
226 if len(segment_angle_map_icw) == 1:
227 return [(closure_guard, norm, norm)] # early exit for cornercase
229 signed_shift_degrees = +90
230 return rotate(closure_guard, norm, segment_angle_map_icw, signed_shift_degrees)
233@no_type_check
234def rotate(closure_guard, norm, segment_angle_map_icw, signed_shift_degrees):
235 """Derive triplet mappings for segments."""
236 triplets = []
237 for data in segment_angle_map_icw:
238 angle_start, angle_stop, angle_mid = data
240 angle_start = math.fmod(angle_start + signed_shift_degrees + norm, norm)
241 delta = angle_stop + signed_shift_degrees
243 angle_stop = derive_stop(angle_start, closure_guard, delta, norm)
244 angle_mid = derive_mid(angle_start, angle_stop, norm)
246 triplets.append((angle_start, angle_stop, angle_mid))
248 return triplets
251@no_type_check
252def derive_stop(angle_start, closure_guard, delta, norm):
253 """Derive stop angle respecting closure via norm."""
254 angle = math.fmod(delta + norm, norm)
255 if angle < angle_start and angle == closure_guard:
256 angle = norm
257 return angle
260@no_type_check
261def derive_mid(angle_start, angle_stop, norm):
262 """Derive mid angle respectng closure via norm."""
263 angle = (angle_stop + angle_start) / 2.0
264 if angle_stop < angle_start:
265 angle = (angle_stop + norm + angle_start) / 2.0
266 return angle