kerykeion.charts.draw_planets
1# type: ignore 2 3from kerykeion.charts.charts_utils import degreeDiff, sliceToX, sliceToY, convert_decimal_to_degree_string 4from kerykeion.kr_types import KerykeionException, ChartType, KerykeionPointModel 5from kerykeion.kr_types.settings_models import KerykeionSettingsCelestialPointModel 6from kerykeion.kr_types.kr_literals import Houses 7import logging 8from typing import Union, get_args 9 10 11 12def draw_planets( 13 radius: Union[int, float], 14 available_kerykeion_celestial_points: list[KerykeionPointModel], 15 available_planets_setting: list[KerykeionSettingsCelestialPointModel], 16 third_circle_radius: Union[int, float], 17 main_subject_first_house_degree_ut: Union[int, float], 18 main_subject_seventh_house_degree_ut: Union[int, float], 19 chart_type: ChartType, 20 second_subject_available_kerykeion_celestial_points: Union[list[KerykeionPointModel], None] = None, 21): 22 """ 23 Draws the planets on a chart based on the provided parameters. 24 25 Args: 26 radius (int): The radius of the chart. 27 available_kerykeion_celestial_points (list[KerykeionPointModel]): List of celestial points for the main subject. 28 available_planets_setting (list[KerykeionSettingsCelestialPointModel]): Settings for the celestial points. 29 third_circle_radius (Union[int, float]): Radius of the third circle. 30 main_subject_first_house_degree_ut (Union[int, float]): Degree of the first house for the main subject. 31 main_subject_seventh_house_degree_ut (Union[int, float]): Degree of the seventh house for the main subject. 32 chart_type (ChartType): Type of the chart (e.g., "Transit", "Synastry"). 33 second_subject_available_kerykeion_celestial_points (Union[list[KerykeionPointModel], None], optional): 34 List of celestial points for the second subject, required for "Transit" or "Synastry" charts. Defaults to None. 35 36 Raises: 37 KerykeionException: If the second subject is required but not provided. 38 39 Returns: 40 str: SVG output for the chart with the planets drawn. 41 """ 42 TRANSIT_RING_EXCLUDE_POINTS_NAMES = get_args(Houses) 43 44 if chart_type == "Transit" or chart_type == "Synastry": 45 if second_subject_available_kerykeion_celestial_points is None: 46 raise KerykeionException("Second subject is required for Transit or Synastry charts") 47 48 # Make a list for the absolute degrees of the points of the graphic. 49 points_deg_ut = [] 50 for planet in available_kerykeion_celestial_points: 51 points_deg_ut.append(planet.abs_pos) 52 53 # Make a list of the relative degrees of the points in the graphic. 54 points_deg = [] 55 for planet in available_kerykeion_celestial_points: 56 points_deg.append(planet.position) 57 58 if chart_type == "Transit" or chart_type == "Synastry": 59 # Make a list for the absolute degrees of the points of the graphic. 60 t_points_deg_ut = [] 61 for planet in second_subject_available_kerykeion_celestial_points: 62 t_points_deg_ut.append(planet.abs_pos) 63 64 # Make a list of the relative degrees of the points in the graphic. 65 t_points_deg = [] 66 for planet in second_subject_available_kerykeion_celestial_points: 67 t_points_deg.append(planet.position) 68 69 planets_degut = {} 70 diff = range(len(available_planets_setting)) 71 72 for i in range(len(available_planets_setting)): 73 # list of planets sorted by degree 74 logging.debug(f"planet: {i}, degree: {points_deg_ut[i]}") 75 planets_degut[points_deg_ut[i]] = i 76 77 """ 78 FIXME: The planets_degut is a dictionary like: 79 {planet_degree: planet_index} 80 It should be replaced bu points_deg_ut 81 print(points_deg_ut) 82 print(planets_degut) 83 """ 84 85 output = "" 86 keys = list(planets_degut.keys()) 87 keys.sort() 88 switch = 0 89 90 planets_degrouped = {} 91 groups = [] 92 planets_by_pos = list(range(len(planets_degut))) 93 planet_drange = 3.4 94 # get groups closely together 95 group_open = False 96 for e in range(len(keys)): 97 i = planets_degut[keys[e]] 98 # get distances between planets 99 if e == 0: 100 prev = points_deg_ut[planets_degut[keys[-1]]] 101 next = points_deg_ut[planets_degut[keys[1]]] 102 elif e == (len(keys) - 1): 103 prev = points_deg_ut[planets_degut[keys[e - 1]]] 104 next = points_deg_ut[planets_degut[keys[0]]] 105 else: 106 prev = points_deg_ut[planets_degut[keys[e - 1]]] 107 next = points_deg_ut[planets_degut[keys[e + 1]]] 108 diffa = degreeDiff(prev, points_deg_ut[i]) 109 diffb = degreeDiff(next, points_deg_ut[i]) 110 planets_by_pos[e] = [i, diffa, diffb] 111 112 logging.debug(f'{available_planets_setting[i]["label"]}, {diffa}, {diffb}') 113 114 if diffb < planet_drange: 115 if group_open: 116 groups[-1].append([e, diffa, diffb, available_planets_setting[planets_degut[keys[e]]]["label"]]) 117 else: 118 group_open = True 119 groups.append([]) 120 groups[-1].append([e, diffa, diffb, available_planets_setting[planets_degut[keys[e]]]["label"]]) 121 else: 122 if group_open: 123 groups[-1].append([e, diffa, diffb, available_planets_setting[planets_degut[keys[e]]]["label"]]) 124 group_open = False 125 126 def zero(x): 127 return 0 128 129 planets_delta = list(map(zero, range(len(available_planets_setting)))) 130 131 # print groups 132 # print planets_by_pos 133 for a in range(len(groups)): 134 # Two grouped planets 135 if len(groups[a]) == 2: 136 next_to_a = groups[a][0][0] - 1 137 if groups[a][1][0] == (len(planets_by_pos) - 1): 138 next_to_b = 0 139 else: 140 next_to_b = groups[a][1][0] + 1 141 # if both planets have room 142 if (groups[a][0][1] > (2 * planet_drange)) & (groups[a][1][2] > (2 * planet_drange)): 143 planets_delta[groups[a][0][0]] = -(planet_drange - groups[a][0][2]) / 2 144 planets_delta[groups[a][1][0]] = +(planet_drange - groups[a][0][2]) / 2 145 # if planet a has room 146 elif groups[a][0][1] > (2 * planet_drange): 147 planets_delta[groups[a][0][0]] = -planet_drange 148 # if planet b has room 149 elif groups[a][1][2] > (2 * planet_drange): 150 planets_delta[groups[a][1][0]] = +planet_drange 151 152 # if planets next to a and b have room move them 153 elif (planets_by_pos[next_to_a][1] > (2.4 * planet_drange)) & ( 154 planets_by_pos[next_to_b][2] > (2.4 * planet_drange) 155 ): 156 planets_delta[(next_to_a)] = groups[a][0][1] - planet_drange * 2 157 planets_delta[groups[a][0][0]] = -planet_drange * 0.5 158 planets_delta[next_to_b] = -(groups[a][1][2] - planet_drange * 2) 159 planets_delta[groups[a][1][0]] = +planet_drange * 0.5 160 161 # if planet next to a has room move them 162 elif planets_by_pos[next_to_a][1] > (2 * planet_drange): 163 planets_delta[(next_to_a)] = groups[a][0][1] - planet_drange * 2.5 164 planets_delta[groups[a][0][0]] = -planet_drange * 1.2 165 166 # if planet next to b has room move them 167 elif planets_by_pos[next_to_b][2] > (2 * planet_drange): 168 planets_delta[next_to_b] = -(groups[a][1][2] - planet_drange * 2.5) 169 planets_delta[groups[a][1][0]] = +planet_drange * 1.2 170 171 # Three grouped planets or more 172 xl = len(groups[a]) 173 if xl >= 3: 174 available = groups[a][0][1] 175 for f in range(xl): 176 available += groups[a][f][2] 177 need = (3 * planet_drange) + (1.2 * (xl - 1) * planet_drange) 178 leftover = available - need 179 xa = groups[a][0][1] 180 xb = groups[a][(xl - 1)][2] 181 182 # center 183 if (xa > (need * 0.5)) & (xb > (need * 0.5)): 184 startA = xa - (need * 0.5) 185 # position relative to next planets 186 else: 187 startA = (leftover / (xa + xb)) * xa 188 startB = (leftover / (xa + xb)) * xb 189 190 if available > need: 191 planets_delta[groups[a][0][0]] = startA - groups[a][0][1] + (1.5 * planet_drange) 192 for f in range(xl - 1): 193 planets_delta[groups[a][(f + 1)][0]] = ( 194 1.2 * planet_drange + planets_delta[groups[a][f][0]] - groups[a][f][2] 195 ) 196 197 for e in range(len(keys)): 198 i = planets_degut[keys[e]] 199 200 # coordinates 201 if chart_type == "Transit" or chart_type == "Synastry": 202 if 22 < i < 27: 203 rplanet = 76 204 elif switch == 1: 205 rplanet = 110 206 switch = 0 207 else: 208 rplanet = 130 209 switch = 1 210 else: 211 # if 22 < i < 27 it is asc,mc,dsc,ic (angles of chart) 212 # put on special line (rplanet is range from outer ring) 213 amin, bmin, cmin = 0, 0, 0 214 if chart_type == "ExternalNatal": 215 amin = 74 - 10 216 bmin = 94 - 10 217 cmin = 40 - 10 218 219 if 22 < i < 27: 220 rplanet = 40 - cmin 221 elif switch == 1: 222 rplanet = 74 - amin 223 switch = 0 224 else: 225 rplanet = 94 - bmin 226 switch = 1 227 228 rtext = 45 229 230 offset = (int(main_subject_seventh_house_degree_ut) / -1) + int(points_deg_ut[i] + planets_delta[e]) 231 trueoffset = (int(main_subject_seventh_house_degree_ut) / -1) + int(points_deg_ut[i]) 232 233 planet_x = sliceToX(0, (radius - rplanet), offset) + rplanet 234 planet_y = sliceToY(0, (radius - rplanet), offset) + rplanet 235 if chart_type == "Transit" or chart_type == "Synastry": 236 scale = 0.8 237 238 elif chart_type == "ExternalNatal": 239 scale = 0.8 240 # line1 241 x1 = sliceToX(0, (radius - third_circle_radius), trueoffset) + third_circle_radius 242 y1 = sliceToY(0, (radius - third_circle_radius), trueoffset) + third_circle_radius 243 x2 = sliceToX(0, (radius - rplanet - 30), trueoffset) + rplanet + 30 244 y2 = sliceToY(0, (radius - rplanet - 30), trueoffset) + rplanet + 30 245 color = available_planets_setting[i]["color"] 246 output += ( 247 '<line x1="%s" y1="%s" x2="%s" y2="%s" style="stroke-width:1px;stroke:%s;stroke-opacity:.3;"/>\n' 248 % (x1, y1, x2, y2, color) 249 ) 250 # line2 251 x1 = sliceToX(0, (radius - rplanet - 30), trueoffset) + rplanet + 30 252 y1 = sliceToY(0, (radius - rplanet - 30), trueoffset) + rplanet + 30 253 x2 = sliceToX(0, (radius - rplanet - 10), offset) + rplanet + 10 254 y2 = sliceToY(0, (radius - rplanet - 10), offset) + rplanet + 10 255 output += ( 256 '<line x1="%s" y1="%s" x2="%s" y2="%s" style="stroke-width:1px;stroke:%s;stroke-opacity:.5;"/>\n' 257 % (x1, y1, x2, y2, color) 258 ) 259 260 else: 261 scale = 1 262 263 planet_details = available_kerykeion_celestial_points[i] 264 265 output += f'<g kr:node="ChartPoint" kr:house="{planet_details["house"]}" kr:sign="{planet_details["sign"]}" kr:slug="{planet_details["name"]}" transform="translate(-{12 * scale},-{12 * scale}) scale({scale})">' 266 output += f'<use x="{planet_x * (1/scale)}" y="{planet_y * (1/scale)}" xlink:href="#{available_planets_setting[i]["name"]}" />' 267 output += f"</g>" 268 269 # make transit degut and display planets 270 if chart_type == "Transit" or chart_type == "Synastry": 271 group_offset = {} 272 t_planets_degut = {} 273 list_range = len(available_planets_setting) 274 275 for i in range(list_range): 276 if chart_type == "Transit" and available_planets_setting[i]["name"] in TRANSIT_RING_EXCLUDE_POINTS_NAMES: 277 continue 278 279 group_offset[i] = 0 280 t_planets_degut[t_points_deg_ut[i]] = i 281 282 t_keys = list(t_planets_degut.keys()) 283 t_keys.sort() 284 285 # grab closely grouped planets 286 groups = [] 287 in_group = False 288 for e in range(len(t_keys)): 289 i_a = t_planets_degut[t_keys[e]] 290 if e == (len(t_keys) - 1): 291 i_b = t_planets_degut[t_keys[0]] 292 else: 293 i_b = t_planets_degut[t_keys[e + 1]] 294 295 a = t_points_deg_ut[i_a] 296 b = t_points_deg_ut[i_b] 297 diff = degreeDiff(a, b) 298 if diff <= 2.5: 299 if in_group: 300 groups[-1].append(i_b) 301 else: 302 groups.append([i_a]) 303 groups[-1].append(i_b) 304 in_group = True 305 else: 306 in_group = False 307 # loop groups and set degrees display adjustment 308 for i in range(len(groups)): 309 if len(groups[i]) == 2: 310 group_offset[groups[i][0]] = -1.0 311 group_offset[groups[i][1]] = 1.0 312 elif len(groups[i]) == 3: 313 group_offset[groups[i][0]] = -1.5 314 group_offset[groups[i][1]] = 0 315 group_offset[groups[i][2]] = 1.5 316 elif len(groups[i]) == 4: 317 group_offset[groups[i][0]] = -2.0 318 group_offset[groups[i][1]] = -1.0 319 group_offset[groups[i][2]] = 1.0 320 group_offset[groups[i][3]] = 2.0 321 322 switch = 0 323 324 # Transit planets loop 325 for e in range(len(t_keys)): 326 if chart_type == "Transit" and available_planets_setting[e]["name"] in TRANSIT_RING_EXCLUDE_POINTS_NAMES: 327 continue 328 329 i = t_planets_degut[t_keys[e]] 330 331 if 22 < i < 27: 332 rplanet = 9 333 elif switch == 1: 334 rplanet = 18 335 switch = 0 336 else: 337 rplanet = 26 338 switch = 1 339 340 # Transit planet name 341 zeropoint = 360 - main_subject_seventh_house_degree_ut 342 t_offset = zeropoint + t_points_deg_ut[i] 343 if t_offset > 360: 344 t_offset = t_offset - 360 345 planet_x = sliceToX(0, (radius - rplanet), t_offset) + rplanet 346 planet_y = sliceToY(0, (radius - rplanet), t_offset) + rplanet 347 output += f'<g class="transit-planet-name" transform="translate(-6,-6)"><g transform="scale(0.5)"><use x="{planet_x*2}" y="{planet_y*2}" xlink:href="#{available_planets_setting[i]["name"]}" /></g></g>' 348 349 # Transit planet line 350 x1 = sliceToX(0, radius + 3, t_offset) - 3 351 y1 = sliceToY(0, radius + 3, t_offset) - 3 352 x2 = sliceToX(0, radius - 3, t_offset) + 3 353 y2 = sliceToY(0, radius - 3, t_offset) + 3 354 output += f'<line class="transit-planet-line" x1="{str(x1)}" y1="{str(y1)}" x2="{str(x2)}" y2="{str(y2)}" style="stroke: {available_planets_setting[i]["color"]}; stroke-width: 1px; stroke-opacity:.8;"/>' 355 356 # transit planet degree text 357 rotate = main_subject_first_house_degree_ut - t_points_deg_ut[i] 358 textanchor = "end" 359 t_offset += group_offset[i] 360 rtext = -3.0 361 362 if -90 > rotate > -270: 363 rotate = rotate + 180.0 364 textanchor = "start" 365 if 270 > rotate > 90: 366 rotate = rotate - 180.0 367 textanchor = "start" 368 369 if textanchor == "end": 370 xo = 1 371 else: 372 xo = -1 373 deg_x = sliceToX(0, (radius - rtext), t_offset + xo) + rtext 374 deg_y = sliceToY(0, (radius - rtext), t_offset + xo) + rtext 375 degree = int(t_offset) 376 output += f'<g transform="translate({deg_x},{deg_y})">' 377 output += f'<text transform="rotate({rotate})" text-anchor="{textanchor}' 378 output += f'" style="fill: {available_planets_setting[i]["color"]}; font-size: 10px;">{convert_decimal_to_degree_string(t_points_deg[i], format_type="1")}' 379 output += "</text></g>" 380 381 # check transit 382 if chart_type == "Transit" or chart_type == "Synastry": 383 dropin = 36 384 else: 385 dropin = 0 386 387 # planet line 388 x1 = sliceToX(0, radius - (dropin + 3), offset) + (dropin + 3) 389 y1 = sliceToY(0, radius - (dropin + 3), offset) + (dropin + 3) 390 x2 = sliceToX(0, (radius - (dropin - 3)), offset) + (dropin - 3) 391 y2 = sliceToY(0, (radius - (dropin - 3)), offset) + (dropin - 3) 392 393 output += f'<line x1="{x1}" y1="{y1}" x2="{x2}" y2="{y2}" style="stroke: {available_planets_setting[i]["color"]}; stroke-width: 2px; stroke-opacity:.6;"/>' 394 395 # check transit 396 if chart_type == "Transit" or chart_type == "Synastry": 397 dropin = 160 398 else: 399 dropin = 120 400 401 x1 = sliceToX(0, radius - dropin, offset) + dropin 402 y1 = sliceToY(0, radius - dropin, offset) + dropin 403 x2 = sliceToX(0, (radius - (dropin - 3)), offset) + (dropin - 3) 404 y2 = sliceToY(0, (radius - (dropin - 3)), offset) + (dropin - 3) 405 output += f'<line x1="{x1}" y1="{y1}" x2="{x2}" y2="{y2}" style="stroke: {available_planets_setting[i]["color"]}; stroke-width: 2px; stroke-opacity:.6;"/>' 406 407 return output
13def draw_planets( 14 radius: Union[int, float], 15 available_kerykeion_celestial_points: list[KerykeionPointModel], 16 available_planets_setting: list[KerykeionSettingsCelestialPointModel], 17 third_circle_radius: Union[int, float], 18 main_subject_first_house_degree_ut: Union[int, float], 19 main_subject_seventh_house_degree_ut: Union[int, float], 20 chart_type: ChartType, 21 second_subject_available_kerykeion_celestial_points: Union[list[KerykeionPointModel], None] = None, 22): 23 """ 24 Draws the planets on a chart based on the provided parameters. 25 26 Args: 27 radius (int): The radius of the chart. 28 available_kerykeion_celestial_points (list[KerykeionPointModel]): List of celestial points for the main subject. 29 available_planets_setting (list[KerykeionSettingsCelestialPointModel]): Settings for the celestial points. 30 third_circle_radius (Union[int, float]): Radius of the third circle. 31 main_subject_first_house_degree_ut (Union[int, float]): Degree of the first house for the main subject. 32 main_subject_seventh_house_degree_ut (Union[int, float]): Degree of the seventh house for the main subject. 33 chart_type (ChartType): Type of the chart (e.g., "Transit", "Synastry"). 34 second_subject_available_kerykeion_celestial_points (Union[list[KerykeionPointModel], None], optional): 35 List of celestial points for the second subject, required for "Transit" or "Synastry" charts. Defaults to None. 36 37 Raises: 38 KerykeionException: If the second subject is required but not provided. 39 40 Returns: 41 str: SVG output for the chart with the planets drawn. 42 """ 43 TRANSIT_RING_EXCLUDE_POINTS_NAMES = get_args(Houses) 44 45 if chart_type == "Transit" or chart_type == "Synastry": 46 if second_subject_available_kerykeion_celestial_points is None: 47 raise KerykeionException("Second subject is required for Transit or Synastry charts") 48 49 # Make a list for the absolute degrees of the points of the graphic. 50 points_deg_ut = [] 51 for planet in available_kerykeion_celestial_points: 52 points_deg_ut.append(planet.abs_pos) 53 54 # Make a list of the relative degrees of the points in the graphic. 55 points_deg = [] 56 for planet in available_kerykeion_celestial_points: 57 points_deg.append(planet.position) 58 59 if chart_type == "Transit" or chart_type == "Synastry": 60 # Make a list for the absolute degrees of the points of the graphic. 61 t_points_deg_ut = [] 62 for planet in second_subject_available_kerykeion_celestial_points: 63 t_points_deg_ut.append(planet.abs_pos) 64 65 # Make a list of the relative degrees of the points in the graphic. 66 t_points_deg = [] 67 for planet in second_subject_available_kerykeion_celestial_points: 68 t_points_deg.append(planet.position) 69 70 planets_degut = {} 71 diff = range(len(available_planets_setting)) 72 73 for i in range(len(available_planets_setting)): 74 # list of planets sorted by degree 75 logging.debug(f"planet: {i}, degree: {points_deg_ut[i]}") 76 planets_degut[points_deg_ut[i]] = i 77 78 """ 79 FIXME: The planets_degut is a dictionary like: 80 {planet_degree: planet_index} 81 It should be replaced bu points_deg_ut 82 print(points_deg_ut) 83 print(planets_degut) 84 """ 85 86 output = "" 87 keys = list(planets_degut.keys()) 88 keys.sort() 89 switch = 0 90 91 planets_degrouped = {} 92 groups = [] 93 planets_by_pos = list(range(len(planets_degut))) 94 planet_drange = 3.4 95 # get groups closely together 96 group_open = False 97 for e in range(len(keys)): 98 i = planets_degut[keys[e]] 99 # get distances between planets 100 if e == 0: 101 prev = points_deg_ut[planets_degut[keys[-1]]] 102 next = points_deg_ut[planets_degut[keys[1]]] 103 elif e == (len(keys) - 1): 104 prev = points_deg_ut[planets_degut[keys[e - 1]]] 105 next = points_deg_ut[planets_degut[keys[0]]] 106 else: 107 prev = points_deg_ut[planets_degut[keys[e - 1]]] 108 next = points_deg_ut[planets_degut[keys[e + 1]]] 109 diffa = degreeDiff(prev, points_deg_ut[i]) 110 diffb = degreeDiff(next, points_deg_ut[i]) 111 planets_by_pos[e] = [i, diffa, diffb] 112 113 logging.debug(f'{available_planets_setting[i]["label"]}, {diffa}, {diffb}') 114 115 if diffb < planet_drange: 116 if group_open: 117 groups[-1].append([e, diffa, diffb, available_planets_setting[planets_degut[keys[e]]]["label"]]) 118 else: 119 group_open = True 120 groups.append([]) 121 groups[-1].append([e, diffa, diffb, available_planets_setting[planets_degut[keys[e]]]["label"]]) 122 else: 123 if group_open: 124 groups[-1].append([e, diffa, diffb, available_planets_setting[planets_degut[keys[e]]]["label"]]) 125 group_open = False 126 127 def zero(x): 128 return 0 129 130 planets_delta = list(map(zero, range(len(available_planets_setting)))) 131 132 # print groups 133 # print planets_by_pos 134 for a in range(len(groups)): 135 # Two grouped planets 136 if len(groups[a]) == 2: 137 next_to_a = groups[a][0][0] - 1 138 if groups[a][1][0] == (len(planets_by_pos) - 1): 139 next_to_b = 0 140 else: 141 next_to_b = groups[a][1][0] + 1 142 # if both planets have room 143 if (groups[a][0][1] > (2 * planet_drange)) & (groups[a][1][2] > (2 * planet_drange)): 144 planets_delta[groups[a][0][0]] = -(planet_drange - groups[a][0][2]) / 2 145 planets_delta[groups[a][1][0]] = +(planet_drange - groups[a][0][2]) / 2 146 # if planet a has room 147 elif groups[a][0][1] > (2 * planet_drange): 148 planets_delta[groups[a][0][0]] = -planet_drange 149 # if planet b has room 150 elif groups[a][1][2] > (2 * planet_drange): 151 planets_delta[groups[a][1][0]] = +planet_drange 152 153 # if planets next to a and b have room move them 154 elif (planets_by_pos[next_to_a][1] > (2.4 * planet_drange)) & ( 155 planets_by_pos[next_to_b][2] > (2.4 * planet_drange) 156 ): 157 planets_delta[(next_to_a)] = groups[a][0][1] - planet_drange * 2 158 planets_delta[groups[a][0][0]] = -planet_drange * 0.5 159 planets_delta[next_to_b] = -(groups[a][1][2] - planet_drange * 2) 160 planets_delta[groups[a][1][0]] = +planet_drange * 0.5 161 162 # if planet next to a has room move them 163 elif planets_by_pos[next_to_a][1] > (2 * planet_drange): 164 planets_delta[(next_to_a)] = groups[a][0][1] - planet_drange * 2.5 165 planets_delta[groups[a][0][0]] = -planet_drange * 1.2 166 167 # if planet next to b has room move them 168 elif planets_by_pos[next_to_b][2] > (2 * planet_drange): 169 planets_delta[next_to_b] = -(groups[a][1][2] - planet_drange * 2.5) 170 planets_delta[groups[a][1][0]] = +planet_drange * 1.2 171 172 # Three grouped planets or more 173 xl = len(groups[a]) 174 if xl >= 3: 175 available = groups[a][0][1] 176 for f in range(xl): 177 available += groups[a][f][2] 178 need = (3 * planet_drange) + (1.2 * (xl - 1) * planet_drange) 179 leftover = available - need 180 xa = groups[a][0][1] 181 xb = groups[a][(xl - 1)][2] 182 183 # center 184 if (xa > (need * 0.5)) & (xb > (need * 0.5)): 185 startA = xa - (need * 0.5) 186 # position relative to next planets 187 else: 188 startA = (leftover / (xa + xb)) * xa 189 startB = (leftover / (xa + xb)) * xb 190 191 if available > need: 192 planets_delta[groups[a][0][0]] = startA - groups[a][0][1] + (1.5 * planet_drange) 193 for f in range(xl - 1): 194 planets_delta[groups[a][(f + 1)][0]] = ( 195 1.2 * planet_drange + planets_delta[groups[a][f][0]] - groups[a][f][2] 196 ) 197 198 for e in range(len(keys)): 199 i = planets_degut[keys[e]] 200 201 # coordinates 202 if chart_type == "Transit" or chart_type == "Synastry": 203 if 22 < i < 27: 204 rplanet = 76 205 elif switch == 1: 206 rplanet = 110 207 switch = 0 208 else: 209 rplanet = 130 210 switch = 1 211 else: 212 # if 22 < i < 27 it is asc,mc,dsc,ic (angles of chart) 213 # put on special line (rplanet is range from outer ring) 214 amin, bmin, cmin = 0, 0, 0 215 if chart_type == "ExternalNatal": 216 amin = 74 - 10 217 bmin = 94 - 10 218 cmin = 40 - 10 219 220 if 22 < i < 27: 221 rplanet = 40 - cmin 222 elif switch == 1: 223 rplanet = 74 - amin 224 switch = 0 225 else: 226 rplanet = 94 - bmin 227 switch = 1 228 229 rtext = 45 230 231 offset = (int(main_subject_seventh_house_degree_ut) / -1) + int(points_deg_ut[i] + planets_delta[e]) 232 trueoffset = (int(main_subject_seventh_house_degree_ut) / -1) + int(points_deg_ut[i]) 233 234 planet_x = sliceToX(0, (radius - rplanet), offset) + rplanet 235 planet_y = sliceToY(0, (radius - rplanet), offset) + rplanet 236 if chart_type == "Transit" or chart_type == "Synastry": 237 scale = 0.8 238 239 elif chart_type == "ExternalNatal": 240 scale = 0.8 241 # line1 242 x1 = sliceToX(0, (radius - third_circle_radius), trueoffset) + third_circle_radius 243 y1 = sliceToY(0, (radius - third_circle_radius), trueoffset) + third_circle_radius 244 x2 = sliceToX(0, (radius - rplanet - 30), trueoffset) + rplanet + 30 245 y2 = sliceToY(0, (radius - rplanet - 30), trueoffset) + rplanet + 30 246 color = available_planets_setting[i]["color"] 247 output += ( 248 '<line x1="%s" y1="%s" x2="%s" y2="%s" style="stroke-width:1px;stroke:%s;stroke-opacity:.3;"/>\n' 249 % (x1, y1, x2, y2, color) 250 ) 251 # line2 252 x1 = sliceToX(0, (radius - rplanet - 30), trueoffset) + rplanet + 30 253 y1 = sliceToY(0, (radius - rplanet - 30), trueoffset) + rplanet + 30 254 x2 = sliceToX(0, (radius - rplanet - 10), offset) + rplanet + 10 255 y2 = sliceToY(0, (radius - rplanet - 10), offset) + rplanet + 10 256 output += ( 257 '<line x1="%s" y1="%s" x2="%s" y2="%s" style="stroke-width:1px;stroke:%s;stroke-opacity:.5;"/>\n' 258 % (x1, y1, x2, y2, color) 259 ) 260 261 else: 262 scale = 1 263 264 planet_details = available_kerykeion_celestial_points[i] 265 266 output += f'<g kr:node="ChartPoint" kr:house="{planet_details["house"]}" kr:sign="{planet_details["sign"]}" kr:slug="{planet_details["name"]}" transform="translate(-{12 * scale},-{12 * scale}) scale({scale})">' 267 output += f'<use x="{planet_x * (1/scale)}" y="{planet_y * (1/scale)}" xlink:href="#{available_planets_setting[i]["name"]}" />' 268 output += f"</g>" 269 270 # make transit degut and display planets 271 if chart_type == "Transit" or chart_type == "Synastry": 272 group_offset = {} 273 t_planets_degut = {} 274 list_range = len(available_planets_setting) 275 276 for i in range(list_range): 277 if chart_type == "Transit" and available_planets_setting[i]["name"] in TRANSIT_RING_EXCLUDE_POINTS_NAMES: 278 continue 279 280 group_offset[i] = 0 281 t_planets_degut[t_points_deg_ut[i]] = i 282 283 t_keys = list(t_planets_degut.keys()) 284 t_keys.sort() 285 286 # grab closely grouped planets 287 groups = [] 288 in_group = False 289 for e in range(len(t_keys)): 290 i_a = t_planets_degut[t_keys[e]] 291 if e == (len(t_keys) - 1): 292 i_b = t_planets_degut[t_keys[0]] 293 else: 294 i_b = t_planets_degut[t_keys[e + 1]] 295 296 a = t_points_deg_ut[i_a] 297 b = t_points_deg_ut[i_b] 298 diff = degreeDiff(a, b) 299 if diff <= 2.5: 300 if in_group: 301 groups[-1].append(i_b) 302 else: 303 groups.append([i_a]) 304 groups[-1].append(i_b) 305 in_group = True 306 else: 307 in_group = False 308 # loop groups and set degrees display adjustment 309 for i in range(len(groups)): 310 if len(groups[i]) == 2: 311 group_offset[groups[i][0]] = -1.0 312 group_offset[groups[i][1]] = 1.0 313 elif len(groups[i]) == 3: 314 group_offset[groups[i][0]] = -1.5 315 group_offset[groups[i][1]] = 0 316 group_offset[groups[i][2]] = 1.5 317 elif len(groups[i]) == 4: 318 group_offset[groups[i][0]] = -2.0 319 group_offset[groups[i][1]] = -1.0 320 group_offset[groups[i][2]] = 1.0 321 group_offset[groups[i][3]] = 2.0 322 323 switch = 0 324 325 # Transit planets loop 326 for e in range(len(t_keys)): 327 if chart_type == "Transit" and available_planets_setting[e]["name"] in TRANSIT_RING_EXCLUDE_POINTS_NAMES: 328 continue 329 330 i = t_planets_degut[t_keys[e]] 331 332 if 22 < i < 27: 333 rplanet = 9 334 elif switch == 1: 335 rplanet = 18 336 switch = 0 337 else: 338 rplanet = 26 339 switch = 1 340 341 # Transit planet name 342 zeropoint = 360 - main_subject_seventh_house_degree_ut 343 t_offset = zeropoint + t_points_deg_ut[i] 344 if t_offset > 360: 345 t_offset = t_offset - 360 346 planet_x = sliceToX(0, (radius - rplanet), t_offset) + rplanet 347 planet_y = sliceToY(0, (radius - rplanet), t_offset) + rplanet 348 output += f'<g class="transit-planet-name" transform="translate(-6,-6)"><g transform="scale(0.5)"><use x="{planet_x*2}" y="{planet_y*2}" xlink:href="#{available_planets_setting[i]["name"]}" /></g></g>' 349 350 # Transit planet line 351 x1 = sliceToX(0, radius + 3, t_offset) - 3 352 y1 = sliceToY(0, radius + 3, t_offset) - 3 353 x2 = sliceToX(0, radius - 3, t_offset) + 3 354 y2 = sliceToY(0, radius - 3, t_offset) + 3 355 output += f'<line class="transit-planet-line" x1="{str(x1)}" y1="{str(y1)}" x2="{str(x2)}" y2="{str(y2)}" style="stroke: {available_planets_setting[i]["color"]}; stroke-width: 1px; stroke-opacity:.8;"/>' 356 357 # transit planet degree text 358 rotate = main_subject_first_house_degree_ut - t_points_deg_ut[i] 359 textanchor = "end" 360 t_offset += group_offset[i] 361 rtext = -3.0 362 363 if -90 > rotate > -270: 364 rotate = rotate + 180.0 365 textanchor = "start" 366 if 270 > rotate > 90: 367 rotate = rotate - 180.0 368 textanchor = "start" 369 370 if textanchor == "end": 371 xo = 1 372 else: 373 xo = -1 374 deg_x = sliceToX(0, (radius - rtext), t_offset + xo) + rtext 375 deg_y = sliceToY(0, (radius - rtext), t_offset + xo) + rtext 376 degree = int(t_offset) 377 output += f'<g transform="translate({deg_x},{deg_y})">' 378 output += f'<text transform="rotate({rotate})" text-anchor="{textanchor}' 379 output += f'" style="fill: {available_planets_setting[i]["color"]}; font-size: 10px;">{convert_decimal_to_degree_string(t_points_deg[i], format_type="1")}' 380 output += "</text></g>" 381 382 # check transit 383 if chart_type == "Transit" or chart_type == "Synastry": 384 dropin = 36 385 else: 386 dropin = 0 387 388 # planet line 389 x1 = sliceToX(0, radius - (dropin + 3), offset) + (dropin + 3) 390 y1 = sliceToY(0, radius - (dropin + 3), offset) + (dropin + 3) 391 x2 = sliceToX(0, (radius - (dropin - 3)), offset) + (dropin - 3) 392 y2 = sliceToY(0, (radius - (dropin - 3)), offset) + (dropin - 3) 393 394 output += f'<line x1="{x1}" y1="{y1}" x2="{x2}" y2="{y2}" style="stroke: {available_planets_setting[i]["color"]}; stroke-width: 2px; stroke-opacity:.6;"/>' 395 396 # check transit 397 if chart_type == "Transit" or chart_type == "Synastry": 398 dropin = 160 399 else: 400 dropin = 120 401 402 x1 = sliceToX(0, radius - dropin, offset) + dropin 403 y1 = sliceToY(0, radius - dropin, offset) + dropin 404 x2 = sliceToX(0, (radius - (dropin - 3)), offset) + (dropin - 3) 405 y2 = sliceToY(0, (radius - (dropin - 3)), offset) + (dropin - 3) 406 output += f'<line x1="{x1}" y1="{y1}" x2="{x2}" y2="{y2}" style="stroke: {available_planets_setting[i]["color"]}; stroke-width: 2px; stroke-opacity:.6;"/>' 407 408 return output
Draws the planets on a chart based on the provided parameters.
Args: radius (int): The radius of the chart. available_kerykeion_celestial_points (list[KerykeionPointModel]): List of celestial points for the main subject. available_planets_setting (list[KerykeionSettingsCelestialPointModel]): Settings for the celestial points. third_circle_radius (Union[int, float]): Radius of the third circle. main_subject_first_house_degree_ut (Union[int, float]): Degree of the first house for the main subject. main_subject_seventh_house_degree_ut (Union[int, float]): Degree of the seventh house for the main subject. chart_type (ChartType): Type of the chart (e.g., "Transit", "Synastry"). second_subject_available_kerykeion_celestial_points (Union[list[KerykeionPointModel], None], optional): List of celestial points for the second subject, required for "Transit" or "Synastry" charts. Defaults to None.
Raises: KerykeionException: If the second subject is required but not provided.
Returns: str: SVG output for the chart with the planets drawn.