kerykeion 5.0.0a9__py3-none-any.whl → 5.1.8__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 +50 -9
- kerykeion/aspects/__init__.py +5 -2
- kerykeion/aspects/aspects_factory.py +568 -0
- kerykeion/aspects/aspects_utils.py +78 -11
- kerykeion/astrological_subject_factory.py +1032 -275
- kerykeion/backword.py +820 -0
- kerykeion/chart_data_factory.py +552 -0
- kerykeion/charts/chart_drawer.py +2661 -0
- kerykeion/charts/charts_utils.py +652 -399
- kerykeion/charts/draw_planets.py +603 -353
- kerykeion/charts/templates/aspect_grid_only.xml +326 -198
- kerykeion/charts/templates/chart.xml +306 -256
- kerykeion/charts/templates/wheel_only.xml +330 -200
- kerykeion/charts/themes/black-and-white.css +148 -0
- 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 +232 -13
- kerykeion/ephemeris_data_factory.py +443 -0
- kerykeion/fetch_geonames.py +78 -21
- kerykeion/house_comparison/__init__.py +4 -1
- kerykeion/house_comparison/house_comparison_factory.py +52 -19
- kerykeion/house_comparison/house_comparison_utils.py +37 -9
- kerykeion/kr_types/__init__.py +66 -6
- kerykeion/kr_types/chart_template_model.py +20 -0
- kerykeion/kr_types/kerykeion_exception.py +15 -9
- kerykeion/kr_types/kr_literals.py +14 -160
- kerykeion/kr_types/kr_models.py +14 -291
- kerykeion/kr_types/settings_models.py +15 -167
- kerykeion/planetary_return_factory.py +545 -40
- kerykeion/relationship_score_factory.py +137 -63
- kerykeion/report.py +749 -64
- kerykeion/schemas/__init__.py +106 -0
- kerykeion/schemas/chart_template_model.py +367 -0
- kerykeion/schemas/kerykeion_exception.py +20 -0
- kerykeion/schemas/kr_literals.py +181 -0
- kerykeion/schemas/kr_models.py +603 -0
- kerykeion/schemas/settings_models.py +188 -0
- kerykeion/settings/__init__.py +20 -1
- kerykeion/settings/chart_defaults.py +444 -0
- kerykeion/settings/config_constants.py +88 -12
- kerykeion/settings/kerykeion_settings.py +32 -75
- kerykeion/settings/translation_strings.py +1499 -0
- kerykeion/settings/translations.py +74 -0
- 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/sweph/sefstars.txt +1602 -0
- kerykeion/transits_time_range_factory.py +302 -0
- kerykeion/utilities.py +289 -204
- kerykeion-5.1.8.dist-info/METADATA +1793 -0
- kerykeion-5.1.8.dist-info/RECORD +63 -0
- kerykeion/aspects/natal_aspects.py +0 -181
- kerykeion/aspects/synastry_aspects.py +0 -141
- kerykeion/aspects/transits_time_range.py +0 -41
- kerykeion/charts/draw_planets_v2.py +0 -649
- kerykeion/charts/draw_planets_v3.py +0 -679
- kerykeion/charts/kerykeion_chart_svg.py +0 -2038
- kerykeion/enums.py +0 -57
- kerykeion/ephemeris_data.py +0 -238
- kerykeion/house_comparison/house_comparison_models.py +0 -38
- kerykeion/kr_types/chart_types.py +0 -106
- kerykeion/settings/kr.config.json +0 -1304
- kerykeion/settings/legacy/__init__.py +0 -0
- kerykeion/settings/legacy/legacy_celestial_points_settings.py +0 -299
- kerykeion/settings/legacy/legacy_chart_aspects_settings.py +0 -71
- kerykeion/settings/legacy/legacy_color_settings.py +0 -42
- kerykeion/transits_time_range.py +0 -128
- kerykeion-5.0.0a9.dist-info/METADATA +0 -636
- kerykeion-5.0.0a9.dist-info/RECORD +0 -55
- kerykeion-5.0.0a9.dist-info/entry_points.txt +0 -2
- {kerykeion-5.0.0a9.dist-info → kerykeion-5.1.8.dist-info}/WHEEL +0 -0
- {kerykeion-5.0.0a9.dist-info → kerykeion-5.1.8.dist-info}/licenses/LICENSE +0 -0
kerykeion/charts/draw_planets.py
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
|
-
# type: ignore
|
|
2
|
-
# TODO: Legacy original method extracted as function. The V2 is a heavy refactor of this code. If it's safe, delete this.
|
|
3
|
-
|
|
4
1
|
from kerykeion.charts.charts_utils import degreeDiff, sliceToX, sliceToY, convert_decimal_to_degree_string
|
|
5
|
-
from kerykeion.
|
|
6
|
-
from kerykeion.
|
|
7
|
-
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
|
|
8
5
|
import logging
|
|
9
|
-
from typing import Union, get_args
|
|
10
|
-
|
|
6
|
+
from typing import Union, get_args, List, Optional
|
|
11
7
|
|
|
12
8
|
|
|
13
9
|
def draw_planets(
|
|
@@ -19,390 +15,644 @@ def draw_planets(
|
|
|
19
15
|
main_subject_seventh_house_degree_ut: Union[int, float],
|
|
20
16
|
chart_type: ChartType,
|
|
21
17
|
second_subject_available_kerykeion_celestial_points: Union[list[KerykeionPointModel], None] = None,
|
|
22
|
-
|
|
18
|
+
external_view: bool = False,
|
|
19
|
+
) -> str:
|
|
23
20
|
"""
|
|
24
|
-
Draws the planets on
|
|
21
|
+
Draws the planets on an astrological chart based on the provided parameters.
|
|
22
|
+
|
|
23
|
+
This function calculates positions, handles overlap of celestial points, and draws SVG
|
|
24
|
+
elements for each planet/point on the chart. It supports different chart types including
|
|
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
|
-
radius (int): The radius of the chart.
|
|
29
|
+
radius (Union[int, float]): The radius of the chart in pixels.
|
|
28
30
|
available_kerykeion_celestial_points (list[KerykeionPointModel]): List of celestial points for the main subject.
|
|
29
31
|
available_planets_setting (list[KerykeionSettingsCelestialPointModel]): Settings for the celestial points.
|
|
30
|
-
third_circle_radius (Union[int, float]): Radius of the third circle.
|
|
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
|
-
List of celestial points for the second subject, required for "Transit" or "
|
|
37
|
+
List of celestial points for the second subject, required for "Transit", "Synastry", or "Return" charts.
|
|
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.
|
|
36
42
|
|
|
37
43
|
Raises:
|
|
38
|
-
KerykeionException: If
|
|
44
|
+
KerykeionException: If secondary celestial points are required but not provided.
|
|
39
45
|
|
|
40
46
|
Returns:
|
|
41
47
|
str: SVG output for the chart with the planets drawn.
|
|
42
48
|
"""
|
|
43
|
-
|
|
49
|
+
# Constants and initialization
|
|
50
|
+
PLANET_GROUPING_THRESHOLD = 3.4 # Distance threshold to consider planets as grouped
|
|
51
|
+
TRANSIT_RING_EXCLUDE_POINTS_NAMES: List[str] = list(get_args(Houses))
|
|
52
|
+
output = ""
|
|
44
53
|
|
|
54
|
+
# -----------------------------------------------------------
|
|
55
|
+
# 1. Validate inputs and prepare data
|
|
56
|
+
# -----------------------------------------------------------
|
|
57
|
+
if chart_type == "Transit" and second_subject_available_kerykeion_celestial_points is None:
|
|
58
|
+
raise KerykeionException("Secondary celestial points are required for Transit charts")
|
|
59
|
+
elif chart_type == "Synastry" and second_subject_available_kerykeion_celestial_points is None:
|
|
60
|
+
raise KerykeionException("Secondary celestial points are required for Synastry charts")
|
|
61
|
+
elif chart_type == "Return" and second_subject_available_kerykeion_celestial_points is None:
|
|
62
|
+
raise KerykeionException("Secondary celestial points are required for Return charts")
|
|
63
|
+
|
|
64
|
+
# Extract absolute and relative positions for main celestial points
|
|
65
|
+
main_points_abs_positions = [planet.abs_pos for planet in available_kerykeion_celestial_points]
|
|
66
|
+
[planet.position for planet in available_kerykeion_celestial_points]
|
|
67
|
+
|
|
68
|
+
# Extract absolute and relative positions for secondary celestial points if needed
|
|
69
|
+
secondary_points_abs_positions = []
|
|
70
|
+
secondary_points_rel_positions = []
|
|
45
71
|
if chart_type == "Transit" or chart_type == "Synastry" or chart_type == "Return":
|
|
46
|
-
if second_subject_available_kerykeion_celestial_points is None:
|
|
47
|
-
|
|
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
|
+
]
|
|
79
|
+
|
|
80
|
+
# -----------------------------------------------------------
|
|
81
|
+
# 2. Create position lookup dictionary for main celestial points
|
|
82
|
+
# -----------------------------------------------------------
|
|
83
|
+
# Map absolute degree to index in the settings array
|
|
84
|
+
position_index_map = {}
|
|
85
|
+
for i in range(len(available_planets_setting)):
|
|
86
|
+
position_index_map[main_points_abs_positions[i]] = i
|
|
87
|
+
logging.debug(f"Planet index: {i}, degree: {main_points_abs_positions[i]}")
|
|
88
|
+
|
|
89
|
+
# Sort positions for ordered processing
|
|
90
|
+
sorted_positions = sorted(position_index_map.keys())
|
|
91
|
+
|
|
92
|
+
# -----------------------------------------------------------
|
|
93
|
+
# 3. Identify groups of celestial points that are close to each other
|
|
94
|
+
# -----------------------------------------------------------
|
|
95
|
+
point_groups: List[List[List[Union[int, float, str]]]] = []
|
|
96
|
+
is_group_open = False
|
|
97
|
+
planets_by_position: List[Optional[List[Union[int, float]]]] = [None] * len(position_index_map)
|
|
98
|
+
|
|
99
|
+
# Process each celestial point to find groups
|
|
100
|
+
for position_idx, abs_position in enumerate(sorted_positions):
|
|
101
|
+
point_idx = position_index_map[abs_position]
|
|
102
|
+
|
|
103
|
+
# Find previous and next point positions for distance calculations
|
|
104
|
+
# Handle special case when there's only one planet
|
|
105
|
+
if len(sorted_positions) == 1:
|
|
106
|
+
# With only one planet, there are no adjacent planets
|
|
107
|
+
prev_position = main_points_abs_positions[point_idx]
|
|
108
|
+
next_position = main_points_abs_positions[point_idx]
|
|
109
|
+
elif position_idx == 0:
|
|
110
|
+
prev_position = main_points_abs_positions[position_index_map[sorted_positions[-1]]]
|
|
111
|
+
next_position = main_points_abs_positions[position_index_map[sorted_positions[1]]]
|
|
112
|
+
elif position_idx == len(sorted_positions) - 1:
|
|
113
|
+
prev_position = main_points_abs_positions[position_index_map[sorted_positions[position_idx - 1]]]
|
|
114
|
+
next_position = main_points_abs_positions[position_index_map[sorted_positions[0]]]
|
|
115
|
+
else:
|
|
116
|
+
prev_position = main_points_abs_positions[position_index_map[sorted_positions[position_idx - 1]]]
|
|
117
|
+
next_position = main_points_abs_positions[position_index_map[sorted_positions[position_idx + 1]]]
|
|
118
|
+
|
|
119
|
+
# Calculate distance to adjacent points
|
|
120
|
+
# When there's only one planet, set distances to a large value to prevent grouping
|
|
121
|
+
if len(sorted_positions) == 1:
|
|
122
|
+
distance_to_prev = 360.0 # Maximum possible distance
|
|
123
|
+
distance_to_next = 360.0 # Maximum possible distance
|
|
124
|
+
else:
|
|
125
|
+
distance_to_prev = degreeDiff(prev_position, main_points_abs_positions[point_idx])
|
|
126
|
+
distance_to_next = degreeDiff(next_position, main_points_abs_positions[point_idx])
|
|
127
|
+
|
|
128
|
+
# Store position and distance information
|
|
129
|
+
planets_by_position[position_idx] = [point_idx, distance_to_prev, distance_to_next]
|
|
48
130
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
131
|
+
label = available_planets_setting[point_idx]["label"]
|
|
132
|
+
logging.debug(f"{label}, distance_to_prev: {distance_to_prev}, distance_to_next: {distance_to_next}")
|
|
133
|
+
|
|
134
|
+
# Group points that are close to each other
|
|
135
|
+
if distance_to_next < PLANET_GROUPING_THRESHOLD:
|
|
136
|
+
point_data = [position_idx, distance_to_prev, distance_to_next, label]
|
|
137
|
+
if is_group_open:
|
|
138
|
+
point_groups[-1].append(point_data)
|
|
139
|
+
else:
|
|
140
|
+
is_group_open = True
|
|
141
|
+
point_groups.append([point_data])
|
|
142
|
+
else:
|
|
143
|
+
if is_group_open:
|
|
144
|
+
point_data = [position_idx, distance_to_prev, distance_to_next, label]
|
|
145
|
+
point_groups[-1].append(point_data)
|
|
146
|
+
is_group_open = False
|
|
147
|
+
|
|
148
|
+
# -----------------------------------------------------------
|
|
149
|
+
# 4. Calculate position adjustments to avoid overlapping
|
|
150
|
+
# -----------------------------------------------------------
|
|
151
|
+
position_adjustments: List[float] = [0.0] * len(available_planets_setting)
|
|
152
|
+
|
|
153
|
+
# Process each group to calculate position adjustments
|
|
154
|
+
for group in point_groups:
|
|
155
|
+
group_size = len(group)
|
|
156
|
+
|
|
157
|
+
# Handle groups of two celestial points
|
|
158
|
+
if group_size == 2:
|
|
159
|
+
_handle_two_point_group(group, planets_by_position, position_adjustments, PLANET_GROUPING_THRESHOLD)
|
|
160
|
+
|
|
161
|
+
# Handle groups of three or more celestial points
|
|
162
|
+
elif group_size >= 3:
|
|
163
|
+
_handle_multi_point_group(group, position_adjustments, PLANET_GROUPING_THRESHOLD)
|
|
164
|
+
|
|
165
|
+
# -----------------------------------------------------------
|
|
166
|
+
# 5. Draw main celestial points
|
|
167
|
+
# -----------------------------------------------------------
|
|
168
|
+
adjusted_offset = 0.0 # Initialize for use outside loop
|
|
169
|
+
for position_idx, abs_position in enumerate(sorted_positions):
|
|
170
|
+
point_idx = position_index_map[abs_position]
|
|
171
|
+
|
|
172
|
+
# Determine radius based on chart type and point type
|
|
173
|
+
point_radius = _determine_point_radius(point_idx, chart_type, bool(position_idx % 2), external_view)
|
|
174
|
+
|
|
175
|
+
# Calculate position offset for the point
|
|
176
|
+
adjusted_offset = _calculate_point_offset(
|
|
177
|
+
main_subject_seventh_house_degree_ut,
|
|
178
|
+
main_points_abs_positions[point_idx],
|
|
179
|
+
position_adjustments[position_idx],
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
# Calculate true position without adjustment (used for connecting lines)
|
|
183
|
+
true_offset = _calculate_point_offset(
|
|
184
|
+
main_subject_seventh_house_degree_ut,
|
|
185
|
+
main_points_abs_positions[point_idx],
|
|
186
|
+
0
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
# Calculate point coordinates
|
|
190
|
+
point_x = sliceToX(0, radius - point_radius, adjusted_offset) + point_radius
|
|
191
|
+
point_y = sliceToY(0, radius - point_radius, adjusted_offset) + point_radius
|
|
192
|
+
|
|
193
|
+
# Determine scale factor based on chart type
|
|
194
|
+
scale_factor = 1.0
|
|
195
|
+
if chart_type == "Transit":
|
|
196
|
+
scale_factor = 0.8
|
|
197
|
+
elif chart_type == "Synastry":
|
|
198
|
+
scale_factor = 0.8
|
|
199
|
+
elif chart_type == "Return":
|
|
200
|
+
scale_factor = 0.8
|
|
201
|
+
elif external_view:
|
|
202
|
+
scale_factor = 0.8
|
|
203
|
+
|
|
204
|
+
# Draw connecting lines for external view
|
|
205
|
+
if external_view:
|
|
206
|
+
output = _draw_external_natal_lines(
|
|
207
|
+
output,
|
|
208
|
+
radius,
|
|
209
|
+
third_circle_radius,
|
|
210
|
+
point_radius,
|
|
211
|
+
true_offset,
|
|
212
|
+
adjusted_offset,
|
|
213
|
+
available_planets_setting[point_idx]["color"],
|
|
214
|
+
)
|
|
53
215
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
216
|
+
# Draw the celestial point SVG element
|
|
217
|
+
point_details = available_kerykeion_celestial_points[point_idx]
|
|
218
|
+
output += _generate_point_svg(
|
|
219
|
+
point_details, point_x, point_y, scale_factor, available_planets_setting[point_idx]["name"]
|
|
220
|
+
)
|
|
58
221
|
|
|
222
|
+
# -----------------------------------------------------------
|
|
223
|
+
# 6. Draw transit/secondary celestial points
|
|
224
|
+
# -----------------------------------------------------------
|
|
59
225
|
if chart_type == "Transit" or chart_type == "Synastry" or chart_type == "Return":
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
226
|
+
output = _draw_secondary_points(
|
|
227
|
+
output,
|
|
228
|
+
radius,
|
|
229
|
+
main_subject_first_house_degree_ut,
|
|
230
|
+
main_subject_seventh_house_degree_ut,
|
|
231
|
+
secondary_points_abs_positions,
|
|
232
|
+
secondary_points_rel_positions,
|
|
233
|
+
available_planets_setting,
|
|
234
|
+
chart_type,
|
|
235
|
+
TRANSIT_RING_EXCLUDE_POINTS_NAMES,
|
|
236
|
+
adjusted_offset,
|
|
237
|
+
)
|
|
64
238
|
|
|
65
|
-
|
|
66
|
-
t_points_deg = []
|
|
67
|
-
for planet in second_subject_available_kerykeion_celestial_points:
|
|
68
|
-
t_points_deg.append(planet.position)
|
|
239
|
+
return output
|
|
69
240
|
|
|
70
|
-
planets_degut = {}
|
|
71
|
-
diff = range(len(available_planets_setting))
|
|
72
241
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
242
|
+
def _handle_two_point_group(
|
|
243
|
+
group: list, planets_by_position: list, position_adjustments: list, threshold: float
|
|
244
|
+
) -> None:
|
|
245
|
+
"""
|
|
246
|
+
Handle positioning for a group of two celestial points that are close to each other.
|
|
247
|
+
|
|
248
|
+
Adjusts positions to prevent overlapping by calculating appropriate offsets
|
|
249
|
+
based on available space around the points.
|
|
77
250
|
|
|
251
|
+
Args:
|
|
252
|
+
group (list): A list containing data about two closely positioned points.
|
|
253
|
+
planets_by_position (list): A list with data about all planets positions.
|
|
254
|
+
position_adjustments (list): The list to store calculated position adjustments.
|
|
255
|
+
threshold (float): The minimum distance threshold for considering points as grouped.
|
|
78
256
|
"""
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
257
|
+
next_to_a = group[0][0] - 1
|
|
258
|
+
next_to_b = 0 if group[1][0] == (len(planets_by_position) - 1) else group[1][0] + 1
|
|
259
|
+
|
|
260
|
+
# If both points have room
|
|
261
|
+
if (group[0][1] > (2 * threshold)) and (group[1][2] > (2 * threshold)):
|
|
262
|
+
position_adjustments[group[0][0]] = -(threshold - group[0][2]) / 2
|
|
263
|
+
position_adjustments[group[1][0]] = +(threshold - group[0][2]) / 2
|
|
264
|
+
|
|
265
|
+
# If only first point has room
|
|
266
|
+
elif group[0][1] > (2 * threshold):
|
|
267
|
+
position_adjustments[group[0][0]] = -threshold
|
|
268
|
+
|
|
269
|
+
# If only second point has room
|
|
270
|
+
elif group[1][2] > (2 * threshold):
|
|
271
|
+
position_adjustments[group[1][0]] = +threshold
|
|
272
|
+
|
|
273
|
+
# If points adjacent to group have room
|
|
274
|
+
elif (planets_by_position[next_to_a][1] > (2.4 * threshold)) and (planets_by_position[next_to_b][2] > (2.4 * threshold)):
|
|
275
|
+
position_adjustments[next_to_a] = group[0][1] - threshold * 2
|
|
276
|
+
position_adjustments[group[0][0]] = -threshold * 0.5
|
|
277
|
+
position_adjustments[next_to_b] = -(group[1][2] - threshold * 2)
|
|
278
|
+
position_adjustments[group[1][0]] = +threshold * 0.5
|
|
279
|
+
|
|
280
|
+
# If only point adjacent to first has room
|
|
281
|
+
elif planets_by_position[next_to_a][1] > (2 * threshold):
|
|
282
|
+
position_adjustments[next_to_a] = group[0][1] - threshold * 2.5
|
|
283
|
+
position_adjustments[group[0][0]] = -threshold * 1.2
|
|
284
|
+
|
|
285
|
+
# If only point adjacent to second has room
|
|
286
|
+
elif planets_by_position[next_to_b][2] > (2 * threshold):
|
|
287
|
+
position_adjustments[next_to_b] = -(group[1][2] - threshold * 2.5)
|
|
288
|
+
position_adjustments[group[1][0]] = +threshold * 1.2
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def _handle_multi_point_group(group: list, position_adjustments: list, threshold: float) -> None:
|
|
84
292
|
"""
|
|
293
|
+
Handle positioning for a group of three or more celestial points that are close to each other.
|
|
85
294
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
#
|
|
96
|
-
|
|
97
|
-
for
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
295
|
+
Distributes points evenly within the available space to prevent overlapping.
|
|
296
|
+
|
|
297
|
+
Args:
|
|
298
|
+
group (list): A list containing data about grouped points.
|
|
299
|
+
position_adjustments (list): The list to store calculated position adjustments.
|
|
300
|
+
threshold (float): The minimum distance threshold for considering points as grouped.
|
|
301
|
+
"""
|
|
302
|
+
group_size = len(group)
|
|
303
|
+
|
|
304
|
+
# Calculate available space
|
|
305
|
+
available_space = group[0][1] # Distance before first point
|
|
306
|
+
for i in range(group_size):
|
|
307
|
+
available_space += group[i][2] # Add distance after each point
|
|
308
|
+
|
|
309
|
+
# Calculate needed space
|
|
310
|
+
needed_space = (3 * threshold) + (1.2 * (group_size - 1) * threshold)
|
|
311
|
+
leftover_space = available_space - needed_space
|
|
312
|
+
|
|
313
|
+
# Get spacing before first and after last point
|
|
314
|
+
space_before_first = group[0][1]
|
|
315
|
+
space_after_last = group[group_size - 1][2]
|
|
316
|
+
|
|
317
|
+
# Position points based on available space
|
|
318
|
+
if (space_before_first > (needed_space * 0.5)) and (space_after_last > (needed_space * 0.5)):
|
|
319
|
+
# Center the group
|
|
320
|
+
start_position = space_before_first - (needed_space * 0.5)
|
|
321
|
+
else:
|
|
322
|
+
# Distribute leftover space proportionally
|
|
323
|
+
start_position = (leftover_space / (space_before_first + space_after_last)) * space_before_first
|
|
324
|
+
|
|
325
|
+
# Apply positions if there's enough space
|
|
326
|
+
if available_space > needed_space:
|
|
327
|
+
position_adjustments[group[0][0]] = start_position - group[0][1] + (1.5 * threshold)
|
|
328
|
+
|
|
329
|
+
# Position each subsequent point relative to the previous one
|
|
330
|
+
for i in range(group_size - 1):
|
|
331
|
+
position_adjustments[group[i + 1][0]] = 1.2 * threshold + position_adjustments[group[i][0]] - group[i][2]
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def _determine_point_radius(
|
|
335
|
+
point_idx: int,
|
|
336
|
+
chart_type: str,
|
|
337
|
+
is_alternate_position: bool,
|
|
338
|
+
external_view: bool = False
|
|
339
|
+
) -> int:
|
|
340
|
+
"""
|
|
341
|
+
Determine the radius for placing a celestial point based on its type and chart type.
|
|
112
342
|
|
|
113
|
-
|
|
343
|
+
Args:
|
|
344
|
+
point_idx (int): Index of the celestial point.
|
|
345
|
+
chart_type (str): Type of the chart.
|
|
346
|
+
is_alternate_position (bool): Whether to use alternate positioning.
|
|
347
|
+
external_view (bool): Whether external view is enabled.
|
|
114
348
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
349
|
+
Returns:
|
|
350
|
+
int: Radius value for the point.
|
|
351
|
+
"""
|
|
352
|
+
# Check if point is an angle of the chart (ASC, MC, DSC, IC)
|
|
353
|
+
is_chart_angle = 22 < point_idx < 27
|
|
354
|
+
|
|
355
|
+
if chart_type == "Transit":
|
|
356
|
+
if is_chart_angle:
|
|
357
|
+
return 76
|
|
122
358
|
else:
|
|
123
|
-
if
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
def zero(x):
|
|
128
|
-
return 0
|
|
129
|
-
|
|
130
|
-
planets_delta = list(map(zero, range(len(available_planets_setting))))
|
|
131
|
-
|
|
132
|
-
# print groups
|
|
133
|
-
# print planets_by_pos
|
|
134
|
-
for a in range(len(groups)):
|
|
135
|
-
# Two grouped planets
|
|
136
|
-
if len(groups[a]) == 2:
|
|
137
|
-
next_to_a = groups[a][0][0] - 1
|
|
138
|
-
if groups[a][1][0] == (len(planets_by_pos) - 1):
|
|
139
|
-
next_to_b = 0
|
|
140
|
-
else:
|
|
141
|
-
next_to_b = groups[a][1][0] + 1
|
|
142
|
-
# if both planets have room
|
|
143
|
-
if (groups[a][0][1] > (2 * planet_drange)) & (groups[a][1][2] > (2 * planet_drange)):
|
|
144
|
-
planets_delta[groups[a][0][0]] = -(planet_drange - groups[a][0][2]) / 2
|
|
145
|
-
planets_delta[groups[a][1][0]] = +(planet_drange - groups[a][0][2]) / 2
|
|
146
|
-
# if planet a has room
|
|
147
|
-
elif groups[a][0][1] > (2 * planet_drange):
|
|
148
|
-
planets_delta[groups[a][0][0]] = -planet_drange
|
|
149
|
-
# if planet b has room
|
|
150
|
-
elif groups[a][1][2] > (2 * planet_drange):
|
|
151
|
-
planets_delta[groups[a][1][0]] = +planet_drange
|
|
152
|
-
|
|
153
|
-
# if planets next to a and b have room move them
|
|
154
|
-
elif (planets_by_pos[next_to_a][1] > (2.4 * planet_drange)) & (
|
|
155
|
-
planets_by_pos[next_to_b][2] > (2.4 * planet_drange)
|
|
156
|
-
):
|
|
157
|
-
planets_delta[(next_to_a)] = groups[a][0][1] - planet_drange * 2
|
|
158
|
-
planets_delta[groups[a][0][0]] = -planet_drange * 0.5
|
|
159
|
-
planets_delta[next_to_b] = -(groups[a][1][2] - planet_drange * 2)
|
|
160
|
-
planets_delta[groups[a][1][0]] = +planet_drange * 0.5
|
|
161
|
-
|
|
162
|
-
# if planet next to a has room move them
|
|
163
|
-
elif planets_by_pos[next_to_a][1] > (2 * planet_drange):
|
|
164
|
-
planets_delta[(next_to_a)] = groups[a][0][1] - planet_drange * 2.5
|
|
165
|
-
planets_delta[groups[a][0][0]] = -planet_drange * 1.2
|
|
166
|
-
|
|
167
|
-
# if planet next to b has room move them
|
|
168
|
-
elif planets_by_pos[next_to_b][2] > (2 * planet_drange):
|
|
169
|
-
planets_delta[next_to_b] = -(groups[a][1][2] - planet_drange * 2.5)
|
|
170
|
-
planets_delta[groups[a][1][0]] = +planet_drange * 1.2
|
|
171
|
-
|
|
172
|
-
# Three grouped planets or more
|
|
173
|
-
xl = len(groups[a])
|
|
174
|
-
if xl >= 3:
|
|
175
|
-
available = groups[a][0][1]
|
|
176
|
-
for f in range(xl):
|
|
177
|
-
available += groups[a][f][2]
|
|
178
|
-
need = (3 * planet_drange) + (1.2 * (xl - 1) * planet_drange)
|
|
179
|
-
leftover = available - need
|
|
180
|
-
xa = groups[a][0][1]
|
|
181
|
-
xb = groups[a][(xl - 1)][2]
|
|
182
|
-
|
|
183
|
-
# center
|
|
184
|
-
if (xa > (need * 0.5)) & (xb > (need * 0.5)):
|
|
185
|
-
startA = xa - (need * 0.5)
|
|
186
|
-
# position relative to next planets
|
|
187
|
-
else:
|
|
188
|
-
startA = (leftover / (xa + xb)) * xa
|
|
189
|
-
startB = (leftover / (xa + xb)) * xb
|
|
190
|
-
|
|
191
|
-
if available > need:
|
|
192
|
-
planets_delta[groups[a][0][0]] = startA - groups[a][0][1] + (1.5 * planet_drange)
|
|
193
|
-
for f in range(xl - 1):
|
|
194
|
-
planets_delta[groups[a][(f + 1)][0]] = (
|
|
195
|
-
1.2 * planet_drange + planets_delta[groups[a][f][0]] - groups[a][f][2]
|
|
196
|
-
)
|
|
197
|
-
|
|
198
|
-
for e in range(len(keys)):
|
|
199
|
-
i = planets_degut[keys[e]]
|
|
200
|
-
|
|
201
|
-
# coordinates
|
|
202
|
-
if chart_type == "Transit" or chart_type == "Synastry" or chart_type == "Return":
|
|
203
|
-
if 22 < i < 27:
|
|
204
|
-
rplanet = 76
|
|
205
|
-
elif switch == 1:
|
|
206
|
-
rplanet = 110
|
|
207
|
-
switch = 0
|
|
208
|
-
else:
|
|
209
|
-
rplanet = 130
|
|
210
|
-
switch = 1
|
|
359
|
+
return 110 if is_alternate_position else 130
|
|
360
|
+
elif chart_type == "Synastry":
|
|
361
|
+
if is_chart_angle:
|
|
362
|
+
return 76
|
|
211
363
|
else:
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
offset = (int(main_subject_seventh_house_degree_ut) / -1) + int(points_deg_ut[i] + planets_delta[e])
|
|
232
|
-
trueoffset = (int(main_subject_seventh_house_degree_ut) / -1) + int(points_deg_ut[i])
|
|
233
|
-
|
|
234
|
-
planet_x = sliceToX(0, (radius - rplanet), offset) + rplanet
|
|
235
|
-
planet_y = sliceToY(0, (radius - rplanet), offset) + rplanet
|
|
236
|
-
if chart_type == "Transit" or chart_type == "Synastry" or chart_type == "Return":
|
|
237
|
-
scale = 0.8
|
|
238
|
-
|
|
239
|
-
elif chart_type == "ExternalNatal":
|
|
240
|
-
scale = 0.8
|
|
241
|
-
# line1
|
|
242
|
-
x1 = sliceToX(0, (radius - third_circle_radius), trueoffset) + third_circle_radius
|
|
243
|
-
y1 = sliceToY(0, (radius - third_circle_radius), trueoffset) + third_circle_radius
|
|
244
|
-
x2 = sliceToX(0, (radius - rplanet - 30), trueoffset) + rplanet + 30
|
|
245
|
-
y2 = sliceToY(0, (radius - rplanet - 30), trueoffset) + rplanet + 30
|
|
246
|
-
color = available_planets_setting[i]["color"]
|
|
247
|
-
output += (
|
|
248
|
-
'<line x1="%s" y1="%s" x2="%s" y2="%s" style="stroke-width:1px;stroke:%s;stroke-opacity:.3;"/>\n'
|
|
249
|
-
% (x1, y1, x2, y2, color)
|
|
250
|
-
)
|
|
251
|
-
# line2
|
|
252
|
-
x1 = sliceToX(0, (radius - rplanet - 30), trueoffset) + rplanet + 30
|
|
253
|
-
y1 = sliceToY(0, (radius - rplanet - 30), trueoffset) + rplanet + 30
|
|
254
|
-
x2 = sliceToX(0, (radius - rplanet - 10), offset) + rplanet + 10
|
|
255
|
-
y2 = sliceToY(0, (radius - rplanet - 10), offset) + rplanet + 10
|
|
256
|
-
output += (
|
|
257
|
-
'<line x1="%s" y1="%s" x2="%s" y2="%s" style="stroke-width:1px;stroke:%s;stroke-opacity:.5;"/>\n'
|
|
258
|
-
% (x1, y1, x2, y2, color)
|
|
259
|
-
)
|
|
260
|
-
|
|
364
|
+
return 110 if is_alternate_position else 130
|
|
365
|
+
elif chart_type == "Return":
|
|
366
|
+
if is_chart_angle:
|
|
367
|
+
return 76
|
|
368
|
+
else:
|
|
369
|
+
return 110 if is_alternate_position else 130
|
|
370
|
+
else:
|
|
371
|
+
# Default natal chart and external view handling
|
|
372
|
+
# if 22 < point_idx < 27 it is asc,mc,dsc,ic (angles of chart)
|
|
373
|
+
amin, bmin, cmin = 0, 0, 0
|
|
374
|
+
if external_view:
|
|
375
|
+
amin = 74 - 10
|
|
376
|
+
bmin = 94 - 10
|
|
377
|
+
cmin = 40 - 10
|
|
378
|
+
|
|
379
|
+
if is_chart_angle:
|
|
380
|
+
return 40 - cmin
|
|
381
|
+
elif is_alternate_position:
|
|
382
|
+
return 74 - amin
|
|
261
383
|
else:
|
|
262
|
-
|
|
384
|
+
return 94 - bmin
|
|
263
385
|
|
|
264
|
-
planet_details = available_kerykeion_celestial_points[i]
|
|
265
386
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
387
|
+
def _calculate_point_offset(
|
|
388
|
+
seventh_house_degree: Union[int, float], point_degree: Union[int, float], adjustment: Union[int, float]
|
|
389
|
+
) -> float:
|
|
390
|
+
"""
|
|
391
|
+
Calculate the offset position of a celestial point on the chart.
|
|
269
392
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
switch = 0
|
|
324
|
-
|
|
325
|
-
# Transit planets loop
|
|
326
|
-
for e in range(len(t_keys)):
|
|
327
|
-
if chart_type == "Transit" and available_planets_setting[e]["name"] in TRANSIT_RING_EXCLUDE_POINTS_NAMES:
|
|
328
|
-
continue
|
|
329
|
-
|
|
330
|
-
i = t_planets_degut[t_keys[e]]
|
|
331
|
-
|
|
332
|
-
if 22 < i < 27:
|
|
333
|
-
rplanet = 9
|
|
334
|
-
elif switch == 1:
|
|
335
|
-
rplanet = 18
|
|
336
|
-
switch = 0
|
|
337
|
-
else:
|
|
338
|
-
rplanet = 26
|
|
339
|
-
switch = 1
|
|
340
|
-
|
|
341
|
-
# Transit planet name
|
|
342
|
-
zeropoint = 360 - main_subject_seventh_house_degree_ut
|
|
343
|
-
t_offset = zeropoint + t_points_deg_ut[i]
|
|
344
|
-
if t_offset > 360:
|
|
345
|
-
t_offset = t_offset - 360
|
|
346
|
-
planet_x = sliceToX(0, (radius - rplanet), t_offset) + rplanet
|
|
347
|
-
planet_y = sliceToY(0, (radius - rplanet), t_offset) + rplanet
|
|
348
|
-
output += f'<g class="transit-planet-name" transform="translate(-6,-6)"><g transform="scale(0.5)"><use x="{planet_x*2}" y="{planet_y*2}" xlink:href="#{available_planets_setting[i]["name"]}" /></g></g>'
|
|
349
|
-
|
|
350
|
-
# Transit planet line
|
|
351
|
-
x1 = sliceToX(0, radius + 3, t_offset) - 3
|
|
352
|
-
y1 = sliceToY(0, radius + 3, t_offset) - 3
|
|
353
|
-
x2 = sliceToX(0, radius - 3, t_offset) + 3
|
|
354
|
-
y2 = sliceToY(0, radius - 3, t_offset) + 3
|
|
355
|
-
output += f'<line class="transit-planet-line" x1="{str(x1)}" y1="{str(y1)}" x2="{str(x2)}" y2="{str(y2)}" style="stroke: {available_planets_setting[i]["color"]}; stroke-width: 1px; stroke-opacity:.8;"/>'
|
|
356
|
-
|
|
357
|
-
# transit planet degree text
|
|
358
|
-
rotate = main_subject_first_house_degree_ut - t_points_deg_ut[i]
|
|
359
|
-
textanchor = "end"
|
|
360
|
-
t_offset += group_offset[i]
|
|
361
|
-
rtext = -3.0
|
|
362
|
-
|
|
363
|
-
if -90 > rotate > -270:
|
|
364
|
-
rotate = rotate + 180.0
|
|
365
|
-
textanchor = "start"
|
|
366
|
-
if 270 > rotate > 90:
|
|
367
|
-
rotate = rotate - 180.0
|
|
368
|
-
textanchor = "start"
|
|
369
|
-
|
|
370
|
-
if textanchor == "end":
|
|
371
|
-
xo = 1
|
|
372
|
-
else:
|
|
373
|
-
xo = -1
|
|
374
|
-
deg_x = sliceToX(0, (radius - rtext), t_offset + xo) + rtext
|
|
375
|
-
deg_y = sliceToY(0, (radius - rtext), t_offset + xo) + rtext
|
|
376
|
-
degree = int(t_offset)
|
|
377
|
-
output += f'<g transform="translate({deg_x},{deg_y})">'
|
|
378
|
-
output += f'<text transform="rotate({rotate})" text-anchor="{textanchor}'
|
|
379
|
-
output += f'" style="fill: {available_planets_setting[i]["color"]}; font-size: 10px;">{convert_decimal_to_degree_string(t_points_deg[i], format_type="1")}'
|
|
380
|
-
output += "</text></g>"
|
|
381
|
-
|
|
382
|
-
# check transit
|
|
383
|
-
if chart_type == "Transit" or chart_type == "Synastry" or chart_type == "Return":
|
|
384
|
-
dropin = 36
|
|
385
|
-
else:
|
|
386
|
-
dropin = 0
|
|
393
|
+
Args:
|
|
394
|
+
seventh_house_degree (Union[int, float]): Degree of the seventh house.
|
|
395
|
+
point_degree (Union[int, float]): Degree of the celestial point.
|
|
396
|
+
adjustment (Union[int, float]): Adjustment value to prevent overlapping.
|
|
397
|
+
|
|
398
|
+
Returns:
|
|
399
|
+
float: The calculated offset position.
|
|
400
|
+
"""
|
|
401
|
+
return (int(seventh_house_degree) / -1) + int(point_degree + adjustment)
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
def _draw_external_natal_lines(
|
|
405
|
+
output: str,
|
|
406
|
+
radius: Union[int, float],
|
|
407
|
+
third_circle_radius: Union[int, float],
|
|
408
|
+
point_radius: Union[int, float],
|
|
409
|
+
true_offset: Union[int, float],
|
|
410
|
+
adjusted_offset: Union[int, float],
|
|
411
|
+
color: str,
|
|
412
|
+
) -> str:
|
|
413
|
+
"""
|
|
414
|
+
Draw connecting lines for external view charts.
|
|
415
|
+
|
|
416
|
+
Creates two line segments: one from the circle to the original position,
|
|
417
|
+
and another from the original position to the adjusted position.
|
|
418
|
+
|
|
419
|
+
Args:
|
|
420
|
+
output (str): The SVG output string to append to.
|
|
421
|
+
radius (Union[int, float]): Chart radius.
|
|
422
|
+
third_circle_radius (Union[int, float]): Radius of the third circle.
|
|
423
|
+
point_radius (Union[int, float]): Radius of the celestial point.
|
|
424
|
+
true_offset (Union[int, float]): True position offset.
|
|
425
|
+
adjusted_offset (Union[int, float]): Adjusted position offset.
|
|
426
|
+
color (str): Line color.
|
|
427
|
+
|
|
428
|
+
Returns:
|
|
429
|
+
str: Updated SVG output with added line elements.
|
|
430
|
+
"""
|
|
431
|
+
# First line - from circle to outer position
|
|
432
|
+
x1 = sliceToX(0, radius - third_circle_radius, true_offset) + third_circle_radius
|
|
433
|
+
y1 = sliceToY(0, radius - third_circle_radius, true_offset) + third_circle_radius
|
|
434
|
+
x2 = sliceToX(0, radius - point_radius - 30, true_offset) + point_radius + 30
|
|
435
|
+
y2 = sliceToY(0, radius - point_radius - 30, true_offset) + point_radius + 30
|
|
436
|
+
output += f'<line x1="{x1}" y1="{y1}" x2="{x2}" y2="{y2}" style="stroke-width:1px;stroke:{color};stroke-opacity:.3;"/>\n'
|
|
437
|
+
|
|
438
|
+
# Second line - from outer position to adjusted position
|
|
439
|
+
x1 = x2
|
|
440
|
+
y1 = y2
|
|
441
|
+
x2 = sliceToX(0, radius - point_radius - 10, adjusted_offset) + point_radius + 10
|
|
442
|
+
y2 = sliceToY(0, radius - point_radius - 10, adjusted_offset) + point_radius + 10
|
|
443
|
+
output += f'<line x1="{x1}" y1="{y1}" x2="{x2}" y2="{y2}" style="stroke-width:1px;stroke:{color};stroke-opacity:.5;"/>\n'
|
|
444
|
+
|
|
445
|
+
return output
|
|
387
446
|
|
|
388
|
-
# planet line
|
|
389
|
-
x1 = sliceToX(0, radius - (dropin + 3), offset) + (dropin + 3)
|
|
390
|
-
y1 = sliceToY(0, radius - (dropin + 3), offset) + (dropin + 3)
|
|
391
|
-
x2 = sliceToX(0, (radius - (dropin - 3)), offset) + (dropin - 3)
|
|
392
|
-
y2 = sliceToY(0, (radius - (dropin - 3)), offset) + (dropin - 3)
|
|
393
447
|
|
|
394
|
-
|
|
448
|
+
def _generate_point_svg(point_details: KerykeionPointModel, x: float, y: float, scale: float, point_name: str) -> str:
|
|
449
|
+
"""
|
|
450
|
+
Generate the SVG element for a celestial point.
|
|
451
|
+
|
|
452
|
+
Args:
|
|
453
|
+
point_details (KerykeionPointModel): Details about the celestial point.
|
|
454
|
+
x (float): X-coordinate for the point.
|
|
455
|
+
y (float): Y-coordinate for the point.
|
|
456
|
+
scale (float): Scale factor for the point.
|
|
457
|
+
point_name (str): Name of the celestial point.
|
|
458
|
+
|
|
459
|
+
Returns:
|
|
460
|
+
str: SVG element for the celestial point.
|
|
461
|
+
"""
|
|
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"]}" '
|
|
463
|
+
svg += f'kr:slug="{point_details["name"]}" transform="translate(-{12 * scale},-{12 * scale}) scale({scale})">'
|
|
464
|
+
svg += f'<use x="{x * (1/scale)}" y="{y * (1/scale)}" xlink:href="#{point_name}" />'
|
|
465
|
+
svg += "</g>"
|
|
466
|
+
return svg
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
def _draw_secondary_points(
|
|
470
|
+
output: str,
|
|
471
|
+
radius: Union[int, float],
|
|
472
|
+
first_house_degree: Union[int, float],
|
|
473
|
+
seventh_house_degree: Union[int, float],
|
|
474
|
+
points_abs_positions: list[Union[int, float]],
|
|
475
|
+
points_rel_positions: list[Union[int, float]],
|
|
476
|
+
points_settings: list[KerykeionSettingsCelestialPointModel],
|
|
477
|
+
chart_type: str,
|
|
478
|
+
exclude_points: list[str],
|
|
479
|
+
main_offset: float,
|
|
480
|
+
) -> str:
|
|
481
|
+
"""
|
|
482
|
+
Draw secondary celestial points (transit/synastry/return) on the chart.
|
|
483
|
+
|
|
484
|
+
Args:
|
|
485
|
+
output (str): Current SVG output to append to.
|
|
486
|
+
radius (Union[int, float]): Chart radius.
|
|
487
|
+
first_house_degree (Union[int, float]): Degree of the first house.
|
|
488
|
+
seventh_house_degree (Union[int, float]): Degree of the seventh house.
|
|
489
|
+
points_abs_positions (list[Union[int, float]]): Absolute positions of points.
|
|
490
|
+
points_rel_positions (list[Union[int, float]]): Relative positions of points.
|
|
491
|
+
points_settings (list[KerykeionSettingsCelestialPointModel]): Settings for points.
|
|
492
|
+
chart_type (str): Type of chart.
|
|
493
|
+
exclude_points (list[str]): List of point names to exclude.
|
|
494
|
+
main_offset (float): Offset position for the main point.
|
|
395
495
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
496
|
+
Returns:
|
|
497
|
+
str: Updated SVG output with added secondary points.
|
|
498
|
+
"""
|
|
499
|
+
# Initialize position adjustments for grouped points
|
|
500
|
+
position_adjustments: dict[int, float] = {i: 0.0 for i in range(len(points_settings))}
|
|
501
|
+
|
|
502
|
+
# Map absolute position to point index
|
|
503
|
+
position_index_map = {}
|
|
504
|
+
for i in range(len(points_settings)):
|
|
505
|
+
if chart_type == "Transit" and points_settings[i]["name"] in exclude_points:
|
|
506
|
+
continue
|
|
507
|
+
position_index_map[points_abs_positions[i]] = i
|
|
508
|
+
|
|
509
|
+
# Sort positions
|
|
510
|
+
sorted_positions = sorted(position_index_map.keys())
|
|
511
|
+
|
|
512
|
+
# Find groups of points that are close to each other
|
|
513
|
+
point_groups: List[List[int]] = []
|
|
514
|
+
in_group = False
|
|
515
|
+
|
|
516
|
+
for pos_idx, abs_position in enumerate(sorted_positions):
|
|
517
|
+
point_a_idx = position_index_map[abs_position]
|
|
518
|
+
|
|
519
|
+
# Get next point
|
|
520
|
+
if pos_idx == len(sorted_positions) - 1:
|
|
521
|
+
point_b_idx = position_index_map[sorted_positions[0]]
|
|
399
522
|
else:
|
|
400
|
-
|
|
523
|
+
point_b_idx = position_index_map[sorted_positions[pos_idx + 1]]
|
|
524
|
+
|
|
525
|
+
# Check distance between points
|
|
526
|
+
position_a = points_abs_positions[point_a_idx]
|
|
527
|
+
position_b = points_abs_positions[point_b_idx]
|
|
528
|
+
distance = degreeDiff(position_a, position_b)
|
|
401
529
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
530
|
+
# Group points that are close
|
|
531
|
+
if distance <= 2.5:
|
|
532
|
+
if in_group:
|
|
533
|
+
point_groups[-1].append(point_b_idx)
|
|
534
|
+
else:
|
|
535
|
+
point_groups.append([point_a_idx])
|
|
536
|
+
point_groups[-1].append(point_b_idx)
|
|
537
|
+
in_group = True
|
|
538
|
+
else:
|
|
539
|
+
in_group = False
|
|
540
|
+
|
|
541
|
+
# Set position adjustments for grouped points
|
|
542
|
+
for group in point_groups:
|
|
543
|
+
if len(group) == 2:
|
|
544
|
+
position_adjustments[group[0]] = -1.0
|
|
545
|
+
position_adjustments[group[1]] = 1.0
|
|
546
|
+
elif len(group) == 3:
|
|
547
|
+
position_adjustments[group[0]] = -1.5
|
|
548
|
+
position_adjustments[group[1]] = 0.0
|
|
549
|
+
position_adjustments[group[2]] = 1.5
|
|
550
|
+
elif len(group) == 4:
|
|
551
|
+
position_adjustments[group[0]] = -2.0
|
|
552
|
+
position_adjustments[group[1]] = -1.0
|
|
553
|
+
position_adjustments[group[2]] = 1.0
|
|
554
|
+
position_adjustments[group[3]] = 2.0
|
|
555
|
+
|
|
556
|
+
# Draw each secondary point
|
|
557
|
+
alternate_position = False
|
|
558
|
+
point_idx = 0 # Initialize for use outside loop
|
|
559
|
+
|
|
560
|
+
for pos_idx, abs_position in enumerate(sorted_positions):
|
|
561
|
+
point_idx = position_index_map[abs_position]
|
|
562
|
+
|
|
563
|
+
if chart_type == "Transit" and points_settings[point_idx]["name"] in exclude_points:
|
|
564
|
+
continue
|
|
565
|
+
|
|
566
|
+
# Determine radius based on point type
|
|
567
|
+
if 22 < point_idx < 27: # Chart angles
|
|
568
|
+
point_radius = 9
|
|
569
|
+
elif alternate_position:
|
|
570
|
+
point_radius = 18
|
|
571
|
+
alternate_position = False
|
|
572
|
+
else:
|
|
573
|
+
point_radius = 26
|
|
574
|
+
alternate_position = True
|
|
575
|
+
|
|
576
|
+
# Calculate position
|
|
577
|
+
zero_point = 360 - seventh_house_degree
|
|
578
|
+
point_offset = zero_point + points_abs_positions[point_idx]
|
|
579
|
+
if point_offset > 360:
|
|
580
|
+
point_offset -= 360
|
|
581
|
+
|
|
582
|
+
# Draw point symbol
|
|
583
|
+
point_x = sliceToX(0, radius - point_radius, point_offset) + point_radius
|
|
584
|
+
point_y = sliceToY(0, radius - point_radius, point_offset) + point_radius
|
|
585
|
+
output += '<g class="transit-planet-name" transform="translate(-6,-6)"><g transform="scale(0.5)">'
|
|
586
|
+
output += f'<use x="{point_x*2}" y="{point_y*2}" xlink:href="#{points_settings[point_idx]["name"]}" /></g></g>'
|
|
587
|
+
|
|
588
|
+
# Draw connecting line
|
|
589
|
+
x1 = sliceToX(0, radius + 3, point_offset) - 3
|
|
590
|
+
y1 = sliceToY(0, radius + 3, point_offset) - 3
|
|
591
|
+
x2 = sliceToX(0, radius - 3, point_offset) + 3
|
|
592
|
+
y2 = sliceToY(0, radius - 3, point_offset) + 3
|
|
593
|
+
output += f'<line class="transit-planet-line" x1="{x1}" y1="{y1}" x2="{x2}" y2="{y2}" '
|
|
594
|
+
output += f'style="stroke: {points_settings[point_idx]["color"]}; stroke-width: 1px; stroke-opacity:.8;"/>'
|
|
595
|
+
|
|
596
|
+
# Draw degree text with rotation
|
|
597
|
+
rotation = first_house_degree - points_abs_positions[point_idx]
|
|
598
|
+
text_anchor = "end"
|
|
599
|
+
|
|
600
|
+
# Adjust text rotation and anchor for readability
|
|
601
|
+
if -90 > rotation > -270:
|
|
602
|
+
rotation += 180.0
|
|
603
|
+
text_anchor = "start"
|
|
604
|
+
if 270 > rotation > 90:
|
|
605
|
+
rotation -= 180.0
|
|
606
|
+
text_anchor = "start"
|
|
607
|
+
|
|
608
|
+
# Position the degree text
|
|
609
|
+
x_offset = 1 if text_anchor == "end" else -1
|
|
610
|
+
adjusted_point_offset = point_offset + position_adjustments[point_idx]
|
|
611
|
+
text_radius = -3.0
|
|
612
|
+
|
|
613
|
+
deg_x = sliceToX(0, radius - text_radius, adjusted_point_offset + x_offset) + text_radius
|
|
614
|
+
deg_y = sliceToY(0, radius - text_radius, adjusted_point_offset + x_offset) + text_radius
|
|
615
|
+
|
|
616
|
+
# Format and output the degree text
|
|
617
|
+
degree_text = convert_decimal_to_degree_string(points_rel_positions[point_idx], format_type="1")
|
|
618
|
+
output += f'<g transform="translate({deg_x},{deg_y})">'
|
|
619
|
+
output += f'<text transform="rotate({rotation})" text-anchor="{text_anchor}" '
|
|
620
|
+
output += f'style="fill: {points_settings[point_idx]["color"]}; font-size: 10px;">{degree_text}</text></g>'
|
|
621
|
+
|
|
622
|
+
# Draw connecting lines for the main point
|
|
623
|
+
dropin = 0
|
|
624
|
+
if chart_type == "Transit":
|
|
625
|
+
dropin = 36
|
|
626
|
+
elif chart_type == "Synastry":
|
|
627
|
+
dropin = 36
|
|
628
|
+
elif chart_type == "Return":
|
|
629
|
+
dropin = 36
|
|
630
|
+
|
|
631
|
+
# First connecting line segment
|
|
632
|
+
x1 = sliceToX(0, radius - (dropin + 3), main_offset) + (dropin + 3)
|
|
633
|
+
y1 = sliceToY(0, radius - (dropin + 3), main_offset) + (dropin + 3)
|
|
634
|
+
x2 = sliceToX(0, radius - (dropin - 3), main_offset) + (dropin - 3)
|
|
635
|
+
y2 = sliceToY(0, radius - (dropin - 3), main_offset) + (dropin - 3)
|
|
636
|
+
|
|
637
|
+
point_color = points_settings[point_idx]["color"]
|
|
638
|
+
output += f'<line x1="{x1}" y1="{y1}" x2="{x2}" y2="{y2}" '
|
|
639
|
+
output += f'style="stroke: {point_color}; stroke-width: 2px; stroke-opacity:.6;"/>'
|
|
640
|
+
|
|
641
|
+
# Second connecting line segment
|
|
642
|
+
dropin = 120
|
|
643
|
+
if chart_type == "Transit":
|
|
644
|
+
dropin = 160
|
|
645
|
+
elif chart_type == "Synastry":
|
|
646
|
+
dropin = 160
|
|
647
|
+
elif chart_type == "Return":
|
|
648
|
+
dropin = 160
|
|
649
|
+
|
|
650
|
+
x1 = sliceToX(0, radius - dropin, main_offset) + dropin
|
|
651
|
+
y1 = sliceToY(0, radius - dropin, main_offset) + dropin
|
|
652
|
+
x2 = sliceToX(0, radius - (dropin - 3), main_offset) + (dropin - 3)
|
|
653
|
+
y2 = sliceToY(0, radius - (dropin - 3), main_offset) + (dropin - 3)
|
|
654
|
+
|
|
655
|
+
output += f'<line x1="{x1}" y1="{y1}" x2="{x2}" y2="{y2}" '
|
|
656
|
+
output += f'style="stroke: {point_color}; stroke-width: 2px; stroke-opacity:.6;"/>'
|
|
407
657
|
|
|
408
658
|
return output
|