kerykeion 5.0.0a9__py3-none-any.whl → 5.1.8__py3-none-any.whl

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

Potentially problematic release.


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

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