kerykeion 4.14.1__py3-none-any.whl → 4.14.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of kerykeion might be problematic. Click here for more details.

@@ -2,6 +2,8 @@ import math
2
2
  import datetime
3
3
  from kerykeion.kr_types import KerykeionException, ChartType
4
4
  from typing import Union
5
+ from kerykeion.kr_types.kr_models import AspectModel
6
+ from kerykeion.kr_types.settings_models import KerykeionLanguageCelestialPointModel, KerykeionSettingsAspectModel
5
7
 
6
8
 
7
9
  def decHourJoin(inH: int, inM: int, inS: int) -> float:
@@ -212,8 +214,7 @@ def convert_longitude_coordinate_to_string(coord: Union[int, float], east_label:
212
214
  def draw_aspect_line(
213
215
  r: Union[int, float],
214
216
  ar: Union[int, float],
215
- degA: Union[int, float],
216
- degB: Union[int, float],
217
+ aspect: Union[AspectModel, dict],
217
218
  color: str,
218
219
  seventh_house_degree_ut: Union[int, float],
219
220
  ) -> str:
@@ -222,8 +223,7 @@ def draw_aspect_line(
222
223
  Args:
223
224
  - r (Union[int, float]): The value of r.
224
225
  - ar (Union[int, float]): The value of ar.
225
- - degA (Union[int, float]): The degree of A.
226
- - degB (Union[int, float]): The degree of B.
226
+ - aspect_dict (dict): The aspect dictionary.
227
227
  - color (str): The color of the aspect.
228
228
  - seventh_house_degree_ut (Union[int, float]): The degree of the seventh house.
229
229
 
@@ -231,15 +231,21 @@ def draw_aspect_line(
231
231
  str: The SVG line element as a string.
232
232
  """
233
233
 
234
- first_offset = (int(seventh_house_degree_ut) / -1) + int(degA)
234
+ if isinstance(aspect, dict):
235
+ aspect = AspectModel(**aspect)
236
+
237
+ first_offset = (int(seventh_house_degree_ut) / -1) + int(aspect["p1_abs_pos"])
235
238
  x1 = sliceToX(0, ar, first_offset) + (r - ar)
236
239
  y1 = sliceToY(0, ar, first_offset) + (r - ar)
237
240
 
238
- second_offset = (int(seventh_house_degree_ut) / -1) + int(degB)
241
+ second_offset = (int(seventh_house_degree_ut) / -1) + int(aspect["p2_abs_pos"])
239
242
  x2 = sliceToX(0, ar, second_offset) + (r - ar)
240
243
  y2 = sliceToY(0, ar, second_offset) + (r - ar)
241
244
 
242
- out = f'<line class="aspect" x1="{x1}" y1="{y1}" x2="{x2}" y2="{y2}" style="stroke: {color}; stroke-width: 1; stroke-opacity: .9;"/>'
245
+ out = ""
246
+ out += 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"]}">'
247
+ out += f'<line class="aspect" x1="{x1}" y1="{y1}" x2="{x2}" y2="{y2}" style="stroke: {color}; stroke-width: 1; stroke-opacity: .9;"/>'
248
+ out += f"</g>"
243
249
 
244
250
  return out
245
251
 
@@ -344,19 +350,21 @@ def draw_transit_ring_degree_steps(r: Union[int, float], seventh_house_degree_ut
344
350
  y2 = sliceToY(0, r + 2, offset) - 2
345
351
  out += f'<line x1="{x1}" y1="{y1}" x2="{x2}" y2="{y2}" style="stroke: #F00; stroke-width: 1px; stroke-opacity:.9;"/>'
346
352
  out += "</g>"
347
-
353
+
348
354
  return out
349
355
 
350
356
 
351
- def draw_degree_ring(r: Union[int, float], c1: Union[int, float], seventh_house_degree_ut: Union[int, float], stroke_color: str) -> str:
357
+ def draw_degree_ring(
358
+ r: Union[int, float], c1: Union[int, float], seventh_house_degree_ut: Union[int, float], stroke_color: str
359
+ ) -> str:
352
360
  """Draws the degree ring.
353
-
361
+
354
362
  Args:
355
363
  - r (Union[int, float]): The value of r.
356
364
  - c1 (Union[int, float]): The value of c1.
357
365
  - seventh_house_degree_ut (Union[int, float]): The degree of the seventh house.
358
366
  - stroke_color (str): The color of the stroke.
359
-
367
+
360
368
  Returns:
361
369
  str: The SVG path of the degree ring.
362
370
  """
@@ -374,18 +382,19 @@ def draw_degree_ring(r: Union[int, float], c1: Union[int, float], seventh_house_
374
382
 
375
383
  out += f'<line x1="{x1}" y1="{y1}" x2="{x2}" y2="{y2}" style="stroke: {stroke_color}; stroke-width: 1px; stroke-opacity:.9;"/>'
376
384
  out += "</g>"
377
-
385
+
378
386
  return out
379
387
 
388
+
380
389
  def draw_transit_ring(r: Union[int, float], paper_1_color: str, zodiac_transit_ring_3_color: str) -> str:
381
390
  """
382
391
  Draws the transit ring.
383
-
392
+
384
393
  Args:
385
394
  - r (Union[int, float]): The value of r.
386
395
  - paper_1_color (str): The color of paper 1.
387
396
  - zodiac_transit_ring_3_color (str): The color of the zodiac transit ring
388
-
397
+
389
398
  Returns:
390
399
  str: The SVG path of the transit ring.
391
400
  """
@@ -397,16 +406,18 @@ def draw_transit_ring(r: Union[int, float], paper_1_color: str, zodiac_transit_r
397
406
  return out
398
407
 
399
408
 
400
- def draw_first_circle(r: Union[int, float], stroke_color: str, chart_type: ChartType, c1: Union[int, float, None] = None) -> str:
409
+ def draw_first_circle(
410
+ r: Union[int, float], stroke_color: str, chart_type: ChartType, c1: Union[int, float, None] = None
411
+ ) -> str:
401
412
  """
402
413
  Draws the first circle.
403
-
414
+
404
415
  Args:
405
416
  - r (Union[int, float]): The value of r.
406
417
  - color (str): The color of the circle.
407
418
  - chart_type (ChartType): The type of chart.
408
419
  - c1 (Union[int, float]): The value of c1.
409
-
420
+
410
421
  Returns:
411
422
  str: The SVG path of the first circle.
412
423
  """
@@ -416,29 +427,383 @@ def draw_first_circle(r: Union[int, float], stroke_color: str, chart_type: Chart
416
427
  if c1 is None:
417
428
  raise KerykeionException("c1 is None")
418
429
 
419
- return f'<circle cx="{r}" cy="{r}" r="{r - c1}" style="fill: none; stroke: {stroke_color}; stroke-width: 1px; " />'
430
+ return (
431
+ f'<circle cx="{r}" cy="{r}" r="{r - c1}" style="fill: none; stroke: {stroke_color}; stroke-width: 1px; " />'
432
+ )
420
433
 
421
434
 
422
- def draw_second_circle(r: Union[int, float], stroke_color: str, fill_color: str, chart_type: ChartType, c2: Union[int, float, None] = None) -> str:
435
+ def draw_second_circle(
436
+ r: Union[int, float], stroke_color: str, fill_color: str, chart_type: ChartType, c2: Union[int, float, None] = None
437
+ ) -> str:
423
438
  """
424
439
  Draws the second circle.
425
-
440
+
426
441
  Args:
427
442
  - r (Union[int, float]): The value of r.
428
443
  - stroke_color (str): The color of the stroke.
429
444
  - fill_color (str): The color of the fill.
430
445
  - chart_type (ChartType): The type of chart.
431
446
  - c2 (Union[int, float]): The value of c2.
432
-
447
+
433
448
  Returns:
434
449
  str: The SVG path of the second circle.
435
450
  """
436
-
451
+
437
452
  if chart_type == "Synastry" or chart_type == "Transit":
438
453
  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" />'
439
-
454
+
440
455
  else:
441
456
  if c2 is None:
442
457
  raise KerykeionException("c2 is None")
443
458
 
444
- 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" />'
459
+ 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" />'
460
+
461
+
462
+ def draw_third_circle(
463
+ radius: Union[int, float],
464
+ stroke_color: str,
465
+ fill_color: str,
466
+ chart_type: ChartType,
467
+ c3: Union[int, float, None] = None,
468
+ ) -> str:
469
+ """
470
+ Draws the third circle in an SVG chart.
471
+
472
+ Parameters:
473
+ - radius (Union[int, float]): The radius of the circle.
474
+ - stroke_color (str): The stroke color of the circle.
475
+ - fill_color (str): The fill color of the circle.
476
+ - chart_type (ChartType): The type of the chart.
477
+ - c3 (Union[int, float, None], optional): The radius adjustment for non-Synastry and non-Transit charts.
478
+
479
+ Returns:
480
+ - str: The SVG element as a string.
481
+ """
482
+ if chart_type in {"Synastry", "Transit"}:
483
+ # For Synastry and Transit charts, use a fixed radius adjustment of 160
484
+ return f'<circle cx="{radius}" cy="{radius}" r="{radius - 160}" style="fill: {fill_color}; fill-opacity:.8; stroke: {stroke_color}; stroke-width: 1px" />'
485
+
486
+ else:
487
+ return f'<circle cx="{radius}" cy="{radius}" r="{radius - c3}" style="fill: {fill_color}; fill-opacity:.8; stroke: {stroke_color}; stroke-width: 1px" />'
488
+
489
+
490
+ def draw_aspect_grid(stroke_color: str, available_planets_list: list, aspects_list: list) -> str:
491
+ """
492
+ Draws the aspect grid.
493
+
494
+ Args:
495
+ - stroke_color (str): The color of the stroke.
496
+ - available_planets_list (list): List of all the planets, they will be actually filtered to so if they have
497
+ the "is_active" key set to True inside the function to have the correct list of just the active planets.
498
+ - aspects_list (list): List of aspects.
499
+
500
+ """
501
+
502
+ out = ""
503
+ style = f"stroke:{stroke_color}; stroke-width: 1px; stroke-opacity:.6; fill:none"
504
+ xindent = 380
505
+ yindent = 468
506
+ box = 14
507
+ counter = 0
508
+
509
+ actual_planets = []
510
+ for planet in available_planets_list:
511
+ if planet.is_active:
512
+ actual_planets.append(planet)
513
+
514
+ first_iteration_revers_planets = actual_planets[::-1]
515
+ for index, a in enumerate(first_iteration_revers_planets):
516
+ counter += 1
517
+ out += f'<rect x="{xindent}" y="{yindent}" width="{box}" height="{box}" style="{style}"/>'
518
+ out += f'<use transform="scale(0.4)" x="{(xindent+2)*2.5}" y="{(yindent+1)*2.5}" xlink:href="#{a["name"]}" />'
519
+
520
+ xindent = xindent + box
521
+ yindent = yindent - box
522
+
523
+ xorb = xindent
524
+ yorb = yindent + box
525
+
526
+ second_iteration_revers_planets = first_iteration_revers_planets[index + 1 :]
527
+ for b in second_iteration_revers_planets:
528
+ out += f'<rect x="{xorb}" y="{yorb}" width="{box}" height="{box}" style="{style}"/>'
529
+ xorb = xorb + box
530
+
531
+ for aspect in aspects_list:
532
+ if (aspect["p1"] == a["id"] and aspect["p2"] == b["id"]) or (
533
+ aspect["p1"] == b["id"] and aspect["p2"] == a["id"]
534
+ ):
535
+ out += f'<use x="{xorb-box+1}" y="{yorb+1}" xlink:href="#orb{aspect["aspect_degrees"]}" />'
536
+
537
+ return out
538
+
539
+
540
+ def draw_houses_cusps_and_text_number(
541
+ r: Union[int, float],
542
+ first_subject_houses_list_ut: list,
543
+ standard_house_cusp_color: str,
544
+ first_house_color: str,
545
+ tenth_house_color: str,
546
+ seventh_house_color: str,
547
+ fourth_house_color: str,
548
+ c1: Union[int, float],
549
+ c3: Union[int, float],
550
+ chart_type: ChartType,
551
+ second_subject_houses_list_ut: list = None,
552
+ transit_house_cusp_color: str = None,
553
+ ) -> str:
554
+ """
555
+ Draws the houses cusps and text numbers for a given chart type.
556
+
557
+ Parameters:
558
+ - r: Radius of the chart.
559
+ - first_subject_houses_list_ut: List of house cusps for the first subject.
560
+ - standard_house_cusp_color: Default color for house cusps.
561
+ - first_house_color: Color for the first house cusp.
562
+ - tenth_house_color: Color for the tenth house cusp.
563
+ - seventh_house_color: Color for the seventh house cusp.
564
+ - fourth_house_color: Color for the fourth house cusp.
565
+ - c1: Offset for the first subject.
566
+ - c3: Offset for the third subject.
567
+ - chart_type: Type of the chart (e.g., Transit, Synastry).
568
+ - second_subject_houses_list_ut: List of house cusps for the second subject (optional).
569
+ - transit_house_cusp_color: Color for transit house cusps (optional).
570
+
571
+ Returns:
572
+ - A string containing the SVG path for the houses cusps and text numbers.
573
+ """
574
+ if chart_type in ["Transit", "Synastry"]:
575
+ if second_subject_houses_list_ut is None or transit_house_cusp_color is None:
576
+ raise KerykeionException("second_subject_houses_list_ut or transit_house_cusp_color is None")
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, None)
584
+
585
+ # Calculate the offset for the current house cusp
586
+ offset = (int(first_subject_houses_list_ut[int(xr / 2)]) / -1) + int(first_subject_houses_list_ut[i])
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_ut[next_index], first_subject_houses_list_ut[i]) / 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
+ # Calculate the offset for the second subject's house cusp
607
+ zeropoint = 360 - first_subject_houses_list_ut[6]
608
+ t_offset = (zeropoint + second_subject_houses_list_ut[i]) % 360
609
+
610
+ # Calculate the coordinates for the second subject's house cusp lines
611
+ t_x1 = sliceToX(0, (r - t_roff), t_offset) + t_roff
612
+ t_y1 = sliceToY(0, (r - t_roff), t_offset) + t_roff
613
+ t_x2 = sliceToX(0, r, t_offset)
614
+ t_y2 = sliceToY(0, r, t_offset)
615
+
616
+ # Calculate the text offset for the second subject's house number
617
+ t_text_offset = t_offset + int(
618
+ degreeDiff(second_subject_houses_list_ut[next_index], second_subject_houses_list_ut[i]) / 2
619
+ )
620
+ t_linecolor = linecolor if i in [0, 9, 6, 3] else transit_house_cusp_color
621
+ xtext = sliceToX(0, (r - 8), t_text_offset) + 8
622
+ ytext = sliceToY(0, (r - 8), t_text_offset) + 8
623
+
624
+ # Add the house number text for the second subject
625
+ fill_opacity = "0" if chart_type == "Transit" else ".4"
626
+ path += f'<g kr:node="HouseNumber">'
627
+ path += f'<text style="fill: #00f; fill-opacity: {fill_opacity}; font-size: 14px"><tspan x="{xtext - 3}" y="{ytext + 3}">{i + 1}</tspan></text>'
628
+ path += f"</g>"
629
+
630
+ # Add the house cusp line for the second subject
631
+ stroke_opacity = "0" if chart_type == "Transit" else ".3"
632
+ path += f'<g kr:node="Cusp">'
633
+ 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};'/>"
634
+ path += f"</g>"
635
+
636
+ # Adjust dropin based on chart type
637
+ dropin = {"Transit": 84, "Synastry": 84, "ExternalNatal": 100}.get(chart_type, 48)
638
+ xtext = sliceToX(0, (r - dropin), text_offset) + dropin
639
+ ytext = sliceToY(0, (r - dropin), text_offset) + dropin
640
+
641
+ # Add the house cusp line for the first subject
642
+ path += f'<g kr:node="Cusp">'
643
+ path += f'<line x1="{x1}" y1="{y1}" x2="{x2}" y2="{y2}" style="stroke: {linecolor}; stroke-width: 1px; stroke-dasharray:3,2; stroke-opacity:.4;"/>'
644
+ path += f"</g>"
645
+
646
+ # Add the house number text for the first subject
647
+ path += f'<g kr:node="HouseNumber">'
648
+ path += f'<text style="fill: #f00; fill-opacity: .6; font-size: 14px"><tspan x="{xtext - 3}" y="{ytext + 3}">{i + 1}</tspan></text>'
649
+ path += f"</g>"
650
+
651
+ return path
652
+
653
+
654
+ def draw_aspect_transit_grid(
655
+ grid_title: str,
656
+ aspects_list: Union[list[AspectModel], list[dict]],
657
+ celestial_point_language: KerykeionLanguageCelestialPointModel,
658
+ aspects_settings: KerykeionSettingsAspectModel,
659
+ ) -> str:
660
+ """
661
+ Generates the SVG output for the aspect transit grid.
662
+
663
+ Parameters:
664
+ - grid_title: Title of the grid.
665
+ - aspects_list: List of aspects.
666
+ - planets_labels: Dictionary containing the planet labels.
667
+ - aspects_settings: Dictionary containing the aspect settings.
668
+
669
+ Returns:
670
+ - A string containing the SVG path data for the aspect transit grid.
671
+ """
672
+
673
+ # If not instance of AspectModel, convert to AspectModel
674
+ if isinstance(aspects_list[0], dict):
675
+ aspects_list = [AspectModel(**aspect) for aspect in aspects_list]
676
+
677
+ line = 0
678
+ nl = 0
679
+ inner_path = ""
680
+ scale = 1
681
+ for i, aspect in enumerate(aspects_list):
682
+ # Adjust the vertical position for every 12 aspects
683
+ if i == 12:
684
+ nl = 100
685
+ line = 0
686
+
687
+ elif i == 24:
688
+ nl = 200
689
+ line = 0
690
+
691
+ elif i == 36:
692
+ nl = 300
693
+ line = 0
694
+
695
+ elif i == 48:
696
+ nl = 400
697
+ # When there are more than 60 aspects, the text is moved up
698
+ if len(aspects_list) > 60:
699
+ line = -1 * (len(aspects_list) - 60) * 14
700
+ else:
701
+ line = 0
702
+
703
+ inner_path += f'<g transform="translate({nl},{line})">'
704
+
705
+ # first planet symbol
706
+ inner_path += f'<use transform="scale(0.4)" x="0" y="3" xlink:href="#{celestial_point_language[aspects_list[i]["p1"]]["name"]}" />'
707
+
708
+ # aspect symbol
709
+ inner_path += f'<use x="15" y="0" xlink:href="#orb{aspects_settings[aspects_list[i]["aid"]]["degree"]}" />'
710
+
711
+ # second planet symbol
712
+ inner_path += f'<g transform="translate(30,0)">'
713
+ inner_path += f'<use transform="scale(0.4)" x="0" y="3" xlink:href="#{celestial_point_language[aspects_list[i]["p2"]]["name"]}" />'
714
+ inner_path += f"</g>"
715
+
716
+ # difference in degrees
717
+ inner_path += f'<text y="8" x="45" style="fill:#000000; font-size: 10px;">{convert_decimal_to_degree_string(aspects_list[i]["orbit"])}</text>'
718
+ # line
719
+ inner_path += f"</g>"
720
+ line = line + 14
721
+
722
+ out = f'<g style="transform: translate(47%, 61%) scale({scale})">'
723
+ out += f'<text y="-15" x="0" style="fill:#000000; font-size: 14px;">{grid_title}:</text>'
724
+ out += inner_path
725
+ out += "</g>"
726
+
727
+ return out
728
+
729
+
730
+ def draw_moon_phase(
731
+ degrees_between_sun_and_moon: float,
732
+ latitude: float,
733
+ lunar_phase_outline_color: str = "#000000",
734
+ dark_color: str = "#000000",
735
+ light_color: str = "#ffffff",
736
+ ) -> str:
737
+ """
738
+ Draws the moon phase based on the degrees between the sun and the moon.
739
+
740
+ Parameters:
741
+ - degrees_between_sun_and_moon (float): The degrees between the sun and the moon.
742
+ - latitude (float): The latitude for rotation calculation.
743
+ - lunar_phase_outline_color (str): The color for the lunar phase outline.
744
+ - dark_color (str): The color for the dark part of the moon.
745
+ - light_color (str): The color for the light part of the moon.
746
+
747
+ Returns:
748
+ - str: The SVG element as a string.
749
+ """
750
+ deg = degrees_between_sun_and_moon
751
+
752
+ # Initialize variables for lunar phase properties
753
+ fill_color_foreground = None
754
+ fill_color_background = None
755
+ circle_center_x = None
756
+ circle_radius = None
757
+
758
+ # Determine lunar phase properties based on the degree
759
+ if deg < 90.0:
760
+ max_radius = deg
761
+ if deg > 80.0:
762
+ max_radius = max_radius * max_radius
763
+ circle_center_x = 20.0 + (deg / 90.0) * (max_radius + 10.0)
764
+ circle_radius = 10.0 + (deg / 90.0) * max_radius
765
+ fill_color_foreground = dark_color
766
+ fill_color_background = light_color
767
+
768
+ elif deg < 180.0:
769
+ max_radius = 180.0 - deg
770
+ if deg < 100.0:
771
+ max_radius = max_radius * max_radius
772
+ circle_center_x = 20.0 + ((deg - 90.0) / 90.0 * (max_radius + 10.0)) - (max_radius + 10.0)
773
+ circle_radius = 10.0 + max_radius - ((deg - 90.0) / 90.0 * max_radius)
774
+ fill_color_foreground = light_color
775
+ fill_color_background = dark_color
776
+
777
+ elif deg < 270.0:
778
+ max_radius = deg - 180.0
779
+ if deg > 260.0:
780
+ max_radius = max_radius * max_radius
781
+ circle_center_x = 20.0 + ((deg - 180.0) / 90.0 * (max_radius + 10.0))
782
+ circle_radius = 10.0 + ((deg - 180.0) / 90.0 * max_radius)
783
+ fill_color_foreground = light_color
784
+ fill_color_background = dark_color
785
+
786
+ elif deg < 361.0:
787
+ max_radius = 360.0 - deg
788
+ if deg < 280.0:
789
+ max_radius = max_radius * max_radius
790
+ circle_center_x = 20.0 + ((deg - 270.0) / 90.0 * (max_radius + 10.0)) - (max_radius + 10.0)
791
+ circle_radius = 10.0 + max_radius - ((deg - 270.0) / 90.0 * max_radius)
792
+ fill_color_foreground = dark_color
793
+ fill_color_background = light_color
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 the SVG element as a string
803
+ return (
804
+ f'<g transform="rotate({lunar_phase_rotate} 20 10)">'
805
+ f' <circle cx="20" cy="10" r="10" style="fill: {fill_color_background}" />'
806
+ f' <circle cx="{circle_center_x}" cy="10" r="{circle_radius}" style="fill: {fill_color_foreground}" clip-path="url(#moonPhaseCutOffCircle)" />'
807
+ f' <circle cx="20" cy="10" r="10" style="fill: none; stroke: {lunar_phase_outline_color}; stroke-width: 0.5px; stroke-opacity: 0.5" />'
808
+ f"</g>"
809
+ )