kerykeion 4.26.2__py3-none-any.whl → 5.0.0a2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of kerykeion might be problematic. Click here for more details.

Files changed (49) hide show
  1. kerykeion/__init__.py +9 -7
  2. kerykeion/aspects/aspects_utils.py +14 -8
  3. kerykeion/aspects/natal_aspects.py +26 -17
  4. kerykeion/aspects/synastry_aspects.py +32 -15
  5. kerykeion/aspects/transits_time_range.py +2 -2
  6. kerykeion/astrological_subject_factory.py +1132 -0
  7. kerykeion/charts/charts_utils.py +676 -146
  8. kerykeion/charts/draw_planets.py +9 -8
  9. kerykeion/charts/draw_planets_v2.py +639 -0
  10. kerykeion/charts/kerykeion_chart_svg.py +1334 -601
  11. kerykeion/charts/templates/chart.xml +184 -78
  12. kerykeion/charts/templates/wheel_only.xml +13 -12
  13. kerykeion/charts/themes/classic.css +91 -76
  14. kerykeion/charts/themes/dark-high-contrast.css +129 -107
  15. kerykeion/charts/themes/dark.css +130 -107
  16. kerykeion/charts/themes/light.css +130 -103
  17. kerykeion/charts/themes/strawberry.css +143 -0
  18. kerykeion/composite_subject_factory.py +26 -43
  19. kerykeion/ephemeris_data.py +6 -10
  20. kerykeion/house_comparison/__init__.py +3 -0
  21. kerykeion/house_comparison/house_comparison_factory.py +70 -0
  22. kerykeion/house_comparison/house_comparison_models.py +38 -0
  23. kerykeion/house_comparison/house_comparison_utils.py +98 -0
  24. kerykeion/kr_types/chart_types.py +13 -5
  25. kerykeion/kr_types/kr_literals.py +34 -6
  26. kerykeion/kr_types/kr_models.py +122 -160
  27. kerykeion/kr_types/settings_models.py +107 -143
  28. kerykeion/planetary_return_factory.py +299 -0
  29. kerykeion/{relationship_score/relationship_score_factory.py → relationship_score_factory.py} +10 -13
  30. kerykeion/report.py +4 -4
  31. kerykeion/settings/config_constants.py +35 -6
  32. kerykeion/settings/kerykeion_settings.py +1 -0
  33. kerykeion/settings/kr.config.json +1301 -1255
  34. kerykeion/settings/legacy/__init__.py +0 -0
  35. kerykeion/settings/legacy/legacy_celestial_points_settings.py +299 -0
  36. kerykeion/settings/legacy/legacy_chart_aspects_settings.py +71 -0
  37. kerykeion/settings/legacy/legacy_color_settings.py +42 -0
  38. kerykeion/transits_time_range.py +13 -9
  39. kerykeion/utilities.py +228 -31
  40. {kerykeion-4.26.2.dist-info → kerykeion-5.0.0a2.dist-info}/METADATA +119 -107
  41. kerykeion-5.0.0a2.dist-info/RECORD +54 -0
  42. {kerykeion-4.26.2.dist-info → kerykeion-5.0.0a2.dist-info}/WHEEL +1 -1
  43. kerykeion/astrological_subject.py +0 -841
  44. kerykeion/relationship_score/__init__.py +0 -2
  45. kerykeion/relationship_score/relationship_score.py +0 -175
  46. kerykeion-4.26.2.dist-info/LICENSE +0 -661
  47. kerykeion-4.26.2.dist-info/RECORD +0 -46
  48. /LICENSE → /kerykeion-5.0.0a2.dist-info/LICENSE +0 -0
  49. {kerykeion-4.26.2.dist-info → kerykeion-5.0.0a2.dist-info}/entry_points.txt +0 -0
@@ -6,17 +6,34 @@
6
6
 
7
7
  import logging
8
8
  import swisseph as swe
9
- from typing import get_args
9
+ from typing import get_args, Union, Optional
10
10
 
11
11
  from kerykeion.settings.kerykeion_settings import get_settings
12
12
  from kerykeion.aspects.synastry_aspects import SynastryAspects
13
13
  from kerykeion.aspects.natal_aspects import NatalAspects
14
- from kerykeion.astrological_subject import AstrologicalSubject
15
- from kerykeion.kr_types import KerykeionException, ChartType, KerykeionPointModel, Sign, ActiveAspect
14
+ from kerykeion.house_comparison.house_comparison_factory import HouseComparisonFactory
15
+ from kerykeion.kr_types import (
16
+ KerykeionException,
17
+ ChartType,
18
+ Sign,
19
+ ActiveAspect,
20
+ )
16
21
  from kerykeion.kr_types import ChartTemplateDictionary
17
- from kerykeion.kr_types.kr_models import AstrologicalSubjectModel, CompositeSubjectModel
18
- from kerykeion.kr_types.settings_models import KerykeionSettingsCelestialPointModel, KerykeionSettingsModel
19
- from kerykeion.kr_types.kr_literals import KerykeionChartTheme, KerykeionChartLanguage, AxialCusps, Planet
22
+ from kerykeion.kr_types.kr_models import (
23
+ AstrologicalSubjectModel,
24
+ CompositeSubjectModel,
25
+ PlanetReturnModel,
26
+ )
27
+ from kerykeion.kr_types.settings_models import (
28
+ KerykeionSettingsCelestialPointModel,
29
+ KerykeionSettingsModel,
30
+ )
31
+ from kerykeion.kr_types.kr_literals import (
32
+ KerykeionChartTheme,
33
+ KerykeionChartLanguage,
34
+ AstrologicalPoint,
35
+ )
36
+ from kerykeion.utilities import find_common_active_points
20
37
  from kerykeion.charts.charts_utils import (
21
38
  draw_zodiac_slice,
22
39
  convert_latitude_coordinate_to_string,
@@ -26,25 +43,39 @@ from kerykeion.charts.charts_utils import (
26
43
  draw_degree_ring,
27
44
  draw_transit_ring,
28
45
  draw_first_circle,
46
+ draw_house_comparison_grid,
29
47
  draw_second_circle,
30
48
  draw_third_circle,
31
49
  draw_aspect_grid,
32
50
  draw_houses_cusps_and_text_number,
33
51
  draw_transit_aspect_list,
34
52
  draw_transit_aspect_grid,
35
- calculate_moon_phase_chart_params,
36
- draw_house_grid,
37
- draw_planet_grid,
53
+ draw_single_house_comparison_grid,
54
+ makeLunarPhase,
55
+ draw_main_house_grid,
56
+ draw_secondary_house_grid,
57
+ draw_main_planet_grid,
58
+ draw_secondary_planet_grid,
59
+ format_location_string,
60
+ format_datetime_with_timezone,
61
+ calculate_element_points,
62
+ calculate_synastry_element_points,
63
+ calculate_quality_points,
64
+ calculate_synastry_quality_points
38
65
  )
39
- from kerykeion.charts.draw_planets import draw_planets # type: ignore
66
+ from kerykeion.charts.draw_planets_v2 import draw_planets_v2 as draw_planets
40
67
  from kerykeion.utilities import get_houses_list, inline_css_variables_in_svg
41
- from kerykeion.settings.config_constants import DEFAULT_ACTIVE_POINTS, DEFAULT_ACTIVE_ASPECTS
68
+ from kerykeion.settings.config_constants import DEFAULT_ACTIVE_ASPECTS
69
+ from kerykeion.settings.legacy.legacy_color_settings import DEFAULT_CHART_COLORS
70
+ from kerykeion.settings.legacy.legacy_celestial_points_settings import DEFAULT_CELESTIAL_POINTS_SETTINGS
71
+ from kerykeion.settings.legacy.legacy_chart_aspects_settings import DEFAULT_CHART_ASPECTS_SETTINGS
42
72
  from pathlib import Path
43
73
  from scour.scour import scourString
44
74
  from string import Template
45
75
  from typing import Union, List, Literal
46
76
  from datetime import datetime
47
77
 
78
+
48
79
  class KerykeionChartSVG:
49
80
  """
50
81
  KerykeionChartSVG generates astrological chart visualizations as SVG files.
@@ -77,7 +108,7 @@ class KerykeionChartSVG:
77
108
  Specifies rendering style for double-chart aspect grids. Defaults to 'list'.
78
109
  chart_language (KerykeionChartLanguage, optional):
79
110
  Language code for chart labels. Defaults to 'EN'.
80
- active_points (list[Planet | AxialCusps], optional):
111
+ active_points (list[AstrologicalPoint], optional):
81
112
  List of celestial points and angles to include. Defaults to DEFAULT_ACTIVE_POINTS.
82
113
  Example:
83
114
  ["Sun", "Moon", "Mercury", "Venus"]
@@ -120,19 +151,21 @@ class KerykeionChartSVG:
120
151
  """
121
152
 
122
153
  # Constants
123
- _BASIC_CHART_VIEWBOX = "0 0 820 550.0"
124
- _WIDE_CHART_VIEWBOX = "0 0 1200 546.0"
125
- _TRANSIT_CHART_WITH_TABLE_VIWBOX = "0 0 960 546.0"
126
154
 
127
155
  _DEFAULT_HEIGHT = 550
128
156
  _DEFAULT_FULL_WIDTH = 1200
129
- _DEFAULT_NATAL_WIDTH = 820
130
- _DEFAULT_FULL_WIDTH_WITH_TABLE = 960
131
- _PLANET_IN_ZODIAC_EXTRA_POINTS = 10
157
+ _DEFAULT_NATAL_WIDTH = 870
158
+ _DEFAULT_FULL_WIDTH_WITH_TABLE = 1200
159
+ _DEFAULT_ULTRA_WIDE_WIDTH = 1270
160
+
161
+ _BASIC_CHART_VIEWBOX = f"0 0 {_DEFAULT_NATAL_WIDTH} {_DEFAULT_HEIGHT}"
162
+ _WIDE_CHART_VIEWBOX = f"0 0 {_DEFAULT_FULL_WIDTH} 546.0"
163
+ _ULTRA_WIDE_CHART_VIEWBOX = f"0 0 {_DEFAULT_ULTRA_WIDE_WIDTH} 546.0"
164
+ _TRANSIT_CHART_WITH_TABLE_VIWBOX = f"0 0 {_DEFAULT_FULL_WIDTH_WITH_TABLE} 546.0"
132
165
 
133
166
  # Set at init
134
- first_obj: Union[AstrologicalSubject, AstrologicalSubjectModel]
135
- second_obj: Union[AstrologicalSubject, AstrologicalSubjectModel, None]
167
+ first_obj: Union[AstrologicalSubjectModel, CompositeSubjectModel, PlanetReturnModel]
168
+ second_obj: Union[AstrologicalSubjectModel, PlanetReturnModel, None]
136
169
  chart_type: ChartType
137
170
  new_output_directory: Union[Path, None]
138
171
  new_settings_file: Union[Path, None, KerykeionSettingsModel, dict]
@@ -141,7 +174,7 @@ class KerykeionChartSVG:
141
174
  theme: Union[KerykeionChartTheme, None]
142
175
  double_chart_aspect_grid_type: Literal["list", "table"]
143
176
  chart_language: KerykeionChartLanguage
144
- active_points: List[Union[Planet, AxialCusps]]
177
+ active_points: List[AstrologicalPoint]
145
178
  active_aspects: List[ActiveAspect]
146
179
 
147
180
  # Internal properties
@@ -157,7 +190,6 @@ class KerykeionChartSVG:
157
190
  chart_colors_settings: dict
158
191
  planets_settings: dict
159
192
  aspects_settings: dict
160
- user: Union[AstrologicalSubject, AstrologicalSubjectModel, CompositeSubjectModel]
161
193
  available_planets_setting: List[KerykeionSettingsCelestialPointModel]
162
194
  height: float
163
195
  location: str
@@ -167,26 +199,30 @@ class KerykeionChartSVG:
167
199
 
168
200
  def __init__(
169
201
  self,
170
- first_obj: Union[AstrologicalSubject, AstrologicalSubjectModel, CompositeSubjectModel],
202
+ first_obj: Union[AstrologicalSubjectModel, CompositeSubjectModel, PlanetReturnModel],
171
203
  chart_type: ChartType = "Natal",
172
- second_obj: Union[AstrologicalSubject, AstrologicalSubjectModel, None] = None,
204
+ second_obj: Union[AstrologicalSubjectModel, PlanetReturnModel, None] = None,
173
205
  new_output_directory: Union[str, None] = None,
174
206
  new_settings_file: Union[Path, None, KerykeionSettingsModel, dict] = None,
175
207
  theme: Union[KerykeionChartTheme, None] = "classic",
176
208
  double_chart_aspect_grid_type: Literal["list", "table"] = "list",
177
209
  chart_language: KerykeionChartLanguage = "EN",
178
- active_points: List[Union[Planet, AxialCusps]] = DEFAULT_ACTIVE_POINTS,
179
- active_aspects: List[ActiveAspect] = DEFAULT_ACTIVE_ASPECTS,
210
+ active_points: Optional[list[AstrologicalPoint]] = None,
211
+ active_aspects: list[ActiveAspect]= DEFAULT_ACTIVE_ASPECTS,
212
+ *,
213
+ colors_settings: dict = DEFAULT_CHART_COLORS,
214
+ celestial_points_settings: list[dict] = DEFAULT_CELESTIAL_POINTS_SETTINGS,
215
+ aspects_settings: list[dict] = DEFAULT_CHART_ASPECTS_SETTINGS,
180
216
  ):
181
217
  """
182
218
  Initialize the chart generator with subject data and configuration options.
183
219
 
184
220
  Args:
185
- first_obj (AstrologicalSubject, AstrologicalSubjectModel, or CompositeSubjectModel):
221
+ first_obj (AstrologicalSubjectModel, or CompositeSubjectModel):
186
222
  Primary astrological subject instance.
187
223
  chart_type (ChartType, optional):
188
224
  Type of chart to generate (e.g., 'Natal', 'Transit').
189
- second_obj (AstrologicalSubject or AstrologicalSubjectModel, optional):
225
+ second_obj (AstrologicalSubject, optional):
190
226
  Secondary subject for Transit or Synastry charts.
191
227
  new_output_directory (str or Path, optional):
192
228
  Base directory to save generated SVG files.
@@ -198,138 +234,341 @@ class KerykeionChartSVG:
198
234
  Layout style for double-chart aspect grids ('list' or 'table').
199
235
  chart_language (KerykeionChartLanguage, optional):
200
236
  Language code for chart labels (e.g., 'EN', 'IT').
201
- active_points (List[Planet or AxialCusps], optional):
237
+ active_points (List[AstrologicalPoint], optional):
202
238
  Celestial points to include in the chart visualization.
203
239
  active_aspects (List[ActiveAspect], optional):
204
240
  Aspects to calculate, each defined by name and orb.
205
241
  """
242
+ # --------------------
243
+ # COMMON INITIALIZATION
244
+ # --------------------
206
245
  home_directory = Path.home()
207
246
  self.new_settings_file = new_settings_file
208
247
  self.chart_language = chart_language
209
248
  self.active_points = active_points
210
249
  self.active_aspects = active_aspects
250
+ self.chart_type = chart_type
251
+ self.double_chart_aspect_grid_type = double_chart_aspect_grid_type
252
+ self.chart_colors_settings = colors_settings
253
+ self.planets_settings = celestial_points_settings
254
+ self.aspects_settings = aspects_settings
211
255
 
256
+ if not active_points:
257
+ active_points = first_obj.active_points
258
+ else:
259
+ active_points = find_common_active_points(
260
+ active_points,
261
+ first_obj.active_points
262
+ )
263
+
264
+ if second_obj:
265
+ active_points = find_common_active_points(
266
+ active_points,
267
+ second_obj.active_points
268
+ )
269
+
270
+ # Set output directory
212
271
  if new_output_directory:
213
272
  self.output_directory = Path(new_output_directory)
214
273
  else:
215
274
  self.output_directory = home_directory
216
275
 
276
+ # Load settings
217
277
  self.parse_json_settings(new_settings_file)
218
- self.chart_type = chart_type
219
278
 
220
- # Kerykeion instance
221
- self.user = first_obj
279
+ # Primary subject
280
+ self.first_obj = first_obj
222
281
 
282
+ # Default radius for all charts
283
+ self.main_radius = 240
284
+
285
+ # Configure available planets
223
286
  self.available_planets_setting = []
224
287
  for body in self.planets_settings:
225
- if body["name"] not in active_points:
226
- continue
227
- else:
288
+ if body["name"] in active_points:
228
289
  body["is_active"] = True
290
+ self.available_planets_setting.append(body)
229
291
 
230
- self.available_planets_setting.append(body)
292
+ # Set available celestial points
293
+ available_celestial_points_names = [body["name"].lower() for body in self.available_planets_setting]
294
+ self.available_kerykeion_celestial_points = []
295
+ for body in available_celestial_points_names:
296
+ self.available_kerykeion_celestial_points.append(self.first_obj.get(body))
231
297
 
232
- # Available bodies
233
- available_celestial_points_names = []
234
- for body in self.available_planets_setting:
235
- available_celestial_points_names.append(body["name"].lower())
298
+ # ------------------------
299
+ # CHART TYPE SPECIFIC SETUP
300
+ # ------------------------
236
301
 
237
- self.available_kerykeion_celestial_points: list[KerykeionPointModel] = []
238
- for body in available_celestial_points_names:
239
- self.available_kerykeion_celestial_points.append(self.user.get(body))
302
+ if self.chart_type in ["Natal", "ExternalNatal"]:
303
+ # --- NATAL / EXTERNAL NATAL CHART SETUP ---
240
304
 
241
- # Makes the sign number list.
242
- if self.chart_type == "Natal" or self.chart_type == "ExternalNatal":
305
+ # Validate Subject
306
+ if not isinstance(self.first_obj, AstrologicalSubjectModel):
307
+ raise KerykeionException("First object must be an AstrologicalSubjectModel or AstrologicalSubject instance.")
308
+
309
+ # Calculate aspects
243
310
  natal_aspects_instance = NatalAspects(
244
- self.user, new_settings_file=self.new_settings_file,
311
+ self.first_obj,
312
+ new_settings_file=self.new_settings_file,
245
313
  active_points=active_points,
246
314
  active_aspects=active_aspects,
247
315
  )
248
316
  self.aspects_list = natal_aspects_instance.relevant_aspects
249
317
 
250
- elif self.chart_type == "Transit" or self.chart_type == "Synastry":
251
- if not second_obj:
252
- raise KerykeionException("Second object is required for Transit or Synastry charts.")
253
-
254
- # Kerykeion instance
255
- self.t_user = second_obj
318
+ # Screen size
319
+ self.height = self._DEFAULT_HEIGHT
320
+ self.width = self._DEFAULT_NATAL_WIDTH
256
321
 
257
- # Aspects
258
- if self.chart_type == "Transit":
259
- synastry_aspects_instance = SynastryAspects(
260
- self.t_user,
261
- self.user,
262
- new_settings_file=self.new_settings_file,
263
- active_points=active_points,
264
- active_aspects=active_aspects,
265
- )
322
+ # Location and coordinates
323
+ self.location = self.first_obj.city
324
+ self.geolat = self.first_obj.lat
325
+ self.geolon = self.first_obj.lng
266
326
 
327
+ # Circle radii
328
+ if self.chart_type == "ExternalNatal":
329
+ self.first_circle_radius = 56
330
+ self.second_circle_radius = 92
331
+ self.third_circle_radius = 112
267
332
  else:
268
- synastry_aspects_instance = SynastryAspects(
269
- self.user,
270
- self.t_user,
271
- new_settings_file=self.new_settings_file,
272
- active_points=active_points,
273
- active_aspects=active_aspects,
274
- )
333
+ self.first_circle_radius = 0
334
+ self.second_circle_radius = 36
335
+ self.third_circle_radius = 120
336
+
337
+ elif self.chart_type == "Composite":
338
+ # --- COMPOSITE CHART SETUP ---
339
+
340
+ # Validate Subject
341
+ if not isinstance(self.first_obj, CompositeSubjectModel):
342
+ raise KerykeionException("First object must be a CompositeSubjectModel instance.")
343
+
344
+ # Calculate aspects
345
+ self.aspects_list = NatalAspects(self.first_obj, new_settings_file=self.new_settings_file, active_points=active_points).relevant_aspects
346
+
347
+ # Screen size
348
+ self.height = self._DEFAULT_HEIGHT
349
+ self.width = self._DEFAULT_NATAL_WIDTH
275
350
 
351
+ # Location and coordinates (average of both subjects)
352
+ self.location = ""
353
+ self.geolat = (self.first_obj.first_subject.lat + self.first_obj.second_subject.lat) / 2
354
+ self.geolon = (self.first_obj.first_subject.lng + self.first_obj.second_subject.lng) / 2
355
+
356
+ # Circle radii
357
+ self.first_circle_radius = 0
358
+ self.second_circle_radius = 36
359
+ self.third_circle_radius = 120
360
+
361
+ elif self.chart_type == "Transit":
362
+ # --- TRANSIT CHART SETUP ---
363
+
364
+ # Validate Subjects
365
+ if not second_obj:
366
+ raise KerykeionException("Second object is required for Transit charts.")
367
+ if not isinstance(self.first_obj, AstrologicalSubjectModel):
368
+ raise KerykeionException("First object must be an AstrologicalSubjectModel or AstrologicalSubject instance.")
369
+ if not isinstance(second_obj, AstrologicalSubjectModel):
370
+ raise KerykeionException("Second object must be an AstrologicalSubjectModel or AstrologicalSubject instance.")
371
+
372
+ # Secondary subject setup
373
+ self.second_obj = second_obj
374
+
375
+ # Calculate aspects (transit to natal)
376
+ synastry_aspects_instance = SynastryAspects(
377
+ self.first_obj,
378
+ self.second_obj,
379
+ new_settings_file=self.new_settings_file,
380
+ active_points=active_points,
381
+ active_aspects=active_aspects,
382
+ )
276
383
  self.aspects_list = synastry_aspects_instance.relevant_aspects
277
384
 
385
+ # Secondary subject available points
278
386
  self.t_available_kerykeion_celestial_points = []
279
387
  for body in available_celestial_points_names:
280
- self.t_available_kerykeion_celestial_points.append(self.t_user.get(body))
388
+ self.t_available_kerykeion_celestial_points.append(self.second_obj.get(body))
281
389
 
282
- elif self.chart_type == "Composite":
283
- if not isinstance(first_obj, CompositeSubjectModel):
284
- raise KerykeionException("First object must be a CompositeSubjectModel instance.")
390
+ # Screen size
391
+ self.height = self._DEFAULT_HEIGHT
392
+ if self.double_chart_aspect_grid_type == "table":
393
+ self.width = self._DEFAULT_FULL_WIDTH_WITH_TABLE
394
+ else:
395
+ self.width = self._DEFAULT_FULL_WIDTH
396
+
397
+ # Location and coordinates (from transit subject)
398
+ self.location = self.second_obj.city
399
+ self.geolat = self.second_obj.lat
400
+ self.geolon = self.second_obj.lng
401
+ self.t_name = self.language_settings["transit_name"]
285
402
 
286
- self.aspects_list = NatalAspects(self.user, new_settings_file=self.new_settings_file, active_points=active_points).relevant_aspects
403
+ # Circle radii
404
+ self.first_circle_radius = 0
405
+ self.second_circle_radius = 36
406
+ self.third_circle_radius = 120
287
407
 
288
- # Double chart aspect grid type
289
- self.double_chart_aspect_grid_type = double_chart_aspect_grid_type
408
+ elif self.chart_type == "Synastry":
409
+ # --- SYNASTRY CHART SETUP ---
410
+
411
+ # Validate Subjects
412
+ if not second_obj:
413
+ raise KerykeionException("Second object is required for Synastry charts.")
414
+ if not isinstance(self.first_obj, AstrologicalSubjectModel):
415
+ raise KerykeionException("First object must be an AstrologicalSubjectModel or AstrologicalSubject instance.")
416
+ if not isinstance(second_obj, AstrologicalSubjectModel):
417
+ raise KerykeionException("Second object must be an AstrologicalSubjectModel or AstrologicalSubject instance.")
418
+
419
+ # Secondary subject setup
420
+ self.second_obj = second_obj
421
+
422
+ # Calculate aspects (natal to partner)
423
+ synastry_aspects_instance = SynastryAspects(
424
+ self.first_obj,
425
+ self.second_obj,
426
+ new_settings_file=self.new_settings_file,
427
+ active_points=active_points,
428
+ active_aspects=active_aspects,
429
+ )
430
+ self.aspects_list = synastry_aspects_instance.relevant_aspects
290
431
 
291
- # screen size
292
- self.height = self._DEFAULT_HEIGHT
293
- if self.chart_type == "Synastry" or self.chart_type == "Transit":
432
+ # Secondary subject available points
433
+ self.t_available_kerykeion_celestial_points = []
434
+ for body in available_celestial_points_names:
435
+ self.t_available_kerykeion_celestial_points.append(self.second_obj.get(body))
436
+
437
+ # Screen size
438
+ self.height = self._DEFAULT_HEIGHT
294
439
  self.width = self._DEFAULT_FULL_WIDTH
295
- elif self.double_chart_aspect_grid_type == "table" and self.chart_type == "Transit":
296
- self.width = self._DEFAULT_FULL_WIDTH_WITH_TABLE
297
- else:
298
- self.width = self._DEFAULT_NATAL_WIDTH
299
440
 
300
- if self.chart_type in ["Natal", "ExternalNatal", "Synastry"]:
301
- self.location = self.user.city
302
- self.geolat = self.user.lat
303
- self.geolon = self.user.lng
441
+ # Location and coordinates (from primary subject)
442
+ self.location = self.first_obj.city
443
+ self.geolat = self.first_obj.lat
444
+ self.geolon = self.first_obj.lng
304
445
 
305
- elif self.chart_type == "Composite":
306
- self.location = ""
307
- self.geolat = (self.user.first_subject.lat + self.user.second_subject.lat) / 2
308
- self.geolon = (self.user.first_subject.lng + self.user.second_subject.lng) / 2
446
+ # Circle radii
447
+ self.first_circle_radius = 0
448
+ self.second_circle_radius = 36
449
+ self.third_circle_radius = 120
309
450
 
310
- elif self.chart_type in ["Transit"]:
311
- self.location = self.t_user.city
312
- self.geolat = self.t_user.lat
313
- self.geolon = self.t_user.lng
314
- self.t_name = self.language_settings["transit_name"]
451
+ elif self.chart_type == "Return":
452
+ # --- RETURN CHART SETUP ---
315
453
 
316
- # Default radius for the chart
317
- self.main_radius = 240
454
+ # Validate Subjects
455
+ if not second_obj:
456
+ raise KerykeionException("Second object is required for Return charts.")
457
+ if not isinstance(self.first_obj, AstrologicalSubjectModel):
458
+ raise KerykeionException("First object must be an AstrologicalSubjectModel or AstrologicalSubject instance.")
459
+ if not isinstance(second_obj, PlanetReturnModel):
460
+ raise KerykeionException("Second object must be a PlanetReturnModel instance.")
461
+
462
+ # Secondary subject setup
463
+ self.second_obj = second_obj
464
+
465
+ # Calculate aspects (natal to return)
466
+ synastry_aspects_instance = SynastryAspects(
467
+ self.first_obj,
468
+ self.second_obj,
469
+ new_settings_file=self.new_settings_file,
470
+ active_points=active_points,
471
+ active_aspects=active_aspects,
472
+ )
473
+ self.aspects_list = synastry_aspects_instance.relevant_aspects
474
+
475
+ # Secondary subject available points
476
+ self.t_available_kerykeion_celestial_points = []
477
+ for body in available_celestial_points_names:
478
+ self.t_available_kerykeion_celestial_points.append(self.second_obj.get(body))
479
+
480
+ # Screen size
481
+ self.height = self._DEFAULT_HEIGHT
482
+ self.width = self._DEFAULT_ULTRA_WIDE_WIDTH
483
+
484
+ # Location and coordinates (from natal subject)
485
+ self.location = self.first_obj.city
486
+ self.geolat = self.first_obj.lat
487
+ self.geolon = self.first_obj.lng
488
+
489
+ # Circle radii
490
+ self.first_circle_radius = 0
491
+ self.second_circle_radius = 36
492
+ self.third_circle_radius = 120
493
+
494
+ elif self.chart_type == "SingleWheelReturn":
495
+ # --- NATAL / EXTERNAL NATAL CHART SETUP ---
496
+
497
+ # Validate Subject
498
+ if not isinstance(self.first_obj, PlanetReturnModel):
499
+ raise KerykeionException("First object must be an AstrologicalSubjectModel or AstrologicalSubject instance.")
500
+
501
+ # Calculate aspects
502
+ natal_aspects_instance = NatalAspects(
503
+ self.first_obj,
504
+ new_settings_file=self.new_settings_file,
505
+ active_points=active_points,
506
+ active_aspects=active_aspects,
507
+ )
508
+ self.aspects_list = natal_aspects_instance.relevant_aspects
509
+
510
+ # Screen size
511
+ self.height = self._DEFAULT_HEIGHT
512
+ self.width = self._DEFAULT_NATAL_WIDTH
513
+
514
+ # Location and coordinates
515
+ self.location = self.first_obj.city
516
+ self.geolat = self.first_obj.lat
517
+ self.geolon = self.first_obj.lng
518
+
519
+ # Circle radii
520
+ if self.chart_type == "ExternalNatal":
521
+ self.first_circle_radius = 56
522
+ self.second_circle_radius = 92
523
+ self.third_circle_radius = 112
524
+ else:
525
+ self.first_circle_radius = 0
526
+ self.second_circle_radius = 36
527
+ self.third_circle_radius = 120
318
528
 
319
- # Set circle radii based on chart type
320
- if self.chart_type == "ExternalNatal":
321
- self.first_circle_radius, self.second_circle_radius, self.third_circle_radius = 56, 92, 112
529
+ # --------------------
530
+ # FINAL COMMON SETUP
531
+ # --------------------
532
+
533
+ # Calculate element points
534
+ celestial_points_names = [body["name"].lower() for body in self.available_planets_setting]
535
+ if self.chart_type == "Synastry":
536
+ element_totals = calculate_synastry_element_points(
537
+ self.available_planets_setting,
538
+ celestial_points_names,
539
+ self.first_obj,
540
+ self.second_obj,
541
+ )
322
542
  else:
323
- self.first_circle_radius, self.second_circle_radius, self.third_circle_radius = 0, 36, 120
543
+ element_totals = calculate_element_points(
544
+ self.available_planets_setting,
545
+ celestial_points_names,
546
+ self.first_obj,
547
+ )
324
548
 
325
- # Initialize element points
326
- self.fire = 0.0
327
- self.earth = 0.0
328
- self.air = 0.0
329
- self.water = 0.0
549
+ self.fire = element_totals["fire"]
550
+ self.earth = element_totals["earth"]
551
+ self.air = element_totals["air"]
552
+ self.water = element_totals["water"]
330
553
 
331
- # Calculate element points from planets
332
- self._calculate_elements_points_from_planets()
554
+ # Calculate qualities points
555
+ if self.chart_type == "Synastry":
556
+ qualities_totals = calculate_synastry_quality_points(
557
+ self.available_planets_setting,
558
+ celestial_points_names,
559
+ self.first_obj,
560
+ self.second_obj,
561
+ )
562
+ else:
563
+ qualities_totals = calculate_quality_points(
564
+ self.available_planets_setting,
565
+ celestial_points_names,
566
+ self.first_obj,
567
+ )
568
+
569
+ self.cardinal = qualities_totals["cardinal"]
570
+ self.fixed = qualities_totals["fixed"]
571
+ self.mutable = qualities_totals["mutable"]
333
572
 
334
573
  # Set up theme
335
574
  if theme not in get_args(KerykeionChartTheme) and theme is not None:
@@ -361,7 +600,7 @@ class KerykeionChartSVG:
361
600
  dir_path (Path): Target directory for SVG output.
362
601
  """
363
602
  self.output_directory = dir_path
364
- logging.info(f"Output direcotry set to: {self.output_directory}")
603
+ logging.info(f"Output directory set to: {self.output_directory}")
365
604
 
366
605
  def parse_json_settings(self, settings_file_or_dict: Union[Path, dict, KerykeionSettingsModel, None]) -> None:
367
606
  """
@@ -374,9 +613,6 @@ class KerykeionChartSVG:
374
613
  settings = get_settings(settings_file_or_dict)
375
614
 
376
615
  self.language_settings = settings["language_settings"][self.chart_language]
377
- self.chart_colors_settings = settings["chart_colors"]
378
- self.planets_settings = settings["celestial_points"]
379
- self.aspects_settings = settings["aspects"]
380
616
 
381
617
  def _draw_zodiac_circle_slices(self, r):
382
618
  """
@@ -394,7 +630,7 @@ class KerykeionChartSVG:
394
630
  output += draw_zodiac_slice(
395
631
  c1=self.first_circle_radius,
396
632
  chart_type=self.chart_type,
397
- seventh_house_degree_ut=self.user.seventh_house.abs_pos,
633
+ seventh_house_degree_ut=self.first_obj.seventh_house.abs_pos,
398
634
  num=i,
399
635
  r=r,
400
636
  style=f'fill:{self.chart_colors_settings[f"zodiac_bg_{i}"]}; fill-opacity: 0.5;',
@@ -403,65 +639,6 @@ class KerykeionChartSVG:
403
639
 
404
640
  return output
405
641
 
406
- def _calculate_elements_points_from_planets(self):
407
- """
408
- Compute elemental point totals based on active planetary positions.
409
-
410
- Iterates over each active planet to determine its zodiac element and adds extra points
411
- if the planet is in a related sign. Updates self.fire, self.earth, self.air, and self.water.
412
-
413
- Returns:
414
- None
415
- """
416
-
417
- ZODIAC = (
418
- {"name": "Ari", "element": "fire"},
419
- {"name": "Tau", "element": "earth"},
420
- {"name": "Gem", "element": "air"},
421
- {"name": "Can", "element": "water"},
422
- {"name": "Leo", "element": "fire"},
423
- {"name": "Vir", "element": "earth"},
424
- {"name": "Lib", "element": "air"},
425
- {"name": "Sco", "element": "water"},
426
- {"name": "Sag", "element": "fire"},
427
- {"name": "Cap", "element": "earth"},
428
- {"name": "Aqu", "element": "air"},
429
- {"name": "Pis", "element": "water"},
430
- )
431
-
432
- # Available bodies
433
- available_celestial_points_names = []
434
- for body in self.available_planets_setting:
435
- available_celestial_points_names.append(body["name"].lower())
436
-
437
- # Make list of the points sign
438
- points_sign = []
439
- for planet in available_celestial_points_names:
440
- points_sign.append(self.user.get(planet).sign_num)
441
-
442
- for i in range(len(self.available_planets_setting)):
443
- # element: get extra points if planet is in own zodiac sign.
444
- related_zodiac_signs = self.available_planets_setting[i]["related_zodiac_signs"]
445
- cz = points_sign[i]
446
- extra_points = 0
447
- if related_zodiac_signs != []:
448
- for e in range(len(related_zodiac_signs)):
449
- if int(related_zodiac_signs[e]) == int(cz):
450
- extra_points = self._PLANET_IN_ZODIAC_EXTRA_POINTS
451
-
452
- ele = ZODIAC[points_sign[i]]["element"]
453
- if ele == "fire":
454
- self.fire = self.fire + self.available_planets_setting[i]["element_points"] + extra_points
455
-
456
- elif ele == "earth":
457
- self.earth = self.earth + self.available_planets_setting[i]["element_points"] + extra_points
458
-
459
- elif ele == "air":
460
- self.air = self.air + self.available_planets_setting[i]["element_points"] + extra_points
461
-
462
- elif ele == "water":
463
- self.water = self.water + self.available_planets_setting[i]["element_points"] + extra_points
464
-
465
642
  def _draw_all_aspects_lines(self, r, ar):
466
643
  """
467
644
  Render SVG lines for all aspects in the chart.
@@ -483,7 +660,7 @@ class KerykeionChartSVG:
483
660
  ar=ar,
484
661
  aspect=aspect,
485
662
  color=aspect_color,
486
- seventh_house_degree_ut=self.user.seventh_house.abs_pos
663
+ seventh_house_degree_ut=self.first_obj.seventh_house.abs_pos,
487
664
  )
488
665
  return out
489
666
 
@@ -508,7 +685,7 @@ class KerykeionChartSVG:
508
685
  ar=ar,
509
686
  aspect=aspect,
510
687
  color=aspect_color,
511
- seventh_house_degree_ut=self.user.seventh_house.abs_pos
688
+ seventh_house_degree_ut=self.first_obj.seventh_house.abs_pos,
512
689
  )
513
690
  return out
514
691
 
@@ -525,169 +702,431 @@ class KerykeionChartSVG:
525
702
  # Initialize template dictionary
526
703
  template_dict: dict = {}
527
704
 
528
- # Set the color style tag
529
- template_dict["color_style_tag"] = self.color_style_tag
705
+ # -------------------------------------#
706
+ # COMMON SETTINGS FOR ALL CHART TYPES #
707
+ # -------------------------------------#
530
708
 
531
- # Set chart dimensions
709
+ # Set the color style tag and basic dimensions
710
+ template_dict["color_style_tag"] = self.color_style_tag
532
711
  template_dict["chart_height"] = self.height
533
712
  template_dict["chart_width"] = self.width
534
713
 
535
- # Set viewbox based on chart type
536
- if self.chart_type in ["Natal", "ExternalNatal", "Composite"]:
537
- template_dict['viewbox'] = self._BASIC_CHART_VIEWBOX
538
- elif self.double_chart_aspect_grid_type == "table" and self.chart_type == "Transit":
539
- template_dict['viewbox'] = self._TRANSIT_CHART_WITH_TABLE_VIWBOX
540
- else:
541
- template_dict['viewbox'] = self._WIDE_CHART_VIEWBOX
714
+ # Set paper colors
715
+ template_dict["paper_color_0"] = self.chart_colors_settings["paper_0"]
716
+ template_dict["paper_color_1"] = self.chart_colors_settings["paper_1"]
542
717
 
543
- # Generate rings and circles based on chart type
544
- if self.chart_type in ["Transit", "Synastry"]:
545
- template_dict["transitRing"] = draw_transit_ring(self.main_radius, self.chart_colors_settings["paper_1"], self.chart_colors_settings["zodiac_transit_ring_3"])
546
- template_dict["degreeRing"] = draw_transit_ring_degree_steps(self.main_radius, self.user.seventh_house.abs_pos)
547
- template_dict["first_circle"] = draw_first_circle(self.main_radius, self.chart_colors_settings["zodiac_transit_ring_2"], self.chart_type)
548
- template_dict["second_circle"] = draw_second_circle(self.main_radius, self.chart_colors_settings['zodiac_transit_ring_1'], self.chart_colors_settings['paper_1'], self.chart_type)
549
- template_dict['third_circle'] = draw_third_circle(self.main_radius, self.chart_colors_settings['zodiac_transit_ring_0'], self.chart_colors_settings['paper_1'], self.chart_type, self.third_circle_radius)
718
+ # Set planet colors
719
+ for planet in self.planets_settings:
720
+ planet_id = planet["id"]
721
+ template_dict[f"planets_color_{planet_id}"] = planet["color"]
550
722
 
551
- if self.double_chart_aspect_grid_type == "list":
552
- title = ""
553
- if self.chart_type == "Synastry":
554
- title = self.language_settings.get("couple_aspects", "Couple Aspects")
555
- else:
556
- title = self.language_settings.get("transit_aspects", "Transit Aspects")
723
+ # Set zodiac colors
724
+ for i in range(12):
725
+ template_dict[f"zodiac_color_{i}"] = self.chart_colors_settings[f"zodiac_icon_{i}"]
557
726
 
558
- template_dict["makeAspectGrid"] = draw_transit_aspect_list(title, self.aspects_list, self.planets_settings, self.aspects_settings)
559
- else:
560
- template_dict["makeAspectGrid"] = draw_transit_aspect_grid(self.chart_colors_settings['paper_0'], self.available_planets_setting, self.aspects_list, 550, 450)
727
+ # Set orb colors
728
+ for aspect in self.aspects_settings:
729
+ template_dict[f"orb_color_{aspect['degree']}"] = aspect["color"]
561
730
 
562
- template_dict["makeAspects"] = self._draw_all_transit_aspects_lines(self.main_radius, self.main_radius - 160)
563
- else:
731
+ # Draw zodiac circle slices
732
+ template_dict["makeZodiac"] = self._draw_zodiac_circle_slices(self.main_radius)
733
+
734
+ # Calculate element percentages
735
+ total_elements = self.fire + self.water + self.earth + self.air
736
+ fire_percentage = int(round(100 * self.fire / total_elements))
737
+ earth_percentage = int(round(100 * self.earth / total_elements))
738
+ air_percentage = int(round(100 * self.air / total_elements))
739
+ water_percentage = int(round(100 * self.water / total_elements))
740
+
741
+ # Element Percentages
742
+ template_dict["elements_string"] = f"{self.language_settings.get('elements', 'Elements')}:"
743
+ template_dict["fire_string"] = f"{self.language_settings['fire']} {fire_percentage}%"
744
+ template_dict["earth_string"] = f"{self.language_settings['earth']} {earth_percentage}%"
745
+ template_dict["air_string"] = f"{self.language_settings['air']} {air_percentage}%"
746
+ template_dict["water_string"] = f"{self.language_settings['water']} {water_percentage}%"
747
+
748
+
749
+ # Qualities Percentages
750
+ total_qualities = self.cardinal + self.fixed + self.mutable
751
+ cardinal_percentage = int(round(100 * self.cardinal / total_qualities))
752
+ fixed_percentage = int(round(100 * self.fixed / total_qualities))
753
+ mutable_percentage = int(round(100 * self.mutable / total_qualities))
754
+
755
+ template_dict["qualities_string"] = f"{self.language_settings.get('qualities', 'Qualities')}:"
756
+ template_dict["cardinal_string"] = f"{self.language_settings.get('cardinal', 'Cardinal')} {cardinal_percentage}%"
757
+ template_dict["fixed_string"] = f"{self.language_settings.get('fixed', 'Fixed')} {fixed_percentage}%"
758
+ template_dict["mutable_string"] = f"{self.language_settings.get('mutable', 'Mutable')} {mutable_percentage}%"
759
+
760
+ # Get houses list for main subject
761
+ first_subject_houses_list = get_houses_list(self.first_obj)
762
+
763
+ # ------------------------------- #
764
+ # CHART TYPE SPECIFIC SETTINGS #
765
+ # ------------------------------- #
766
+
767
+ if self.chart_type in ["Natal", "ExternalNatal"]:
768
+ # Set viewbox
769
+ template_dict["viewbox"] = self._BASIC_CHART_VIEWBOX
770
+
771
+ # Rings and circles
564
772
  template_dict["transitRing"] = ""
565
- template_dict["degreeRing"] = draw_degree_ring(self.main_radius, self.first_circle_radius, self.user.seventh_house.abs_pos, self.chart_colors_settings["paper_0"])
566
- template_dict['first_circle'] = draw_first_circle(self.main_radius, self.chart_colors_settings["zodiac_radix_ring_2"], self.chart_type, self.first_circle_radius)
567
- template_dict["second_circle"] = draw_second_circle(self.main_radius, self.chart_colors_settings["zodiac_radix_ring_1"], self.chart_colors_settings["paper_1"], self.chart_type, self.second_circle_radius)
568
- template_dict['third_circle'] = draw_third_circle(self.main_radius, self.chart_colors_settings["zodiac_radix_ring_0"], self.chart_colors_settings["paper_1"], self.chart_type, self.third_circle_radius)
569
- template_dict["makeAspectGrid"] = draw_aspect_grid(self.chart_colors_settings['paper_0'], self.available_planets_setting, self.aspects_list)
773
+ template_dict["degreeRing"] = draw_degree_ring(
774
+ self.main_radius,
775
+ self.first_circle_radius,
776
+ self.first_obj.seventh_house.abs_pos,
777
+ self.chart_colors_settings["paper_0"],
778
+ )
779
+ template_dict["first_circle"] = draw_first_circle(
780
+ self.main_radius,
781
+ self.chart_colors_settings["zodiac_radix_ring_2"],
782
+ self.chart_type,
783
+ self.first_circle_radius,
784
+ )
785
+ template_dict["second_circle"] = draw_second_circle(
786
+ self.main_radius,
787
+ self.chart_colors_settings["zodiac_radix_ring_1"],
788
+ self.chart_colors_settings["paper_1"],
789
+ self.chart_type,
790
+ self.second_circle_radius,
791
+ )
792
+ template_dict["third_circle"] = draw_third_circle(
793
+ self.main_radius,
794
+ self.chart_colors_settings["zodiac_radix_ring_0"],
795
+ self.chart_colors_settings["paper_1"],
796
+ self.chart_type,
797
+ self.third_circle_radius,
798
+ )
570
799
 
800
+ # Aspects
801
+ template_dict["makeDoubleChartAspectList"] = ""
802
+ template_dict["makeAspectGrid"] = draw_aspect_grid(
803
+ self.chart_colors_settings["paper_0"],
804
+ self.available_planets_setting,
805
+ self.aspects_list,
806
+ )
571
807
  template_dict["makeAspects"] = self._draw_all_aspects_lines(self.main_radius, self.main_radius - self.third_circle_radius)
572
808
 
573
- # Set chart title
574
- if self.chart_type == "Synastry":
575
- template_dict["stringTitle"] = f"{self.user.name} {self.language_settings['and_word']} {self.t_user.name}"
576
- elif self.chart_type == "Transit":
577
- template_dict["stringTitle"] = f"{self.language_settings['transits']} {self.t_user.day}/{self.t_user.month}/{self.t_user.year}"
578
- elif self.chart_type in ["Natal", "ExternalNatal"]:
579
- template_dict["stringTitle"] = self.user.name
580
- elif self.chart_type == "Composite":
581
- template_dict["stringTitle"] = f"{self.user.first_subject.name} {self.language_settings['and_word']} {self.user.second_subject.name}"
809
+ # Chart title
810
+ template_dict["stringTitle"] = f"{self.first_obj.name} - {self.language_settings.get("Birth Chart", "Birth Chart")}"
582
811
 
583
- # Zodiac Type Info
584
- if self.user.zodiac_type == 'Tropic':
585
- zodiac_info = f"{self.language_settings.get('zodiac', 'Zodiac')}: {self.language_settings.get('tropical', 'Tropical')}"
586
- else:
587
- mode_const = "SIDM_" + self.user.sidereal_mode # type: ignore
588
- mode_name = swe.get_ayanamsa_name(getattr(swe, mode_const))
589
- zodiac_info = f"{self.language_settings.get('ayanamsa', 'Ayanamsa')}: {mode_name}"
812
+ # Top left section
813
+ latitude_string = convert_latitude_coordinate_to_string(self.geolat, self.language_settings["north"], self.language_settings["south"])
814
+ longitude_string = convert_longitude_coordinate_to_string(self.geolon, self.language_settings["east"], self.language_settings["west"])
590
815
 
591
- template_dict["bottom_left_0"] = f"{self.language_settings.get('houses_system_' + self.user.houses_system_identifier, self.user.houses_system_name)} {self.language_settings.get('houses', 'Houses')}"
592
- template_dict["bottom_left_1"] = zodiac_info
816
+ template_dict["top_left_0"] = f'{self.language_settings.get("location", "Location")}:'
817
+ template_dict["top_left_1"] = f"{self.first_obj.city}, {self.first_obj.nation}"
818
+ template_dict["top_left_2"] = f"{self.language_settings['latitude']}: {latitude_string}"
819
+ template_dict["top_left_3"] = f"{self.language_settings['longitude']}: {longitude_string}"
820
+ template_dict["top_left_4"] = format_datetime_with_timezone(self.first_obj.iso_formatted_local_datetime) # type: ignore
821
+ template_dict["top_left_5"] = f"{self.language_settings.get('day_of_week', 'Day of Week')}: {self.first_obj.day_of_week}" # type: ignore
822
+
823
+ # Bottom left section
824
+ if self.first_obj.zodiac_type == "Tropic":
825
+ zodiac_info = f"{self.language_settings.get('zodiac', 'Zodiac')}: {self.language_settings.get('tropical', 'Tropical')}"
826
+ else:
827
+ mode_const = "SIDM_" + self.first_obj.sidereal_mode # type: ignore
828
+ mode_name = swe.get_ayanamsa_name(getattr(swe, mode_const))
829
+ zodiac_info = f"{self.language_settings.get('ayanamsa', 'Ayanamsa')}: {mode_name}"
830
+
831
+ template_dict["bottom_left_0"] = zodiac_info
832
+ template_dict["bottom_left_1"] = f"{self.language_settings.get('domification', 'Domification')}: {self.language_settings.get('houses_system_' + self.first_obj.houses_system_identifier, self.first_obj.houses_system_name)}"
833
+ template_dict["bottom_left_2"] = f'{self.language_settings.get("lunation_day", "Lunation Day")}: {self.first_obj.lunar_phase.get("moon_phase", "")}'
834
+ template_dict["bottom_left_3"] = f'{self.language_settings.get("lunar_phase", "Lunar Phase")}: {self.language_settings.get(self.first_obj.lunar_phase.moon_phase_name.lower().replace(" ", "_"), self.first_obj.lunar_phase.moon_phase_name)}'
835
+ template_dict["bottom_left_4"] = f'{self.language_settings.get("perspective_type", "Perspective")}: {self.language_settings.get(self.first_obj.perspective_type.lower().replace(" ", "_"), self.first_obj.perspective_type)}'
836
+
837
+ # Moon phase section calculations
838
+ template_dict["makeLunarPhase"] = makeLunarPhase(self.first_obj.lunar_phase["degrees_between_s_m"], self.geolat)
839
+
840
+ # Houses and planet drawing
841
+ template_dict["makeMainHousesGrid"] = draw_main_house_grid(
842
+ main_subject_houses_list=first_subject_houses_list,
843
+ text_color=self.chart_colors_settings["paper_0"],
844
+ house_cusp_generale_name_label=self.language_settings["cusp"],
845
+ )
846
+ template_dict["makeSecondaryHousesGrid"] = ""
847
+
848
+ template_dict["makeHouses"] = draw_houses_cusps_and_text_number(
849
+ r=self.main_radius,
850
+ first_subject_houses_list=first_subject_houses_list,
851
+ standard_house_cusp_color=self.chart_colors_settings["houses_radix_line"],
852
+ first_house_color=self.planets_settings[12]["color"],
853
+ tenth_house_color=self.planets_settings[13]["color"],
854
+ seventh_house_color=self.planets_settings[14]["color"],
855
+ fourth_house_color=self.planets_settings[15]["color"],
856
+ c1=self.first_circle_radius,
857
+ c3=self.third_circle_radius,
858
+ chart_type=self.chart_type,
859
+ )
860
+
861
+ template_dict["makePlanets"] = draw_planets(
862
+ available_planets_setting=self.available_planets_setting,
863
+ chart_type=self.chart_type,
864
+ radius=self.main_radius,
865
+ available_kerykeion_celestial_points=self.available_kerykeion_celestial_points,
866
+ third_circle_radius=self.third_circle_radius,
867
+ main_subject_first_house_degree_ut=self.first_obj.first_house.abs_pos,
868
+ main_subject_seventh_house_degree_ut=self.first_obj.seventh_house.abs_pos,
869
+ )
870
+
871
+ template_dict["makeMainPlanetGrid"] = draw_main_planet_grid(
872
+ planets_and_houses_grid_title=self.language_settings["planets_and_house"],
873
+ subject_name=self.first_obj.name,
874
+ available_kerykeion_celestial_points=self.available_kerykeion_celestial_points,
875
+ chart_type=self.chart_type,
876
+ text_color=self.chart_colors_settings["paper_0"],
877
+ celestial_point_language=self.language_settings["celestial_points"],
878
+ )
879
+ template_dict["makeSecondaryPlanetGrid"] = ""
880
+ template_dict["makeHouseComparisonGrid"] = ""
593
881
 
594
- if self.chart_type in ["Natal", "ExternalNatal", "Synastry"]:
595
- template_dict["bottom_left_2"] = f'{self.language_settings.get("lunar_phase", "Lunar Phase")} {self.language_settings.get("day", "Day").lower()}: {self.user.lunar_phase.get("moon_phase", "")}'
596
- template_dict["bottom_left_3"] = f'{self.language_settings.get("lunar_phase", "Lunar Phase")}: {self.language_settings.get(self.user.lunar_phase.moon_phase_name.lower().replace(" ", "_"), self.user.lunar_phase.moon_phase_name)}'
597
- template_dict["bottom_left_4"] = f'{self.language_settings.get(self.user.perspective_type.lower().replace(" ", "_"), self.user.perspective_type)}'
598
- elif self.chart_type == "Transit":
599
- template_dict["bottom_left_2"] = f'{self.language_settings.get("lunar_phase", "Lunar Phase")}: {self.language_settings.get("day", "Day")} {self.t_user.lunar_phase.get("moon_phase", "")}'
600
- template_dict["bottom_left_3"] = f'{self.language_settings.get("lunar_phase", "Lunar Phase")}: {self.t_user.lunar_phase.moon_phase_name}'
601
- template_dict["bottom_left_4"] = f'{self.language_settings.get(self.t_user.perspective_type.lower().replace(" ", "_"), self.t_user.perspective_type)}'
602
882
  elif self.chart_type == "Composite":
603
- template_dict["bottom_left_2"] = f'{self.user.first_subject.perspective_type}'
883
+ # Set viewbox
884
+ template_dict["viewbox"] = self._BASIC_CHART_VIEWBOX
885
+
886
+ # Rings and circles
887
+ template_dict["transitRing"] = ""
888
+ template_dict["degreeRing"] = draw_degree_ring(
889
+ self.main_radius,
890
+ self.first_circle_radius,
891
+ self.first_obj.seventh_house.abs_pos,
892
+ self.chart_colors_settings["paper_0"],
893
+ )
894
+ template_dict["first_circle"] = draw_first_circle(
895
+ self.main_radius,
896
+ self.chart_colors_settings["zodiac_radix_ring_2"],
897
+ self.chart_type,
898
+ self.first_circle_radius,
899
+ )
900
+ template_dict["second_circle"] = draw_second_circle(
901
+ self.main_radius,
902
+ self.chart_colors_settings["zodiac_radix_ring_1"],
903
+ self.chart_colors_settings["paper_1"],
904
+ self.chart_type,
905
+ self.second_circle_radius,
906
+ )
907
+ template_dict["third_circle"] = draw_third_circle(
908
+ self.main_radius,
909
+ self.chart_colors_settings["zodiac_radix_ring_0"],
910
+ self.chart_colors_settings["paper_1"],
911
+ self.chart_type,
912
+ self.third_circle_radius,
913
+ )
914
+
915
+ # Aspects
916
+ template_dict["makeDoubleChartAspectList"] = ""
917
+ template_dict["makeAspectGrid"] = draw_aspect_grid(
918
+ self.chart_colors_settings["paper_0"],
919
+ self.available_planets_setting,
920
+ self.aspects_list,
921
+ )
922
+ template_dict["makeAspects"] = self._draw_all_aspects_lines(self.main_radius, self.main_radius - self.third_circle_radius)
923
+
924
+ # Chart title
925
+ template_dict["stringTitle"] = f"{self.first_obj.first_subject.name} {self.language_settings['and_word']} {self.first_obj.second_subject.name}" # type: ignore
926
+
927
+ # Top left section
928
+ # First subject
929
+ latitude = convert_latitude_coordinate_to_string(
930
+ self.first_obj.first_subject.lat, # type: ignore
931
+ self.language_settings["north_letter"],
932
+ self.language_settings["south_letter"],
933
+ )
934
+ longitude = convert_longitude_coordinate_to_string(
935
+ self.first_obj.first_subject.lng, # type: ignore
936
+ self.language_settings["east_letter"],
937
+ self.language_settings["west_letter"],
938
+ )
939
+
940
+ # Second subject
941
+ latitude_string = convert_latitude_coordinate_to_string(
942
+ self.first_obj.second_subject.lat, # type: ignore
943
+ self.language_settings["north_letter"],
944
+ self.language_settings["south_letter"],
945
+ )
946
+ longitude_string = convert_longitude_coordinate_to_string(
947
+ self.first_obj.second_subject.lng, # type: ignore
948
+ self.language_settings["east_letter"],
949
+ self.language_settings["west_letter"],
950
+ )
951
+
952
+ template_dict["top_left_0"] = f"{self.first_obj.first_subject.name}" # type: ignore
953
+ template_dict["top_left_1"] = f"{datetime.fromisoformat(self.first_obj.first_subject.iso_formatted_local_datetime).strftime('%Y-%m-%d %H:%M')}" # type: ignore
954
+ template_dict["top_left_2"] = f"{latitude} {longitude}"
955
+ template_dict["top_left_3"] = self.first_obj.second_subject.name # type: ignore
956
+ template_dict["top_left_4"] = f"{datetime.fromisoformat(self.first_obj.second_subject.iso_formatted_local_datetime).strftime('%Y-%m-%d %H:%M')}" # type: ignore
957
+ template_dict["top_left_5"] = f"{latitude_string} / {longitude_string}"
958
+
959
+ # Bottom left section
960
+ if self.first_obj.zodiac_type == "Tropic":
961
+ zodiac_info = f"{self.language_settings.get('zodiac', 'Zodiac')}: {self.language_settings.get('tropical', 'Tropical')}"
962
+ else:
963
+ mode_const = "SIDM_" + self.first_obj.sidereal_mode # type: ignore
964
+ mode_name = swe.get_ayanamsa_name(getattr(swe, mode_const))
965
+ zodiac_info = f"{self.language_settings.get('ayanamsa', 'Ayanamsa')}: {mode_name}"
966
+
967
+ template_dict["bottom_left_0"] = zodiac_info
968
+ template_dict["bottom_left_1"] = f"{self.language_settings.get('houses_system_' + self.first_obj.houses_system_identifier, self.first_obj.houses_system_name)} {self.language_settings.get('houses', 'Houses')}"
969
+ template_dict["bottom_left_2"] = f"{self.language_settings.get("perspective_type", "Perspective")}: {self.first_obj.first_subject.perspective_type}" # type: ignore
604
970
  template_dict["bottom_left_3"] = f'{self.language_settings.get("composite_chart", "Composite Chart")} - {self.language_settings.get("midpoints", "Midpoints")}'
605
971
  template_dict["bottom_left_4"] = ""
606
972
 
607
- # Draw moon phase
608
- moon_phase_dict = calculate_moon_phase_chart_params(
609
- self.user.lunar_phase["degrees_between_s_m"],
610
- self.geolat
611
- )
612
-
613
- template_dict["lunar_phase_rotate"] = moon_phase_dict["lunar_phase_rotate"]
614
- template_dict["lunar_phase_circle_center_x"] = moon_phase_dict["circle_center_x"]
615
- template_dict["lunar_phase_circle_radius"] = moon_phase_dict["circle_radius"]
616
-
617
- if self.chart_type == "Composite":
618
- template_dict["top_left_1"] = f"{datetime.fromisoformat(self.user.first_subject.iso_formatted_local_datetime).strftime('%Y-%m-%d %H:%M')}"
619
- # Set location string
620
- elif len(self.location) > 35:
621
- split_location = self.location.split(",")
622
- if len(split_location) > 1:
623
- template_dict["top_left_1"] = split_location[0] + ", " + split_location[-1]
624
- if len(template_dict["top_left_1"]) > 35:
625
- template_dict["top_left_1"] = template_dict["top_left_1"][:35] + "..."
973
+ # Moon phase section calculations
974
+ template_dict["makeLunarPhase"] = makeLunarPhase(self.first_obj.lunar_phase["degrees_between_s_m"], self.geolat)
975
+
976
+ # Houses and planet drawing
977
+ template_dict["makeMainHousesGrid"] = draw_main_house_grid(
978
+ main_subject_houses_list=first_subject_houses_list,
979
+ text_color=self.chart_colors_settings["paper_0"],
980
+ house_cusp_generale_name_label=self.language_settings["cusp"],
981
+ )
982
+ template_dict["makeSecondaryHousesGrid"] = ""
983
+
984
+ template_dict["makeHouses"] = draw_houses_cusps_and_text_number(
985
+ r=self.main_radius,
986
+ first_subject_houses_list=first_subject_houses_list,
987
+ standard_house_cusp_color=self.chart_colors_settings["houses_radix_line"],
988
+ first_house_color=self.planets_settings[12]["color"],
989
+ tenth_house_color=self.planets_settings[13]["color"],
990
+ seventh_house_color=self.planets_settings[14]["color"],
991
+ fourth_house_color=self.planets_settings[15]["color"],
992
+ c1=self.first_circle_radius,
993
+ c3=self.third_circle_radius,
994
+ chart_type=self.chart_type,
995
+ )
996
+
997
+ template_dict["makePlanets"] = draw_planets(
998
+ available_planets_setting=self.available_planets_setting,
999
+ chart_type=self.chart_type,
1000
+ radius=self.main_radius,
1001
+ available_kerykeion_celestial_points=self.available_kerykeion_celestial_points,
1002
+ third_circle_radius=self.third_circle_radius,
1003
+ main_subject_first_house_degree_ut=self.first_obj.first_house.abs_pos,
1004
+ main_subject_seventh_house_degree_ut=self.first_obj.seventh_house.abs_pos,
1005
+ )
1006
+
1007
+ subject_name = f"{self.first_obj.first_subject.name} {self.language_settings['and_word']} {self.first_obj.second_subject.name}" # type: ignore
1008
+
1009
+ template_dict["makeMainPlanetGrid"] = draw_main_planet_grid(
1010
+ planets_and_houses_grid_title=self.language_settings["planets_and_house"],
1011
+ subject_name=subject_name,
1012
+ available_kerykeion_celestial_points=self.available_kerykeion_celestial_points,
1013
+ chart_type=self.chart_type,
1014
+ text_color=self.chart_colors_settings["paper_0"],
1015
+ celestial_point_language=self.language_settings["celestial_points"],
1016
+ )
1017
+ template_dict["makeSecondaryPlanetGrid"] = ""
1018
+ template_dict["makeHouseComparisonGrid"] = ""
1019
+
1020
+ elif self.chart_type == "Transit":
1021
+
1022
+ # Transit has no Element Percentages
1023
+ template_dict["elements_string"] = ""
1024
+ template_dict["fire_string"] = ""
1025
+ template_dict["earth_string"] = ""
1026
+ template_dict["air_string"] = ""
1027
+ template_dict["water_string"] = ""
1028
+
1029
+ # Transit has no Qualities Percentages
1030
+ template_dict["qualities_string"] = ""
1031
+ template_dict["cardinal_string"] = ""
1032
+ template_dict["fixed_string"] = ""
1033
+ template_dict["mutable_string"] = ""
1034
+
1035
+ # Set viewbox
1036
+ if self.double_chart_aspect_grid_type == "table":
1037
+ template_dict["viewbox"] = self._TRANSIT_CHART_WITH_TABLE_VIWBOX
626
1038
  else:
627
- template_dict["top_left_1"] = self.location[:35] + "..."
628
- else:
629
- template_dict["top_left_1"] = self.location
1039
+ template_dict["viewbox"] = self._WIDE_CHART_VIEWBOX
630
1040
 
631
- # Set chart name
632
- if self.chart_type in ["Synastry", "Transit"]:
633
- template_dict["top_left_0"] = f"{self.user.name}:"
634
- elif self.chart_type in ["Natal", "ExternalNatal"]:
635
- template_dict["top_left_0"] = f'{self.language_settings["info"]}:'
636
- elif self.chart_type == "Composite":
637
- template_dict["top_left_0"] = f'{self.user.first_subject.name}'
1041
+ # Get houses list for secondary subject
1042
+ second_subject_houses_list = get_houses_list(self.second_obj) # type: ignore
638
1043
 
639
- # Set additional information for Synastry chart type
640
- if self.chart_type == "Synastry":
641
- template_dict["top_left_3"] = f"{self.t_user.name}: "
642
- template_dict["top_left_4"] = self.t_user.city
643
- template_dict["top_left_5"] = f"{self.t_user.year}-{self.t_user.month}-{self.t_user.day} {self.t_user.hour:02d}:{self.t_user.minute:02d}"
644
- elif self.chart_type == "Composite":
645
- template_dict["top_left_3"] = self.user.second_subject.name
646
- template_dict["top_left_4"] = f"{datetime.fromisoformat(self.user.second_subject.iso_formatted_local_datetime).strftime('%Y-%m-%d %H:%M')}"
647
- latitude_string = convert_latitude_coordinate_to_string(self.user.second_subject.lat, self.language_settings['north_letter'], self.language_settings['south_letter'])
648
- longitude_string = convert_longitude_coordinate_to_string(self.user.second_subject.lng, self.language_settings['east_letter'], self.language_settings['west_letter'])
649
- template_dict["top_left_5"] = f"{latitude_string} / {longitude_string}"
650
- else:
651
- latitude_string = convert_latitude_coordinate_to_string(self.geolat, self.language_settings['north'], self.language_settings['south'])
652
- longitude_string = convert_longitude_coordinate_to_string(self.geolon, self.language_settings['east'], self.language_settings['west'])
653
- template_dict["top_left_3"] = f"{self.language_settings['latitude']}: {latitude_string}"
654
- template_dict["top_left_4"] = f"{self.language_settings['longitude']}: {longitude_string}"
655
- template_dict["top_left_5"] = f"{self.language_settings['type']}: {self.language_settings.get(self.chart_type, self.chart_type)}"
1044
+ # Rings and circles
1045
+ template_dict["transitRing"] = draw_transit_ring(
1046
+ self.main_radius,
1047
+ self.chart_colors_settings["paper_1"],
1048
+ self.chart_colors_settings["zodiac_transit_ring_3"],
1049
+ )
1050
+ template_dict["degreeRing"] = draw_transit_ring_degree_steps(self.main_radius, self.first_obj.seventh_house.abs_pos)
1051
+ template_dict["first_circle"] = draw_first_circle(
1052
+ self.main_radius,
1053
+ self.chart_colors_settings["zodiac_transit_ring_2"],
1054
+ self.chart_type,
1055
+ )
1056
+ template_dict["second_circle"] = draw_second_circle(
1057
+ self.main_radius,
1058
+ self.chart_colors_settings["zodiac_transit_ring_1"],
1059
+ self.chart_colors_settings["paper_1"],
1060
+ self.chart_type,
1061
+ )
1062
+ template_dict["third_circle"] = draw_third_circle(
1063
+ self.main_radius,
1064
+ self.chart_colors_settings["zodiac_transit_ring_0"],
1065
+ self.chart_colors_settings["paper_1"],
1066
+ self.chart_type,
1067
+ self.third_circle_radius,
1068
+ )
656
1069
 
1070
+ # Aspects
1071
+ if self.double_chart_aspect_grid_type == "list":
1072
+ title = f"{self.first_obj.name} - {self.language_settings.get("transit_aspects", "Transit Aspects")}"
1073
+ template_dict["makeAspectGrid"] = ""
1074
+ template_dict["makeDoubleChartAspectList"] = draw_transit_aspect_list(title, self.aspects_list, self.planets_settings, self.aspects_settings)
1075
+ else:
1076
+ template_dict["makeAspectGrid"] = ""
1077
+ template_dict["makeDoubleChartAspectList"] = draw_transit_aspect_grid(
1078
+ self.chart_colors_settings["paper_0"],
1079
+ self.available_planets_setting,
1080
+ self.aspects_list,
1081
+ 550,
1082
+ 450,
1083
+ )
657
1084
 
658
- # Set paper colors
659
- template_dict["paper_color_0"] = self.chart_colors_settings["paper_0"]
660
- template_dict["paper_color_1"] = self.chart_colors_settings["paper_1"]
1085
+ template_dict["makeAspects"] = self._draw_all_transit_aspects_lines(self.main_radius, self.main_radius - 160)
661
1086
 
662
- # Set planet colors
663
- for planet in self.planets_settings:
664
- planet_id = planet["id"]
665
- template_dict[f"planets_color_{planet_id}"] = planet["color"] # type: ignore
1087
+ # Chart title
1088
+ template_dict["stringTitle"] = f"{self.language_settings['transits']} {format_datetime_with_timezone(self.second_obj.iso_formatted_local_datetime)}" # type: ignore
666
1089
 
667
- # Set zodiac colors
668
- for i in range(12):
669
- template_dict[f"zodiac_color_{i}"] = self.chart_colors_settings[f"zodiac_icon_{i}"] # type: ignore
1090
+ # Top left section
1091
+ latitude_string = convert_latitude_coordinate_to_string(self.geolat, self.language_settings["north"], self.language_settings["south"])
1092
+ longitude_string = convert_longitude_coordinate_to_string(self.geolon, self.language_settings["east"], self.language_settings["west"])
670
1093
 
671
- # Set orb colors
672
- for aspect in self.aspects_settings:
673
- template_dict[f"orb_color_{aspect['degree']}"] = aspect['color'] # type: ignore
1094
+ template_dict["top_left_0"] = template_dict["top_left_0"] = f'{self.first_obj.name}'
1095
+ template_dict["top_left_1"] = f"{format_location_string(self.first_obj.city)}, {self.first_obj.nation}" # type: ignore
1096
+ template_dict["top_left_2"] = format_datetime_with_timezone(self.first_obj.iso_formatted_local_datetime) # type: ignore
1097
+ template_dict["top_left_3"] = f"{self.language_settings['latitude']}: {latitude_string}"
1098
+ template_dict["top_left_4"] = f"{self.language_settings['longitude']}: {longitude_string}"
1099
+ template_dict["top_left_5"] = ""#f"{self.language_settings['type']}: {self.language_settings.get(self.chart_type, self.chart_type)}"
674
1100
 
675
- # Drawing functions
676
- template_dict["makeZodiac"] = self._draw_zodiac_circle_slices(self.main_radius)
1101
+ # Bottom left section
1102
+ if self.first_obj.zodiac_type == "Tropic":
1103
+ zodiac_info = f"{self.language_settings.get('zodiac', 'Zodiac')}: {self.language_settings.get('tropical', 'Tropical')}"
1104
+ else:
1105
+ mode_const = "SIDM_" + self.first_obj.sidereal_mode # type: ignore
1106
+ mode_name = swe.get_ayanamsa_name(getattr(swe, mode_const))
1107
+ zodiac_info = f"{self.language_settings.get('ayanamsa', 'Ayanamsa')}: {mode_name}"
677
1108
 
678
- first_subject_houses_list = get_houses_list(self.user)
1109
+ template_dict["bottom_left_0"] = zodiac_info
1110
+ template_dict["bottom_left_1"] = f"{self.language_settings.get('domification', 'Domification')}: {self.language_settings.get('houses_system_' + self.first_obj.houses_system_identifier, self.first_obj.houses_system_name)}"
1111
+ template_dict["bottom_left_2"] = f'{self.language_settings.get("lunation_day", "Lunation Day")}: {self.second_obj.lunar_phase.get("moon_phase", "")}' # type: ignore
1112
+ template_dict["bottom_left_3"] = f'{self.language_settings.get("lunar_phase", "Lunar Phase")}: {self.language_settings.get(self.second_obj.lunar_phase.moon_phase_name.lower().replace(" ", "_"), self.first_obj.lunar_phase.moon_phase_name)}'
1113
+ template_dict["bottom_left_4"] = f'{self.language_settings.get("perspective_type", "Perspective")}: {self.language_settings.get(self.second_obj.perspective_type.lower().replace(" ", "_"), self.second_obj.perspective_type)}' # type: ignore
679
1114
 
680
- # Draw houses grid and cusps
681
- if self.chart_type in ["Transit", "Synastry"]:
682
- second_subject_houses_list = get_houses_list(self.t_user)
1115
+ # Moon phase section calculations
1116
+ template_dict["makeLunarPhase"] = makeLunarPhase(self.first_obj.lunar_phase["degrees_between_s_m"], self.geolat)
683
1117
 
684
- template_dict["makeHousesGrid"] = draw_house_grid(
1118
+ # Houses and planet drawing
1119
+ template_dict["makeMainHousesGrid"] = draw_main_house_grid(
685
1120
  main_subject_houses_list=first_subject_houses_list,
686
- secondary_subject_houses_list=second_subject_houses_list,
687
- chart_type=self.chart_type,
688
1121
  text_color=self.chart_colors_settings["paper_0"],
689
- house_cusp_generale_name_label=self.language_settings["cusp"]
1122
+ house_cusp_generale_name_label=self.language_settings["cusp"],
690
1123
  )
1124
+ # template_dict["makeSecondaryHousesGrid"] = draw_secondary_house_grid(
1125
+ # secondary_subject_houses_list=second_subject_houses_list,
1126
+ # text_color=self.chart_colors_settings["paper_0"],
1127
+ # house_cusp_generale_name_label=self.language_settings["cusp"],
1128
+ # )
1129
+ template_dict["makeSecondaryHousesGrid"] = ""
691
1130
 
692
1131
  template_dict["makeHouses"] = draw_houses_cusps_and_text_number(
693
1132
  r=self.main_radius,
@@ -704,12 +1143,151 @@ class KerykeionChartSVG:
704
1143
  transit_house_cusp_color=self.chart_colors_settings["houses_transit_line"],
705
1144
  )
706
1145
 
707
- else:
708
- template_dict["makeHousesGrid"] = draw_house_grid(
709
- main_subject_houses_list=first_subject_houses_list,
1146
+ template_dict["makePlanets"] = draw_planets(
1147
+ available_kerykeion_celestial_points=self.available_kerykeion_celestial_points,
1148
+ available_planets_setting=self.available_planets_setting,
1149
+ second_subject_available_kerykeion_celestial_points=self.t_available_kerykeion_celestial_points,
1150
+ radius=self.main_radius,
1151
+ main_subject_first_house_degree_ut=self.first_obj.first_house.abs_pos,
1152
+ main_subject_seventh_house_degree_ut=self.first_obj.seventh_house.abs_pos,
1153
+ chart_type=self.chart_type,
1154
+ third_circle_radius=self.third_circle_radius,
1155
+ )
1156
+
1157
+ # Planet grids
1158
+ first_return_grid_title = f"{self.first_obj.name} ({self.language_settings.get('inner_wheel', 'Inner Wheel')})"
1159
+ second_return_grid_title = f"{self.language_settings.get('Transit', 'Transit')} ({self.language_settings.get('outer_wheel', 'Outer Wheel')})"
1160
+ template_dict["makeMainPlanetGrid"] = draw_main_planet_grid(
1161
+ planets_and_houses_grid_title="",
1162
+ subject_name=first_return_grid_title,
1163
+ available_kerykeion_celestial_points=self.available_kerykeion_celestial_points,
1164
+ chart_type=self.chart_type,
1165
+ text_color=self.chart_colors_settings["paper_0"],
1166
+ celestial_point_language=self.language_settings["celestial_points"],
1167
+ )
1168
+
1169
+ template_dict["makeSecondaryPlanetGrid"] = draw_secondary_planet_grid(
1170
+ planets_and_houses_grid_title="",
1171
+ second_subject_name=second_return_grid_title,
1172
+ second_subject_available_kerykeion_celestial_points=self.t_available_kerykeion_celestial_points,
710
1173
  chart_type=self.chart_type,
711
1174
  text_color=self.chart_colors_settings["paper_0"],
712
- house_cusp_generale_name_label=self.language_settings["cusp"]
1175
+ celestial_point_language=self.language_settings["celestial_points"],
1176
+ )
1177
+
1178
+ # House comparison grid
1179
+ house_comparison_factory = HouseComparisonFactory(
1180
+ first_subject=self.first_obj,
1181
+ second_subject=self.second_obj,
1182
+ active_points=self.active_points,
1183
+ )
1184
+ house_comparison = house_comparison_factory.get_house_comparison()
1185
+
1186
+ template_dict["makeHouseComparisonGrid"] = draw_single_house_comparison_grid(
1187
+ house_comparison,
1188
+ celestial_point_language=self.language_settings.get("celestial_points", "Celestial Points"),
1189
+ active_points=self.active_points,
1190
+ points_owner_subject_number=2, # The second subject is the Transit
1191
+ house_position_comparison_label=self.language_settings.get("house_position_comparison", "House Position Comparison"),
1192
+ return_point_label=self.language_settings.get("transit_point", "Transit Point"),
1193
+ natal_house_label=self.language_settings.get("house_position", "Natal House"),
1194
+ x_position=930,
1195
+ )
1196
+
1197
+ elif self.chart_type == "Synastry":
1198
+ # Set viewbox
1199
+ template_dict["viewbox"] = self._WIDE_CHART_VIEWBOX
1200
+
1201
+ # Get houses list for secondary subject
1202
+ second_subject_houses_list = get_houses_list(self.second_obj) # type: ignore
1203
+
1204
+ # Rings and circles
1205
+ template_dict["transitRing"] = draw_transit_ring(
1206
+ self.main_radius,
1207
+ self.chart_colors_settings["paper_1"],
1208
+ self.chart_colors_settings["zodiac_transit_ring_3"],
1209
+ )
1210
+ template_dict["degreeRing"] = draw_transit_ring_degree_steps(self.main_radius, self.first_obj.seventh_house.abs_pos)
1211
+ template_dict["first_circle"] = draw_first_circle(
1212
+ self.main_radius,
1213
+ self.chart_colors_settings["zodiac_transit_ring_2"],
1214
+ self.chart_type,
1215
+ )
1216
+ template_dict["second_circle"] = draw_second_circle(
1217
+ self.main_radius,
1218
+ self.chart_colors_settings["zodiac_transit_ring_1"],
1219
+ self.chart_colors_settings["paper_1"],
1220
+ self.chart_type,
1221
+ )
1222
+ template_dict["third_circle"] = draw_third_circle(
1223
+ self.main_radius,
1224
+ self.chart_colors_settings["zodiac_transit_ring_0"],
1225
+ self.chart_colors_settings["paper_1"],
1226
+ self.chart_type,
1227
+ self.third_circle_radius,
1228
+ )
1229
+
1230
+ # Aspects
1231
+ if self.double_chart_aspect_grid_type == "list":
1232
+ template_dict["makeAspectGrid"] = ""
1233
+ template_dict["makeDoubleChartAspectList"] = draw_transit_aspect_list(
1234
+ f"{self.first_obj.name} - {self.second_obj.name} {self.language_settings.get('synastry_aspects', 'Synastry Aspects')}", # type: ignore
1235
+ self.aspects_list,
1236
+ self.planets_settings,
1237
+ self.aspects_settings
1238
+ )
1239
+ else:
1240
+ template_dict["makeAspectGrid"] = ""
1241
+ template_dict["makeDoubleChartAspectList"] = draw_transit_aspect_grid(
1242
+ self.chart_colors_settings["paper_0"],
1243
+ self.available_planets_setting,
1244
+ self.aspects_list,
1245
+ 550,
1246
+ 450,
1247
+ )
1248
+
1249
+ template_dict["makeAspects"] = self._draw_all_transit_aspects_lines(self.main_radius, self.main_radius - 160)
1250
+
1251
+ # Chart title
1252
+ template_dict["stringTitle"] = f"{self.first_obj.name} {self.language_settings['and_word']} {self.second_obj.name}" # type: ignore
1253
+
1254
+ # Top left section
1255
+ template_dict["top_left_0"] = f"{self.first_obj.name}:"
1256
+ template_dict["top_left_1"] = f"{self.first_obj.city}, {self.first_obj.nation}" # type: ignore
1257
+ template_dict["top_left_2"] = format_datetime_with_timezone(self.first_obj.iso_formatted_local_datetime) # type: ignore
1258
+ template_dict["top_left_3"] = f"{self.second_obj.name}: " # type: ignore
1259
+ template_dict["top_left_4"] = f"{self.second_obj.city}, {self.second_obj.nation}" # type: ignore
1260
+ template_dict["top_left_5"] = format_datetime_with_timezone(self.second_obj.iso_formatted_local_datetime) # type: ignore
1261
+
1262
+ # Bottom left section
1263
+ if self.first_obj.zodiac_type == "Tropic":
1264
+ zodiac_info = f"{self.language_settings.get('zodiac', 'Zodiac')}: {self.language_settings.get('tropical', 'Tropical')}"
1265
+ else:
1266
+ mode_const = "SIDM_" + self.first_obj.sidereal_mode # type: ignore
1267
+ mode_name = swe.get_ayanamsa_name(getattr(swe, mode_const))
1268
+ zodiac_info = f"{self.language_settings.get('ayanamsa', 'Ayanamsa')}: {mode_name}"
1269
+
1270
+ template_dict["bottom_left_0"] = ""
1271
+ # FIXME!
1272
+ template_dict["bottom_left_1"] = "" # f"Compatibility Score: {16}/44" # type: ignore
1273
+ template_dict["bottom_left_2"] = zodiac_info
1274
+ template_dict["bottom_left_3"] = f"{self.language_settings.get('houses_system_' + self.first_obj.houses_system_identifier, self.first_obj.houses_system_name)} {self.language_settings.get('houses', 'Houses')}"
1275
+ template_dict["bottom_left_4"] = f'{self.language_settings.get("perspective_type", "Perspective")}: {self.language_settings.get(self.first_obj.perspective_type.lower().replace(" ", "_"), self.first_obj.perspective_type)}'
1276
+
1277
+ # Moon phase section calculations
1278
+ template_dict["makeLunarPhase"] = ""
1279
+
1280
+ # Houses and planet drawing
1281
+ template_dict["makeMainHousesGrid"] = draw_main_house_grid(
1282
+ main_subject_houses_list=first_subject_houses_list,
1283
+ text_color=self.chart_colors_settings["paper_0"],
1284
+ house_cusp_generale_name_label=self.language_settings["cusp"],
1285
+ )
1286
+
1287
+ template_dict["makeSecondaryHousesGrid"] = draw_secondary_house_grid(
1288
+ secondary_subject_houses_list=second_subject_houses_list,
1289
+ text_color=self.chart_colors_settings["paper_0"],
1290
+ house_cusp_generale_name_label=self.language_settings["cusp"],
713
1291
  )
714
1292
 
715
1293
  template_dict["makeHouses"] = draw_houses_cusps_and_text_number(
@@ -723,91 +1301,336 @@ class KerykeionChartSVG:
723
1301
  c1=self.first_circle_radius,
724
1302
  c3=self.third_circle_radius,
725
1303
  chart_type=self.chart_type,
1304
+ second_subject_houses_list=second_subject_houses_list,
1305
+ transit_house_cusp_color=self.chart_colors_settings["houses_transit_line"],
726
1306
  )
727
1307
 
728
- # Draw planets
729
- if self.chart_type in ["Transit", "Synastry"]:
730
1308
  template_dict["makePlanets"] = draw_planets(
731
1309
  available_kerykeion_celestial_points=self.available_kerykeion_celestial_points,
732
1310
  available_planets_setting=self.available_planets_setting,
733
1311
  second_subject_available_kerykeion_celestial_points=self.t_available_kerykeion_celestial_points,
734
1312
  radius=self.main_radius,
735
- main_subject_first_house_degree_ut=self.user.first_house.abs_pos,
736
- main_subject_seventh_house_degree_ut=self.user.seventh_house.abs_pos,
1313
+ main_subject_first_house_degree_ut=self.first_obj.first_house.abs_pos,
1314
+ main_subject_seventh_house_degree_ut=self.first_obj.seventh_house.abs_pos,
737
1315
  chart_type=self.chart_type,
738
1316
  third_circle_radius=self.third_circle_radius,
739
1317
  )
740
- else:
741
- template_dict["makePlanets"] = draw_planets(
742
- available_planets_setting=self.available_planets_setting,
743
- chart_type=self.chart_type,
744
- radius=self.main_radius,
1318
+
1319
+ # Planet grid
1320
+ template_dict["makeMainPlanetGrid"] = draw_main_planet_grid(
1321
+ planets_and_houses_grid_title="",
1322
+ subject_name=f"{self.first_obj.name} ({self.language_settings.get('inner_wheel', 'Inner Wheel')})",
745
1323
  available_kerykeion_celestial_points=self.available_kerykeion_celestial_points,
746
- third_circle_radius=self.third_circle_radius,
747
- main_subject_first_house_degree_ut=self.user.first_house.abs_pos,
748
- main_subject_seventh_house_degree_ut=self.user.seventh_house.abs_pos
1324
+ chart_type=self.chart_type,
1325
+ text_color=self.chart_colors_settings["paper_0"],
1326
+ celestial_point_language=self.language_settings["celestial_points"],
1327
+ )
1328
+ template_dict["makeSecondaryPlanetGrid"] = draw_secondary_planet_grid(
1329
+ planets_and_houses_grid_title="",
1330
+ second_subject_name= f"{self.second_obj.name} ({self.language_settings.get('outer_wheel', 'Outer Wheel')})", # type: ignore
1331
+ second_subject_available_kerykeion_celestial_points=self.t_available_kerykeion_celestial_points,
1332
+ chart_type=self.chart_type,
1333
+ text_color=self.chart_colors_settings["paper_0"],
1334
+ celestial_point_language=self.language_settings["celestial_points"],
749
1335
  )
1336
+ template_dict["makeHouseComparisonGrid"] = ""
750
1337
 
751
- # Draw elements percentages
752
- total = self.fire + self.water + self.earth + self.air
1338
+ elif self.chart_type == "Return":
1339
+ # Set viewbox
1340
+ template_dict["viewbox"] = self._ULTRA_WIDE_CHART_VIEWBOX
753
1341
 
754
- fire_percentage = int(round(100 * self.fire / total))
755
- earth_percentage = int(round(100 * self.earth / total))
756
- air_percentage = int(round(100 * self.air / total))
757
- water_percentage = int(round(100 * self.water / total))
1342
+ # Get houses list for secondary subject
1343
+ second_subject_houses_list = get_houses_list(self.second_obj) # type: ignore
758
1344
 
759
- template_dict["fire_string"] = f"{self.language_settings['fire']} {fire_percentage}%"
760
- template_dict["earth_string"] = f"{self.language_settings['earth']} {earth_percentage}%"
761
- template_dict["air_string"] = f"{self.language_settings['air']} {air_percentage}%"
762
- template_dict["water_string"] = f"{self.language_settings['water']} {water_percentage}%"
1345
+ # Rings and circles
1346
+ template_dict["transitRing"] = draw_transit_ring(
1347
+ self.main_radius,
1348
+ self.chart_colors_settings["paper_1"],
1349
+ self.chart_colors_settings["zodiac_transit_ring_3"],
1350
+ )
1351
+ template_dict["degreeRing"] = draw_transit_ring_degree_steps(self.main_radius, self.first_obj.seventh_house.abs_pos)
1352
+ template_dict["first_circle"] = draw_first_circle(
1353
+ self.main_radius,
1354
+ self.chart_colors_settings["zodiac_transit_ring_2"],
1355
+ self.chart_type,
1356
+ )
1357
+ template_dict["second_circle"] = draw_second_circle(
1358
+ self.main_radius,
1359
+ self.chart_colors_settings["zodiac_transit_ring_1"],
1360
+ self.chart_colors_settings["paper_1"],
1361
+ self.chart_type,
1362
+ )
1363
+ template_dict["third_circle"] = draw_third_circle(
1364
+ self.main_radius,
1365
+ self.chart_colors_settings["zodiac_transit_ring_0"],
1366
+ self.chart_colors_settings["paper_1"],
1367
+ self.chart_type,
1368
+ self.third_circle_radius,
1369
+ )
763
1370
 
764
- # Draw planet grid
765
- if self.chart_type in ["Transit", "Synastry"]:
766
- if self.chart_type == "Transit":
767
- second_subject_table_name = self.language_settings["transit_name"]
1371
+ # Aspects
1372
+ if self.double_chart_aspect_grid_type == "list":
1373
+ title = self.language_settings.get("return_aspects", "Natal to Return Aspects")
1374
+ template_dict["makeAspectGrid"] = ""
1375
+ template_dict["makeDoubleChartAspectList"] = draw_transit_aspect_list(title, self.aspects_list, self.planets_settings, self.aspects_settings, max_columns=7)
768
1376
  else:
769
- second_subject_table_name = self.t_user.name
1377
+ template_dict["makeAspectGrid"] = ""
1378
+ template_dict["makeDoubleChartAspectList"] = draw_transit_aspect_grid(
1379
+ self.chart_colors_settings["paper_0"],
1380
+ self.available_planets_setting,
1381
+ self.aspects_list,
1382
+ 550,
1383
+ 450,
1384
+ )
770
1385
 
771
- template_dict["makePlanetGrid"] = draw_planet_grid(
772
- planets_and_houses_grid_title=self.language_settings["planets_and_house"],
773
- subject_name=self.user.name,
1386
+ template_dict["makeAspects"] = self._draw_all_transit_aspects_lines(self.main_radius, self.main_radius - 160)
1387
+
1388
+ # Chart title
1389
+ if self.second_obj.return_type == "Solar":
1390
+ template_dict["stringTitle"] = f"{self.first_obj.name} - {self.language_settings.get('solar_return', 'Solar Return')}"
1391
+ else:
1392
+ template_dict["stringTitle"] = f"{self.first_obj.name} - {self.language_settings.get('lunar_return', 'Lunar Return')}"
1393
+
1394
+
1395
+ # Top left section
1396
+ # Subject
1397
+ latitude_string = convert_latitude_coordinate_to_string(self.first_obj.lat, self.language_settings["north"], self.language_settings["south"]) # type: ignore
1398
+ longitude_string = convert_longitude_coordinate_to_string(self.first_obj.lng, self.language_settings["east"], self.language_settings["west"]) # type: ignore
1399
+
1400
+ # Return
1401
+ return_latitude_string = convert_latitude_coordinate_to_string(self.second_obj.lat, self.language_settings["north"], self.language_settings["south"]) # type: ignore
1402
+ return_longitude_string = convert_longitude_coordinate_to_string(self.second_obj.lng, self.language_settings["east"], self.language_settings["west"]) # type: ignore
1403
+
1404
+ if self.second_obj.return_type == "Solar":
1405
+ template_dict["top_left_0"] = f"{self.language_settings.get('solar_return', 'Solar Return')}:"
1406
+ else:
1407
+ template_dict["top_left_0"] = f"{self.language_settings.get('lunar_return', 'Lunar Return')}:"
1408
+ template_dict["top_left_1"] = format_datetime_with_timezone(self.second_obj.iso_formatted_local_datetime) # type: ignore
1409
+ template_dict["top_left_2"] = f"{return_latitude_string} / {return_longitude_string}"
1410
+ template_dict["top_left_3"] = f"{self.first_obj.name}"
1411
+ template_dict["top_left_4"] = format_datetime_with_timezone(self.first_obj.iso_formatted_local_datetime) # type: ignore
1412
+ template_dict["top_left_5"] = f"{latitude_string} / {longitude_string}"
1413
+
1414
+ # Bottom left section
1415
+ if self.first_obj.zodiac_type == "Tropic":
1416
+ zodiac_info = f"{self.language_settings.get('zodiac', 'Zodiac')}: {self.language_settings.get('tropical', 'Tropical')}"
1417
+ else:
1418
+ mode_const = "SIDM_" + self.first_obj.sidereal_mode # type: ignore
1419
+ mode_name = swe.get_ayanamsa_name(getattr(swe, mode_const))
1420
+ zodiac_info = f"{self.language_settings.get('ayanamsa', 'Ayanamsa')}: {mode_name}"
1421
+
1422
+ template_dict["bottom_left_0"] = zodiac_info
1423
+ template_dict["bottom_left_1"] = f"{self.language_settings.get('domification', 'Domification')}: {self.language_settings.get('houses_system_' + self.first_obj.houses_system_identifier, self.first_obj.houses_system_name)}"
1424
+ template_dict["bottom_left_2"] = f'{self.language_settings.get("lunation_day", "Lunation Day")}: {self.first_obj.lunar_phase.get("moon_phase", "")}'
1425
+ template_dict["bottom_left_3"] = f'{self.language_settings.get("lunar_phase", "Lunar Phase")}: {self.language_settings.get(self.first_obj.lunar_phase.moon_phase_name.lower().replace(" ", "_"), self.first_obj.lunar_phase.moon_phase_name)}'
1426
+ template_dict["bottom_left_4"] = f'{self.language_settings.get("perspective_type", "Perspective")}: {self.language_settings.get(self.first_obj.perspective_type.lower().replace(" ", "_"), self.first_obj.perspective_type)}'
1427
+
1428
+ # Moon phase section calculations
1429
+ template_dict["makeLunarPhase"] = makeLunarPhase(self.first_obj.lunar_phase["degrees_between_s_m"], self.geolat)
1430
+
1431
+ # Houses and planet drawing
1432
+ template_dict["makeMainHousesGrid"] = draw_main_house_grid(
1433
+ main_subject_houses_list=first_subject_houses_list,
1434
+ text_color=self.chart_colors_settings["paper_0"],
1435
+ house_cusp_generale_name_label=self.language_settings["cusp"],
1436
+ )
1437
+
1438
+ template_dict["makeSecondaryHousesGrid"] = draw_secondary_house_grid(
1439
+ secondary_subject_houses_list=second_subject_houses_list,
1440
+ text_color=self.chart_colors_settings["paper_0"],
1441
+ house_cusp_generale_name_label=self.language_settings["cusp"],
1442
+ )
1443
+
1444
+ template_dict["makeHouses"] = draw_houses_cusps_and_text_number(
1445
+ r=self.main_radius,
1446
+ first_subject_houses_list=first_subject_houses_list,
1447
+ standard_house_cusp_color=self.chart_colors_settings["houses_radix_line"],
1448
+ first_house_color=self.planets_settings[12]["color"],
1449
+ tenth_house_color=self.planets_settings[13]["color"],
1450
+ seventh_house_color=self.planets_settings[14]["color"],
1451
+ fourth_house_color=self.planets_settings[15]["color"],
1452
+ c1=self.first_circle_radius,
1453
+ c3=self.third_circle_radius,
1454
+ chart_type=self.chart_type,
1455
+ second_subject_houses_list=second_subject_houses_list,
1456
+ transit_house_cusp_color=self.chart_colors_settings["houses_transit_line"],
1457
+ )
1458
+
1459
+ template_dict["makePlanets"] = draw_planets(
1460
+ available_kerykeion_celestial_points=self.available_kerykeion_celestial_points,
1461
+ available_planets_setting=self.available_planets_setting,
1462
+ second_subject_available_kerykeion_celestial_points=self.t_available_kerykeion_celestial_points,
1463
+ radius=self.main_radius,
1464
+ main_subject_first_house_degree_ut=self.first_obj.first_house.abs_pos,
1465
+ main_subject_seventh_house_degree_ut=self.first_obj.seventh_house.abs_pos,
1466
+ chart_type=self.chart_type,
1467
+ third_circle_radius=self.third_circle_radius,
1468
+ )
1469
+
1470
+ # Planet grid
1471
+ if self.second_obj.return_type == "Solar":
1472
+ first_return_grid_title = f"{self.first_obj.name} ({self.language_settings.get('inner_wheel', 'Inner Wheel')})"
1473
+ second_return_grid_title = f"{self.language_settings.get('solar_return', 'Solar Return')} ({self.language_settings.get('outer_wheel', 'Outer Wheel')})"
1474
+ else:
1475
+ first_return_grid_title = f"{self.first_obj.name} ({self.language_settings.get('inner_wheel', 'Inner Wheel')})"
1476
+ second_return_grid_title = f"{self.language_settings.get("lunar_return", "Lunar Return")} ({self.language_settings.get("outer_wheel", "Outer Wheel")})"
1477
+ template_dict["makeMainPlanetGrid"] = draw_main_planet_grid(
1478
+ planets_and_houses_grid_title="",
1479
+ subject_name=first_return_grid_title,
774
1480
  available_kerykeion_celestial_points=self.available_kerykeion_celestial_points,
775
1481
  chart_type=self.chart_type,
776
1482
  text_color=self.chart_colors_settings["paper_0"],
777
1483
  celestial_point_language=self.language_settings["celestial_points"],
778
- second_subject_name=second_subject_table_name,
1484
+ )
1485
+ template_dict["makeSecondaryPlanetGrid"] = draw_secondary_planet_grid(
1486
+ planets_and_houses_grid_title="",
1487
+ second_subject_name=second_return_grid_title,
779
1488
  second_subject_available_kerykeion_celestial_points=self.t_available_kerykeion_celestial_points,
1489
+ chart_type=self.chart_type,
1490
+ text_color=self.chart_colors_settings["paper_0"],
1491
+ celestial_point_language=self.language_settings["celestial_points"]
780
1492
  )
781
- else:
782
- if self.chart_type == "Composite":
783
- subject_name = f"{self.user.first_subject.name} {self.language_settings['and_word']} {self.user.second_subject.name}"
1493
+
1494
+ house_comparison_factory = HouseComparisonFactory(
1495
+ first_subject=self.first_obj,
1496
+ second_subject=self.second_obj,
1497
+ active_points=self.active_points,
1498
+ )
1499
+ house_comparison = house_comparison_factory.get_house_comparison()
1500
+
1501
+ template_dict["makeHouseComparisonGrid"] = draw_house_comparison_grid(
1502
+ house_comparison,
1503
+ celestial_point_language=self.language_settings["celestial_points"],
1504
+ active_points=self.active_points,
1505
+ points_owner_subject_number=2, # The second subject is the Solar Return
1506
+ house_position_comparison_label=self.language_settings.get("house_position_comparison", "House Position Comparison"),
1507
+ return_point_label=self.language_settings.get("return_point", "Return Point"),
1508
+ return_label=self.language_settings.get("Return", "Return"),
1509
+ radix_label=self.language_settings.get("Natal", "Natal"),
1510
+ )
1511
+
1512
+ elif self.chart_type == "SingleWheelReturn":
1513
+ # Set viewbox
1514
+ template_dict["viewbox"] = self._BASIC_CHART_VIEWBOX
1515
+
1516
+ # Rings and circles
1517
+ template_dict["transitRing"] = ""
1518
+ template_dict["degreeRing"] = draw_degree_ring(
1519
+ self.main_radius,
1520
+ self.first_circle_radius,
1521
+ self.first_obj.seventh_house.abs_pos,
1522
+ self.chart_colors_settings["paper_0"],
1523
+ )
1524
+ template_dict["first_circle"] = draw_first_circle(
1525
+ self.main_radius,
1526
+ self.chart_colors_settings["zodiac_radix_ring_2"],
1527
+ self.chart_type,
1528
+ self.first_circle_radius,
1529
+ )
1530
+ template_dict["second_circle"] = draw_second_circle(
1531
+ self.main_radius,
1532
+ self.chart_colors_settings["zodiac_radix_ring_1"],
1533
+ self.chart_colors_settings["paper_1"],
1534
+ self.chart_type,
1535
+ self.second_circle_radius,
1536
+ )
1537
+ template_dict["third_circle"] = draw_third_circle(
1538
+ self.main_radius,
1539
+ self.chart_colors_settings["zodiac_radix_ring_0"],
1540
+ self.chart_colors_settings["paper_1"],
1541
+ self.chart_type,
1542
+ self.third_circle_radius,
1543
+ )
1544
+
1545
+ # Aspects
1546
+ template_dict["makeDoubleChartAspectList"] = ""
1547
+ template_dict["makeAspectGrid"] = draw_aspect_grid(
1548
+ self.chart_colors_settings["paper_0"],
1549
+ self.available_planets_setting,
1550
+ self.aspects_list,
1551
+ )
1552
+ template_dict["makeAspects"] = self._draw_all_aspects_lines(self.main_radius, self.main_radius - self.third_circle_radius)
1553
+
1554
+ # Chart title
1555
+ template_dict["stringTitle"] = self.first_obj.name
1556
+
1557
+ # Top left section
1558
+ latitude_string = convert_latitude_coordinate_to_string(self.geolat, self.language_settings["north"], self.language_settings["south"])
1559
+ longitude_string = convert_longitude_coordinate_to_string(self.geolon, self.language_settings["east"], self.language_settings["west"])
1560
+
1561
+ template_dict["top_left_0"] = f'{self.language_settings["info"]}:'
1562
+ template_dict["top_left_1"] = format_datetime_with_timezone(self.first_obj.iso_formatted_local_datetime) # type: ignore
1563
+ template_dict["top_left_2"] = f"{self.first_obj.city}, {self.first_obj.nation}"
1564
+ template_dict["top_left_3"] = f"{self.language_settings['latitude']}: {latitude_string}"
1565
+ template_dict["top_left_4"] = f"{self.language_settings['longitude']}: {longitude_string}"
1566
+
1567
+ if self.first_obj.return_type == "Solar":
1568
+ template_dict["top_left_5"] = f"{self.language_settings['type']}: {self.language_settings.get('solar_return', 'Solar Return')}"
1569
+ else:
1570
+ template_dict["top_left_5"] = f"{self.language_settings['type']}: {self.language_settings.get('lunar_return', 'Lunar Return')}"
1571
+
1572
+ # Bottom left section
1573
+ if self.first_obj.zodiac_type == "Tropic":
1574
+ zodiac_info = f"{self.language_settings.get('zodiac', 'Zodiac')}: {self.language_settings.get('tropical', 'Tropical')}"
784
1575
  else:
785
- subject_name = self.user.name
1576
+ mode_const = "SIDM_" + self.first_obj.sidereal_mode # type: ignore
1577
+ mode_name = swe.get_ayanamsa_name(getattr(swe, mode_const))
1578
+ zodiac_info = f"{self.language_settings.get('ayanamsa', 'Ayanamsa')}: {mode_name}"
1579
+
1580
+ template_dict["bottom_left_0"] = zodiac_info
1581
+ template_dict["bottom_left_1"] = f"{self.language_settings.get('houses_system_' + self.first_obj.houses_system_identifier, self.first_obj.houses_system_name)} {self.language_settings.get('houses', 'Houses')}"
1582
+ template_dict["bottom_left_2"] = f'{self.language_settings.get("lunation_day", "Lunation Day")}: {self.first_obj.lunar_phase.get("moon_phase", "")}'
1583
+ template_dict["bottom_left_3"] = f'{self.language_settings.get("lunar_phase", "Lunar Phase")}: {self.language_settings.get(self.first_obj.lunar_phase.moon_phase_name.lower().replace(" ", "_"), self.first_obj.lunar_phase.moon_phase_name)}'
1584
+ template_dict["bottom_left_4"] = f'{self.language_settings.get("perspective_type", "Perspective")}: {self.language_settings.get(self.first_obj.perspective_type.lower().replace(" ", "_"), self.first_obj.perspective_type)}'
1585
+
1586
+ # Moon phase section calculations
1587
+ template_dict["makeLunarPhase"] = makeLunarPhase(self.first_obj.lunar_phase["degrees_between_s_m"], self.geolat)
1588
+
1589
+ # Houses and planet drawing
1590
+ template_dict["makeMainHousesGrid"] = draw_main_house_grid(
1591
+ main_subject_houses_list=first_subject_houses_list,
1592
+ text_color=self.chart_colors_settings["paper_0"],
1593
+ house_cusp_generale_name_label=self.language_settings["cusp"],
1594
+ )
1595
+ template_dict["makeSecondaryHousesGrid"] = ""
786
1596
 
787
- template_dict["makePlanetGrid"] = draw_planet_grid(
1597
+ template_dict["makeHouses"] = draw_houses_cusps_and_text_number(
1598
+ r=self.main_radius,
1599
+ first_subject_houses_list=first_subject_houses_list,
1600
+ standard_house_cusp_color=self.chart_colors_settings["houses_radix_line"],
1601
+ first_house_color=self.planets_settings[12]["color"],
1602
+ tenth_house_color=self.planets_settings[13]["color"],
1603
+ seventh_house_color=self.planets_settings[14]["color"],
1604
+ fourth_house_color=self.planets_settings[15]["color"],
1605
+ c1=self.first_circle_radius,
1606
+ c3=self.third_circle_radius,
1607
+ chart_type=self.chart_type,
1608
+ )
1609
+
1610
+ template_dict["makePlanets"] = draw_planets(
1611
+ available_planets_setting=self.available_planets_setting,
1612
+ chart_type=self.chart_type,
1613
+ radius=self.main_radius,
1614
+ available_kerykeion_celestial_points=self.available_kerykeion_celestial_points,
1615
+ third_circle_radius=self.third_circle_radius,
1616
+ main_subject_first_house_degree_ut=self.first_obj.first_house.abs_pos,
1617
+ main_subject_seventh_house_degree_ut=self.first_obj.seventh_house.abs_pos,
1618
+ )
1619
+
1620
+ template_dict["makeMainPlanetGrid"] = draw_main_planet_grid(
788
1621
  planets_and_houses_grid_title=self.language_settings["planets_and_house"],
789
- subject_name=subject_name,
1622
+ subject_name=self.first_obj.name,
790
1623
  available_kerykeion_celestial_points=self.available_kerykeion_celestial_points,
791
1624
  chart_type=self.chart_type,
792
1625
  text_color=self.chart_colors_settings["paper_0"],
793
1626
  celestial_point_language=self.language_settings["celestial_points"],
794
1627
  )
795
-
796
- # Set date time string
797
- if self.chart_type in ["Composite"]:
798
- # First Subject Latitude and Longitude
799
- latitude = convert_latitude_coordinate_to_string(self.user.first_subject.lat, self.language_settings["north_letter"], self.language_settings["south_letter"])
800
- longitude = convert_longitude_coordinate_to_string(self.user.first_subject.lng, self.language_settings["east_letter"], self.language_settings["west_letter"])
801
- template_dict["top_left_2"] = f"{latitude} {longitude}"
802
- else:
803
- dt = datetime.fromisoformat(self.user.iso_formatted_local_datetime)
804
- custom_format = dt.strftime('%Y-%m-%d %H:%M [%z]')
805
- custom_format = custom_format[:-3] + ':' + custom_format[-3:]
806
- template_dict["top_left_2"] = f"{custom_format}"
1628
+ template_dict["makeSecondaryPlanetGrid"] = ""
1629
+ template_dict["makeHouseComparisonGrid"] = ""
807
1630
 
808
1631
  return ChartTemplateDictionary(**template_dict)
809
1632
 
810
- def makeTemplate(self, minify: bool = False, remove_css_variables = False) -> str:
1633
+ def makeTemplate(self, minify: bool = False, remove_css_variables=False) -> str:
811
1634
  """
812
1635
  Render the full chart SVG as a string.
813
1636
 
@@ -840,14 +1663,14 @@ class KerykeionChartSVG:
840
1663
  template = inline_css_variables_in_svg(template)
841
1664
 
842
1665
  if minify:
843
- template = scourString(template).replace('"', "'").replace("\n", "").replace("\t","").replace(" ", "").replace(" ", "")
1666
+ template = scourString(template).replace('"', "'").replace("\n", "").replace("\t", "").replace(" ", "").replace(" ", "")
844
1667
 
845
1668
  else:
846
1669
  template = template.replace('"', "'")
847
1670
 
848
1671
  return template
849
1672
 
850
- def makeSVG(self, minify: bool = False, remove_css_variables = False):
1673
+ def makeSVG(self, minify: bool = False, remove_css_variables=False):
851
1674
  """
852
1675
  Generate and save the full chart SVG to disk.
853
1676
 
@@ -864,14 +1687,19 @@ class KerykeionChartSVG:
864
1687
 
865
1688
  self.template = self.makeTemplate(minify, remove_css_variables)
866
1689
 
867
- chartname = self.output_directory / f"{self.user.name} - {self.chart_type} Chart.svg"
1690
+ if self.chart_type == "Return" and self.second_obj.return_type == "Lunar":
1691
+ chartname = self.output_directory / f"{self.first_obj.name} - {self.chart_type} Chart - Lunar Return.svg"
1692
+ elif self.chart_type == "Return" and self.second_obj.return_type == "Solar":
1693
+ chartname = self.output_directory / f"{self.first_obj.name} - {self.chart_type} Chart - Solar Return.svg"
1694
+ else:
1695
+ chartname = self.output_directory / f"{self.first_obj.name} - {self.chart_type} Chart.svg"
868
1696
 
869
1697
  with open(chartname, "w", encoding="utf-8", errors="ignore") as output_file:
870
1698
  output_file.write(self.template)
871
1699
 
872
1700
  print(f"SVG Generated Correctly in: {chartname}")
873
1701
 
874
- def makeWheelOnlyTemplate(self, minify: bool = False, remove_css_variables = False):
1702
+ def makeWheelOnlyTemplate(self, minify: bool = False, remove_css_variables=False):
875
1703
  """
876
1704
  Render the wheel-only chart SVG as a string.
877
1705
 
@@ -886,7 +1714,12 @@ class KerykeionChartSVG:
886
1714
  str: SVG markup for the chart wheel only.
887
1715
  """
888
1716
 
889
- with open(Path(__file__).parent / "templates" / "wheel_only.xml", "r", encoding="utf-8", errors="ignore") as f:
1717
+ with open(
1718
+ Path(__file__).parent / "templates" / "wheel_only.xml",
1719
+ "r",
1720
+ encoding="utf-8",
1721
+ errors="ignore",
1722
+ ) as f:
890
1723
  template = f.read()
891
1724
 
892
1725
  template_dict = self._create_template_dictionary()
@@ -896,14 +1729,14 @@ class KerykeionChartSVG:
896
1729
  template = inline_css_variables_in_svg(template)
897
1730
 
898
1731
  if minify:
899
- template = scourString(template).replace('"', "'").replace("\n", "").replace("\t","").replace(" ", "").replace(" ", "")
1732
+ template = scourString(template).replace('"', "'").replace("\n", "").replace("\t", "").replace(" ", "").replace(" ", "")
900
1733
 
901
1734
  else:
902
1735
  template = template.replace('"', "'")
903
1736
 
904
1737
  return template
905
1738
 
906
- def makeWheelOnlySVG(self, minify: bool = False, remove_css_variables = False):
1739
+ def makeWheelOnlySVG(self, minify: bool = False, remove_css_variables=False):
907
1740
  """
908
1741
  Generate and save wheel-only chart SVG to disk.
909
1742
 
@@ -919,14 +1752,14 @@ class KerykeionChartSVG:
919
1752
  """
920
1753
 
921
1754
  template = self.makeWheelOnlyTemplate(minify, remove_css_variables)
922
- chartname = self.output_directory / f"{self.user.name} - {self.chart_type} Chart - Wheel Only.svg"
1755
+ chartname = self.output_directory / f"{self.first_obj.name} - {self.chart_type} Chart - Wheel Only.svg"
923
1756
 
924
1757
  with open(chartname, "w", encoding="utf-8", errors="ignore") as output_file:
925
1758
  output_file.write(template)
926
1759
 
927
1760
  print(f"SVG Generated Correctly in: {chartname}")
928
1761
 
929
- def makeAspectGridOnlyTemplate(self, minify: bool = False, remove_css_variables = False):
1762
+ def makeAspectGridOnlyTemplate(self, minify: bool = False, remove_css_variables=False):
930
1763
  """
931
1764
  Render the aspect-grid-only chart SVG as a string.
932
1765
 
@@ -941,15 +1774,30 @@ class KerykeionChartSVG:
941
1774
  str: SVG markup for the aspect grid only.
942
1775
  """
943
1776
 
944
- with open(Path(__file__).parent / "templates" / "aspect_grid_only.xml", "r", encoding="utf-8", errors="ignore") as f:
1777
+ with open(
1778
+ Path(__file__).parent / "templates" / "aspect_grid_only.xml",
1779
+ "r",
1780
+ encoding="utf-8",
1781
+ errors="ignore",
1782
+ ) as f:
945
1783
  template = f.read()
946
1784
 
947
1785
  template_dict = self._create_template_dictionary()
948
1786
 
949
- if self.chart_type in ["Transit", "Synastry"]:
950
- aspects_grid = draw_transit_aspect_grid(self.chart_colors_settings['paper_0'], self.available_planets_setting, self.aspects_list)
1787
+ if self.chart_type in ["Transit", "Synastry", "Return"]:
1788
+ aspects_grid = draw_transit_aspect_grid(
1789
+ self.chart_colors_settings["paper_0"],
1790
+ self.available_planets_setting,
1791
+ self.aspects_list,
1792
+ )
951
1793
  else:
952
- aspects_grid = draw_aspect_grid(self.chart_colors_settings['paper_0'], self.available_planets_setting, self.aspects_list, x_start=50, y_start=250)
1794
+ aspects_grid = draw_aspect_grid(
1795
+ self.chart_colors_settings["paper_0"],
1796
+ self.available_planets_setting,
1797
+ self.aspects_list,
1798
+ x_start=50,
1799
+ y_start=250,
1800
+ )
953
1801
 
954
1802
  template = Template(template).substitute({**template_dict, "makeAspectGrid": aspects_grid})
955
1803
 
@@ -957,14 +1805,14 @@ class KerykeionChartSVG:
957
1805
  template = inline_css_variables_in_svg(template)
958
1806
 
959
1807
  if minify:
960
- template = scourString(template).replace('"', "'").replace("\n", "").replace("\t","").replace(" ", "").replace(" ", "")
1808
+ template = scourString(template).replace('"', "'").replace("\n", "").replace("\t", "").replace(" ", "").replace(" ", "")
961
1809
 
962
1810
  else:
963
1811
  template = template.replace('"', "'")
964
1812
 
965
1813
  return template
966
1814
 
967
- def makeAspectGridOnlySVG(self, minify: bool = False, remove_css_variables = False):
1815
+ def makeAspectGridOnlySVG(self, minify: bool = False, remove_css_variables=False):
968
1816
  """
969
1817
  Generate and save aspect-grid-only chart SVG to disk.
970
1818
 
@@ -980,240 +1828,125 @@ class KerykeionChartSVG:
980
1828
  """
981
1829
 
982
1830
  template = self.makeAspectGridOnlyTemplate(minify, remove_css_variables)
983
- chartname = self.output_directory / f"{self.user.name} - {self.chart_type} Chart - Aspect Grid Only.svg"
1831
+ chartname = self.output_directory / f"{self.first_obj.name} - {self.chart_type} Chart - Aspect Grid Only.svg"
984
1832
 
985
1833
  with open(chartname, "w", encoding="utf-8", errors="ignore") as output_file:
986
1834
  output_file.write(template)
987
1835
 
988
1836
  print(f"SVG Generated Correctly in: {chartname}")
989
1837
 
1838
+
990
1839
  if __name__ == "__main__":
991
1840
  from kerykeion.utilities import setup_logging
992
- from kerykeion.composite_subject_factory import CompositeSubjectFactory
993
- setup_logging(level="debug")
994
-
995
- first = AstrologicalSubject("John Lennon", 1940, 10, 9, 18, 30, "Liverpool", "GB")
996
- second = AstrologicalSubject("Paul McCartney", 1942, 6, 18, 15, 30, "Liverpool", "GB")
997
-
998
- # Internal Natal Chart
999
- internal_natal_chart = KerykeionChartSVG(first)
1000
- internal_natal_chart.makeSVG()
1001
-
1002
- # External Natal Chart
1003
- external_natal_chart = KerykeionChartSVG(first, "ExternalNatal", second)
1004
- external_natal_chart.makeSVG()
1005
-
1006
- # Synastry Chart
1007
- synastry_chart = KerykeionChartSVG(first, "Synastry", second)
1008
- synastry_chart.makeSVG()
1009
-
1010
- # Transits Chart
1011
- transits_chart = KerykeionChartSVG(first, "Transit", second)
1012
- transits_chart.makeSVG()
1013
-
1014
- # Sidereal Birth Chart (Lahiri)
1015
- sidereal_subject = AstrologicalSubject("John Lennon Lahiri", 1940, 10, 9, 18, 30, "Liverpool", "GB", zodiac_type="Sidereal", sidereal_mode="LAHIRI")
1016
- sidereal_chart = KerykeionChartSVG(sidereal_subject)
1017
- sidereal_chart.makeSVG()
1018
-
1019
- # Sidereal Birth Chart (Fagan-Bradley)
1020
- sidereal_subject = AstrologicalSubject("John Lennon Fagan-Bradley", 1940, 10, 9, 18, 30, "Liverpool", "GB", zodiac_type="Sidereal", sidereal_mode="FAGAN_BRADLEY")
1021
- sidereal_chart = KerykeionChartSVG(sidereal_subject)
1022
- sidereal_chart.makeSVG()
1023
-
1024
- # Sidereal Birth Chart (DeLuce)
1025
- sidereal_subject = AstrologicalSubject("John Lennon DeLuce", 1940, 10, 9, 18, 30, "Liverpool", "GB", zodiac_type="Sidereal", sidereal_mode="DELUCE")
1026
- sidereal_chart = KerykeionChartSVG(sidereal_subject)
1027
- sidereal_chart.makeSVG()
1028
-
1029
- # Sidereal Birth Chart (J2000)
1030
- sidereal_subject = AstrologicalSubject("John Lennon J2000", 1940, 10, 9, 18, 30, "Liverpool", "GB", zodiac_type="Sidereal", sidereal_mode="J2000")
1031
- sidereal_chart = KerykeionChartSVG(sidereal_subject)
1032
- sidereal_chart.makeSVG()
1033
-
1034
- # House System Morinus
1035
- morinus_house_subject = AstrologicalSubject("John Lennon - House System Morinus", 1940, 10, 9, 18, 30, "Liverpool", "GB", houses_system_identifier="M")
1036
- morinus_house_chart = KerykeionChartSVG(morinus_house_subject)
1037
- morinus_house_chart.makeSVG()
1038
-
1039
- ## To check all the available house systems uncomment the following code:
1040
- # from kerykeion.kr_types import HousesSystemIdentifier
1041
- # from typing import get_args
1042
- # for i in get_args(HousesSystemIdentifier):
1043
- # alternatives_house_subject = AstrologicalSubject(f"John Lennon - House System {i}", 1940, 10, 9, 18, 30, "Liverpool", "GB", houses_system=i)
1044
- # alternatives_house_chart = KerykeionChartSVG(alternatives_house_subject)
1045
- # alternatives_house_chart.makeSVG()
1046
-
1047
- # With True Geocentric Perspective
1048
- true_geocentric_subject = AstrologicalSubject("John Lennon - True Geocentric", 1940, 10, 9, 18, 30, "Liverpool", "GB", perspective_type="True Geocentric")
1049
- true_geocentric_chart = KerykeionChartSVG(true_geocentric_subject)
1050
- true_geocentric_chart.makeSVG()
1051
-
1052
- # With Heliocentric Perspective
1053
- heliocentric_subject = AstrologicalSubject("John Lennon - Heliocentric", 1940, 10, 9, 18, 30, "Liverpool", "GB", perspective_type="Heliocentric")
1054
- heliocentric_chart = KerykeionChartSVG(heliocentric_subject)
1055
- heliocentric_chart.makeSVG()
1056
-
1057
- # With Topocentric Perspective
1058
- topocentric_subject = AstrologicalSubject("John Lennon - Topocentric", 1940, 10, 9, 18, 30, "Liverpool", "GB", perspective_type="Topocentric")
1059
- topocentric_chart = KerykeionChartSVG(topocentric_subject)
1060
- topocentric_chart.makeSVG()
1061
-
1062
- # Minified SVG
1063
- minified_subject = AstrologicalSubject("John Lennon - Minified", 1940, 10, 9, 18, 30, "Liverpool", "GB")
1064
- minified_chart = KerykeionChartSVG(minified_subject)
1065
- minified_chart.makeSVG(minify=True)
1066
-
1067
- # Dark Theme Natal Chart
1068
- dark_theme_subject = AstrologicalSubject("John Lennon - Dark Theme", 1940, 10, 9, 18, 30, "Liverpool", "GB")
1069
- dark_theme_natal_chart = KerykeionChartSVG(dark_theme_subject, theme="dark")
1070
- dark_theme_natal_chart.makeSVG()
1071
-
1072
- # Dark High Contrast Theme Natal Chart
1073
- dark_high_contrast_theme_subject = AstrologicalSubject("John Lennon - Dark High Contrast Theme", 1940, 10, 9, 18, 30, "Liverpool", "GB")
1074
- dark_high_contrast_theme_natal_chart = KerykeionChartSVG(dark_high_contrast_theme_subject, theme="dark-high-contrast")
1075
- dark_high_contrast_theme_natal_chart.makeSVG()
1076
-
1077
- # Light Theme Natal Chart
1078
- light_theme_subject = AstrologicalSubject("John Lennon - Light Theme", 1940, 10, 9, 18, 30, "Liverpool", "GB")
1079
- light_theme_natal_chart = KerykeionChartSVG(light_theme_subject, theme="light")
1080
- light_theme_natal_chart.makeSVG()
1081
-
1082
- # Dark Theme External Natal Chart
1083
- dark_theme_external_subject = AstrologicalSubject("John Lennon - Dark Theme External", 1940, 10, 9, 18, 30, "Liverpool", "GB")
1084
- dark_theme_external_chart = KerykeionChartSVG(dark_theme_external_subject, "ExternalNatal", second, theme="dark")
1085
- dark_theme_external_chart.makeSVG()
1086
-
1087
- # Dark Theme Synastry Chart
1088
- dark_theme_synastry_subject = AstrologicalSubject("John Lennon - DTS", 1940, 10, 9, 18, 30, "Liverpool", "GB")
1089
- dark_theme_synastry_chart = KerykeionChartSVG(dark_theme_synastry_subject, "Synastry", second, theme="dark")
1090
- dark_theme_synastry_chart.makeSVG()
1091
-
1092
- # Wheel Natal Only Chart
1093
- wheel_only_subject = AstrologicalSubject("John Lennon - Wheel Only", 1940, 10, 9, 18, 30, "Liverpool", "GB")
1094
- wheel_only_chart = KerykeionChartSVG(wheel_only_subject)
1095
- wheel_only_chart.makeWheelOnlySVG()
1096
-
1097
- # Wheel External Natal Only Chart
1098
- wheel_external_subject = AstrologicalSubject("John Lennon - Wheel External Only", 1940, 10, 9, 18, 30, "Liverpool", "GB")
1099
- wheel_external_chart = KerykeionChartSVG(wheel_external_subject, "ExternalNatal", second)
1100
- wheel_external_chart.makeWheelOnlySVG()
1101
-
1102
- # Wheel Synastry Only Chart
1103
- wheel_synastry_subject = AstrologicalSubject("John Lennon - Wheel Synastry Only", 1940, 10, 9, 18, 30, "Liverpool", "GB")
1104
- wheel_synastry_chart = KerykeionChartSVG(wheel_synastry_subject, "Synastry", second)
1105
- wheel_synastry_chart.makeWheelOnlySVG()
1106
-
1107
- # Wheel Transit Only Chart
1108
- wheel_transit_subject = AstrologicalSubject("John Lennon - Wheel Transit Only", 1940, 10, 9, 18, 30, "Liverpool", "GB")
1109
- wheel_transit_chart = KerykeionChartSVG(wheel_transit_subject, "Transit", second)
1110
- wheel_transit_chart.makeWheelOnlySVG()
1111
-
1112
- # Wheel Sidereal Birth Chart (Lahiri) Dark Theme
1113
- sidereal_dark_subject = AstrologicalSubject("John Lennon Lahiri - Dark Theme", 1940, 10, 9, 18, 30, "Liverpool", "GB", zodiac_type="Sidereal", sidereal_mode="LAHIRI")
1114
- sidereal_dark_chart = KerykeionChartSVG(sidereal_dark_subject, theme="dark")
1115
- sidereal_dark_chart.makeWheelOnlySVG()
1116
-
1117
- # Wheel Sidereal Birth Chart (Fagan-Bradley) Light Theme
1118
- sidereal_light_subject = AstrologicalSubject("John Lennon Fagan-Bradley - Light Theme", 1940, 10, 9, 18, 30, "Liverpool", "GB", zodiac_type="Sidereal", sidereal_mode="FAGAN_BRADLEY")
1119
- sidereal_light_chart = KerykeionChartSVG(sidereal_light_subject, theme="light")
1120
- sidereal_light_chart.makeWheelOnlySVG()
1121
-
1122
- # Aspect Grid Only Natal Chart
1123
- aspect_grid_only_subject = AstrologicalSubject("John Lennon - Aspect Grid Only", 1940, 10, 9, 18, 30, "Liverpool", "GB")
1124
- aspect_grid_only_chart = KerykeionChartSVG(aspect_grid_only_subject)
1125
- aspect_grid_only_chart.makeAspectGridOnlySVG()
1126
-
1127
- # Aspect Grid Only Dark Theme Natal Chart
1128
- aspect_grid_dark_subject = AstrologicalSubject("John Lennon - Aspect Grid Dark Theme", 1940, 10, 9, 18, 30, "Liverpool", "GB")
1129
- aspect_grid_dark_chart = KerykeionChartSVG(aspect_grid_dark_subject, theme="dark")
1130
- aspect_grid_dark_chart.makeAspectGridOnlySVG()
1131
-
1132
- # Aspect Grid Only Light Theme Natal Chart
1133
- aspect_grid_light_subject = AstrologicalSubject("John Lennon - Aspect Grid Light Theme", 1940, 10, 9, 18, 30, "Liverpool", "GB")
1134
- aspect_grid_light_chart = KerykeionChartSVG(aspect_grid_light_subject, theme="light")
1135
- aspect_grid_light_chart.makeAspectGridOnlySVG()
1136
-
1137
- # Synastry Chart Aspect Grid Only
1138
- aspect_grid_synastry_subject = AstrologicalSubject("John Lennon - Aspect Grid Synastry", 1940, 10, 9, 18, 30, "Liverpool", "GB")
1139
- aspect_grid_synastry_chart = KerykeionChartSVG(aspect_grid_synastry_subject, "Synastry", second)
1140
- aspect_grid_synastry_chart.makeAspectGridOnlySVG()
1141
-
1142
- # Transit Chart Aspect Grid Only
1143
- aspect_grid_transit_subject = AstrologicalSubject("John Lennon - Aspect Grid Transit", 1940, 10, 9, 18, 30, "Liverpool", "GB")
1144
- aspect_grid_transit_chart = KerykeionChartSVG(aspect_grid_transit_subject, "Transit", second)
1145
- aspect_grid_transit_chart.makeAspectGridOnlySVG()
1146
-
1147
- # Synastry Chart Aspect Grid Only Dark Theme
1148
- aspect_grid_dark_synastry_subject = AstrologicalSubject("John Lennon - Aspect Grid Dark Synastry", 1940, 10, 9, 18, 30, "Liverpool", "GB")
1149
- aspect_grid_dark_synastry_chart = KerykeionChartSVG(aspect_grid_dark_synastry_subject, "Synastry", second, theme="dark")
1150
- aspect_grid_dark_synastry_chart.makeAspectGridOnlySVG()
1151
-
1152
- # Synastry Chart With draw_transit_aspect_list table
1153
- synastry_chart_with_table_list_subject = AstrologicalSubject("John Lennon - SCTWL", 1940, 10, 9, 18, 30, "Liverpool", "GB")
1154
- synastry_chart_with_table_list = KerykeionChartSVG(synastry_chart_with_table_list_subject, "Synastry", second, double_chart_aspect_grid_type="list", theme="dark")
1155
- synastry_chart_with_table_list.makeSVG()
1156
-
1157
- # Transit Chart With draw_transit_aspect_grid table
1158
- transit_chart_with_table_grid_subject = AstrologicalSubject("John Lennon - TCWTG", 1940, 10, 9, 18, 30, "Liverpool", "GB")
1159
- transit_chart_with_table_grid = KerykeionChartSVG(transit_chart_with_table_grid_subject, "Transit", second, double_chart_aspect_grid_type="table", theme="dark")
1160
- transit_chart_with_table_grid.makeSVG()
1161
-
1162
- # Chines Language Chart
1163
- chinese_subject = AstrologicalSubject("Hua Chenyu", 1990, 2, 7, 12, 0, "Hunan", "CN")
1164
- chinese_chart = KerykeionChartSVG(chinese_subject, chart_language="CN")
1165
- chinese_chart.makeSVG()
1166
-
1167
- # French Language Chart
1168
- french_subject = AstrologicalSubject("Jeanne Moreau", 1928, 1, 23, 10, 0, "Paris", "FR")
1169
- french_chart = KerykeionChartSVG(french_subject, chart_language="FR")
1170
- french_chart.makeSVG()
1171
-
1172
- # Spanish Language Chart
1173
- spanish_subject = AstrologicalSubject("Antonio Banderas", 1960, 8, 10, 12, 0, "Malaga", "ES")
1174
- spanish_chart = KerykeionChartSVG(spanish_subject, chart_language="ES")
1175
- spanish_chart.makeSVG()
1176
-
1177
- # Portuguese Language Chart
1178
- portuguese_subject = AstrologicalSubject("Cristiano Ronaldo", 1985, 2, 5, 5, 25, "Funchal", "PT")
1179
- portuguese_chart = KerykeionChartSVG(portuguese_subject, chart_language="PT")
1180
- portuguese_chart.makeSVG()
1181
-
1182
- # Italian Language Chart
1183
- italian_subject = AstrologicalSubject("Sophia Loren", 1934, 9, 20, 2, 0, "Rome", "IT")
1184
- italian_chart = KerykeionChartSVG(italian_subject, chart_language="IT")
1185
- italian_chart.makeSVG()
1186
-
1187
- # Russian Language Chart
1188
- russian_subject = AstrologicalSubject("Mikhail Bulgakov", 1891, 5, 15, 12, 0, "Kiev", "UA")
1189
- russian_chart = KerykeionChartSVG(russian_subject, chart_language="RU")
1190
- russian_chart.makeSVG()
1191
-
1192
- # Turkish Language Chart
1193
- turkish_subject = AstrologicalSubject("Mehmet Oz", 1960, 6, 11, 12, 0, "Istanbul", "TR")
1194
- turkish_chart = KerykeionChartSVG(turkish_subject, chart_language="TR")
1195
- turkish_chart.makeSVG()
1196
-
1197
- # German Language Chart
1198
- german_subject = AstrologicalSubject("Albert Einstein", 1879, 3, 14, 11, 30, "Ulm", "DE")
1199
- german_chart = KerykeionChartSVG(german_subject, chart_language="DE")
1200
- german_chart.makeSVG()
1201
-
1202
- # Hindi Language Chart
1203
- hindi_subject = AstrologicalSubject("Amitabh Bachchan", 1942, 10, 11, 4, 0, "Allahabad", "IN")
1204
- hindi_chart = KerykeionChartSVG(hindi_subject, chart_language="HI")
1205
- hindi_chart.makeSVG()
1206
-
1207
- # Kanye West Natal Chart
1208
- kanye_west_subject = AstrologicalSubject("Kanye", 1977, 6, 8, 8, 45, "Atlanta", "US")
1209
- kanye_west_chart = KerykeionChartSVG(kanye_west_subject)
1210
- kanye_west_chart.makeSVG()
1211
-
1212
- # Composite Chart
1213
- angelina = AstrologicalSubject("Angelina Jolie", 1975, 6, 4, 9, 9, "Los Angeles", "US", lng=-118.15, lat=34.03, tz_str="America/Los_Angeles")
1214
- brad = AstrologicalSubject("Brad Pitt", 1963, 12, 18, 6, 31, "Shawnee", "US", lng=-96.56, lat=35.20, tz_str="America/Chicago")
1215
-
1216
- composite_subject_factory = CompositeSubjectFactory(angelina, brad)
1217
- composite_subject_model = composite_subject_factory.get_midpoint_composite_subject_model()
1218
- composite_chart = KerykeionChartSVG(composite_subject_model, "Composite")
1219
- composite_chart.makeSVG()
1841
+ from kerykeion.planetary_return_factory import PlanetaryReturnFactory
1842
+ from kerykeion.astrological_subject_factory import AstrologicalSubjectFactory
1843
+
1844
+ setup_logging(level="info")
1845
+
1846
+ subject = AstrologicalSubjectFactory.from_birth_data("John Lennon", 1940, 10, 9, 18, 30, "Liverpool", "GB")
1847
+
1848
+ return_factory = PlanetaryReturnFactory(
1849
+ subject,
1850
+ city="Los Angeles",
1851
+ nation="US",
1852
+ lng=-118.2437,
1853
+ lat=34.0522,
1854
+ tz_str="America/Los_Angeles",
1855
+ altitude=0,
1856
+ )
1857
+
1858
+ ###
1859
+ ## Birth Chart
1860
+ birth_chart = KerykeionChartSVG(
1861
+ first_obj=subject,
1862
+ chart_language="IT",
1863
+ theme="strawberry",
1864
+ )
1865
+ birth_chart.makeSVG() # minify=True, remove_css_variables=True)
1866
+
1867
+ ###
1868
+ ## Solar Return Chart
1869
+ solar_return = return_factory.next_return_from_iso_formatted_time(
1870
+ "2025-01-09T18:30:00+01:00", # UTC+1
1871
+ return_type="Solar",
1872
+ )
1873
+ solar_return_chart = KerykeionChartSVG(
1874
+ first_obj=subject, chart_type="Return",
1875
+ second_obj=solar_return,
1876
+ chart_language="IT",
1877
+ theme="classic",
1878
+ active_points=["Sun", "Moon", "Mercury", "Venus", "Mars", "Jupiter", "Saturn", "Ascendant"],
1879
+ )
1880
+
1881
+ solar_return_chart.makeSVG() # minify=True, remove_css_variables=True)
1882
+
1883
+ ###
1884
+ ## Single wheel return
1885
+ single_wheel_return_chart = KerykeionChartSVG(
1886
+ first_obj=solar_return,
1887
+ chart_type="SingleWheelReturn",
1888
+ second_obj=solar_return,
1889
+ chart_language="IT",
1890
+ theme="dark",
1891
+ active_points=["Sun", "Moon", "Mercury", "Venus", "Mars", "Jupiter", "Saturn"],
1892
+ )
1893
+
1894
+ single_wheel_return_chart.makeSVG() # minify=True, remove_css_variables=True)
1895
+
1896
+ ###
1897
+ ## Lunar return
1898
+ lunar_return = return_factory.next_return_from_iso_formatted_time(
1899
+ "2025-01-09T18:30:00+01:00", # UTC+1
1900
+ return_type="Lunar",
1901
+ )
1902
+ lunar_return_chart = KerykeionChartSVG(
1903
+ first_obj=subject,
1904
+ chart_type="Return",
1905
+ second_obj=lunar_return,
1906
+ chart_language="IT",
1907
+ theme="dark",
1908
+ active_points=["Sun", "Moon", "Mercury", "Venus", "Mars", "Jupiter", "Saturn"],
1909
+ )
1910
+ lunar_return_chart.makeSVG() # minify=True, remove_css_variables=True)
1911
+
1912
+ ###
1913
+ ## Transit Chart
1914
+ transit = AstrologicalSubjectFactory.from_current_time()
1915
+ transit_chart = KerykeionChartSVG(
1916
+ first_obj=subject,
1917
+ chart_type="Transit",
1918
+ second_obj=transit,
1919
+ chart_language="IT",
1920
+ theme="dark",
1921
+ active_points=["Sun", "Moon", "Mercury", "Venus", "Mars", "Jupiter", "Saturn", "Ascendant"]
1922
+ )
1923
+ transit_chart.makeSVG() # minify=True, remove_css_variables=True)
1924
+
1925
+ ###
1926
+ ## Synastry Chart
1927
+ second_subject = AstrologicalSubjectFactory.from_birth_data("Yoko Ono", 1933, 2, 18, 18, 30, "Tokyo", "JP")
1928
+ synastry_chart = KerykeionChartSVG(
1929
+ first_obj=subject,
1930
+ chart_type="Synastry",
1931
+ second_obj=second_subject,
1932
+ chart_language="IT",
1933
+ theme="dark",
1934
+ active_points=["Sun", "Moon", "Mercury", "Venus", "Mars", "Jupiter", "Saturn"],
1935
+ )
1936
+ synastry_chart.makeSVG() # minify=True, remove_css_variables=True)
1937
+
1938
+ ##
1939
+ # Transit Chart with Grid
1940
+ subject.name = "Grid"
1941
+ transit_chart_with_grid = KerykeionChartSVG(
1942
+ first_obj=subject,
1943
+ chart_type="Transit",
1944
+ second_obj=transit,
1945
+ chart_language="IT",
1946
+ theme="dark",
1947
+ active_points=["Sun", "Moon", "Mercury", "Venus", "Mars", "Jupiter", "Saturn", "Ascendant"],
1948
+ double_chart_aspect_grid_type="table"
1949
+ )
1950
+ transit_chart_with_grid.makeSVG() # minify=True, remove_css_variables=True)
1951
+ transit_chart_with_grid.makeAspectGridOnlySVG()
1952
+ transit_chart_with_grid.makeWheelOnlySVG()