kerykeion 5.0.0b2__py3-none-any.whl → 5.0.0b5__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.
- kerykeion/__init__.py +3 -2
- kerykeion/aspects/aspects_factory.py +60 -21
- kerykeion/aspects/aspects_utils.py +1 -1
- kerykeion/backword.py +111 -18
- kerykeion/chart_data_factory.py +72 -7
- kerykeion/charts/chart_drawer.py +524 -203
- kerykeion/charts/charts_utils.py +416 -253
- kerykeion/charts/templates/aspect_grid_only.xml +269 -312
- kerykeion/charts/templates/chart.xml +248 -304
- kerykeion/charts/templates/wheel_only.xml +271 -312
- kerykeion/charts/themes/black-and-white.css +148 -0
- kerykeion/kr_types/__init__.py +70 -0
- kerykeion/kr_types/chart_template_model.py +20 -0
- kerykeion/kr_types/kerykeion_exception.py +20 -0
- kerykeion/kr_types/kr_literals.py +20 -0
- kerykeion/kr_types/kr_models.py +20 -0
- kerykeion/kr_types/settings_models.py +20 -0
- kerykeion/relationship_score_factory.py +12 -2
- kerykeion/schemas/__init__.py +7 -0
- kerykeion/schemas/kr_literals.py +12 -1
- kerykeion/settings/__init__.py +16 -2
- kerykeion/settings/chart_defaults.py +444 -0
- kerykeion/settings/config_constants.py +0 -5
- kerykeion/settings/kerykeion_settings.py +31 -74
- kerykeion/settings/translation_strings.py +1479 -0
- kerykeion/settings/translations.py +74 -0
- kerykeion/transits_time_range_factory.py +10 -1
- {kerykeion-5.0.0b2.dist-info → kerykeion-5.0.0b5.dist-info}/METADATA +333 -207
- {kerykeion-5.0.0b2.dist-info → kerykeion-5.0.0b5.dist-info}/RECORD +31 -26
- kerykeion/settings/kr.config.json +0 -1474
- kerykeion/settings/legacy/__init__.py +0 -0
- kerykeion/settings/legacy/legacy_celestial_points_settings.py +0 -299
- kerykeion/settings/legacy/legacy_chart_aspects_settings.py +0 -71
- kerykeion/settings/legacy/legacy_color_settings.py +0 -42
- {kerykeion-5.0.0b2.dist-info → kerykeion-5.0.0b5.dist-info}/WHEEL +0 -0
- {kerykeion-5.0.0b2.dist-info → kerykeion-5.0.0b5.dist-info}/licenses/LICENSE +0 -0
kerykeion/charts/chart_drawer.py
CHANGED
|
@@ -5,19 +5,23 @@
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
import logging
|
|
8
|
-
|
|
9
|
-
from
|
|
8
|
+
from copy import deepcopy
|
|
9
|
+
from math import ceil
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from string import Template
|
|
13
|
+
from typing import Any, Mapping, Optional, Union, get_args
|
|
10
14
|
|
|
15
|
+
import swisseph as swe
|
|
16
|
+
from scour.scour import scourString
|
|
11
17
|
|
|
12
|
-
from kerykeion.schemas.kr_models import ChartDataModel
|
|
13
|
-
from kerykeion.settings.config_constants import DEFAULT_ACTIVE_POINTS
|
|
14
|
-
from kerykeion.settings.kerykeion_settings import get_settings
|
|
15
18
|
from kerykeion.house_comparison.house_comparison_factory import HouseComparisonFactory
|
|
16
19
|
from kerykeion.schemas import (
|
|
17
20
|
KerykeionException,
|
|
18
21
|
ChartType,
|
|
19
22
|
Sign,
|
|
20
23
|
ActiveAspect,
|
|
24
|
+
KerykeionPointModel,
|
|
21
25
|
)
|
|
22
26
|
from kerykeion.schemas import ChartTemplateModel
|
|
23
27
|
from kerykeion.schemas.kr_models import (
|
|
@@ -27,13 +31,17 @@ from kerykeion.schemas.kr_models import (
|
|
|
27
31
|
)
|
|
28
32
|
from kerykeion.schemas.settings_models import (
|
|
29
33
|
KerykeionSettingsCelestialPointModel,
|
|
30
|
-
|
|
34
|
+
KerykeionLanguageModel,
|
|
31
35
|
)
|
|
32
36
|
from kerykeion.schemas.kr_literals import (
|
|
33
37
|
KerykeionChartTheme,
|
|
34
38
|
KerykeionChartLanguage,
|
|
35
39
|
AstrologicalPoint,
|
|
36
40
|
)
|
|
41
|
+
from kerykeion.schemas.kr_models import ChartDataModel
|
|
42
|
+
from kerykeion.settings import LANGUAGE_SETTINGS
|
|
43
|
+
from kerykeion.settings.config_constants import DEFAULT_ACTIVE_POINTS
|
|
44
|
+
from kerykeion.settings.translations import get_translations, load_language_settings
|
|
37
45
|
from kerykeion.charts.charts_utils import (
|
|
38
46
|
draw_zodiac_slice,
|
|
39
47
|
convert_latitude_coordinate_to_string,
|
|
@@ -62,14 +70,12 @@ from kerykeion.charts.charts_utils import (
|
|
|
62
70
|
)
|
|
63
71
|
from kerykeion.charts.draw_planets import draw_planets
|
|
64
72
|
from kerykeion.utilities import get_houses_list, inline_css_variables_in_svg, distribute_percentages_to_100
|
|
65
|
-
from kerykeion.settings.
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
from string import Template
|
|
73
|
+
from kerykeion.settings.chart_defaults import (
|
|
74
|
+
DEFAULT_CHART_COLORS,
|
|
75
|
+
DEFAULT_CELESTIAL_POINTS_SETTINGS,
|
|
76
|
+
DEFAULT_CHART_ASPECTS_SETTINGS,
|
|
77
|
+
)
|
|
71
78
|
from typing import List, Literal
|
|
72
|
-
from datetime import datetime
|
|
73
79
|
|
|
74
80
|
|
|
75
81
|
class ChartDrawer:
|
|
@@ -98,14 +104,16 @@ class ChartDrawer:
|
|
|
98
104
|
Pre-computed chart data from ChartDataFactory containing all subjects, aspects,
|
|
99
105
|
element/quality distributions, and other analytical data. This is the ONLY source
|
|
100
106
|
of chart information - no calculations are performed by ChartDrawer.
|
|
101
|
-
new_settings_file (Path | dict | KerykeionSettingsModel, optional):
|
|
102
|
-
Path or settings object to override default chart configuration (colors, fonts, aspects).
|
|
103
107
|
theme (KerykeionChartTheme, optional):
|
|
104
108
|
CSS theme for the chart. If None, no default styles are applied. Defaults to 'classic'.
|
|
105
109
|
double_chart_aspect_grid_type (Literal['list', 'table'], optional):
|
|
106
110
|
Specifies rendering style for double-chart aspect grids. Defaults to 'list'.
|
|
107
111
|
chart_language (KerykeionChartLanguage, optional):
|
|
108
112
|
Language code for chart labels. Defaults to 'EN'.
|
|
113
|
+
language_pack (dict | None, optional):
|
|
114
|
+
Additional translations merged over the bundled defaults for the
|
|
115
|
+
selected language. Useful to introduce new languages or override
|
|
116
|
+
existing labels.
|
|
109
117
|
transparent_background (bool, optional):
|
|
110
118
|
Whether to use a transparent background instead of the theme color. Defaults to False.
|
|
111
119
|
|
|
@@ -157,10 +165,14 @@ class ChartDrawer:
|
|
|
157
165
|
|
|
158
166
|
_DEFAULT_HEIGHT = 550
|
|
159
167
|
_DEFAULT_FULL_WIDTH = 1250
|
|
168
|
+
_DEFAULT_SYNASTRY_WIDTH = 1570
|
|
160
169
|
_DEFAULT_NATAL_WIDTH = 870
|
|
161
170
|
_DEFAULT_FULL_WIDTH_WITH_TABLE = 1250
|
|
162
171
|
_DEFAULT_ULTRA_WIDE_WIDTH = 1320
|
|
163
172
|
|
|
173
|
+
_ASPECT_LIST_ASPECTS_PER_COLUMN = 14
|
|
174
|
+
_ASPECT_LIST_COLUMN_WIDTH = 105
|
|
175
|
+
|
|
164
176
|
_BASE_VERTICAL_OFFSETS = {
|
|
165
177
|
"wheel": 50,
|
|
166
178
|
"grid": 0,
|
|
@@ -185,7 +197,6 @@ class ChartDrawer:
|
|
|
185
197
|
first_obj: Union[AstrologicalSubjectModel, CompositeSubjectModel, PlanetReturnModel]
|
|
186
198
|
second_obj: Union[AstrologicalSubjectModel, PlanetReturnModel, None]
|
|
187
199
|
chart_type: ChartType
|
|
188
|
-
new_settings_file: Union[Path, None, KerykeionSettingsModel, dict]
|
|
189
200
|
theme: Union[KerykeionChartTheme, None]
|
|
190
201
|
double_chart_aspect_grid_type: Literal["list", "table"]
|
|
191
202
|
chart_language: KerykeionChartLanguage
|
|
@@ -194,6 +205,8 @@ class ChartDrawer:
|
|
|
194
205
|
transparent_background: bool
|
|
195
206
|
external_view: bool
|
|
196
207
|
custom_title: Union[str, None]
|
|
208
|
+
_language_model: KerykeionLanguageModel
|
|
209
|
+
_fallback_language_model: KerykeionLanguageModel
|
|
197
210
|
|
|
198
211
|
# Internal properties
|
|
199
212
|
fire: float
|
|
@@ -219,10 +232,10 @@ class ChartDrawer:
|
|
|
219
232
|
self,
|
|
220
233
|
chart_data: "ChartDataModel",
|
|
221
234
|
*,
|
|
222
|
-
new_settings_file: Union[Path, None, KerykeionSettingsModel, dict] = None,
|
|
223
235
|
theme: Union[KerykeionChartTheme, None] = "classic",
|
|
224
236
|
double_chart_aspect_grid_type: Literal["list", "table"] = "list",
|
|
225
237
|
chart_language: KerykeionChartLanguage = "EN",
|
|
238
|
+
language_pack: Optional[Mapping[str, Any]] = None,
|
|
226
239
|
external_view: bool = False,
|
|
227
240
|
transparent_background: bool = False,
|
|
228
241
|
colors_settings: dict = DEFAULT_CHART_COLORS,
|
|
@@ -239,14 +252,16 @@ class ChartDrawer:
|
|
|
239
252
|
chart_data (ChartDataModel):
|
|
240
253
|
Pre-computed chart data from ChartDataFactory containing all subjects,
|
|
241
254
|
aspects, element/quality distributions, and other analytical data.
|
|
242
|
-
new_settings_file (Path, dict, or KerykeionSettingsModel, optional):
|
|
243
|
-
Custom settings source for chart colors, fonts, and aspects.
|
|
244
255
|
theme (KerykeionChartTheme or None, optional):
|
|
245
256
|
CSS theme to apply; None for default styling.
|
|
246
257
|
double_chart_aspect_grid_type (Literal['list','table'], optional):
|
|
247
258
|
Layout style for double-chart aspect grids ('list' or 'table').
|
|
248
259
|
chart_language (KerykeionChartLanguage, optional):
|
|
249
260
|
Language code for chart labels (e.g., 'EN', 'IT').
|
|
261
|
+
language_pack (dict | None, optional):
|
|
262
|
+
Additional translations merged over the bundled defaults for the
|
|
263
|
+
selected language. Useful to introduce new languages or override
|
|
264
|
+
existing labels.
|
|
250
265
|
external_view (bool, optional):
|
|
251
266
|
Whether to use external visualization (planets on outer ring) for single-subject charts. Defaults to False.
|
|
252
267
|
transparent_background (bool, optional):
|
|
@@ -257,14 +272,13 @@ class ChartDrawer:
|
|
|
257
272
|
# --------------------
|
|
258
273
|
# COMMON INITIALIZATION
|
|
259
274
|
# --------------------
|
|
260
|
-
self.new_settings_file = new_settings_file
|
|
261
275
|
self.chart_language = chart_language
|
|
262
276
|
self.double_chart_aspect_grid_type = double_chart_aspect_grid_type
|
|
263
277
|
self.transparent_background = transparent_background
|
|
264
278
|
self.external_view = external_view
|
|
265
|
-
self.chart_colors_settings = colors_settings
|
|
266
|
-
self.planets_settings = celestial_points_settings
|
|
267
|
-
self.aspects_settings = aspects_settings
|
|
279
|
+
self.chart_colors_settings = deepcopy(colors_settings)
|
|
280
|
+
self.planets_settings = [dict(body) for body in celestial_points_settings]
|
|
281
|
+
self.aspects_settings = [dict(aspect) for aspect in aspects_settings]
|
|
268
282
|
self.custom_title = custom_title
|
|
269
283
|
self.auto_size = auto_size
|
|
270
284
|
self._padding = padding
|
|
@@ -287,7 +301,7 @@ class ChartDrawer:
|
|
|
287
301
|
self.second_obj = getattr(chart_data, 'second_subject')
|
|
288
302
|
|
|
289
303
|
# Load settings
|
|
290
|
-
self.
|
|
304
|
+
self._load_language_settings(language_pack)
|
|
291
305
|
|
|
292
306
|
# Default radius for all charts
|
|
293
307
|
self.main_radius = 240
|
|
@@ -308,10 +322,18 @@ class ChartDrawer:
|
|
|
308
322
|
|
|
309
323
|
# Set available celestial points
|
|
310
324
|
available_celestial_points_names = [body["name"].lower() for body in self.available_planets_setting]
|
|
311
|
-
self.available_kerykeion_celestial_points =
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
325
|
+
self.available_kerykeion_celestial_points = self._collect_subject_points(
|
|
326
|
+
self.first_obj,
|
|
327
|
+
available_celestial_points_names,
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
# Collect secondary subject points for dual charts using the same active set
|
|
331
|
+
self.t_available_kerykeion_celestial_points: list[KerykeionPointModel] = []
|
|
332
|
+
if self.second_obj is not None:
|
|
333
|
+
self.t_available_kerykeion_celestial_points = self._collect_subject_points(
|
|
334
|
+
self.second_obj,
|
|
335
|
+
available_celestial_points_names,
|
|
336
|
+
)
|
|
315
337
|
|
|
316
338
|
# ------------------------
|
|
317
339
|
# CHART TYPE SPECIFIC SETUP FROM CHART DATA
|
|
@@ -364,9 +386,6 @@ class ChartDrawer:
|
|
|
364
386
|
# Extract aspects from pre-computed chart data
|
|
365
387
|
self.aspects_list = chart_data.aspects.relevant_aspects
|
|
366
388
|
|
|
367
|
-
# Secondary subject available points
|
|
368
|
-
self.t_available_kerykeion_celestial_points = self.available_kerykeion_celestial_points
|
|
369
|
-
|
|
370
389
|
# Screen size
|
|
371
390
|
self.height = self._DEFAULT_HEIGHT
|
|
372
391
|
if self.double_chart_aspect_grid_type == "table":
|
|
@@ -388,12 +407,9 @@ class ChartDrawer:
|
|
|
388
407
|
# Extract aspects from pre-computed chart data
|
|
389
408
|
self.aspects_list = chart_data.aspects.relevant_aspects
|
|
390
409
|
|
|
391
|
-
# Secondary subject available points
|
|
392
|
-
self.t_available_kerykeion_celestial_points = self.available_kerykeion_celestial_points
|
|
393
|
-
|
|
394
410
|
# Screen size
|
|
395
411
|
self.height = self._DEFAULT_HEIGHT
|
|
396
|
-
self.width = self.
|
|
412
|
+
self.width = self._DEFAULT_SYNASTRY_WIDTH
|
|
397
413
|
|
|
398
414
|
# Get location and coordinates
|
|
399
415
|
self.location, self.geolat, self.geolon = self._get_location_info()
|
|
@@ -409,9 +425,6 @@ class ChartDrawer:
|
|
|
409
425
|
# Extract aspects from pre-computed chart data
|
|
410
426
|
self.aspects_list = chart_data.aspects.relevant_aspects
|
|
411
427
|
|
|
412
|
-
# Secondary subject available points
|
|
413
|
-
self.t_available_kerykeion_celestial_points = self.available_kerykeion_celestial_points
|
|
414
|
-
|
|
415
428
|
# Screen size
|
|
416
429
|
self.height = self._DEFAULT_HEIGHT
|
|
417
430
|
self.width = self._DEFAULT_ULTRA_WIDE_WIDTH
|
|
@@ -462,17 +475,11 @@ class ChartDrawer:
|
|
|
462
475
|
|
|
463
476
|
self.set_up_theme(theme)
|
|
464
477
|
|
|
465
|
-
# Optionally expand width dynamically to fit content
|
|
466
|
-
if self.auto_size:
|
|
467
|
-
try:
|
|
468
|
-
required_width = self._estimate_required_width_full()
|
|
469
|
-
if required_width > self.width:
|
|
470
|
-
self.width = required_width
|
|
471
|
-
except Exception as e:
|
|
472
|
-
# Keep default on any unexpected issue; do not break rendering
|
|
473
|
-
logging.debug(f"Auto-size width calculation failed: {e}")
|
|
474
|
-
|
|
475
478
|
self._apply_dynamic_height_adjustment()
|
|
479
|
+
self._adjust_height_for_extended_aspect_columns()
|
|
480
|
+
# Reconcile width with the updated layout once height adjustments are known.
|
|
481
|
+
if self.auto_size:
|
|
482
|
+
self._update_width_to_content()
|
|
476
483
|
|
|
477
484
|
def _count_active_planets(self) -> int:
|
|
478
485
|
"""Return number of active celestial points in the current chart."""
|
|
@@ -485,6 +492,15 @@ class ChartDrawer:
|
|
|
485
492
|
offsets = self._BASE_VERTICAL_OFFSETS.copy()
|
|
486
493
|
|
|
487
494
|
minimum_height = self._DEFAULT_HEIGHT
|
|
495
|
+
|
|
496
|
+
if self.chart_type == "Synastry":
|
|
497
|
+
self._apply_synastry_height_adjustment(
|
|
498
|
+
active_points_count=active_points_count,
|
|
499
|
+
offsets=offsets,
|
|
500
|
+
minimum_height=minimum_height,
|
|
501
|
+
)
|
|
502
|
+
return
|
|
503
|
+
|
|
488
504
|
if active_points_count <= 20:
|
|
489
505
|
self.height = max(self.height, minimum_height)
|
|
490
506
|
self._vertical_offsets = offsets
|
|
@@ -515,6 +531,135 @@ class ChartDrawer:
|
|
|
515
531
|
|
|
516
532
|
self._vertical_offsets = offsets
|
|
517
533
|
|
|
534
|
+
def _adjust_height_for_extended_aspect_columns(self) -> None:
|
|
535
|
+
"""Ensure tall aspect columns fit within the SVG for double-chart lists."""
|
|
536
|
+
if self.double_chart_aspect_grid_type != "list":
|
|
537
|
+
return
|
|
538
|
+
|
|
539
|
+
if self.chart_type not in ("Synastry", "Transit", "DualReturnChart"):
|
|
540
|
+
return
|
|
541
|
+
|
|
542
|
+
total_aspects = len(self.aspects_list) if hasattr(self, "aspects_list") else 0
|
|
543
|
+
if total_aspects == 0:
|
|
544
|
+
return
|
|
545
|
+
|
|
546
|
+
aspects_per_column = 14
|
|
547
|
+
extended_column_start = 11 # Zero-based column index where tall columns begin
|
|
548
|
+
base_capacity = aspects_per_column * extended_column_start
|
|
549
|
+
|
|
550
|
+
if total_aspects <= base_capacity:
|
|
551
|
+
return
|
|
552
|
+
|
|
553
|
+
translate_y = 273
|
|
554
|
+
bottom_padding = 40
|
|
555
|
+
title_clearance = 18
|
|
556
|
+
line_height = 14
|
|
557
|
+
baseline_index = aspects_per_column - 1
|
|
558
|
+
top_limit_index = ceil((-translate_y + title_clearance) / line_height)
|
|
559
|
+
max_capacity_by_top = baseline_index - top_limit_index + 1
|
|
560
|
+
|
|
561
|
+
if max_capacity_by_top <= aspects_per_column:
|
|
562
|
+
return
|
|
563
|
+
|
|
564
|
+
target_capacity = max_capacity_by_top
|
|
565
|
+
required_available_height = target_capacity * line_height
|
|
566
|
+
required_height = translate_y + bottom_padding + required_available_height
|
|
567
|
+
|
|
568
|
+
if required_height <= self.height:
|
|
569
|
+
return
|
|
570
|
+
|
|
571
|
+
delta = required_height - self.height
|
|
572
|
+
self.height = required_height
|
|
573
|
+
|
|
574
|
+
offsets = self._vertical_offsets
|
|
575
|
+
# Keep bottom-anchored groups aligned after changing the overall height.
|
|
576
|
+
offsets["wheel"] += delta
|
|
577
|
+
offsets["aspect_grid"] += delta
|
|
578
|
+
offsets["aspect_list"] += delta
|
|
579
|
+
offsets["lunar_phase"] += delta
|
|
580
|
+
offsets["bottom_left"] += delta
|
|
581
|
+
self._vertical_offsets = offsets
|
|
582
|
+
|
|
583
|
+
def _apply_synastry_height_adjustment(
|
|
584
|
+
self,
|
|
585
|
+
*,
|
|
586
|
+
active_points_count: int,
|
|
587
|
+
offsets: dict[str, int],
|
|
588
|
+
minimum_height: int,
|
|
589
|
+
) -> None:
|
|
590
|
+
"""Specialised dynamic height handling for Synastry charts.
|
|
591
|
+
|
|
592
|
+
With the planet grids locked to a single column, every additional active
|
|
593
|
+
point extends multiple tables vertically (planets, houses, comparisons).
|
|
594
|
+
We therefore scale the height using the actual line spacing used by those
|
|
595
|
+
tables (≈14px) and keep the bottom anchored elements aligned.
|
|
596
|
+
"""
|
|
597
|
+
base_rows = 14 # Up to 16 active points fit without extra height
|
|
598
|
+
extra_rows = max(active_points_count - base_rows, 0)
|
|
599
|
+
|
|
600
|
+
synastry_row_height = 15
|
|
601
|
+
comparison_padding_per_row = 4 # Keeps house comparison grids within view.
|
|
602
|
+
extra_height = extra_rows * (synastry_row_height + comparison_padding_per_row)
|
|
603
|
+
|
|
604
|
+
self.height = max(self.height, minimum_height + extra_height)
|
|
605
|
+
|
|
606
|
+
delta_height = max(self.height - minimum_height, 0)
|
|
607
|
+
|
|
608
|
+
offsets["wheel"] += delta_height
|
|
609
|
+
offsets["aspect_grid"] += delta_height
|
|
610
|
+
offsets["aspect_list"] += delta_height
|
|
611
|
+
offsets["lunar_phase"] += delta_height
|
|
612
|
+
offsets["bottom_left"] += delta_height
|
|
613
|
+
|
|
614
|
+
row_height_ratio = synastry_row_height / max(self._ROW_HEIGHT, 1)
|
|
615
|
+
synastry_top_shift_factor = max(
|
|
616
|
+
self._TOP_SHIFT_FACTOR,
|
|
617
|
+
int(ceil(self._TOP_SHIFT_FACTOR * row_height_ratio)),
|
|
618
|
+
)
|
|
619
|
+
shift = min(extra_rows * synastry_top_shift_factor, self._MAX_TOP_SHIFT)
|
|
620
|
+
|
|
621
|
+
base_grid_padding = 36
|
|
622
|
+
grid_padding_per_row = 6
|
|
623
|
+
base_header_padding = 12
|
|
624
|
+
header_padding_per_row = 4
|
|
625
|
+
min_title_to_grid_gap = 36
|
|
626
|
+
|
|
627
|
+
grid_shift = shift + base_grid_padding + (extra_rows * grid_padding_per_row)
|
|
628
|
+
grid_shift = min(grid_shift, shift + self._MAX_TOP_SHIFT)
|
|
629
|
+
|
|
630
|
+
top_shift = (shift // 2) + base_header_padding + (extra_rows * header_padding_per_row)
|
|
631
|
+
|
|
632
|
+
max_allowed_shift = shift + self._MAX_TOP_SHIFT
|
|
633
|
+
missing_gap = min_title_to_grid_gap - (grid_shift - top_shift)
|
|
634
|
+
grid_shift = min(grid_shift + missing_gap, max_allowed_shift)
|
|
635
|
+
if grid_shift - top_shift < min_title_to_grid_gap:
|
|
636
|
+
top_shift = max(0, grid_shift - min_title_to_grid_gap)
|
|
637
|
+
|
|
638
|
+
offsets["grid"] += grid_shift
|
|
639
|
+
offsets["title"] += top_shift
|
|
640
|
+
offsets["elements"] += top_shift
|
|
641
|
+
offsets["qualities"] += top_shift
|
|
642
|
+
|
|
643
|
+
self._vertical_offsets = offsets
|
|
644
|
+
|
|
645
|
+
def _collect_subject_points(
|
|
646
|
+
self,
|
|
647
|
+
subject: Union[AstrologicalSubjectModel, CompositeSubjectModel, PlanetReturnModel],
|
|
648
|
+
point_attribute_names: list[str],
|
|
649
|
+
) -> list[KerykeionPointModel]:
|
|
650
|
+
"""Collect ordered active celestial points for a subject."""
|
|
651
|
+
|
|
652
|
+
collected: list[KerykeionPointModel] = []
|
|
653
|
+
|
|
654
|
+
for raw_name in point_attribute_names:
|
|
655
|
+
attr_name = raw_name if hasattr(subject, raw_name) else raw_name.lower()
|
|
656
|
+
point = getattr(subject, attr_name, None)
|
|
657
|
+
if point is None:
|
|
658
|
+
continue
|
|
659
|
+
collected.append(point)
|
|
660
|
+
|
|
661
|
+
return collected
|
|
662
|
+
|
|
518
663
|
def _dynamic_viewbox(self) -> str:
|
|
519
664
|
"""Return the viewBox string based on current width/height."""
|
|
520
665
|
return f"0 0 {int(self.width)} {int(self.height)}"
|
|
@@ -604,14 +749,10 @@ class ChartDrawer:
|
|
|
604
749
|
if self.chart_type in ("Transit", "Synastry", "DualReturnChart"):
|
|
605
750
|
# Double-chart aspects placement
|
|
606
751
|
if self.double_chart_aspect_grid_type == "list":
|
|
607
|
-
# Columnar list placed at translate(565,273), ~100-110px per column, 14 aspects per column
|
|
608
|
-
aspects_per_column = 14
|
|
609
752
|
total_aspects = len(self.aspects_list) if hasattr(self, "aspects_list") else 0
|
|
610
|
-
columns =
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
columns = min(columns, max_cols_cap)
|
|
614
|
-
aspect_list_right = 565 + (columns * 110)
|
|
753
|
+
columns = self._calculate_double_chart_aspect_columns(total_aspects, self.height)
|
|
754
|
+
columns = max(columns, 1)
|
|
755
|
+
aspect_list_right = 565 + (columns * self._ASPECT_LIST_COLUMN_WIDTH)
|
|
615
756
|
extents.append(aspect_list_right)
|
|
616
757
|
else:
|
|
617
758
|
# Grid table placed with x_indent ~550, width ~ 14px per cell across n_active+1
|
|
@@ -626,6 +767,9 @@ class ChartDrawer:
|
|
|
626
767
|
# Secondary houses grid default x ~ 1015
|
|
627
768
|
secondary_houses_grid_right = 1015 + 120
|
|
628
769
|
extents.append(secondary_houses_grid_right)
|
|
770
|
+
first_house_comparison_grid_right = 1090 + 180
|
|
771
|
+
second_house_comparison_grid_right = 1290 + 180
|
|
772
|
+
extents.extend([first_house_comparison_grid_right, second_house_comparison_grid_right])
|
|
629
773
|
|
|
630
774
|
if self.chart_type == "Transit":
|
|
631
775
|
# House comparison grid at x ~ 1030
|
|
@@ -640,6 +784,84 @@ class ChartDrawer:
|
|
|
640
784
|
# Conservative safety padding
|
|
641
785
|
return int(max(extents) + self._padding)
|
|
642
786
|
|
|
787
|
+
def _calculate_double_chart_aspect_columns(
|
|
788
|
+
self,
|
|
789
|
+
total_aspects: int,
|
|
790
|
+
chart_height: Optional[int],
|
|
791
|
+
) -> int:
|
|
792
|
+
"""Return how many columns the double-chart aspect list needs.
|
|
793
|
+
|
|
794
|
+
The first 11 columns follow the legacy 14-rows layout. Starting from the
|
|
795
|
+
12th column we can fit more rows thanks to the taller chart height that
|
|
796
|
+
gets computed earlier, so we re-use the same capacity as the SVG builder.
|
|
797
|
+
"""
|
|
798
|
+
if total_aspects <= 0:
|
|
799
|
+
return 0
|
|
800
|
+
|
|
801
|
+
per_column = self._ASPECT_LIST_ASPECTS_PER_COLUMN
|
|
802
|
+
extended_start = 10 # 0-based index where tall columns begin
|
|
803
|
+
base_capacity = per_column * extended_start
|
|
804
|
+
|
|
805
|
+
full_height_capacity = self._calculate_full_height_column_capacity(chart_height)
|
|
806
|
+
|
|
807
|
+
if total_aspects <= base_capacity:
|
|
808
|
+
return ceil(total_aspects / per_column)
|
|
809
|
+
|
|
810
|
+
remaining = max(total_aspects - base_capacity, 0)
|
|
811
|
+
extra_columns = ceil(remaining / full_height_capacity) if remaining > 0 else 0
|
|
812
|
+
return extended_start + extra_columns
|
|
813
|
+
|
|
814
|
+
def _calculate_full_height_column_capacity(
|
|
815
|
+
self,
|
|
816
|
+
chart_height: Optional[int],
|
|
817
|
+
) -> int:
|
|
818
|
+
"""Compute the row capacity for columns that use the tall layout."""
|
|
819
|
+
per_column = self._ASPECT_LIST_ASPECTS_PER_COLUMN
|
|
820
|
+
|
|
821
|
+
if chart_height is None:
|
|
822
|
+
return per_column
|
|
823
|
+
|
|
824
|
+
translate_y = 273
|
|
825
|
+
bottom_padding = 40
|
|
826
|
+
title_clearance = 18
|
|
827
|
+
line_height = 14
|
|
828
|
+
baseline_index = per_column - 1
|
|
829
|
+
top_limit_index = ceil((-translate_y + title_clearance) / line_height)
|
|
830
|
+
max_capacity_by_top = baseline_index - top_limit_index + 1
|
|
831
|
+
|
|
832
|
+
available_height = max(chart_height - translate_y - bottom_padding, line_height)
|
|
833
|
+
allowed_capacity = max(per_column, int(available_height // line_height))
|
|
834
|
+
|
|
835
|
+
# Respect both the physical height of the SVG and the visual limit
|
|
836
|
+
# imposed by the title area.
|
|
837
|
+
return max(per_column, min(allowed_capacity, max_capacity_by_top))
|
|
838
|
+
|
|
839
|
+
def _minimum_width_for_chart_type(self) -> int:
|
|
840
|
+
"""Baseline width to avoid compressing core groups too tightly."""
|
|
841
|
+
wheel_right = 100 + (2 * self.main_radius)
|
|
842
|
+
baseline = wheel_right + self._padding
|
|
843
|
+
|
|
844
|
+
if self.chart_type in ("Natal", "Composite", "SingleReturnChart"):
|
|
845
|
+
return max(int(baseline), self._DEFAULT_NATAL_WIDTH)
|
|
846
|
+
if self.chart_type == "Synastry":
|
|
847
|
+
return max(int(baseline), self._DEFAULT_SYNASTRY_WIDTH // 2)
|
|
848
|
+
if self.chart_type == "DualReturnChart":
|
|
849
|
+
return max(int(baseline), self._DEFAULT_ULTRA_WIDE_WIDTH // 2)
|
|
850
|
+
if self.chart_type == "Transit":
|
|
851
|
+
return max(int(baseline), self._DEFAULT_FULL_WIDTH // 2)
|
|
852
|
+
return max(int(baseline), self._DEFAULT_NATAL_WIDTH)
|
|
853
|
+
|
|
854
|
+
def _update_width_to_content(self) -> None:
|
|
855
|
+
"""Resize the chart width so the farthest element fits comfortably."""
|
|
856
|
+
try:
|
|
857
|
+
required_width = self._estimate_required_width_full()
|
|
858
|
+
except Exception as e:
|
|
859
|
+
logging.debug(f"Auto-size width calculation failed: {e}")
|
|
860
|
+
return
|
|
861
|
+
|
|
862
|
+
minimum_width = self._minimum_width_for_chart_type()
|
|
863
|
+
self.width = max(required_width, minimum_width)
|
|
864
|
+
|
|
643
865
|
def _get_location_info(self) -> tuple[str, float, float]:
|
|
644
866
|
"""
|
|
645
867
|
Determine location information based on chart type and subjects.
|
|
@@ -687,17 +909,31 @@ class ChartDrawer:
|
|
|
687
909
|
with open(theme_dir / f"{theme}.css", "r") as f:
|
|
688
910
|
self.color_style_tag = f.read()
|
|
689
911
|
|
|
690
|
-
def
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
912
|
+
def _load_language_settings(
|
|
913
|
+
self,
|
|
914
|
+
language_pack: Optional[Mapping[str, Any]],
|
|
915
|
+
) -> None:
|
|
916
|
+
"""Resolve language models for the requested chart language."""
|
|
917
|
+
overrides = {self.chart_language: dict(language_pack)} if language_pack else None
|
|
918
|
+
languages = load_language_settings(overrides)
|
|
919
|
+
|
|
920
|
+
fallback_data = languages.get("EN")
|
|
921
|
+
if fallback_data is None:
|
|
922
|
+
raise KerykeionException("English translations are missing from LANGUAGE_SETTINGS.")
|
|
923
|
+
|
|
924
|
+
base_data = languages.get(self.chart_language, fallback_data)
|
|
925
|
+
selected_model = KerykeionLanguageModel(**base_data)
|
|
926
|
+
fallback_model = KerykeionLanguageModel(**fallback_data)
|
|
927
|
+
|
|
928
|
+
self._fallback_language_model = fallback_model
|
|
929
|
+
self._language_model = selected_model
|
|
930
|
+
self._fallback_language_dict = fallback_model.model_dump()
|
|
931
|
+
self._language_dict = selected_model.model_dump()
|
|
932
|
+
self.language_settings = self._language_dict # Backward compatibility
|
|
933
|
+
|
|
934
|
+
def _translate(self, key: str, default: Any) -> Any:
|
|
935
|
+
fallback_value = get_translations(key, default, language_dict=self._fallback_language_dict)
|
|
936
|
+
return get_translations(key, fallback_value, language_dict=self._language_dict)
|
|
701
937
|
|
|
702
938
|
def _draw_zodiac_circle_slices(self, r):
|
|
703
939
|
"""
|
|
@@ -774,7 +1010,7 @@ class ChartDrawer:
|
|
|
774
1010
|
)
|
|
775
1011
|
return out
|
|
776
1012
|
|
|
777
|
-
def _truncate_name(self, name: str, max_length: int = 50) -> str:
|
|
1013
|
+
def _truncate_name(self, name: str, max_length: int = 50, ellipsis_symbol: str = "…", truncate_at_space: bool = False) -> str:
|
|
778
1014
|
"""
|
|
779
1015
|
Truncate a name if it's too long, preserving readability.
|
|
780
1016
|
|
|
@@ -785,9 +1021,13 @@ class ChartDrawer:
|
|
|
785
1021
|
Returns:
|
|
786
1022
|
str: Truncated name with ellipsis if needed
|
|
787
1023
|
"""
|
|
1024
|
+
if truncate_at_space:
|
|
1025
|
+
name = name.split(" ")[0]
|
|
1026
|
+
|
|
788
1027
|
if len(name) <= max_length:
|
|
789
1028
|
return name
|
|
790
|
-
|
|
1029
|
+
|
|
1030
|
+
return name[:max_length-1] + ellipsis_symbol
|
|
791
1031
|
|
|
792
1032
|
def _get_chart_title(self) -> str:
|
|
793
1033
|
"""
|
|
@@ -805,52 +1045,49 @@ class ChartDrawer:
|
|
|
805
1045
|
|
|
806
1046
|
# Generate default title based on chart type
|
|
807
1047
|
if self.chart_type == "Natal":
|
|
808
|
-
natal_label = self.
|
|
1048
|
+
natal_label = self._translate("birth_chart", "Natal")
|
|
809
1049
|
truncated_name = self._truncate_name(self.first_obj.name)
|
|
810
1050
|
return f'{truncated_name} - {natal_label}'
|
|
811
1051
|
|
|
812
1052
|
elif self.chart_type == "Composite":
|
|
813
|
-
composite_label = self.
|
|
814
|
-
and_word = self.
|
|
1053
|
+
composite_label = self._translate("composite_chart", "Composite")
|
|
1054
|
+
and_word = self._translate("and_word", "&")
|
|
815
1055
|
name1 = self._truncate_name(self.first_obj.first_subject.name) # type: ignore
|
|
816
1056
|
name2 = self._truncate_name(self.first_obj.second_subject.name) # type: ignore
|
|
817
1057
|
return f"{composite_label}: {name1} {and_word} {name2}"
|
|
818
1058
|
|
|
819
1059
|
elif self.chart_type == "Transit":
|
|
820
|
-
transit_label = self.
|
|
821
|
-
from datetime import datetime
|
|
1060
|
+
transit_label = self._translate("transits", "Transits")
|
|
822
1061
|
date_obj = datetime.fromisoformat(self.second_obj.iso_formatted_local_datetime) # type: ignore
|
|
823
1062
|
date_str = date_obj.strftime("%d/%m/%y")
|
|
824
1063
|
truncated_name = self._truncate_name(self.first_obj.name)
|
|
825
1064
|
return f"{truncated_name} - {transit_label} {date_str}"
|
|
826
1065
|
|
|
827
1066
|
elif self.chart_type == "Synastry":
|
|
828
|
-
synastry_label = self.
|
|
829
|
-
and_word = self.
|
|
1067
|
+
synastry_label = self._translate("synastry_chart", "Synastry")
|
|
1068
|
+
and_word = self._translate("and_word", "&")
|
|
830
1069
|
name1 = self._truncate_name(self.first_obj.name)
|
|
831
1070
|
name2 = self._truncate_name(self.second_obj.name) # type: ignore
|
|
832
1071
|
return f"{synastry_label}: {name1} {and_word} {name2}"
|
|
833
1072
|
|
|
834
1073
|
elif self.chart_type == "DualReturnChart":
|
|
835
|
-
from datetime import datetime
|
|
836
1074
|
year = datetime.fromisoformat(self.second_obj.iso_formatted_local_datetime).year # type: ignore
|
|
837
1075
|
truncated_name = self._truncate_name(self.first_obj.name)
|
|
838
1076
|
if self.second_obj is not None and isinstance(self.second_obj, PlanetReturnModel) and self.second_obj.return_type == "Solar":
|
|
839
|
-
solar_label = self.
|
|
1077
|
+
solar_label = self._translate("solar_return", "Solar")
|
|
840
1078
|
return f"{truncated_name} - {solar_label} {year}"
|
|
841
1079
|
else:
|
|
842
|
-
lunar_label = self.
|
|
1080
|
+
lunar_label = self._translate("lunar_return", "Lunar")
|
|
843
1081
|
return f"{truncated_name} - {lunar_label} {year}"
|
|
844
1082
|
|
|
845
1083
|
elif self.chart_type == "SingleReturnChart":
|
|
846
|
-
from datetime import datetime
|
|
847
1084
|
year = datetime.fromisoformat(self.first_obj.iso_formatted_local_datetime).year # type: ignore
|
|
848
1085
|
truncated_name = self._truncate_name(self.first_obj.name)
|
|
849
1086
|
if isinstance(self.first_obj, PlanetReturnModel) and self.first_obj.return_type == "Solar":
|
|
850
|
-
solar_label = self.
|
|
1087
|
+
solar_label = self._translate("solar_return", "Solar")
|
|
851
1088
|
return f"{truncated_name} - {solar_label} {year}"
|
|
852
1089
|
else:
|
|
853
|
-
lunar_label = self.
|
|
1090
|
+
lunar_label = self._translate("lunar_return", "Lunar")
|
|
854
1091
|
return f"{truncated_name} - {lunar_label} {year}"
|
|
855
1092
|
|
|
856
1093
|
# Fallback for unknown chart types
|
|
@@ -929,11 +1166,11 @@ class ChartDrawer:
|
|
|
929
1166
|
water_percentage = element_percentages["water"]
|
|
930
1167
|
|
|
931
1168
|
# Element Percentages
|
|
932
|
-
template_dict["elements_string"] = f"{self.
|
|
933
|
-
template_dict["fire_string"] = f"{self.
|
|
934
|
-
template_dict["earth_string"] = f"{self.
|
|
935
|
-
template_dict["air_string"] = f"{self.
|
|
936
|
-
template_dict["water_string"] = f"{self.
|
|
1169
|
+
template_dict["elements_string"] = f"{self._translate('elements', 'Elements')}:"
|
|
1170
|
+
template_dict["fire_string"] = f"{self._translate('fire', 'Fire')} {fire_percentage}%"
|
|
1171
|
+
template_dict["earth_string"] = f"{self._translate('earth', 'Earth')} {earth_percentage}%"
|
|
1172
|
+
template_dict["air_string"] = f"{self._translate('air', 'Air')} {air_percentage}%"
|
|
1173
|
+
template_dict["water_string"] = f"{self._translate('water', 'Water')} {water_percentage}%"
|
|
937
1174
|
|
|
938
1175
|
|
|
939
1176
|
# Qualities Percentages
|
|
@@ -944,10 +1181,10 @@ class ChartDrawer:
|
|
|
944
1181
|
fixed_percentage = quality_percentages["fixed"]
|
|
945
1182
|
mutable_percentage = quality_percentages["mutable"]
|
|
946
1183
|
|
|
947
|
-
template_dict["qualities_string"] = f"{self.
|
|
948
|
-
template_dict["cardinal_string"] = f"{self.
|
|
949
|
-
template_dict["fixed_string"] = f"{self.
|
|
950
|
-
template_dict["mutable_string"] = f"{self.
|
|
1184
|
+
template_dict["qualities_string"] = f"{self._translate('qualities', 'Qualities')}:"
|
|
1185
|
+
template_dict["cardinal_string"] = f"{self._translate('cardinal', 'Cardinal')} {cardinal_percentage}%"
|
|
1186
|
+
template_dict["fixed_string"] = f"{self._translate('fixed', 'Fixed')} {fixed_percentage}%"
|
|
1187
|
+
template_dict["mutable_string"] = f"{self._translate('mutable', 'Mutable')} {mutable_percentage}%"
|
|
951
1188
|
|
|
952
1189
|
# Get houses list for main subject
|
|
953
1190
|
first_subject_houses_list = get_houses_list(self.first_obj)
|
|
@@ -1007,37 +1244,60 @@ class ChartDrawer:
|
|
|
1007
1244
|
template_dict["makeAspects"] = self._draw_all_aspects_lines(self.main_radius, self.main_radius - self.third_circle_radius)
|
|
1008
1245
|
|
|
1009
1246
|
# Top left section
|
|
1010
|
-
latitude_string = convert_latitude_coordinate_to_string(
|
|
1011
|
-
|
|
1247
|
+
latitude_string = convert_latitude_coordinate_to_string(
|
|
1248
|
+
self.geolat,
|
|
1249
|
+
self._translate("north", "North"),
|
|
1250
|
+
self._translate("south", "South"),
|
|
1251
|
+
)
|
|
1252
|
+
longitude_string = convert_longitude_coordinate_to_string(
|
|
1253
|
+
self.geolon,
|
|
1254
|
+
self._translate("east", "East"),
|
|
1255
|
+
self._translate("west", "West"),
|
|
1256
|
+
)
|
|
1012
1257
|
|
|
1013
|
-
template_dict["top_left_0"] = f'{self.
|
|
1258
|
+
template_dict["top_left_0"] = f'{self._translate("location", "Location")}:'
|
|
1014
1259
|
template_dict["top_left_1"] = f"{self.first_obj.city}, {self.first_obj.nation}"
|
|
1015
|
-
template_dict["top_left_2"] = f"{self.
|
|
1016
|
-
template_dict["top_left_3"] = f"{self.
|
|
1260
|
+
template_dict["top_left_2"] = f"{self._translate('latitude', 'Latitude')}: {latitude_string}"
|
|
1261
|
+
template_dict["top_left_3"] = f"{self._translate('longitude', 'Longitude')}: {longitude_string}"
|
|
1017
1262
|
template_dict["top_left_4"] = format_datetime_with_timezone(self.first_obj.iso_formatted_local_datetime) # type: ignore
|
|
1018
|
-
localized_weekday = self.
|
|
1019
|
-
|
|
1263
|
+
localized_weekday = self._translate(
|
|
1264
|
+
f"weekdays.{self.first_obj.day_of_week}",
|
|
1265
|
+
self.first_obj.day_of_week, # type: ignore[arg-type]
|
|
1266
|
+
)
|
|
1267
|
+
template_dict["top_left_5"] = f"{self._translate('day_of_week', 'Day of Week')}: {localized_weekday}" # type: ignore
|
|
1020
1268
|
|
|
1021
1269
|
# Bottom left section
|
|
1022
1270
|
if self.first_obj.zodiac_type == "Tropic":
|
|
1023
|
-
zodiac_info = f"{self.
|
|
1271
|
+
zodiac_info = f"{self._translate('zodiac', 'Zodiac')}: {self._translate('tropical', 'Tropical')}"
|
|
1024
1272
|
else:
|
|
1025
1273
|
mode_const = "SIDM_" + self.first_obj.sidereal_mode # type: ignore
|
|
1026
1274
|
mode_name = swe.get_ayanamsa_name(getattr(swe, mode_const))
|
|
1027
|
-
zodiac_info = f"{self.
|
|
1275
|
+
zodiac_info = f"{self._translate('ayanamsa', 'Ayanamsa')}: {mode_name}"
|
|
1028
1276
|
|
|
1029
1277
|
template_dict["bottom_left_0"] = zodiac_info
|
|
1030
|
-
template_dict["bottom_left_1"] =
|
|
1278
|
+
template_dict["bottom_left_1"] = (
|
|
1279
|
+
f"{self._translate('domification', 'Domification')}: "
|
|
1280
|
+
f"{self._translate('houses_system_' + self.first_obj.houses_system_identifier, self.first_obj.houses_system_name)}"
|
|
1281
|
+
)
|
|
1031
1282
|
|
|
1032
1283
|
# Lunar phase information (optional)
|
|
1033
1284
|
if self.first_obj.lunar_phase is not None:
|
|
1034
|
-
template_dict["bottom_left_2"] =
|
|
1035
|
-
|
|
1285
|
+
template_dict["bottom_left_2"] = (
|
|
1286
|
+
f'{self._translate("lunation_day", "Lunation Day")}: '
|
|
1287
|
+
f'{self.first_obj.lunar_phase.get("moon_phase", "")}'
|
|
1288
|
+
)
|
|
1289
|
+
template_dict["bottom_left_3"] = (
|
|
1290
|
+
f'{self._translate("lunar_phase", "Lunar Phase")}: '
|
|
1291
|
+
f'{self._translate(self.first_obj.lunar_phase.moon_phase_name.lower().replace(" ", "_"), self.first_obj.lunar_phase.moon_phase_name)}'
|
|
1292
|
+
)
|
|
1036
1293
|
else:
|
|
1037
1294
|
template_dict["bottom_left_2"] = ""
|
|
1038
1295
|
template_dict["bottom_left_3"] = ""
|
|
1039
1296
|
|
|
1040
|
-
template_dict["bottom_left_4"] =
|
|
1297
|
+
template_dict["bottom_left_4"] = (
|
|
1298
|
+
f'{self._translate("perspective_type", "Perspective")}: '
|
|
1299
|
+
f'{self._translate(self.first_obj.perspective_type.lower().replace(" ", "_"), self.first_obj.perspective_type)}'
|
|
1300
|
+
)
|
|
1041
1301
|
|
|
1042
1302
|
# Moon phase section calculations
|
|
1043
1303
|
if self.first_obj.lunar_phase is not None:
|
|
@@ -1049,7 +1309,7 @@ class ChartDrawer:
|
|
|
1049
1309
|
template_dict["makeMainHousesGrid"] = draw_main_house_grid(
|
|
1050
1310
|
main_subject_houses_list=first_subject_houses_list,
|
|
1051
1311
|
text_color=self.chart_colors_settings["paper_0"],
|
|
1052
|
-
house_cusp_generale_name_label=self.
|
|
1312
|
+
house_cusp_generale_name_label=self._translate("cusp", "Cusp"),
|
|
1053
1313
|
)
|
|
1054
1314
|
template_dict["makeSecondaryHousesGrid"] = ""
|
|
1055
1315
|
|
|
@@ -1079,12 +1339,12 @@ class ChartDrawer:
|
|
|
1079
1339
|
)
|
|
1080
1340
|
|
|
1081
1341
|
template_dict["makeMainPlanetGrid"] = draw_main_planet_grid(
|
|
1082
|
-
planets_and_houses_grid_title=self.
|
|
1342
|
+
planets_and_houses_grid_title=self._translate("planets_and_house", "Points for"),
|
|
1083
1343
|
subject_name=self.first_obj.name,
|
|
1084
1344
|
available_kerykeion_celestial_points=self.available_kerykeion_celestial_points,
|
|
1085
1345
|
chart_type=self.chart_type,
|
|
1086
1346
|
text_color=self.chart_colors_settings["paper_0"],
|
|
1087
|
-
celestial_point_language=self.
|
|
1347
|
+
celestial_point_language=self._language_model.celestial_points,
|
|
1088
1348
|
)
|
|
1089
1349
|
template_dict["makeSecondaryPlanetGrid"] = ""
|
|
1090
1350
|
template_dict["makeHouseComparisonGrid"] = ""
|
|
@@ -1140,25 +1400,25 @@ class ChartDrawer:
|
|
|
1140
1400
|
# First subject
|
|
1141
1401
|
latitude = convert_latitude_coordinate_to_string(
|
|
1142
1402
|
self.first_obj.first_subject.lat, # type: ignore
|
|
1143
|
-
self.
|
|
1144
|
-
self.
|
|
1403
|
+
self._translate("north_letter", "N"),
|
|
1404
|
+
self._translate("south_letter", "S"),
|
|
1145
1405
|
)
|
|
1146
1406
|
longitude = convert_longitude_coordinate_to_string(
|
|
1147
1407
|
self.first_obj.first_subject.lng, # type: ignore
|
|
1148
|
-
self.
|
|
1149
|
-
self.
|
|
1408
|
+
self._translate("east_letter", "E"),
|
|
1409
|
+
self._translate("west_letter", "W"),
|
|
1150
1410
|
)
|
|
1151
1411
|
|
|
1152
1412
|
# Second subject
|
|
1153
1413
|
latitude_string = convert_latitude_coordinate_to_string(
|
|
1154
1414
|
self.first_obj.second_subject.lat, # type: ignore
|
|
1155
|
-
self.
|
|
1156
|
-
self.
|
|
1415
|
+
self._translate("north_letter", "N"),
|
|
1416
|
+
self._translate("south_letter", "S"),
|
|
1157
1417
|
)
|
|
1158
1418
|
longitude_string = convert_longitude_coordinate_to_string(
|
|
1159
1419
|
self.first_obj.second_subject.lng, # type: ignore
|
|
1160
|
-
self.
|
|
1161
|
-
self.
|
|
1420
|
+
self._translate("east_letter", "E"),
|
|
1421
|
+
self._translate("west_letter", "W"),
|
|
1162
1422
|
)
|
|
1163
1423
|
|
|
1164
1424
|
template_dict["top_left_0"] = f"{self.first_obj.first_subject.name}" # type: ignore
|
|
@@ -1170,16 +1430,16 @@ class ChartDrawer:
|
|
|
1170
1430
|
|
|
1171
1431
|
# Bottom left section
|
|
1172
1432
|
if self.first_obj.zodiac_type == "Tropic":
|
|
1173
|
-
zodiac_info = f"{self.
|
|
1433
|
+
zodiac_info = f"{self._translate('zodiac', 'Zodiac')}: {self._translate('tropical', 'Tropical')}"
|
|
1174
1434
|
else:
|
|
1175
1435
|
mode_const = "SIDM_" + self.first_obj.sidereal_mode # type: ignore
|
|
1176
1436
|
mode_name = swe.get_ayanamsa_name(getattr(swe, mode_const))
|
|
1177
|
-
zodiac_info = f"{self.
|
|
1437
|
+
zodiac_info = f"{self._translate('ayanamsa', 'Ayanamsa')}: {mode_name}"
|
|
1178
1438
|
|
|
1179
1439
|
template_dict["bottom_left_0"] = zodiac_info
|
|
1180
|
-
template_dict["bottom_left_1"] = f"{self.
|
|
1181
|
-
template_dict["bottom_left_2"] = f'{self.
|
|
1182
|
-
template_dict["bottom_left_3"] = f'{self.
|
|
1440
|
+
template_dict["bottom_left_1"] = f"{self._translate('houses_system_' + self.first_obj.houses_system_identifier, self.first_obj.houses_system_name)} {self._translate('houses', 'Houses')}"
|
|
1441
|
+
template_dict["bottom_left_2"] = f'{self._translate("perspective_type", "Perspective")}: {self.first_obj.first_subject.perspective_type}' # type: ignore
|
|
1442
|
+
template_dict["bottom_left_3"] = f'{self._translate("composite_chart", "Composite Chart")} - {self._translate("midpoints", "Midpoints")}'
|
|
1183
1443
|
template_dict["bottom_left_4"] = ""
|
|
1184
1444
|
|
|
1185
1445
|
# Moon phase section calculations
|
|
@@ -1192,7 +1452,7 @@ class ChartDrawer:
|
|
|
1192
1452
|
template_dict["makeMainHousesGrid"] = draw_main_house_grid(
|
|
1193
1453
|
main_subject_houses_list=first_subject_houses_list,
|
|
1194
1454
|
text_color=self.chart_colors_settings["paper_0"],
|
|
1195
|
-
house_cusp_generale_name_label=self.
|
|
1455
|
+
house_cusp_generale_name_label=self._translate("cusp", "Cusp"),
|
|
1196
1456
|
)
|
|
1197
1457
|
template_dict["makeSecondaryHousesGrid"] = ""
|
|
1198
1458
|
|
|
@@ -1221,15 +1481,19 @@ class ChartDrawer:
|
|
|
1221
1481
|
external_view=self.external_view,
|
|
1222
1482
|
)
|
|
1223
1483
|
|
|
1224
|
-
subject_name =
|
|
1484
|
+
subject_name = (
|
|
1485
|
+
f"{self.first_obj.first_subject.name}"
|
|
1486
|
+
f" {self._translate('and_word', '&')} "
|
|
1487
|
+
f"{self.first_obj.second_subject.name}"
|
|
1488
|
+
) # type: ignore
|
|
1225
1489
|
|
|
1226
1490
|
template_dict["makeMainPlanetGrid"] = draw_main_planet_grid(
|
|
1227
|
-
planets_and_houses_grid_title=self.
|
|
1491
|
+
planets_and_houses_grid_title=self._translate("planets_and_house", "Points for"),
|
|
1228
1492
|
subject_name=subject_name,
|
|
1229
1493
|
available_kerykeion_celestial_points=self.available_kerykeion_celestial_points,
|
|
1230
1494
|
chart_type=self.chart_type,
|
|
1231
1495
|
text_color=self.chart_colors_settings["paper_0"],
|
|
1232
|
-
celestial_point_language=self.
|
|
1496
|
+
celestial_point_language=self._language_model.celestial_points,
|
|
1233
1497
|
)
|
|
1234
1498
|
template_dict["makeSecondaryPlanetGrid"] = ""
|
|
1235
1499
|
template_dict["makeHouseComparisonGrid"] = ""
|
|
@@ -1288,9 +1552,15 @@ class ChartDrawer:
|
|
|
1288
1552
|
|
|
1289
1553
|
# Aspects
|
|
1290
1554
|
if self.double_chart_aspect_grid_type == "list":
|
|
1291
|
-
title = f'{self.first_obj.name} - {self.
|
|
1555
|
+
title = f'{self.first_obj.name} - {self._translate("transit_aspects", "Transit Aspects")}'
|
|
1292
1556
|
template_dict["makeAspectGrid"] = ""
|
|
1293
|
-
template_dict["makeDoubleChartAspectList"] = draw_transit_aspect_list(
|
|
1557
|
+
template_dict["makeDoubleChartAspectList"] = draw_transit_aspect_list(
|
|
1558
|
+
title,
|
|
1559
|
+
self.aspects_list,
|
|
1560
|
+
self.planets_settings,
|
|
1561
|
+
self.aspects_settings,
|
|
1562
|
+
chart_height=self.height,
|
|
1563
|
+
) # type: ignore[arg-type]
|
|
1294
1564
|
else:
|
|
1295
1565
|
template_dict["makeAspectGrid"] = ""
|
|
1296
1566
|
template_dict["makeDoubleChartAspectList"] = draw_transit_aspect_grid(
|
|
@@ -1304,36 +1574,36 @@ class ChartDrawer:
|
|
|
1304
1574
|
template_dict["makeAspects"] = self._draw_all_transit_aspects_lines(self.main_radius, self.main_radius - 160)
|
|
1305
1575
|
|
|
1306
1576
|
# Top left section
|
|
1307
|
-
latitude_string = convert_latitude_coordinate_to_string(self.geolat, self.
|
|
1308
|
-
longitude_string = convert_longitude_coordinate_to_string(self.geolon, self.
|
|
1577
|
+
latitude_string = convert_latitude_coordinate_to_string(self.geolat, self._translate("north", "North"), self._translate("south", "South"))
|
|
1578
|
+
longitude_string = convert_longitude_coordinate_to_string(self.geolon, self._translate("east", "East"), self._translate("west", "West"))
|
|
1309
1579
|
|
|
1310
|
-
template_dict["top_left_0"] =
|
|
1580
|
+
template_dict["top_left_0"] = f"{self.first_obj.name}"
|
|
1311
1581
|
template_dict["top_left_1"] = f"{format_location_string(self.first_obj.city)}, {self.first_obj.nation}" # type: ignore
|
|
1312
1582
|
template_dict["top_left_2"] = format_datetime_with_timezone(self.first_obj.iso_formatted_local_datetime) # type: ignore
|
|
1313
|
-
template_dict["top_left_3"] = f"{self.
|
|
1314
|
-
template_dict["top_left_4"] = f"{self.
|
|
1315
|
-
template_dict["top_left_5"] = ""#f"{self.
|
|
1583
|
+
template_dict["top_left_3"] = f"{self._translate('latitude', 'Latitude')}: {latitude_string}"
|
|
1584
|
+
template_dict["top_left_4"] = f"{self._translate('longitude', 'Longitude')}: {longitude_string}"
|
|
1585
|
+
template_dict["top_left_5"] = ""#f"{self._translate('type', 'Type')}: {self._translate(self.chart_type, self.chart_type)}"
|
|
1316
1586
|
|
|
1317
1587
|
# Bottom left section
|
|
1318
1588
|
if self.first_obj.zodiac_type == "Tropic":
|
|
1319
|
-
zodiac_info = f"{self.
|
|
1589
|
+
zodiac_info = f"{self._translate('zodiac', 'Zodiac')}: {self._translate('tropical', 'Tropical')}"
|
|
1320
1590
|
else:
|
|
1321
1591
|
mode_const = "SIDM_" + self.first_obj.sidereal_mode # type: ignore
|
|
1322
1592
|
mode_name = swe.get_ayanamsa_name(getattr(swe, mode_const))
|
|
1323
|
-
zodiac_info = f"{self.
|
|
1593
|
+
zodiac_info = f"{self._translate('ayanamsa', 'Ayanamsa')}: {mode_name}"
|
|
1324
1594
|
|
|
1325
1595
|
template_dict["bottom_left_0"] = zodiac_info
|
|
1326
|
-
template_dict["bottom_left_1"] = f"{self.
|
|
1596
|
+
template_dict["bottom_left_1"] = f"{self._translate('domification', 'Domification')}: {self._translate('houses_system_' + self.first_obj.houses_system_identifier, self.first_obj.houses_system_name)}"
|
|
1327
1597
|
|
|
1328
1598
|
# Lunar phase information from second object (Transit) (optional)
|
|
1329
1599
|
if self.second_obj is not None and hasattr(self.second_obj, 'lunar_phase') and self.second_obj.lunar_phase is not None:
|
|
1330
|
-
template_dict["bottom_left_2"] = f'{self.
|
|
1331
|
-
template_dict["bottom_left_3"] = f'{self.
|
|
1600
|
+
template_dict["bottom_left_2"] = f'{self._translate("lunation_day", "Lunation Day")}: {self.second_obj.lunar_phase.get("moon_phase", "")}' # type: ignore
|
|
1601
|
+
template_dict["bottom_left_3"] = f'{self._translate("lunar_phase", "Lunar Phase")}: {self._translate(self.second_obj.lunar_phase.moon_phase_name.lower().replace(" ", "_"), self.second_obj.lunar_phase.moon_phase_name)}'
|
|
1332
1602
|
else:
|
|
1333
1603
|
template_dict["bottom_left_2"] = ""
|
|
1334
1604
|
template_dict["bottom_left_3"] = ""
|
|
1335
1605
|
|
|
1336
|
-
template_dict["bottom_left_4"] = f'{self.
|
|
1606
|
+
template_dict["bottom_left_4"] = f'{self._translate("perspective_type", "Perspective")}: {self._translate(self.second_obj.perspective_type.lower().replace(" ", "_"), self.second_obj.perspective_type)}' # type: ignore
|
|
1337
1607
|
|
|
1338
1608
|
# Moon phase section calculations - use first_obj for visualization
|
|
1339
1609
|
if self.first_obj.lunar_phase is not None:
|
|
@@ -1345,12 +1615,12 @@ class ChartDrawer:
|
|
|
1345
1615
|
template_dict["makeMainHousesGrid"] = draw_main_house_grid(
|
|
1346
1616
|
main_subject_houses_list=first_subject_houses_list,
|
|
1347
1617
|
text_color=self.chart_colors_settings["paper_0"],
|
|
1348
|
-
house_cusp_generale_name_label=self.
|
|
1618
|
+
house_cusp_generale_name_label=self._translate("cusp", "Cusp"),
|
|
1349
1619
|
)
|
|
1350
1620
|
# template_dict["makeSecondaryHousesGrid"] = draw_secondary_house_grid(
|
|
1351
1621
|
# secondary_subject_houses_list=second_subject_houses_list,
|
|
1352
1622
|
# text_color=self.chart_colors_settings["paper_0"],
|
|
1353
|
-
# house_cusp_generale_name_label=self.
|
|
1623
|
+
# house_cusp_generale_name_label=self._translate("cusp", "Cusp"),
|
|
1354
1624
|
# )
|
|
1355
1625
|
template_dict["makeSecondaryHousesGrid"] = ""
|
|
1356
1626
|
|
|
@@ -1383,15 +1653,17 @@ class ChartDrawer:
|
|
|
1383
1653
|
)
|
|
1384
1654
|
|
|
1385
1655
|
# Planet grids
|
|
1386
|
-
|
|
1387
|
-
|
|
1656
|
+
first_name_label = self._truncate_name(self.first_obj.name)
|
|
1657
|
+
transit_label = self._translate("transit", "Transit")
|
|
1658
|
+
first_return_grid_title = f"{first_name_label} ({self._translate('inner_wheel', 'Inner Wheel')})"
|
|
1659
|
+
second_return_grid_title = f"{transit_label} ({self._translate('outer_wheel', 'Outer Wheel')})"
|
|
1388
1660
|
template_dict["makeMainPlanetGrid"] = draw_main_planet_grid(
|
|
1389
1661
|
planets_and_houses_grid_title="",
|
|
1390
1662
|
subject_name=first_return_grid_title,
|
|
1391
1663
|
available_kerykeion_celestial_points=self.available_kerykeion_celestial_points,
|
|
1392
1664
|
chart_type=self.chart_type,
|
|
1393
1665
|
text_color=self.chart_colors_settings["paper_0"],
|
|
1394
|
-
celestial_point_language=self.
|
|
1666
|
+
celestial_point_language=self._language_model.celestial_points,
|
|
1395
1667
|
)
|
|
1396
1668
|
|
|
1397
1669
|
template_dict["makeSecondaryPlanetGrid"] = draw_secondary_planet_grid(
|
|
@@ -1400,7 +1672,7 @@ class ChartDrawer:
|
|
|
1400
1672
|
second_subject_available_kerykeion_celestial_points=self.t_available_kerykeion_celestial_points,
|
|
1401
1673
|
chart_type=self.chart_type,
|
|
1402
1674
|
text_color=self.chart_colors_settings["paper_0"],
|
|
1403
|
-
celestial_point_language=self.
|
|
1675
|
+
celestial_point_language=self._language_model.celestial_points,
|
|
1404
1676
|
)
|
|
1405
1677
|
|
|
1406
1678
|
# House comparison grid
|
|
@@ -1413,12 +1685,12 @@ class ChartDrawer:
|
|
|
1413
1685
|
|
|
1414
1686
|
template_dict["makeHouseComparisonGrid"] = draw_single_house_comparison_grid(
|
|
1415
1687
|
house_comparison,
|
|
1416
|
-
celestial_point_language=self.
|
|
1688
|
+
celestial_point_language=self._language_model.celestial_points,
|
|
1417
1689
|
active_points=self.active_points,
|
|
1418
1690
|
points_owner_subject_number=2, # The second subject is the Transit
|
|
1419
|
-
house_position_comparison_label=self.
|
|
1420
|
-
return_point_label=self.
|
|
1421
|
-
natal_house_label=self.
|
|
1691
|
+
house_position_comparison_label=self._translate("house_position_comparison", "House Position Comparison"),
|
|
1692
|
+
return_point_label=self._translate("transit_point", "Transit Point"),
|
|
1693
|
+
natal_house_label=self._translate("house_position", "Natal House"),
|
|
1422
1694
|
x_position=980,
|
|
1423
1695
|
)
|
|
1424
1696
|
|
|
@@ -1464,10 +1736,11 @@ class ChartDrawer:
|
|
|
1464
1736
|
if self.double_chart_aspect_grid_type == "list":
|
|
1465
1737
|
template_dict["makeAspectGrid"] = ""
|
|
1466
1738
|
template_dict["makeDoubleChartAspectList"] = draw_transit_aspect_list(
|
|
1467
|
-
f"{self.first_obj.name} - {self.second_obj.name} {self.
|
|
1739
|
+
f"{self.first_obj.name} - {self.second_obj.name} {self._translate('synastry_aspects', 'Synastry Aspects')}", # type: ignore[union-attr]
|
|
1468
1740
|
self.aspects_list,
|
|
1469
1741
|
self.planets_settings, # type: ignore[arg-type]
|
|
1470
|
-
self.aspects_settings # type: ignore[arg-type]
|
|
1742
|
+
self.aspects_settings, # type: ignore[arg-type]
|
|
1743
|
+
chart_height=self.height,
|
|
1471
1744
|
)
|
|
1472
1745
|
else:
|
|
1473
1746
|
template_dict["makeAspectGrid"] = ""
|
|
@@ -1491,18 +1764,18 @@ class ChartDrawer:
|
|
|
1491
1764
|
|
|
1492
1765
|
# Bottom left section
|
|
1493
1766
|
if self.first_obj.zodiac_type == "Tropic":
|
|
1494
|
-
zodiac_info = f"{self.
|
|
1767
|
+
zodiac_info = f"{self._translate('zodiac', 'Zodiac')}: {self._translate('tropical', 'Tropical')}"
|
|
1495
1768
|
else:
|
|
1496
1769
|
mode_const = "SIDM_" + self.first_obj.sidereal_mode # type: ignore
|
|
1497
1770
|
mode_name = swe.get_ayanamsa_name(getattr(swe, mode_const))
|
|
1498
|
-
zodiac_info = f"{self.
|
|
1771
|
+
zodiac_info = f"{self._translate('ayanamsa', 'Ayanamsa')}: {mode_name}"
|
|
1499
1772
|
|
|
1500
1773
|
template_dict["bottom_left_0"] = ""
|
|
1501
1774
|
# FIXME!
|
|
1502
1775
|
template_dict["bottom_left_1"] = "" # f"Compatibility Score: {16}/44" # type: ignore
|
|
1503
1776
|
template_dict["bottom_left_2"] = zodiac_info
|
|
1504
|
-
template_dict["bottom_left_3"] = f"{self.
|
|
1505
|
-
template_dict["bottom_left_4"] = f'{self.
|
|
1777
|
+
template_dict["bottom_left_3"] = f"{self._translate('houses_system_' + self.first_obj.houses_system_identifier, self.first_obj.houses_system_name)} {self._translate('houses', 'Houses')}"
|
|
1778
|
+
template_dict["bottom_left_4"] = f'{self._translate("perspective_type", "Perspective")}: {self._translate(self.first_obj.perspective_type.lower().replace(" ", "_"), self.first_obj.perspective_type)}'
|
|
1506
1779
|
|
|
1507
1780
|
# Moon phase section calculations
|
|
1508
1781
|
template_dict["makeLunarPhase"] = ""
|
|
@@ -1511,13 +1784,13 @@ class ChartDrawer:
|
|
|
1511
1784
|
template_dict["makeMainHousesGrid"] = draw_main_house_grid(
|
|
1512
1785
|
main_subject_houses_list=first_subject_houses_list,
|
|
1513
1786
|
text_color=self.chart_colors_settings["paper_0"],
|
|
1514
|
-
house_cusp_generale_name_label=self.
|
|
1787
|
+
house_cusp_generale_name_label=self._translate("cusp", "Cusp"),
|
|
1515
1788
|
)
|
|
1516
1789
|
|
|
1517
1790
|
template_dict["makeSecondaryHousesGrid"] = draw_secondary_house_grid(
|
|
1518
1791
|
secondary_subject_houses_list=second_subject_houses_list,
|
|
1519
1792
|
text_color=self.chart_colors_settings["paper_0"],
|
|
1520
|
-
house_cusp_generale_name_label=self.
|
|
1793
|
+
house_cusp_generale_name_label=self._translate("cusp", "Cusp"),
|
|
1521
1794
|
)
|
|
1522
1795
|
|
|
1523
1796
|
template_dict["makeHouses"] = draw_houses_cusps_and_text_number(
|
|
@@ -1549,23 +1822,63 @@ class ChartDrawer:
|
|
|
1549
1822
|
)
|
|
1550
1823
|
|
|
1551
1824
|
# Planet grid
|
|
1825
|
+
first_name_label = self._truncate_name(self.first_obj.name, 18, "…") # type: ignore[union-attr]
|
|
1826
|
+
second_name_label = self._truncate_name(self.second_obj.name, 18, "…") # type: ignore[union-attr]
|
|
1552
1827
|
template_dict["makeMainPlanetGrid"] = draw_main_planet_grid(
|
|
1553
1828
|
planets_and_houses_grid_title="",
|
|
1554
|
-
subject_name=f"{
|
|
1829
|
+
subject_name=f"{first_name_label} ({self._translate('inner_wheel', 'Inner Wheel')})",
|
|
1555
1830
|
available_kerykeion_celestial_points=self.available_kerykeion_celestial_points,
|
|
1556
1831
|
chart_type=self.chart_type,
|
|
1557
1832
|
text_color=self.chart_colors_settings["paper_0"],
|
|
1558
|
-
celestial_point_language=self.
|
|
1833
|
+
celestial_point_language=self._language_model.celestial_points,
|
|
1559
1834
|
)
|
|
1560
1835
|
template_dict["makeSecondaryPlanetGrid"] = draw_secondary_planet_grid(
|
|
1561
1836
|
planets_and_houses_grid_title="",
|
|
1562
|
-
second_subject_name= f"{
|
|
1837
|
+
second_subject_name= f"{second_name_label} ({self._translate('outer_wheel', 'Outer Wheel')})", # type: ignore
|
|
1563
1838
|
second_subject_available_kerykeion_celestial_points=self.t_available_kerykeion_celestial_points,
|
|
1564
1839
|
chart_type=self.chart_type,
|
|
1565
1840
|
text_color=self.chart_colors_settings["paper_0"],
|
|
1566
|
-
celestial_point_language=self.
|
|
1841
|
+
celestial_point_language=self._language_model.celestial_points,
|
|
1567
1842
|
)
|
|
1568
|
-
|
|
1843
|
+
house_comparison_factory = HouseComparisonFactory(
|
|
1844
|
+
first_subject=self.first_obj, # type: ignore[arg-type]
|
|
1845
|
+
second_subject=self.second_obj, # type: ignore[arg-type]
|
|
1846
|
+
active_points=self.active_points,
|
|
1847
|
+
)
|
|
1848
|
+
house_comparison = house_comparison_factory.get_house_comparison()
|
|
1849
|
+
|
|
1850
|
+
first_subject_label = self._truncate_name(self.first_obj.name, 8, "…", True) # type: ignore[union-attr]
|
|
1851
|
+
second_subject_label = self._truncate_name(self.second_obj.name, 8, "…", True) # type: ignore[union-attr]
|
|
1852
|
+
point_column_label = self._translate("point", "Point")
|
|
1853
|
+
comparison_label = self._translate("house_position_comparison", "House Position Comparison")
|
|
1854
|
+
|
|
1855
|
+
first_subject_grid = draw_house_comparison_grid(
|
|
1856
|
+
house_comparison,
|
|
1857
|
+
celestial_point_language=self._language_model.celestial_points,
|
|
1858
|
+
active_points=self.active_points,
|
|
1859
|
+
points_owner_subject_number=1,
|
|
1860
|
+
house_position_comparison_label=comparison_label,
|
|
1861
|
+
return_point_label=first_subject_label + " " + point_column_label,
|
|
1862
|
+
return_label=first_subject_label,
|
|
1863
|
+
radix_label=second_subject_label,
|
|
1864
|
+
x_position=1090,
|
|
1865
|
+
y_position=0,
|
|
1866
|
+
)
|
|
1867
|
+
|
|
1868
|
+
second_subject_grid = draw_house_comparison_grid(
|
|
1869
|
+
house_comparison,
|
|
1870
|
+
celestial_point_language=self._language_model.celestial_points,
|
|
1871
|
+
active_points=self.active_points,
|
|
1872
|
+
points_owner_subject_number=2,
|
|
1873
|
+
house_position_comparison_label="",
|
|
1874
|
+
return_point_label=second_subject_label + " " + point_column_label,
|
|
1875
|
+
return_label=second_subject_label,
|
|
1876
|
+
radix_label=first_subject_label,
|
|
1877
|
+
x_position=1290,
|
|
1878
|
+
y_position=0,
|
|
1879
|
+
)
|
|
1880
|
+
|
|
1881
|
+
template_dict["makeHouseComparisonGrid"] = first_subject_grid + second_subject_grid
|
|
1569
1882
|
|
|
1570
1883
|
elif self.chart_type == "DualReturnChart":
|
|
1571
1884
|
# Set viewbox dynamically
|
|
@@ -1607,9 +1920,16 @@ class ChartDrawer:
|
|
|
1607
1920
|
|
|
1608
1921
|
# Aspects
|
|
1609
1922
|
if self.double_chart_aspect_grid_type == "list":
|
|
1610
|
-
title = self.
|
|
1923
|
+
title = self._translate("return_aspects", "Natal to Return Aspects")
|
|
1611
1924
|
template_dict["makeAspectGrid"] = ""
|
|
1612
|
-
template_dict["makeDoubleChartAspectList"] = draw_transit_aspect_list(
|
|
1925
|
+
template_dict["makeDoubleChartAspectList"] = draw_transit_aspect_list(
|
|
1926
|
+
title,
|
|
1927
|
+
self.aspects_list,
|
|
1928
|
+
self.planets_settings,
|
|
1929
|
+
self.aspects_settings,
|
|
1930
|
+
max_columns=7,
|
|
1931
|
+
chart_height=self.height,
|
|
1932
|
+
) # type: ignore[arg-type]
|
|
1613
1933
|
else:
|
|
1614
1934
|
template_dict["makeAspectGrid"] = ""
|
|
1615
1935
|
template_dict["makeDoubleChartAspectList"] = draw_transit_aspect_grid(
|
|
@@ -1625,17 +1945,17 @@ class ChartDrawer:
|
|
|
1625
1945
|
|
|
1626
1946
|
# Top left section
|
|
1627
1947
|
# Subject
|
|
1628
|
-
latitude_string = convert_latitude_coordinate_to_string(self.first_obj.lat, self.
|
|
1629
|
-
longitude_string = convert_longitude_coordinate_to_string(self.first_obj.lng, self.
|
|
1948
|
+
latitude_string = convert_latitude_coordinate_to_string(self.first_obj.lat, self._translate("north", "North"), self._translate("south", "South")) # type: ignore
|
|
1949
|
+
longitude_string = convert_longitude_coordinate_to_string(self.first_obj.lng, self._translate("east", "East"), self._translate("west", "West")) # type: ignore
|
|
1630
1950
|
|
|
1631
1951
|
# Return
|
|
1632
|
-
return_latitude_string = convert_latitude_coordinate_to_string(self.second_obj.lat, self.
|
|
1633
|
-
return_longitude_string = convert_longitude_coordinate_to_string(self.second_obj.lng, self.
|
|
1952
|
+
return_latitude_string = convert_latitude_coordinate_to_string(self.second_obj.lat, self._translate("north", "North"), self._translate("south", "South")) # type: ignore
|
|
1953
|
+
return_longitude_string = convert_longitude_coordinate_to_string(self.second_obj.lng, self._translate("east", "East"), self._translate("west", "West")) # type: ignore
|
|
1634
1954
|
|
|
1635
1955
|
if self.second_obj is not None and hasattr(self.second_obj, 'return_type') and self.second_obj.return_type == "Solar":
|
|
1636
|
-
template_dict["top_left_0"] = f"{self.
|
|
1956
|
+
template_dict["top_left_0"] = f"{self._translate('solar_return', 'Solar Return')}:"
|
|
1637
1957
|
else:
|
|
1638
|
-
template_dict["top_left_0"] = f"{self.
|
|
1958
|
+
template_dict["top_left_0"] = f"{self._translate('lunar_return', 'Lunar Return')}:"
|
|
1639
1959
|
template_dict["top_left_1"] = format_datetime_with_timezone(self.second_obj.iso_formatted_local_datetime) # type: ignore
|
|
1640
1960
|
template_dict["top_left_2"] = f"{return_latitude_string} / {return_longitude_string}"
|
|
1641
1961
|
template_dict["top_left_3"] = f"{self.first_obj.name}"
|
|
@@ -1644,24 +1964,24 @@ class ChartDrawer:
|
|
|
1644
1964
|
|
|
1645
1965
|
# Bottom left section
|
|
1646
1966
|
if self.first_obj.zodiac_type == "Tropic":
|
|
1647
|
-
zodiac_info = f"{self.
|
|
1967
|
+
zodiac_info = f"{self._translate('zodiac', 'Zodiac')}: {self._translate('tropical', 'Tropical')}"
|
|
1648
1968
|
else:
|
|
1649
1969
|
mode_const = "SIDM_" + self.first_obj.sidereal_mode # type: ignore
|
|
1650
1970
|
mode_name = swe.get_ayanamsa_name(getattr(swe, mode_const))
|
|
1651
|
-
zodiac_info = f"{self.
|
|
1971
|
+
zodiac_info = f"{self._translate('ayanamsa', 'Ayanamsa')}: {mode_name}"
|
|
1652
1972
|
|
|
1653
1973
|
template_dict["bottom_left_0"] = zodiac_info
|
|
1654
|
-
template_dict["bottom_left_1"] = f"{self.
|
|
1974
|
+
template_dict["bottom_left_1"] = f"{self._translate('domification', 'Domification')}: {self._translate('houses_system_' + self.first_obj.houses_system_identifier, self.first_obj.houses_system_name)}"
|
|
1655
1975
|
|
|
1656
1976
|
# Lunar phase information (optional)
|
|
1657
1977
|
if self.first_obj.lunar_phase is not None:
|
|
1658
|
-
template_dict["bottom_left_2"] = f'{self.
|
|
1659
|
-
template_dict["bottom_left_3"] = f'{self.
|
|
1978
|
+
template_dict["bottom_left_2"] = f'{self._translate("lunation_day", "Lunation Day")}: {self.first_obj.lunar_phase.get("moon_phase", "")}'
|
|
1979
|
+
template_dict["bottom_left_3"] = f'{self._translate("lunar_phase", "Lunar Phase")}: {self._translate(self.first_obj.lunar_phase.moon_phase_name.lower().replace(" ", "_"), self.first_obj.lunar_phase.moon_phase_name)}'
|
|
1660
1980
|
else:
|
|
1661
1981
|
template_dict["bottom_left_2"] = ""
|
|
1662
1982
|
template_dict["bottom_left_3"] = ""
|
|
1663
1983
|
|
|
1664
|
-
template_dict["bottom_left_4"] = f'{self.
|
|
1984
|
+
template_dict["bottom_left_4"] = f'{self._translate("perspective_type", "Perspective")}: {self._translate(self.first_obj.perspective_type.lower().replace(" ", "_"), self.first_obj.perspective_type)}'
|
|
1665
1985
|
|
|
1666
1986
|
# Moon phase section calculations
|
|
1667
1987
|
if self.first_obj.lunar_phase is not None:
|
|
@@ -1673,13 +1993,13 @@ class ChartDrawer:
|
|
|
1673
1993
|
template_dict["makeMainHousesGrid"] = draw_main_house_grid(
|
|
1674
1994
|
main_subject_houses_list=first_subject_houses_list,
|
|
1675
1995
|
text_color=self.chart_colors_settings["paper_0"],
|
|
1676
|
-
house_cusp_generale_name_label=self.
|
|
1996
|
+
house_cusp_generale_name_label=self._translate("cusp", "Cusp"),
|
|
1677
1997
|
)
|
|
1678
1998
|
|
|
1679
1999
|
template_dict["makeSecondaryHousesGrid"] = draw_secondary_house_grid(
|
|
1680
2000
|
secondary_subject_houses_list=second_subject_houses_list,
|
|
1681
2001
|
text_color=self.chart_colors_settings["paper_0"],
|
|
1682
|
-
house_cusp_generale_name_label=self.
|
|
2002
|
+
house_cusp_generale_name_label=self._translate("cusp", "Cusp"),
|
|
1683
2003
|
)
|
|
1684
2004
|
|
|
1685
2005
|
template_dict["makeHouses"] = draw_houses_cusps_and_text_number(
|
|
@@ -1711,19 +2031,20 @@ class ChartDrawer:
|
|
|
1711
2031
|
)
|
|
1712
2032
|
|
|
1713
2033
|
# Planet grid
|
|
2034
|
+
first_name_label = self._truncate_name(self.first_obj.name)
|
|
1714
2035
|
if self.second_obj is not None and hasattr(self.second_obj, 'return_type') and self.second_obj.return_type == "Solar":
|
|
1715
|
-
first_return_grid_title = f"{
|
|
1716
|
-
second_return_grid_title = f"{self.
|
|
2036
|
+
first_return_grid_title = f"{first_name_label} ({self._translate('inner_wheel', 'Inner Wheel')})"
|
|
2037
|
+
second_return_grid_title = f"{self._translate('solar_return', 'Solar Return')} ({self._translate('outer_wheel', 'Outer Wheel')})"
|
|
1717
2038
|
else:
|
|
1718
|
-
first_return_grid_title = f"{
|
|
1719
|
-
second_return_grid_title = f'{self.
|
|
2039
|
+
first_return_grid_title = f"{first_name_label} ({self._translate('inner_wheel', 'Inner Wheel')})"
|
|
2040
|
+
second_return_grid_title = f'{self._translate("lunar_return", "Lunar Return")} ({self._translate("outer_wheel", "Outer Wheel")})'
|
|
1720
2041
|
template_dict["makeMainPlanetGrid"] = draw_main_planet_grid(
|
|
1721
2042
|
planets_and_houses_grid_title="",
|
|
1722
2043
|
subject_name=first_return_grid_title,
|
|
1723
2044
|
available_kerykeion_celestial_points=self.available_kerykeion_celestial_points,
|
|
1724
2045
|
chart_type=self.chart_type,
|
|
1725
2046
|
text_color=self.chart_colors_settings["paper_0"],
|
|
1726
|
-
celestial_point_language=self.
|
|
2047
|
+
celestial_point_language=self._language_model.celestial_points,
|
|
1727
2048
|
)
|
|
1728
2049
|
template_dict["makeSecondaryPlanetGrid"] = draw_secondary_planet_grid(
|
|
1729
2050
|
planets_and_houses_grid_title="",
|
|
@@ -1731,7 +2052,7 @@ class ChartDrawer:
|
|
|
1731
2052
|
second_subject_available_kerykeion_celestial_points=self.t_available_kerykeion_celestial_points,
|
|
1732
2053
|
chart_type=self.chart_type,
|
|
1733
2054
|
text_color=self.chart_colors_settings["paper_0"],
|
|
1734
|
-
celestial_point_language=self.
|
|
2055
|
+
celestial_point_language=self._language_model.celestial_points,
|
|
1735
2056
|
)
|
|
1736
2057
|
|
|
1737
2058
|
house_comparison_factory = HouseComparisonFactory(
|
|
@@ -1743,13 +2064,13 @@ class ChartDrawer:
|
|
|
1743
2064
|
|
|
1744
2065
|
template_dict["makeHouseComparisonGrid"] = draw_house_comparison_grid(
|
|
1745
2066
|
house_comparison,
|
|
1746
|
-
celestial_point_language=self.
|
|
2067
|
+
celestial_point_language=self._language_model.celestial_points,
|
|
1747
2068
|
active_points=self.active_points,
|
|
1748
2069
|
points_owner_subject_number=2, # The second subject is the Solar Return
|
|
1749
|
-
house_position_comparison_label=self.
|
|
1750
|
-
return_point_label=self.
|
|
1751
|
-
return_label=self.
|
|
1752
|
-
radix_label=self.
|
|
2070
|
+
house_position_comparison_label=self._translate("house_position_comparison", "House Position Comparison"),
|
|
2071
|
+
return_point_label=self._translate("return_point", "Return Point"),
|
|
2072
|
+
return_label=self._translate("Return", "DualReturnChart"),
|
|
2073
|
+
radix_label=self._translate("Natal", "Natal"),
|
|
1753
2074
|
)
|
|
1754
2075
|
|
|
1755
2076
|
elif self.chart_type == "SingleReturnChart":
|
|
@@ -1800,40 +2121,40 @@ class ChartDrawer:
|
|
|
1800
2121
|
template_dict["makeAspects"] = self._draw_all_aspects_lines(self.main_radius, self.main_radius - self.third_circle_radius)
|
|
1801
2122
|
|
|
1802
2123
|
# Top left section
|
|
1803
|
-
latitude_string = convert_latitude_coordinate_to_string(self.geolat, self.
|
|
1804
|
-
longitude_string = convert_longitude_coordinate_to_string(self.geolon, self.
|
|
2124
|
+
latitude_string = convert_latitude_coordinate_to_string(self.geolat, self._translate("north", "North"), self._translate("south", "South"))
|
|
2125
|
+
longitude_string = convert_longitude_coordinate_to_string(self.geolon, self._translate("east", "East"), self._translate("west", "West"))
|
|
1805
2126
|
|
|
1806
|
-
template_dict["top_left_0"] = f'{self.
|
|
2127
|
+
template_dict["top_left_0"] = f'{self._translate("info", "Info")}:'
|
|
1807
2128
|
template_dict["top_left_1"] = format_datetime_with_timezone(self.first_obj.iso_formatted_local_datetime) # type: ignore
|
|
1808
2129
|
template_dict["top_left_2"] = f"{self.first_obj.city}, {self.first_obj.nation}"
|
|
1809
|
-
template_dict["top_left_3"] = f"{self.
|
|
1810
|
-
template_dict["top_left_4"] = f"{self.
|
|
2130
|
+
template_dict["top_left_3"] = f"{self._translate('latitude', 'Latitude')}: {latitude_string}"
|
|
2131
|
+
template_dict["top_left_4"] = f"{self._translate('longitude', 'Longitude')}: {longitude_string}"
|
|
1811
2132
|
|
|
1812
2133
|
if hasattr(self.first_obj, 'return_type') and self.first_obj.return_type == "Solar":
|
|
1813
|
-
template_dict["top_left_5"] = f"{self.
|
|
2134
|
+
template_dict["top_left_5"] = f"{self._translate('type', 'Type')}: {self._translate('solar_return', 'Solar Return')}"
|
|
1814
2135
|
else:
|
|
1815
|
-
template_dict["top_left_5"] = f"{self.
|
|
2136
|
+
template_dict["top_left_5"] = f"{self._translate('type', 'Type')}: {self._translate('lunar_return', 'Lunar Return')}"
|
|
1816
2137
|
|
|
1817
2138
|
# Bottom left section
|
|
1818
2139
|
if self.first_obj.zodiac_type == "Tropic":
|
|
1819
|
-
zodiac_info = f"{self.
|
|
2140
|
+
zodiac_info = f"{self._translate('zodiac', 'Zodiac')}: {self._translate('tropical', 'Tropical')}"
|
|
1820
2141
|
else:
|
|
1821
2142
|
mode_const = "SIDM_" + self.first_obj.sidereal_mode # type: ignore
|
|
1822
2143
|
mode_name = swe.get_ayanamsa_name(getattr(swe, mode_const))
|
|
1823
|
-
zodiac_info = f"{self.
|
|
2144
|
+
zodiac_info = f"{self._translate('ayanamsa', 'Ayanamsa')}: {mode_name}"
|
|
1824
2145
|
|
|
1825
2146
|
template_dict["bottom_left_0"] = zodiac_info
|
|
1826
|
-
template_dict["bottom_left_1"] = f"{self.
|
|
2147
|
+
template_dict["bottom_left_1"] = f"{self._translate('houses_system_' + self.first_obj.houses_system_identifier, self.first_obj.houses_system_name)} {self._translate('houses', 'Houses')}"
|
|
1827
2148
|
|
|
1828
2149
|
# Lunar phase information (optional)
|
|
1829
2150
|
if self.first_obj.lunar_phase is not None:
|
|
1830
|
-
template_dict["bottom_left_2"] = f'{self.
|
|
1831
|
-
template_dict["bottom_left_3"] = f'{self.
|
|
2151
|
+
template_dict["bottom_left_2"] = f'{self._translate("lunation_day", "Lunation Day")}: {self.first_obj.lunar_phase.get("moon_phase", "")}'
|
|
2152
|
+
template_dict["bottom_left_3"] = f'{self._translate("lunar_phase", "Lunar Phase")}: {self._translate(self.first_obj.lunar_phase.moon_phase_name.lower().replace(" ", "_"), self.first_obj.lunar_phase.moon_phase_name)}'
|
|
1832
2153
|
else:
|
|
1833
2154
|
template_dict["bottom_left_2"] = ""
|
|
1834
2155
|
template_dict["bottom_left_3"] = ""
|
|
1835
2156
|
|
|
1836
|
-
template_dict["bottom_left_4"] = f'{self.
|
|
2157
|
+
template_dict["bottom_left_4"] = f'{self._translate("perspective_type", "Perspective")}: {self._translate(self.first_obj.perspective_type.lower().replace(" ", "_"), self.first_obj.perspective_type)}'
|
|
1837
2158
|
|
|
1838
2159
|
# Moon phase section calculations
|
|
1839
2160
|
if self.first_obj.lunar_phase is not None:
|
|
@@ -1845,7 +2166,7 @@ class ChartDrawer:
|
|
|
1845
2166
|
template_dict["makeMainHousesGrid"] = draw_main_house_grid(
|
|
1846
2167
|
main_subject_houses_list=first_subject_houses_list,
|
|
1847
2168
|
text_color=self.chart_colors_settings["paper_0"],
|
|
1848
|
-
house_cusp_generale_name_label=self.
|
|
2169
|
+
house_cusp_generale_name_label=self._translate("cusp", "Cusp"),
|
|
1849
2170
|
)
|
|
1850
2171
|
template_dict["makeSecondaryHousesGrid"] = ""
|
|
1851
2172
|
|
|
@@ -1875,12 +2196,12 @@ class ChartDrawer:
|
|
|
1875
2196
|
)
|
|
1876
2197
|
|
|
1877
2198
|
template_dict["makeMainPlanetGrid"] = draw_main_planet_grid(
|
|
1878
|
-
planets_and_houses_grid_title=self.
|
|
2199
|
+
planets_and_houses_grid_title=self._translate("planets_and_house", "Points for"),
|
|
1879
2200
|
subject_name=self.first_obj.name,
|
|
1880
2201
|
available_kerykeion_celestial_points=self.available_kerykeion_celestial_points,
|
|
1881
2202
|
chart_type=self.chart_type,
|
|
1882
2203
|
text_color=self.chart_colors_settings["paper_0"],
|
|
1883
|
-
celestial_point_language=self.
|
|
2204
|
+
celestial_point_language=self._language_model.celestial_points,
|
|
1884
2205
|
)
|
|
1885
2206
|
template_dict["makeSecondaryPlanetGrid"] = ""
|
|
1886
2207
|
template_dict["makeHouseComparisonGrid"] = ""
|