kerykeion 4.12.3__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 +3 -1
- kerykeion/aspects/aspects_utils.py +40 -123
- kerykeion/aspects/natal_aspects.py +34 -25
- kerykeion/aspects/synastry_aspects.py +34 -28
- kerykeion/astrological_subject.py +199 -196
- kerykeion/charts/charts_utils.py +701 -62
- kerykeion/charts/draw_planets.py +407 -0
- kerykeion/charts/kerykeion_chart_svg.py +534 -1140
- kerykeion/charts/templates/aspect_grid_only.xml +452 -0
- kerykeion/charts/templates/chart.xml +88 -70
- 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/enums.py +1 -0
- kerykeion/ephemeris_data.py +178 -0
- kerykeion/fetch_geonames.py +2 -3
- kerykeion/kr_types/chart_types.py +6 -16
- kerykeion/kr_types/kr_literals.py +12 -3
- kerykeion/kr_types/kr_models.py +77 -32
- kerykeion/kr_types/settings_models.py +4 -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 +256 -102
- kerykeion/utilities.py +122 -217
- {kerykeion-4.12.3.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.12.3.dist-info/RECORD +0 -32
- {kerykeion-4.12.3.dist-info → kerykeion-4.18.0.dist-info}/LICENSE +0 -0
- {kerykeion-4.12.3.dist-info → kerykeion-4.18.0.dist-info}/WHEEL +0 -0
- {kerykeion-4.12.3.dist-info → kerykeion-4.18.0.dist-info}/entry_points.txt +0 -0
kerykeion/charts/charts_utils.py
CHANGED
|
@@ -1,7 +1,40 @@
|
|
|
1
1
|
import math
|
|
2
2
|
import datetime
|
|
3
3
|
from kerykeion.kr_types import KerykeionException, ChartType
|
|
4
|
-
from typing import Union
|
|
4
|
+
from typing import Union, Literal
|
|
5
|
+
from kerykeion.kr_types.kr_models import AspectModel, KerykeionPointModel
|
|
6
|
+
from kerykeion.kr_types.settings_models import KerykeionLanguageCelestialPointModel, KerykeionSettingsAspectModel
|
|
7
|
+
|
|
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, "")
|
|
5
38
|
|
|
6
39
|
|
|
7
40
|
def decHourJoin(inH: int, inM: int, inS: int) -> float:
|
|
@@ -130,10 +163,10 @@ def draw_zodiac_slice(
|
|
|
130
163
|
- seventh_house_degree_ut (Union[int, float]): The degree of the seventh house.
|
|
131
164
|
- num (int): The number of the sign. Note: In OpenAstro it did refer to self.zodiac,
|
|
132
165
|
which is a list of the signs in order, starting with Aries. Eg:
|
|
133
|
-
{"name": "
|
|
166
|
+
{"name": "Ari", "element": "fire"}
|
|
134
167
|
- r (Union[int, float]): The value of r.
|
|
135
168
|
- style (str): The CSS inline style.
|
|
136
|
-
- 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".
|
|
137
170
|
self.zodiac[i]["name"]
|
|
138
171
|
|
|
139
172
|
Returns:
|
|
@@ -144,7 +177,7 @@ def draw_zodiac_slice(
|
|
|
144
177
|
offset = 360 - seventh_house_degree_ut
|
|
145
178
|
# check transit
|
|
146
179
|
if chart_type == "Transit" or chart_type == "Synastry":
|
|
147
|
-
dropin = 0
|
|
180
|
+
dropin: Union[int, float] = 0
|
|
148
181
|
else:
|
|
149
182
|
dropin = c1
|
|
150
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}"/>'
|
|
@@ -212,8 +245,7 @@ def convert_longitude_coordinate_to_string(coord: Union[int, float], east_label:
|
|
|
212
245
|
def draw_aspect_line(
|
|
213
246
|
r: Union[int, float],
|
|
214
247
|
ar: Union[int, float],
|
|
215
|
-
|
|
216
|
-
degB: Union[int, float],
|
|
248
|
+
aspect: Union[AspectModel, dict],
|
|
217
249
|
color: str,
|
|
218
250
|
seventh_house_degree_ut: Union[int, float],
|
|
219
251
|
) -> str:
|
|
@@ -222,8 +254,7 @@ def draw_aspect_line(
|
|
|
222
254
|
Args:
|
|
223
255
|
- r (Union[int, float]): The value of r.
|
|
224
256
|
- ar (Union[int, float]): The value of ar.
|
|
225
|
-
-
|
|
226
|
-
- degB (Union[int, float]): The degree of B.
|
|
257
|
+
- aspect_dict (dict): The aspect dictionary.
|
|
227
258
|
- color (str): The color of the aspect.
|
|
228
259
|
- seventh_house_degree_ut (Union[int, float]): The degree of the seventh house.
|
|
229
260
|
|
|
@@ -231,17 +262,22 @@ def draw_aspect_line(
|
|
|
231
262
|
str: The SVG line element as a string.
|
|
232
263
|
"""
|
|
233
264
|
|
|
234
|
-
|
|
265
|
+
if isinstance(aspect, dict):
|
|
266
|
+
aspect = AspectModel(**aspect)
|
|
267
|
+
|
|
268
|
+
first_offset = (int(seventh_house_degree_ut) / -1) + int(aspect["p1_abs_pos"])
|
|
235
269
|
x1 = sliceToX(0, ar, first_offset) + (r - ar)
|
|
236
270
|
y1 = sliceToY(0, ar, first_offset) + (r - ar)
|
|
237
271
|
|
|
238
|
-
second_offset = (int(seventh_house_degree_ut) / -1) + int(
|
|
272
|
+
second_offset = (int(seventh_house_degree_ut) / -1) + int(aspect["p2_abs_pos"])
|
|
239
273
|
x2 = sliceToX(0, ar, second_offset) + (r - ar)
|
|
240
274
|
y2 = sliceToY(0, ar, second_offset) + (r - ar)
|
|
241
275
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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
|
+
)
|
|
245
281
|
|
|
246
282
|
|
|
247
283
|
def draw_elements_percentages(
|
|
@@ -276,48 +312,45 @@ def draw_elements_percentages(
|
|
|
276
312
|
air_percentage = int(round(100 * air_points / total))
|
|
277
313
|
water_percentage = int(round(100 * water_points / total))
|
|
278
314
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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
|
+
)
|
|
287
323
|
|
|
288
324
|
|
|
289
|
-
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:
|
|
290
326
|
"""
|
|
291
|
-
|
|
327
|
+
Converts a decimal float to a degrees string in the specified format.
|
|
292
328
|
|
|
293
329
|
Args:
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
- 1: a°
|
|
297
|
-
- 2: a°b'
|
|
298
|
-
- 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)
|
|
299
335
|
|
|
300
336
|
Returns:
|
|
301
|
-
str: degrees in format
|
|
337
|
+
str: The degrees string in the specified format.
|
|
302
338
|
"""
|
|
303
|
-
|
|
339
|
+
# Ensure the input is a float
|
|
304
340
|
dec = float(dec)
|
|
305
|
-
a = int(dec)
|
|
306
|
-
a_new = (dec - float(a)) * 60.0
|
|
307
|
-
b_rounded = int(round(a_new))
|
|
308
|
-
b = int(a_new)
|
|
309
|
-
c = int(round((a_new - float(b)) * 60.0))
|
|
310
|
-
|
|
311
|
-
if type == "3":
|
|
312
|
-
out = f"{a:02d}°{b:02d}'{c:02d}""
|
|
313
|
-
elif type == "2":
|
|
314
|
-
out = f"{a:02d}°{b_rounded:02d}'"
|
|
315
|
-
elif type == "1":
|
|
316
|
-
out = f"{a:02d}°"
|
|
317
|
-
else:
|
|
318
|
-
raise KerykeionException(f"Wrong type: {type}, it must be 1, 2 or 3.")
|
|
319
341
|
|
|
320
|
-
|
|
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}\""
|
|
321
354
|
|
|
322
355
|
|
|
323
356
|
def draw_transit_ring_degree_steps(r: Union[int, float], seventh_house_degree_ut: Union[int, float]) -> str:
|
|
@@ -344,19 +377,21 @@ def draw_transit_ring_degree_steps(r: Union[int, float], seventh_house_degree_ut
|
|
|
344
377
|
y2 = sliceToY(0, r + 2, offset) - 2
|
|
345
378
|
out += f'<line x1="{x1}" y1="{y1}" x2="{x2}" y2="{y2}" style="stroke: #F00; stroke-width: 1px; stroke-opacity:.9;"/>'
|
|
346
379
|
out += "</g>"
|
|
347
|
-
|
|
380
|
+
|
|
348
381
|
return out
|
|
349
382
|
|
|
350
383
|
|
|
351
|
-
def draw_degree_ring(
|
|
384
|
+
def draw_degree_ring(
|
|
385
|
+
r: Union[int, float], c1: Union[int, float], seventh_house_degree_ut: Union[int, float], stroke_color: str
|
|
386
|
+
) -> str:
|
|
352
387
|
"""Draws the degree ring.
|
|
353
|
-
|
|
388
|
+
|
|
354
389
|
Args:
|
|
355
390
|
- r (Union[int, float]): The value of r.
|
|
356
391
|
- c1 (Union[int, float]): The value of c1.
|
|
357
392
|
- seventh_house_degree_ut (Union[int, float]): The degree of the seventh house.
|
|
358
393
|
- stroke_color (str): The color of the stroke.
|
|
359
|
-
|
|
394
|
+
|
|
360
395
|
Returns:
|
|
361
396
|
str: The SVG path of the degree ring.
|
|
362
397
|
"""
|
|
@@ -374,18 +409,19 @@ def draw_degree_ring(r: Union[int, float], c1: Union[int, float], seventh_house_
|
|
|
374
409
|
|
|
375
410
|
out += f'<line x1="{x1}" y1="{y1}" x2="{x2}" y2="{y2}" style="stroke: {stroke_color}; stroke-width: 1px; stroke-opacity:.9;"/>'
|
|
376
411
|
out += "</g>"
|
|
377
|
-
|
|
412
|
+
|
|
378
413
|
return out
|
|
379
414
|
|
|
415
|
+
|
|
380
416
|
def draw_transit_ring(r: Union[int, float], paper_1_color: str, zodiac_transit_ring_3_color: str) -> str:
|
|
381
417
|
"""
|
|
382
418
|
Draws the transit ring.
|
|
383
|
-
|
|
419
|
+
|
|
384
420
|
Args:
|
|
385
421
|
- r (Union[int, float]): The value of r.
|
|
386
422
|
- paper_1_color (str): The color of paper 1.
|
|
387
423
|
- zodiac_transit_ring_3_color (str): The color of the zodiac transit ring
|
|
388
|
-
|
|
424
|
+
|
|
389
425
|
Returns:
|
|
390
426
|
str: The SVG path of the transit ring.
|
|
391
427
|
"""
|
|
@@ -397,16 +433,18 @@ def draw_transit_ring(r: Union[int, float], paper_1_color: str, zodiac_transit_r
|
|
|
397
433
|
return out
|
|
398
434
|
|
|
399
435
|
|
|
400
|
-
def draw_first_circle(
|
|
436
|
+
def draw_first_circle(
|
|
437
|
+
r: Union[int, float], stroke_color: str, chart_type: ChartType, c1: Union[int, float, None] = None
|
|
438
|
+
) -> str:
|
|
401
439
|
"""
|
|
402
440
|
Draws the first circle.
|
|
403
|
-
|
|
441
|
+
|
|
404
442
|
Args:
|
|
405
443
|
- r (Union[int, float]): The value of r.
|
|
406
444
|
- color (str): The color of the circle.
|
|
407
445
|
- chart_type (ChartType): The type of chart.
|
|
408
446
|
- c1 (Union[int, float]): The value of c1.
|
|
409
|
-
|
|
447
|
+
|
|
410
448
|
Returns:
|
|
411
449
|
str: The SVG path of the first circle.
|
|
412
450
|
"""
|
|
@@ -416,29 +454,630 @@ def draw_first_circle(r: Union[int, float], stroke_color: str, chart_type: Chart
|
|
|
416
454
|
if c1 is None:
|
|
417
455
|
raise KerykeionException("c1 is None")
|
|
418
456
|
|
|
419
|
-
return
|
|
457
|
+
return (
|
|
458
|
+
f'<circle cx="{r}" cy="{r}" r="{r - c1}" style="fill: none; stroke: {stroke_color}; stroke-width: 1px; " />'
|
|
459
|
+
)
|
|
420
460
|
|
|
421
461
|
|
|
422
|
-
def draw_second_circle(
|
|
462
|
+
def draw_second_circle(
|
|
463
|
+
r: Union[int, float], stroke_color: str, fill_color: str, chart_type: ChartType, c2: Union[int, float, None] = None
|
|
464
|
+
) -> str:
|
|
423
465
|
"""
|
|
424
466
|
Draws the second circle.
|
|
425
|
-
|
|
467
|
+
|
|
426
468
|
Args:
|
|
427
469
|
- r (Union[int, float]): The value of r.
|
|
428
470
|
- stroke_color (str): The color of the stroke.
|
|
429
471
|
- fill_color (str): The color of the fill.
|
|
430
472
|
- chart_type (ChartType): The type of chart.
|
|
431
473
|
- c2 (Union[int, float]): The value of c2.
|
|
432
|
-
|
|
474
|
+
|
|
433
475
|
Returns:
|
|
434
476
|
str: The SVG path of the second circle.
|
|
435
477
|
"""
|
|
436
|
-
|
|
478
|
+
|
|
437
479
|
if chart_type == "Synastry" or chart_type == "Transit":
|
|
438
480
|
return f'<circle cx="{r}" cy="{r}" r="{r - 72}" style="fill: {fill_color}; fill-opacity:.4; stroke: {stroke_color}; stroke-opacity:.4; stroke-width: 1px" />'
|
|
439
|
-
|
|
481
|
+
|
|
440
482
|
else:
|
|
441
483
|
if c2 is None:
|
|
442
484
|
raise KerykeionException("c2 is None")
|
|
443
485
|
|
|
444
|
-
return f'<circle cx="{r}" cy="{r}" r="{r - c2}" style="fill: {fill_color}; fill-opacity:.2; stroke: {stroke_color}; stroke-opacity:.4; stroke-width: 1px" />'
|
|
486
|
+
return f'<circle cx="{r}" cy="{r}" r="{r - c2}" style="fill: {fill_color}; fill-opacity:.2; stroke: {stroke_color}; stroke-opacity:.4; stroke-width: 1px" />'
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
def draw_third_circle(
|
|
490
|
+
radius: Union[int, float],
|
|
491
|
+
stroke_color: str,
|
|
492
|
+
fill_color: str,
|
|
493
|
+
chart_type: ChartType,
|
|
494
|
+
c3: Union[int, float]
|
|
495
|
+
) -> str:
|
|
496
|
+
"""
|
|
497
|
+
Draws the third circle in an SVG chart.
|
|
498
|
+
|
|
499
|
+
Parameters:
|
|
500
|
+
- radius (Union[int, float]): The radius of the circle.
|
|
501
|
+
- stroke_color (str): The stroke color of the circle.
|
|
502
|
+
- fill_color (str): The fill color of the circle.
|
|
503
|
+
- chart_type (ChartType): The type of the chart.
|
|
504
|
+
- c3 (Union[int, float, None], optional): The radius adjustment for non-Synastry and non-Transit charts.
|
|
505
|
+
|
|
506
|
+
Returns:
|
|
507
|
+
- str: The SVG element as a string.
|
|
508
|
+
"""
|
|
509
|
+
if chart_type in {"Synastry", "Transit"}:
|
|
510
|
+
# For Synastry and Transit charts, use a fixed radius adjustment of 160
|
|
511
|
+
return f'<circle cx="{radius}" cy="{radius}" r="{radius - 160}" style="fill: {fill_color}; fill-opacity:.8; stroke: {stroke_color}; stroke-width: 1px" />'
|
|
512
|
+
|
|
513
|
+
else:
|
|
514
|
+
return f'<circle cx="{radius}" cy="{radius}" r="{radius - c3}" style="fill: {fill_color}; fill-opacity:.8; stroke: {stroke_color}; stroke-width: 1px" />'
|
|
515
|
+
|
|
516
|
+
|
|
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:
|
|
524
|
+
"""
|
|
525
|
+
Draws the aspect grid for the given planets and aspects.
|
|
526
|
+
|
|
527
|
+
Args:
|
|
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.
|
|
533
|
+
|
|
534
|
+
Returns:
|
|
535
|
+
str: SVG string representing the aspect grid.
|
|
536
|
+
"""
|
|
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"]
|
|
570
|
+
):
|
|
571
|
+
svg_output += f'<use x="{x_aspect - box_size + 1}" y="{y_aspect + 1}" xlink:href="#orb{aspect["aspect_degrees"]}" />'
|
|
572
|
+
|
|
573
|
+
return svg_output
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
def draw_houses_cusps_and_text_number(
|
|
577
|
+
r: Union[int, float],
|
|
578
|
+
first_subject_houses_list: list[KerykeionPointModel],
|
|
579
|
+
standard_house_cusp_color: str,
|
|
580
|
+
first_house_color: str,
|
|
581
|
+
tenth_house_color: str,
|
|
582
|
+
seventh_house_color: str,
|
|
583
|
+
fourth_house_color: str,
|
|
584
|
+
c1: Union[int, float],
|
|
585
|
+
c3: Union[int, float],
|
|
586
|
+
chart_type: ChartType,
|
|
587
|
+
second_subject_houses_list: Union[list[KerykeionPointModel], None] = None,
|
|
588
|
+
transit_house_cusp_color: Union[str, None] = None,
|
|
589
|
+
) -> str:
|
|
590
|
+
"""
|
|
591
|
+
Draws the houses cusps and text numbers for a given chart type.
|
|
592
|
+
|
|
593
|
+
Parameters:
|
|
594
|
+
- r: Radius of the chart.
|
|
595
|
+
- first_subject_houses_list: List of house for the first subject.
|
|
596
|
+
- standard_house_cusp_color: Default color for house cusps.
|
|
597
|
+
- first_house_color: Color for the first house cusp.
|
|
598
|
+
- tenth_house_color: Color for the tenth house cusp.
|
|
599
|
+
- seventh_house_color: Color for the seventh house cusp.
|
|
600
|
+
- fourth_house_color: Color for the fourth house cusp.
|
|
601
|
+
- c1: Offset for the first subject.
|
|
602
|
+
- c3: Offset for the third subject.
|
|
603
|
+
- chart_type: Type of the chart (e.g., Transit, Synastry).
|
|
604
|
+
- second_subject_houses_list: List of house for the second subject (optional).
|
|
605
|
+
- transit_house_cusp_color: Color for transit house cusps (optional).
|
|
606
|
+
|
|
607
|
+
Returns:
|
|
608
|
+
- A string containing the SVG path for the houses cusps and text numbers.
|
|
609
|
+
"""
|
|
610
|
+
|
|
611
|
+
path = ""
|
|
612
|
+
xr = 12
|
|
613
|
+
|
|
614
|
+
for i in range(xr):
|
|
615
|
+
# Determine offsets based on chart type
|
|
616
|
+
dropin, roff, t_roff = (160, 72, 36) if chart_type in ["Transit", "Synastry"] else (c3, c1, False)
|
|
617
|
+
|
|
618
|
+
# Calculate the offset for the current house cusp
|
|
619
|
+
offset = (int(first_subject_houses_list[int(xr / 2)].abs_pos) / -1) + int(first_subject_houses_list[i].abs_pos)
|
|
620
|
+
|
|
621
|
+
# Calculate the coordinates for the house cusp lines
|
|
622
|
+
x1 = sliceToX(0, (r - dropin), offset) + dropin
|
|
623
|
+
y1 = sliceToY(0, (r - dropin), offset) + dropin
|
|
624
|
+
x2 = sliceToX(0, r - roff, offset) + roff
|
|
625
|
+
y2 = sliceToY(0, r - roff, offset) + roff
|
|
626
|
+
|
|
627
|
+
# Calculate the text offset for the house number
|
|
628
|
+
next_index = (i + 1) % xr
|
|
629
|
+
text_offset = offset + int(
|
|
630
|
+
degreeDiff(first_subject_houses_list[next_index].abs_pos, first_subject_houses_list[i].abs_pos) / 2
|
|
631
|
+
)
|
|
632
|
+
|
|
633
|
+
# Determine the line color based on the house index
|
|
634
|
+
linecolor = {0: first_house_color, 9: tenth_house_color, 6: seventh_house_color, 3: fourth_house_color}.get(
|
|
635
|
+
i, standard_house_cusp_color
|
|
636
|
+
)
|
|
637
|
+
|
|
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
|
+
|
|
642
|
+
# Calculate the offset for the second subject's house cusp
|
|
643
|
+
zeropoint = 360 - first_subject_houses_list[6].abs_pos
|
|
644
|
+
t_offset = (zeropoint + second_subject_houses_list[i].abs_pos) % 360
|
|
645
|
+
|
|
646
|
+
# Calculate the coordinates for the second subject's house cusp lines
|
|
647
|
+
t_x1 = sliceToX(0, (r - t_roff), t_offset) + t_roff
|
|
648
|
+
t_y1 = sliceToY(0, (r - t_roff), t_offset) + t_roff
|
|
649
|
+
t_x2 = sliceToX(0, r, t_offset)
|
|
650
|
+
t_y2 = sliceToY(0, r, t_offset)
|
|
651
|
+
|
|
652
|
+
# Calculate the text offset for the second subject's house number
|
|
653
|
+
t_text_offset = t_offset + int(
|
|
654
|
+
degreeDiff(second_subject_houses_list[next_index].abs_pos, second_subject_houses_list[i].abs_pos) / 2
|
|
655
|
+
)
|
|
656
|
+
t_linecolor = linecolor if i in [0, 9, 6, 3] else transit_house_cusp_color
|
|
657
|
+
xtext = sliceToX(0, (r - 8), t_text_offset) + 8
|
|
658
|
+
ytext = sliceToY(0, (r - 8), t_text_offset) + 8
|
|
659
|
+
|
|
660
|
+
# Add the house number text for the second subject
|
|
661
|
+
fill_opacity = "0" if chart_type == "Transit" else ".4"
|
|
662
|
+
path += f'<g kr:node="HouseNumber">'
|
|
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>'
|
|
664
|
+
path += f"</g>"
|
|
665
|
+
|
|
666
|
+
# Add the house cusp line for the second subject
|
|
667
|
+
stroke_opacity = "0" if chart_type == "Transit" else ".3"
|
|
668
|
+
path += f'<g kr:node="Cusp">'
|
|
669
|
+
path += f"<line x1='{t_x1}' y1='{t_y1}' x2='{t_x2}' y2='{t_y2}' style='stroke: {t_linecolor}; stroke-width: 1px; stroke-opacity:{stroke_opacity};'/>"
|
|
670
|
+
path += f"</g>"
|
|
671
|
+
|
|
672
|
+
# Adjust dropin based on chart type
|
|
673
|
+
dropin = {"Transit": 84, "Synastry": 84, "ExternalNatal": 100}.get(chart_type, 48)
|
|
674
|
+
xtext = sliceToX(0, (r - dropin), text_offset) + dropin
|
|
675
|
+
ytext = sliceToY(0, (r - dropin), text_offset) + dropin
|
|
676
|
+
|
|
677
|
+
# Add the house cusp line for the first subject
|
|
678
|
+
path += f'<g kr:node="Cusp">'
|
|
679
|
+
path += f'<line x1="{x1}" y1="{y1}" x2="{x2}" y2="{y2}" style="stroke: {linecolor}; stroke-width: 1px; stroke-dasharray:3,2; stroke-opacity:.4;"/>'
|
|
680
|
+
path += f"</g>"
|
|
681
|
+
|
|
682
|
+
# Add the house number text for the first subject
|
|
683
|
+
path += f'<g kr:node="HouseNumber">'
|
|
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>'
|
|
685
|
+
path += f"</g>"
|
|
686
|
+
|
|
687
|
+
return path
|
|
688
|
+
|
|
689
|
+
|
|
690
|
+
def draw_transit_aspect_list(
|
|
691
|
+
grid_title: str,
|
|
692
|
+
aspects_list: Union[list[AspectModel], list[dict]],
|
|
693
|
+
celestial_point_language: Union[KerykeionLanguageCelestialPointModel, dict],
|
|
694
|
+
aspects_settings: Union[KerykeionSettingsAspectModel, dict],
|
|
695
|
+
) -> str:
|
|
696
|
+
"""
|
|
697
|
+
Generates the SVG output for the aspect transit grid.
|
|
698
|
+
|
|
699
|
+
Parameters:
|
|
700
|
+
- grid_title: Title of the grid.
|
|
701
|
+
- aspects_list: List of aspects.
|
|
702
|
+
- planets_labels: Dictionary containing the planet labels.
|
|
703
|
+
- aspects_settings: Dictionary containing the aspect settings.
|
|
704
|
+
|
|
705
|
+
Returns:
|
|
706
|
+
- A string containing the SVG path data for the aspect transit grid.
|
|
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)
|
|
714
|
+
|
|
715
|
+
# If not instance of AspectModel, convert to AspectModel
|
|
716
|
+
if isinstance(aspects_list[0], dict):
|
|
717
|
+
aspects_list = [AspectModel(**aspect) for aspect in aspects_list] # type: ignore
|
|
718
|
+
|
|
719
|
+
line = 0
|
|
720
|
+
nl = 0
|
|
721
|
+
inner_path = ""
|
|
722
|
+
scale = 1
|
|
723
|
+
for i, aspect in enumerate(aspects_list):
|
|
724
|
+
# Adjust the vertical position for every 12 aspects
|
|
725
|
+
if i == 12:
|
|
726
|
+
nl = 100
|
|
727
|
+
line = 0
|
|
728
|
+
|
|
729
|
+
elif i == 24:
|
|
730
|
+
nl = 200
|
|
731
|
+
line = 0
|
|
732
|
+
|
|
733
|
+
elif i == 36:
|
|
734
|
+
nl = 300
|
|
735
|
+
line = 0
|
|
736
|
+
|
|
737
|
+
elif i == 48:
|
|
738
|
+
nl = 400
|
|
739
|
+
# When there are more than 60 aspects, the text is moved up
|
|
740
|
+
if len(aspects_list) > 60:
|
|
741
|
+
line = -1 * (len(aspects_list) - 60) * 14
|
|
742
|
+
else:
|
|
743
|
+
line = 0
|
|
744
|
+
|
|
745
|
+
inner_path += f'<g transform="translate({nl},{line})">'
|
|
746
|
+
|
|
747
|
+
# first planet symbol
|
|
748
|
+
inner_path += f'<use transform="scale(0.4)" x="0" y="3" xlink:href="#{celestial_point_language[aspects_list[i]["p1"]]["name"]}" />'
|
|
749
|
+
|
|
750
|
+
# aspect symbol
|
|
751
|
+
inner_path += f'<use x="15" y="0" xlink:href="#orb{aspects_settings[aspects_list[i]["aid"]]["degree"]}" />'
|
|
752
|
+
|
|
753
|
+
# second planet symbol
|
|
754
|
+
inner_path += f'<g transform="translate(30,0)">'
|
|
755
|
+
inner_path += f'<use transform="scale(0.4)" x="0" y="3" xlink:href="#{celestial_point_language[aspects_list[i]["p2"]]["name"]}" />'
|
|
756
|
+
inner_path += f"</g>"
|
|
757
|
+
|
|
758
|
+
# difference in degrees
|
|
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>'
|
|
760
|
+
# line
|
|
761
|
+
inner_path += f"</g>"
|
|
762
|
+
line = line + 14
|
|
763
|
+
|
|
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>'
|
|
766
|
+
out += inner_path
|
|
767
|
+
out += "</g>"
|
|
768
|
+
|
|
769
|
+
return out
|
|
770
|
+
|
|
771
|
+
|
|
772
|
+
def draw_moon_phase(
|
|
773
|
+
degrees_between_sun_and_moon: float,
|
|
774
|
+
latitude: float
|
|
775
|
+
) -> str:
|
|
776
|
+
"""
|
|
777
|
+
Draws the moon phase based on the degrees between the sun and the moon.
|
|
778
|
+
|
|
779
|
+
Parameters:
|
|
780
|
+
- degrees_between_sun_and_moon (float): The degrees between the sun and the moon.
|
|
781
|
+
- latitude (float): The latitude for rotation calculation.
|
|
782
|
+
- lunar_phase_outline_color (str): The color for the lunar phase outline.
|
|
783
|
+
- dark_color (str): The color for the dark part of the moon.
|
|
784
|
+
- light_color (str): The color for the light part of the moon.
|
|
785
|
+
|
|
786
|
+
Returns:
|
|
787
|
+
- str: The SVG element as a string.
|
|
788
|
+
"""
|
|
789
|
+
deg = degrees_between_sun_and_moon
|
|
790
|
+
|
|
791
|
+
# Initialize variables for lunar phase properties
|
|
792
|
+
circle_center_x = None
|
|
793
|
+
circle_radius = None
|
|
794
|
+
|
|
795
|
+
# Determine lunar phase properties based on the degree
|
|
796
|
+
if deg < 90.0:
|
|
797
|
+
max_radius = deg
|
|
798
|
+
if deg > 80.0:
|
|
799
|
+
max_radius = max_radius * max_radius
|
|
800
|
+
circle_center_x = 20.0 + (deg / 90.0) * (max_radius + 10.0)
|
|
801
|
+
circle_radius = 10.0 + (deg / 90.0) * max_radius
|
|
802
|
+
|
|
803
|
+
elif deg < 180.0:
|
|
804
|
+
max_radius = 180.0 - deg
|
|
805
|
+
if deg < 100.0:
|
|
806
|
+
max_radius = max_radius * max_radius
|
|
807
|
+
circle_center_x = 20.0 + ((deg - 90.0) / 90.0 * (max_radius + 10.0)) - (max_radius + 10.0)
|
|
808
|
+
circle_radius = 10.0 + max_radius - ((deg - 90.0) / 90.0 * max_radius)
|
|
809
|
+
|
|
810
|
+
elif deg < 270.0:
|
|
811
|
+
max_radius = deg - 180.0
|
|
812
|
+
if deg > 260.0:
|
|
813
|
+
max_radius = max_radius * max_radius
|
|
814
|
+
circle_center_x = 20.0 + ((deg - 180.0) / 90.0 * (max_radius + 10.0))
|
|
815
|
+
circle_radius = 10.0 + ((deg - 180.0) / 90.0 * max_radius)
|
|
816
|
+
|
|
817
|
+
elif deg < 361.0:
|
|
818
|
+
max_radius = 360.0 - deg
|
|
819
|
+
if deg < 280.0:
|
|
820
|
+
max_radius = max_radius * max_radius
|
|
821
|
+
circle_center_x = 20.0 + ((deg - 270.0) / 90.0 * (max_radius + 10.0)) - (max_radius + 10.0)
|
|
822
|
+
circle_radius = 10.0 + max_radius - ((deg - 270.0) / 90.0 * max_radius)
|
|
823
|
+
|
|
824
|
+
else:
|
|
825
|
+
raise KerykeionException(f"Invalid degree value: {deg}")
|
|
826
|
+
|
|
827
|
+
|
|
828
|
+
# Calculate rotation based on latitude
|
|
829
|
+
lunar_phase_rotate = -90.0 - latitude
|
|
830
|
+
|
|
831
|
+
# Return the SVG element as a string
|
|
832
|
+
return (
|
|
833
|
+
f'<g transform="rotate({lunar_phase_rotate} 20 10)">'
|
|
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>'
|
|
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
|