kerykeion 4.26.2__py3-none-any.whl → 5.0.0__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 (77) hide show
  1. kerykeion/__init__.py +54 -11
  2. kerykeion/aspects/__init__.py +5 -2
  3. kerykeion/aspects/aspects_factory.py +569 -0
  4. kerykeion/aspects/aspects_utils.py +81 -8
  5. kerykeion/astrological_subject_factory.py +1897 -0
  6. kerykeion/backword.py +773 -0
  7. kerykeion/chart_data_factory.py +549 -0
  8. kerykeion/charts/chart_drawer.py +2601 -0
  9. kerykeion/charts/charts_utils.py +948 -177
  10. kerykeion/charts/draw_planets.py +602 -351
  11. kerykeion/charts/templates/aspect_grid_only.xml +328 -202
  12. kerykeion/charts/templates/chart.xml +432 -272
  13. kerykeion/charts/templates/wheel_only.xml +350 -214
  14. kerykeion/charts/themes/black-and-white.css +148 -0
  15. kerykeion/charts/themes/classic.css +107 -76
  16. kerykeion/charts/themes/dark-high-contrast.css +145 -107
  17. kerykeion/charts/themes/dark.css +146 -107
  18. kerykeion/charts/themes/light.css +146 -103
  19. kerykeion/charts/themes/strawberry.css +158 -0
  20. kerykeion/composite_subject_factory.py +253 -51
  21. kerykeion/ephemeris_data_factory.py +434 -0
  22. kerykeion/fetch_geonames.py +27 -8
  23. kerykeion/house_comparison/__init__.py +6 -0
  24. kerykeion/house_comparison/house_comparison_factory.py +103 -0
  25. kerykeion/house_comparison/house_comparison_utils.py +126 -0
  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 -132
  30. kerykeion/kr_types/kr_models.py +14 -318
  31. kerykeion/kr_types/settings_models.py +15 -203
  32. kerykeion/planetary_return_factory.py +805 -0
  33. kerykeion/relationship_score_factory.py +301 -0
  34. kerykeion/report.py +751 -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 +605 -0
  40. kerykeion/schemas/settings_models.py +180 -0
  41. kerykeion/settings/__init__.py +20 -1
  42. kerykeion/settings/chart_defaults.py +444 -0
  43. kerykeion/settings/config_constants.py +117 -12
  44. kerykeion/settings/kerykeion_settings.py +31 -73
  45. kerykeion/settings/translation_strings.py +1479 -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 +393 -114
  57. kerykeion-5.0.0.dist-info/METADATA +1176 -0
  58. kerykeion-5.0.0.dist-info/RECORD +63 -0
  59. {kerykeion-4.26.2.dist-info → kerykeion-5.0.0.dist-info}/WHEEL +1 -1
  60. kerykeion/aspects/natal_aspects.py +0 -172
  61. kerykeion/aspects/synastry_aspects.py +0 -124
  62. kerykeion/aspects/transits_time_range.py +0 -41
  63. kerykeion/astrological_subject.py +0 -841
  64. kerykeion/charts/kerykeion_chart_svg.py +0 -1219
  65. kerykeion/enums.py +0 -57
  66. kerykeion/ephemeris_data.py +0 -242
  67. kerykeion/kr_types/chart_types.py +0 -95
  68. kerykeion/relationship_score/__init__.py +0 -2
  69. kerykeion/relationship_score/relationship_score.py +0 -175
  70. kerykeion/relationship_score/relationship_score_factory.py +0 -230
  71. kerykeion/settings/kr.config.json +0 -1258
  72. kerykeion/transits_time_range.py +0 -124
  73. kerykeion-4.26.2.dist-info/LICENSE +0 -661
  74. kerykeion-4.26.2.dist-info/METADATA +0 -629
  75. kerykeion-4.26.2.dist-info/RECORD +0 -46
  76. kerykeion-4.26.2.dist-info/entry_points.txt +0 -3
  77. /LICENSE → /kerykeion-5.0.0.dist-info/licenses/LICENSE +0 -0
@@ -1,1219 +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
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.astrological_subject import AstrologicalSubject
15
- from kerykeion.kr_types import KerykeionException, ChartType, KerykeionPointModel, Sign, ActiveAspect
16
- from kerykeion.kr_types import ChartTemplateDictionary
17
- from kerykeion.kr_types.kr_models import AstrologicalSubjectModel, CompositeSubjectModel
18
- from kerykeion.kr_types.settings_models import KerykeionSettingsCelestialPointModel, KerykeionSettingsModel
19
- from kerykeion.kr_types.kr_literals import KerykeionChartTheme, KerykeionChartLanguage, AxialCusps, Planet
20
- from kerykeion.charts.charts_utils import (
21
- draw_zodiac_slice,
22
- convert_latitude_coordinate_to_string,
23
- convert_longitude_coordinate_to_string,
24
- draw_aspect_line,
25
- draw_transit_ring_degree_steps,
26
- draw_degree_ring,
27
- draw_transit_ring,
28
- draw_first_circle,
29
- draw_second_circle,
30
- draw_third_circle,
31
- draw_aspect_grid,
32
- draw_houses_cusps_and_text_number,
33
- draw_transit_aspect_list,
34
- draw_transit_aspect_grid,
35
- calculate_moon_phase_chart_params,
36
- draw_house_grid,
37
- draw_planet_grid,
38
- )
39
- from kerykeion.charts.draw_planets import draw_planets # type: ignore
40
- from kerykeion.utilities import get_houses_list, inline_css_variables_in_svg
41
- from kerykeion.settings.config_constants import DEFAULT_ACTIVE_POINTS, DEFAULT_ACTIVE_ASPECTS
42
- from pathlib import Path
43
- from scour.scour import scourString
44
- from string import Template
45
- from typing import Union, List, Literal
46
- from datetime import datetime
47
-
48
- class KerykeionChartSVG:
49
- """
50
- KerykeionChartSVG generates astrological chart visualizations as SVG files.
51
-
52
- This class supports creating full chart SVGs, wheel-only SVGs, and aspect-grid-only SVGs
53
- for various chart types including Natal, ExternalNatal, Transit, Synastry, and Composite.
54
- Charts are rendered using XML templates and drawing utilities, with customizable themes,
55
- language, active points, and aspects.
56
- The rendered SVGs can be saved to a specified output directory or, by default, to the user's home directory.
57
-
58
- NOTE:
59
- The generated SVG files are optimized for web use, opening in browsers. If you want to
60
- use them in other applications, you might need to adjust the SVG settings or styles.
61
-
62
- Args:
63
- first_obj (AstrologicalSubject | AstrologicalSubjectModel | CompositeSubjectModel):
64
- The primary astrological subject for the chart.
65
- chart_type (ChartType, optional):
66
- The type of chart to generate ('Natal', 'ExternalNatal', 'Transit', 'Synastry', 'Composite').
67
- Defaults to 'Natal'.
68
- second_obj (AstrologicalSubject | AstrologicalSubjectModel, optional):
69
- The secondary subject for Transit or Synastry charts. Not required for Natal or Composite.
70
- new_output_directory (str | Path, optional):
71
- Directory to write generated SVG files. Defaults to the user's home directory.
72
- new_settings_file (Path | dict | KerykeionSettingsModel, optional):
73
- Path or settings object to override default chart configuration (colors, fonts, aspects).
74
- theme (KerykeionChartTheme, optional):
75
- CSS theme for the chart. If None, no default styles are applied. Defaults to 'classic'.
76
- double_chart_aspect_grid_type (Literal['list', 'table'], optional):
77
- Specifies rendering style for double-chart aspect grids. Defaults to 'list'.
78
- chart_language (KerykeionChartLanguage, optional):
79
- Language code for chart labels. Defaults to 'EN'.
80
- active_points (list[Planet | AxialCusps], optional):
81
- List of celestial points and angles to include. Defaults to DEFAULT_ACTIVE_POINTS.
82
- Example:
83
- ["Sun", "Moon", "Mercury", "Venus"]
84
-
85
- active_aspects (list[ActiveAspect], optional):
86
- List of aspects (name and orb) to calculate. Defaults to DEFAULT_ACTIVE_ASPECTS.
87
- Example:
88
- [
89
- {"name": "conjunction", "orb": 10},
90
- {"name": "opposition", "orb": 10},
91
- {"name": "trine", "orb": 8},
92
- {"name": "sextile", "orb": 6},
93
- {"name": "square", "orb": 5},
94
- {"name": "quintile", "orb": 1},
95
- ]
96
-
97
- Public Methods:
98
- makeTemplate(minify=False, remove_css_variables=False) -> str:
99
- Render the full chart SVG as a string without writing to disk. Use `minify=True`
100
- to remove whitespace and quotes, and `remove_css_variables=True` to embed CSS vars.
101
-
102
- makeSVG(minify=False, remove_css_variables=False) -> None:
103
- Generate and write the full chart SVG file to the output directory.
104
- Filenames follow the pattern:
105
- '{subject.name} - {chart_type} Chart.svg'.
106
-
107
- makeWheelOnlyTemplate(minify=False, remove_css_variables=False) -> str:
108
- Render only the chart wheel (no aspect grid) as an SVG string.
109
-
110
- makeWheelOnlySVG(minify=False, remove_css_variables=False) -> None:
111
- Generate and write the wheel-only SVG file:
112
- '{subject.name} - {chart_type} Chart - Wheel Only.svg'.
113
-
114
- makeAspectGridOnlyTemplate(minify=False, remove_css_variables=False) -> str:
115
- Render only the aspect grid as an SVG string.
116
-
117
- makeAspectGridOnlySVG(minify=False, remove_css_variables=False) -> None:
118
- Generate and write the aspect-grid-only SVG file:
119
- '{subject.name} - {chart_type} Chart - Aspect Grid Only.svg'.
120
- """
121
-
122
- # Constants
123
- _BASIC_CHART_VIEWBOX = "0 0 820 550.0"
124
- _WIDE_CHART_VIEWBOX = "0 0 1200 546.0"
125
- _TRANSIT_CHART_WITH_TABLE_VIWBOX = "0 0 960 546.0"
126
-
127
- _DEFAULT_HEIGHT = 550
128
- _DEFAULT_FULL_WIDTH = 1200
129
- _DEFAULT_NATAL_WIDTH = 820
130
- _DEFAULT_FULL_WIDTH_WITH_TABLE = 960
131
- _PLANET_IN_ZODIAC_EXTRA_POINTS = 10
132
-
133
- # Set at init
134
- first_obj: Union[AstrologicalSubject, AstrologicalSubjectModel]
135
- second_obj: Union[AstrologicalSubject, AstrologicalSubjectModel, None]
136
- chart_type: ChartType
137
- new_output_directory: Union[Path, None]
138
- new_settings_file: Union[Path, None, KerykeionSettingsModel, dict]
139
- output_directory: Path
140
- new_settings_file: Union[Path, None, KerykeionSettingsModel, dict]
141
- theme: Union[KerykeionChartTheme, None]
142
- double_chart_aspect_grid_type: Literal["list", "table"]
143
- chart_language: KerykeionChartLanguage
144
- active_points: List[Union[Planet, AxialCusps]]
145
- active_aspects: List[ActiveAspect]
146
-
147
- # Internal properties
148
- fire: float
149
- earth: float
150
- air: float
151
- water: float
152
- first_circle_radius: float
153
- second_circle_radius: float
154
- third_circle_radius: float
155
- width: Union[float, int]
156
- language_settings: dict
157
- chart_colors_settings: dict
158
- planets_settings: dict
159
- aspects_settings: dict
160
- user: Union[AstrologicalSubject, AstrologicalSubjectModel, CompositeSubjectModel]
161
- available_planets_setting: List[KerykeionSettingsCelestialPointModel]
162
- height: float
163
- location: str
164
- geolat: float
165
- geolon: float
166
- template: str
167
-
168
- def __init__(
169
- self,
170
- first_obj: Union[AstrologicalSubject, AstrologicalSubjectModel, CompositeSubjectModel],
171
- chart_type: ChartType = "Natal",
172
- second_obj: Union[AstrologicalSubject, AstrologicalSubjectModel, None] = None,
173
- new_output_directory: Union[str, None] = None,
174
- new_settings_file: Union[Path, None, KerykeionSettingsModel, dict] = None,
175
- theme: Union[KerykeionChartTheme, None] = "classic",
176
- double_chart_aspect_grid_type: Literal["list", "table"] = "list",
177
- chart_language: KerykeionChartLanguage = "EN",
178
- active_points: List[Union[Planet, AxialCusps]] = DEFAULT_ACTIVE_POINTS,
179
- active_aspects: List[ActiveAspect] = DEFAULT_ACTIVE_ASPECTS,
180
- ):
181
- """
182
- Initialize the chart generator with subject data and configuration options.
183
-
184
- Args:
185
- first_obj (AstrologicalSubject, AstrologicalSubjectModel, or CompositeSubjectModel):
186
- Primary astrological subject instance.
187
- chart_type (ChartType, optional):
188
- Type of chart to generate (e.g., 'Natal', 'Transit').
189
- second_obj (AstrologicalSubject or AstrologicalSubjectModel, optional):
190
- Secondary subject for Transit or Synastry charts.
191
- new_output_directory (str or Path, optional):
192
- Base directory to save generated SVG files.
193
- new_settings_file (Path, dict, or KerykeionSettingsModel, optional):
194
- Custom settings source for chart colors, fonts, and aspects.
195
- theme (KerykeionChartTheme or None, optional):
196
- CSS theme to apply; None for default styling.
197
- double_chart_aspect_grid_type (Literal['list','table'], optional):
198
- Layout style for double-chart aspect grids ('list' or 'table').
199
- chart_language (KerykeionChartLanguage, optional):
200
- Language code for chart labels (e.g., 'EN', 'IT').
201
- active_points (List[Planet or AxialCusps], optional):
202
- Celestial points to include in the chart visualization.
203
- active_aspects (List[ActiveAspect], optional):
204
- Aspects to calculate, each defined by name and orb.
205
- """
206
- home_directory = Path.home()
207
- self.new_settings_file = new_settings_file
208
- self.chart_language = chart_language
209
- self.active_points = active_points
210
- self.active_aspects = active_aspects
211
-
212
- if new_output_directory:
213
- self.output_directory = Path(new_output_directory)
214
- else:
215
- self.output_directory = home_directory
216
-
217
- self.parse_json_settings(new_settings_file)
218
- self.chart_type = chart_type
219
-
220
- # Kerykeion instance
221
- self.user = first_obj
222
-
223
- self.available_planets_setting = []
224
- for body in self.planets_settings:
225
- if body["name"] not in active_points:
226
- continue
227
- else:
228
- body["is_active"] = True
229
-
230
- self.available_planets_setting.append(body)
231
-
232
- # Available bodies
233
- available_celestial_points_names = []
234
- for body in self.available_planets_setting:
235
- available_celestial_points_names.append(body["name"].lower())
236
-
237
- self.available_kerykeion_celestial_points: list[KerykeionPointModel] = []
238
- for body in available_celestial_points_names:
239
- self.available_kerykeion_celestial_points.append(self.user.get(body))
240
-
241
- # Makes the sign number list.
242
- if self.chart_type == "Natal" or self.chart_type == "ExternalNatal":
243
- natal_aspects_instance = NatalAspects(
244
- self.user, new_settings_file=self.new_settings_file,
245
- active_points=active_points,
246
- active_aspects=active_aspects,
247
- )
248
- self.aspects_list = natal_aspects_instance.relevant_aspects
249
-
250
- elif self.chart_type == "Transit" or self.chart_type == "Synastry":
251
- if not second_obj:
252
- raise KerykeionException("Second object is required for Transit or Synastry charts.")
253
-
254
- # Kerykeion instance
255
- self.t_user = second_obj
256
-
257
- # Aspects
258
- if self.chart_type == "Transit":
259
- synastry_aspects_instance = SynastryAspects(
260
- self.t_user,
261
- self.user,
262
- new_settings_file=self.new_settings_file,
263
- active_points=active_points,
264
- active_aspects=active_aspects,
265
- )
266
-
267
- else:
268
- synastry_aspects_instance = SynastryAspects(
269
- self.user,
270
- self.t_user,
271
- new_settings_file=self.new_settings_file,
272
- active_points=active_points,
273
- active_aspects=active_aspects,
274
- )
275
-
276
- self.aspects_list = synastry_aspects_instance.relevant_aspects
277
-
278
- self.t_available_kerykeion_celestial_points = []
279
- for body in available_celestial_points_names:
280
- self.t_available_kerykeion_celestial_points.append(self.t_user.get(body))
281
-
282
- elif self.chart_type == "Composite":
283
- if not isinstance(first_obj, CompositeSubjectModel):
284
- raise KerykeionException("First object must be a CompositeSubjectModel instance.")
285
-
286
- self.aspects_list = NatalAspects(self.user, new_settings_file=self.new_settings_file, active_points=active_points).relevant_aspects
287
-
288
- # Double chart aspect grid type
289
- self.double_chart_aspect_grid_type = double_chart_aspect_grid_type
290
-
291
- # screen size
292
- self.height = self._DEFAULT_HEIGHT
293
- if self.chart_type == "Synastry" or self.chart_type == "Transit":
294
- self.width = self._DEFAULT_FULL_WIDTH
295
- elif self.double_chart_aspect_grid_type == "table" and self.chart_type == "Transit":
296
- self.width = self._DEFAULT_FULL_WIDTH_WITH_TABLE
297
- else:
298
- self.width = self._DEFAULT_NATAL_WIDTH
299
-
300
- if self.chart_type in ["Natal", "ExternalNatal", "Synastry"]:
301
- self.location = self.user.city
302
- self.geolat = self.user.lat
303
- self.geolon = self.user.lng
304
-
305
- elif self.chart_type == "Composite":
306
- self.location = ""
307
- self.geolat = (self.user.first_subject.lat + self.user.second_subject.lat) / 2
308
- self.geolon = (self.user.first_subject.lng + self.user.second_subject.lng) / 2
309
-
310
- elif self.chart_type in ["Transit"]:
311
- self.location = self.t_user.city
312
- self.geolat = self.t_user.lat
313
- self.geolon = self.t_user.lng
314
- self.t_name = self.language_settings["transit_name"]
315
-
316
- # Default radius for the chart
317
- self.main_radius = 240
318
-
319
- # Set circle radii based on chart type
320
- if self.chart_type == "ExternalNatal":
321
- self.first_circle_radius, self.second_circle_radius, self.third_circle_radius = 56, 92, 112
322
- else:
323
- self.first_circle_radius, self.second_circle_radius, self.third_circle_radius = 0, 36, 120
324
-
325
- # Initialize element points
326
- self.fire = 0.0
327
- self.earth = 0.0
328
- self.air = 0.0
329
- self.water = 0.0
330
-
331
- # Calculate element points from planets
332
- self._calculate_elements_points_from_planets()
333
-
334
- # Set up theme
335
- if theme not in get_args(KerykeionChartTheme) and theme is not None:
336
- raise KerykeionException(f"Theme {theme} is not available. Set None for default theme.")
337
-
338
- self.set_up_theme(theme)
339
-
340
- def set_up_theme(self, theme: Union[KerykeionChartTheme, None] = None) -> None:
341
- """
342
- Load and apply a CSS theme for the chart visualization.
343
-
344
- Args:
345
- theme (KerykeionChartTheme or None): Name of the theme to apply. If None, no CSS is applied.
346
- """
347
- if theme is None:
348
- self.color_style_tag = ""
349
- return
350
-
351
- theme_dir = Path(__file__).parent / "themes"
352
-
353
- with open(theme_dir / f"{theme}.css", "r") as f:
354
- self.color_style_tag = f.read()
355
-
356
- def set_output_directory(self, dir_path: Path) -> None:
357
- """
358
- Set the directory where generated SVG files will be saved.
359
-
360
- Args:
361
- dir_path (Path): Target directory for SVG output.
362
- """
363
- self.output_directory = dir_path
364
- logging.info(f"Output direcotry set to: {self.output_directory}")
365
-
366
- def parse_json_settings(self, settings_file_or_dict: Union[Path, dict, KerykeionSettingsModel, None]) -> None:
367
- """
368
- Load and parse chart configuration settings.
369
-
370
- Args:
371
- settings_file_or_dict (Path, dict, or KerykeionSettingsModel):
372
- Source for custom chart settings.
373
- """
374
- settings = get_settings(settings_file_or_dict)
375
-
376
- self.language_settings = settings["language_settings"][self.chart_language]
377
- self.chart_colors_settings = settings["chart_colors"]
378
- self.planets_settings = settings["celestial_points"]
379
- self.aspects_settings = settings["aspects"]
380
-
381
- def _draw_zodiac_circle_slices(self, r):
382
- """
383
- Draw zodiac circle slices for each sign.
384
-
385
- Args:
386
- r (float): Outer radius of the zodiac ring.
387
-
388
- Returns:
389
- str: Concatenated SVG elements for zodiac slices.
390
- """
391
- sings = get_args(Sign)
392
- output = ""
393
- for i, sing in enumerate(sings):
394
- output += draw_zodiac_slice(
395
- c1=self.first_circle_radius,
396
- chart_type=self.chart_type,
397
- seventh_house_degree_ut=self.user.seventh_house.abs_pos,
398
- num=i,
399
- r=r,
400
- style=f'fill:{self.chart_colors_settings[f"zodiac_bg_{i}"]}; fill-opacity: 0.5;',
401
- type=sing,
402
- )
403
-
404
- return output
405
-
406
- def _calculate_elements_points_from_planets(self):
407
- """
408
- Compute elemental point totals based on active planetary positions.
409
-
410
- Iterates over each active planet to determine its zodiac element and adds extra points
411
- if the planet is in a related sign. Updates self.fire, self.earth, self.air, and self.water.
412
-
413
- Returns:
414
- None
415
- """
416
-
417
- ZODIAC = (
418
- {"name": "Ari", "element": "fire"},
419
- {"name": "Tau", "element": "earth"},
420
- {"name": "Gem", "element": "air"},
421
- {"name": "Can", "element": "water"},
422
- {"name": "Leo", "element": "fire"},
423
- {"name": "Vir", "element": "earth"},
424
- {"name": "Lib", "element": "air"},
425
- {"name": "Sco", "element": "water"},
426
- {"name": "Sag", "element": "fire"},
427
- {"name": "Cap", "element": "earth"},
428
- {"name": "Aqu", "element": "air"},
429
- {"name": "Pis", "element": "water"},
430
- )
431
-
432
- # Available bodies
433
- available_celestial_points_names = []
434
- for body in self.available_planets_setting:
435
- available_celestial_points_names.append(body["name"].lower())
436
-
437
- # Make list of the points sign
438
- points_sign = []
439
- for planet in available_celestial_points_names:
440
- points_sign.append(self.user.get(planet).sign_num)
441
-
442
- for i in range(len(self.available_planets_setting)):
443
- # element: get extra points if planet is in own zodiac sign.
444
- related_zodiac_signs = self.available_planets_setting[i]["related_zodiac_signs"]
445
- cz = points_sign[i]
446
- extra_points = 0
447
- if related_zodiac_signs != []:
448
- for e in range(len(related_zodiac_signs)):
449
- if int(related_zodiac_signs[e]) == int(cz):
450
- extra_points = self._PLANET_IN_ZODIAC_EXTRA_POINTS
451
-
452
- ele = ZODIAC[points_sign[i]]["element"]
453
- if ele == "fire":
454
- self.fire = self.fire + self.available_planets_setting[i]["element_points"] + extra_points
455
-
456
- elif ele == "earth":
457
- self.earth = self.earth + self.available_planets_setting[i]["element_points"] + extra_points
458
-
459
- elif ele == "air":
460
- self.air = self.air + self.available_planets_setting[i]["element_points"] + extra_points
461
-
462
- elif ele == "water":
463
- self.water = self.water + self.available_planets_setting[i]["element_points"] + extra_points
464
-
465
- def _draw_all_aspects_lines(self, r, ar):
466
- """
467
- Render SVG lines for all aspects in the chart.
468
-
469
- Args:
470
- r (float): Radius at which aspect lines originate.
471
- ar (float): Radius at which aspect lines terminate.
472
-
473
- Returns:
474
- str: SVG markup for all aspect lines.
475
- """
476
- out = ""
477
- for aspect in self.aspects_list:
478
- aspect_name = aspect["aspect"]
479
- aspect_color = next((a["color"] for a in self.aspects_settings if a["name"] == aspect_name), None)
480
- if aspect_color:
481
- out += draw_aspect_line(
482
- r=r,
483
- ar=ar,
484
- aspect=aspect,
485
- color=aspect_color,
486
- seventh_house_degree_ut=self.user.seventh_house.abs_pos
487
- )
488
- return out
489
-
490
- def _draw_all_transit_aspects_lines(self, r, ar):
491
- """
492
- Render SVG lines for all transit aspects in the chart.
493
-
494
- Args:
495
- r (float): Radius at which transit aspect lines originate.
496
- ar (float): Radius at which transit aspect lines terminate.
497
-
498
- Returns:
499
- str: SVG markup for all transit aspect lines.
500
- """
501
- out = ""
502
- for aspect in self.aspects_list:
503
- aspect_name = aspect["aspect"]
504
- aspect_color = next((a["color"] for a in self.aspects_settings if a["name"] == aspect_name), None)
505
- if aspect_color:
506
- out += draw_aspect_line(
507
- r=r,
508
- ar=ar,
509
- aspect=aspect,
510
- color=aspect_color,
511
- seventh_house_degree_ut=self.user.seventh_house.abs_pos
512
- )
513
- return out
514
-
515
- def _create_template_dictionary(self) -> ChartTemplateDictionary:
516
- """
517
- Assemble chart data and rendering instructions into a template dictionary.
518
-
519
- Gathers styling, dimensions, and SVG fragments for chart components based on
520
- chart type and subjects.
521
-
522
- Returns:
523
- ChartTemplateDictionary: Populated structure of template variables.
524
- """
525
- # Initialize template dictionary
526
- template_dict: dict = {}
527
-
528
- # Set the color style tag
529
- template_dict["color_style_tag"] = self.color_style_tag
530
-
531
- # Set chart dimensions
532
- template_dict["chart_height"] = self.height
533
- template_dict["chart_width"] = self.width
534
-
535
- # Set viewbox based on chart type
536
- if self.chart_type in ["Natal", "ExternalNatal", "Composite"]:
537
- template_dict['viewbox'] = self._BASIC_CHART_VIEWBOX
538
- elif self.double_chart_aspect_grid_type == "table" and self.chart_type == "Transit":
539
- template_dict['viewbox'] = self._TRANSIT_CHART_WITH_TABLE_VIWBOX
540
- else:
541
- template_dict['viewbox'] = self._WIDE_CHART_VIEWBOX
542
-
543
- # Generate rings and circles based on chart type
544
- if self.chart_type in ["Transit", "Synastry"]:
545
- template_dict["transitRing"] = draw_transit_ring(self.main_radius, self.chart_colors_settings["paper_1"], self.chart_colors_settings["zodiac_transit_ring_3"])
546
- template_dict["degreeRing"] = draw_transit_ring_degree_steps(self.main_radius, self.user.seventh_house.abs_pos)
547
- template_dict["first_circle"] = draw_first_circle(self.main_radius, self.chart_colors_settings["zodiac_transit_ring_2"], self.chart_type)
548
- template_dict["second_circle"] = draw_second_circle(self.main_radius, self.chart_colors_settings['zodiac_transit_ring_1'], self.chart_colors_settings['paper_1'], self.chart_type)
549
- template_dict['third_circle'] = draw_third_circle(self.main_radius, self.chart_colors_settings['zodiac_transit_ring_0'], self.chart_colors_settings['paper_1'], self.chart_type, self.third_circle_radius)
550
-
551
- if self.double_chart_aspect_grid_type == "list":
552
- title = ""
553
- if self.chart_type == "Synastry":
554
- title = self.language_settings.get("couple_aspects", "Couple Aspects")
555
- else:
556
- title = self.language_settings.get("transit_aspects", "Transit Aspects")
557
-
558
- template_dict["makeAspectGrid"] = draw_transit_aspect_list(title, self.aspects_list, self.planets_settings, self.aspects_settings)
559
- else:
560
- template_dict["makeAspectGrid"] = draw_transit_aspect_grid(self.chart_colors_settings['paper_0'], self.available_planets_setting, self.aspects_list, 550, 450)
561
-
562
- template_dict["makeAspects"] = self._draw_all_transit_aspects_lines(self.main_radius, self.main_radius - 160)
563
- else:
564
- template_dict["transitRing"] = ""
565
- template_dict["degreeRing"] = draw_degree_ring(self.main_radius, self.first_circle_radius, self.user.seventh_house.abs_pos, self.chart_colors_settings["paper_0"])
566
- template_dict['first_circle'] = draw_first_circle(self.main_radius, self.chart_colors_settings["zodiac_radix_ring_2"], self.chart_type, self.first_circle_radius)
567
- template_dict["second_circle"] = draw_second_circle(self.main_radius, self.chart_colors_settings["zodiac_radix_ring_1"], self.chart_colors_settings["paper_1"], self.chart_type, self.second_circle_radius)
568
- template_dict['third_circle'] = draw_third_circle(self.main_radius, self.chart_colors_settings["zodiac_radix_ring_0"], self.chart_colors_settings["paper_1"], self.chart_type, self.third_circle_radius)
569
- template_dict["makeAspectGrid"] = draw_aspect_grid(self.chart_colors_settings['paper_0'], self.available_planets_setting, self.aspects_list)
570
-
571
- template_dict["makeAspects"] = self._draw_all_aspects_lines(self.main_radius, self.main_radius - self.third_circle_radius)
572
-
573
- # Set chart title
574
- if self.chart_type == "Synastry":
575
- template_dict["stringTitle"] = f"{self.user.name} {self.language_settings['and_word']} {self.t_user.name}"
576
- elif self.chart_type == "Transit":
577
- template_dict["stringTitle"] = f"{self.language_settings['transits']} {self.t_user.day}/{self.t_user.month}/{self.t_user.year}"
578
- elif self.chart_type in ["Natal", "ExternalNatal"]:
579
- template_dict["stringTitle"] = self.user.name
580
- elif self.chart_type == "Composite":
581
- template_dict["stringTitle"] = f"{self.user.first_subject.name} {self.language_settings['and_word']} {self.user.second_subject.name}"
582
-
583
- # Zodiac Type Info
584
- if self.user.zodiac_type == 'Tropic':
585
- zodiac_info = f"{self.language_settings.get('zodiac', 'Zodiac')}: {self.language_settings.get('tropical', 'Tropical')}"
586
- else:
587
- mode_const = "SIDM_" + self.user.sidereal_mode # type: ignore
588
- mode_name = swe.get_ayanamsa_name(getattr(swe, mode_const))
589
- zodiac_info = f"{self.language_settings.get('ayanamsa', 'Ayanamsa')}: {mode_name}"
590
-
591
- template_dict["bottom_left_0"] = f"{self.language_settings.get('houses_system_' + self.user.houses_system_identifier, self.user.houses_system_name)} {self.language_settings.get('houses', 'Houses')}"
592
- template_dict["bottom_left_1"] = zodiac_info
593
-
594
- if self.chart_type in ["Natal", "ExternalNatal", "Synastry"]:
595
- template_dict["bottom_left_2"] = f'{self.language_settings.get("lunar_phase", "Lunar Phase")} {self.language_settings.get("day", "Day").lower()}: {self.user.lunar_phase.get("moon_phase", "")}'
596
- template_dict["bottom_left_3"] = f'{self.language_settings.get("lunar_phase", "Lunar Phase")}: {self.language_settings.get(self.user.lunar_phase.moon_phase_name.lower().replace(" ", "_"), self.user.lunar_phase.moon_phase_name)}'
597
- template_dict["bottom_left_4"] = f'{self.language_settings.get(self.user.perspective_type.lower().replace(" ", "_"), self.user.perspective_type)}'
598
- elif self.chart_type == "Transit":
599
- template_dict["bottom_left_2"] = f'{self.language_settings.get("lunar_phase", "Lunar Phase")}: {self.language_settings.get("day", "Day")} {self.t_user.lunar_phase.get("moon_phase", "")}'
600
- template_dict["bottom_left_3"] = f'{self.language_settings.get("lunar_phase", "Lunar Phase")}: {self.t_user.lunar_phase.moon_phase_name}'
601
- template_dict["bottom_left_4"] = f'{self.language_settings.get(self.t_user.perspective_type.lower().replace(" ", "_"), self.t_user.perspective_type)}'
602
- elif self.chart_type == "Composite":
603
- template_dict["bottom_left_2"] = f'{self.user.first_subject.perspective_type}'
604
- template_dict["bottom_left_3"] = f'{self.language_settings.get("composite_chart", "Composite Chart")} - {self.language_settings.get("midpoints", "Midpoints")}'
605
- template_dict["bottom_left_4"] = ""
606
-
607
- # Draw moon phase
608
- moon_phase_dict = calculate_moon_phase_chart_params(
609
- self.user.lunar_phase["degrees_between_s_m"],
610
- self.geolat
611
- )
612
-
613
- template_dict["lunar_phase_rotate"] = moon_phase_dict["lunar_phase_rotate"]
614
- template_dict["lunar_phase_circle_center_x"] = moon_phase_dict["circle_center_x"]
615
- template_dict["lunar_phase_circle_radius"] = moon_phase_dict["circle_radius"]
616
-
617
- if self.chart_type == "Composite":
618
- template_dict["top_left_1"] = f"{datetime.fromisoformat(self.user.first_subject.iso_formatted_local_datetime).strftime('%Y-%m-%d %H:%M')}"
619
- # Set location string
620
- elif len(self.location) > 35:
621
- split_location = self.location.split(",")
622
- if len(split_location) > 1:
623
- template_dict["top_left_1"] = split_location[0] + ", " + split_location[-1]
624
- if len(template_dict["top_left_1"]) > 35:
625
- template_dict["top_left_1"] = template_dict["top_left_1"][:35] + "..."
626
- else:
627
- template_dict["top_left_1"] = self.location[:35] + "..."
628
- else:
629
- template_dict["top_left_1"] = self.location
630
-
631
- # Set chart name
632
- if self.chart_type in ["Synastry", "Transit"]:
633
- template_dict["top_left_0"] = f"{self.user.name}:"
634
- elif self.chart_type in ["Natal", "ExternalNatal"]:
635
- template_dict["top_left_0"] = f'{self.language_settings["info"]}:'
636
- elif self.chart_type == "Composite":
637
- template_dict["top_left_0"] = f'{self.user.first_subject.name}'
638
-
639
- # Set additional information for Synastry chart type
640
- if self.chart_type == "Synastry":
641
- template_dict["top_left_3"] = f"{self.t_user.name}: "
642
- template_dict["top_left_4"] = self.t_user.city
643
- template_dict["top_left_5"] = f"{self.t_user.year}-{self.t_user.month}-{self.t_user.day} {self.t_user.hour:02d}:{self.t_user.minute:02d}"
644
- elif self.chart_type == "Composite":
645
- template_dict["top_left_3"] = self.user.second_subject.name
646
- template_dict["top_left_4"] = f"{datetime.fromisoformat(self.user.second_subject.iso_formatted_local_datetime).strftime('%Y-%m-%d %H:%M')}"
647
- latitude_string = convert_latitude_coordinate_to_string(self.user.second_subject.lat, self.language_settings['north_letter'], self.language_settings['south_letter'])
648
- longitude_string = convert_longitude_coordinate_to_string(self.user.second_subject.lng, self.language_settings['east_letter'], self.language_settings['west_letter'])
649
- template_dict["top_left_5"] = f"{latitude_string} / {longitude_string}"
650
- else:
651
- latitude_string = convert_latitude_coordinate_to_string(self.geolat, self.language_settings['north'], self.language_settings['south'])
652
- longitude_string = convert_longitude_coordinate_to_string(self.geolon, self.language_settings['east'], self.language_settings['west'])
653
- template_dict["top_left_3"] = f"{self.language_settings['latitude']}: {latitude_string}"
654
- template_dict["top_left_4"] = f"{self.language_settings['longitude']}: {longitude_string}"
655
- template_dict["top_left_5"] = f"{self.language_settings['type']}: {self.language_settings.get(self.chart_type, self.chart_type)}"
656
-
657
-
658
- # Set paper colors
659
- template_dict["paper_color_0"] = self.chart_colors_settings["paper_0"]
660
- template_dict["paper_color_1"] = self.chart_colors_settings["paper_1"]
661
-
662
- # Set planet colors
663
- for planet in self.planets_settings:
664
- planet_id = planet["id"]
665
- template_dict[f"planets_color_{planet_id}"] = planet["color"] # type: ignore
666
-
667
- # Set zodiac colors
668
- for i in range(12):
669
- template_dict[f"zodiac_color_{i}"] = self.chart_colors_settings[f"zodiac_icon_{i}"] # type: ignore
670
-
671
- # Set orb colors
672
- for aspect in self.aspects_settings:
673
- template_dict[f"orb_color_{aspect['degree']}"] = aspect['color'] # type: ignore
674
-
675
- # Drawing functions
676
- template_dict["makeZodiac"] = self._draw_zodiac_circle_slices(self.main_radius)
677
-
678
- first_subject_houses_list = get_houses_list(self.user)
679
-
680
- # Draw houses grid and cusps
681
- if self.chart_type in ["Transit", "Synastry"]:
682
- second_subject_houses_list = get_houses_list(self.t_user)
683
-
684
- template_dict["makeHousesGrid"] = draw_house_grid(
685
- main_subject_houses_list=first_subject_houses_list,
686
- secondary_subject_houses_list=second_subject_houses_list,
687
- chart_type=self.chart_type,
688
- text_color=self.chart_colors_settings["paper_0"],
689
- house_cusp_generale_name_label=self.language_settings["cusp"]
690
- )
691
-
692
- template_dict["makeHouses"] = draw_houses_cusps_and_text_number(
693
- r=self.main_radius,
694
- first_subject_houses_list=first_subject_houses_list,
695
- standard_house_cusp_color=self.chart_colors_settings["houses_radix_line"],
696
- first_house_color=self.planets_settings[12]["color"],
697
- tenth_house_color=self.planets_settings[13]["color"],
698
- seventh_house_color=self.planets_settings[14]["color"],
699
- fourth_house_color=self.planets_settings[15]["color"],
700
- c1=self.first_circle_radius,
701
- c3=self.third_circle_radius,
702
- chart_type=self.chart_type,
703
- second_subject_houses_list=second_subject_houses_list,
704
- transit_house_cusp_color=self.chart_colors_settings["houses_transit_line"],
705
- )
706
-
707
- else:
708
- template_dict["makeHousesGrid"] = draw_house_grid(
709
- main_subject_houses_list=first_subject_houses_list,
710
- chart_type=self.chart_type,
711
- text_color=self.chart_colors_settings["paper_0"],
712
- house_cusp_generale_name_label=self.language_settings["cusp"]
713
- )
714
-
715
- template_dict["makeHouses"] = draw_houses_cusps_and_text_number(
716
- r=self.main_radius,
717
- first_subject_houses_list=first_subject_houses_list,
718
- standard_house_cusp_color=self.chart_colors_settings["houses_radix_line"],
719
- first_house_color=self.planets_settings[12]["color"],
720
- tenth_house_color=self.planets_settings[13]["color"],
721
- seventh_house_color=self.planets_settings[14]["color"],
722
- fourth_house_color=self.planets_settings[15]["color"],
723
- c1=self.first_circle_radius,
724
- c3=self.third_circle_radius,
725
- chart_type=self.chart_type,
726
- )
727
-
728
- # Draw planets
729
- if self.chart_type in ["Transit", "Synastry"]:
730
- template_dict["makePlanets"] = draw_planets(
731
- available_kerykeion_celestial_points=self.available_kerykeion_celestial_points,
732
- available_planets_setting=self.available_planets_setting,
733
- second_subject_available_kerykeion_celestial_points=self.t_available_kerykeion_celestial_points,
734
- radius=self.main_radius,
735
- main_subject_first_house_degree_ut=self.user.first_house.abs_pos,
736
- main_subject_seventh_house_degree_ut=self.user.seventh_house.abs_pos,
737
- chart_type=self.chart_type,
738
- third_circle_radius=self.third_circle_radius,
739
- )
740
- else:
741
- template_dict["makePlanets"] = draw_planets(
742
- available_planets_setting=self.available_planets_setting,
743
- chart_type=self.chart_type,
744
- radius=self.main_radius,
745
- available_kerykeion_celestial_points=self.available_kerykeion_celestial_points,
746
- third_circle_radius=self.third_circle_radius,
747
- main_subject_first_house_degree_ut=self.user.first_house.abs_pos,
748
- main_subject_seventh_house_degree_ut=self.user.seventh_house.abs_pos
749
- )
750
-
751
- # Draw elements percentages
752
- total = self.fire + self.water + self.earth + self.air
753
-
754
- fire_percentage = int(round(100 * self.fire / total))
755
- earth_percentage = int(round(100 * self.earth / total))
756
- air_percentage = int(round(100 * self.air / total))
757
- water_percentage = int(round(100 * self.water / total))
758
-
759
- template_dict["fire_string"] = f"{self.language_settings['fire']} {fire_percentage}%"
760
- template_dict["earth_string"] = f"{self.language_settings['earth']} {earth_percentage}%"
761
- template_dict["air_string"] = f"{self.language_settings['air']} {air_percentage}%"
762
- template_dict["water_string"] = f"{self.language_settings['water']} {water_percentage}%"
763
-
764
- # Draw planet grid
765
- if self.chart_type in ["Transit", "Synastry"]:
766
- if self.chart_type == "Transit":
767
- second_subject_table_name = self.language_settings["transit_name"]
768
- else:
769
- second_subject_table_name = self.t_user.name
770
-
771
- template_dict["makePlanetGrid"] = draw_planet_grid(
772
- planets_and_houses_grid_title=self.language_settings["planets_and_house"],
773
- subject_name=self.user.name,
774
- available_kerykeion_celestial_points=self.available_kerykeion_celestial_points,
775
- chart_type=self.chart_type,
776
- text_color=self.chart_colors_settings["paper_0"],
777
- celestial_point_language=self.language_settings["celestial_points"],
778
- second_subject_name=second_subject_table_name,
779
- second_subject_available_kerykeion_celestial_points=self.t_available_kerykeion_celestial_points,
780
- )
781
- else:
782
- if self.chart_type == "Composite":
783
- subject_name = f"{self.user.first_subject.name} {self.language_settings['and_word']} {self.user.second_subject.name}"
784
- else:
785
- subject_name = self.user.name
786
-
787
- template_dict["makePlanetGrid"] = draw_planet_grid(
788
- planets_and_houses_grid_title=self.language_settings["planets_and_house"],
789
- subject_name=subject_name,
790
- available_kerykeion_celestial_points=self.available_kerykeion_celestial_points,
791
- chart_type=self.chart_type,
792
- text_color=self.chart_colors_settings["paper_0"],
793
- celestial_point_language=self.language_settings["celestial_points"],
794
- )
795
-
796
- # Set date time string
797
- if self.chart_type in ["Composite"]:
798
- # First Subject Latitude and Longitude
799
- latitude = convert_latitude_coordinate_to_string(self.user.first_subject.lat, self.language_settings["north_letter"], self.language_settings["south_letter"])
800
- longitude = convert_longitude_coordinate_to_string(self.user.first_subject.lng, self.language_settings["east_letter"], self.language_settings["west_letter"])
801
- template_dict["top_left_2"] = f"{latitude} {longitude}"
802
- else:
803
- dt = datetime.fromisoformat(self.user.iso_formatted_local_datetime)
804
- custom_format = dt.strftime('%Y-%m-%d %H:%M [%z]')
805
- custom_format = custom_format[:-3] + ':' + custom_format[-3:]
806
- template_dict["top_left_2"] = f"{custom_format}"
807
-
808
- return ChartTemplateDictionary(**template_dict)
809
-
810
- def makeTemplate(self, minify: bool = False, remove_css_variables = False) -> str:
811
- """
812
- Render the full chart SVG as a string.
813
-
814
- Reads the XML template, substitutes variables, and optionally inlines CSS
815
- variables and minifies the output.
816
-
817
- Args:
818
- minify (bool): Remove whitespace and quotes for compactness.
819
- remove_css_variables (bool): Embed CSS variable definitions.
820
-
821
- Returns:
822
- str: SVG markup as a string.
823
- """
824
- td = self._create_template_dictionary()
825
-
826
- DATA_DIR = Path(__file__).parent
827
- xml_svg = DATA_DIR / "templates" / "chart.xml"
828
-
829
- # read template
830
- with open(xml_svg, "r", encoding="utf-8", errors="ignore") as f:
831
- template = Template(f.read()).substitute(td)
832
-
833
- # return filename
834
-
835
- logging.debug(f"Template dictionary keys: {td.keys()}")
836
-
837
- self._create_template_dictionary()
838
-
839
- if remove_css_variables:
840
- template = inline_css_variables_in_svg(template)
841
-
842
- if minify:
843
- template = scourString(template).replace('"', "'").replace("\n", "").replace("\t","").replace(" ", "").replace(" ", "")
844
-
845
- else:
846
- template = template.replace('"', "'")
847
-
848
- return template
849
-
850
- def makeSVG(self, minify: bool = False, remove_css_variables = False):
851
- """
852
- Generate and save the full chart SVG to disk.
853
-
854
- Calls makeTemplate to render the SVG, then writes a file named
855
- "{subject.name} - {chart_type} Chart.svg" in the output directory.
856
-
857
- Args:
858
- minify (bool): Pass-through to makeTemplate for compact output.
859
- remove_css_variables (bool): Pass-through to makeTemplate to embed CSS variables.
860
-
861
- Returns:
862
- None
863
- """
864
-
865
- self.template = self.makeTemplate(minify, remove_css_variables)
866
-
867
- chartname = self.output_directory / f"{self.user.name} - {self.chart_type} Chart.svg"
868
-
869
- with open(chartname, "w", encoding="utf-8", errors="ignore") as output_file:
870
- output_file.write(self.template)
871
-
872
- print(f"SVG Generated Correctly in: {chartname}")
873
-
874
- def makeWheelOnlyTemplate(self, minify: bool = False, remove_css_variables = False):
875
- """
876
- Render the wheel-only chart SVG as a string.
877
-
878
- Reads the wheel-only XML template, substitutes chart data, and applies optional
879
- CSS inlining and minification.
880
-
881
- Args:
882
- minify (bool): Remove whitespace and quotes for compactness.
883
- remove_css_variables (bool): Embed CSS variable definitions.
884
-
885
- Returns:
886
- str: SVG markup for the chart wheel only.
887
- """
888
-
889
- with open(Path(__file__).parent / "templates" / "wheel_only.xml", "r", encoding="utf-8", errors="ignore") as f:
890
- template = f.read()
891
-
892
- template_dict = self._create_template_dictionary()
893
- template = Template(template).substitute(template_dict)
894
-
895
- if remove_css_variables:
896
- template = inline_css_variables_in_svg(template)
897
-
898
- if minify:
899
- template = scourString(template).replace('"', "'").replace("\n", "").replace("\t","").replace(" ", "").replace(" ", "")
900
-
901
- else:
902
- template = template.replace('"', "'")
903
-
904
- return template
905
-
906
- def makeWheelOnlySVG(self, minify: bool = False, remove_css_variables = False):
907
- """
908
- Generate and save wheel-only chart SVG to disk.
909
-
910
- Calls makeWheelOnlyTemplate and writes a file named
911
- "{subject.name} - {chart_type} Chart - Wheel Only.svg" in the output directory.
912
-
913
- Args:
914
- minify (bool): Pass-through to makeWheelOnlyTemplate for compact output.
915
- remove_css_variables (bool): Pass-through to makeWheelOnlyTemplate to embed CSS variables.
916
-
917
- Returns:
918
- None
919
- """
920
-
921
- template = self.makeWheelOnlyTemplate(minify, remove_css_variables)
922
- chartname = self.output_directory / f"{self.user.name} - {self.chart_type} Chart - Wheel Only.svg"
923
-
924
- with open(chartname, "w", encoding="utf-8", errors="ignore") as output_file:
925
- output_file.write(template)
926
-
927
- print(f"SVG Generated Correctly in: {chartname}")
928
-
929
- def makeAspectGridOnlyTemplate(self, minify: bool = False, remove_css_variables = False):
930
- """
931
- Render the aspect-grid-only chart SVG as a string.
932
-
933
- Reads the aspect-grid XML template, generates the aspect grid based on chart type,
934
- and applies optional CSS inlining and minification.
935
-
936
- Args:
937
- minify (bool): Remove whitespace and quotes for compactness.
938
- remove_css_variables (bool): Embed CSS variable definitions.
939
-
940
- Returns:
941
- str: SVG markup for the aspect grid only.
942
- """
943
-
944
- with open(Path(__file__).parent / "templates" / "aspect_grid_only.xml", "r", encoding="utf-8", errors="ignore") as f:
945
- template = f.read()
946
-
947
- template_dict = self._create_template_dictionary()
948
-
949
- if self.chart_type in ["Transit", "Synastry"]:
950
- aspects_grid = draw_transit_aspect_grid(self.chart_colors_settings['paper_0'], self.available_planets_setting, self.aspects_list)
951
- else:
952
- aspects_grid = draw_aspect_grid(self.chart_colors_settings['paper_0'], self.available_planets_setting, self.aspects_list, x_start=50, y_start=250)
953
-
954
- template = Template(template).substitute({**template_dict, "makeAspectGrid": aspects_grid})
955
-
956
- if remove_css_variables:
957
- template = inline_css_variables_in_svg(template)
958
-
959
- if minify:
960
- template = scourString(template).replace('"', "'").replace("\n", "").replace("\t","").replace(" ", "").replace(" ", "")
961
-
962
- else:
963
- template = template.replace('"', "'")
964
-
965
- return template
966
-
967
- def makeAspectGridOnlySVG(self, minify: bool = False, remove_css_variables = False):
968
- """
969
- Generate and save aspect-grid-only chart SVG to disk.
970
-
971
- Calls makeAspectGridOnlyTemplate and writes a file named
972
- "{subject.name} - {chart_type} Chart - Aspect Grid Only.svg" in the output directory.
973
-
974
- Args:
975
- minify (bool): Pass-through to makeAspectGridOnlyTemplate for compact output.
976
- remove_css_variables (bool): Pass-through to makeAspectGridOnlyTemplate to embed CSS variables.
977
-
978
- Returns:
979
- None
980
- """
981
-
982
- template = self.makeAspectGridOnlyTemplate(minify, remove_css_variables)
983
- chartname = self.output_directory / f"{self.user.name} - {self.chart_type} Chart - Aspect Grid Only.svg"
984
-
985
- with open(chartname, "w", encoding="utf-8", errors="ignore") as output_file:
986
- output_file.write(template)
987
-
988
- print(f"SVG Generated Correctly in: {chartname}")
989
-
990
- if __name__ == "__main__":
991
- from kerykeion.utilities import setup_logging
992
- from kerykeion.composite_subject_factory import CompositeSubjectFactory
993
- setup_logging(level="debug")
994
-
995
- first = AstrologicalSubject("John Lennon", 1940, 10, 9, 18, 30, "Liverpool", "GB")
996
- second = AstrologicalSubject("Paul McCartney", 1942, 6, 18, 15, 30, "Liverpool", "GB")
997
-
998
- # Internal Natal Chart
999
- internal_natal_chart = KerykeionChartSVG(first)
1000
- internal_natal_chart.makeSVG()
1001
-
1002
- # External Natal Chart
1003
- external_natal_chart = KerykeionChartSVG(first, "ExternalNatal", second)
1004
- external_natal_chart.makeSVG()
1005
-
1006
- # Synastry Chart
1007
- synastry_chart = KerykeionChartSVG(first, "Synastry", second)
1008
- synastry_chart.makeSVG()
1009
-
1010
- # Transits Chart
1011
- transits_chart = KerykeionChartSVG(first, "Transit", second)
1012
- transits_chart.makeSVG()
1013
-
1014
- # Sidereal Birth Chart (Lahiri)
1015
- sidereal_subject = AstrologicalSubject("John Lennon Lahiri", 1940, 10, 9, 18, 30, "Liverpool", "GB", zodiac_type="Sidereal", sidereal_mode="LAHIRI")
1016
- sidereal_chart = KerykeionChartSVG(sidereal_subject)
1017
- sidereal_chart.makeSVG()
1018
-
1019
- # Sidereal Birth Chart (Fagan-Bradley)
1020
- sidereal_subject = AstrologicalSubject("John Lennon Fagan-Bradley", 1940, 10, 9, 18, 30, "Liverpool", "GB", zodiac_type="Sidereal", sidereal_mode="FAGAN_BRADLEY")
1021
- sidereal_chart = KerykeionChartSVG(sidereal_subject)
1022
- sidereal_chart.makeSVG()
1023
-
1024
- # Sidereal Birth Chart (DeLuce)
1025
- sidereal_subject = AstrologicalSubject("John Lennon DeLuce", 1940, 10, 9, 18, 30, "Liverpool", "GB", zodiac_type="Sidereal", sidereal_mode="DELUCE")
1026
- sidereal_chart = KerykeionChartSVG(sidereal_subject)
1027
- sidereal_chart.makeSVG()
1028
-
1029
- # Sidereal Birth Chart (J2000)
1030
- sidereal_subject = AstrologicalSubject("John Lennon J2000", 1940, 10, 9, 18, 30, "Liverpool", "GB", zodiac_type="Sidereal", sidereal_mode="J2000")
1031
- sidereal_chart = KerykeionChartSVG(sidereal_subject)
1032
- sidereal_chart.makeSVG()
1033
-
1034
- # House System Morinus
1035
- morinus_house_subject = AstrologicalSubject("John Lennon - House System Morinus", 1940, 10, 9, 18, 30, "Liverpool", "GB", houses_system_identifier="M")
1036
- morinus_house_chart = KerykeionChartSVG(morinus_house_subject)
1037
- morinus_house_chart.makeSVG()
1038
-
1039
- ## To check all the available house systems uncomment the following code:
1040
- # from kerykeion.kr_types import HousesSystemIdentifier
1041
- # from typing import get_args
1042
- # for i in get_args(HousesSystemIdentifier):
1043
- # alternatives_house_subject = AstrologicalSubject(f"John Lennon - House System {i}", 1940, 10, 9, 18, 30, "Liverpool", "GB", houses_system=i)
1044
- # alternatives_house_chart = KerykeionChartSVG(alternatives_house_subject)
1045
- # alternatives_house_chart.makeSVG()
1046
-
1047
- # With True Geocentric Perspective
1048
- true_geocentric_subject = AstrologicalSubject("John Lennon - True Geocentric", 1940, 10, 9, 18, 30, "Liverpool", "GB", perspective_type="True Geocentric")
1049
- true_geocentric_chart = KerykeionChartSVG(true_geocentric_subject)
1050
- true_geocentric_chart.makeSVG()
1051
-
1052
- # With Heliocentric Perspective
1053
- heliocentric_subject = AstrologicalSubject("John Lennon - Heliocentric", 1940, 10, 9, 18, 30, "Liverpool", "GB", perspective_type="Heliocentric")
1054
- heliocentric_chart = KerykeionChartSVG(heliocentric_subject)
1055
- heliocentric_chart.makeSVG()
1056
-
1057
- # With Topocentric Perspective
1058
- topocentric_subject = AstrologicalSubject("John Lennon - Topocentric", 1940, 10, 9, 18, 30, "Liverpool", "GB", perspective_type="Topocentric")
1059
- topocentric_chart = KerykeionChartSVG(topocentric_subject)
1060
- topocentric_chart.makeSVG()
1061
-
1062
- # Minified SVG
1063
- minified_subject = AstrologicalSubject("John Lennon - Minified", 1940, 10, 9, 18, 30, "Liverpool", "GB")
1064
- minified_chart = KerykeionChartSVG(minified_subject)
1065
- minified_chart.makeSVG(minify=True)
1066
-
1067
- # Dark Theme Natal Chart
1068
- dark_theme_subject = AstrologicalSubject("John Lennon - Dark Theme", 1940, 10, 9, 18, 30, "Liverpool", "GB")
1069
- dark_theme_natal_chart = KerykeionChartSVG(dark_theme_subject, theme="dark")
1070
- dark_theme_natal_chart.makeSVG()
1071
-
1072
- # Dark High Contrast Theme Natal Chart
1073
- dark_high_contrast_theme_subject = AstrologicalSubject("John Lennon - Dark High Contrast Theme", 1940, 10, 9, 18, 30, "Liverpool", "GB")
1074
- dark_high_contrast_theme_natal_chart = KerykeionChartSVG(dark_high_contrast_theme_subject, theme="dark-high-contrast")
1075
- dark_high_contrast_theme_natal_chart.makeSVG()
1076
-
1077
- # Light Theme Natal Chart
1078
- light_theme_subject = AstrologicalSubject("John Lennon - Light Theme", 1940, 10, 9, 18, 30, "Liverpool", "GB")
1079
- light_theme_natal_chart = KerykeionChartSVG(light_theme_subject, theme="light")
1080
- light_theme_natal_chart.makeSVG()
1081
-
1082
- # Dark Theme External Natal Chart
1083
- dark_theme_external_subject = AstrologicalSubject("John Lennon - Dark Theme External", 1940, 10, 9, 18, 30, "Liverpool", "GB")
1084
- dark_theme_external_chart = KerykeionChartSVG(dark_theme_external_subject, "ExternalNatal", second, theme="dark")
1085
- dark_theme_external_chart.makeSVG()
1086
-
1087
- # Dark Theme Synastry Chart
1088
- dark_theme_synastry_subject = AstrologicalSubject("John Lennon - DTS", 1940, 10, 9, 18, 30, "Liverpool", "GB")
1089
- dark_theme_synastry_chart = KerykeionChartSVG(dark_theme_synastry_subject, "Synastry", second, theme="dark")
1090
- dark_theme_synastry_chart.makeSVG()
1091
-
1092
- # Wheel Natal Only Chart
1093
- wheel_only_subject = AstrologicalSubject("John Lennon - Wheel Only", 1940, 10, 9, 18, 30, "Liverpool", "GB")
1094
- wheel_only_chart = KerykeionChartSVG(wheel_only_subject)
1095
- wheel_only_chart.makeWheelOnlySVG()
1096
-
1097
- # Wheel External Natal Only Chart
1098
- wheel_external_subject = AstrologicalSubject("John Lennon - Wheel External Only", 1940, 10, 9, 18, 30, "Liverpool", "GB")
1099
- wheel_external_chart = KerykeionChartSVG(wheel_external_subject, "ExternalNatal", second)
1100
- wheel_external_chart.makeWheelOnlySVG()
1101
-
1102
- # Wheel Synastry Only Chart
1103
- wheel_synastry_subject = AstrologicalSubject("John Lennon - Wheel Synastry Only", 1940, 10, 9, 18, 30, "Liverpool", "GB")
1104
- wheel_synastry_chart = KerykeionChartSVG(wheel_synastry_subject, "Synastry", second)
1105
- wheel_synastry_chart.makeWheelOnlySVG()
1106
-
1107
- # Wheel Transit Only Chart
1108
- wheel_transit_subject = AstrologicalSubject("John Lennon - Wheel Transit Only", 1940, 10, 9, 18, 30, "Liverpool", "GB")
1109
- wheel_transit_chart = KerykeionChartSVG(wheel_transit_subject, "Transit", second)
1110
- wheel_transit_chart.makeWheelOnlySVG()
1111
-
1112
- # Wheel Sidereal Birth Chart (Lahiri) Dark Theme
1113
- sidereal_dark_subject = AstrologicalSubject("John Lennon Lahiri - Dark Theme", 1940, 10, 9, 18, 30, "Liverpool", "GB", zodiac_type="Sidereal", sidereal_mode="LAHIRI")
1114
- sidereal_dark_chart = KerykeionChartSVG(sidereal_dark_subject, theme="dark")
1115
- sidereal_dark_chart.makeWheelOnlySVG()
1116
-
1117
- # Wheel Sidereal Birth Chart (Fagan-Bradley) Light Theme
1118
- sidereal_light_subject = AstrologicalSubject("John Lennon Fagan-Bradley - Light Theme", 1940, 10, 9, 18, 30, "Liverpool", "GB", zodiac_type="Sidereal", sidereal_mode="FAGAN_BRADLEY")
1119
- sidereal_light_chart = KerykeionChartSVG(sidereal_light_subject, theme="light")
1120
- sidereal_light_chart.makeWheelOnlySVG()
1121
-
1122
- # Aspect Grid Only Natal Chart
1123
- aspect_grid_only_subject = AstrologicalSubject("John Lennon - Aspect Grid Only", 1940, 10, 9, 18, 30, "Liverpool", "GB")
1124
- aspect_grid_only_chart = KerykeionChartSVG(aspect_grid_only_subject)
1125
- aspect_grid_only_chart.makeAspectGridOnlySVG()
1126
-
1127
- # Aspect Grid Only Dark Theme Natal Chart
1128
- aspect_grid_dark_subject = AstrologicalSubject("John Lennon - Aspect Grid Dark Theme", 1940, 10, 9, 18, 30, "Liverpool", "GB")
1129
- aspect_grid_dark_chart = KerykeionChartSVG(aspect_grid_dark_subject, theme="dark")
1130
- aspect_grid_dark_chart.makeAspectGridOnlySVG()
1131
-
1132
- # Aspect Grid Only Light Theme Natal Chart
1133
- aspect_grid_light_subject = AstrologicalSubject("John Lennon - Aspect Grid Light Theme", 1940, 10, 9, 18, 30, "Liverpool", "GB")
1134
- aspect_grid_light_chart = KerykeionChartSVG(aspect_grid_light_subject, theme="light")
1135
- aspect_grid_light_chart.makeAspectGridOnlySVG()
1136
-
1137
- # Synastry Chart Aspect Grid Only
1138
- aspect_grid_synastry_subject = AstrologicalSubject("John Lennon - Aspect Grid Synastry", 1940, 10, 9, 18, 30, "Liverpool", "GB")
1139
- aspect_grid_synastry_chart = KerykeionChartSVG(aspect_grid_synastry_subject, "Synastry", second)
1140
- aspect_grid_synastry_chart.makeAspectGridOnlySVG()
1141
-
1142
- # Transit Chart Aspect Grid Only
1143
- aspect_grid_transit_subject = AstrologicalSubject("John Lennon - Aspect Grid Transit", 1940, 10, 9, 18, 30, "Liverpool", "GB")
1144
- aspect_grid_transit_chart = KerykeionChartSVG(aspect_grid_transit_subject, "Transit", second)
1145
- aspect_grid_transit_chart.makeAspectGridOnlySVG()
1146
-
1147
- # Synastry Chart Aspect Grid Only Dark Theme
1148
- aspect_grid_dark_synastry_subject = AstrologicalSubject("John Lennon - Aspect Grid Dark Synastry", 1940, 10, 9, 18, 30, "Liverpool", "GB")
1149
- aspect_grid_dark_synastry_chart = KerykeionChartSVG(aspect_grid_dark_synastry_subject, "Synastry", second, theme="dark")
1150
- aspect_grid_dark_synastry_chart.makeAspectGridOnlySVG()
1151
-
1152
- # Synastry Chart With draw_transit_aspect_list table
1153
- synastry_chart_with_table_list_subject = AstrologicalSubject("John Lennon - SCTWL", 1940, 10, 9, 18, 30, "Liverpool", "GB")
1154
- synastry_chart_with_table_list = KerykeionChartSVG(synastry_chart_with_table_list_subject, "Synastry", second, double_chart_aspect_grid_type="list", theme="dark")
1155
- synastry_chart_with_table_list.makeSVG()
1156
-
1157
- # Transit Chart With draw_transit_aspect_grid table
1158
- transit_chart_with_table_grid_subject = AstrologicalSubject("John Lennon - TCWTG", 1940, 10, 9, 18, 30, "Liverpool", "GB")
1159
- transit_chart_with_table_grid = KerykeionChartSVG(transit_chart_with_table_grid_subject, "Transit", second, double_chart_aspect_grid_type="table", theme="dark")
1160
- transit_chart_with_table_grid.makeSVG()
1161
-
1162
- # Chines Language Chart
1163
- chinese_subject = AstrologicalSubject("Hua Chenyu", 1990, 2, 7, 12, 0, "Hunan", "CN")
1164
- chinese_chart = KerykeionChartSVG(chinese_subject, chart_language="CN")
1165
- chinese_chart.makeSVG()
1166
-
1167
- # French Language Chart
1168
- french_subject = AstrologicalSubject("Jeanne Moreau", 1928, 1, 23, 10, 0, "Paris", "FR")
1169
- french_chart = KerykeionChartSVG(french_subject, chart_language="FR")
1170
- french_chart.makeSVG()
1171
-
1172
- # Spanish Language Chart
1173
- spanish_subject = AstrologicalSubject("Antonio Banderas", 1960, 8, 10, 12, 0, "Malaga", "ES")
1174
- spanish_chart = KerykeionChartSVG(spanish_subject, chart_language="ES")
1175
- spanish_chart.makeSVG()
1176
-
1177
- # Portuguese Language Chart
1178
- portuguese_subject = AstrologicalSubject("Cristiano Ronaldo", 1985, 2, 5, 5, 25, "Funchal", "PT")
1179
- portuguese_chart = KerykeionChartSVG(portuguese_subject, chart_language="PT")
1180
- portuguese_chart.makeSVG()
1181
-
1182
- # Italian Language Chart
1183
- italian_subject = AstrologicalSubject("Sophia Loren", 1934, 9, 20, 2, 0, "Rome", "IT")
1184
- italian_chart = KerykeionChartSVG(italian_subject, chart_language="IT")
1185
- italian_chart.makeSVG()
1186
-
1187
- # Russian Language Chart
1188
- russian_subject = AstrologicalSubject("Mikhail Bulgakov", 1891, 5, 15, 12, 0, "Kiev", "UA")
1189
- russian_chart = KerykeionChartSVG(russian_subject, chart_language="RU")
1190
- russian_chart.makeSVG()
1191
-
1192
- # Turkish Language Chart
1193
- turkish_subject = AstrologicalSubject("Mehmet Oz", 1960, 6, 11, 12, 0, "Istanbul", "TR")
1194
- turkish_chart = KerykeionChartSVG(turkish_subject, chart_language="TR")
1195
- turkish_chart.makeSVG()
1196
-
1197
- # German Language Chart
1198
- german_subject = AstrologicalSubject("Albert Einstein", 1879, 3, 14, 11, 30, "Ulm", "DE")
1199
- german_chart = KerykeionChartSVG(german_subject, chart_language="DE")
1200
- german_chart.makeSVG()
1201
-
1202
- # Hindi Language Chart
1203
- hindi_subject = AstrologicalSubject("Amitabh Bachchan", 1942, 10, 11, 4, 0, "Allahabad", "IN")
1204
- hindi_chart = KerykeionChartSVG(hindi_subject, chart_language="HI")
1205
- hindi_chart.makeSVG()
1206
-
1207
- # Kanye West Natal Chart
1208
- kanye_west_subject = AstrologicalSubject("Kanye", 1977, 6, 8, 8, 45, "Atlanta", "US")
1209
- kanye_west_chart = KerykeionChartSVG(kanye_west_subject)
1210
- kanye_west_chart.makeSVG()
1211
-
1212
- # Composite Chart
1213
- angelina = AstrologicalSubject("Angelina Jolie", 1975, 6, 4, 9, 9, "Los Angeles", "US", lng=-118.15, lat=34.03, tz_str="America/Los_Angeles")
1214
- brad = AstrologicalSubject("Brad Pitt", 1963, 12, 18, 6, 31, "Shawnee", "US", lng=-96.56, lat=35.20, tz_str="America/Chicago")
1215
-
1216
- composite_subject_factory = CompositeSubjectFactory(angelina, brad)
1217
- composite_subject_model = composite_subject_factory.get_midpoint_composite_subject_model()
1218
- composite_chart = KerykeionChartSVG(composite_subject_model, "Composite")
1219
- composite_chart.makeSVG()