kerykeion.charts.charts_utils
1import math 2import datetime 3from kerykeion.kr_types import KerykeionException, ChartType 4from typing import Union, Literal 5from kerykeion.kr_types.kr_models import AspectModel, KerykeionPointModel 6from kerykeion.kr_types.settings_models import KerykeionLanguageCelestialPointModel, KerykeionSettingsAspectModel 7 8 9def get_decoded_kerykeion_celestial_point_name(input_planet_name: str, celestial_point_language: KerykeionLanguageCelestialPointModel) -> str: 10 """ 11 Decode the given celestial point name based on the provided language model. 12 13 Args: 14 input_planet_name (str): The name of the celestial point to decode. 15 celestial_point_language (KerykeionLanguageCelestialPointModel): The language model containing celestial point names. 16 17 Returns: 18 str: The decoded celestial point name. 19 """ 20 21 22 # Get the language model keys 23 language_keys = celestial_point_language.model_dump().keys() 24 25 # Check if the input planet name exists in the language model 26 if input_planet_name in language_keys: 27 return celestial_point_language[input_planet_name] 28 else: 29 raise KerykeionException(f"Celestial point {input_planet_name} not found in language model.") 30 31 32def decHourJoin(inH: int, inM: int, inS: int) -> float: 33 """Join hour, minutes, seconds, timezone integer to hour float. 34 35 Args: 36 - inH (int): hour 37 - inM (int): minutes 38 - inS (int): seconds 39 Returns: 40 float: hour in float format 41 """ 42 43 dh = float(inH) 44 dm = float(inM) / 60 45 ds = float(inS) / 3600 46 output = dh + dm + ds 47 return output 48 49 50def degreeDiff(a: Union[int, float], b: Union[int, float]) -> float: 51 """Calculate the smallest difference between two angles in degrees. 52 53 Args: 54 a (int | float): first angle in degrees 55 b (int | float): second angle in degrees 56 57 Returns: 58 float: smallest difference between a and b (0 to 180 degrees) 59 """ 60 diff = math.fmod(abs(a - b), 360) # Assicura che il valore sia in [0, 360) 61 return min(diff, 360 - diff) # Prende l'angolo piĆ¹ piccolo tra i due possibili 62 63 64def degreeSum(a: Union[int, float], b: Union[int, float]) -> float: 65 """Calculate the sum of two angles in degrees, normalized to [0, 360). 66 67 Args: 68 a (int | float): first angle in degrees 69 b (int | float): second angle in degrees 70 71 Returns: 72 float: normalized sum of a and b in the range [0, 360) 73 """ 74 return math.fmod(a + b, 360) if (a + b) % 360 != 0 else 0.0 75 76 77def normalizeDegree(angle: Union[int, float]) -> float: 78 """Normalize an angle to the range [0, 360). 79 80 Args: 81 angle (int | float): The input angle in degrees. 82 83 Returns: 84 float: The normalized angle in the range [0, 360). 85 """ 86 return angle % 360 if angle % 360 != 0 else 0.0 87 88 89def offsetToTz(datetime_offset: Union[datetime.timedelta, None]) -> float: 90 """Convert datetime offset to float in hours. 91 92 Args: 93 - datetime_offset (datetime.timedelta): datetime offset 94 95 Returns: 96 - float: offset in hours 97 """ 98 99 if datetime_offset is None: 100 raise KerykeionException("datetime_offset is None") 101 102 # days to hours 103 dh = float(datetime_offset.days * 24) 104 # seconds to hours 105 sh = float(datetime_offset.seconds / 3600.0) 106 # total hours 107 output = dh + sh 108 return output 109 110 111def sliceToX(slice: Union[int, float], radius: Union[int, float], offset: Union[int, float]) -> float: 112 """Calculates the x-coordinate of a point on a circle based on the slice, radius, and offset. 113 114 Args: 115 - slice (int | float): Represents the 116 slice of the circle to calculate the x-coordinate for. 117 It must be between 0 and 11 (inclusive). 118 - radius (int | float): Represents the radius of the circle. 119 - offset (int | float): Represents the offset in degrees. 120 It must be between 0 and 360 (inclusive). 121 122 Returns: 123 float: The x-coordinate of the point on the circle. 124 125 Example: 126 >>> import math 127 >>> sliceToX(3, 5, 45) 128 2.5000000000000018 129 """ 130 131 plus = (math.pi * offset) / 180 132 radial = ((math.pi / 6) * slice) + plus 133 return radius * (math.cos(radial) + 1) 134 135 136def sliceToY(slice: Union[int, float], r: Union[int, float], offset: Union[int, float]) -> float: 137 """Calculates the y-coordinate of a point on a circle based on the slice, radius, and offset. 138 139 Args: 140 - slice (int | float): Represents the slice of the circle to calculate 141 the y-coordinate for. It must be between 0 and 11 (inclusive). 142 - r (int | float): Represents the radius of the circle. 143 - offset (int | float): Represents the offset in degrees. 144 It must be between 0 and 360 (inclusive). 145 146 Returns: 147 float: The y-coordinate of the point on the circle. 148 149 Example: 150 >>> import math 151 >>> __sliceToY(3, 5, 45) 152 -4.330127018922194 153 """ 154 plus = (math.pi * offset) / 180 155 radial = ((math.pi / 6) * slice) + plus 156 return r * ((math.sin(radial) / -1) + 1) 157 158 159def draw_zodiac_slice( 160 c1: Union[int, float], 161 chart_type: ChartType, 162 seventh_house_degree_ut: Union[int, float], 163 num: int, 164 r: Union[int, float], 165 style: str, 166 type: str, 167) -> str: 168 """Draws a zodiac slice based on the given parameters. 169 170 Args: 171 - c1 (Union[int, float]): The value of c1. 172 - chart_type (ChartType): The type of chart. 173 - seventh_house_degree_ut (Union[int, float]): The degree of the seventh house. 174 - num (int): The number of the sign. Note: In OpenAstro it did refer to self.zodiac, 175 which is a list of the signs in order, starting with Aries. Eg: 176 {"name": "Ari", "element": "fire"} 177 - r (Union[int, float]): The value of r. 178 - style (str): The CSS inline style. 179 - type (str): The type ?. In OpenAstro, it was the symbol of the sign. Eg: "Ari". 180 self.zodiac[i]["name"] 181 182 Returns: 183 - str: The zodiac slice and symbol as an SVG path. 184 """ 185 186 # pie slices 187 offset = 360 - seventh_house_degree_ut 188 # check transit 189 if chart_type == "Transit" or chart_type == "Synastry": 190 dropin: Union[int, float] = 0 191 else: 192 dropin = c1 193 slice = f'<path d="M{str(r)},{str(r)} L{str(dropin + sliceToX(num, r - dropin, offset))},{str(dropin + sliceToY(num, r - dropin, offset))} A{str(r - dropin)},{str(r - dropin)} 0 0,0 {str(dropin + sliceToX(num + 1, r - dropin, offset))},{str(dropin + sliceToY(num + 1, r - dropin, offset))} z" style="{style}"/>' 194 195 # symbols 196 offset = offset + 15 197 # check transit 198 if chart_type == "Transit" or chart_type == "Synastry": 199 dropin = 54 200 else: 201 dropin = 18 + c1 202 sign = f'<g transform="translate(-16,-16)"><use x="{str(dropin + sliceToX(num, r - dropin, offset))}" y="{str(dropin + sliceToY(num, r - dropin, offset))}" xlink:href="#{type}" /></g>' 203 204 return slice + "" + sign 205 206 207def convert_latitude_coordinate_to_string(coord: Union[int, float], north_label: str, south_label: str) -> str: 208 """Converts a floating point latitude to string with 209 degree, minutes and seconds and the appropriate sign 210 (north or south). Eg. 52.1234567 -> 52Ā°7'25" N 211 212 Args: 213 - coord (float | int): latitude in floating or integer format 214 - north_label (str): String label for north 215 - south_label (str): String label for south 216 Returns: 217 - str: latitude in string format with degree, minutes, 218 seconds and sign (N/S) 219 """ 220 221 sign = north_label 222 if coord < 0.0: 223 sign = south_label 224 coord = abs(coord) 225 deg = int(coord) 226 min = int((float(coord) - deg) * 60) 227 sec = int(round(float(((float(coord) - deg) * 60) - min) * 60.0)) 228 return f"{deg}Ā°{min}'{sec}\" {sign}" 229 230 231def convert_longitude_coordinate_to_string(coord: Union[int, float], east_label: str, west_label: str) -> str: 232 """Converts a floating point longitude to string with 233 degree, minutes and seconds and the appropriate sign 234 (east or west). Eg. 52.1234567 -> 52Ā°7'25" E 235 236 Args: 237 - coord (float|int): longitude in floating point format 238 - east_label (str): String label for east 239 - west_label (str): String label for west 240 Returns: 241 str: longitude in string format with degree, minutes, 242 seconds and sign (E/W) 243 """ 244 245 sign = east_label 246 if coord < 0.0: 247 sign = west_label 248 coord = abs(coord) 249 deg = int(coord) 250 min = int((float(coord) - deg) * 60) 251 sec = int(round(float(((float(coord) - deg) * 60) - min) * 60.0)) 252 return f"{deg}Ā°{min}'{sec}\" {sign}" 253 254 255def draw_aspect_line( 256 r: Union[int, float], 257 ar: Union[int, float], 258 aspect: Union[AspectModel, dict], 259 color: str, 260 seventh_house_degree_ut: Union[int, float], 261) -> str: 262 """Draws svg aspects: ring, aspect ring, degreeA degreeB 263 264 Args: 265 - r (Union[int, float]): The value of r. 266 - ar (Union[int, float]): The value of ar. 267 - aspect_dict (dict): The aspect dictionary. 268 - color (str): The color of the aspect. 269 - seventh_house_degree_ut (Union[int, float]): The degree of the seventh house. 270 271 Returns: 272 str: The SVG line element as a string. 273 """ 274 275 if isinstance(aspect, dict): 276 aspect = AspectModel(**aspect) 277 278 first_offset = (int(seventh_house_degree_ut) / -1) + int(aspect["p1_abs_pos"]) 279 x1 = sliceToX(0, ar, first_offset) + (r - ar) 280 y1 = sliceToY(0, ar, first_offset) + (r - ar) 281 282 second_offset = (int(seventh_house_degree_ut) / -1) + int(aspect["p2_abs_pos"]) 283 x2 = sliceToX(0, ar, second_offset) + (r - ar) 284 y2 = sliceToY(0, ar, second_offset) + (r - ar) 285 286 return ( 287 f'<g kr:node="Aspect" kr:aspectname="{aspect["aspect"]}" kr:to="{aspect["p1_name"]}" kr:tooriginaldegrees="{aspect["p1_abs_pos"]}" kr:from="{aspect["p2_name"]}" kr:fromoriginaldegrees="{aspect["p2_abs_pos"]}">' 288 f'<line class="aspect" x1="{x1}" y1="{y1}" x2="{x2}" y2="{y2}" style="stroke: {color}; stroke-width: 1; stroke-opacity: .9;"/>' 289 f"</g>" 290 ) 291 292def convert_decimal_to_degree_string(dec: float, format_type: Literal["1", "2", "3"] = "3") -> str: 293 """ 294 Converts a decimal float to a degrees string in the specified format. 295 296 Args: 297 dec (float): The decimal float to convert. 298 format_type (str): The format type: 299 - "1": aĀ° 300 - "2": aĀ°b' 301 - "3": aĀ°b'c" (default) 302 303 Returns: 304 str: The degrees string in the specified format. 305 """ 306 # Ensure the input is a float 307 dec = float(dec) 308 309 # Calculate degrees, minutes, and seconds 310 degrees = int(dec) 311 minutes = int((dec - degrees) * 60) 312 seconds = int(round((dec - degrees - minutes / 60) * 3600)) 313 314 # Format the output based on the specified type 315 if format_type == "1": 316 return f"{degrees}Ā°" 317 elif format_type == "2": 318 return f"{degrees}Ā°{minutes:02d}'" 319 elif format_type == "3": 320 return f"{degrees}Ā°{minutes:02d}'{seconds:02d}\"" 321 322 323def draw_transit_ring_degree_steps(r: Union[int, float], seventh_house_degree_ut: Union[int, float]) -> str: 324 """Draws the transit ring degree steps. 325 326 Args: 327 - r (Union[int, float]): The value of r. 328 - seventh_house_degree_ut (Union[int, float]): The degree of the seventh house. 329 330 Returns: 331 str: The SVG path of the transit ring degree steps. 332 """ 333 334 out = '<g id="transitRingDegreeSteps">' 335 for i in range(72): 336 offset = float(i * 5) - seventh_house_degree_ut 337 if offset < 0: 338 offset = offset + 360.0 339 elif offset > 360: 340 offset = offset - 360.0 341 x1 = sliceToX(0, r, offset) 342 y1 = sliceToY(0, r, offset) 343 x2 = sliceToX(0, r + 2, offset) - 2 344 y2 = sliceToY(0, r + 2, offset) - 2 345 out += f'<line x1="{x1}" y1="{y1}" x2="{x2}" y2="{y2}" style="stroke: #F00; stroke-width: 1px; stroke-opacity:.9;"/>' 346 out += "</g>" 347 348 return out 349 350 351def draw_degree_ring( 352 r: Union[int, float], c1: Union[int, float], seventh_house_degree_ut: Union[int, float], stroke_color: str 353) -> str: 354 """Draws the degree ring. 355 356 Args: 357 - r (Union[int, float]): The value of r. 358 - c1 (Union[int, float]): The value of c1. 359 - seventh_house_degree_ut (Union[int, float]): The degree of the seventh house. 360 - stroke_color (str): The color of the stroke. 361 362 Returns: 363 str: The SVG path of the degree ring. 364 """ 365 out = '<g id="degreeRing">' 366 for i in range(72): 367 offset = float(i * 5) - seventh_house_degree_ut 368 if offset < 0: 369 offset = offset + 360.0 370 elif offset > 360: 371 offset = offset - 360.0 372 x1 = sliceToX(0, r - c1, offset) + c1 373 y1 = sliceToY(0, r - c1, offset) + c1 374 x2 = sliceToX(0, r + 2 - c1, offset) - 2 + c1 375 y2 = sliceToY(0, r + 2 - c1, offset) - 2 + c1 376 377 out += f'<line x1="{x1}" y1="{y1}" x2="{x2}" y2="{y2}" style="stroke: {stroke_color}; stroke-width: 1px; stroke-opacity:.9;"/>' 378 out += "</g>" 379 380 return out 381 382 383def draw_transit_ring(r: Union[int, float], paper_1_color: str, zodiac_transit_ring_3_color: str) -> str: 384 """ 385 Draws the transit ring. 386 387 Args: 388 - r (Union[int, float]): The value of r. 389 - paper_1_color (str): The color of paper 1. 390 - zodiac_transit_ring_3_color (str): The color of the zodiac transit ring 391 392 Returns: 393 str: The SVG path of the transit ring. 394 """ 395 radius_offset = 18 396 397 out = f'<circle cx="{r}" cy="{r}" r="{r - radius_offset}" style="fill: none; stroke: {paper_1_color}; stroke-width: 36px; stroke-opacity: .4;"/>' 398 out += f'<circle cx="{r}" cy="{r}" r="{r}" style="fill: none; stroke: {zodiac_transit_ring_3_color}; stroke-width: 1px; stroke-opacity: .6;"/>' 399 400 return out 401 402 403def draw_first_circle( 404 r: Union[int, float], stroke_color: str, chart_type: ChartType, c1: Union[int, float, None] = None 405) -> str: 406 """ 407 Draws the first circle. 408 409 Args: 410 - r (Union[int, float]): The value of r. 411 - color (str): The color of the circle. 412 - chart_type (ChartType): The type of chart. 413 - c1 (Union[int, float]): The value of c1. 414 415 Returns: 416 str: The SVG path of the first circle. 417 """ 418 if chart_type == "Synastry" or chart_type == "Transit": 419 return f'<circle cx="{r}" cy="{r}" r="{r - 36}" style="fill: none; stroke: {stroke_color}; stroke-width: 1px; stroke-opacity:.4;" />' 420 else: 421 if c1 is None: 422 raise KerykeionException("c1 is None") 423 424 return ( 425 f'<circle cx="{r}" cy="{r}" r="{r - c1}" style="fill: none; stroke: {stroke_color}; stroke-width: 1px; " />' 426 ) 427 428 429def draw_second_circle( 430 r: Union[int, float], stroke_color: str, fill_color: str, chart_type: ChartType, c2: Union[int, float, None] = None 431) -> str: 432 """ 433 Draws the second circle. 434 435 Args: 436 - r (Union[int, float]): The value of r. 437 - stroke_color (str): The color of the stroke. 438 - fill_color (str): The color of the fill. 439 - chart_type (ChartType): The type of chart. 440 - c2 (Union[int, float]): The value of c2. 441 442 Returns: 443 str: The SVG path of the second circle. 444 """ 445 446 if chart_type == "Synastry" or chart_type == "Transit": 447 return f'<circle cx="{r}" cy="{r}" r="{r - 72}" style="fill: {fill_color}; fill-opacity:.4; stroke: {stroke_color}; stroke-opacity:.4; stroke-width: 1px" />' 448 449 else: 450 if c2 is None: 451 raise KerykeionException("c2 is None") 452 453 return f'<circle cx="{r}" cy="{r}" r="{r - c2}" style="fill: {fill_color}; fill-opacity:.2; stroke: {stroke_color}; stroke-opacity:.4; stroke-width: 1px" />' 454 455 456def draw_third_circle( 457 radius: Union[int, float], 458 stroke_color: str, 459 fill_color: str, 460 chart_type: ChartType, 461 c3: Union[int, float] 462) -> str: 463 """ 464 Draws the third circle in an SVG chart. 465 466 Parameters: 467 - radius (Union[int, float]): The radius of the circle. 468 - stroke_color (str): The stroke color of the circle. 469 - fill_color (str): The fill color of the circle. 470 - chart_type (ChartType): The type of the chart. 471 - c3 (Union[int, float, None], optional): The radius adjustment for non-Synastry and non-Transit charts. 472 473 Returns: 474 - str: The SVG element as a string. 475 """ 476 if chart_type in {"Synastry", "Transit"}: 477 # For Synastry and Transit charts, use a fixed radius adjustment of 160 478 return f'<circle cx="{radius}" cy="{radius}" r="{radius - 160}" style="fill: {fill_color}; fill-opacity:.8; stroke: {stroke_color}; stroke-width: 1px" />' 479 480 else: 481 return f'<circle cx="{radius}" cy="{radius}" r="{radius - c3}" style="fill: {fill_color}; fill-opacity:.8; stroke: {stroke_color}; stroke-width: 1px" />' 482 483 484def draw_aspect_grid( 485 stroke_color: str, 486 available_planets: list, 487 aspects: list, 488 x_start: int = 380, 489 y_start: int = 468, 490 ) -> str: 491 """ 492 Draws the aspect grid for the given planets and aspects. 493 494 Args: 495 stroke_color (str): The color of the stroke. 496 available_planets (list): List of all planets. Only planets with "is_active" set to True will be used. 497 aspects (list): List of aspects. 498 x_start (int): The x-coordinate starting point. 499 y_start (int): The y-coordinate starting point. 500 501 Returns: 502 str: SVG string representing the aspect grid. 503 """ 504 svg_output = "" 505 style = f"stroke:{stroke_color}; stroke-width: 1px; stroke-width: 0.5px; fill:none" 506 box_size = 14 507 508 # Filter active planets 509 active_planets = [planet for planet in available_planets if planet.is_active] 510 511 # Reverse the list of active planets for the first iteration 512 reversed_planets = active_planets[::-1] 513 514 for index, planet_a in enumerate(reversed_planets): 515 # Draw the grid box for the planet 516 svg_output += f'<rect kr:node="AspectsGridRect" x="{x_start}" y="{y_start}" width="{box_size}" height="{box_size}" style="{style}"/>' 517 svg_output += f'<use transform="scale(0.4)" x="{(x_start + 2) * 2.5}" y="{(y_start + 1) * 2.5}" xlink:href="#{planet_a["name"]}" />' 518 519 # Update the starting coordinates for the next box 520 x_start += box_size 521 y_start -= box_size 522 523 # Coordinates for the aspect symbols 524 x_aspect = x_start 525 y_aspect = y_start + box_size 526 527 # Iterate over the remaining planets 528 for planet_b in reversed_planets[index + 1:]: 529 # Draw the grid box for the aspect 530 svg_output += f'<rect kr:node="AspectsGridRect" x="{x_aspect}" y="{y_aspect}" width="{box_size}" height="{box_size}" style="{style}"/>' 531 x_aspect += box_size 532 533 # Check for aspects between the planets 534 for aspect in aspects: 535 if (aspect["p1"] == planet_a["id"] and aspect["p2"] == planet_b["id"]) or ( 536 aspect["p1"] == planet_b["id"] and aspect["p2"] == planet_a["id"] 537 ): 538 svg_output += f'<use x="{x_aspect - box_size + 1}" y="{y_aspect + 1}" xlink:href="#orb{aspect["aspect_degrees"]}" />' 539 540 return svg_output 541 542 543def draw_houses_cusps_and_text_number( 544 r: Union[int, float], 545 first_subject_houses_list: list[KerykeionPointModel], 546 standard_house_cusp_color: str, 547 first_house_color: str, 548 tenth_house_color: str, 549 seventh_house_color: str, 550 fourth_house_color: str, 551 c1: Union[int, float], 552 c3: Union[int, float], 553 chart_type: ChartType, 554 second_subject_houses_list: Union[list[KerykeionPointModel], None] = None, 555 transit_house_cusp_color: Union[str, None] = None, 556) -> str: 557 """ 558 Draws the houses cusps and text numbers for a given chart type. 559 560 Parameters: 561 - r: Radius of the chart. 562 - first_subject_houses_list: List of house for the first subject. 563 - standard_house_cusp_color: Default color for house cusps. 564 - first_house_color: Color for the first house cusp. 565 - tenth_house_color: Color for the tenth house cusp. 566 - seventh_house_color: Color for the seventh house cusp. 567 - fourth_house_color: Color for the fourth house cusp. 568 - c1: Offset for the first subject. 569 - c3: Offset for the third subject. 570 - chart_type: Type of the chart (e.g., Transit, Synastry). 571 - second_subject_houses_list: List of house for the second subject (optional). 572 - transit_house_cusp_color: Color for transit house cusps (optional). 573 574 Returns: 575 - A string containing the SVG path for the houses cusps and text numbers. 576 """ 577 578 path = "" 579 xr = 12 580 581 for i in range(xr): 582 # Determine offsets based on chart type 583 dropin, roff, t_roff = (160, 72, 36) if chart_type in ["Transit", "Synastry"] else (c3, c1, False) 584 585 # Calculate the offset for the current house cusp 586 offset = (int(first_subject_houses_list[int(xr / 2)].abs_pos) / -1) + int(first_subject_houses_list[i].abs_pos) 587 588 # Calculate the coordinates for the house cusp lines 589 x1 = sliceToX(0, (r - dropin), offset) + dropin 590 y1 = sliceToY(0, (r - dropin), offset) + dropin 591 x2 = sliceToX(0, r - roff, offset) + roff 592 y2 = sliceToY(0, r - roff, offset) + roff 593 594 # Calculate the text offset for the house number 595 next_index = (i + 1) % xr 596 text_offset = offset + int( 597 degreeDiff(first_subject_houses_list[next_index].abs_pos, first_subject_houses_list[i].abs_pos) / 2 598 ) 599 600 # Determine the line color based on the house index 601 linecolor = {0: first_house_color, 9: tenth_house_color, 6: seventh_house_color, 3: fourth_house_color}.get( 602 i, standard_house_cusp_color 603 ) 604 605 if chart_type in ["Transit", "Synastry"]: 606 if second_subject_houses_list is None or transit_house_cusp_color is None: 607 raise KerykeionException("second_subject_houses_list_ut or transit_house_cusp_color is None") 608 609 # Calculate the offset for the second subject's house cusp 610 zeropoint = 360 - first_subject_houses_list[6].abs_pos 611 t_offset = (zeropoint + second_subject_houses_list[i].abs_pos) % 360 612 613 # Calculate the coordinates for the second subject's house cusp lines 614 t_x1 = sliceToX(0, (r - t_roff), t_offset) + t_roff 615 t_y1 = sliceToY(0, (r - t_roff), t_offset) + t_roff 616 t_x2 = sliceToX(0, r, t_offset) 617 t_y2 = sliceToY(0, r, t_offset) 618 619 # Calculate the text offset for the second subject's house number 620 t_text_offset = t_offset + int( 621 degreeDiff(second_subject_houses_list[next_index].abs_pos, second_subject_houses_list[i].abs_pos) / 2 622 ) 623 t_linecolor = linecolor if i in [0, 9, 6, 3] else transit_house_cusp_color 624 xtext = sliceToX(0, (r - 8), t_text_offset) + 8 625 ytext = sliceToY(0, (r - 8), t_text_offset) + 8 626 627 # Add the house number text for the second subject 628 fill_opacity = "0" if chart_type == "Transit" else ".4" 629 path += f'<g kr:node="HouseNumber">' 630 path += f'<text style="fill: var(--kerykeion-chart-color-house-number); fill-opacity: {fill_opacity}; font-size: 14px"><tspan x="{xtext - 3}" y="{ytext + 3}">{i + 1}</tspan></text>' 631 path += f"</g>" 632 633 # Add the house cusp line for the second subject 634 stroke_opacity = "0" if chart_type == "Transit" else ".3" 635 path += f'<g kr:node="Cusp">' 636 path += f"<line x1='{t_x1}' y1='{t_y1}' x2='{t_x2}' y2='{t_y2}' style='stroke: {t_linecolor}; stroke-width: 1px; stroke-opacity:{stroke_opacity};'/>" 637 path += f"</g>" 638 639 # Adjust dropin based on chart type 640 dropin = {"Transit": 84, "Synastry": 84, "ExternalNatal": 100}.get(chart_type, 48) 641 xtext = sliceToX(0, (r - dropin), text_offset) + dropin 642 ytext = sliceToY(0, (r - dropin), text_offset) + dropin 643 644 # Add the house cusp line for the first subject 645 path += f'<g kr:node="Cusp">' 646 path += f'<line x1="{x1}" y1="{y1}" x2="{x2}" y2="{y2}" style="stroke: {linecolor}; stroke-width: 1px; stroke-dasharray:3,2; stroke-opacity:.4;"/>' 647 path += f"</g>" 648 649 # Add the house number text for the first subject 650 path += f'<g kr:node="HouseNumber">' 651 path += f'<text style="fill: var(--kerykeion-chart-color-house-number); fill-opacity: .6; font-size: 14px"><tspan x="{xtext - 3}" y="{ytext + 3}">{i + 1}</tspan></text>' 652 path += f"</g>" 653 654 return path 655 656 657def draw_transit_aspect_list( 658 grid_title: str, 659 aspects_list: Union[list[AspectModel], list[dict]], 660 celestial_point_language: Union[KerykeionLanguageCelestialPointModel, dict], 661 aspects_settings: Union[KerykeionSettingsAspectModel, dict], 662) -> str: 663 """ 664 Generates the SVG output for the aspect transit grid. 665 666 Parameters: 667 - grid_title: Title of the grid. 668 - aspects_list: List of aspects. 669 - planets_labels: Dictionary containing the planet labels. 670 - aspects_settings: Dictionary containing the aspect settings. 671 672 Returns: 673 - A string containing the SVG path data for the aspect transit grid. 674 """ 675 676 if isinstance(celestial_point_language, dict): 677 celestial_point_language = KerykeionLanguageCelestialPointModel(**celestial_point_language) 678 679 if isinstance(aspects_settings, dict): 680 aspects_settings = KerykeionSettingsAspectModel(**aspects_settings) 681 682 # If not instance of AspectModel, convert to AspectModel 683 if isinstance(aspects_list[0], dict): 684 aspects_list = [AspectModel(**aspect) for aspect in aspects_list] # type: ignore 685 686 line = 0 687 nl = 0 688 inner_path = "" 689 for i, aspect in enumerate(aspects_list): 690 # Adjust the vertical position for every 12 aspects 691 if i == 14: 692 nl = 100 693 line = 0 694 695 elif i == 28: 696 nl = 200 697 line = 0 698 699 elif i == 42: 700 nl = 300 701 line = 0 702 703 elif i == 56: 704 nl = 400 705 line = 0 706 707 elif i == 70: 708 nl = 500 709 # When there are more than 60 aspects, the text is moved up 710 if len(aspects_list) > 84: 711 line = -1 * (len(aspects_list) - 84) * 14 712 else: 713 line = 0 714 715 inner_path += f'<g transform="translate({nl},{line})">' 716 717 # first planet symbol 718 inner_path += f'<use transform="scale(0.4)" x="0" y="3" xlink:href="#{celestial_point_language[aspects_list[i]["p1"]]["name"]}" />' 719 720 # aspect symbol 721 # TODO: Remove the "degree" element EVERYWHERE! 722 aspect_name = aspects_list[i]["aspect"] 723 id_value = next((a["degree"] for a in aspects_settings if a["name"] == aspect_name), None) # type: ignore 724 inner_path += f'<use x="15" y="0" xlink:href="#orb{id_value}" />' 725 726 # second planet symbol 727 inner_path += f'<g transform="translate(30,0)">' 728 inner_path += f'<use transform="scale(0.4)" x="0" y="3" xlink:href="#{celestial_point_language[aspects_list[i]["p2"]]["name"]}" />' 729 inner_path += f"</g>" 730 731 # difference in degrees 732 inner_path += f'<text y="8" x="45" style="fill: var(--kerykeion-chart-color-paper-0); font-size: 10px;">{convert_decimal_to_degree_string(aspects_list[i]["orbit"])}</text>' 733 # line 734 inner_path += f"</g>" 735 line = line + 14 736 737 out = '<g transform="translate(526,273)">' 738 out += f'<text y="-15" x="0" style="fill: var(--kerykeion-chart-color-paper-0); font-size: 14px;">{grid_title}:</text>' 739 out += inner_path 740 out += '</g>' 741 742 return out 743 744 745def calculate_moon_phase_chart_params( 746 degrees_between_sun_and_moon: float, 747 latitude: float 748) -> dict: 749 """ 750 Calculate the parameters for the moon phase chart. 751 752 Parameters: 753 - degrees_between_sun_and_moon (float): The degrees between the sun and the moon. 754 - latitude (float): The latitude for rotation calculation. 755 756 Returns: 757 - dict: The moon phase chart parameters. 758 """ 759 deg = degrees_between_sun_and_moon 760 761 # Initialize variables for lunar phase properties 762 circle_center_x = None 763 circle_radius = None 764 765 # Determine lunar phase properties based on the degree 766 if deg < 90.0: 767 max_radius = deg 768 if deg > 80.0: 769 max_radius = max_radius * max_radius 770 circle_center_x = 20.0 + (deg / 90.0) * (max_radius + 10.0) 771 circle_radius = 10.0 + (deg / 90.0) * max_radius 772 773 elif deg < 180.0: 774 max_radius = 180.0 - deg 775 if deg < 100.0: 776 max_radius = max_radius * max_radius 777 circle_center_x = 20.0 + ((deg - 90.0) / 90.0 * (max_radius + 10.0)) - (max_radius + 10.0) 778 circle_radius = 10.0 + max_radius - ((deg - 90.0) / 90.0 * max_radius) 779 780 elif deg < 270.0: 781 max_radius = deg - 180.0 782 if deg > 260.0: 783 max_radius = max_radius * max_radius 784 circle_center_x = 20.0 + ((deg - 180.0) / 90.0 * (max_radius + 10.0)) 785 circle_radius = 10.0 + ((deg - 180.0) / 90.0 * max_radius) 786 787 elif deg < 361.0: 788 max_radius = 360.0 - deg 789 if deg < 280.0: 790 max_radius = max_radius * max_radius 791 circle_center_x = 20.0 + ((deg - 270.0) / 90.0 * (max_radius + 10.0)) - (max_radius + 10.0) 792 circle_radius = 10.0 + max_radius - ((deg - 270.0) / 90.0 * max_radius) 793 794 else: 795 raise KerykeionException(f"Invalid degree value: {deg}") 796 797 798 # Calculate rotation based on latitude 799 lunar_phase_rotate = -90.0 - latitude 800 801 return { 802 "circle_center_x": circle_center_x, 803 "circle_radius": circle_radius, 804 "lunar_phase_rotate": lunar_phase_rotate, 805 } 806 807def draw_house_grid( 808 main_subject_houses_list: list[KerykeionPointModel], 809 chart_type: ChartType, 810 secondary_subject_houses_list: Union[list[KerykeionPointModel], None] = None, 811 text_color: str = "#000000", 812 house_cusp_generale_name_label: str = "Cusp", 813 ) -> str: 814 """ 815 Generate SVG code for a grid of astrological houses. 816 817 Parameters: 818 - main_houses (list[KerykeionPointModel]): List of houses for the main subject. 819 - chart_type (ChartType): Type of the chart (e.g., Synastry, Transit). 820 - secondary_houses (list[KerykeionPointModel], optional): List of houses for the secondary subject. 821 - text_color (str): Color of the text. 822 - cusp_label (str): Label for the house cusp. 823 824 Returns: 825 - str: The SVG code for the grid of houses. 826 """ 827 828 if chart_type in ["Synastry", "Transit"] and secondary_subject_houses_list is None: 829 raise KerykeionException("secondary_houses is None") 830 831 svg_output = '<g transform="translate(650,-20)">' 832 833 line_increment = 10 834 for i, house in enumerate(main_subject_houses_list): 835 cusp_number = f"  {i + 1}" if i < 9 else str(i + 1) 836 svg_output += ( 837 f'<g transform="translate(0,{line_increment})">' 838 f'<text text-anchor="end" x="40" style="fill:{text_color}; font-size: 10px;">{house_cusp_generale_name_label} {cusp_number}:</text>' 839 f'<g transform="translate(40,-8)"><use transform="scale(0.3)" xlink:href="#{house["sign"]}" /></g>' 840 f'<text x="53" style="fill:{text_color}; font-size: 10px;"> {convert_decimal_to_degree_string(house["position"])}</text>' 841 f'</g>' 842 ) 843 line_increment += 14 844 845 svg_output += "</g>" 846 847 if chart_type == "Synastry": 848 svg_output += '<!-- Synastry Houses -->' 849 svg_output += '<g transform="translate(910, -20)">' 850 line_increment = 10 851 852 for i, house in enumerate(secondary_subject_houses_list): # type: ignore 853 cusp_number = f"  {i + 1}" if i < 9 else str(i + 1) 854 svg_output += ( 855 f'<g transform="translate(0,{line_increment})">' 856 f'<text text-anchor="end" x="40" style="fill:{text_color}; font-size: 10px;">{house_cusp_generale_name_label} {cusp_number}:</text>' 857 f'<g transform="translate(40,-8)"><use transform="scale(0.3)" xlink:href="#{house["sign"]}" /></g>' 858 f'<text x="53" style="fill:{text_color}; font-size: 10px;"> {convert_decimal_to_degree_string(house["position"])}</text>' 859 f'</g>' 860 ) 861 line_increment += 14 862 863 svg_output += "</g>" 864 865 return svg_output 866 867 868def draw_planet_grid( 869 planets_and_houses_grid_title: str, 870 subject_name: str, 871 available_kerykeion_celestial_points: list[KerykeionPointModel], 872 chart_type: ChartType, 873 celestial_point_language: KerykeionLanguageCelestialPointModel, 874 second_subject_name: Union[str, None] = None, 875 second_subject_available_kerykeion_celestial_points: Union[list[KerykeionPointModel], None] = None, 876 text_color: str = "#000000", 877 ) -> str: 878 """ 879 Draws the planet grid for the given celestial points and chart type. 880 881 Args: 882 planets_and_houses_grid_title (str): Title of the grid. 883 subject_name (str): Name of the subject. 884 available_kerykeion_celestial_points (list[KerykeionPointModel]): List of celestial points for the subject. 885 chart_type (ChartType): Type of the chart. 886 celestial_point_language (KerykeionLanguageCelestialPointModel): Language model for celestial points. 887 second_subject_name (str, optional): Name of the second subject. Defaults to None. 888 second_subject_available_kerykeion_celestial_points (list[KerykeionPointModel], optional): List of celestial points for the second subject. Defaults to None. 889 text_color (str, optional): Color of the text. Defaults to "#000000". 890 891 Returns: 892 str: The SVG output for the planet grid. 893 """ 894 line_height = 10 895 offset = 0 896 offset_between_lines = 14 897 898 svg_output = ( 899 f'<g transform="translate(175, -15)">' 900 f'<text text-anchor="end" style="fill:{text_color}; font-size: 14px;">{planets_and_houses_grid_title} {subject_name}:</text>' 901 f'</g>' 902 ) 903 904 end_of_line = "</g>" 905 906 for i, planet in enumerate(available_kerykeion_celestial_points): 907 if i == 27: 908 line_height = 10 909 offset = -120 910 911 decoded_name = get_decoded_kerykeion_celestial_point_name(planet["name"], celestial_point_language) 912 svg_output += ( 913 f'<g transform="translate({offset},{line_height})">' 914 f'<text text-anchor="end" style="fill:{text_color}; font-size: 10px;">{decoded_name}</text>' 915 f'<g transform="translate(5,-8)"><use transform="scale(0.4)" xlink:href="#{planet["name"]}" /></g>' 916 f'<text text-anchor="start" x="19" style="fill:{text_color}; font-size: 10px;">{convert_decimal_to_degree_string(planet["position"])}</text>' 917 f'<g transform="translate(60,-8)"><use transform="scale(0.3)" xlink:href="#{planet["sign"]}" /></g>' 918 ) 919 920 if planet["retrograde"]: 921 svg_output += '<g transform="translate(74,-6)"><use transform="scale(.5)" xlink:href="#retrograde" /></g>' 922 923 svg_output += end_of_line 924 line_height += offset_between_lines 925 926 if chart_type in ["Transit", "Synastry"]: 927 if second_subject_available_kerykeion_celestial_points is None: 928 raise KerykeionException("second_subject_available_kerykeion_celestial_points is None") 929 930 if chart_type == "Transit": 931 svg_output += ( 932 f'<g transform="translate(320, -15)">' 933 f'<text text-anchor="end" style="fill:{text_color}; font-size: 14px;">{second_subject_name}:</text>' 934 ) 935 else: 936 svg_output += ( 937 f'<g transform="translate(380, -15)">' 938 f'<text text-anchor="end" style="fill:{text_color}; font-size: 14px;">{planets_and_houses_grid_title} {second_subject_name}:</text>' 939 ) 940 941 svg_output += end_of_line 942 943 second_line_height = 10 944 second_offset = 250 945 946 for i, t_planet in enumerate(second_subject_available_kerykeion_celestial_points): 947 if i == 27: 948 second_line_height = 10 949 second_offset = -120 950 951 second_decoded_name = get_decoded_kerykeion_celestial_point_name(t_planet["name"], celestial_point_language) 952 svg_output += ( 953 f'<g transform="translate({second_offset},{second_line_height})">' 954 f'<text text-anchor="end" style="fill:{text_color}; font-size: 10px;">{second_decoded_name}</text>' 955 f'<g transform="translate(5,-8)"><use transform="scale(0.4)" xlink:href="#{t_planet["name"]}" /></g>' 956 f'<text text-anchor="start" x="19" style="fill:{text_color}; font-size: 10px;">{convert_decimal_to_degree_string(t_planet["position"])}</text>' 957 f'<g transform="translate(60,-8)"><use transform="scale(0.3)" xlink:href="#{t_planet["sign"]}" /></g>' 958 ) 959 960 if t_planet["retrograde"]: 961 svg_output += '<g transform="translate(74,-6)"><use transform="scale(.5)" xlink:href="#retrograde" /></g>' 962 963 svg_output += end_of_line 964 second_line_height += offset_between_lines 965 966 return svg_output 967 968 969def draw_transit_aspect_grid( 970 stroke_color: str, 971 available_planets: list, 972 aspects: list, 973 x_indent: int = 50, 974 y_indent: int = 250, 975 box_size: int = 14 976 ) -> str: 977 """ 978 Draws the aspect grid for the given planets and aspects. The default args value are specific for a stand alone 979 aspect grid. 980 981 Args: 982 stroke_color (str): The color of the stroke. 983 available_planets (list): List of all planets. Only planets with "is_active" set to True will be used. 984 aspects (list): List of aspects. 985 x_indent (int): The initial x-coordinate starting point. 986 y_indent (int): The initial y-coordinate starting point. 987 988 Returns: 989 str: SVG string representing the aspect grid. 990 """ 991 svg_output = "" 992 style = f"stroke:{stroke_color}; stroke-width: 1px; stroke-width: 0.5px; fill:none" 993 x_start = x_indent 994 y_start = y_indent 995 996 # Filter active planets 997 active_planets = [planet for planet in available_planets if planet.is_active] 998 999 # Reverse the list of active planets for the first iteration 1000 reversed_planets = active_planets[::-1] 1001 for index, planet_a in enumerate(reversed_planets): 1002 # Draw the grid box for the planet 1003 svg_output += f'<rect x="{x_start}" y="{y_start}" width="{box_size}" height="{box_size}" style="{style}"/>' 1004 svg_output += f'<use transform="scale(0.4)" x="{(x_start + 2) * 2.5}" y="{(y_start + 1) * 2.5}" xlink:href="#{planet_a["name"]}" />' 1005 x_start += box_size 1006 1007 x_start = x_indent - box_size 1008 y_start = y_indent - box_size 1009 1010 for index, planet_a in enumerate(reversed_planets): 1011 # Draw the grid box for the planet 1012 svg_output += f'<rect x="{x_start}" y="{y_start}" width="{box_size}" height="{box_size}" style="{style}"/>' 1013 svg_output += f'<use transform="scale(0.4)" x="{(x_start + 2) * 2.5}" y="{(y_start + 1) * 2.5}" xlink:href="#{planet_a["name"]}" />' 1014 y_start -= box_size 1015 1016 x_start = x_indent 1017 y_start = y_indent 1018 y_start = y_start - box_size 1019 1020 for index, planet_a in enumerate(reversed_planets): 1021 # Draw the grid box for the planet 1022 svg_output += f'<rect x="{x_start}" y="{y_start}" width="{box_size}" height="{box_size}" style="{style}"/>' 1023 1024 # Update the starting coordinates for the next box 1025 y_start -= box_size 1026 1027 # Coordinates for the aspect symbols 1028 x_aspect = x_start 1029 y_aspect = y_start + box_size 1030 1031 # Iterate over the remaining planets 1032 for planet_b in reversed_planets: 1033 # Draw the grid box for the aspect 1034 svg_output += f'<rect x="{x_aspect}" y="{y_aspect}" width="{box_size}" height="{box_size}" style="{style}"/>' 1035 x_aspect += box_size 1036 1037 # Check for aspects between the planets 1038 for aspect in aspects: 1039 if (aspect["p1"] == planet_a["id"] and aspect["p2"] == planet_b["id"]): 1040 svg_output += f'<use x="{x_aspect - box_size + 1}" y="{y_aspect + 1}" xlink:href="#orb{aspect["aspect_degrees"]}" />' 1041 1042 return svg_output
10def get_decoded_kerykeion_celestial_point_name(input_planet_name: str, celestial_point_language: KerykeionLanguageCelestialPointModel) -> str: 11 """ 12 Decode the given celestial point name based on the provided language model. 13 14 Args: 15 input_planet_name (str): The name of the celestial point to decode. 16 celestial_point_language (KerykeionLanguageCelestialPointModel): The language model containing celestial point names. 17 18 Returns: 19 str: The decoded celestial point name. 20 """ 21 22 23 # Get the language model keys 24 language_keys = celestial_point_language.model_dump().keys() 25 26 # Check if the input planet name exists in the language model 27 if input_planet_name in language_keys: 28 return celestial_point_language[input_planet_name] 29 else: 30 raise KerykeionException(f"Celestial point {input_planet_name} not found in language model.")
Decode the given celestial point name based on the provided language model.
Args: input_planet_name (str): The name of the celestial point to decode. celestial_point_language (KerykeionLanguageCelestialPointModel): The language model containing celestial point names.
Returns: str: The decoded celestial point name.
33def decHourJoin(inH: int, inM: int, inS: int) -> float: 34 """Join hour, minutes, seconds, timezone integer to hour float. 35 36 Args: 37 - inH (int): hour 38 - inM (int): minutes 39 - inS (int): seconds 40 Returns: 41 float: hour in float format 42 """ 43 44 dh = float(inH) 45 dm = float(inM) / 60 46 ds = float(inS) / 3600 47 output = dh + dm + ds 48 return output
Join hour, minutes, seconds, timezone integer to hour float.
Args: - inH (int): hour - inM (int): minutes - inS (int): seconds Returns: float: hour in float format
51def degreeDiff(a: Union[int, float], b: Union[int, float]) -> float: 52 """Calculate the smallest difference between two angles in degrees. 53 54 Args: 55 a (int | float): first angle in degrees 56 b (int | float): second angle in degrees 57 58 Returns: 59 float: smallest difference between a and b (0 to 180 degrees) 60 """ 61 diff = math.fmod(abs(a - b), 360) # Assicura che il valore sia in [0, 360) 62 return min(diff, 360 - diff) # Prende l'angolo piĆ¹ piccolo tra i due possibili
Calculate the smallest difference between two angles in degrees.
Args: a (int | float): first angle in degrees b (int | float): second angle in degrees
Returns: float: smallest difference between a and b (0 to 180 degrees)
65def degreeSum(a: Union[int, float], b: Union[int, float]) -> float: 66 """Calculate the sum of two angles in degrees, normalized to [0, 360). 67 68 Args: 69 a (int | float): first angle in degrees 70 b (int | float): second angle in degrees 71 72 Returns: 73 float: normalized sum of a and b in the range [0, 360) 74 """ 75 return math.fmod(a + b, 360) if (a + b) % 360 != 0 else 0.0
Calculate the sum of two angles in degrees, normalized to [0, 360).
Args: a (int | float): first angle in degrees b (int | float): second angle in degrees
Returns: float: normalized sum of a and b in the range [0, 360)
78def normalizeDegree(angle: Union[int, float]) -> float: 79 """Normalize an angle to the range [0, 360). 80 81 Args: 82 angle (int | float): The input angle in degrees. 83 84 Returns: 85 float: The normalized angle in the range [0, 360). 86 """ 87 return angle % 360 if angle % 360 != 0 else 0.0
Normalize an angle to the range [0, 360).
Args: angle (int | float): The input angle in degrees.
Returns: float: The normalized angle in the range [0, 360).
90def offsetToTz(datetime_offset: Union[datetime.timedelta, None]) -> float: 91 """Convert datetime offset to float in hours. 92 93 Args: 94 - datetime_offset (datetime.timedelta): datetime offset 95 96 Returns: 97 - float: offset in hours 98 """ 99 100 if datetime_offset is None: 101 raise KerykeionException("datetime_offset is None") 102 103 # days to hours 104 dh = float(datetime_offset.days * 24) 105 # seconds to hours 106 sh = float(datetime_offset.seconds / 3600.0) 107 # total hours 108 output = dh + sh 109 return output
Convert datetime offset to float in hours.
Args: - datetime_offset (datetime.timedelta): datetime offset
Returns: - float: offset in hours
112def sliceToX(slice: Union[int, float], radius: Union[int, float], offset: Union[int, float]) -> float: 113 """Calculates the x-coordinate of a point on a circle based on the slice, radius, and offset. 114 115 Args: 116 - slice (int | float): Represents the 117 slice of the circle to calculate the x-coordinate for. 118 It must be between 0 and 11 (inclusive). 119 - radius (int | float): Represents the radius of the circle. 120 - offset (int | float): Represents the offset in degrees. 121 It must be between 0 and 360 (inclusive). 122 123 Returns: 124 float: The x-coordinate of the point on the circle. 125 126 Example: 127 >>> import math 128 >>> sliceToX(3, 5, 45) 129 2.5000000000000018 130 """ 131 132 plus = (math.pi * offset) / 180 133 radial = ((math.pi / 6) * slice) + plus 134 return radius * (math.cos(radial) + 1)
Calculates the x-coordinate of a point on a circle based on the slice, radius, and offset.
Args: - slice (int | float): Represents the slice of the circle to calculate the x-coordinate for. It must be between 0 and 11 (inclusive). - radius (int | float): Represents the radius of the circle. - offset (int | float): Represents the offset in degrees. It must be between 0 and 360 (inclusive).
Returns: float: The x-coordinate of the point on the circle.
Example:
import math sliceToX(3, 5, 45) 2.5000000000000018
137def sliceToY(slice: Union[int, float], r: Union[int, float], offset: Union[int, float]) -> float: 138 """Calculates the y-coordinate of a point on a circle based on the slice, radius, and offset. 139 140 Args: 141 - slice (int | float): Represents the slice of the circle to calculate 142 the y-coordinate for. It must be between 0 and 11 (inclusive). 143 - r (int | float): Represents the radius of the circle. 144 - offset (int | float): Represents the offset in degrees. 145 It must be between 0 and 360 (inclusive). 146 147 Returns: 148 float: The y-coordinate of the point on the circle. 149 150 Example: 151 >>> import math 152 >>> __sliceToY(3, 5, 45) 153 -4.330127018922194 154 """ 155 plus = (math.pi * offset) / 180 156 radial = ((math.pi / 6) * slice) + plus 157 return r * ((math.sin(radial) / -1) + 1)
Calculates the y-coordinate of a point on a circle based on the slice, radius, and offset.
Args: - slice (int | float): Represents the slice of the circle to calculate the y-coordinate for. It must be between 0 and 11 (inclusive). - r (int | float): Represents the radius of the circle. - offset (int | float): Represents the offset in degrees. It must be between 0 and 360 (inclusive).
Returns: float: The y-coordinate of the point on the circle.
Example:
import math __sliceToY(3, 5, 45) -4.330127018922194
160def draw_zodiac_slice( 161 c1: Union[int, float], 162 chart_type: ChartType, 163 seventh_house_degree_ut: Union[int, float], 164 num: int, 165 r: Union[int, float], 166 style: str, 167 type: str, 168) -> str: 169 """Draws a zodiac slice based on the given parameters. 170 171 Args: 172 - c1 (Union[int, float]): The value of c1. 173 - chart_type (ChartType): The type of chart. 174 - seventh_house_degree_ut (Union[int, float]): The degree of the seventh house. 175 - num (int): The number of the sign. Note: In OpenAstro it did refer to self.zodiac, 176 which is a list of the signs in order, starting with Aries. Eg: 177 {"name": "Ari", "element": "fire"} 178 - r (Union[int, float]): The value of r. 179 - style (str): The CSS inline style. 180 - type (str): The type ?. In OpenAstro, it was the symbol of the sign. Eg: "Ari". 181 self.zodiac[i]["name"] 182 183 Returns: 184 - str: The zodiac slice and symbol as an SVG path. 185 """ 186 187 # pie slices 188 offset = 360 - seventh_house_degree_ut 189 # check transit 190 if chart_type == "Transit" or chart_type == "Synastry": 191 dropin: Union[int, float] = 0 192 else: 193 dropin = c1 194 slice = f'<path d="M{str(r)},{str(r)} L{str(dropin + sliceToX(num, r - dropin, offset))},{str(dropin + sliceToY(num, r - dropin, offset))} A{str(r - dropin)},{str(r - dropin)} 0 0,0 {str(dropin + sliceToX(num + 1, r - dropin, offset))},{str(dropin + sliceToY(num + 1, r - dropin, offset))} z" style="{style}"/>' 195 196 # symbols 197 offset = offset + 15 198 # check transit 199 if chart_type == "Transit" or chart_type == "Synastry": 200 dropin = 54 201 else: 202 dropin = 18 + c1 203 sign = f'<g transform="translate(-16,-16)"><use x="{str(dropin + sliceToX(num, r - dropin, offset))}" y="{str(dropin + sliceToY(num, r - dropin, offset))}" xlink:href="#{type}" /></g>' 204 205 return slice + "" + sign
Draws a zodiac slice based on the given parameters.
Args: - c1 (Union[int, float]): The value of c1. - chart_type (ChartType): The type of chart. - seventh_house_degree_ut (Union[int, float]): The degree of the seventh house. - num (int): The number of the sign. Note: In OpenAstro it did refer to self.zodiac, which is a list of the signs in order, starting with Aries. Eg: {"name": "Ari", "element": "fire"} - r (Union[int, float]): The value of r. - style (str): The CSS inline style. - type (str): The type ?. In OpenAstro, it was the symbol of the sign. Eg: "Ari". self.zodiac[i]["name"]
Returns: - str: The zodiac slice and symbol as an SVG path.
208def convert_latitude_coordinate_to_string(coord: Union[int, float], north_label: str, south_label: str) -> str: 209 """Converts a floating point latitude to string with 210 degree, minutes and seconds and the appropriate sign 211 (north or south). Eg. 52.1234567 -> 52Ā°7'25" N 212 213 Args: 214 - coord (float | int): latitude in floating or integer format 215 - north_label (str): String label for north 216 - south_label (str): String label for south 217 Returns: 218 - str: latitude in string format with degree, minutes, 219 seconds and sign (N/S) 220 """ 221 222 sign = north_label 223 if coord < 0.0: 224 sign = south_label 225 coord = abs(coord) 226 deg = int(coord) 227 min = int((float(coord) - deg) * 60) 228 sec = int(round(float(((float(coord) - deg) * 60) - min) * 60.0)) 229 return f"{deg}Ā°{min}'{sec}\" {sign}"
Converts a floating point latitude to string with degree, minutes and seconds and the appropriate sign (north or south). Eg. 52.1234567 -> 52Ā°7'25" N
Args: - coord (float | int): latitude in floating or integer format - north_label (str): String label for north - south_label (str): String label for south Returns: - str: latitude in string format with degree, minutes, seconds and sign (N/S)
232def convert_longitude_coordinate_to_string(coord: Union[int, float], east_label: str, west_label: str) -> str: 233 """Converts a floating point longitude to string with 234 degree, minutes and seconds and the appropriate sign 235 (east or west). Eg. 52.1234567 -> 52Ā°7'25" E 236 237 Args: 238 - coord (float|int): longitude in floating point format 239 - east_label (str): String label for east 240 - west_label (str): String label for west 241 Returns: 242 str: longitude in string format with degree, minutes, 243 seconds and sign (E/W) 244 """ 245 246 sign = east_label 247 if coord < 0.0: 248 sign = west_label 249 coord = abs(coord) 250 deg = int(coord) 251 min = int((float(coord) - deg) * 60) 252 sec = int(round(float(((float(coord) - deg) * 60) - min) * 60.0)) 253 return f"{deg}Ā°{min}'{sec}\" {sign}"
Converts a floating point longitude to string with degree, minutes and seconds and the appropriate sign (east or west). Eg. 52.1234567 -> 52Ā°7'25" E
Args: - coord (float|int): longitude in floating point format - east_label (str): String label for east - west_label (str): String label for west Returns: str: longitude in string format with degree, minutes, seconds and sign (E/W)
256def draw_aspect_line( 257 r: Union[int, float], 258 ar: Union[int, float], 259 aspect: Union[AspectModel, dict], 260 color: str, 261 seventh_house_degree_ut: Union[int, float], 262) -> str: 263 """Draws svg aspects: ring, aspect ring, degreeA degreeB 264 265 Args: 266 - r (Union[int, float]): The value of r. 267 - ar (Union[int, float]): The value of ar. 268 - aspect_dict (dict): The aspect dictionary. 269 - color (str): The color of the aspect. 270 - seventh_house_degree_ut (Union[int, float]): The degree of the seventh house. 271 272 Returns: 273 str: The SVG line element as a string. 274 """ 275 276 if isinstance(aspect, dict): 277 aspect = AspectModel(**aspect) 278 279 first_offset = (int(seventh_house_degree_ut) / -1) + int(aspect["p1_abs_pos"]) 280 x1 = sliceToX(0, ar, first_offset) + (r - ar) 281 y1 = sliceToY(0, ar, first_offset) + (r - ar) 282 283 second_offset = (int(seventh_house_degree_ut) / -1) + int(aspect["p2_abs_pos"]) 284 x2 = sliceToX(0, ar, second_offset) + (r - ar) 285 y2 = sliceToY(0, ar, second_offset) + (r - ar) 286 287 return ( 288 f'<g kr:node="Aspect" kr:aspectname="{aspect["aspect"]}" kr:to="{aspect["p1_name"]}" kr:tooriginaldegrees="{aspect["p1_abs_pos"]}" kr:from="{aspect["p2_name"]}" kr:fromoriginaldegrees="{aspect["p2_abs_pos"]}">' 289 f'<line class="aspect" x1="{x1}" y1="{y1}" x2="{x2}" y2="{y2}" style="stroke: {color}; stroke-width: 1; stroke-opacity: .9;"/>' 290 f"</g>" 291 )
Draws svg aspects: ring, aspect ring, degreeA degreeB
Args: - r (Union[int, float]): The value of r. - ar (Union[int, float]): The value of ar. - aspect_dict (dict): The aspect dictionary. - color (str): The color of the aspect. - seventh_house_degree_ut (Union[int, float]): The degree of the seventh house.
Returns: str: The SVG line element as a string.
293def convert_decimal_to_degree_string(dec: float, format_type: Literal["1", "2", "3"] = "3") -> str: 294 """ 295 Converts a decimal float to a degrees string in the specified format. 296 297 Args: 298 dec (float): The decimal float to convert. 299 format_type (str): The format type: 300 - "1": aĀ° 301 - "2": aĀ°b' 302 - "3": aĀ°b'c" (default) 303 304 Returns: 305 str: The degrees string in the specified format. 306 """ 307 # Ensure the input is a float 308 dec = float(dec) 309 310 # Calculate degrees, minutes, and seconds 311 degrees = int(dec) 312 minutes = int((dec - degrees) * 60) 313 seconds = int(round((dec - degrees - minutes / 60) * 3600)) 314 315 # Format the output based on the specified type 316 if format_type == "1": 317 return f"{degrees}Ā°" 318 elif format_type == "2": 319 return f"{degrees}Ā°{minutes:02d}'" 320 elif format_type == "3": 321 return f"{degrees}Ā°{minutes:02d}'{seconds:02d}\""
Converts a decimal float to a degrees string in the specified format.
Args: dec (float): The decimal float to convert. format_type (str): The format type: - "1": aĀ° - "2": aĀ°b' - "3": aĀ°b'c" (default)
Returns: str: The degrees string in the specified format.
324def draw_transit_ring_degree_steps(r: Union[int, float], seventh_house_degree_ut: Union[int, float]) -> str: 325 """Draws the transit ring degree steps. 326 327 Args: 328 - r (Union[int, float]): The value of r. 329 - seventh_house_degree_ut (Union[int, float]): The degree of the seventh house. 330 331 Returns: 332 str: The SVG path of the transit ring degree steps. 333 """ 334 335 out = '<g id="transitRingDegreeSteps">' 336 for i in range(72): 337 offset = float(i * 5) - seventh_house_degree_ut 338 if offset < 0: 339 offset = offset + 360.0 340 elif offset > 360: 341 offset = offset - 360.0 342 x1 = sliceToX(0, r, offset) 343 y1 = sliceToY(0, r, offset) 344 x2 = sliceToX(0, r + 2, offset) - 2 345 y2 = sliceToY(0, r + 2, offset) - 2 346 out += f'<line x1="{x1}" y1="{y1}" x2="{x2}" y2="{y2}" style="stroke: #F00; stroke-width: 1px; stroke-opacity:.9;"/>' 347 out += "</g>" 348 349 return out
Draws the transit ring degree steps.
Args: - r (Union[int, float]): The value of r. - seventh_house_degree_ut (Union[int, float]): The degree of the seventh house.
Returns: str: The SVG path of the transit ring degree steps.
352def draw_degree_ring( 353 r: Union[int, float], c1: Union[int, float], seventh_house_degree_ut: Union[int, float], stroke_color: str 354) -> str: 355 """Draws the degree ring. 356 357 Args: 358 - r (Union[int, float]): The value of r. 359 - c1 (Union[int, float]): The value of c1. 360 - seventh_house_degree_ut (Union[int, float]): The degree of the seventh house. 361 - stroke_color (str): The color of the stroke. 362 363 Returns: 364 str: The SVG path of the degree ring. 365 """ 366 out = '<g id="degreeRing">' 367 for i in range(72): 368 offset = float(i * 5) - seventh_house_degree_ut 369 if offset < 0: 370 offset = offset + 360.0 371 elif offset > 360: 372 offset = offset - 360.0 373 x1 = sliceToX(0, r - c1, offset) + c1 374 y1 = sliceToY(0, r - c1, offset) + c1 375 x2 = sliceToX(0, r + 2 - c1, offset) - 2 + c1 376 y2 = sliceToY(0, r + 2 - c1, offset) - 2 + c1 377 378 out += f'<line x1="{x1}" y1="{y1}" x2="{x2}" y2="{y2}" style="stroke: {stroke_color}; stroke-width: 1px; stroke-opacity:.9;"/>' 379 out += "</g>" 380 381 return out
Draws the degree ring.
Args: - r (Union[int, float]): The value of r. - c1 (Union[int, float]): The value of c1. - seventh_house_degree_ut (Union[int, float]): The degree of the seventh house. - stroke_color (str): The color of the stroke.
Returns: str: The SVG path of the degree ring.
384def draw_transit_ring(r: Union[int, float], paper_1_color: str, zodiac_transit_ring_3_color: str) -> str: 385 """ 386 Draws the transit ring. 387 388 Args: 389 - r (Union[int, float]): The value of r. 390 - paper_1_color (str): The color of paper 1. 391 - zodiac_transit_ring_3_color (str): The color of the zodiac transit ring 392 393 Returns: 394 str: The SVG path of the transit ring. 395 """ 396 radius_offset = 18 397 398 out = f'<circle cx="{r}" cy="{r}" r="{r - radius_offset}" style="fill: none; stroke: {paper_1_color}; stroke-width: 36px; stroke-opacity: .4;"/>' 399 out += f'<circle cx="{r}" cy="{r}" r="{r}" style="fill: none; stroke: {zodiac_transit_ring_3_color}; stroke-width: 1px; stroke-opacity: .6;"/>' 400 401 return out
Draws the transit ring.
Args: - r (Union[int, float]): The value of r. - paper_1_color (str): The color of paper 1. - zodiac_transit_ring_3_color (str): The color of the zodiac transit ring
Returns: str: The SVG path of the transit ring.
404def draw_first_circle( 405 r: Union[int, float], stroke_color: str, chart_type: ChartType, c1: Union[int, float, None] = None 406) -> str: 407 """ 408 Draws the first circle. 409 410 Args: 411 - r (Union[int, float]): The value of r. 412 - color (str): The color of the circle. 413 - chart_type (ChartType): The type of chart. 414 - c1 (Union[int, float]): The value of c1. 415 416 Returns: 417 str: The SVG path of the first circle. 418 """ 419 if chart_type == "Synastry" or chart_type == "Transit": 420 return f'<circle cx="{r}" cy="{r}" r="{r - 36}" style="fill: none; stroke: {stroke_color}; stroke-width: 1px; stroke-opacity:.4;" />' 421 else: 422 if c1 is None: 423 raise KerykeionException("c1 is None") 424 425 return ( 426 f'<circle cx="{r}" cy="{r}" r="{r - c1}" style="fill: none; stroke: {stroke_color}; stroke-width: 1px; " />' 427 )
Draws the first circle.
Args: - r (Union[int, float]): The value of r. - color (str): The color of the circle. - chart_type (ChartType): The type of chart. - c1 (Union[int, float]): The value of c1.
Returns: str: The SVG path of the first circle.
430def draw_second_circle( 431 r: Union[int, float], stroke_color: str, fill_color: str, chart_type: ChartType, c2: Union[int, float, None] = None 432) -> str: 433 """ 434 Draws the second circle. 435 436 Args: 437 - r (Union[int, float]): The value of r. 438 - stroke_color (str): The color of the stroke. 439 - fill_color (str): The color of the fill. 440 - chart_type (ChartType): The type of chart. 441 - c2 (Union[int, float]): The value of c2. 442 443 Returns: 444 str: The SVG path of the second circle. 445 """ 446 447 if chart_type == "Synastry" or chart_type == "Transit": 448 return f'<circle cx="{r}" cy="{r}" r="{r - 72}" style="fill: {fill_color}; fill-opacity:.4; stroke: {stroke_color}; stroke-opacity:.4; stroke-width: 1px" />' 449 450 else: 451 if c2 is None: 452 raise KerykeionException("c2 is None") 453 454 return f'<circle cx="{r}" cy="{r}" r="{r - c2}" style="fill: {fill_color}; fill-opacity:.2; stroke: {stroke_color}; stroke-opacity:.4; stroke-width: 1px" />'
Draws the second circle.
Args: - r (Union[int, float]): The value of r. - stroke_color (str): The color of the stroke. - fill_color (str): The color of the fill. - chart_type (ChartType): The type of chart. - c2 (Union[int, float]): The value of c2.
Returns: str: The SVG path of the second circle.
457def draw_third_circle( 458 radius: Union[int, float], 459 stroke_color: str, 460 fill_color: str, 461 chart_type: ChartType, 462 c3: Union[int, float] 463) -> str: 464 """ 465 Draws the third circle in an SVG chart. 466 467 Parameters: 468 - radius (Union[int, float]): The radius of the circle. 469 - stroke_color (str): The stroke color of the circle. 470 - fill_color (str): The fill color of the circle. 471 - chart_type (ChartType): The type of the chart. 472 - c3 (Union[int, float, None], optional): The radius adjustment for non-Synastry and non-Transit charts. 473 474 Returns: 475 - str: The SVG element as a string. 476 """ 477 if chart_type in {"Synastry", "Transit"}: 478 # For Synastry and Transit charts, use a fixed radius adjustment of 160 479 return f'<circle cx="{radius}" cy="{radius}" r="{radius - 160}" style="fill: {fill_color}; fill-opacity:.8; stroke: {stroke_color}; stroke-width: 1px" />' 480 481 else: 482 return f'<circle cx="{radius}" cy="{radius}" r="{radius - c3}" style="fill: {fill_color}; fill-opacity:.8; stroke: {stroke_color}; stroke-width: 1px" />'
Draws the third circle in an SVG chart.
Parameters:
- radius (Union[int, float]): The radius of the circle.
- stroke_color (str): The stroke color of the circle.
- fill_color (str): The fill color of the circle.
- chart_type (ChartType): The type of the chart.
- c3 (Union[int, float, None], optional): The radius adjustment for non-Synastry and non-Transit charts.
Returns:
- str: The SVG element as a string.
485def draw_aspect_grid( 486 stroke_color: str, 487 available_planets: list, 488 aspects: list, 489 x_start: int = 380, 490 y_start: int = 468, 491 ) -> str: 492 """ 493 Draws the aspect grid for the given planets and aspects. 494 495 Args: 496 stroke_color (str): The color of the stroke. 497 available_planets (list): List of all planets. Only planets with "is_active" set to True will be used. 498 aspects (list): List of aspects. 499 x_start (int): The x-coordinate starting point. 500 y_start (int): The y-coordinate starting point. 501 502 Returns: 503 str: SVG string representing the aspect grid. 504 """ 505 svg_output = "" 506 style = f"stroke:{stroke_color}; stroke-width: 1px; stroke-width: 0.5px; fill:none" 507 box_size = 14 508 509 # Filter active planets 510 active_planets = [planet for planet in available_planets if planet.is_active] 511 512 # Reverse the list of active planets for the first iteration 513 reversed_planets = active_planets[::-1] 514 515 for index, planet_a in enumerate(reversed_planets): 516 # Draw the grid box for the planet 517 svg_output += f'<rect kr:node="AspectsGridRect" x="{x_start}" y="{y_start}" width="{box_size}" height="{box_size}" style="{style}"/>' 518 svg_output += f'<use transform="scale(0.4)" x="{(x_start + 2) * 2.5}" y="{(y_start + 1) * 2.5}" xlink:href="#{planet_a["name"]}" />' 519 520 # Update the starting coordinates for the next box 521 x_start += box_size 522 y_start -= box_size 523 524 # Coordinates for the aspect symbols 525 x_aspect = x_start 526 y_aspect = y_start + box_size 527 528 # Iterate over the remaining planets 529 for planet_b in reversed_planets[index + 1:]: 530 # Draw the grid box for the aspect 531 svg_output += f'<rect kr:node="AspectsGridRect" x="{x_aspect}" y="{y_aspect}" width="{box_size}" height="{box_size}" style="{style}"/>' 532 x_aspect += box_size 533 534 # Check for aspects between the planets 535 for aspect in aspects: 536 if (aspect["p1"] == planet_a["id"] and aspect["p2"] == planet_b["id"]) or ( 537 aspect["p1"] == planet_b["id"] and aspect["p2"] == planet_a["id"] 538 ): 539 svg_output += f'<use x="{x_aspect - box_size + 1}" y="{y_aspect + 1}" xlink:href="#orb{aspect["aspect_degrees"]}" />' 540 541 return svg_output
Draws the aspect grid for the given planets and aspects.
Args: stroke_color (str): The color of the stroke. available_planets (list): List of all planets. Only planets with "is_active" set to True will be used. aspects (list): List of aspects. x_start (int): The x-coordinate starting point. y_start (int): The y-coordinate starting point.
Returns: str: SVG string representing the aspect grid.
544def draw_houses_cusps_and_text_number( 545 r: Union[int, float], 546 first_subject_houses_list: list[KerykeionPointModel], 547 standard_house_cusp_color: str, 548 first_house_color: str, 549 tenth_house_color: str, 550 seventh_house_color: str, 551 fourth_house_color: str, 552 c1: Union[int, float], 553 c3: Union[int, float], 554 chart_type: ChartType, 555 second_subject_houses_list: Union[list[KerykeionPointModel], None] = None, 556 transit_house_cusp_color: Union[str, None] = None, 557) -> str: 558 """ 559 Draws the houses cusps and text numbers for a given chart type. 560 561 Parameters: 562 - r: Radius of the chart. 563 - first_subject_houses_list: List of house for the first subject. 564 - standard_house_cusp_color: Default color for house cusps. 565 - first_house_color: Color for the first house cusp. 566 - tenth_house_color: Color for the tenth house cusp. 567 - seventh_house_color: Color for the seventh house cusp. 568 - fourth_house_color: Color for the fourth house cusp. 569 - c1: Offset for the first subject. 570 - c3: Offset for the third subject. 571 - chart_type: Type of the chart (e.g., Transit, Synastry). 572 - second_subject_houses_list: List of house for the second subject (optional). 573 - transit_house_cusp_color: Color for transit house cusps (optional). 574 575 Returns: 576 - A string containing the SVG path for the houses cusps and text numbers. 577 """ 578 579 path = "" 580 xr = 12 581 582 for i in range(xr): 583 # Determine offsets based on chart type 584 dropin, roff, t_roff = (160, 72, 36) if chart_type in ["Transit", "Synastry"] else (c3, c1, False) 585 586 # Calculate the offset for the current house cusp 587 offset = (int(first_subject_houses_list[int(xr / 2)].abs_pos) / -1) + int(first_subject_houses_list[i].abs_pos) 588 589 # Calculate the coordinates for the house cusp lines 590 x1 = sliceToX(0, (r - dropin), offset) + dropin 591 y1 = sliceToY(0, (r - dropin), offset) + dropin 592 x2 = sliceToX(0, r - roff, offset) + roff 593 y2 = sliceToY(0, r - roff, offset) + roff 594 595 # Calculate the text offset for the house number 596 next_index = (i + 1) % xr 597 text_offset = offset + int( 598 degreeDiff(first_subject_houses_list[next_index].abs_pos, first_subject_houses_list[i].abs_pos) / 2 599 ) 600 601 # Determine the line color based on the house index 602 linecolor = {0: first_house_color, 9: tenth_house_color, 6: seventh_house_color, 3: fourth_house_color}.get( 603 i, standard_house_cusp_color 604 ) 605 606 if chart_type in ["Transit", "Synastry"]: 607 if second_subject_houses_list is None or transit_house_cusp_color is None: 608 raise KerykeionException("second_subject_houses_list_ut or transit_house_cusp_color is None") 609 610 # Calculate the offset for the second subject's house cusp 611 zeropoint = 360 - first_subject_houses_list[6].abs_pos 612 t_offset = (zeropoint + second_subject_houses_list[i].abs_pos) % 360 613 614 # Calculate the coordinates for the second subject's house cusp lines 615 t_x1 = sliceToX(0, (r - t_roff), t_offset) + t_roff 616 t_y1 = sliceToY(0, (r - t_roff), t_offset) + t_roff 617 t_x2 = sliceToX(0, r, t_offset) 618 t_y2 = sliceToY(0, r, t_offset) 619 620 # Calculate the text offset for the second subject's house number 621 t_text_offset = t_offset + int( 622 degreeDiff(second_subject_houses_list[next_index].abs_pos, second_subject_houses_list[i].abs_pos) / 2 623 ) 624 t_linecolor = linecolor if i in [0, 9, 6, 3] else transit_house_cusp_color 625 xtext = sliceToX(0, (r - 8), t_text_offset) + 8 626 ytext = sliceToY(0, (r - 8), t_text_offset) + 8 627 628 # Add the house number text for the second subject 629 fill_opacity = "0" if chart_type == "Transit" else ".4" 630 path += f'<g kr:node="HouseNumber">' 631 path += f'<text style="fill: var(--kerykeion-chart-color-house-number); fill-opacity: {fill_opacity}; font-size: 14px"><tspan x="{xtext - 3}" y="{ytext + 3}">{i + 1}</tspan></text>' 632 path += f"</g>" 633 634 # Add the house cusp line for the second subject 635 stroke_opacity = "0" if chart_type == "Transit" else ".3" 636 path += f'<g kr:node="Cusp">' 637 path += f"<line x1='{t_x1}' y1='{t_y1}' x2='{t_x2}' y2='{t_y2}' style='stroke: {t_linecolor}; stroke-width: 1px; stroke-opacity:{stroke_opacity};'/>" 638 path += f"</g>" 639 640 # Adjust dropin based on chart type 641 dropin = {"Transit": 84, "Synastry": 84, "ExternalNatal": 100}.get(chart_type, 48) 642 xtext = sliceToX(0, (r - dropin), text_offset) + dropin 643 ytext = sliceToY(0, (r - dropin), text_offset) + dropin 644 645 # Add the house cusp line for the first subject 646 path += f'<g kr:node="Cusp">' 647 path += f'<line x1="{x1}" y1="{y1}" x2="{x2}" y2="{y2}" style="stroke: {linecolor}; stroke-width: 1px; stroke-dasharray:3,2; stroke-opacity:.4;"/>' 648 path += f"</g>" 649 650 # Add the house number text for the first subject 651 path += f'<g kr:node="HouseNumber">' 652 path += f'<text style="fill: var(--kerykeion-chart-color-house-number); fill-opacity: .6; font-size: 14px"><tspan x="{xtext - 3}" y="{ytext + 3}">{i + 1}</tspan></text>' 653 path += f"</g>" 654 655 return path
Draws the houses cusps and text numbers for a given chart type.
Parameters:
- r: Radius of the chart.
- first_subject_houses_list: List of house for the first subject.
- standard_house_cusp_color: Default color for house cusps.
- first_house_color: Color for the first house cusp.
- tenth_house_color: Color for the tenth house cusp.
- seventh_house_color: Color for the seventh house cusp.
- fourth_house_color: Color for the fourth house cusp.
- c1: Offset for the first subject.
- c3: Offset for the third subject.
- chart_type: Type of the chart (e.g., Transit, Synastry).
- second_subject_houses_list: List of house for the second subject (optional).
- transit_house_cusp_color: Color for transit house cusps (optional).
Returns:
- A string containing the SVG path for the houses cusps and text numbers.
658def draw_transit_aspect_list( 659 grid_title: str, 660 aspects_list: Union[list[AspectModel], list[dict]], 661 celestial_point_language: Union[KerykeionLanguageCelestialPointModel, dict], 662 aspects_settings: Union[KerykeionSettingsAspectModel, dict], 663) -> str: 664 """ 665 Generates the SVG output for the aspect transit grid. 666 667 Parameters: 668 - grid_title: Title of the grid. 669 - aspects_list: List of aspects. 670 - planets_labels: Dictionary containing the planet labels. 671 - aspects_settings: Dictionary containing the aspect settings. 672 673 Returns: 674 - A string containing the SVG path data for the aspect transit grid. 675 """ 676 677 if isinstance(celestial_point_language, dict): 678 celestial_point_language = KerykeionLanguageCelestialPointModel(**celestial_point_language) 679 680 if isinstance(aspects_settings, dict): 681 aspects_settings = KerykeionSettingsAspectModel(**aspects_settings) 682 683 # If not instance of AspectModel, convert to AspectModel 684 if isinstance(aspects_list[0], dict): 685 aspects_list = [AspectModel(**aspect) for aspect in aspects_list] # type: ignore 686 687 line = 0 688 nl = 0 689 inner_path = "" 690 for i, aspect in enumerate(aspects_list): 691 # Adjust the vertical position for every 12 aspects 692 if i == 14: 693 nl = 100 694 line = 0 695 696 elif i == 28: 697 nl = 200 698 line = 0 699 700 elif i == 42: 701 nl = 300 702 line = 0 703 704 elif i == 56: 705 nl = 400 706 line = 0 707 708 elif i == 70: 709 nl = 500 710 # When there are more than 60 aspects, the text is moved up 711 if len(aspects_list) > 84: 712 line = -1 * (len(aspects_list) - 84) * 14 713 else: 714 line = 0 715 716 inner_path += f'<g transform="translate({nl},{line})">' 717 718 # first planet symbol 719 inner_path += f'<use transform="scale(0.4)" x="0" y="3" xlink:href="#{celestial_point_language[aspects_list[i]["p1"]]["name"]}" />' 720 721 # aspect symbol 722 # TODO: Remove the "degree" element EVERYWHERE! 723 aspect_name = aspects_list[i]["aspect"] 724 id_value = next((a["degree"] for a in aspects_settings if a["name"] == aspect_name), None) # type: ignore 725 inner_path += f'<use x="15" y="0" xlink:href="#orb{id_value}" />' 726 727 # second planet symbol 728 inner_path += f'<g transform="translate(30,0)">' 729 inner_path += f'<use transform="scale(0.4)" x="0" y="3" xlink:href="#{celestial_point_language[aspects_list[i]["p2"]]["name"]}" />' 730 inner_path += f"</g>" 731 732 # difference in degrees 733 inner_path += f'<text y="8" x="45" style="fill: var(--kerykeion-chart-color-paper-0); font-size: 10px;">{convert_decimal_to_degree_string(aspects_list[i]["orbit"])}</text>' 734 # line 735 inner_path += f"</g>" 736 line = line + 14 737 738 out = '<g transform="translate(526,273)">' 739 out += f'<text y="-15" x="0" style="fill: var(--kerykeion-chart-color-paper-0); font-size: 14px;">{grid_title}:</text>' 740 out += inner_path 741 out += '</g>' 742 743 return out
Generates the SVG output for the aspect transit grid.
Parameters:
- grid_title: Title of the grid.
- aspects_list: List of aspects.
- planets_labels: Dictionary containing the planet labels.
- aspects_settings: Dictionary containing the aspect settings.
Returns:
- A string containing the SVG path data for the aspect transit grid.
746def calculate_moon_phase_chart_params( 747 degrees_between_sun_and_moon: float, 748 latitude: float 749) -> dict: 750 """ 751 Calculate the parameters for the moon phase chart. 752 753 Parameters: 754 - degrees_between_sun_and_moon (float): The degrees between the sun and the moon. 755 - latitude (float): The latitude for rotation calculation. 756 757 Returns: 758 - dict: The moon phase chart parameters. 759 """ 760 deg = degrees_between_sun_and_moon 761 762 # Initialize variables for lunar phase properties 763 circle_center_x = None 764 circle_radius = None 765 766 # Determine lunar phase properties based on the degree 767 if deg < 90.0: 768 max_radius = deg 769 if deg > 80.0: 770 max_radius = max_radius * max_radius 771 circle_center_x = 20.0 + (deg / 90.0) * (max_radius + 10.0) 772 circle_radius = 10.0 + (deg / 90.0) * max_radius 773 774 elif deg < 180.0: 775 max_radius = 180.0 - deg 776 if deg < 100.0: 777 max_radius = max_radius * max_radius 778 circle_center_x = 20.0 + ((deg - 90.0) / 90.0 * (max_radius + 10.0)) - (max_radius + 10.0) 779 circle_radius = 10.0 + max_radius - ((deg - 90.0) / 90.0 * max_radius) 780 781 elif deg < 270.0: 782 max_radius = deg - 180.0 783 if deg > 260.0: 784 max_radius = max_radius * max_radius 785 circle_center_x = 20.0 + ((deg - 180.0) / 90.0 * (max_radius + 10.0)) 786 circle_radius = 10.0 + ((deg - 180.0) / 90.0 * max_radius) 787 788 elif deg < 361.0: 789 max_radius = 360.0 - deg 790 if deg < 280.0: 791 max_radius = max_radius * max_radius 792 circle_center_x = 20.0 + ((deg - 270.0) / 90.0 * (max_radius + 10.0)) - (max_radius + 10.0) 793 circle_radius = 10.0 + max_radius - ((deg - 270.0) / 90.0 * max_radius) 794 795 else: 796 raise KerykeionException(f"Invalid degree value: {deg}") 797 798 799 # Calculate rotation based on latitude 800 lunar_phase_rotate = -90.0 - latitude 801 802 return { 803 "circle_center_x": circle_center_x, 804 "circle_radius": circle_radius, 805 "lunar_phase_rotate": lunar_phase_rotate, 806 }
Calculate the parameters for the moon phase chart.
Parameters:
- degrees_between_sun_and_moon (float): The degrees between the sun and the moon.
- latitude (float): The latitude for rotation calculation.
Returns:
- dict: The moon phase chart parameters.
808def draw_house_grid( 809 main_subject_houses_list: list[KerykeionPointModel], 810 chart_type: ChartType, 811 secondary_subject_houses_list: Union[list[KerykeionPointModel], None] = None, 812 text_color: str = "#000000", 813 house_cusp_generale_name_label: str = "Cusp", 814 ) -> str: 815 """ 816 Generate SVG code for a grid of astrological houses. 817 818 Parameters: 819 - main_houses (list[KerykeionPointModel]): List of houses for the main subject. 820 - chart_type (ChartType): Type of the chart (e.g., Synastry, Transit). 821 - secondary_houses (list[KerykeionPointModel], optional): List of houses for the secondary subject. 822 - text_color (str): Color of the text. 823 - cusp_label (str): Label for the house cusp. 824 825 Returns: 826 - str: The SVG code for the grid of houses. 827 """ 828 829 if chart_type in ["Synastry", "Transit"] and secondary_subject_houses_list is None: 830 raise KerykeionException("secondary_houses is None") 831 832 svg_output = '<g transform="translate(650,-20)">' 833 834 line_increment = 10 835 for i, house in enumerate(main_subject_houses_list): 836 cusp_number = f"  {i + 1}" if i < 9 else str(i + 1) 837 svg_output += ( 838 f'<g transform="translate(0,{line_increment})">' 839 f'<text text-anchor="end" x="40" style="fill:{text_color}; font-size: 10px;">{house_cusp_generale_name_label} {cusp_number}:</text>' 840 f'<g transform="translate(40,-8)"><use transform="scale(0.3)" xlink:href="#{house["sign"]}" /></g>' 841 f'<text x="53" style="fill:{text_color}; font-size: 10px;"> {convert_decimal_to_degree_string(house["position"])}</text>' 842 f'</g>' 843 ) 844 line_increment += 14 845 846 svg_output += "</g>" 847 848 if chart_type == "Synastry": 849 svg_output += '<!-- Synastry Houses -->' 850 svg_output += '<g transform="translate(910, -20)">' 851 line_increment = 10 852 853 for i, house in enumerate(secondary_subject_houses_list): # type: ignore 854 cusp_number = f"  {i + 1}" if i < 9 else str(i + 1) 855 svg_output += ( 856 f'<g transform="translate(0,{line_increment})">' 857 f'<text text-anchor="end" x="40" style="fill:{text_color}; font-size: 10px;">{house_cusp_generale_name_label} {cusp_number}:</text>' 858 f'<g transform="translate(40,-8)"><use transform="scale(0.3)" xlink:href="#{house["sign"]}" /></g>' 859 f'<text x="53" style="fill:{text_color}; font-size: 10px;"> {convert_decimal_to_degree_string(house["position"])}</text>' 860 f'</g>' 861 ) 862 line_increment += 14 863 864 svg_output += "</g>" 865 866 return svg_output
Generate SVG code for a grid of astrological houses.
Parameters:
- main_houses (list[KerykeionPointModel]): List of houses for the main subject.
- chart_type (ChartType): Type of the chart (e.g., Synastry, Transit).
- secondary_houses (list[KerykeionPointModel], optional): List of houses for the secondary subject.
- text_color (str): Color of the text.
- cusp_label (str): Label for the house cusp.
Returns:
- str: The SVG code for the grid of houses.
869def draw_planet_grid( 870 planets_and_houses_grid_title: str, 871 subject_name: str, 872 available_kerykeion_celestial_points: list[KerykeionPointModel], 873 chart_type: ChartType, 874 celestial_point_language: KerykeionLanguageCelestialPointModel, 875 second_subject_name: Union[str, None] = None, 876 second_subject_available_kerykeion_celestial_points: Union[list[KerykeionPointModel], None] = None, 877 text_color: str = "#000000", 878 ) -> str: 879 """ 880 Draws the planet grid for the given celestial points and chart type. 881 882 Args: 883 planets_and_houses_grid_title (str): Title of the grid. 884 subject_name (str): Name of the subject. 885 available_kerykeion_celestial_points (list[KerykeionPointModel]): List of celestial points for the subject. 886 chart_type (ChartType): Type of the chart. 887 celestial_point_language (KerykeionLanguageCelestialPointModel): Language model for celestial points. 888 second_subject_name (str, optional): Name of the second subject. Defaults to None. 889 second_subject_available_kerykeion_celestial_points (list[KerykeionPointModel], optional): List of celestial points for the second subject. Defaults to None. 890 text_color (str, optional): Color of the text. Defaults to "#000000". 891 892 Returns: 893 str: The SVG output for the planet grid. 894 """ 895 line_height = 10 896 offset = 0 897 offset_between_lines = 14 898 899 svg_output = ( 900 f'<g transform="translate(175, -15)">' 901 f'<text text-anchor="end" style="fill:{text_color}; font-size: 14px;">{planets_and_houses_grid_title} {subject_name}:</text>' 902 f'</g>' 903 ) 904 905 end_of_line = "</g>" 906 907 for i, planet in enumerate(available_kerykeion_celestial_points): 908 if i == 27: 909 line_height = 10 910 offset = -120 911 912 decoded_name = get_decoded_kerykeion_celestial_point_name(planet["name"], celestial_point_language) 913 svg_output += ( 914 f'<g transform="translate({offset},{line_height})">' 915 f'<text text-anchor="end" style="fill:{text_color}; font-size: 10px;">{decoded_name}</text>' 916 f'<g transform="translate(5,-8)"><use transform="scale(0.4)" xlink:href="#{planet["name"]}" /></g>' 917 f'<text text-anchor="start" x="19" style="fill:{text_color}; font-size: 10px;">{convert_decimal_to_degree_string(planet["position"])}</text>' 918 f'<g transform="translate(60,-8)"><use transform="scale(0.3)" xlink:href="#{planet["sign"]}" /></g>' 919 ) 920 921 if planet["retrograde"]: 922 svg_output += '<g transform="translate(74,-6)"><use transform="scale(.5)" xlink:href="#retrograde" /></g>' 923 924 svg_output += end_of_line 925 line_height += offset_between_lines 926 927 if chart_type in ["Transit", "Synastry"]: 928 if second_subject_available_kerykeion_celestial_points is None: 929 raise KerykeionException("second_subject_available_kerykeion_celestial_points is None") 930 931 if chart_type == "Transit": 932 svg_output += ( 933 f'<g transform="translate(320, -15)">' 934 f'<text text-anchor="end" style="fill:{text_color}; font-size: 14px;">{second_subject_name}:</text>' 935 ) 936 else: 937 svg_output += ( 938 f'<g transform="translate(380, -15)">' 939 f'<text text-anchor="end" style="fill:{text_color}; font-size: 14px;">{planets_and_houses_grid_title} {second_subject_name}:</text>' 940 ) 941 942 svg_output += end_of_line 943 944 second_line_height = 10 945 second_offset = 250 946 947 for i, t_planet in enumerate(second_subject_available_kerykeion_celestial_points): 948 if i == 27: 949 second_line_height = 10 950 second_offset = -120 951 952 second_decoded_name = get_decoded_kerykeion_celestial_point_name(t_planet["name"], celestial_point_language) 953 svg_output += ( 954 f'<g transform="translate({second_offset},{second_line_height})">' 955 f'<text text-anchor="end" style="fill:{text_color}; font-size: 10px;">{second_decoded_name}</text>' 956 f'<g transform="translate(5,-8)"><use transform="scale(0.4)" xlink:href="#{t_planet["name"]}" /></g>' 957 f'<text text-anchor="start" x="19" style="fill:{text_color}; font-size: 10px;">{convert_decimal_to_degree_string(t_planet["position"])}</text>' 958 f'<g transform="translate(60,-8)"><use transform="scale(0.3)" xlink:href="#{t_planet["sign"]}" /></g>' 959 ) 960 961 if t_planet["retrograde"]: 962 svg_output += '<g transform="translate(74,-6)"><use transform="scale(.5)" xlink:href="#retrograde" /></g>' 963 964 svg_output += end_of_line 965 second_line_height += offset_between_lines 966 967 return svg_output
Draws the planet grid for the given celestial points and chart type.
Args: planets_and_houses_grid_title (str): Title of the grid. subject_name (str): Name of the subject. available_kerykeion_celestial_points (list[KerykeionPointModel]): List of celestial points for the subject. chart_type (ChartType): Type of the chart. celestial_point_language (KerykeionLanguageCelestialPointModel): Language model for celestial points. second_subject_name (str, optional): Name of the second subject. Defaults to None. second_subject_available_kerykeion_celestial_points (list[KerykeionPointModel], optional): List of celestial points for the second subject. Defaults to None. text_color (str, optional): Color of the text. Defaults to "#000000".
Returns: str: The SVG output for the planet grid.
970def draw_transit_aspect_grid( 971 stroke_color: str, 972 available_planets: list, 973 aspects: list, 974 x_indent: int = 50, 975 y_indent: int = 250, 976 box_size: int = 14 977 ) -> str: 978 """ 979 Draws the aspect grid for the given planets and aspects. The default args value are specific for a stand alone 980 aspect grid. 981 982 Args: 983 stroke_color (str): The color of the stroke. 984 available_planets (list): List of all planets. Only planets with "is_active" set to True will be used. 985 aspects (list): List of aspects. 986 x_indent (int): The initial x-coordinate starting point. 987 y_indent (int): The initial y-coordinate starting point. 988 989 Returns: 990 str: SVG string representing the aspect grid. 991 """ 992 svg_output = "" 993 style = f"stroke:{stroke_color}; stroke-width: 1px; stroke-width: 0.5px; fill:none" 994 x_start = x_indent 995 y_start = y_indent 996 997 # Filter active planets 998 active_planets = [planet for planet in available_planets if planet.is_active] 999 1000 # Reverse the list of active planets for the first iteration 1001 reversed_planets = active_planets[::-1] 1002 for index, planet_a in enumerate(reversed_planets): 1003 # Draw the grid box for the planet 1004 svg_output += f'<rect x="{x_start}" y="{y_start}" width="{box_size}" height="{box_size}" style="{style}"/>' 1005 svg_output += f'<use transform="scale(0.4)" x="{(x_start + 2) * 2.5}" y="{(y_start + 1) * 2.5}" xlink:href="#{planet_a["name"]}" />' 1006 x_start += box_size 1007 1008 x_start = x_indent - box_size 1009 y_start = y_indent - box_size 1010 1011 for index, planet_a in enumerate(reversed_planets): 1012 # Draw the grid box for the planet 1013 svg_output += f'<rect x="{x_start}" y="{y_start}" width="{box_size}" height="{box_size}" style="{style}"/>' 1014 svg_output += f'<use transform="scale(0.4)" x="{(x_start + 2) * 2.5}" y="{(y_start + 1) * 2.5}" xlink:href="#{planet_a["name"]}" />' 1015 y_start -= box_size 1016 1017 x_start = x_indent 1018 y_start = y_indent 1019 y_start = y_start - box_size 1020 1021 for index, planet_a in enumerate(reversed_planets): 1022 # Draw the grid box for the planet 1023 svg_output += f'<rect x="{x_start}" y="{y_start}" width="{box_size}" height="{box_size}" style="{style}"/>' 1024 1025 # Update the starting coordinates for the next box 1026 y_start -= box_size 1027 1028 # Coordinates for the aspect symbols 1029 x_aspect = x_start 1030 y_aspect = y_start + box_size 1031 1032 # Iterate over the remaining planets 1033 for planet_b in reversed_planets: 1034 # Draw the grid box for the aspect 1035 svg_output += f'<rect x="{x_aspect}" y="{y_aspect}" width="{box_size}" height="{box_size}" style="{style}"/>' 1036 x_aspect += box_size 1037 1038 # Check for aspects between the planets 1039 for aspect in aspects: 1040 if (aspect["p1"] == planet_a["id"] and aspect["p2"] == planet_b["id"]): 1041 svg_output += f'<use x="{x_aspect - box_size + 1}" y="{y_aspect + 1}" xlink:href="#orb{aspect["aspect_degrees"]}" />' 1042 1043 return svg_output
Draws the aspect grid for the given planets and aspects. The default args value are specific for a stand alone aspect grid.
Args: stroke_color (str): The color of the stroke. available_planets (list): List of all planets. Only planets with "is_active" set to True will be used. aspects (list): List of aspects. x_indent (int): The initial x-coordinate starting point. y_indent (int): The initial y-coordinate starting point.
Returns: str: SVG string representing the aspect grid.