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.

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