kerykeion 5.0.0a12__py3-none-any.whl → 5.0.0b2__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} +688 -440
- kerykeion/charts/charts_utils.py +157 -94
- kerykeion/charts/draw_planets.py +38 -28
- kerykeion/charts/templates/aspect_grid_only.xml +188 -17
- kerykeion/charts/templates/chart.xml +153 -47
- 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 +367 -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 +132 -42
- 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.0b2.dist-info}/METADATA +507 -120
- kerykeion-5.0.0b2.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.0b2.dist-info}/WHEEL +0 -0
- {kerykeion-5.0.0a12.dist-info → kerykeion-5.0.0b2.dist-info}/licenses/LICENSE +0 -0
kerykeion/charts/charts_utils.py
CHANGED
|
@@ -1,11 +1,35 @@
|
|
|
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
|
-
|
|
6
|
+
from kerykeion.schemas.kr_models import AspectModel, KerykeionPointModel, CompositeSubjectModel, PlanetReturnModel, AstrologicalSubjectModel, HouseComparisonModel
|
|
7
|
+
from kerykeion.schemas.settings_models import KerykeionLanguageCelestialPointModel, KerykeionSettingsCelestialPointModel
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
_SECOND_COLUMN_THRESHOLD = 20
|
|
11
|
+
_THIRD_COLUMN_THRESHOLD = 28
|
|
12
|
+
_FOURTH_COLUMN_THRESHOLD = 36
|
|
13
|
+
_GRID_COLUMN_WIDTH = 125
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _planet_grid_layout_position(index: int) -> tuple[int, int]:
|
|
17
|
+
"""Return horizontal offset and row index for planet grids."""
|
|
18
|
+
if index < _SECOND_COLUMN_THRESHOLD:
|
|
19
|
+
column = 0
|
|
20
|
+
row = index
|
|
21
|
+
elif index < _THIRD_COLUMN_THRESHOLD:
|
|
22
|
+
column = 1
|
|
23
|
+
row = index - _SECOND_COLUMN_THRESHOLD
|
|
24
|
+
elif index < _FOURTH_COLUMN_THRESHOLD:
|
|
25
|
+
column = 2
|
|
26
|
+
row = index - _THIRD_COLUMN_THRESHOLD
|
|
27
|
+
else:
|
|
28
|
+
column = 3
|
|
29
|
+
row = index - _FOURTH_COLUMN_THRESHOLD
|
|
30
|
+
|
|
31
|
+
offset = -(_GRID_COLUMN_WIDTH * column)
|
|
32
|
+
return offset, row
|
|
9
33
|
|
|
10
34
|
|
|
11
35
|
|
|
@@ -189,7 +213,7 @@ def draw_zodiac_slice(
|
|
|
189
213
|
# pie slices
|
|
190
214
|
offset = 360 - seventh_house_degree_ut
|
|
191
215
|
# check transit
|
|
192
|
-
if chart_type == "Transit" or chart_type == "Synastry" or chart_type == "
|
|
216
|
+
if chart_type == "Transit" or chart_type == "Synastry" or chart_type == "DualReturnChart":
|
|
193
217
|
dropin: Union[int, float] = 0
|
|
194
218
|
else:
|
|
195
219
|
dropin = c1
|
|
@@ -198,7 +222,7 @@ def draw_zodiac_slice(
|
|
|
198
222
|
# symbols
|
|
199
223
|
offset = offset + 15
|
|
200
224
|
# check transit
|
|
201
|
-
if chart_type == "Transit" or chart_type == "Synastry" or chart_type == "
|
|
225
|
+
if chart_type == "Transit" or chart_type == "Synastry" or chart_type == "DualReturnChart":
|
|
202
226
|
dropin = 54
|
|
203
227
|
else:
|
|
204
228
|
dropin = 18 + c1
|
|
@@ -287,7 +311,7 @@ def draw_aspect_line(
|
|
|
287
311
|
y2 = sliceToY(0, ar, second_offset) + (r - ar)
|
|
288
312
|
|
|
289
313
|
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"]}">'
|
|
314
|
+
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
315
|
f'<line class="aspect" x1="{x1}" y1="{y1}" x2="{x2}" y2="{y2}" style="stroke: {color}; stroke-width: 1; stroke-opacity: .9;"/>'
|
|
292
316
|
f"</g>"
|
|
293
317
|
)
|
|
@@ -419,7 +443,7 @@ def draw_first_circle(
|
|
|
419
443
|
Returns:
|
|
420
444
|
str: The SVG path of the first circle.
|
|
421
445
|
"""
|
|
422
|
-
if chart_type == "Synastry" or chart_type == "Transit" or chart_type == "
|
|
446
|
+
if chart_type == "Synastry" or chart_type == "Transit" or chart_type == "DualReturnChart":
|
|
423
447
|
return f'<circle cx="{r}" cy="{r}" r="{r - 36}" style="fill: none; stroke: {stroke_color}; stroke-width: 1px; stroke-opacity:.4;" />'
|
|
424
448
|
else:
|
|
425
449
|
if c1 is None:
|
|
@@ -462,7 +486,7 @@ def draw_second_circle(
|
|
|
462
486
|
str: The SVG path of the second circle.
|
|
463
487
|
"""
|
|
464
488
|
|
|
465
|
-
if chart_type == "Synastry" or chart_type == "Transit" or chart_type == "
|
|
489
|
+
if chart_type == "Synastry" or chart_type == "Transit" or chart_type == "DualReturnChart":
|
|
466
490
|
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
491
|
|
|
468
492
|
else:
|
|
@@ -492,7 +516,7 @@ def draw_third_circle(
|
|
|
492
516
|
Returns:
|
|
493
517
|
- str: The SVG element as a string.
|
|
494
518
|
"""
|
|
495
|
-
if chart_type in {"Synastry", "Transit", "
|
|
519
|
+
if chart_type in {"Synastry", "Transit", "DualReturnChart"}:
|
|
496
520
|
# For Synastry and Transit charts, use a fixed radius adjustment of 160
|
|
497
521
|
return f'<circle cx="{radius}" cy="{radius}" r="{radius - 160}" style="fill: {fill_color}; fill-opacity:.8; stroke: {stroke_color}; stroke-width: 1px" />'
|
|
498
522
|
|
|
@@ -572,6 +596,7 @@ def draw_houses_cusps_and_text_number(
|
|
|
572
596
|
chart_type: ChartType,
|
|
573
597
|
second_subject_houses_list: Union[list[KerykeionPointModel], None] = None,
|
|
574
598
|
transit_house_cusp_color: Union[str, None] = None,
|
|
599
|
+
external_view: bool = False,
|
|
575
600
|
) -> str:
|
|
576
601
|
"""
|
|
577
602
|
Draws the houses cusps and text numbers for a given chart type.
|
|
@@ -589,9 +614,10 @@ def draw_houses_cusps_and_text_number(
|
|
|
589
614
|
- chart_type: Type of the chart (e.g., Transit, Synastry).
|
|
590
615
|
- second_subject_houses_list: List of house for the second subject (optional).
|
|
591
616
|
- transit_house_cusp_color: Color for transit house cusps (optional).
|
|
617
|
+
- external_view: Whether to use external view mode for positioning (optional).
|
|
592
618
|
|
|
593
619
|
Returns:
|
|
594
|
-
- A string containing
|
|
620
|
+
- A string containing SVG elements for house cusps and numbers.
|
|
595
621
|
"""
|
|
596
622
|
|
|
597
623
|
path = ""
|
|
@@ -599,7 +625,7 @@ def draw_houses_cusps_and_text_number(
|
|
|
599
625
|
|
|
600
626
|
for i in range(xr):
|
|
601
627
|
# Determine offsets based on chart type
|
|
602
|
-
dropin, roff, t_roff = (160, 72, 36) if chart_type in ["Transit", "Synastry", "
|
|
628
|
+
dropin, roff, t_roff = (160, 72, 36) if chart_type in ["Transit", "Synastry", "DualReturnChart"] else (c3, c1, False)
|
|
603
629
|
|
|
604
630
|
# Calculate the offset for the current house cusp
|
|
605
631
|
offset = (int(first_subject_houses_list[int(xr / 2)].abs_pos) / -1) + int(first_subject_houses_list[i].abs_pos)
|
|
@@ -621,7 +647,7 @@ def draw_houses_cusps_and_text_number(
|
|
|
621
647
|
i, standard_house_cusp_color
|
|
622
648
|
)
|
|
623
649
|
|
|
624
|
-
if chart_type in ["Transit", "Synastry", "
|
|
650
|
+
if chart_type in ["Transit", "Synastry", "DualReturnChart"]:
|
|
625
651
|
if second_subject_houses_list is None or transit_house_cusp_color is None:
|
|
626
652
|
raise KerykeionException("second_subject_houses_list_ut or transit_house_cusp_color is None")
|
|
627
653
|
|
|
@@ -651,17 +677,21 @@ def draw_houses_cusps_and_text_number(
|
|
|
651
677
|
|
|
652
678
|
# Add the house cusp line for the second subject
|
|
653
679
|
stroke_opacity = "0" if chart_type == "Transit" else ".3"
|
|
654
|
-
path += '<g kr:node="Cusp">'
|
|
680
|
+
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
681
|
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
682
|
path += "</g>"
|
|
657
683
|
|
|
658
|
-
# Adjust dropin based on chart type
|
|
659
|
-
|
|
684
|
+
# Adjust dropin based on chart type and external view
|
|
685
|
+
dropin_map = {"Transit": 84, "Synastry": 84, "DualReturnChart": 84}
|
|
686
|
+
if external_view:
|
|
687
|
+
dropin = 100
|
|
688
|
+
else:
|
|
689
|
+
dropin = dropin_map.get(chart_type, 48)
|
|
660
690
|
xtext = sliceToX(0, (r - dropin), text_offset) + dropin
|
|
661
691
|
ytext = sliceToY(0, (r - dropin), text_offset) + dropin
|
|
662
692
|
|
|
663
693
|
# Add the house cusp line for the first subject
|
|
664
|
-
path += '<g kr:node="Cusp">'
|
|
694
|
+
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
695
|
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
696
|
path += "</g>"
|
|
667
697
|
|
|
@@ -708,9 +738,12 @@ def draw_transit_aspect_list(
|
|
|
708
738
|
if aspects_list and isinstance(aspects_list[0], dict):
|
|
709
739
|
aspects_list = [AspectModel(**aspect) for aspect in aspects_list] # type: ignore
|
|
710
740
|
|
|
741
|
+
# Type narrowing: at this point aspects_list contains AspectModel instances
|
|
742
|
+
typed_aspects_list: list[AspectModel] = aspects_list # type: ignore
|
|
743
|
+
|
|
711
744
|
inner_path = ""
|
|
712
745
|
|
|
713
|
-
for i, aspect in enumerate(
|
|
746
|
+
for i, aspect in enumerate(typed_aspects_list):
|
|
714
747
|
# Calculate which column this aspect belongs in
|
|
715
748
|
current_column = i // aspects_per_column
|
|
716
749
|
|
|
@@ -722,12 +755,22 @@ def draw_transit_aspect_list(
|
|
|
722
755
|
vertical_position = current_line * line_height
|
|
723
756
|
|
|
724
757
|
# Special handling for many aspects - if we exceed max_columns
|
|
758
|
+
# Bottom-align the overflow columns so the list starts from the bottom
|
|
725
759
|
if current_column >= max_columns:
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
760
|
+
overflow_total = len(aspects_list) - (aspects_per_column * max_columns)
|
|
761
|
+
if overflow_total > 0:
|
|
762
|
+
# Index within the overflow sequence (beyond the first row of columns)
|
|
763
|
+
overflow_index = i - (aspects_per_column * max_columns)
|
|
764
|
+
# Which overflow column we are in (0-based)
|
|
765
|
+
overflow_col_idx = overflow_index // aspects_per_column
|
|
766
|
+
# How many items go into this overflow column
|
|
767
|
+
items_in_this_column = min(
|
|
768
|
+
aspects_per_column,
|
|
769
|
+
max(0, overflow_total - (overflow_col_idx * aspects_per_column)),
|
|
770
|
+
)
|
|
771
|
+
# Compute extra top offset (in lines) to bottom-align this column
|
|
772
|
+
top_offset_lines = max(0, aspects_per_column - items_in_this_column)
|
|
773
|
+
vertical_position = (top_offset_lines + (i % aspects_per_column)) * line_height
|
|
731
774
|
|
|
732
775
|
inner_path += f'<g transform="translate({horizontal_position},{vertical_position})">'
|
|
733
776
|
|
|
@@ -824,7 +867,7 @@ def draw_main_house_grid(
|
|
|
824
867
|
main_subject_houses_list: list[KerykeionPointModel],
|
|
825
868
|
house_cusp_generale_name_label: str = "Cusp",
|
|
826
869
|
text_color: str = "#000000",
|
|
827
|
-
x_position: int =
|
|
870
|
+
x_position: int = 750,
|
|
828
871
|
y_position: int = 30,
|
|
829
872
|
) -> str:
|
|
830
873
|
"""
|
|
@@ -862,7 +905,7 @@ def draw_secondary_house_grid(
|
|
|
862
905
|
secondary_subject_houses_list: list[KerykeionPointModel],
|
|
863
906
|
house_cusp_generale_name_label: str = "Cusp",
|
|
864
907
|
text_color: str = "#000000",
|
|
865
|
-
x_position: int =
|
|
908
|
+
x_position: int = 1015,
|
|
866
909
|
y_position: int = 30,
|
|
867
910
|
) -> str:
|
|
868
911
|
"""
|
|
@@ -903,57 +946,58 @@ def draw_main_planet_grid(
|
|
|
903
946
|
chart_type: ChartType,
|
|
904
947
|
celestial_point_language: KerykeionLanguageCelestialPointModel,
|
|
905
948
|
text_color: str = "#000000",
|
|
949
|
+
x_position: int = 645,
|
|
950
|
+
y_position: int = 0,
|
|
906
951
|
) -> str:
|
|
907
952
|
"""
|
|
908
|
-
|
|
953
|
+
Draw the planet grid (main subject) and optional title.
|
|
954
|
+
|
|
955
|
+
The entire output is wrapped in a single SVG group `<g>` so the
|
|
956
|
+
whole block can be repositioned by changing the group transform.
|
|
909
957
|
|
|
910
958
|
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
|
|
959
|
+
planets_and_houses_grid_title: Title prefix to show for eligible chart types.
|
|
960
|
+
subject_name: Subject name to append to the title.
|
|
961
|
+
available_kerykeion_celestial_points: Celestial points to render in the grid.
|
|
962
|
+
chart_type: Chart type identifier (Literal string).
|
|
963
|
+
celestial_point_language: Language model for celestial point decoding.
|
|
964
|
+
text_color: Text color for labels (default: "#000000").
|
|
965
|
+
x_position: X translation applied to the outer `<g>` (default: 620).
|
|
966
|
+
y_position: Y translation applied to the outer `<g>` (default: 0).
|
|
917
967
|
|
|
918
968
|
Returns:
|
|
919
|
-
|
|
969
|
+
SVG string for the main planet grid wrapped in a `<g>`.
|
|
920
970
|
"""
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
971
|
+
# Layout constants (kept identical to previous behavior)
|
|
972
|
+
BASE_Y = 30
|
|
973
|
+
HEADER_Y = 15 # Title baseline inside the wrapper
|
|
974
|
+
LINE_START = 10
|
|
975
|
+
LINE_STEP = 14
|
|
925
976
|
|
|
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":
|
|
977
|
+
# Wrap everything inside a single group so position can be changed once
|
|
978
|
+
svg_output = f'<g transform="translate({x_position},{y_position})">'
|
|
979
|
+
|
|
980
|
+
# Add title only for specific chart types
|
|
981
|
+
if chart_type in ("Synastry", "Transit", "DualReturnChart"):
|
|
939
982
|
svg_output += (
|
|
940
|
-
f'<g transform="translate(
|
|
983
|
+
f'<g transform="translate(0, {HEADER_Y})">'
|
|
941
984
|
f'<text style="fill:{text_color}; font-size: 14px;">{planets_and_houses_grid_title} {subject_name}</text>'
|
|
942
985
|
f'</g>'
|
|
943
986
|
)
|
|
944
|
-
else:
|
|
945
|
-
svg_output += ""
|
|
946
987
|
|
|
947
988
|
end_of_line = "</g>"
|
|
948
989
|
|
|
949
990
|
for i, planet in enumerate(available_kerykeion_celestial_points):
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
991
|
+
offset, row_index = _planet_grid_layout_position(i)
|
|
992
|
+
line_height = LINE_START + (row_index * LINE_STEP)
|
|
993
|
+
|
|
994
|
+
decoded_name = get_decoded_kerykeion_celestial_point_name(
|
|
995
|
+
planet["name"],
|
|
996
|
+
celestial_point_language,
|
|
997
|
+
)
|
|
953
998
|
|
|
954
|
-
decoded_name = get_decoded_kerykeion_celestial_point_name(planet["name"], celestial_point_language)
|
|
955
999
|
svg_output += (
|
|
956
|
-
f'<g transform="translate({
|
|
1000
|
+
f'<g transform="translate({offset},{BASE_Y + line_height})">'
|
|
957
1001
|
f'<text text-anchor="end" style="fill:{text_color}; font-size: 10px;">{decoded_name}</text>'
|
|
958
1002
|
f'<g transform="translate(5,-8)"><use transform="scale(0.4)" xlink:href="#{planet["name"]}" /></g>'
|
|
959
1003
|
f'<text text-anchor="start" x="19" style="fill:{text_color}; font-size: 10px;">{convert_decimal_to_degree_string(planet["position"])}</text>'
|
|
@@ -964,7 +1008,9 @@ def draw_main_planet_grid(
|
|
|
964
1008
|
svg_output += '<g transform="translate(74,-6)"><use transform="scale(.5)" xlink:href="#retrograde" /></g>'
|
|
965
1009
|
|
|
966
1010
|
svg_output += end_of_line
|
|
967
|
-
|
|
1011
|
+
|
|
1012
|
+
# Close the wrapper group
|
|
1013
|
+
svg_output += "</g>"
|
|
968
1014
|
|
|
969
1015
|
return svg_output
|
|
970
1016
|
|
|
@@ -976,49 +1022,64 @@ def draw_secondary_planet_grid(
|
|
|
976
1022
|
chart_type: ChartType,
|
|
977
1023
|
celestial_point_language: KerykeionLanguageCelestialPointModel,
|
|
978
1024
|
text_color: str = "#000000",
|
|
1025
|
+
x_position: int = 910,
|
|
1026
|
+
y_position: int = 0,
|
|
979
1027
|
) -> str:
|
|
980
1028
|
"""
|
|
981
|
-
|
|
1029
|
+
Draw the planet grid for the secondary subject and its title.
|
|
1030
|
+
|
|
1031
|
+
The entire output is wrapped in a single SVG group `<g>` so the
|
|
1032
|
+
whole block can be repositioned by changing the group transform.
|
|
982
1033
|
|
|
983
1034
|
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
|
|
1035
|
+
planets_and_houses_grid_title: Title prefix (used except for Transit charts).
|
|
1036
|
+
second_subject_name: Name of the secondary subject.
|
|
1037
|
+
second_subject_available_kerykeion_celestial_points: Celestial points to render for the secondary subject.
|
|
1038
|
+
chart_type: Chart type identifier (Literal string).
|
|
1039
|
+
celestial_point_language: Language model for celestial point decoding.
|
|
1040
|
+
text_color: Text color for labels (default: "#000000").
|
|
1041
|
+
x_position: X translation applied to the outer `<g>` (default: 870).
|
|
1042
|
+
y_position: Y translation applied to the outer `<g>` (default: 0).
|
|
990
1043
|
|
|
991
1044
|
Returns:
|
|
992
|
-
|
|
1045
|
+
SVG string for the secondary planet grid wrapped in a `<g>`.
|
|
993
1046
|
"""
|
|
994
|
-
|
|
995
|
-
|
|
1047
|
+
# Layout constants
|
|
1048
|
+
BASE_Y = 30
|
|
1049
|
+
HEADER_Y = 15
|
|
1050
|
+
LINE_START = 10
|
|
1051
|
+
LINE_STEP = 14
|
|
996
1052
|
|
|
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
|
-
)
|
|
1053
|
+
# Open wrapper group
|
|
1054
|
+
svg_output = f'<g transform="translate({x_position},{y_position})">'
|
|
1012
1055
|
|
|
1013
|
-
|
|
1056
|
+
# Title content and its relative x offset
|
|
1057
|
+
header_text = (
|
|
1058
|
+
second_subject_name if chart_type == "Transit"
|
|
1059
|
+
else f"{planets_and_houses_grid_title} {second_subject_name}"
|
|
1060
|
+
)
|
|
1061
|
+
header_x_offset = -50 if chart_type == "Transit" else 0
|
|
1014
1062
|
|
|
1015
|
-
|
|
1016
|
-
|
|
1063
|
+
svg_output += (
|
|
1064
|
+
f'<g transform="translate({header_x_offset}, {HEADER_Y})">'
|
|
1065
|
+
f'<text style="fill:{text_color}; font-size: 14px;">{header_text}</text>'
|
|
1066
|
+
f'</g>'
|
|
1067
|
+
)
|
|
1068
|
+
|
|
1069
|
+
# Grid rows
|
|
1070
|
+
line_height = LINE_START
|
|
1071
|
+
end_of_line = "</g>"
|
|
1017
1072
|
|
|
1018
1073
|
for i, t_planet in enumerate(second_subject_available_kerykeion_celestial_points):
|
|
1019
|
-
|
|
1074
|
+
offset, row_index = _planet_grid_layout_position(i)
|
|
1075
|
+
line_height = LINE_START + (row_index * LINE_STEP)
|
|
1076
|
+
|
|
1077
|
+
second_decoded_name = get_decoded_kerykeion_celestial_point_name(
|
|
1078
|
+
t_planet["name"],
|
|
1079
|
+
celestial_point_language,
|
|
1080
|
+
)
|
|
1020
1081
|
svg_output += (
|
|
1021
|
-
f'<g transform="translate({
|
|
1082
|
+
f'<g transform="translate({offset},{BASE_Y + line_height})">'
|
|
1022
1083
|
f'<text text-anchor="end" style="fill:{text_color}; font-size: 10px;">{second_decoded_name}</text>'
|
|
1023
1084
|
f'<g transform="translate(5,-8)"><use transform="scale(0.4)" xlink:href="#{t_planet["name"]}" /></g>'
|
|
1024
1085
|
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 +1090,10 @@ def draw_secondary_planet_grid(
|
|
|
1029
1090
|
svg_output += '<g transform="translate(74,-6)"><use transform="scale(.5)" xlink:href="#retrograde" /></g>'
|
|
1030
1091
|
|
|
1031
1092
|
svg_output += end_of_line
|
|
1032
|
-
|
|
1093
|
+
|
|
1094
|
+
|
|
1095
|
+
# Close wrapper group
|
|
1096
|
+
svg_output += "</g>"
|
|
1033
1097
|
|
|
1034
1098
|
return svg_output
|
|
1035
1099
|
|
|
@@ -1281,9 +1345,9 @@ def draw_house_comparison_grid(
|
|
|
1281
1345
|
text_color: str = "var(--kerykeion-color-neutral-content)",
|
|
1282
1346
|
house_position_comparison_label: str = "House Position Comparison",
|
|
1283
1347
|
return_point_label: str = "Return Point",
|
|
1284
|
-
return_label: str = "
|
|
1348
|
+
return_label: str = "DualReturnChart",
|
|
1285
1349
|
radix_label: str = "Radix",
|
|
1286
|
-
x_position: int =
|
|
1350
|
+
x_position: int = 1100,
|
|
1287
1351
|
y_position: int = 0,
|
|
1288
1352
|
) -> str:
|
|
1289
1353
|
"""
|
|
@@ -1584,4 +1648,3 @@ def calculate_synastry_quality_points(
|
|
|
1584
1648
|
combined_totals[quality] = (combined_totals[quality] / total_points) * 100.0
|
|
1585
1649
|
|
|
1586
1650
|
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]
|