kerykeion 4.14.2__py3-none-any.whl → 4.18.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.
- kerykeion/__init__.py +2 -1
- kerykeion/aspects/aspects_utils.py +33 -117
- kerykeion/aspects/natal_aspects.py +26 -25
- kerykeion/aspects/synastry_aspects.py +25 -27
- kerykeion/astrological_subject.py +145 -189
- kerykeion/charts/charts_utils.py +400 -126
- kerykeion/charts/draw_planets.py +407 -0
- kerykeion/charts/kerykeion_chart_svg.py +502 -770
- kerykeion/charts/templates/aspect_grid_only.xml +452 -0
- kerykeion/charts/templates/chart.xml +39 -39
- kerykeion/charts/templates/wheel_only.xml +499 -0
- kerykeion/charts/themes/classic.css +82 -0
- kerykeion/charts/themes/dark-high-contrast.css +121 -0
- kerykeion/charts/themes/dark.css +121 -0
- kerykeion/charts/themes/light.css +117 -0
- kerykeion/ephemeris_data.py +22 -18
- kerykeion/kr_types/chart_types.py +3 -7
- kerykeion/kr_types/kr_literals.py +10 -1
- kerykeion/kr_types/kr_models.py +33 -8
- kerykeion/kr_types/settings_models.py +1 -10
- kerykeion/relationship_score/__init__.py +2 -0
- kerykeion/relationship_score/relationship_score.py +175 -0
- kerykeion/relationship_score/relationship_score_factory.py +275 -0
- kerykeion/report.py +6 -3
- kerykeion/settings/kerykeion_settings.py +6 -1
- kerykeion/settings/kr.config.json +238 -98
- kerykeion/utilities.py +116 -215
- {kerykeion-4.14.2.dist-info → kerykeion-4.18.0.dist-info}/METADATA +40 -10
- kerykeion-4.18.0.dist-info/RECORD +42 -0
- kerykeion/relationship_score.py +0 -205
- kerykeion-4.14.2.dist-info/RECORD +0 -33
- {kerykeion-4.14.2.dist-info → kerykeion-4.18.0.dist-info}/LICENSE +0 -0
- {kerykeion-4.14.2.dist-info → kerykeion-4.18.0.dist-info}/WHEEL +0 -0
- {kerykeion-4.14.2.dist-info → kerykeion-4.18.0.dist-info}/entry_points.txt +0 -0
kerykeion/charts/charts_utils.py
CHANGED
|
@@ -1,11 +1,42 @@
|
|
|
1
1
|
import math
|
|
2
2
|
import datetime
|
|
3
3
|
from kerykeion.kr_types import KerykeionException, ChartType
|
|
4
|
-
from typing import Union
|
|
5
|
-
from kerykeion.kr_types.kr_models import AspectModel
|
|
4
|
+
from typing import Union, Literal
|
|
5
|
+
from kerykeion.kr_types.kr_models import AspectModel, KerykeionPointModel
|
|
6
6
|
from kerykeion.kr_types.settings_models import KerykeionLanguageCelestialPointModel, KerykeionSettingsAspectModel
|
|
7
7
|
|
|
8
8
|
|
|
9
|
+
def get_decoded_kerykeion_celestial_point_name(input_planet_name: str, celestial_point_language: KerykeionLanguageCelestialPointModel) -> str:
|
|
10
|
+
"""
|
|
11
|
+
Decode the given celestial point name based on the provided language model.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
input_planet_name (str): The name of the celestial point to decode.
|
|
15
|
+
celestial_point_language (KerykeionLanguageCelestialPointModel): The language model containing celestial point names.
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
str: The decoded celestial point name.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
# Dictionary for special house names
|
|
22
|
+
special_house_names = {
|
|
23
|
+
"First_House": "Asc",
|
|
24
|
+
"Seventh_House": "Dsc",
|
|
25
|
+
"Tenth_House": "Mc",
|
|
26
|
+
"Fourth_House": "Ic"
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
# Get the language model keys
|
|
30
|
+
language_keys = celestial_point_language.model_dump().keys()
|
|
31
|
+
|
|
32
|
+
# Check if the input planet name exists in the language model
|
|
33
|
+
if input_planet_name in language_keys:
|
|
34
|
+
return celestial_point_language[input_planet_name]
|
|
35
|
+
|
|
36
|
+
# Return the special house name if it exists, otherwise return an empty string
|
|
37
|
+
return special_house_names.get(input_planet_name, "")
|
|
38
|
+
|
|
39
|
+
|
|
9
40
|
def decHourJoin(inH: int, inM: int, inS: int) -> float:
|
|
10
41
|
"""Join hour, minutes, seconds, timezone integer to hour float.
|
|
11
42
|
|
|
@@ -132,10 +163,10 @@ def draw_zodiac_slice(
|
|
|
132
163
|
- seventh_house_degree_ut (Union[int, float]): The degree of the seventh house.
|
|
133
164
|
- num (int): The number of the sign. Note: In OpenAstro it did refer to self.zodiac,
|
|
134
165
|
which is a list of the signs in order, starting with Aries. Eg:
|
|
135
|
-
{"name": "
|
|
166
|
+
{"name": "Ari", "element": "fire"}
|
|
136
167
|
- r (Union[int, float]): The value of r.
|
|
137
168
|
- style (str): The CSS inline style.
|
|
138
|
-
- type (str): The type ?. In OpenAstro, it was the symbol of the sign. Eg: "
|
|
169
|
+
- type (str): The type ?. In OpenAstro, it was the symbol of the sign. Eg: "Ari".
|
|
139
170
|
self.zodiac[i]["name"]
|
|
140
171
|
|
|
141
172
|
Returns:
|
|
@@ -146,7 +177,7 @@ def draw_zodiac_slice(
|
|
|
146
177
|
offset = 360 - seventh_house_degree_ut
|
|
147
178
|
# check transit
|
|
148
179
|
if chart_type == "Transit" or chart_type == "Synastry":
|
|
149
|
-
dropin = 0
|
|
180
|
+
dropin: Union[int, float] = 0
|
|
150
181
|
else:
|
|
151
182
|
dropin = c1
|
|
152
183
|
slice = f'<path d="M{str(r)},{str(r)} L{str(dropin + sliceToX(num, r - dropin, offset))},{str(dropin + sliceToY(num, r - dropin, offset))} A{str(r - dropin)},{str(r - dropin)} 0 0,0 {str(dropin + sliceToX(num + 1, r - dropin, offset))},{str(dropin + sliceToY(num + 1, r - dropin, offset))} z" style="{style}"/>'
|
|
@@ -242,12 +273,11 @@ def draw_aspect_line(
|
|
|
242
273
|
x2 = sliceToX(0, ar, second_offset) + (r - ar)
|
|
243
274
|
y2 = sliceToY(0, ar, second_offset) + (r - ar)
|
|
244
275
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
return out
|
|
276
|
+
return (
|
|
277
|
+
f'<g kr:node="Aspect" kr:aspectname="{aspect["aspect"]}" kr:to="{aspect["p1_name"]}" kr:tooriginaldegrees="{aspect["p1_abs_pos"]}" kr:from="{aspect["p2_name"]}" kr:fromoriginaldegrees="{aspect["p2_abs_pos"]}">'
|
|
278
|
+
f'<line class="aspect" x1="{x1}" y1="{y1}" x2="{x2}" y2="{y2}" style="stroke: {color}; stroke-width: 1; stroke-opacity: .9;"/>'
|
|
279
|
+
f"</g>"
|
|
280
|
+
)
|
|
251
281
|
|
|
252
282
|
|
|
253
283
|
def draw_elements_percentages(
|
|
@@ -282,48 +312,45 @@ def draw_elements_percentages(
|
|
|
282
312
|
air_percentage = int(round(100 * air_points / total))
|
|
283
313
|
water_percentage = int(round(100 * water_points / total))
|
|
284
314
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
315
|
+
return (
|
|
316
|
+
f'<g transform="translate(-30,79)">'
|
|
317
|
+
f'<text y="0" style="fill: var(--kerykeion-chart-color-fire-percentage); font-size: 10px;">{fire_label} {str(fire_percentage)}%</text>'
|
|
318
|
+
f'<text y="12" style="fill: var(--kerykeion-chart-color-earth-percentage); font-size: 10px;">{earth_label} {str(earth_percentage)}%</text>'
|
|
319
|
+
f'<text y="24" style="fill: var(--kerykeion-chart-color-air-percentage); font-size: 10px;">{air_label} {str(air_percentage)}%</text>'
|
|
320
|
+
f'<text y="36" style="fill: var(--kerykeion-chart-color-water-percentage); font-size: 10px;">{water_label} {str(water_percentage)}%</text>'
|
|
321
|
+
f"</g>"
|
|
322
|
+
)
|
|
293
323
|
|
|
294
324
|
|
|
295
|
-
def convert_decimal_to_degree_string(dec: float,
|
|
325
|
+
def convert_decimal_to_degree_string(dec: float, format_type: Literal["1", "2", "3"] = "3") -> str:
|
|
296
326
|
"""
|
|
297
|
-
|
|
327
|
+
Converts a decimal float to a degrees string in the specified format.
|
|
298
328
|
|
|
299
329
|
Args:
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
- 1: a°
|
|
303
|
-
- 2: a°b'
|
|
304
|
-
- 3: a°b'c"
|
|
330
|
+
dec (float): The decimal float to convert.
|
|
331
|
+
format_type (str): The format type:
|
|
332
|
+
- "1": a°
|
|
333
|
+
- "2": a°b'
|
|
334
|
+
- "3": a°b'c" (default)
|
|
305
335
|
|
|
306
336
|
Returns:
|
|
307
|
-
str: degrees in format
|
|
337
|
+
str: The degrees string in the specified format.
|
|
308
338
|
"""
|
|
309
|
-
|
|
339
|
+
# Ensure the input is a float
|
|
310
340
|
dec = float(dec)
|
|
311
|
-
a = int(dec)
|
|
312
|
-
a_new = (dec - float(a)) * 60.0
|
|
313
|
-
b_rounded = int(round(a_new))
|
|
314
|
-
b = int(a_new)
|
|
315
|
-
c = int(round((a_new - float(b)) * 60.0))
|
|
316
|
-
|
|
317
|
-
if type == "3":
|
|
318
|
-
out = f"{a:02d}°{b:02d}'{c:02d}""
|
|
319
|
-
elif type == "2":
|
|
320
|
-
out = f"{a:02d}°{b_rounded:02d}'"
|
|
321
|
-
elif type == "1":
|
|
322
|
-
out = f"{a:02d}°"
|
|
323
|
-
else:
|
|
324
|
-
raise KerykeionException(f"Wrong type: {type}, it must be 1, 2 or 3.")
|
|
325
341
|
|
|
326
|
-
|
|
342
|
+
# Calculate degrees, minutes, and seconds
|
|
343
|
+
degrees = int(dec)
|
|
344
|
+
minutes = int((dec - degrees) * 60)
|
|
345
|
+
seconds = int(round((dec - degrees - minutes / 60) * 3600))
|
|
346
|
+
|
|
347
|
+
# Format the output based on the specified type
|
|
348
|
+
if format_type == "1":
|
|
349
|
+
return f"{degrees}°"
|
|
350
|
+
elif format_type == "2":
|
|
351
|
+
return f"{degrees}°{minutes:02d}'"
|
|
352
|
+
elif format_type == "3":
|
|
353
|
+
return f"{degrees}°{minutes:02d}'{seconds:02d}\""
|
|
327
354
|
|
|
328
355
|
|
|
329
356
|
def draw_transit_ring_degree_steps(r: Union[int, float], seventh_house_degree_ut: Union[int, float]) -> str:
|
|
@@ -464,7 +491,7 @@ def draw_third_circle(
|
|
|
464
491
|
stroke_color: str,
|
|
465
492
|
fill_color: str,
|
|
466
493
|
chart_type: ChartType,
|
|
467
|
-
c3: Union[int, float
|
|
494
|
+
c3: Union[int, float]
|
|
468
495
|
) -> str:
|
|
469
496
|
"""
|
|
470
497
|
Draws the third circle in an SVG chart.
|
|
@@ -487,59 +514,68 @@ def draw_third_circle(
|
|
|
487
514
|
return f'<circle cx="{radius}" cy="{radius}" r="{radius - c3}" style="fill: {fill_color}; fill-opacity:.8; stroke: {stroke_color}; stroke-width: 1px" />'
|
|
488
515
|
|
|
489
516
|
|
|
490
|
-
def draw_aspect_grid(
|
|
517
|
+
def draw_aspect_grid(
|
|
518
|
+
stroke_color: str,
|
|
519
|
+
available_planets: list,
|
|
520
|
+
aspects: list,
|
|
521
|
+
x_start: int = 380,
|
|
522
|
+
y_start: int = 468,
|
|
523
|
+
) -> str:
|
|
491
524
|
"""
|
|
492
|
-
Draws the aspect grid.
|
|
525
|
+
Draws the aspect grid for the given planets and aspects.
|
|
493
526
|
|
|
494
527
|
Args:
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
528
|
+
stroke_color (str): The color of the stroke.
|
|
529
|
+
available_planets (list): List of all planets. Only planets with "is_active" set to True will be used.
|
|
530
|
+
aspects (list): List of aspects.
|
|
531
|
+
x_start (int): The x-coordinate starting point.
|
|
532
|
+
y_start (int): The y-coordinate starting point.
|
|
499
533
|
|
|
534
|
+
Returns:
|
|
535
|
+
str: SVG string representing the aspect grid.
|
|
500
536
|
"""
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
for aspect in
|
|
532
|
-
if (aspect["p1"] ==
|
|
533
|
-
aspect["p1"] ==
|
|
537
|
+
svg_output = ""
|
|
538
|
+
style = f"stroke:{stroke_color}; stroke-width: 1px; fill:none"
|
|
539
|
+
box_size = 14
|
|
540
|
+
|
|
541
|
+
# Filter active planets
|
|
542
|
+
active_planets = [planet for planet in available_planets if planet.is_active]
|
|
543
|
+
|
|
544
|
+
# Reverse the list of active planets for the first iteration
|
|
545
|
+
reversed_planets = active_planets[::-1]
|
|
546
|
+
|
|
547
|
+
for index, planet_a in enumerate(reversed_planets):
|
|
548
|
+
# Draw the grid box for the planet
|
|
549
|
+
svg_output += f'<rect x="{x_start}" y="{y_start}" width="{box_size}" height="{box_size}" style="{style}"/>'
|
|
550
|
+
svg_output += f'<use transform="scale(0.4)" x="{(x_start + 2) * 2.5}" y="{(y_start + 1) * 2.5}" xlink:href="#{planet_a["name"]}" />'
|
|
551
|
+
|
|
552
|
+
# Update the starting coordinates for the next box
|
|
553
|
+
x_start += box_size
|
|
554
|
+
y_start -= box_size
|
|
555
|
+
|
|
556
|
+
# Coordinates for the aspect symbols
|
|
557
|
+
x_aspect = x_start
|
|
558
|
+
y_aspect = y_start + box_size
|
|
559
|
+
|
|
560
|
+
# Iterate over the remaining planets
|
|
561
|
+
for planet_b in reversed_planets[index + 1:]:
|
|
562
|
+
# Draw the grid box for the aspect
|
|
563
|
+
svg_output += f'<rect x="{x_aspect}" y="{y_aspect}" width="{box_size}" height="{box_size}" style="{style}"/>'
|
|
564
|
+
x_aspect += box_size
|
|
565
|
+
|
|
566
|
+
# Check for aspects between the planets
|
|
567
|
+
for aspect in aspects:
|
|
568
|
+
if (aspect["p1"] == planet_a["id"] and aspect["p2"] == planet_b["id"]) or (
|
|
569
|
+
aspect["p1"] == planet_b["id"] and aspect["p2"] == planet_a["id"]
|
|
534
570
|
):
|
|
535
|
-
|
|
571
|
+
svg_output += f'<use x="{x_aspect - box_size + 1}" y="{y_aspect + 1}" xlink:href="#orb{aspect["aspect_degrees"]}" />'
|
|
536
572
|
|
|
537
|
-
return
|
|
573
|
+
return svg_output
|
|
538
574
|
|
|
539
575
|
|
|
540
576
|
def draw_houses_cusps_and_text_number(
|
|
541
577
|
r: Union[int, float],
|
|
542
|
-
|
|
578
|
+
first_subject_houses_list: list[KerykeionPointModel],
|
|
543
579
|
standard_house_cusp_color: str,
|
|
544
580
|
first_house_color: str,
|
|
545
581
|
tenth_house_color: str,
|
|
@@ -548,15 +584,15 @@ def draw_houses_cusps_and_text_number(
|
|
|
548
584
|
c1: Union[int, float],
|
|
549
585
|
c3: Union[int, float],
|
|
550
586
|
chart_type: ChartType,
|
|
551
|
-
|
|
552
|
-
transit_house_cusp_color: str = None,
|
|
587
|
+
second_subject_houses_list: Union[list[KerykeionPointModel], None] = None,
|
|
588
|
+
transit_house_cusp_color: Union[str, None] = None,
|
|
553
589
|
) -> str:
|
|
554
590
|
"""
|
|
555
591
|
Draws the houses cusps and text numbers for a given chart type.
|
|
556
592
|
|
|
557
593
|
Parameters:
|
|
558
594
|
- r: Radius of the chart.
|
|
559
|
-
-
|
|
595
|
+
- first_subject_houses_list: List of house for the first subject.
|
|
560
596
|
- standard_house_cusp_color: Default color for house cusps.
|
|
561
597
|
- first_house_color: Color for the first house cusp.
|
|
562
598
|
- tenth_house_color: Color for the tenth house cusp.
|
|
@@ -565,25 +601,22 @@ def draw_houses_cusps_and_text_number(
|
|
|
565
601
|
- c1: Offset for the first subject.
|
|
566
602
|
- c3: Offset for the third subject.
|
|
567
603
|
- chart_type: Type of the chart (e.g., Transit, Synastry).
|
|
568
|
-
-
|
|
604
|
+
- second_subject_houses_list: List of house for the second subject (optional).
|
|
569
605
|
- transit_house_cusp_color: Color for transit house cusps (optional).
|
|
570
606
|
|
|
571
607
|
Returns:
|
|
572
608
|
- A string containing the SVG path for the houses cusps and text numbers.
|
|
573
609
|
"""
|
|
574
|
-
if chart_type in ["Transit", "Synastry"]:
|
|
575
|
-
if second_subject_houses_list_ut is None or transit_house_cusp_color is None:
|
|
576
|
-
raise KerykeionException("second_subject_houses_list_ut or transit_house_cusp_color is None")
|
|
577
610
|
|
|
578
611
|
path = ""
|
|
579
612
|
xr = 12
|
|
580
613
|
|
|
581
614
|
for i in range(xr):
|
|
582
615
|
# Determine offsets based on chart type
|
|
583
|
-
dropin, roff, t_roff = (160, 72, 36) if chart_type in ["Transit", "Synastry"] else (c3, c1,
|
|
616
|
+
dropin, roff, t_roff = (160, 72, 36) if chart_type in ["Transit", "Synastry"] else (c3, c1, False)
|
|
584
617
|
|
|
585
618
|
# Calculate the offset for the current house cusp
|
|
586
|
-
offset = (int(
|
|
619
|
+
offset = (int(first_subject_houses_list[int(xr / 2)].abs_pos) / -1) + int(first_subject_houses_list[i].abs_pos)
|
|
587
620
|
|
|
588
621
|
# Calculate the coordinates for the house cusp lines
|
|
589
622
|
x1 = sliceToX(0, (r - dropin), offset) + dropin
|
|
@@ -594,7 +627,7 @@ def draw_houses_cusps_and_text_number(
|
|
|
594
627
|
# Calculate the text offset for the house number
|
|
595
628
|
next_index = (i + 1) % xr
|
|
596
629
|
text_offset = offset + int(
|
|
597
|
-
degreeDiff(
|
|
630
|
+
degreeDiff(first_subject_houses_list[next_index].abs_pos, first_subject_houses_list[i].abs_pos) / 2
|
|
598
631
|
)
|
|
599
632
|
|
|
600
633
|
# Determine the line color based on the house index
|
|
@@ -603,9 +636,12 @@ def draw_houses_cusps_and_text_number(
|
|
|
603
636
|
)
|
|
604
637
|
|
|
605
638
|
if chart_type in ["Transit", "Synastry"]:
|
|
639
|
+
if second_subject_houses_list is None or transit_house_cusp_color is None:
|
|
640
|
+
raise KerykeionException("second_subject_houses_list_ut or transit_house_cusp_color is None")
|
|
641
|
+
|
|
606
642
|
# Calculate the offset for the second subject's house cusp
|
|
607
|
-
zeropoint = 360 -
|
|
608
|
-
t_offset = (zeropoint +
|
|
643
|
+
zeropoint = 360 - first_subject_houses_list[6].abs_pos
|
|
644
|
+
t_offset = (zeropoint + second_subject_houses_list[i].abs_pos) % 360
|
|
609
645
|
|
|
610
646
|
# Calculate the coordinates for the second subject's house cusp lines
|
|
611
647
|
t_x1 = sliceToX(0, (r - t_roff), t_offset) + t_roff
|
|
@@ -615,7 +651,7 @@ def draw_houses_cusps_and_text_number(
|
|
|
615
651
|
|
|
616
652
|
# Calculate the text offset for the second subject's house number
|
|
617
653
|
t_text_offset = t_offset + int(
|
|
618
|
-
degreeDiff(
|
|
654
|
+
degreeDiff(second_subject_houses_list[next_index].abs_pos, second_subject_houses_list[i].abs_pos) / 2
|
|
619
655
|
)
|
|
620
656
|
t_linecolor = linecolor if i in [0, 9, 6, 3] else transit_house_cusp_color
|
|
621
657
|
xtext = sliceToX(0, (r - 8), t_text_offset) + 8
|
|
@@ -624,7 +660,7 @@ def draw_houses_cusps_and_text_number(
|
|
|
624
660
|
# Add the house number text for the second subject
|
|
625
661
|
fill_opacity = "0" if chart_type == "Transit" else ".4"
|
|
626
662
|
path += f'<g kr:node="HouseNumber">'
|
|
627
|
-
path += f'<text style="fill:
|
|
663
|
+
path += f'<text style="fill: var(--kerykeion-chart-color-house-number); fill-opacity: {fill_opacity}; font-size: 14px"><tspan x="{xtext - 3}" y="{ytext + 3}">{i + 1}</tspan></text>'
|
|
628
664
|
path += f"</g>"
|
|
629
665
|
|
|
630
666
|
# Add the house cusp line for the second subject
|
|
@@ -645,17 +681,17 @@ def draw_houses_cusps_and_text_number(
|
|
|
645
681
|
|
|
646
682
|
# Add the house number text for the first subject
|
|
647
683
|
path += f'<g kr:node="HouseNumber">'
|
|
648
|
-
path += f'<text style="fill:
|
|
684
|
+
path += f'<text style="fill: var(--kerykeion-chart-color-house-number); fill-opacity: .6; font-size: 14px"><tspan x="{xtext - 3}" y="{ytext + 3}">{i + 1}</tspan></text>'
|
|
649
685
|
path += f"</g>"
|
|
650
686
|
|
|
651
687
|
return path
|
|
652
688
|
|
|
653
689
|
|
|
654
|
-
def
|
|
690
|
+
def draw_transit_aspect_list(
|
|
655
691
|
grid_title: str,
|
|
656
692
|
aspects_list: Union[list[AspectModel], list[dict]],
|
|
657
|
-
celestial_point_language: KerykeionLanguageCelestialPointModel,
|
|
658
|
-
aspects_settings: KerykeionSettingsAspectModel,
|
|
693
|
+
celestial_point_language: Union[KerykeionLanguageCelestialPointModel, dict],
|
|
694
|
+
aspects_settings: Union[KerykeionSettingsAspectModel, dict],
|
|
659
695
|
) -> str:
|
|
660
696
|
"""
|
|
661
697
|
Generates the SVG output for the aspect transit grid.
|
|
@@ -669,10 +705,16 @@ def draw_aspect_transit_grid(
|
|
|
669
705
|
Returns:
|
|
670
706
|
- A string containing the SVG path data for the aspect transit grid.
|
|
671
707
|
"""
|
|
708
|
+
|
|
709
|
+
if isinstance(celestial_point_language, dict):
|
|
710
|
+
celestial_point_language = KerykeionLanguageCelestialPointModel(**celestial_point_language)
|
|
711
|
+
|
|
712
|
+
if isinstance(aspects_settings, dict):
|
|
713
|
+
aspects_settings = KerykeionSettingsAspectModel(**aspects_settings)
|
|
672
714
|
|
|
673
715
|
# If not instance of AspectModel, convert to AspectModel
|
|
674
716
|
if isinstance(aspects_list[0], dict):
|
|
675
|
-
aspects_list = [AspectModel(**aspect) for aspect in aspects_list]
|
|
717
|
+
aspects_list = [AspectModel(**aspect) for aspect in aspects_list] # type: ignore
|
|
676
718
|
|
|
677
719
|
line = 0
|
|
678
720
|
nl = 0
|
|
@@ -714,13 +756,13 @@ def draw_aspect_transit_grid(
|
|
|
714
756
|
inner_path += f"</g>"
|
|
715
757
|
|
|
716
758
|
# difference in degrees
|
|
717
|
-
inner_path += f'<text y="8" x="45" style="fill
|
|
759
|
+
inner_path += f'<text y="8" x="45" style="fill: var(--kerykeion-chart-color-paper-0); font-size: 10px;">{convert_decimal_to_degree_string(aspects_list[i]["orbit"])}</text>'
|
|
718
760
|
# line
|
|
719
761
|
inner_path += f"</g>"
|
|
720
762
|
line = line + 14
|
|
721
763
|
|
|
722
|
-
out = f'<g style="transform: translate(47%,
|
|
723
|
-
out += f'<text y="-15" x="0" style="fill
|
|
764
|
+
out = f'<g style="transform: translate(47%, 59%) scale({scale})">'
|
|
765
|
+
out += f'<text y="-15" x="0" style="fill: var(--kerykeion-chart-color-paper-0); font-size: 14px;">{grid_title}:</text>'
|
|
724
766
|
out += inner_path
|
|
725
767
|
out += "</g>"
|
|
726
768
|
|
|
@@ -729,10 +771,7 @@ def draw_aspect_transit_grid(
|
|
|
729
771
|
|
|
730
772
|
def draw_moon_phase(
|
|
731
773
|
degrees_between_sun_and_moon: float,
|
|
732
|
-
latitude: float
|
|
733
|
-
lunar_phase_outline_color: str = "#000000",
|
|
734
|
-
dark_color: str = "#000000",
|
|
735
|
-
light_color: str = "#ffffff",
|
|
774
|
+
latitude: float
|
|
736
775
|
) -> str:
|
|
737
776
|
"""
|
|
738
777
|
Draws the moon phase based on the degrees between the sun and the moon.
|
|
@@ -750,8 +789,6 @@ def draw_moon_phase(
|
|
|
750
789
|
deg = degrees_between_sun_and_moon
|
|
751
790
|
|
|
752
791
|
# Initialize variables for lunar phase properties
|
|
753
|
-
fill_color_foreground = None
|
|
754
|
-
fill_color_background = None
|
|
755
792
|
circle_center_x = None
|
|
756
793
|
circle_radius = None
|
|
757
794
|
|
|
@@ -762,8 +799,6 @@ def draw_moon_phase(
|
|
|
762
799
|
max_radius = max_radius * max_radius
|
|
763
800
|
circle_center_x = 20.0 + (deg / 90.0) * (max_radius + 10.0)
|
|
764
801
|
circle_radius = 10.0 + (deg / 90.0) * max_radius
|
|
765
|
-
fill_color_foreground = dark_color
|
|
766
|
-
fill_color_background = light_color
|
|
767
802
|
|
|
768
803
|
elif deg < 180.0:
|
|
769
804
|
max_radius = 180.0 - deg
|
|
@@ -771,8 +806,6 @@ def draw_moon_phase(
|
|
|
771
806
|
max_radius = max_radius * max_radius
|
|
772
807
|
circle_center_x = 20.0 + ((deg - 90.0) / 90.0 * (max_radius + 10.0)) - (max_radius + 10.0)
|
|
773
808
|
circle_radius = 10.0 + max_radius - ((deg - 90.0) / 90.0 * max_radius)
|
|
774
|
-
fill_color_foreground = light_color
|
|
775
|
-
fill_color_background = dark_color
|
|
776
809
|
|
|
777
810
|
elif deg < 270.0:
|
|
778
811
|
max_radius = deg - 180.0
|
|
@@ -780,8 +813,6 @@ def draw_moon_phase(
|
|
|
780
813
|
max_radius = max_radius * max_radius
|
|
781
814
|
circle_center_x = 20.0 + ((deg - 180.0) / 90.0 * (max_radius + 10.0))
|
|
782
815
|
circle_radius = 10.0 + ((deg - 180.0) / 90.0 * max_radius)
|
|
783
|
-
fill_color_foreground = light_color
|
|
784
|
-
fill_color_background = dark_color
|
|
785
816
|
|
|
786
817
|
elif deg < 361.0:
|
|
787
818
|
max_radius = 360.0 - deg
|
|
@@ -789,8 +820,6 @@ def draw_moon_phase(
|
|
|
789
820
|
max_radius = max_radius * max_radius
|
|
790
821
|
circle_center_x = 20.0 + ((deg - 270.0) / 90.0 * (max_radius + 10.0)) - (max_radius + 10.0)
|
|
791
822
|
circle_radius = 10.0 + max_radius - ((deg - 270.0) / 90.0 * max_radius)
|
|
792
|
-
fill_color_foreground = dark_color
|
|
793
|
-
fill_color_background = light_color
|
|
794
823
|
|
|
795
824
|
else:
|
|
796
825
|
raise KerykeionException(f"Invalid degree value: {deg}")
|
|
@@ -802,8 +831,253 @@ def draw_moon_phase(
|
|
|
802
831
|
# Return the SVG element as a string
|
|
803
832
|
return (
|
|
804
833
|
f'<g transform="rotate({lunar_phase_rotate} 20 10)">'
|
|
805
|
-
f' <
|
|
806
|
-
f'
|
|
807
|
-
f'
|
|
808
|
-
f
|
|
834
|
+
f' <defs>'
|
|
835
|
+
f' <clipPath id="moonPhaseCutOffCircle">'
|
|
836
|
+
f' <circle cx="20" cy="10" r="10" />'
|
|
837
|
+
f' </clipPath>'
|
|
838
|
+
f' </defs>'
|
|
839
|
+
f' <circle cx="20" cy="10" r="10" style="fill: var(--kerykeion-chart-color-lunar-phase-0)" />'
|
|
840
|
+
f' <circle cx="{circle_center_x}" cy="10" r="{circle_radius}" style="fill: var(--kerykeion-chart-color-lunar-phase-1)" clip-path="url(#moonPhaseCutOffCircle)" />'
|
|
841
|
+
f' <circle cx="20" cy="10" r="10" style="fill: none; stroke: var(--kerykeion-chart-color-lunar-phase-0); stroke-width: 0.5px; stroke-opacity: 0.5" />'
|
|
842
|
+
f'</g>'
|
|
843
|
+
)
|
|
844
|
+
|
|
845
|
+
|
|
846
|
+
def draw_house_grid(
|
|
847
|
+
main_subject_houses_list: list[KerykeionPointModel],
|
|
848
|
+
chart_type: ChartType,
|
|
849
|
+
secondary_subject_houses_list: Union[list[KerykeionPointModel], None] = None,
|
|
850
|
+
text_color: str = "#000000",
|
|
851
|
+
house_cusp_generale_name_label: str = "Cusp",
|
|
852
|
+
) -> str:
|
|
853
|
+
"""
|
|
854
|
+
Generate SVG code for a grid of astrological houses.
|
|
855
|
+
|
|
856
|
+
Parameters:
|
|
857
|
+
- main_houses (list[KerykeionPointModel]): List of houses for the main subject.
|
|
858
|
+
- chart_type (ChartType): Type of the chart (e.g., Synastry, Transit).
|
|
859
|
+
- secondary_houses (list[KerykeionPointModel], optional): List of houses for the secondary subject.
|
|
860
|
+
- text_color (str): Color of the text.
|
|
861
|
+
- cusp_label (str): Label for the house cusp.
|
|
862
|
+
|
|
863
|
+
Returns:
|
|
864
|
+
- str: The SVG code for the grid of houses.
|
|
865
|
+
"""
|
|
866
|
+
|
|
867
|
+
if chart_type in ["Synastry", "Transit"] and secondary_subject_houses_list is None:
|
|
868
|
+
raise KerykeionException("secondary_houses is None")
|
|
869
|
+
|
|
870
|
+
svg_output = '<g transform="translate(610,-20)">'
|
|
871
|
+
|
|
872
|
+
line_increment = 10
|
|
873
|
+
for i, house in enumerate(main_subject_houses_list):
|
|
874
|
+
cusp_number = f"  {i + 1}" if i < 9 else str(i + 1)
|
|
875
|
+
svg_output += (
|
|
876
|
+
f'<g transform="translate(0,{line_increment})">'
|
|
877
|
+
f'<text text-anchor="end" x="40" style="fill:{text_color}; font-size: 10px;">{house_cusp_generale_name_label} {cusp_number}:</text>'
|
|
878
|
+
f'<g transform="translate(40,-8)"><use transform="scale(0.3)" xlink:href="#{house["sign"]}" /></g>'
|
|
879
|
+
f'<text x="53" style="fill:{text_color}; font-size: 10px;"> {convert_decimal_to_degree_string(house["position"])}</text>'
|
|
880
|
+
f'</g>'
|
|
881
|
+
)
|
|
882
|
+
line_increment += 14
|
|
883
|
+
|
|
884
|
+
svg_output += "</g>"
|
|
885
|
+
|
|
886
|
+
if chart_type == "Synastry":
|
|
887
|
+
svg_output += '<!-- Synastry Houses -->'
|
|
888
|
+
svg_output += '<g transform="translate(850, -20)">'
|
|
889
|
+
line_increment = 10
|
|
890
|
+
|
|
891
|
+
for i, house in enumerate(secondary_subject_houses_list): # type: ignore
|
|
892
|
+
cusp_number = f"  {i + 1}" if i < 9 else str(i + 1)
|
|
893
|
+
svg_output += (
|
|
894
|
+
f'<g transform="translate(0,{line_increment})">'
|
|
895
|
+
f'<text text-anchor="end" x="40" style="fill:{text_color}; font-size: 10px;">{house_cusp_generale_name_label} {cusp_number}:</text>'
|
|
896
|
+
f'<g transform="translate(40,-8)"><use transform="scale(0.3)" xlink:href="#{house["sign"]}" /></g>'
|
|
897
|
+
f'<text x="53" style="fill:{text_color}; font-size: 10px;"> {convert_decimal_to_degree_string(house["position"])}</text>'
|
|
898
|
+
f'</g>'
|
|
899
|
+
)
|
|
900
|
+
line_increment += 14
|
|
901
|
+
|
|
902
|
+
svg_output += "</g>"
|
|
903
|
+
|
|
904
|
+
return svg_output
|
|
905
|
+
|
|
906
|
+
|
|
907
|
+
def draw_planet_grid(
|
|
908
|
+
planets_and_houses_grid_title: str,
|
|
909
|
+
subject_name: str,
|
|
910
|
+
available_kerykeion_celestial_points: list[KerykeionPointModel],
|
|
911
|
+
chart_type: ChartType,
|
|
912
|
+
celestial_point_language: KerykeionLanguageCelestialPointModel,
|
|
913
|
+
second_subject_name: Union[str, None] = None,
|
|
914
|
+
second_subject_available_kerykeion_celestial_points: Union[list[KerykeionPointModel], None] = None,
|
|
915
|
+
text_color: str = "#000000",
|
|
916
|
+
) -> str:
|
|
917
|
+
"""
|
|
918
|
+
Draws the planet grid for the given celestial points and chart type.
|
|
919
|
+
|
|
920
|
+
Args:
|
|
921
|
+
planets_and_houses_grid_title (str): Title of the grid.
|
|
922
|
+
subject_name (str): Name of the subject.
|
|
923
|
+
available_kerykeion_celestial_points (list[KerykeionPointModel]): List of celestial points for the subject.
|
|
924
|
+
chart_type (ChartType): Type of the chart.
|
|
925
|
+
celestial_point_language (KerykeionLanguageCelestialPointModel): Language model for celestial points.
|
|
926
|
+
second_subject_name (str, optional): Name of the second subject. Defaults to None.
|
|
927
|
+
second_subject_available_kerykeion_celestial_points (list[KerykeionPointModel], optional): List of celestial points for the second subject. Defaults to None.
|
|
928
|
+
text_color (str, optional): Color of the text. Defaults to "#000000".
|
|
929
|
+
|
|
930
|
+
Returns:
|
|
931
|
+
str: The SVG output for the planet grid.
|
|
932
|
+
"""
|
|
933
|
+
line_height = 10
|
|
934
|
+
offset = 0
|
|
935
|
+
offset_between_lines = 14
|
|
936
|
+
|
|
937
|
+
svg_output = (
|
|
938
|
+
f'<g transform="translate(510,-20)">'
|
|
939
|
+
f'<g transform="translate(140, -15)">'
|
|
940
|
+
f'<text text-anchor="end" style="fill:{text_color}; font-size: 14px;">{planets_and_houses_grid_title} {subject_name}:</text>'
|
|
941
|
+
f'</g>'
|
|
809
942
|
)
|
|
943
|
+
|
|
944
|
+
end_of_line = "</g>"
|
|
945
|
+
|
|
946
|
+
for i, planet in enumerate(available_kerykeion_celestial_points):
|
|
947
|
+
if i == 27:
|
|
948
|
+
line_height = 10
|
|
949
|
+
offset = -120
|
|
950
|
+
|
|
951
|
+
decoded_name = get_decoded_kerykeion_celestial_point_name(planet["name"], celestial_point_language)
|
|
952
|
+
svg_output += (
|
|
953
|
+
f'<g transform="translate({offset},{line_height})">'
|
|
954
|
+
f'<text text-anchor="end" style="fill:{text_color}; font-size: 10px;">{decoded_name}</text>'
|
|
955
|
+
f'<g transform="translate(5,-8)"><use transform="scale(0.4)" xlink:href="#{planet["name"]}" /></g>'
|
|
956
|
+
f'<text text-anchor="start" x="19" style="fill:{text_color}; font-size: 10px;">{convert_decimal_to_degree_string(planet["position"])}</text>'
|
|
957
|
+
f'<g transform="translate(60,-8)"><use transform="scale(0.3)" xlink:href="#{planet["sign"]}" /></g>'
|
|
958
|
+
)
|
|
959
|
+
|
|
960
|
+
if planet["retrograde"]:
|
|
961
|
+
svg_output += '<g transform="translate(74,-6)"><use transform="scale(.5)" xlink:href="#retrograde" /></g>'
|
|
962
|
+
|
|
963
|
+
svg_output += end_of_line
|
|
964
|
+
line_height += offset_between_lines
|
|
965
|
+
|
|
966
|
+
if chart_type in ["Transit", "Synastry"]:
|
|
967
|
+
if second_subject_available_kerykeion_celestial_points is None:
|
|
968
|
+
raise KerykeionException("second_subject_available_kerykeion_celestial_points is None")
|
|
969
|
+
|
|
970
|
+
if chart_type == "Transit":
|
|
971
|
+
svg_output += (
|
|
972
|
+
f'<g transform="translate(320, -15)">'
|
|
973
|
+
f'<text text-anchor="end" style="fill:{text_color}; font-size: 14px;">{second_subject_name}:</text>'
|
|
974
|
+
)
|
|
975
|
+
else:
|
|
976
|
+
svg_output += (
|
|
977
|
+
f'<g transform="translate(380, -15)">'
|
|
978
|
+
f'<text text-anchor="end" style="fill:{text_color}; font-size: 14px;">{planets_and_houses_grid_title} {second_subject_name}:</text>'
|
|
979
|
+
)
|
|
980
|
+
|
|
981
|
+
svg_output += end_of_line
|
|
982
|
+
|
|
983
|
+
second_line_height = 10
|
|
984
|
+
second_offset = 250
|
|
985
|
+
|
|
986
|
+
for i, t_planet in enumerate(second_subject_available_kerykeion_celestial_points):
|
|
987
|
+
if i == 27:
|
|
988
|
+
second_line_height = 10
|
|
989
|
+
second_offset = -120
|
|
990
|
+
|
|
991
|
+
second_decoded_name = get_decoded_kerykeion_celestial_point_name(t_planet["name"], celestial_point_language)
|
|
992
|
+
svg_output += (
|
|
993
|
+
f'<g transform="translate({second_offset},{second_line_height})">'
|
|
994
|
+
f'<text text-anchor="end" style="fill:{text_color}; font-size: 10px;">{second_decoded_name}</text>'
|
|
995
|
+
f'<g transform="translate(5,-8)"><use transform="scale(0.4)" xlink:href="#{t_planet["name"]}" /></g>'
|
|
996
|
+
f'<text text-anchor="start" x="19" style="fill:{text_color}; font-size: 10px;">{convert_decimal_to_degree_string(t_planet["position"])}</text>'
|
|
997
|
+
f'<g transform="translate(60,-8)"><use transform="scale(0.3)" xlink:href="#{t_planet["sign"]}" /></g>'
|
|
998
|
+
)
|
|
999
|
+
|
|
1000
|
+
if t_planet["retrograde"]:
|
|
1001
|
+
svg_output += '<g transform="translate(74,-6)"><use transform="scale(.5)" xlink:href="#retrograde" /></g>'
|
|
1002
|
+
|
|
1003
|
+
svg_output += end_of_line
|
|
1004
|
+
second_line_height += offset_between_lines
|
|
1005
|
+
|
|
1006
|
+
svg_output += end_of_line
|
|
1007
|
+
return svg_output
|
|
1008
|
+
|
|
1009
|
+
|
|
1010
|
+
def draw_transit_aspect_grid(
|
|
1011
|
+
stroke_color: str,
|
|
1012
|
+
available_planets: list,
|
|
1013
|
+
aspects: list,
|
|
1014
|
+
x_indent: int = 50,
|
|
1015
|
+
y_indent: int = 250,
|
|
1016
|
+
box_size: int = 14
|
|
1017
|
+
) -> str:
|
|
1018
|
+
"""
|
|
1019
|
+
Draws the aspect grid for the given planets and aspects. The default args value are specific for a stand alone
|
|
1020
|
+
aspect grid.
|
|
1021
|
+
|
|
1022
|
+
Args:
|
|
1023
|
+
stroke_color (str): The color of the stroke.
|
|
1024
|
+
available_planets (list): List of all planets. Only planets with "is_active" set to True will be used.
|
|
1025
|
+
aspects (list): List of aspects.
|
|
1026
|
+
x_indent (int): The initial x-coordinate starting point.
|
|
1027
|
+
y_indent (int): The initial y-coordinate starting point.
|
|
1028
|
+
|
|
1029
|
+
Returns:
|
|
1030
|
+
str: SVG string representing the aspect grid.
|
|
1031
|
+
"""
|
|
1032
|
+
svg_output = ""
|
|
1033
|
+
style = f"stroke:{stroke_color}; stroke-width: 1px; fill:none"
|
|
1034
|
+
x_start = x_indent
|
|
1035
|
+
y_start = y_indent
|
|
1036
|
+
|
|
1037
|
+
# Filter active planets
|
|
1038
|
+
active_planets = [planet for planet in available_planets if planet.is_active]
|
|
1039
|
+
|
|
1040
|
+
# Reverse the list of active planets for the first iteration
|
|
1041
|
+
reversed_planets = active_planets[::-1]
|
|
1042
|
+
for index, planet_a in enumerate(reversed_planets):
|
|
1043
|
+
# Draw the grid box for the planet
|
|
1044
|
+
svg_output += f'<rect x="{x_start}" y="{y_start}" width="{box_size}" height="{box_size}" style="{style}"/>'
|
|
1045
|
+
svg_output += f'<use transform="scale(0.4)" x="{(x_start + 2) * 2.5}" y="{(y_start + 1) * 2.5}" xlink:href="#{planet_a["name"]}" />'
|
|
1046
|
+
x_start += box_size
|
|
1047
|
+
|
|
1048
|
+
x_start = x_indent - box_size
|
|
1049
|
+
y_start = y_indent - box_size
|
|
1050
|
+
|
|
1051
|
+
for index, planet_a in enumerate(reversed_planets):
|
|
1052
|
+
# Draw the grid box for the planet
|
|
1053
|
+
svg_output += f'<rect x="{x_start}" y="{y_start}" width="{box_size}" height="{box_size}" style="{style}"/>'
|
|
1054
|
+
svg_output += f'<use transform="scale(0.4)" x="{(x_start + 2) * 2.5}" y="{(y_start + 1) * 2.5}" xlink:href="#{planet_a["name"]}" />'
|
|
1055
|
+
y_start -= box_size
|
|
1056
|
+
|
|
1057
|
+
x_start = x_indent
|
|
1058
|
+
y_start = y_indent
|
|
1059
|
+
y_start = y_start - box_size
|
|
1060
|
+
|
|
1061
|
+
for index, planet_a in enumerate(reversed_planets):
|
|
1062
|
+
# Draw the grid box for the planet
|
|
1063
|
+
svg_output += f'<rect x="{x_start}" y="{y_start}" width="{box_size}" height="{box_size}" style="{style}"/>'
|
|
1064
|
+
|
|
1065
|
+
# Update the starting coordinates for the next box
|
|
1066
|
+
y_start -= box_size
|
|
1067
|
+
|
|
1068
|
+
# Coordinates for the aspect symbols
|
|
1069
|
+
x_aspect = x_start
|
|
1070
|
+
y_aspect = y_start + box_size
|
|
1071
|
+
|
|
1072
|
+
# Iterate over the remaining planets
|
|
1073
|
+
for planet_b in reversed_planets:
|
|
1074
|
+
# Draw the grid box for the aspect
|
|
1075
|
+
svg_output += f'<rect x="{x_aspect}" y="{y_aspect}" width="{box_size}" height="{box_size}" style="{style}"/>'
|
|
1076
|
+
x_aspect += box_size
|
|
1077
|
+
|
|
1078
|
+
# Check for aspects between the planets
|
|
1079
|
+
for aspect in aspects:
|
|
1080
|
+
if (aspect["p1"] == planet_a["id"] and aspect["p2"] == planet_b["id"]):
|
|
1081
|
+
svg_output += f'<use x="{x_aspect - box_size + 1}" y="{y_aspect + 1}" xlink:href="#orb{aspect["aspect_degrees"]}" />'
|
|
1082
|
+
|
|
1083
|
+
return svg_output
|