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