kerykeion 4.26.2__py3-none-any.whl → 5.0.0a2__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.
- kerykeion/__init__.py +9 -7
- kerykeion/aspects/aspects_utils.py +14 -8
- kerykeion/aspects/natal_aspects.py +26 -17
- kerykeion/aspects/synastry_aspects.py +32 -15
- kerykeion/aspects/transits_time_range.py +2 -2
- kerykeion/astrological_subject_factory.py +1132 -0
- kerykeion/charts/charts_utils.py +676 -146
- kerykeion/charts/draw_planets.py +9 -8
- kerykeion/charts/draw_planets_v2.py +639 -0
- kerykeion/charts/kerykeion_chart_svg.py +1334 -601
- kerykeion/charts/templates/chart.xml +184 -78
- kerykeion/charts/templates/wheel_only.xml +13 -12
- kerykeion/charts/themes/classic.css +91 -76
- kerykeion/charts/themes/dark-high-contrast.css +129 -107
- kerykeion/charts/themes/dark.css +130 -107
- kerykeion/charts/themes/light.css +130 -103
- kerykeion/charts/themes/strawberry.css +143 -0
- kerykeion/composite_subject_factory.py +26 -43
- kerykeion/ephemeris_data.py +6 -10
- kerykeion/house_comparison/__init__.py +3 -0
- kerykeion/house_comparison/house_comparison_factory.py +70 -0
- kerykeion/house_comparison/house_comparison_models.py +38 -0
- kerykeion/house_comparison/house_comparison_utils.py +98 -0
- kerykeion/kr_types/chart_types.py +13 -5
- kerykeion/kr_types/kr_literals.py +34 -6
- kerykeion/kr_types/kr_models.py +122 -160
- kerykeion/kr_types/settings_models.py +107 -143
- kerykeion/planetary_return_factory.py +299 -0
- kerykeion/{relationship_score/relationship_score_factory.py → relationship_score_factory.py} +10 -13
- kerykeion/report.py +4 -4
- kerykeion/settings/config_constants.py +35 -6
- kerykeion/settings/kerykeion_settings.py +1 -0
- kerykeion/settings/kr.config.json +1301 -1255
- kerykeion/settings/legacy/__init__.py +0 -0
- kerykeion/settings/legacy/legacy_celestial_points_settings.py +299 -0
- kerykeion/settings/legacy/legacy_chart_aspects_settings.py +71 -0
- kerykeion/settings/legacy/legacy_color_settings.py +42 -0
- kerykeion/transits_time_range.py +13 -9
- kerykeion/utilities.py +228 -31
- {kerykeion-4.26.2.dist-info → kerykeion-5.0.0a2.dist-info}/METADATA +119 -107
- kerykeion-5.0.0a2.dist-info/RECORD +54 -0
- {kerykeion-4.26.2.dist-info → kerykeion-5.0.0a2.dist-info}/WHEEL +1 -1
- kerykeion/astrological_subject.py +0 -841
- kerykeion/relationship_score/__init__.py +0 -2
- kerykeion/relationship_score/relationship_score.py +0 -175
- kerykeion-4.26.2.dist-info/LICENSE +0 -661
- kerykeion-4.26.2.dist-info/RECORD +0 -46
- /LICENSE → /kerykeion-5.0.0a2.dist-info/LICENSE +0 -0
- {kerykeion-4.26.2.dist-info → kerykeion-5.0.0a2.dist-info}/entry_points.txt +0 -0
kerykeion/charts/charts_utils.py
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import math
|
|
2
2
|
import datetime
|
|
3
3
|
from kerykeion.kr_types import KerykeionException, ChartType
|
|
4
|
-
from
|
|
5
|
-
from
|
|
6
|
-
from kerykeion.kr_types.
|
|
4
|
+
from kerykeion.kr_types.kr_literals import AstrologicalPoint
|
|
5
|
+
from typing import Union, Literal, TYPE_CHECKING
|
|
6
|
+
from kerykeion.kr_types.kr_models import AspectModel, KerykeionPointModel, CompositeSubjectModel, PlanetReturnModel, AstrologicalSubjectModel
|
|
7
|
+
from kerykeion.kr_types.settings_models import KerykeionLanguageCelestialPointModel, KerykeionSettingsCelestialPointModel
|
|
8
|
+
from kerykeion.house_comparison import HouseComparisonModel
|
|
9
|
+
|
|
7
10
|
|
|
8
11
|
|
|
9
12
|
def get_decoded_kerykeion_celestial_point_name(input_planet_name: str, celestial_point_language: KerykeionLanguageCelestialPointModel) -> str:
|
|
@@ -186,7 +189,7 @@ def draw_zodiac_slice(
|
|
|
186
189
|
# pie slices
|
|
187
190
|
offset = 360 - seventh_house_degree_ut
|
|
188
191
|
# check transit
|
|
189
|
-
if chart_type == "Transit" or chart_type == "Synastry":
|
|
192
|
+
if chart_type == "Transit" or chart_type == "Synastry" or chart_type == "Return":
|
|
190
193
|
dropin: Union[int, float] = 0
|
|
191
194
|
else:
|
|
192
195
|
dropin = c1
|
|
@@ -195,7 +198,7 @@ def draw_zodiac_slice(
|
|
|
195
198
|
# symbols
|
|
196
199
|
offset = offset + 15
|
|
197
200
|
# check transit
|
|
198
|
-
if chart_type == "Transit" or chart_type == "Synastry":
|
|
201
|
+
if chart_type == "Transit" or chart_type == "Synastry" or chart_type == "Return":
|
|
199
202
|
dropin = 54
|
|
200
203
|
else:
|
|
201
204
|
dropin = 18 + c1
|
|
@@ -289,6 +292,7 @@ def draw_aspect_line(
|
|
|
289
292
|
f"</g>"
|
|
290
293
|
)
|
|
291
294
|
|
|
295
|
+
|
|
292
296
|
def convert_decimal_to_degree_string(dec: float, format_type: Literal["1", "2", "3"] = "3") -> str:
|
|
293
297
|
"""
|
|
294
298
|
Converts a decimal float to a degrees string in the specified format.
|
|
@@ -415,7 +419,7 @@ def draw_first_circle(
|
|
|
415
419
|
Returns:
|
|
416
420
|
str: The SVG path of the first circle.
|
|
417
421
|
"""
|
|
418
|
-
if chart_type == "Synastry" or chart_type == "Transit":
|
|
422
|
+
if chart_type == "Synastry" or chart_type == "Transit" or chart_type == "Return":
|
|
419
423
|
return f'<circle cx="{r}" cy="{r}" r="{r - 36}" style="fill: none; stroke: {stroke_color}; stroke-width: 1px; stroke-opacity:.4;" />'
|
|
420
424
|
else:
|
|
421
425
|
if c1 is None:
|
|
@@ -443,7 +447,7 @@ def draw_second_circle(
|
|
|
443
447
|
str: The SVG path of the second circle.
|
|
444
448
|
"""
|
|
445
449
|
|
|
446
|
-
if chart_type == "Synastry" or chart_type == "Transit":
|
|
450
|
+
if chart_type == "Synastry" or chart_type == "Transit" or chart_type == "Return":
|
|
447
451
|
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
452
|
|
|
449
453
|
else:
|
|
@@ -473,7 +477,7 @@ def draw_third_circle(
|
|
|
473
477
|
Returns:
|
|
474
478
|
- str: The SVG element as a string.
|
|
475
479
|
"""
|
|
476
|
-
if chart_type in {"Synastry", "Transit"}:
|
|
480
|
+
if chart_type in {"Synastry", "Transit", "Return"}:
|
|
477
481
|
# For Synastry and Transit charts, use a fixed radius adjustment of 160
|
|
478
482
|
return f'<circle cx="{radius}" cy="{radius}" r="{radius - 160}" style="fill: {fill_color}; fill-opacity:.8; stroke: {stroke_color}; stroke-width: 1px" />'
|
|
479
483
|
|
|
@@ -485,7 +489,7 @@ def draw_aspect_grid(
|
|
|
485
489
|
stroke_color: str,
|
|
486
490
|
available_planets: list,
|
|
487
491
|
aspects: list,
|
|
488
|
-
x_start: int =
|
|
492
|
+
x_start: int = 510,
|
|
489
493
|
y_start: int = 468,
|
|
490
494
|
) -> str:
|
|
491
495
|
"""
|
|
@@ -506,7 +510,7 @@ def draw_aspect_grid(
|
|
|
506
510
|
box_size = 14
|
|
507
511
|
|
|
508
512
|
# Filter active planets
|
|
509
|
-
active_planets = [planet for planet in available_planets if planet
|
|
513
|
+
active_planets = [planet for planet in available_planets if planet["is_active"]]
|
|
510
514
|
|
|
511
515
|
# Reverse the list of active planets for the first iteration
|
|
512
516
|
reversed_planets = active_planets[::-1]
|
|
@@ -580,7 +584,7 @@ def draw_houses_cusps_and_text_number(
|
|
|
580
584
|
|
|
581
585
|
for i in range(xr):
|
|
582
586
|
# Determine offsets based on chart type
|
|
583
|
-
dropin, roff, t_roff = (160, 72, 36) if chart_type in ["Transit", "Synastry"] else (c3, c1, False)
|
|
587
|
+
dropin, roff, t_roff = (160, 72, 36) if chart_type in ["Transit", "Synastry", "Return"] else (c3, c1, False)
|
|
584
588
|
|
|
585
589
|
# Calculate the offset for the current house cusp
|
|
586
590
|
offset = (int(first_subject_houses_list[int(xr / 2)].abs_pos) / -1) + int(first_subject_houses_list[i].abs_pos)
|
|
@@ -602,7 +606,7 @@ def draw_houses_cusps_and_text_number(
|
|
|
602
606
|
i, standard_house_cusp_color
|
|
603
607
|
)
|
|
604
608
|
|
|
605
|
-
if chart_type in ["Transit", "Synastry"]:
|
|
609
|
+
if chart_type in ["Transit", "Synastry", "Return"]:
|
|
606
610
|
if second_subject_houses_list is None or transit_house_cusp_color is None:
|
|
607
611
|
raise KerykeionException("second_subject_houses_list_ut or transit_house_cusp_color is None")
|
|
608
612
|
|
|
@@ -637,7 +641,7 @@ def draw_houses_cusps_and_text_number(
|
|
|
637
641
|
path += f"</g>"
|
|
638
642
|
|
|
639
643
|
# Adjust dropin based on chart type
|
|
640
|
-
dropin = {"Transit": 84, "Synastry": 84, "ExternalNatal": 100}.get(chart_type, 48)
|
|
644
|
+
dropin = {"Transit": 84, "Synastry": 84, "Return": 84, "ExternalNatal": 100}.get(chart_type, 48)
|
|
641
645
|
xtext = sliceToX(0, (r - dropin), text_offset) + dropin
|
|
642
646
|
ytext = sliceToY(0, (r - dropin), text_offset) + dropin
|
|
643
647
|
|
|
@@ -658,7 +662,12 @@ def draw_transit_aspect_list(
|
|
|
658
662
|
grid_title: str,
|
|
659
663
|
aspects_list: Union[list[AspectModel], list[dict]],
|
|
660
664
|
celestial_point_language: Union[KerykeionLanguageCelestialPointModel, dict],
|
|
661
|
-
aspects_settings:
|
|
665
|
+
aspects_settings: dict,
|
|
666
|
+
*,
|
|
667
|
+
aspects_per_column: int = 14,
|
|
668
|
+
column_width: int = 100,
|
|
669
|
+
line_height: int = 14,
|
|
670
|
+
max_columns: int = 6
|
|
662
671
|
) -> str:
|
|
663
672
|
"""
|
|
664
673
|
Generates the SVG output for the aspect transit grid.
|
|
@@ -666,8 +675,12 @@ def draw_transit_aspect_list(
|
|
|
666
675
|
Parameters:
|
|
667
676
|
- grid_title: Title of the grid.
|
|
668
677
|
- aspects_list: List of aspects.
|
|
669
|
-
-
|
|
678
|
+
- celestial_point_language: Dictionary containing the celestial point language data.
|
|
670
679
|
- aspects_settings: Dictionary containing the aspect settings.
|
|
680
|
+
- aspects_per_column: Number of aspects to display per column (default: 14).
|
|
681
|
+
- column_width: Width in pixels for each column (default: 100).
|
|
682
|
+
- line_height: Height in pixels for each line (default: 14).
|
|
683
|
+
- max_columns: Maximum number of columns before vertical adjustment (default: 6).
|
|
671
684
|
|
|
672
685
|
Returns:
|
|
673
686
|
- A string containing the SVG path data for the aspect transit grid.
|
|
@@ -676,65 +689,52 @@ def draw_transit_aspect_list(
|
|
|
676
689
|
if isinstance(celestial_point_language, dict):
|
|
677
690
|
celestial_point_language = KerykeionLanguageCelestialPointModel(**celestial_point_language)
|
|
678
691
|
|
|
679
|
-
if isinstance(aspects_settings, dict):
|
|
680
|
-
aspects_settings = KerykeionSettingsAspectModel(**aspects_settings)
|
|
681
|
-
|
|
682
692
|
# 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]
|
|
693
|
+
if aspects_list and isinstance(aspects_list[0], dict):
|
|
694
|
+
aspects_list = [AspectModel(**aspect) for aspect in aspects_list] # type: ignore
|
|
685
695
|
|
|
686
|
-
line = 0
|
|
687
|
-
nl = 0
|
|
688
696
|
inner_path = ""
|
|
697
|
+
|
|
689
698
|
for i, aspect in enumerate(aspects_list):
|
|
690
|
-
#
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
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
|
|
699
|
+
# Calculate which column this aspect belongs in
|
|
700
|
+
current_column = i // aspects_per_column
|
|
701
|
+
|
|
702
|
+
# Calculate horizontal position based on column
|
|
703
|
+
horizontal_position = current_column * column_width
|
|
704
|
+
|
|
705
|
+
# Calculate vertical position within the column
|
|
706
|
+
current_line = i % aspects_per_column
|
|
707
|
+
vertical_position = current_line * line_height
|
|
708
|
+
|
|
709
|
+
# Special handling for many aspects - if we exceed max_columns
|
|
710
|
+
if current_column >= max_columns:
|
|
711
|
+
# Calculate how many aspects will overflow beyond the max columns
|
|
712
|
+
overflow_aspects = len(aspects_list) - (aspects_per_column * max_columns)
|
|
713
|
+
if overflow_aspects > 0:
|
|
714
|
+
# Adjust the starting vertical position to move text up
|
|
715
|
+
vertical_position = vertical_position - (overflow_aspects * line_height)
|
|
716
|
+
|
|
717
|
+
inner_path += f'<g transform="translate({horizontal_position},{vertical_position})">'
|
|
718
|
+
|
|
719
|
+
# First planet symbol
|
|
720
|
+
inner_path += f'<use transform="scale(0.4)" x="0" y="3" xlink:href="#{celestial_point_language[aspect["p1"]]["name"]}" />'
|
|
721
|
+
|
|
722
|
+
# Aspect symbol
|
|
723
|
+
aspect_name = aspect["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
|
|
727
728
|
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[
|
|
729
|
+
inner_path += f'<use transform="scale(0.4)" x="0" y="3" xlink:href="#{celestial_point_language[aspect["p2"]]["name"]}" />'
|
|
729
730
|
inner_path += f"</g>"
|
|
730
731
|
|
|
731
|
-
#
|
|
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(
|
|
733
|
-
|
|
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(aspect["orbit"])}</text>'
|
|
734
|
+
|
|
734
735
|
inner_path += f"</g>"
|
|
735
|
-
line = line + 14
|
|
736
736
|
|
|
737
|
-
out = '<g transform="translate(
|
|
737
|
+
out = '<g transform="translate(565,273)">'
|
|
738
738
|
out += f'<text y="-15" x="0" style="fill: var(--kerykeion-chart-color-paper-0); font-size: 14px;">{grid_title}:</text>'
|
|
739
739
|
out += inner_path
|
|
740
740
|
out += '</g>'
|
|
@@ -804,31 +804,28 @@ def calculate_moon_phase_chart_params(
|
|
|
804
804
|
"lunar_phase_rotate": lunar_phase_rotate,
|
|
805
805
|
}
|
|
806
806
|
|
|
807
|
-
|
|
807
|
+
|
|
808
|
+
def draw_main_house_grid(
|
|
808
809
|
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
810
|
house_cusp_generale_name_label: str = "Cusp",
|
|
811
|
+
text_color: str = "#000000",
|
|
812
|
+
x_position: int = 720,
|
|
813
|
+
y_position: int = 30,
|
|
813
814
|
) -> str:
|
|
814
815
|
"""
|
|
815
|
-
Generate SVG code for a grid of astrological houses.
|
|
816
|
+
Generate SVG code for a grid of astrological houses for the main subject.
|
|
816
817
|
|
|
817
818
|
Parameters:
|
|
818
|
-
-
|
|
819
|
-
-
|
|
820
|
-
-
|
|
821
|
-
-
|
|
822
|
-
-
|
|
819
|
+
- main_subject_houses_list (list[KerykeionPointModel]): List of houses for the main subject.
|
|
820
|
+
- house_cusp_generale_name_label (str): Label for the house cusp. Defaults to "Cusp".
|
|
821
|
+
- text_color (str): Color of the text. Defaults to "#000000".
|
|
822
|
+
- x_position (int): X position for the grid. Defaults to 720.
|
|
823
|
+
- y_position (int): Y position for the grid. Defaults to 30.
|
|
823
824
|
|
|
824
825
|
Returns:
|
|
825
826
|
- str: The SVG code for the grid of houses.
|
|
826
827
|
"""
|
|
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)">'
|
|
828
|
+
svg_output = f'<g transform="translate({x_position},{y_position})">'
|
|
832
829
|
|
|
833
830
|
line_increment = 10
|
|
834
831
|
for i, house in enumerate(main_subject_houses_list):
|
|
@@ -843,40 +840,57 @@ def draw_house_grid(
|
|
|
843
840
|
line_increment += 14
|
|
844
841
|
|
|
845
842
|
svg_output += "</g>"
|
|
843
|
+
return svg_output
|
|
846
844
|
|
|
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
845
|
|
|
863
|
-
|
|
846
|
+
def draw_secondary_house_grid(
|
|
847
|
+
secondary_subject_houses_list: list[KerykeionPointModel],
|
|
848
|
+
house_cusp_generale_name_label: str = "Cusp",
|
|
849
|
+
text_color: str = "#000000",
|
|
850
|
+
x_position: int = 970,
|
|
851
|
+
y_position: int = 30,
|
|
852
|
+
) -> str:
|
|
853
|
+
"""
|
|
854
|
+
Generate SVG code for a grid of astrological houses for the secondary subject.
|
|
855
|
+
|
|
856
|
+
Parameters:
|
|
857
|
+
- secondary_subject_houses_list (list[KerykeionPointModel]): List of houses for the secondary subject.
|
|
858
|
+
- house_cusp_generale_name_label (str): Label for the house cusp. Defaults to "Cusp".
|
|
859
|
+
- text_color (str): Color of the text. Defaults to "#000000".
|
|
860
|
+
- x_position (int): X position for the grid. Defaults to 970.
|
|
861
|
+
- y_position (int): Y position for the grid. Defaults to 30.
|
|
862
|
+
|
|
863
|
+
Returns:
|
|
864
|
+
- str: The SVG code for the grid of houses.
|
|
865
|
+
"""
|
|
866
|
+
svg_output = f'<g transform="translate({x_position},{y_position})">'
|
|
867
|
+
|
|
868
|
+
line_increment = 10
|
|
869
|
+
for i, house in enumerate(secondary_subject_houses_list):
|
|
870
|
+
cusp_number = f"  {i + 1}" if i < 9 else str(i + 1)
|
|
871
|
+
svg_output += (
|
|
872
|
+
f'<g transform="translate(0,{line_increment})">'
|
|
873
|
+
f'<text text-anchor="end" x="40" style="fill:{text_color}; font-size: 10px;">{house_cusp_generale_name_label} {cusp_number}:</text>'
|
|
874
|
+
f'<g transform="translate(40,-8)"><use transform="scale(0.3)" xlink:href="#{house["sign"]}" /></g>'
|
|
875
|
+
f'<text x="53" style="fill:{text_color}; font-size: 10px;"> {convert_decimal_to_degree_string(house["position"])}</text>'
|
|
876
|
+
f'</g>'
|
|
877
|
+
)
|
|
878
|
+
line_increment += 14
|
|
864
879
|
|
|
880
|
+
svg_output += "</g>"
|
|
865
881
|
return svg_output
|
|
866
882
|
|
|
867
883
|
|
|
868
|
-
def
|
|
884
|
+
def draw_main_planet_grid(
|
|
869
885
|
planets_and_houses_grid_title: str,
|
|
870
886
|
subject_name: str,
|
|
871
887
|
available_kerykeion_celestial_points: list[KerykeionPointModel],
|
|
872
888
|
chart_type: ChartType,
|
|
873
889
|
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
890
|
text_color: str = "#000000",
|
|
877
891
|
) -> str:
|
|
878
892
|
"""
|
|
879
|
-
Draws the planet grid for the
|
|
893
|
+
Draws the planet grid for the main subject.
|
|
880
894
|
|
|
881
895
|
Args:
|
|
882
896
|
planets_and_houses_grid_title (str): Title of the grid.
|
|
@@ -884,33 +898,47 @@ def draw_planet_grid(
|
|
|
884
898
|
available_kerykeion_celestial_points (list[KerykeionPointModel]): List of celestial points for the subject.
|
|
885
899
|
chart_type (ChartType): Type of the chart.
|
|
886
900
|
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
901
|
text_color (str, optional): Color of the text. Defaults to "#000000".
|
|
890
902
|
|
|
891
903
|
Returns:
|
|
892
|
-
str: The SVG output for the planet grid.
|
|
904
|
+
str: The SVG output for the main planet grid.
|
|
893
905
|
"""
|
|
894
906
|
line_height = 10
|
|
895
907
|
offset = 0
|
|
896
908
|
offset_between_lines = 14
|
|
909
|
+
svg_output = ""
|
|
897
910
|
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
911
|
+
if chart_type == "Synastry":
|
|
912
|
+
svg_output += (
|
|
913
|
+
f'<g transform="translate(620, 15)">' # Added the 620,30 offset (adjusted for -15)
|
|
914
|
+
f'<text style="fill:{text_color}; font-size: 14px;">{planets_and_houses_grid_title} {subject_name}</text>'
|
|
915
|
+
f'</g>'
|
|
916
|
+
)
|
|
917
|
+
elif chart_type == "Transit":
|
|
918
|
+
svg_output += (
|
|
919
|
+
f'<g transform="translate(620, 15)">' # Added the 620,30 offset (adjusted for -15)
|
|
920
|
+
f'<text style="fill:{text_color}; font-size: 14px;">{planets_and_houses_grid_title} {subject_name}</text>'
|
|
921
|
+
f'</g>'
|
|
922
|
+
)
|
|
923
|
+
elif chart_type == "Return":
|
|
924
|
+
svg_output += (
|
|
925
|
+
f'<g transform="translate(620, 15)">' # Added the 620,30 offset (adjusted for -15)
|
|
926
|
+
f'<text style="fill:{text_color}; font-size: 14px;">{planets_and_houses_grid_title} {subject_name}</text>'
|
|
927
|
+
f'</g>'
|
|
928
|
+
)
|
|
929
|
+
else:
|
|
930
|
+
svg_output += ""
|
|
903
931
|
|
|
904
932
|
end_of_line = "</g>"
|
|
905
933
|
|
|
906
934
|
for i, planet in enumerate(available_kerykeion_celestial_points):
|
|
907
|
-
if i ==
|
|
935
|
+
if i == 22:
|
|
908
936
|
line_height = 10
|
|
909
|
-
offset = -
|
|
937
|
+
offset = -125
|
|
910
938
|
|
|
911
939
|
decoded_name = get_decoded_kerykeion_celestial_point_name(planet["name"], celestial_point_language)
|
|
912
940
|
svg_output += (
|
|
913
|
-
f'<g transform="translate({offset},{line_height})">'
|
|
941
|
+
f'<g transform="translate({620 + offset},{30 + line_height})">' # Added the 620,30 offset
|
|
914
942
|
f'<text text-anchor="end" style="fill:{text_color}; font-size: 10px;">{decoded_name}</text>'
|
|
915
943
|
f'<g transform="translate(5,-8)"><use transform="scale(0.4)" xlink:href="#{planet["name"]}" /></g>'
|
|
916
944
|
f'<text text-anchor="start" x="19" style="fill:{text_color}; font-size: 10px;">{convert_decimal_to_degree_string(planet["position"])}</text>'
|
|
@@ -923,45 +951,70 @@ def draw_planet_grid(
|
|
|
923
951
|
svg_output += end_of_line
|
|
924
952
|
line_height += offset_between_lines
|
|
925
953
|
|
|
926
|
-
|
|
927
|
-
if second_subject_available_kerykeion_celestial_points is None:
|
|
928
|
-
raise KerykeionException("second_subject_available_kerykeion_celestial_points is None")
|
|
954
|
+
return svg_output
|
|
929
955
|
|
|
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
956
|
|
|
941
|
-
|
|
957
|
+
def draw_secondary_planet_grid(
|
|
958
|
+
planets_and_houses_grid_title: str,
|
|
959
|
+
second_subject_name: str,
|
|
960
|
+
second_subject_available_kerykeion_celestial_points: list[KerykeionPointModel],
|
|
961
|
+
chart_type: ChartType,
|
|
962
|
+
celestial_point_language: KerykeionLanguageCelestialPointModel,
|
|
963
|
+
text_color: str = "#000000",
|
|
964
|
+
) -> str:
|
|
965
|
+
"""
|
|
966
|
+
Draws the planet grid for the secondary subject in Transit, Synastry, or Return charts.
|
|
942
967
|
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
for
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
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
|
-
)
|
|
968
|
+
Args:
|
|
969
|
+
planets_and_houses_grid_title (str): Title of the grid.
|
|
970
|
+
second_subject_name (str): Name of the second subject.
|
|
971
|
+
second_subject_available_kerykeion_celestial_points (list[KerykeionPointModel]): List of celestial points for the second subject.
|
|
972
|
+
chart_type (ChartType): Type of the chart.
|
|
973
|
+
celestial_point_language (KerykeionLanguageCelestialPointModel): Language model for celestial points.
|
|
974
|
+
text_color (str, optional): Color of the text. Defaults to "#000000".
|
|
959
975
|
|
|
960
|
-
|
|
961
|
-
|
|
976
|
+
Returns:
|
|
977
|
+
str: The SVG output for the secondary planet grid.
|
|
978
|
+
"""
|
|
979
|
+
svg_output = ""
|
|
980
|
+
end_of_line = "</g>"
|
|
962
981
|
|
|
963
|
-
|
|
964
|
-
|
|
982
|
+
if chart_type == "Transit":
|
|
983
|
+
svg_output += (
|
|
984
|
+
f'<g transform="translate(820, 15)">' # 620+200, 30-15
|
|
985
|
+
f'<text style="fill:{text_color}; font-size: 14px;">{second_subject_name}</text>'
|
|
986
|
+
)
|
|
987
|
+
elif chart_type == "Return":
|
|
988
|
+
svg_output += (
|
|
989
|
+
f'<g transform="translate(870, 15)">' # 620+250, 30-15
|
|
990
|
+
f'<text style="fill:{text_color}; font-size: 14px;">{planets_and_houses_grid_title} {second_subject_name}</text>'
|
|
991
|
+
)
|
|
992
|
+
else:
|
|
993
|
+
svg_output += (
|
|
994
|
+
f'<g transform="translate(870, 15)">' # 620+250, 30-15
|
|
995
|
+
f'<text style="fill:{text_color}; font-size: 14px;">{planets_and_houses_grid_title} {second_subject_name}</text>'
|
|
996
|
+
)
|
|
997
|
+
|
|
998
|
+
svg_output += end_of_line
|
|
999
|
+
|
|
1000
|
+
line_height = 10
|
|
1001
|
+
offset = 250
|
|
1002
|
+
|
|
1003
|
+
for i, t_planet in enumerate(second_subject_available_kerykeion_celestial_points):
|
|
1004
|
+
second_decoded_name = get_decoded_kerykeion_celestial_point_name(t_planet["name"], celestial_point_language)
|
|
1005
|
+
svg_output += (
|
|
1006
|
+
f'<g transform="translate({620 + offset},{30 + line_height})">' # Added the 620,30 offset
|
|
1007
|
+
f'<text text-anchor="end" style="fill:{text_color}; font-size: 10px;">{second_decoded_name}</text>'
|
|
1008
|
+
f'<g transform="translate(5,-8)"><use transform="scale(0.4)" xlink:href="#{t_planet["name"]}" /></g>'
|
|
1009
|
+
f'<text text-anchor="start" x="19" style="fill:{text_color}; font-size: 10px;">{convert_decimal_to_degree_string(t_planet["position"])}</text>'
|
|
1010
|
+
f'<g transform="translate(60,-8)"><use transform="scale(0.3)" xlink:href="#{t_planet["sign"]}" /></g>'
|
|
1011
|
+
)
|
|
1012
|
+
|
|
1013
|
+
if t_planet["retrograde"]:
|
|
1014
|
+
svg_output += '<g transform="translate(74,-6)"><use transform="scale(.5)" xlink:href="#retrograde" /></g>'
|
|
1015
|
+
|
|
1016
|
+
svg_output += end_of_line
|
|
1017
|
+
line_height += 14 # Using fixed offset_between_lines value
|
|
965
1018
|
|
|
966
1019
|
return svg_output
|
|
967
1020
|
|
|
@@ -994,7 +1047,7 @@ def draw_transit_aspect_grid(
|
|
|
994
1047
|
y_start = y_indent
|
|
995
1048
|
|
|
996
1049
|
# Filter active planets
|
|
997
|
-
active_planets = [planet for planet in available_planets if planet
|
|
1050
|
+
active_planets = [planet for planet in available_planets if planet["is_active"]]
|
|
998
1051
|
|
|
999
1052
|
# Reverse the list of active planets for the first iteration
|
|
1000
1053
|
reversed_planets = active_planets[::-1]
|
|
@@ -1040,3 +1093,480 @@ def draw_transit_aspect_grid(
|
|
|
1040
1093
|
svg_output += f'<use x="{x_aspect - box_size + 1}" y="{y_aspect + 1}" xlink:href="#orb{aspect["aspect_degrees"]}" />'
|
|
1041
1094
|
|
|
1042
1095
|
return svg_output
|
|
1096
|
+
|
|
1097
|
+
|
|
1098
|
+
def format_location_string(location: str, max_length: int = 35) -> str:
|
|
1099
|
+
"""
|
|
1100
|
+
Format a location string to ensure it fits within a specified maximum length.
|
|
1101
|
+
|
|
1102
|
+
If the location is longer than max_length, it attempts to shorten by using only
|
|
1103
|
+
the first and last parts separated by commas. If still too long, it truncates
|
|
1104
|
+
and adds ellipsis.
|
|
1105
|
+
|
|
1106
|
+
Args:
|
|
1107
|
+
location: The original location string
|
|
1108
|
+
max_length: Maximum allowed length for the output string (default: 35)
|
|
1109
|
+
|
|
1110
|
+
Returns:
|
|
1111
|
+
Formatted location string that fits within max_length
|
|
1112
|
+
"""
|
|
1113
|
+
if len(location) > max_length:
|
|
1114
|
+
split_location = location.split(",")
|
|
1115
|
+
if len(split_location) > 1:
|
|
1116
|
+
shortened = split_location[0] + ", " + split_location[-1]
|
|
1117
|
+
if len(shortened) > max_length:
|
|
1118
|
+
return shortened[:max_length] + "..."
|
|
1119
|
+
return shortened
|
|
1120
|
+
else:
|
|
1121
|
+
return location[:max_length] + "..."
|
|
1122
|
+
return location
|
|
1123
|
+
|
|
1124
|
+
|
|
1125
|
+
def format_datetime_with_timezone(iso_datetime_string: str) -> str:
|
|
1126
|
+
"""
|
|
1127
|
+
Format an ISO datetime string with a custom format that includes properly formatted timezone.
|
|
1128
|
+
|
|
1129
|
+
Args:
|
|
1130
|
+
iso_datetime_string: ISO formatted datetime string
|
|
1131
|
+
|
|
1132
|
+
Returns:
|
|
1133
|
+
Formatted datetime string with properly formatted timezone offset (HH:MM)
|
|
1134
|
+
"""
|
|
1135
|
+
dt = datetime.datetime.fromisoformat(iso_datetime_string)
|
|
1136
|
+
custom_format = dt.strftime('%Y-%m-%d %H:%M [%z]')
|
|
1137
|
+
custom_format = custom_format[:-3] + ':' + custom_format[-3:]
|
|
1138
|
+
|
|
1139
|
+
return custom_format
|
|
1140
|
+
|
|
1141
|
+
|
|
1142
|
+
def calculate_element_points(
|
|
1143
|
+
planets_settings: list[KerykeionSettingsCelestialPointModel],
|
|
1144
|
+
celestial_points_names: list[str],
|
|
1145
|
+
subject: Union[AstrologicalSubjectModel, CompositeSubjectModel, PlanetReturnModel],
|
|
1146
|
+
):
|
|
1147
|
+
"""
|
|
1148
|
+
Calculate elemental point totals based on planetary positions.
|
|
1149
|
+
|
|
1150
|
+
Args:
|
|
1151
|
+
planets_settings (list): List of planet configuration dictionaries
|
|
1152
|
+
celestial_points_names (list): List of celestial point names to process
|
|
1153
|
+
subject: Astrological subject with get() method for accessing planet data
|
|
1154
|
+
|
|
1155
|
+
Returns:
|
|
1156
|
+
dict: Dictionary with element point totals for 'fire', 'earth', 'air', and 'water'
|
|
1157
|
+
"""
|
|
1158
|
+
ZODIAC = (
|
|
1159
|
+
{"name": "Ari", "element": "fire"},
|
|
1160
|
+
{"name": "Tau", "element": "earth"},
|
|
1161
|
+
{"name": "Gem", "element": "air"},
|
|
1162
|
+
{"name": "Can", "element": "water"},
|
|
1163
|
+
{"name": "Leo", "element": "fire"},
|
|
1164
|
+
{"name": "Vir", "element": "earth"},
|
|
1165
|
+
{"name": "Lib", "element": "air"},
|
|
1166
|
+
{"name": "Sco", "element": "water"},
|
|
1167
|
+
{"name": "Sag", "element": "fire"},
|
|
1168
|
+
{"name": "Cap", "element": "earth"},
|
|
1169
|
+
{"name": "Aqu", "element": "air"},
|
|
1170
|
+
{"name": "Pis", "element": "water"},
|
|
1171
|
+
)
|
|
1172
|
+
|
|
1173
|
+
# Initialize element point totals
|
|
1174
|
+
element_totals = {
|
|
1175
|
+
"fire": 0.0,
|
|
1176
|
+
"earth": 0.0,
|
|
1177
|
+
"air": 0.0,
|
|
1178
|
+
"water": 0.0
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
# Make list of the points sign
|
|
1182
|
+
points_sign = [subject.get(planet).sign_num for planet in celestial_points_names]
|
|
1183
|
+
|
|
1184
|
+
for i in range(len(planets_settings)):
|
|
1185
|
+
# Add points to appropriate element
|
|
1186
|
+
element = ZODIAC[points_sign[i]]["element"]
|
|
1187
|
+
element_totals[element] += planets_settings[i]["element_points"]
|
|
1188
|
+
|
|
1189
|
+
return element_totals
|
|
1190
|
+
|
|
1191
|
+
|
|
1192
|
+
def calculate_synastry_element_points(
|
|
1193
|
+
planets_settings: list[KerykeionSettingsCelestialPointModel],
|
|
1194
|
+
celestial_points_names: list[str],
|
|
1195
|
+
subject1: AstrologicalSubjectModel,
|
|
1196
|
+
subject2: AstrologicalSubjectModel,
|
|
1197
|
+
):
|
|
1198
|
+
"""
|
|
1199
|
+
Calculate elemental point totals for both subjects in a synastry chart.
|
|
1200
|
+
|
|
1201
|
+
Args:
|
|
1202
|
+
planets_settings (list): List of planet configuration dictionaries
|
|
1203
|
+
celestial_points_names (list): List of celestial point names to process
|
|
1204
|
+
subject1: First astrological subject with get() method for accessing planet data
|
|
1205
|
+
subject2: Second astrological subject with get() method for accessing planet data
|
|
1206
|
+
|
|
1207
|
+
Returns:
|
|
1208
|
+
dict: Dictionary with element point totals as percentages, where the sum equals 100%
|
|
1209
|
+
"""
|
|
1210
|
+
ZODIAC = (
|
|
1211
|
+
{"name": "Ari", "element": "fire"},
|
|
1212
|
+
{"name": "Tau", "element": "earth"},
|
|
1213
|
+
{"name": "Gem", "element": "air"},
|
|
1214
|
+
{"name": "Can", "element": "water"},
|
|
1215
|
+
{"name": "Leo", "element": "fire"},
|
|
1216
|
+
{"name": "Vir", "element": "earth"},
|
|
1217
|
+
{"name": "Lib", "element": "air"},
|
|
1218
|
+
{"name": "Sco", "element": "water"},
|
|
1219
|
+
{"name": "Sag", "element": "fire"},
|
|
1220
|
+
{"name": "Cap", "element": "earth"},
|
|
1221
|
+
{"name": "Aqu", "element": "air"},
|
|
1222
|
+
{"name": "Pis", "element": "water"},
|
|
1223
|
+
)
|
|
1224
|
+
|
|
1225
|
+
# Initialize combined element point totals
|
|
1226
|
+
combined_totals = {
|
|
1227
|
+
"fire": 0.0,
|
|
1228
|
+
"earth": 0.0,
|
|
1229
|
+
"air": 0.0,
|
|
1230
|
+
"water": 0.0
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
# Make list of the points sign for both subjects
|
|
1234
|
+
subject1_points_sign = [subject1.get(planet).sign_num for planet in celestial_points_names]
|
|
1235
|
+
subject2_points_sign = [subject2.get(planet).sign_num for planet in celestial_points_names]
|
|
1236
|
+
|
|
1237
|
+
# Calculate element points for subject 1
|
|
1238
|
+
for i in range(len(planets_settings)):
|
|
1239
|
+
# Add points to appropriate element
|
|
1240
|
+
element1 = ZODIAC[subject1_points_sign[i]]["element"]
|
|
1241
|
+
combined_totals[element1] += planets_settings[i]["element_points"]
|
|
1242
|
+
|
|
1243
|
+
# Calculate element points for subject 2
|
|
1244
|
+
for i in range(len(planets_settings)):
|
|
1245
|
+
# Add points to appropriate element
|
|
1246
|
+
element2 = ZODIAC[subject2_points_sign[i]]["element"]
|
|
1247
|
+
combined_totals[element2] += planets_settings[i]["element_points"]
|
|
1248
|
+
|
|
1249
|
+
# Calculate total points across all elements
|
|
1250
|
+
total_points = sum(combined_totals.values())
|
|
1251
|
+
|
|
1252
|
+
# Convert to percentages (total = 100%)
|
|
1253
|
+
if total_points > 0:
|
|
1254
|
+
for element in combined_totals:
|
|
1255
|
+
combined_totals[element] = (combined_totals[element] / total_points) * 100.0
|
|
1256
|
+
|
|
1257
|
+
return combined_totals
|
|
1258
|
+
|
|
1259
|
+
|
|
1260
|
+
def draw_house_comparison_grid(
|
|
1261
|
+
house_comparison: "HouseComparisonModel",
|
|
1262
|
+
celestial_point_language: KerykeionLanguageCelestialPointModel,
|
|
1263
|
+
active_points: list[AstrologicalPoint],
|
|
1264
|
+
*,
|
|
1265
|
+
points_owner_subject_number: Literal[1, 2] = 1,
|
|
1266
|
+
text_color: str = "var(--kerykeion-color-neutral-content)",
|
|
1267
|
+
house_position_comparison_label: str = "House Position Comparison",
|
|
1268
|
+
return_point_label: str = "Return Point",
|
|
1269
|
+
return_label: str = "Return",
|
|
1270
|
+
radix_label: str = "Radix",
|
|
1271
|
+
x_position: int = 1030,
|
|
1272
|
+
y_position: int = 0,
|
|
1273
|
+
) -> str:
|
|
1274
|
+
"""
|
|
1275
|
+
Generate SVG code for displaying a comparison of points across houses between two charts.
|
|
1276
|
+
|
|
1277
|
+
Parameters:
|
|
1278
|
+
- house_comparison ("HouseComparisonModel"): Model containing house comparison data,
|
|
1279
|
+
including first_subject_name, second_subject_name, and points in houses.
|
|
1280
|
+
- celestial_point_language (KerykeionLanguageCelestialPointModel): Language model for celestial points
|
|
1281
|
+
- active_celestial_points (list[KerykeionPointModel]): List of active celestial points to display
|
|
1282
|
+
- text_color (str): Color for the text elements
|
|
1283
|
+
|
|
1284
|
+
Returns:
|
|
1285
|
+
- str: SVG code for the house comparison grid.
|
|
1286
|
+
"""
|
|
1287
|
+
if points_owner_subject_number == 1:
|
|
1288
|
+
comparison_data = house_comparison.first_points_in_second_houses
|
|
1289
|
+
else:
|
|
1290
|
+
comparison_data = house_comparison.second_points_in_first_houses
|
|
1291
|
+
|
|
1292
|
+
svg_output = f'<g transform="translate({x_position},{y_position})">'
|
|
1293
|
+
|
|
1294
|
+
# Add title
|
|
1295
|
+
svg_output += f'<text text-anchor="start" x="0" y="-15" style="fill:{text_color}; font-size: 14px;">{house_position_comparison_label}</text>'
|
|
1296
|
+
|
|
1297
|
+
# Add column headers
|
|
1298
|
+
line_increment = 10
|
|
1299
|
+
svg_output += (
|
|
1300
|
+
f'<g transform="translate(0,{line_increment})">'
|
|
1301
|
+
f'<text text-anchor="start" x="0" style="fill:{text_color}; font-weight: bold; font-size: 10px;">{return_point_label}</text>'
|
|
1302
|
+
f'<text text-anchor="start" x="77" style="fill:{text_color}; font-weight: bold; font-size: 10px;">{return_label}</text>'
|
|
1303
|
+
f'<text text-anchor="start" x="132" style="fill:{text_color}; font-weight: bold; font-size: 10px;">{radix_label}</text>'
|
|
1304
|
+
f'</g>'
|
|
1305
|
+
)
|
|
1306
|
+
line_increment += 15
|
|
1307
|
+
|
|
1308
|
+
# Create a dictionary to store all points by name for combined display
|
|
1309
|
+
all_points_by_name = {}
|
|
1310
|
+
|
|
1311
|
+
for point in comparison_data:
|
|
1312
|
+
# Only process points that are active
|
|
1313
|
+
if point.point_name in active_points and point.point_name not in all_points_by_name:
|
|
1314
|
+
all_points_by_name[point.point_name] = {
|
|
1315
|
+
"name": point.point_name,
|
|
1316
|
+
"secondary_house": point.projected_house_number,
|
|
1317
|
+
"native_house": point.point_owner_house_number
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
# Display all points organized by name
|
|
1321
|
+
for name, point_data in all_points_by_name.items():
|
|
1322
|
+
native_house = point_data.get("native_house", "-")
|
|
1323
|
+
secondary_house = point_data.get("secondary_house", "-")
|
|
1324
|
+
|
|
1325
|
+
svg_output += (
|
|
1326
|
+
f'<g transform="translate(0,{line_increment})">'
|
|
1327
|
+
f'<g transform="translate(0,-9)"><use transform="scale(0.4)" xlink:href="#{name}" /></g>'
|
|
1328
|
+
f'<text text-anchor="start" x="15" style="fill:{text_color}; font-size: 10px;">{get_decoded_kerykeion_celestial_point_name(name, celestial_point_language)}</text>'
|
|
1329
|
+
f'<text text-anchor="start" x="90" style="fill:{text_color}; font-size: 10px;">{native_house}</text>'
|
|
1330
|
+
f'<text text-anchor="start" x="140" style="fill:{text_color}; font-size: 10px;">{secondary_house}</text>'
|
|
1331
|
+
f'</g>'
|
|
1332
|
+
)
|
|
1333
|
+
line_increment += 12
|
|
1334
|
+
|
|
1335
|
+
svg_output += "</g>"
|
|
1336
|
+
|
|
1337
|
+
return svg_output
|
|
1338
|
+
|
|
1339
|
+
|
|
1340
|
+
def draw_single_house_comparison_grid(
|
|
1341
|
+
house_comparison: "HouseComparisonModel",
|
|
1342
|
+
celestial_point_language: KerykeionLanguageCelestialPointModel,
|
|
1343
|
+
active_points: list[AstrologicalPoint],
|
|
1344
|
+
*,
|
|
1345
|
+
points_owner_subject_number: Literal[1, 2] = 1,
|
|
1346
|
+
text_color: str = "var(--kerykeion-color-neutral-content)",
|
|
1347
|
+
house_position_comparison_label: str = "House Position Comparison",
|
|
1348
|
+
return_point_label: str = "Return Point",
|
|
1349
|
+
natal_house_label: str = "Natal House",
|
|
1350
|
+
x_position: int = 1030,
|
|
1351
|
+
y_position: int = 0,
|
|
1352
|
+
) -> str:
|
|
1353
|
+
"""
|
|
1354
|
+
Generate SVG code for displaying celestial points and their house positions.
|
|
1355
|
+
|
|
1356
|
+
Parameters:
|
|
1357
|
+
- house_comparison ("HouseComparisonModel"): Model containing house comparison data,
|
|
1358
|
+
including first_subject_name, second_subject_name, and points in houses.
|
|
1359
|
+
- celestial_point_language (KerykeionLanguageCelestialPointModel): Language model for celestial points
|
|
1360
|
+
- active_points (list[AstrologicalPoint]): List of active celestial points to display
|
|
1361
|
+
- points_owner_subject_number (Literal[1, 2]): Which subject's points to display (1 for first, 2 for second)
|
|
1362
|
+
- text_color (str): Color for the text elements
|
|
1363
|
+
- house_position_comparison_label (str): Label for the house position comparison grid
|
|
1364
|
+
- return_point_label (str): Label for the return point column
|
|
1365
|
+
- house_position_label (str): Label for the house position column
|
|
1366
|
+
- x_position (int): X position for the grid
|
|
1367
|
+
- y_position (int): Y position for the grid
|
|
1368
|
+
|
|
1369
|
+
Returns:
|
|
1370
|
+
- str: SVG code for the house position grid.
|
|
1371
|
+
"""
|
|
1372
|
+
if points_owner_subject_number == 1:
|
|
1373
|
+
comparison_data = house_comparison.first_points_in_second_houses
|
|
1374
|
+
else:
|
|
1375
|
+
comparison_data = house_comparison.second_points_in_first_houses
|
|
1376
|
+
|
|
1377
|
+
svg_output = f'<g transform="translate({x_position},{y_position})">'
|
|
1378
|
+
|
|
1379
|
+
# Add title
|
|
1380
|
+
svg_output += f'<text text-anchor="start" x="0" y="-15" style="fill:{text_color}; font-size: 14px;">{house_position_comparison_label}</text>'
|
|
1381
|
+
|
|
1382
|
+
# Add column headers
|
|
1383
|
+
line_increment = 10
|
|
1384
|
+
svg_output += (
|
|
1385
|
+
f'<g transform="translate(0,{line_increment})">'
|
|
1386
|
+
f'<text text-anchor="start" x="0" style="fill:{text_color}; font-weight: bold; font-size: 10px;">{return_point_label}</text>'
|
|
1387
|
+
f'<text text-anchor="start" x="77" style="fill:{text_color}; font-weight: bold; font-size: 10px;">{natal_house_label}</text>'
|
|
1388
|
+
f'</g>'
|
|
1389
|
+
)
|
|
1390
|
+
line_increment += 15
|
|
1391
|
+
|
|
1392
|
+
# Create a dictionary to store all points by name for combined display
|
|
1393
|
+
all_points_by_name = {}
|
|
1394
|
+
|
|
1395
|
+
for point in comparison_data:
|
|
1396
|
+
# Only process points that are active
|
|
1397
|
+
if point.point_name in active_points and point.point_name not in all_points_by_name:
|
|
1398
|
+
all_points_by_name[point.point_name] = {
|
|
1399
|
+
"name": point.point_name,
|
|
1400
|
+
"house": point.projected_house_number
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
# Display all points organized by name
|
|
1404
|
+
for name, point_data in all_points_by_name.items():
|
|
1405
|
+
house = point_data.get("house", "-")
|
|
1406
|
+
|
|
1407
|
+
svg_output += (
|
|
1408
|
+
f'<g transform="translate(0,{line_increment})">'
|
|
1409
|
+
f'<g transform="translate(0,-9)"><use transform="scale(0.4)" xlink:href="#{name}" /></g>'
|
|
1410
|
+
f'<text text-anchor="start" x="15" style="fill:{text_color}; font-size: 10px;">{get_decoded_kerykeion_celestial_point_name(name, celestial_point_language)}</text>'
|
|
1411
|
+
f'<text text-anchor="start" x="90" style="fill:{text_color}; font-size: 10px;">{house}</text>'
|
|
1412
|
+
f'</g>'
|
|
1413
|
+
)
|
|
1414
|
+
line_increment += 12
|
|
1415
|
+
|
|
1416
|
+
svg_output += "</g>"
|
|
1417
|
+
|
|
1418
|
+
return svg_output
|
|
1419
|
+
|
|
1420
|
+
|
|
1421
|
+
def makeLunarPhase(degrees_between_sun_and_moon: float, latitude: float) -> str:
|
|
1422
|
+
"""
|
|
1423
|
+
Generate SVG representation of lunar phase.
|
|
1424
|
+
|
|
1425
|
+
Parameters:
|
|
1426
|
+
- degrees_between_sun_and_moon (float): Angle between sun and moon in degrees
|
|
1427
|
+
- latitude (float): Observer's latitude for correct orientation
|
|
1428
|
+
|
|
1429
|
+
Returns:
|
|
1430
|
+
- str: SVG representation of lunar phase
|
|
1431
|
+
"""
|
|
1432
|
+
# Calculate parameters for the lunar phase visualization
|
|
1433
|
+
params = calculate_moon_phase_chart_params(degrees_between_sun_and_moon, latitude)
|
|
1434
|
+
|
|
1435
|
+
# Extract the calculated values
|
|
1436
|
+
lunar_phase_circle_center_x = params["circle_center_x"]
|
|
1437
|
+
lunar_phase_circle_radius = params["circle_radius"]
|
|
1438
|
+
lunar_phase_rotate = params["lunar_phase_rotate"]
|
|
1439
|
+
|
|
1440
|
+
# Generate the SVG for the lunar phase
|
|
1441
|
+
svg = (
|
|
1442
|
+
f'<g transform="rotate({lunar_phase_rotate} 20 10)">\n'
|
|
1443
|
+
f' <defs>\n'
|
|
1444
|
+
f' <clipPath id="moonPhaseCutOffCircle">\n'
|
|
1445
|
+
f' <circle cx="20" cy="10" r="10" />\n'
|
|
1446
|
+
f' </clipPath>\n'
|
|
1447
|
+
f' </defs>\n'
|
|
1448
|
+
f' <circle cx="20" cy="10" r="10" style="fill: var(--kerykeion-chart-color-lunar-phase-0)" />\n'
|
|
1449
|
+
f' <circle cx="{lunar_phase_circle_center_x}" cy="10" r="{lunar_phase_circle_radius}" style="fill: var(--kerykeion-chart-color-lunar-phase-1)" clip-path="url(#moonPhaseCutOffCircle)" />\n'
|
|
1450
|
+
f' <circle cx="20" cy="10" r="10" style="fill: none; stroke: var(--kerykeion-chart-color-lunar-phase-0); stroke-width: 0.5px; stroke-opacity: 0.5" />\n'
|
|
1451
|
+
f'</g>'
|
|
1452
|
+
)
|
|
1453
|
+
|
|
1454
|
+
return svg
|
|
1455
|
+
|
|
1456
|
+
|
|
1457
|
+
def calculate_quality_points(
|
|
1458
|
+
planets_settings: list[KerykeionSettingsCelestialPointModel],
|
|
1459
|
+
celestial_points_names: list[str],
|
|
1460
|
+
subject: Union[AstrologicalSubjectModel, CompositeSubjectModel, PlanetReturnModel],
|
|
1461
|
+
):
|
|
1462
|
+
"""
|
|
1463
|
+
Calculate quality point totals based on planetary positions.
|
|
1464
|
+
|
|
1465
|
+
Args:
|
|
1466
|
+
planets_settings (list): List of planet configuration dictionaries
|
|
1467
|
+
celestial_points_names (list): List of celestial point names to process
|
|
1468
|
+
subject: Astrological subject with get() method for accessing planet data
|
|
1469
|
+
planet_in_zodiac_extra_points (int): Extra points awarded for planets in their home sign
|
|
1470
|
+
|
|
1471
|
+
Returns:
|
|
1472
|
+
dict: Dictionary with quality point totals for 'cardinal', 'fixed', and 'mutable'
|
|
1473
|
+
"""
|
|
1474
|
+
ZODIAC = (
|
|
1475
|
+
{"name": "Ari", "quality": "cardinal"},
|
|
1476
|
+
{"name": "Tau", "quality": "fixed"},
|
|
1477
|
+
{"name": "Gem", "quality": "mutable"},
|
|
1478
|
+
{"name": "Can", "quality": "cardinal"},
|
|
1479
|
+
{"name": "Leo", "quality": "fixed"},
|
|
1480
|
+
{"name": "Vir", "quality": "mutable"},
|
|
1481
|
+
{"name": "Lib", "quality": "cardinal"},
|
|
1482
|
+
{"name": "Sco", "quality": "fixed"},
|
|
1483
|
+
{"name": "Sag", "quality": "mutable"},
|
|
1484
|
+
{"name": "Cap", "quality": "cardinal"},
|
|
1485
|
+
{"name": "Aqu", "quality": "fixed"},
|
|
1486
|
+
{"name": "Pis", "quality": "mutable"},
|
|
1487
|
+
)
|
|
1488
|
+
|
|
1489
|
+
# Initialize quality point totals
|
|
1490
|
+
quality_totals = {
|
|
1491
|
+
"cardinal": 0.0,
|
|
1492
|
+
"fixed": 0.0,
|
|
1493
|
+
"mutable": 0.0
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
# Make list of the points sign
|
|
1497
|
+
points_sign = [subject.get(planet).sign_num for planet in celestial_points_names]
|
|
1498
|
+
|
|
1499
|
+
for i in range(len(planets_settings)):
|
|
1500
|
+
# Add points to appropriate quality
|
|
1501
|
+
quality = ZODIAC[points_sign[i]]["quality"]
|
|
1502
|
+
quality_totals[quality] += planets_settings[i]["element_points"]
|
|
1503
|
+
|
|
1504
|
+
return quality_totals
|
|
1505
|
+
|
|
1506
|
+
|
|
1507
|
+
def calculate_synastry_quality_points(
|
|
1508
|
+
planets_settings: list[KerykeionSettingsCelestialPointModel],
|
|
1509
|
+
celestial_points_names: list[str],
|
|
1510
|
+
subject1: AstrologicalSubjectModel,
|
|
1511
|
+
subject2: AstrologicalSubjectModel,
|
|
1512
|
+
):
|
|
1513
|
+
"""
|
|
1514
|
+
Calculate quality point totals for both subjects in a synastry chart.
|
|
1515
|
+
|
|
1516
|
+
Args:
|
|
1517
|
+
planets_settings (list): List of planet configuration dictionaries
|
|
1518
|
+
celestial_points_names (list): List of celestial point names to process
|
|
1519
|
+
subject1: First astrological subject with get() method for accessing planet data
|
|
1520
|
+
subject2: Second astrological subject with get() method for accessing planet data
|
|
1521
|
+
|
|
1522
|
+
Returns:
|
|
1523
|
+
dict: Dictionary with quality point totals as percentages, where the sum equals 100%
|
|
1524
|
+
"""
|
|
1525
|
+
ZODIAC = (
|
|
1526
|
+
{"name": "Ari", "quality": "cardinal"},
|
|
1527
|
+
{"name": "Tau", "quality": "fixed"},
|
|
1528
|
+
{"name": "Gem", "quality": "mutable"},
|
|
1529
|
+
{"name": "Can", "quality": "cardinal"},
|
|
1530
|
+
{"name": "Leo", "quality": "fixed"},
|
|
1531
|
+
{"name": "Vir", "quality": "mutable"},
|
|
1532
|
+
{"name": "Lib", "quality": "cardinal"},
|
|
1533
|
+
{"name": "Sco", "quality": "fixed"},
|
|
1534
|
+
{"name": "Sag", "quality": "mutable"},
|
|
1535
|
+
{"name": "Cap", "quality": "cardinal"},
|
|
1536
|
+
{"name": "Aqu", "quality": "fixed"},
|
|
1537
|
+
{"name": "Pis", "quality": "mutable"},
|
|
1538
|
+
)
|
|
1539
|
+
|
|
1540
|
+
# Initialize combined quality point totals
|
|
1541
|
+
combined_totals = {
|
|
1542
|
+
"cardinal": 0.0,
|
|
1543
|
+
"fixed": 0.0,
|
|
1544
|
+
"mutable": 0.0
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
# Make list of the points sign for both subjects
|
|
1548
|
+
subject1_points_sign = [subject1.get(planet).sign_num for planet in celestial_points_names]
|
|
1549
|
+
subject2_points_sign = [subject2.get(planet).sign_num for planet in celestial_points_names]
|
|
1550
|
+
|
|
1551
|
+
# Calculate quality points for subject 1
|
|
1552
|
+
for i in range(len(planets_settings)):
|
|
1553
|
+
# Add points to appropriate quality
|
|
1554
|
+
quality1 = ZODIAC[subject1_points_sign[i]]["quality"]
|
|
1555
|
+
combined_totals[quality1] += planets_settings[i]["element_points"]
|
|
1556
|
+
|
|
1557
|
+
# Calculate quality points for subject 2
|
|
1558
|
+
for i in range(len(planets_settings)):
|
|
1559
|
+
# Add points to appropriate quality
|
|
1560
|
+
quality2 = ZODIAC[subject2_points_sign[i]]["quality"]
|
|
1561
|
+
combined_totals[quality2] += planets_settings[i]["element_points"]
|
|
1562
|
+
|
|
1563
|
+
# Calculate total points across all qualities
|
|
1564
|
+
total_points = sum(combined_totals.values())
|
|
1565
|
+
|
|
1566
|
+
# Convert to percentages (total = 100%)
|
|
1567
|
+
if total_points > 0:
|
|
1568
|
+
for quality in combined_totals:
|
|
1569
|
+
combined_totals[quality] = (combined_totals[quality] / total_points) * 100.0
|
|
1570
|
+
|
|
1571
|
+
return combined_totals
|
|
1572
|
+
|