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.

Files changed (34) hide show
  1. kerykeion/__init__.py +2 -1
  2. kerykeion/aspects/aspects_utils.py +33 -117
  3. kerykeion/aspects/natal_aspects.py +26 -25
  4. kerykeion/aspects/synastry_aspects.py +25 -27
  5. kerykeion/astrological_subject.py +145 -189
  6. kerykeion/charts/charts_utils.py +400 -126
  7. kerykeion/charts/draw_planets.py +407 -0
  8. kerykeion/charts/kerykeion_chart_svg.py +502 -770
  9. kerykeion/charts/templates/aspect_grid_only.xml +452 -0
  10. kerykeion/charts/templates/chart.xml +39 -39
  11. kerykeion/charts/templates/wheel_only.xml +499 -0
  12. kerykeion/charts/themes/classic.css +82 -0
  13. kerykeion/charts/themes/dark-high-contrast.css +121 -0
  14. kerykeion/charts/themes/dark.css +121 -0
  15. kerykeion/charts/themes/light.css +117 -0
  16. kerykeion/ephemeris_data.py +22 -18
  17. kerykeion/kr_types/chart_types.py +3 -7
  18. kerykeion/kr_types/kr_literals.py +10 -1
  19. kerykeion/kr_types/kr_models.py +33 -8
  20. kerykeion/kr_types/settings_models.py +1 -10
  21. kerykeion/relationship_score/__init__.py +2 -0
  22. kerykeion/relationship_score/relationship_score.py +175 -0
  23. kerykeion/relationship_score/relationship_score_factory.py +275 -0
  24. kerykeion/report.py +6 -3
  25. kerykeion/settings/kerykeion_settings.py +6 -1
  26. kerykeion/settings/kr.config.json +238 -98
  27. kerykeion/utilities.py +116 -215
  28. {kerykeion-4.14.2.dist-info → kerykeion-4.18.0.dist-info}/METADATA +40 -10
  29. kerykeion-4.18.0.dist-info/RECORD +42 -0
  30. kerykeion/relationship_score.py +0 -205
  31. kerykeion-4.14.2.dist-info/RECORD +0 -33
  32. {kerykeion-4.14.2.dist-info → kerykeion-4.18.0.dist-info}/LICENSE +0 -0
  33. {kerykeion-4.14.2.dist-info → kerykeion-4.18.0.dist-info}/WHEEL +0 -0
  34. {kerykeion-4.14.2.dist-info → kerykeion-4.18.0.dist-info}/entry_points.txt +0 -0
@@ -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": "aries", "element": "fire"}
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: "aries".
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
- out = ""
246
- out += 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"]}">'
247
- out += f'<line class="aspect" x1="{x1}" y1="{y1}" x2="{x2}" y2="{y2}" style="stroke: {color}; stroke-width: 1; stroke-opacity: .9;"/>'
248
- out += f"</g>"
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
- out = '<g transform="translate(-30,79)">'
286
- out += f'<text y="0" style="fill:#ff6600; font-size: 10px;">{fire_label} {str(fire_percentage)}%</text>'
287
- out += f'<text y="12" style="fill:#6a2d04; font-size: 10px;">{earth_label} {str(earth_percentage)}%</text>'
288
- out += f'<text y="24" style="fill:#6f76d1; font-size: 10px;">{air_label} {str(air_percentage)}%</text>'
289
- out += f'<text y="36" style="fill:#630e73; font-size: 10px;">{water_label} {str(water_percentage)}%</text>'
290
- out += "</g>"
291
-
292
- return out
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, type="3") -> str:
325
+ def convert_decimal_to_degree_string(dec: float, format_type: Literal["1", "2", "3"] = "3") -> str:
296
326
  """
297
- Coverts decimal float to degrees in format a°b'c".
327
+ Converts a decimal float to a degrees string in the specified format.
298
328
 
299
329
  Args:
300
- - dec (float): decimal float
301
- - type (str): type of format:
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 a°b'c"
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}&#176;{b:02d}&#39;{c:02d}&#34;"
319
- elif type == "2":
320
- out = f"{a:02d}&#176;{b_rounded:02d}&#39;"
321
- elif type == "1":
322
- out = f"{a:02d}&#176;"
323
- else:
324
- raise KerykeionException(f"Wrong type: {type}, it must be 1, 2 or 3.")
325
341
 
326
- return str(out)
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, None] = None,
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(stroke_color: str, available_planets_list: list, aspects_list: list) -> str:
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
- - stroke_color (str): The color of the stroke.
496
- - available_planets_list (list): List of all the planets, they will be actually filtered to so if they have
497
- the "is_active" key set to True inside the function to have the correct list of just the active planets.
498
- - aspects_list (list): List of aspects.
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
- out = ""
503
- style = f"stroke:{stroke_color}; stroke-width: 1px; stroke-opacity:.6; fill:none"
504
- xindent = 380
505
- yindent = 468
506
- box = 14
507
- counter = 0
508
-
509
- actual_planets = []
510
- for planet in available_planets_list:
511
- if planet.is_active:
512
- actual_planets.append(planet)
513
-
514
- first_iteration_revers_planets = actual_planets[::-1]
515
- for index, a in enumerate(first_iteration_revers_planets):
516
- counter += 1
517
- out += f'<rect x="{xindent}" y="{yindent}" width="{box}" height="{box}" style="{style}"/>'
518
- out += f'<use transform="scale(0.4)" x="{(xindent+2)*2.5}" y="{(yindent+1)*2.5}" xlink:href="#{a["name"]}" />'
519
-
520
- xindent = xindent + box
521
- yindent = yindent - box
522
-
523
- xorb = xindent
524
- yorb = yindent + box
525
-
526
- second_iteration_revers_planets = first_iteration_revers_planets[index + 1 :]
527
- for b in second_iteration_revers_planets:
528
- out += f'<rect x="{xorb}" y="{yorb}" width="{box}" height="{box}" style="{style}"/>'
529
- xorb = xorb + box
530
-
531
- for aspect in aspects_list:
532
- if (aspect["p1"] == a["id"] and aspect["p2"] == b["id"]) or (
533
- aspect["p1"] == b["id"] and aspect["p2"] == a["id"]
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
- out += f'<use x="{xorb-box+1}" y="{yorb+1}" xlink:href="#orb{aspect["aspect_degrees"]}" />'
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 out
573
+ return svg_output
538
574
 
539
575
 
540
576
  def draw_houses_cusps_and_text_number(
541
577
  r: Union[int, float],
542
- first_subject_houses_list_ut: list,
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
- second_subject_houses_list_ut: list = None,
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
- - first_subject_houses_list_ut: List of house cusps for the first subject.
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
- - second_subject_houses_list_ut: List of house cusps for the second subject (optional).
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, None)
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(first_subject_houses_list_ut[int(xr / 2)]) / -1) + int(first_subject_houses_list_ut[i])
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(first_subject_houses_list_ut[next_index], first_subject_houses_list_ut[i]) / 2
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 - first_subject_houses_list_ut[6]
608
- t_offset = (zeropoint + second_subject_houses_list_ut[i]) % 360
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(second_subject_houses_list_ut[next_index], second_subject_houses_list_ut[i]) / 2
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: #00f; fill-opacity: {fill_opacity}; font-size: 14px"><tspan x="{xtext - 3}" y="{ytext + 3}">{i + 1}</tspan></text>'
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: #f00; fill-opacity: .6; font-size: 14px"><tspan x="{xtext - 3}" y="{ytext + 3}">{i + 1}</tspan></text>'
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 draw_aspect_transit_grid(
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:#000000; font-size: 10px;">{convert_decimal_to_degree_string(aspects_list[i]["orbit"])}</text>'
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%, 61%) scale({scale})">'
723
- out += f'<text y="-15" x="0" style="fill:#000000; font-size: 14px;">{grid_title}:</text>'
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' <circle cx="20" cy="10" r="10" style="fill: {fill_color_background}" />'
806
- f' <circle cx="{circle_center_x}" cy="10" r="{circle_radius}" style="fill: {fill_color_foreground}" clip-path="url(#moonPhaseCutOffCircle)" />'
807
- f' <circle cx="20" cy="10" r="10" style="fill: none; stroke: {lunar_phase_outline_color}; stroke-width: 0.5px; stroke-opacity: 0.5" />'
808
- f"</g>"
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"&#160;&#160;{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"&#160;&#160;{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