kerykeion 5.0.0a12__py3-none-any.whl → 5.0.0b1__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 +30 -6
- kerykeion/aspects/aspects_factory.py +40 -24
- kerykeion/aspects/aspects_utils.py +75 -6
- kerykeion/astrological_subject_factory.py +377 -226
- kerykeion/backword.py +680 -0
- kerykeion/chart_data_factory.py +484 -0
- kerykeion/charts/{kerykeion_chart_svg.py → chart_drawer.py} +612 -438
- kerykeion/charts/charts_utils.py +135 -94
- kerykeion/charts/draw_planets.py +38 -28
- kerykeion/charts/templates/aspect_grid_only.xml +188 -17
- kerykeion/charts/templates/chart.xml +104 -28
- kerykeion/charts/templates/wheel_only.xml +195 -24
- kerykeion/charts/themes/classic.css +11 -0
- kerykeion/charts/themes/dark-high-contrast.css +11 -0
- kerykeion/charts/themes/dark.css +11 -0
- kerykeion/charts/themes/light.css +11 -0
- kerykeion/charts/themes/strawberry.css +10 -0
- kerykeion/composite_subject_factory.py +4 -4
- kerykeion/ephemeris_data_factory.py +12 -9
- kerykeion/house_comparison/__init__.py +0 -3
- kerykeion/house_comparison/house_comparison_factory.py +3 -3
- kerykeion/house_comparison/house_comparison_utils.py +3 -4
- kerykeion/planetary_return_factory.py +8 -4
- kerykeion/relationship_score_factory.py +3 -3
- kerykeion/report.py +748 -67
- kerykeion/{kr_types → schemas}/__init__.py +44 -4
- kerykeion/schemas/chart_template_model.py +340 -0
- kerykeion/{kr_types → schemas}/kr_literals.py +7 -3
- kerykeion/{kr_types → schemas}/kr_models.py +220 -11
- kerykeion/{kr_types → schemas}/settings_models.py +7 -7
- kerykeion/settings/config_constants.py +75 -8
- kerykeion/settings/kerykeion_settings.py +1 -1
- kerykeion/settings/kr.config.json +130 -40
- kerykeion/settings/legacy/legacy_celestial_points_settings.py +8 -8
- kerykeion/sweph/ast136/s136108s.se1 +0 -0
- kerykeion/sweph/ast136/s136199s.se1 +0 -0
- kerykeion/sweph/ast136/s136472s.se1 +0 -0
- kerykeion/sweph/ast28/se28978s.se1 +0 -0
- kerykeion/sweph/ast50/se50000s.se1 +0 -0
- kerykeion/sweph/ast90/se90377s.se1 +0 -0
- kerykeion/sweph/ast90/se90482s.se1 +0 -0
- kerykeion/transits_time_range_factory.py +7 -7
- kerykeion/utilities.py +61 -38
- {kerykeion-5.0.0a12.dist-info → kerykeion-5.0.0b1.dist-info}/METADATA +507 -120
- kerykeion-5.0.0b1.dist-info/RECORD +58 -0
- kerykeion/house_comparison/house_comparison_models.py +0 -76
- kerykeion/kr_types/chart_types.py +0 -106
- kerykeion-5.0.0a12.dist-info/RECORD +0 -50
- /kerykeion/{kr_types → schemas}/kerykeion_exception.py +0 -0
- {kerykeion-5.0.0a12.dist-info → kerykeion-5.0.0b1.dist-info}/WHEEL +0 -0
- {kerykeion-5.0.0a12.dist-info → kerykeion-5.0.0b1.dist-info}/licenses/LICENSE +0 -0
kerykeion/charts/charts_utils.py
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import math
|
|
2
2
|
import datetime
|
|
3
|
-
from kerykeion.
|
|
4
|
-
from kerykeion.
|
|
3
|
+
from kerykeion.schemas import KerykeionException, ChartType
|
|
4
|
+
from kerykeion.schemas.kr_literals import AstrologicalPoint
|
|
5
5
|
from typing import Union, Literal
|
|
6
|
-
from kerykeion.
|
|
7
|
-
from kerykeion.
|
|
8
|
-
from kerykeion.house_comparison import HouseComparisonModel
|
|
6
|
+
from kerykeion.schemas.kr_models import AspectModel, KerykeionPointModel, CompositeSubjectModel, PlanetReturnModel, AstrologicalSubjectModel, HouseComparisonModel
|
|
7
|
+
from kerykeion.schemas.settings_models import KerykeionLanguageCelestialPointModel, KerykeionSettingsCelestialPointModel
|
|
9
8
|
|
|
10
9
|
|
|
11
10
|
|
|
@@ -189,7 +188,7 @@ def draw_zodiac_slice(
|
|
|
189
188
|
# pie slices
|
|
190
189
|
offset = 360 - seventh_house_degree_ut
|
|
191
190
|
# check transit
|
|
192
|
-
if chart_type == "Transit" or chart_type == "Synastry" or chart_type == "
|
|
191
|
+
if chart_type == "Transit" or chart_type == "Synastry" or chart_type == "DualReturnChart":
|
|
193
192
|
dropin: Union[int, float] = 0
|
|
194
193
|
else:
|
|
195
194
|
dropin = c1
|
|
@@ -198,7 +197,7 @@ def draw_zodiac_slice(
|
|
|
198
197
|
# symbols
|
|
199
198
|
offset = offset + 15
|
|
200
199
|
# check transit
|
|
201
|
-
if chart_type == "Transit" or chart_type == "Synastry" or chart_type == "
|
|
200
|
+
if chart_type == "Transit" or chart_type == "Synastry" or chart_type == "DualReturnChart":
|
|
202
201
|
dropin = 54
|
|
203
202
|
else:
|
|
204
203
|
dropin = 18 + c1
|
|
@@ -287,7 +286,7 @@ def draw_aspect_line(
|
|
|
287
286
|
y2 = sliceToY(0, ar, second_offset) + (r - ar)
|
|
288
287
|
|
|
289
288
|
return (
|
|
290
|
-
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'<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"]}" kr:orb="{aspect["orbit"]}" kr:aspectdegrees="{aspect["aspect_degrees"]}" kr:planetsdiff="{aspect["diff"]}" kr:aspectmovement="{aspect["aspect_movement"]}">'
|
|
291
290
|
f'<line class="aspect" x1="{x1}" y1="{y1}" x2="{x2}" y2="{y2}" style="stroke: {color}; stroke-width: 1; stroke-opacity: .9;"/>'
|
|
292
291
|
f"</g>"
|
|
293
292
|
)
|
|
@@ -419,7 +418,7 @@ def draw_first_circle(
|
|
|
419
418
|
Returns:
|
|
420
419
|
str: The SVG path of the first circle.
|
|
421
420
|
"""
|
|
422
|
-
if chart_type == "Synastry" or chart_type == "Transit" or chart_type == "
|
|
421
|
+
if chart_type == "Synastry" or chart_type == "Transit" or chart_type == "DualReturnChart":
|
|
423
422
|
return f'<circle cx="{r}" cy="{r}" r="{r - 36}" style="fill: none; stroke: {stroke_color}; stroke-width: 1px; stroke-opacity:.4;" />'
|
|
424
423
|
else:
|
|
425
424
|
if c1 is None:
|
|
@@ -462,7 +461,7 @@ def draw_second_circle(
|
|
|
462
461
|
str: The SVG path of the second circle.
|
|
463
462
|
"""
|
|
464
463
|
|
|
465
|
-
if chart_type == "Synastry" or chart_type == "Transit" or chart_type == "
|
|
464
|
+
if chart_type == "Synastry" or chart_type == "Transit" or chart_type == "DualReturnChart":
|
|
466
465
|
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" />'
|
|
467
466
|
|
|
468
467
|
else:
|
|
@@ -492,7 +491,7 @@ def draw_third_circle(
|
|
|
492
491
|
Returns:
|
|
493
492
|
- str: The SVG element as a string.
|
|
494
493
|
"""
|
|
495
|
-
if chart_type in {"Synastry", "Transit", "
|
|
494
|
+
if chart_type in {"Synastry", "Transit", "DualReturnChart"}:
|
|
496
495
|
# For Synastry and Transit charts, use a fixed radius adjustment of 160
|
|
497
496
|
return f'<circle cx="{radius}" cy="{radius}" r="{radius - 160}" style="fill: {fill_color}; fill-opacity:.8; stroke: {stroke_color}; stroke-width: 1px" />'
|
|
498
497
|
|
|
@@ -572,6 +571,7 @@ def draw_houses_cusps_and_text_number(
|
|
|
572
571
|
chart_type: ChartType,
|
|
573
572
|
second_subject_houses_list: Union[list[KerykeionPointModel], None] = None,
|
|
574
573
|
transit_house_cusp_color: Union[str, None] = None,
|
|
574
|
+
external_view: bool = False,
|
|
575
575
|
) -> str:
|
|
576
576
|
"""
|
|
577
577
|
Draws the houses cusps and text numbers for a given chart type.
|
|
@@ -589,9 +589,10 @@ def draw_houses_cusps_and_text_number(
|
|
|
589
589
|
- chart_type: Type of the chart (e.g., Transit, Synastry).
|
|
590
590
|
- second_subject_houses_list: List of house for the second subject (optional).
|
|
591
591
|
- transit_house_cusp_color: Color for transit house cusps (optional).
|
|
592
|
+
- external_view: Whether to use external view mode for positioning (optional).
|
|
592
593
|
|
|
593
594
|
Returns:
|
|
594
|
-
- A string containing
|
|
595
|
+
- A string containing SVG elements for house cusps and numbers.
|
|
595
596
|
"""
|
|
596
597
|
|
|
597
598
|
path = ""
|
|
@@ -599,7 +600,7 @@ def draw_houses_cusps_and_text_number(
|
|
|
599
600
|
|
|
600
601
|
for i in range(xr):
|
|
601
602
|
# Determine offsets based on chart type
|
|
602
|
-
dropin, roff, t_roff = (160, 72, 36) if chart_type in ["Transit", "Synastry", "
|
|
603
|
+
dropin, roff, t_roff = (160, 72, 36) if chart_type in ["Transit", "Synastry", "DualReturnChart"] else (c3, c1, False)
|
|
603
604
|
|
|
604
605
|
# Calculate the offset for the current house cusp
|
|
605
606
|
offset = (int(first_subject_houses_list[int(xr / 2)].abs_pos) / -1) + int(first_subject_houses_list[i].abs_pos)
|
|
@@ -621,7 +622,7 @@ def draw_houses_cusps_and_text_number(
|
|
|
621
622
|
i, standard_house_cusp_color
|
|
622
623
|
)
|
|
623
624
|
|
|
624
|
-
if chart_type in ["Transit", "Synastry", "
|
|
625
|
+
if chart_type in ["Transit", "Synastry", "DualReturnChart"]:
|
|
625
626
|
if second_subject_houses_list is None or transit_house_cusp_color is None:
|
|
626
627
|
raise KerykeionException("second_subject_houses_list_ut or transit_house_cusp_color is None")
|
|
627
628
|
|
|
@@ -651,17 +652,21 @@ def draw_houses_cusps_and_text_number(
|
|
|
651
652
|
|
|
652
653
|
# Add the house cusp line for the second subject
|
|
653
654
|
stroke_opacity = "0" if chart_type == "Transit" else ".3"
|
|
654
|
-
path += '<g kr:node="Cusp">'
|
|
655
|
+
path += f'<g kr:node="Cusp" kr:absoluteposition="{second_subject_houses_list[i].abs_pos}" kr:signposition="{second_subject_houses_list[i].position}" kr:sing="{second_subject_houses_list[i].sign}" kr:slug="{second_subject_houses_list[i].name}">'
|
|
655
656
|
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};'/>"
|
|
656
657
|
path += "</g>"
|
|
657
658
|
|
|
658
|
-
# Adjust dropin based on chart type
|
|
659
|
-
|
|
659
|
+
# Adjust dropin based on chart type and external view
|
|
660
|
+
dropin_map = {"Transit": 84, "Synastry": 84, "DualReturnChart": 84}
|
|
661
|
+
if external_view:
|
|
662
|
+
dropin = 100
|
|
663
|
+
else:
|
|
664
|
+
dropin = dropin_map.get(chart_type, 48)
|
|
660
665
|
xtext = sliceToX(0, (r - dropin), text_offset) + dropin
|
|
661
666
|
ytext = sliceToY(0, (r - dropin), text_offset) + dropin
|
|
662
667
|
|
|
663
668
|
# Add the house cusp line for the first subject
|
|
664
|
-
path += '<g kr:node="Cusp">'
|
|
669
|
+
path += f'<g kr:node="Cusp" kr:absoluteposition="{first_subject_houses_list[i].abs_pos}" kr:signposition="{first_subject_houses_list[i].position}" kr:sing="{first_subject_houses_list[i].sign}" kr:slug="{first_subject_houses_list[i].name}">'
|
|
665
670
|
path += f'<line x1="{x1}" y1="{y1}" x2="{x2}" y2="{y2}" style="stroke: {linecolor}; stroke-width: 1px; stroke-dasharray:3,2; stroke-opacity:.4;"/>'
|
|
666
671
|
path += "</g>"
|
|
667
672
|
|
|
@@ -708,9 +713,12 @@ def draw_transit_aspect_list(
|
|
|
708
713
|
if aspects_list and isinstance(aspects_list[0], dict):
|
|
709
714
|
aspects_list = [AspectModel(**aspect) for aspect in aspects_list] # type: ignore
|
|
710
715
|
|
|
716
|
+
# Type narrowing: at this point aspects_list contains AspectModel instances
|
|
717
|
+
typed_aspects_list: list[AspectModel] = aspects_list # type: ignore
|
|
718
|
+
|
|
711
719
|
inner_path = ""
|
|
712
720
|
|
|
713
|
-
for i, aspect in enumerate(
|
|
721
|
+
for i, aspect in enumerate(typed_aspects_list):
|
|
714
722
|
# Calculate which column this aspect belongs in
|
|
715
723
|
current_column = i // aspects_per_column
|
|
716
724
|
|
|
@@ -722,12 +730,22 @@ def draw_transit_aspect_list(
|
|
|
722
730
|
vertical_position = current_line * line_height
|
|
723
731
|
|
|
724
732
|
# Special handling for many aspects - if we exceed max_columns
|
|
733
|
+
# Bottom-align the overflow columns so the list starts from the bottom
|
|
725
734
|
if current_column >= max_columns:
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
735
|
+
overflow_total = len(aspects_list) - (aspects_per_column * max_columns)
|
|
736
|
+
if overflow_total > 0:
|
|
737
|
+
# Index within the overflow sequence (beyond the first row of columns)
|
|
738
|
+
overflow_index = i - (aspects_per_column * max_columns)
|
|
739
|
+
# Which overflow column we are in (0-based)
|
|
740
|
+
overflow_col_idx = overflow_index // aspects_per_column
|
|
741
|
+
# How many items go into this overflow column
|
|
742
|
+
items_in_this_column = min(
|
|
743
|
+
aspects_per_column,
|
|
744
|
+
max(0, overflow_total - (overflow_col_idx * aspects_per_column)),
|
|
745
|
+
)
|
|
746
|
+
# Compute extra top offset (in lines) to bottom-align this column
|
|
747
|
+
top_offset_lines = max(0, aspects_per_column - items_in_this_column)
|
|
748
|
+
vertical_position = (top_offset_lines + (i % aspects_per_column)) * line_height
|
|
731
749
|
|
|
732
750
|
inner_path += f'<g transform="translate({horizontal_position},{vertical_position})">'
|
|
733
751
|
|
|
@@ -824,7 +842,7 @@ def draw_main_house_grid(
|
|
|
824
842
|
main_subject_houses_list: list[KerykeionPointModel],
|
|
825
843
|
house_cusp_generale_name_label: str = "Cusp",
|
|
826
844
|
text_color: str = "#000000",
|
|
827
|
-
x_position: int =
|
|
845
|
+
x_position: int = 750,
|
|
828
846
|
y_position: int = 30,
|
|
829
847
|
) -> str:
|
|
830
848
|
"""
|
|
@@ -862,7 +880,7 @@ def draw_secondary_house_grid(
|
|
|
862
880
|
secondary_subject_houses_list: list[KerykeionPointModel],
|
|
863
881
|
house_cusp_generale_name_label: str = "Cusp",
|
|
864
882
|
text_color: str = "#000000",
|
|
865
|
-
x_position: int =
|
|
883
|
+
x_position: int = 1015,
|
|
866
884
|
y_position: int = 30,
|
|
867
885
|
) -> str:
|
|
868
886
|
"""
|
|
@@ -903,57 +921,63 @@ def draw_main_planet_grid(
|
|
|
903
921
|
chart_type: ChartType,
|
|
904
922
|
celestial_point_language: KerykeionLanguageCelestialPointModel,
|
|
905
923
|
text_color: str = "#000000",
|
|
924
|
+
x_position: int = 645,
|
|
925
|
+
y_position: int = 0,
|
|
906
926
|
) -> str:
|
|
907
927
|
"""
|
|
908
|
-
|
|
928
|
+
Draw the planet grid (main subject) and optional title.
|
|
929
|
+
|
|
930
|
+
The entire output is wrapped in a single SVG group `<g>` so the
|
|
931
|
+
whole block can be repositioned by changing the group transform.
|
|
909
932
|
|
|
910
933
|
Args:
|
|
911
|
-
planets_and_houses_grid_title
|
|
912
|
-
subject_name
|
|
913
|
-
available_kerykeion_celestial_points
|
|
914
|
-
chart_type
|
|
915
|
-
celestial_point_language
|
|
916
|
-
text_color
|
|
934
|
+
planets_and_houses_grid_title: Title prefix to show for eligible chart types.
|
|
935
|
+
subject_name: Subject name to append to the title.
|
|
936
|
+
available_kerykeion_celestial_points: Celestial points to render in the grid.
|
|
937
|
+
chart_type: Chart type identifier (Literal string).
|
|
938
|
+
celestial_point_language: Language model for celestial point decoding.
|
|
939
|
+
text_color: Text color for labels (default: "#000000").
|
|
940
|
+
x_position: X translation applied to the outer `<g>` (default: 620).
|
|
941
|
+
y_position: Y translation applied to the outer `<g>` (default: 0).
|
|
917
942
|
|
|
918
943
|
Returns:
|
|
919
|
-
|
|
944
|
+
SVG string for the main planet grid wrapped in a `<g>`.
|
|
920
945
|
"""
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
946
|
+
# Layout constants (kept identical to previous behavior)
|
|
947
|
+
BASE_Y = 30
|
|
948
|
+
HEADER_Y = 15 # Title baseline inside the wrapper
|
|
949
|
+
LINE_START = 10
|
|
950
|
+
LINE_STEP = 14
|
|
925
951
|
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
)
|
|
932
|
-
elif chart_type == "Transit":
|
|
933
|
-
svg_output += (
|
|
934
|
-
f'<g transform="translate(620, 15)">' # Added the 620,30 offset (adjusted for -15)
|
|
935
|
-
f'<text style="fill:{text_color}; font-size: 14px;">{planets_and_houses_grid_title} {subject_name}</text>'
|
|
936
|
-
f'</g>'
|
|
937
|
-
)
|
|
938
|
-
elif chart_type == "Return":
|
|
952
|
+
# Wrap everything inside a single group so position can be changed once
|
|
953
|
+
svg_output = f'<g transform="translate({x_position},{y_position})">'
|
|
954
|
+
|
|
955
|
+
# Add title only for specific chart types
|
|
956
|
+
if chart_type in ("Synastry", "Transit", "DualReturnChart"):
|
|
939
957
|
svg_output += (
|
|
940
|
-
f'<g transform="translate(
|
|
958
|
+
f'<g transform="translate(0, {HEADER_Y})">'
|
|
941
959
|
f'<text style="fill:{text_color}; font-size: 14px;">{planets_and_houses_grid_title} {subject_name}</text>'
|
|
942
960
|
f'</g>'
|
|
943
961
|
)
|
|
944
|
-
|
|
945
|
-
|
|
962
|
+
|
|
963
|
+
line_height = LINE_START
|
|
964
|
+
offset = 0
|
|
946
965
|
|
|
947
966
|
end_of_line = "</g>"
|
|
948
967
|
|
|
949
968
|
for i, planet in enumerate(available_kerykeion_celestial_points):
|
|
950
|
-
|
|
951
|
-
|
|
969
|
+
# Start a second column at item 23 (index 22)
|
|
970
|
+
if i == 20:
|
|
971
|
+
line_height = LINE_START
|
|
952
972
|
offset = -125
|
|
953
973
|
|
|
954
|
-
decoded_name = get_decoded_kerykeion_celestial_point_name(
|
|
974
|
+
decoded_name = get_decoded_kerykeion_celestial_point_name(
|
|
975
|
+
planet["name"],
|
|
976
|
+
celestial_point_language,
|
|
977
|
+
)
|
|
978
|
+
|
|
955
979
|
svg_output += (
|
|
956
|
-
f'<g transform="translate({
|
|
980
|
+
f'<g transform="translate({offset},{BASE_Y + line_height})">'
|
|
957
981
|
f'<text text-anchor="end" style="fill:{text_color}; font-size: 10px;">{decoded_name}</text>'
|
|
958
982
|
f'<g transform="translate(5,-8)"><use transform="scale(0.4)" xlink:href="#{planet["name"]}" /></g>'
|
|
959
983
|
f'<text text-anchor="start" x="19" style="fill:{text_color}; font-size: 10px;">{convert_decimal_to_degree_string(planet["position"])}</text>'
|
|
@@ -964,7 +988,10 @@ def draw_main_planet_grid(
|
|
|
964
988
|
svg_output += '<g transform="translate(74,-6)"><use transform="scale(.5)" xlink:href="#retrograde" /></g>'
|
|
965
989
|
|
|
966
990
|
svg_output += end_of_line
|
|
967
|
-
line_height +=
|
|
991
|
+
line_height += LINE_STEP
|
|
992
|
+
|
|
993
|
+
# Close the wrapper group
|
|
994
|
+
svg_output += "</g>"
|
|
968
995
|
|
|
969
996
|
return svg_output
|
|
970
997
|
|
|
@@ -976,49 +1003,61 @@ def draw_secondary_planet_grid(
|
|
|
976
1003
|
chart_type: ChartType,
|
|
977
1004
|
celestial_point_language: KerykeionLanguageCelestialPointModel,
|
|
978
1005
|
text_color: str = "#000000",
|
|
1006
|
+
x_position: int = 910,
|
|
1007
|
+
y_position: int = 0,
|
|
979
1008
|
) -> str:
|
|
980
1009
|
"""
|
|
981
|
-
|
|
1010
|
+
Draw the planet grid for the secondary subject and its title.
|
|
1011
|
+
|
|
1012
|
+
The entire output is wrapped in a single SVG group `<g>` so the
|
|
1013
|
+
whole block can be repositioned by changing the group transform.
|
|
982
1014
|
|
|
983
1015
|
Args:
|
|
984
|
-
planets_and_houses_grid_title
|
|
985
|
-
second_subject_name
|
|
986
|
-
second_subject_available_kerykeion_celestial_points
|
|
987
|
-
chart_type
|
|
988
|
-
celestial_point_language
|
|
989
|
-
text_color
|
|
1016
|
+
planets_and_houses_grid_title: Title prefix (used except for Transit charts).
|
|
1017
|
+
second_subject_name: Name of the secondary subject.
|
|
1018
|
+
second_subject_available_kerykeion_celestial_points: Celestial points to render for the secondary subject.
|
|
1019
|
+
chart_type: Chart type identifier (Literal string).
|
|
1020
|
+
celestial_point_language: Language model for celestial point decoding.
|
|
1021
|
+
text_color: Text color for labels (default: "#000000").
|
|
1022
|
+
x_position: X translation applied to the outer `<g>` (default: 870).
|
|
1023
|
+
y_position: Y translation applied to the outer `<g>` (default: 0).
|
|
990
1024
|
|
|
991
1025
|
Returns:
|
|
992
|
-
|
|
1026
|
+
SVG string for the secondary planet grid wrapped in a `<g>`.
|
|
993
1027
|
"""
|
|
994
|
-
|
|
995
|
-
|
|
1028
|
+
# Layout constants
|
|
1029
|
+
BASE_Y = 30
|
|
1030
|
+
HEADER_Y = 15
|
|
1031
|
+
LINE_START = 10
|
|
1032
|
+
LINE_STEP = 14
|
|
996
1033
|
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
f'<g transform="translate(820, 15)">' # 620+200, 30-15
|
|
1000
|
-
f'<text style="fill:{text_color}; font-size: 14px;">{second_subject_name}</text>'
|
|
1001
|
-
)
|
|
1002
|
-
elif chart_type == "Return":
|
|
1003
|
-
svg_output += (
|
|
1004
|
-
f'<g transform="translate(870, 15)">' # 620+250, 30-15
|
|
1005
|
-
f'<text style="fill:{text_color}; font-size: 14px;">{planets_and_houses_grid_title} {second_subject_name}</text>'
|
|
1006
|
-
)
|
|
1007
|
-
else:
|
|
1008
|
-
svg_output += (
|
|
1009
|
-
f'<g transform="translate(870, 15)">' # 620+250, 30-15
|
|
1010
|
-
f'<text style="fill:{text_color}; font-size: 14px;">{planets_and_houses_grid_title} {second_subject_name}</text>'
|
|
1011
|
-
)
|
|
1034
|
+
# Open wrapper group
|
|
1035
|
+
svg_output = f'<g transform="translate({x_position},{y_position})">'
|
|
1012
1036
|
|
|
1013
|
-
|
|
1037
|
+
# Title content and its relative x offset
|
|
1038
|
+
header_text = (
|
|
1039
|
+
second_subject_name if chart_type == "Transit"
|
|
1040
|
+
else f"{planets_and_houses_grid_title} {second_subject_name}"
|
|
1041
|
+
)
|
|
1042
|
+
header_x_offset = -50 if chart_type == "Transit" else 0
|
|
1043
|
+
|
|
1044
|
+
svg_output += (
|
|
1045
|
+
f'<g transform="translate({header_x_offset}, {HEADER_Y})">'
|
|
1046
|
+
f'<text style="fill:{text_color}; font-size: 14px;">{header_text}</text>'
|
|
1047
|
+
f'</g>'
|
|
1048
|
+
)
|
|
1014
1049
|
|
|
1015
|
-
|
|
1016
|
-
|
|
1050
|
+
# Grid rows
|
|
1051
|
+
line_height = LINE_START
|
|
1052
|
+
end_of_line = "</g>"
|
|
1017
1053
|
|
|
1018
|
-
for
|
|
1019
|
-
second_decoded_name = get_decoded_kerykeion_celestial_point_name(
|
|
1054
|
+
for t_planet in second_subject_available_kerykeion_celestial_points:
|
|
1055
|
+
second_decoded_name = get_decoded_kerykeion_celestial_point_name(
|
|
1056
|
+
t_planet["name"],
|
|
1057
|
+
celestial_point_language,
|
|
1058
|
+
)
|
|
1020
1059
|
svg_output += (
|
|
1021
|
-
f'<g transform="translate(
|
|
1060
|
+
f'<g transform="translate(0,{BASE_Y + line_height})">'
|
|
1022
1061
|
f'<text text-anchor="end" style="fill:{text_color}; font-size: 10px;">{second_decoded_name}</text>'
|
|
1023
1062
|
f'<g transform="translate(5,-8)"><use transform="scale(0.4)" xlink:href="#{t_planet["name"]}" /></g>'
|
|
1024
1063
|
f'<text text-anchor="start" x="19" style="fill:{text_color}; font-size: 10px;">{convert_decimal_to_degree_string(t_planet["position"])}</text>'
|
|
@@ -1029,7 +1068,10 @@ def draw_secondary_planet_grid(
|
|
|
1029
1068
|
svg_output += '<g transform="translate(74,-6)"><use transform="scale(.5)" xlink:href="#retrograde" /></g>'
|
|
1030
1069
|
|
|
1031
1070
|
svg_output += end_of_line
|
|
1032
|
-
line_height +=
|
|
1071
|
+
line_height += LINE_STEP
|
|
1072
|
+
|
|
1073
|
+
# Close wrapper group
|
|
1074
|
+
svg_output += "</g>"
|
|
1033
1075
|
|
|
1034
1076
|
return svg_output
|
|
1035
1077
|
|
|
@@ -1281,9 +1323,9 @@ def draw_house_comparison_grid(
|
|
|
1281
1323
|
text_color: str = "var(--kerykeion-color-neutral-content)",
|
|
1282
1324
|
house_position_comparison_label: str = "House Position Comparison",
|
|
1283
1325
|
return_point_label: str = "Return Point",
|
|
1284
|
-
return_label: str = "
|
|
1326
|
+
return_label: str = "DualReturnChart",
|
|
1285
1327
|
radix_label: str = "Radix",
|
|
1286
|
-
x_position: int =
|
|
1328
|
+
x_position: int = 1100,
|
|
1287
1329
|
y_position: int = 0,
|
|
1288
1330
|
) -> str:
|
|
1289
1331
|
"""
|
|
@@ -1584,4 +1626,3 @@ def calculate_synastry_quality_points(
|
|
|
1584
1626
|
combined_totals[quality] = (combined_totals[quality] / total_points) * 100.0
|
|
1585
1627
|
|
|
1586
1628
|
return combined_totals
|
|
1587
|
-
|
kerykeion/charts/draw_planets.py
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
from kerykeion.charts.charts_utils import degreeDiff, sliceToX, sliceToY, convert_decimal_to_degree_string
|
|
2
|
-
from kerykeion.
|
|
3
|
-
from kerykeion.
|
|
4
|
-
from kerykeion.
|
|
2
|
+
from kerykeion.schemas import KerykeionException, ChartType, KerykeionPointModel
|
|
3
|
+
from kerykeion.schemas.settings_models import KerykeionSettingsCelestialPointModel
|
|
4
|
+
from kerykeion.schemas.kr_literals import Houses
|
|
5
5
|
import logging
|
|
6
|
-
from typing import Union, get_args
|
|
6
|
+
from typing import Union, get_args, List, Optional
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
def draw_planets(
|
|
@@ -15,13 +15,15 @@ def draw_planets(
|
|
|
15
15
|
main_subject_seventh_house_degree_ut: Union[int, float],
|
|
16
16
|
chart_type: ChartType,
|
|
17
17
|
second_subject_available_kerykeion_celestial_points: Union[list[KerykeionPointModel], None] = None,
|
|
18
|
+
external_view: bool = False,
|
|
18
19
|
) -> str:
|
|
19
20
|
"""
|
|
20
21
|
Draws the planets on an astrological chart based on the provided parameters.
|
|
21
22
|
|
|
22
23
|
This function calculates positions, handles overlap of celestial points, and draws SVG
|
|
23
24
|
elements for each planet/point on the chart. It supports different chart types including
|
|
24
|
-
natal charts, transits, synastry, and returns.
|
|
25
|
+
natal charts, transits, synastry, and returns. For single-subject charts (Natal), it
|
|
26
|
+
can render planets in external view mode using the external_view parameter.
|
|
25
27
|
|
|
26
28
|
Args:
|
|
27
29
|
radius (Union[int, float]): The radius of the chart in pixels.
|
|
@@ -30,10 +32,13 @@ def draw_planets(
|
|
|
30
32
|
third_circle_radius (Union[int, float]): Radius of the third circle in the chart.
|
|
31
33
|
main_subject_first_house_degree_ut (Union[int, float]): Degree of the first house for the main subject.
|
|
32
34
|
main_subject_seventh_house_degree_ut (Union[int, float]): Degree of the seventh house for the main subject.
|
|
33
|
-
chart_type (ChartType): Type of the chart (e.g., "Transit", "Synastry", "
|
|
35
|
+
chart_type (ChartType): Type of the chart (e.g., "Transit", "Synastry", "DualReturnChart", "Natal").
|
|
34
36
|
second_subject_available_kerykeion_celestial_points (Union[list[KerykeionPointModel], None], optional):
|
|
35
37
|
List of celestial points for the second subject, required for "Transit", "Synastry", or "Return" charts.
|
|
36
38
|
Defaults to None.
|
|
39
|
+
external_view (bool, optional):
|
|
40
|
+
Whether to render planets in external view mode (planets on outer ring with connecting lines).
|
|
41
|
+
Only applicable for single-subject charts. Defaults to False.
|
|
37
42
|
|
|
38
43
|
Raises:
|
|
39
44
|
KerykeionException: If secondary celestial points are required but not provided.
|
|
@@ -43,7 +48,7 @@ def draw_planets(
|
|
|
43
48
|
"""
|
|
44
49
|
# Constants and initialization
|
|
45
50
|
PLANET_GROUPING_THRESHOLD = 3.4 # Distance threshold to consider planets as grouped
|
|
46
|
-
TRANSIT_RING_EXCLUDE_POINTS_NAMES = get_args(Houses)
|
|
51
|
+
TRANSIT_RING_EXCLUDE_POINTS_NAMES: List[str] = list(get_args(Houses))
|
|
47
52
|
output = ""
|
|
48
53
|
|
|
49
54
|
# -----------------------------------------------------------
|
|
@@ -64,12 +69,13 @@ def draw_planets(
|
|
|
64
69
|
secondary_points_abs_positions = []
|
|
65
70
|
secondary_points_rel_positions = []
|
|
66
71
|
if chart_type == "Transit" or chart_type == "Synastry" or chart_type == "Return":
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
72
|
+
if second_subject_available_kerykeion_celestial_points is not None:
|
|
73
|
+
secondary_points_abs_positions = [
|
|
74
|
+
planet.abs_pos for planet in second_subject_available_kerykeion_celestial_points
|
|
75
|
+
]
|
|
76
|
+
secondary_points_rel_positions = [
|
|
77
|
+
planet.position for planet in second_subject_available_kerykeion_celestial_points
|
|
78
|
+
]
|
|
73
79
|
|
|
74
80
|
# -----------------------------------------------------------
|
|
75
81
|
# 2. Create position lookup dictionary for main celestial points
|
|
@@ -86,9 +92,9 @@ def draw_planets(
|
|
|
86
92
|
# -----------------------------------------------------------
|
|
87
93
|
# 3. Identify groups of celestial points that are close to each other
|
|
88
94
|
# -----------------------------------------------------------
|
|
89
|
-
point_groups = []
|
|
95
|
+
point_groups: List[List[List[Union[int, float, str]]]] = []
|
|
90
96
|
is_group_open = False
|
|
91
|
-
planets_by_position = [None] * len(position_index_map)
|
|
97
|
+
planets_by_position: List[Optional[List[Union[int, float]]]] = [None] * len(position_index_map)
|
|
92
98
|
|
|
93
99
|
# Process each celestial point to find groups
|
|
94
100
|
for position_idx, abs_position in enumerate(sorted_positions):
|
|
@@ -142,7 +148,7 @@ def draw_planets(
|
|
|
142
148
|
# -----------------------------------------------------------
|
|
143
149
|
# 4. Calculate position adjustments to avoid overlapping
|
|
144
150
|
# -----------------------------------------------------------
|
|
145
|
-
position_adjustments = [0] * len(available_planets_setting)
|
|
151
|
+
position_adjustments: List[float] = [0.0] * len(available_planets_setting)
|
|
146
152
|
|
|
147
153
|
# Process each group to calculate position adjustments
|
|
148
154
|
for group in point_groups:
|
|
@@ -159,11 +165,12 @@ def draw_planets(
|
|
|
159
165
|
# -----------------------------------------------------------
|
|
160
166
|
# 5. Draw main celestial points
|
|
161
167
|
# -----------------------------------------------------------
|
|
168
|
+
adjusted_offset = 0.0 # Initialize for use outside loop
|
|
162
169
|
for position_idx, abs_position in enumerate(sorted_positions):
|
|
163
170
|
point_idx = position_index_map[abs_position]
|
|
164
171
|
|
|
165
172
|
# Determine radius based on chart type and point type
|
|
166
|
-
point_radius = _determine_point_radius(point_idx, chart_type, bool(position_idx % 2))
|
|
173
|
+
point_radius = _determine_point_radius(point_idx, chart_type, bool(position_idx % 2), external_view)
|
|
167
174
|
|
|
168
175
|
# Calculate position offset for the point
|
|
169
176
|
adjusted_offset = _calculate_point_offset(
|
|
@@ -191,11 +198,11 @@ def draw_planets(
|
|
|
191
198
|
scale_factor = 0.8
|
|
192
199
|
elif chart_type == "Return":
|
|
193
200
|
scale_factor = 0.8
|
|
194
|
-
elif
|
|
201
|
+
elif external_view:
|
|
195
202
|
scale_factor = 0.8
|
|
196
203
|
|
|
197
|
-
# Draw connecting lines for
|
|
198
|
-
if
|
|
204
|
+
# Draw connecting lines for external view
|
|
205
|
+
if external_view:
|
|
199
206
|
output = _draw_external_natal_lines(
|
|
200
207
|
output,
|
|
201
208
|
radius,
|
|
@@ -327,7 +334,8 @@ def _handle_multi_point_group(group: list, position_adjustments: list, threshold
|
|
|
327
334
|
def _determine_point_radius(
|
|
328
335
|
point_idx: int,
|
|
329
336
|
chart_type: str,
|
|
330
|
-
is_alternate_position: bool
|
|
337
|
+
is_alternate_position: bool,
|
|
338
|
+
external_view: bool = False
|
|
331
339
|
) -> int:
|
|
332
340
|
"""
|
|
333
341
|
Determine the radius for placing a celestial point based on its type and chart type.
|
|
@@ -336,6 +344,7 @@ def _determine_point_radius(
|
|
|
336
344
|
point_idx (int): Index of the celestial point.
|
|
337
345
|
chart_type (str): Type of the chart.
|
|
338
346
|
is_alternate_position (bool): Whether to use alternate positioning.
|
|
347
|
+
external_view (bool): Whether external view is enabled.
|
|
339
348
|
|
|
340
349
|
Returns:
|
|
341
350
|
int: Radius value for the point.
|
|
@@ -359,10 +368,10 @@ def _determine_point_radius(
|
|
|
359
368
|
else:
|
|
360
369
|
return 110 if is_alternate_position else 130
|
|
361
370
|
else:
|
|
362
|
-
# Default natal chart and
|
|
371
|
+
# Default natal chart and external view handling
|
|
363
372
|
# if 22 < point_idx < 27 it is asc,mc,dsc,ic (angles of chart)
|
|
364
373
|
amin, bmin, cmin = 0, 0, 0
|
|
365
|
-
if
|
|
374
|
+
if external_view:
|
|
366
375
|
amin = 74 - 10
|
|
367
376
|
bmin = 94 - 10
|
|
368
377
|
cmin = 40 - 10
|
|
@@ -402,7 +411,7 @@ def _draw_external_natal_lines(
|
|
|
402
411
|
color: str,
|
|
403
412
|
) -> str:
|
|
404
413
|
"""
|
|
405
|
-
Draw connecting lines for
|
|
414
|
+
Draw connecting lines for external view charts.
|
|
406
415
|
|
|
407
416
|
Creates two line segments: one from the circle to the original position,
|
|
408
417
|
and another from the original position to the adjusted position.
|
|
@@ -450,7 +459,7 @@ def _generate_point_svg(point_details: KerykeionPointModel, x: float, y: float,
|
|
|
450
459
|
Returns:
|
|
451
460
|
str: SVG element for the celestial point.
|
|
452
461
|
"""
|
|
453
|
-
svg = f'<g kr:node="ChartPoint" kr:house="{point_details["house"]}" kr:sign="{point_details["sign"]}" '
|
|
462
|
+
svg = f'<g kr:node="ChartPoint" kr:house="{point_details["house"]}" kr:sign="{point_details["sign"]}" kr:absoluteposition="{point_details["abs_pos"]}" kr:signposition="{point_details["position"]}" '
|
|
454
463
|
svg += f'kr:slug="{point_details["name"]}" transform="translate(-{12 * scale},-{12 * scale}) scale({scale})">'
|
|
455
464
|
svg += f'<use x="{x * (1/scale)}" y="{y * (1/scale)}" xlink:href="#{point_name}" />'
|
|
456
465
|
svg += "</g>"
|
|
@@ -488,7 +497,7 @@ def _draw_secondary_points(
|
|
|
488
497
|
str: Updated SVG output with added secondary points.
|
|
489
498
|
"""
|
|
490
499
|
# Initialize position adjustments for grouped points
|
|
491
|
-
position_adjustments = {i: 0 for i in range(len(points_settings))}
|
|
500
|
+
position_adjustments: dict[int, float] = {i: 0.0 for i in range(len(points_settings))}
|
|
492
501
|
|
|
493
502
|
# Map absolute position to point index
|
|
494
503
|
position_index_map = {}
|
|
@@ -501,7 +510,7 @@ def _draw_secondary_points(
|
|
|
501
510
|
sorted_positions = sorted(position_index_map.keys())
|
|
502
511
|
|
|
503
512
|
# Find groups of points that are close to each other
|
|
504
|
-
point_groups = []
|
|
513
|
+
point_groups: List[List[int]] = []
|
|
505
514
|
in_group = False
|
|
506
515
|
|
|
507
516
|
for pos_idx, abs_position in enumerate(sorted_positions):
|
|
@@ -536,7 +545,7 @@ def _draw_secondary_points(
|
|
|
536
545
|
position_adjustments[group[1]] = 1.0
|
|
537
546
|
elif len(group) == 3:
|
|
538
547
|
position_adjustments[group[0]] = -1.5
|
|
539
|
-
position_adjustments[group[1]] = 0
|
|
548
|
+
position_adjustments[group[1]] = 0.0
|
|
540
549
|
position_adjustments[group[2]] = 1.5
|
|
541
550
|
elif len(group) == 4:
|
|
542
551
|
position_adjustments[group[0]] = -2.0
|
|
@@ -546,6 +555,7 @@ def _draw_secondary_points(
|
|
|
546
555
|
|
|
547
556
|
# Draw each secondary point
|
|
548
557
|
alternate_position = False
|
|
558
|
+
point_idx = 0 # Initialize for use outside loop
|
|
549
559
|
|
|
550
560
|
for pos_idx, abs_position in enumerate(sorted_positions):
|
|
551
561
|
point_idx = position_index_map[abs_position]
|