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
@@ -0,0 +1,407 @@
1
+ # type: ignore
2
+
3
+ from kerykeion.charts.charts_utils import degreeDiff, sliceToX, sliceToY, convert_decimal_to_degree_string
4
+ from kerykeion.kr_types import KerykeionException, ChartType, KerykeionPointModel
5
+ from kerykeion.kr_types.settings_models import KerykeionSettingsCelestialPointModel
6
+ from kerykeion.kr_types.kr_literals import Houses
7
+ import logging
8
+ from typing import Union, get_args
9
+
10
+
11
+
12
+ def draw_planets(
13
+ radius: Union[int, float],
14
+ available_kerykeion_celestial_points: list[KerykeionPointModel],
15
+ available_planets_setting: list[KerykeionSettingsCelestialPointModel],
16
+ third_circle_radius: Union[int, float],
17
+ main_subject_first_house_degree_ut: Union[int, float],
18
+ main_subject_seventh_house_degree_ut: Union[int, float],
19
+ chart_type: ChartType,
20
+ second_subject_available_kerykeion_celestial_points: Union[list[KerykeionPointModel], None] = None,
21
+ ):
22
+ """
23
+ Draws the planets on a chart based on the provided parameters.
24
+
25
+ Args:
26
+ radius (int): The radius of the chart.
27
+ available_kerykeion_celestial_points (list[KerykeionPointModel]): List of celestial points for the main subject.
28
+ available_planets_setting (list[KerykeionSettingsCelestialPointModel]): Settings for the celestial points.
29
+ third_circle_radius (Union[int, float]): Radius of the third circle.
30
+ main_subject_first_house_degree_ut (Union[int, float]): Degree of the first house for the main subject.
31
+ main_subject_seventh_house_degree_ut (Union[int, float]): Degree of the seventh house for the main subject.
32
+ chart_type (ChartType): Type of the chart (e.g., "Transit", "Synastry").
33
+ second_subject_available_kerykeion_celestial_points (Union[list[KerykeionPointModel], None], optional):
34
+ List of celestial points for the second subject, required for "Transit" or "Synastry" charts. Defaults to None.
35
+
36
+ Raises:
37
+ KerykeionException: If the second subject is required but not provided.
38
+
39
+ Returns:
40
+ str: SVG output for the chart with the planets drawn.
41
+ """
42
+ TRANSIT_RING_EXCLUDE_POINTS_NAMES = get_args(Houses)
43
+
44
+ if chart_type == "Transit" or chart_type == "Synastry":
45
+ if second_subject_available_kerykeion_celestial_points is None:
46
+ raise KerykeionException("Second subject is required for Transit or Synastry charts")
47
+
48
+ # Make a list for the absolute degrees of the points of the graphic.
49
+ points_deg_ut = []
50
+ for planet in available_kerykeion_celestial_points:
51
+ points_deg_ut.append(planet.abs_pos)
52
+
53
+ # Make a list of the relative degrees of the points in the graphic.
54
+ points_deg = []
55
+ for planet in available_kerykeion_celestial_points:
56
+ points_deg.append(planet.position)
57
+
58
+ if chart_type == "Transit" or chart_type == "Synastry":
59
+ # Make a list for the absolute degrees of the points of the graphic.
60
+ t_points_deg_ut = []
61
+ for planet in second_subject_available_kerykeion_celestial_points:
62
+ t_points_deg_ut.append(planet.abs_pos)
63
+
64
+ # Make a list of the relative degrees of the points in the graphic.
65
+ t_points_deg = []
66
+ for planet in second_subject_available_kerykeion_celestial_points:
67
+ t_points_deg.append(planet.position)
68
+
69
+ planets_degut = {}
70
+ diff = range(len(available_planets_setting))
71
+
72
+ for i in range(len(available_planets_setting)):
73
+ # list of planets sorted by degree
74
+ logging.debug(f"planet: {i}, degree: {points_deg_ut[i]}")
75
+ planets_degut[points_deg_ut[i]] = i
76
+
77
+ """
78
+ FIXME: The planets_degut is a dictionary like:
79
+ {planet_degree: planet_index}
80
+ It should be replaced bu points_deg_ut
81
+ print(points_deg_ut)
82
+ print(planets_degut)
83
+ """
84
+
85
+ output = ""
86
+ keys = list(planets_degut.keys())
87
+ keys.sort()
88
+ switch = 0
89
+
90
+ planets_degrouped = {}
91
+ groups = []
92
+ planets_by_pos = list(range(len(planets_degut)))
93
+ planet_drange = 3.4
94
+ # get groups closely together
95
+ group_open = False
96
+ for e in range(len(keys)):
97
+ i = planets_degut[keys[e]]
98
+ # get distances between planets
99
+ if e == 0:
100
+ prev = points_deg_ut[planets_degut[keys[-1]]]
101
+ next = points_deg_ut[planets_degut[keys[1]]]
102
+ elif e == (len(keys) - 1):
103
+ prev = points_deg_ut[planets_degut[keys[e - 1]]]
104
+ next = points_deg_ut[planets_degut[keys[0]]]
105
+ else:
106
+ prev = points_deg_ut[planets_degut[keys[e - 1]]]
107
+ next = points_deg_ut[planets_degut[keys[e + 1]]]
108
+ diffa = degreeDiff(prev, points_deg_ut[i])
109
+ diffb = degreeDiff(next, points_deg_ut[i])
110
+ planets_by_pos[e] = [i, diffa, diffb]
111
+
112
+ logging.debug(f'{available_planets_setting[i]["label"]}, {diffa}, {diffb}')
113
+
114
+ if diffb < planet_drange:
115
+ if group_open:
116
+ groups[-1].append([e, diffa, diffb, available_planets_setting[planets_degut[keys[e]]]["label"]])
117
+ else:
118
+ group_open = True
119
+ groups.append([])
120
+ groups[-1].append([e, diffa, diffb, available_planets_setting[planets_degut[keys[e]]]["label"]])
121
+ else:
122
+ if group_open:
123
+ groups[-1].append([e, diffa, diffb, available_planets_setting[planets_degut[keys[e]]]["label"]])
124
+ group_open = False
125
+
126
+ def zero(x):
127
+ return 0
128
+
129
+ planets_delta = list(map(zero, range(len(available_planets_setting))))
130
+
131
+ # print groups
132
+ # print planets_by_pos
133
+ for a in range(len(groups)):
134
+ # Two grouped planets
135
+ if len(groups[a]) == 2:
136
+ next_to_a = groups[a][0][0] - 1
137
+ if groups[a][1][0] == (len(planets_by_pos) - 1):
138
+ next_to_b = 0
139
+ else:
140
+ next_to_b = groups[a][1][0] + 1
141
+ # if both planets have room
142
+ if (groups[a][0][1] > (2 * planet_drange)) & (groups[a][1][2] > (2 * planet_drange)):
143
+ planets_delta[groups[a][0][0]] = -(planet_drange - groups[a][0][2]) / 2
144
+ planets_delta[groups[a][1][0]] = +(planet_drange - groups[a][0][2]) / 2
145
+ # if planet a has room
146
+ elif groups[a][0][1] > (2 * planet_drange):
147
+ planets_delta[groups[a][0][0]] = -planet_drange
148
+ # if planet b has room
149
+ elif groups[a][1][2] > (2 * planet_drange):
150
+ planets_delta[groups[a][1][0]] = +planet_drange
151
+
152
+ # if planets next to a and b have room move them
153
+ elif (planets_by_pos[next_to_a][1] > (2.4 * planet_drange)) & (
154
+ planets_by_pos[next_to_b][2] > (2.4 * planet_drange)
155
+ ):
156
+ planets_delta[(next_to_a)] = groups[a][0][1] - planet_drange * 2
157
+ planets_delta[groups[a][0][0]] = -planet_drange * 0.5
158
+ planets_delta[next_to_b] = -(groups[a][1][2] - planet_drange * 2)
159
+ planets_delta[groups[a][1][0]] = +planet_drange * 0.5
160
+
161
+ # if planet next to a has room move them
162
+ elif planets_by_pos[next_to_a][1] > (2 * planet_drange):
163
+ planets_delta[(next_to_a)] = groups[a][0][1] - planet_drange * 2.5
164
+ planets_delta[groups[a][0][0]] = -planet_drange * 1.2
165
+
166
+ # if planet next to b has room move them
167
+ elif planets_by_pos[next_to_b][2] > (2 * planet_drange):
168
+ planets_delta[next_to_b] = -(groups[a][1][2] - planet_drange * 2.5)
169
+ planets_delta[groups[a][1][0]] = +planet_drange * 1.2
170
+
171
+ # Three grouped planets or more
172
+ xl = len(groups[a])
173
+ if xl >= 3:
174
+ available = groups[a][0][1]
175
+ for f in range(xl):
176
+ available += groups[a][f][2]
177
+ need = (3 * planet_drange) + (1.2 * (xl - 1) * planet_drange)
178
+ leftover = available - need
179
+ xa = groups[a][0][1]
180
+ xb = groups[a][(xl - 1)][2]
181
+
182
+ # center
183
+ if (xa > (need * 0.5)) & (xb > (need * 0.5)):
184
+ startA = xa - (need * 0.5)
185
+ # position relative to next planets
186
+ else:
187
+ startA = (leftover / (xa + xb)) * xa
188
+ startB = (leftover / (xa + xb)) * xb
189
+
190
+ if available > need:
191
+ planets_delta[groups[a][0][0]] = startA - groups[a][0][1] + (1.5 * planet_drange)
192
+ for f in range(xl - 1):
193
+ planets_delta[groups[a][(f + 1)][0]] = (
194
+ 1.2 * planet_drange + planets_delta[groups[a][f][0]] - groups[a][f][2]
195
+ )
196
+
197
+ for e in range(len(keys)):
198
+ i = planets_degut[keys[e]]
199
+
200
+ # coordinates
201
+ if chart_type == "Transit" or chart_type == "Synastry":
202
+ if 22 < i < 27:
203
+ rplanet = 76
204
+ elif switch == 1:
205
+ rplanet = 110
206
+ switch = 0
207
+ else:
208
+ rplanet = 130
209
+ switch = 1
210
+ else:
211
+ # if 22 < i < 27 it is asc,mc,dsc,ic (angles of chart)
212
+ # put on special line (rplanet is range from outer ring)
213
+ amin, bmin, cmin = 0, 0, 0
214
+ if chart_type == "ExternalNatal":
215
+ amin = 74 - 10
216
+ bmin = 94 - 10
217
+ cmin = 40 - 10
218
+
219
+ if 22 < i < 27:
220
+ rplanet = 40 - cmin
221
+ elif switch == 1:
222
+ rplanet = 74 - amin
223
+ switch = 0
224
+ else:
225
+ rplanet = 94 - bmin
226
+ switch = 1
227
+
228
+ rtext = 45
229
+
230
+ offset = (int(main_subject_seventh_house_degree_ut) / -1) + int(points_deg_ut[i] + planets_delta[e])
231
+ trueoffset = (int(main_subject_seventh_house_degree_ut) / -1) + int(points_deg_ut[i])
232
+
233
+ planet_x = sliceToX(0, (radius - rplanet), offset) + rplanet
234
+ planet_y = sliceToY(0, (radius - rplanet), offset) + rplanet
235
+ if chart_type == "Transit" or chart_type == "Synastry":
236
+ scale = 0.8
237
+
238
+ elif chart_type == "ExternalNatal":
239
+ scale = 0.8
240
+ # line1
241
+ x1 = sliceToX(0, (radius - third_circle_radius), trueoffset) + third_circle_radius
242
+ y1 = sliceToY(0, (radius - third_circle_radius), trueoffset) + third_circle_radius
243
+ x2 = sliceToX(0, (radius - rplanet - 30), trueoffset) + rplanet + 30
244
+ y2 = sliceToY(0, (radius - rplanet - 30), trueoffset) + rplanet + 30
245
+ color = available_planets_setting[i]["color"]
246
+ output += (
247
+ '<line x1="%s" y1="%s" x2="%s" y2="%s" style="stroke-width:1px;stroke:%s;stroke-opacity:.3;"/>\n'
248
+ % (x1, y1, x2, y2, color)
249
+ )
250
+ # line2
251
+ x1 = sliceToX(0, (radius - rplanet - 30), trueoffset) + rplanet + 30
252
+ y1 = sliceToY(0, (radius - rplanet - 30), trueoffset) + rplanet + 30
253
+ x2 = sliceToX(0, (radius - rplanet - 10), offset) + rplanet + 10
254
+ y2 = sliceToY(0, (radius - rplanet - 10), offset) + rplanet + 10
255
+ output += (
256
+ '<line x1="%s" y1="%s" x2="%s" y2="%s" style="stroke-width:1px;stroke:%s;stroke-opacity:.5;"/>\n'
257
+ % (x1, y1, x2, y2, color)
258
+ )
259
+
260
+ else:
261
+ scale = 1
262
+
263
+ planet_details = available_kerykeion_celestial_points[i]
264
+
265
+ output += f'<g kr:node="ChartPoint" kr:house="{planet_details["house"]}" kr:sign="{planet_details["sign"]}" kr:slug="{planet_details["name"]}" transform="translate(-{12 * scale},-{12 * scale}) scale({scale})">'
266
+ output += f'<use x="{planet_x * (1/scale)}" y="{planet_y * (1/scale)}" xlink:href="#{available_planets_setting[i]["name"]}" />'
267
+ output += f"</g>"
268
+
269
+ # make transit degut and display planets
270
+ if chart_type == "Transit" or chart_type == "Synastry":
271
+ group_offset = {}
272
+ t_planets_degut = {}
273
+ list_range = len(available_planets_setting)
274
+
275
+ for i in range(list_range):
276
+ if chart_type == "Transit" and available_planets_setting[i]["name"] in TRANSIT_RING_EXCLUDE_POINTS_NAMES:
277
+ continue
278
+
279
+ group_offset[i] = 0
280
+ t_planets_degut[t_points_deg_ut[i]] = i
281
+
282
+ t_keys = list(t_planets_degut.keys())
283
+ t_keys.sort()
284
+
285
+ # grab closely grouped planets
286
+ groups = []
287
+ in_group = False
288
+ for e in range(len(t_keys)):
289
+ i_a = t_planets_degut[t_keys[e]]
290
+ if e == (len(t_keys) - 1):
291
+ i_b = t_planets_degut[t_keys[0]]
292
+ else:
293
+ i_b = t_planets_degut[t_keys[e + 1]]
294
+
295
+ a = t_points_deg_ut[i_a]
296
+ b = t_points_deg_ut[i_b]
297
+ diff = degreeDiff(a, b)
298
+ if diff <= 2.5:
299
+ if in_group:
300
+ groups[-1].append(i_b)
301
+ else:
302
+ groups.append([i_a])
303
+ groups[-1].append(i_b)
304
+ in_group = True
305
+ else:
306
+ in_group = False
307
+ # loop groups and set degrees display adjustment
308
+ for i in range(len(groups)):
309
+ if len(groups[i]) == 2:
310
+ group_offset[groups[i][0]] = -1.0
311
+ group_offset[groups[i][1]] = 1.0
312
+ elif len(groups[i]) == 3:
313
+ group_offset[groups[i][0]] = -1.5
314
+ group_offset[groups[i][1]] = 0
315
+ group_offset[groups[i][2]] = 1.5
316
+ elif len(groups[i]) == 4:
317
+ group_offset[groups[i][0]] = -2.0
318
+ group_offset[groups[i][1]] = -1.0
319
+ group_offset[groups[i][2]] = 1.0
320
+ group_offset[groups[i][3]] = 2.0
321
+
322
+ switch = 0
323
+
324
+ # Transit planets loop
325
+ for e in range(len(t_keys)):
326
+ if chart_type == "Transit" and available_planets_setting[e]["name"] in TRANSIT_RING_EXCLUDE_POINTS_NAMES:
327
+ continue
328
+
329
+ i = t_planets_degut[t_keys[e]]
330
+
331
+ if 22 < i < 27:
332
+ rplanet = 9
333
+ elif switch == 1:
334
+ rplanet = 18
335
+ switch = 0
336
+ else:
337
+ rplanet = 26
338
+ switch = 1
339
+
340
+ # Transit planet name
341
+ zeropoint = 360 - main_subject_seventh_house_degree_ut
342
+ t_offset = zeropoint + t_points_deg_ut[i]
343
+ if t_offset > 360:
344
+ t_offset = t_offset - 360
345
+ planet_x = sliceToX(0, (radius - rplanet), t_offset) + rplanet
346
+ planet_y = sliceToY(0, (radius - rplanet), t_offset) + rplanet
347
+ output += f'<g class="transit-planet-name" transform="translate(-6,-6)"><g transform="scale(0.5)"><use x="{planet_x*2}" y="{planet_y*2}" xlink:href="#{available_planets_setting[i]["name"]}" /></g></g>'
348
+
349
+ # Transit planet line
350
+ x1 = sliceToX(0, radius + 3, t_offset) - 3
351
+ y1 = sliceToY(0, radius + 3, t_offset) - 3
352
+ x2 = sliceToX(0, radius - 3, t_offset) + 3
353
+ y2 = sliceToY(0, radius - 3, t_offset) + 3
354
+ output += f'<line class="transit-planet-line" x1="{str(x1)}" y1="{str(y1)}" x2="{str(x2)}" y2="{str(y2)}" style="stroke: {available_planets_setting[i]["color"]}; stroke-width: 1px; stroke-opacity:.8;"/>'
355
+
356
+ # transit planet degree text
357
+ rotate = main_subject_first_house_degree_ut - t_points_deg_ut[i]
358
+ textanchor = "end"
359
+ t_offset += group_offset[i]
360
+ rtext = -3.0
361
+
362
+ if -90 > rotate > -270:
363
+ rotate = rotate + 180.0
364
+ textanchor = "start"
365
+ if 270 > rotate > 90:
366
+ rotate = rotate - 180.0
367
+ textanchor = "start"
368
+
369
+ if textanchor == "end":
370
+ xo = 1
371
+ else:
372
+ xo = -1
373
+ deg_x = sliceToX(0, (radius - rtext), t_offset + xo) + rtext
374
+ deg_y = sliceToY(0, (radius - rtext), t_offset + xo) + rtext
375
+ degree = int(t_offset)
376
+ output += f'<g transform="translate({deg_x},{deg_y})">'
377
+ output += f'<text transform="rotate({rotate})" text-anchor="{textanchor}'
378
+ output += f'" style="fill: {available_planets_setting[i]["color"]}; font-size: 10px;">{convert_decimal_to_degree_string(t_points_deg[i], format_type="1")}'
379
+ output += "</text></g>"
380
+
381
+ # check transit
382
+ if chart_type == "Transit" or chart_type == "Synastry":
383
+ dropin = 36
384
+ else:
385
+ dropin = 0
386
+
387
+ # planet line
388
+ x1 = sliceToX(0, radius - (dropin + 3), offset) + (dropin + 3)
389
+ y1 = sliceToY(0, radius - (dropin + 3), offset) + (dropin + 3)
390
+ x2 = sliceToX(0, (radius - (dropin - 3)), offset) + (dropin - 3)
391
+ y2 = sliceToY(0, (radius - (dropin - 3)), offset) + (dropin - 3)
392
+
393
+ output += f'<line x1="{x1}" y1="{y1}" x2="{x2}" y2="{y2}" style="stroke: {available_planets_setting[i]["color"]}; stroke-width: 2px; stroke-opacity:.6;"/>'
394
+
395
+ # check transit
396
+ if chart_type == "Transit" or chart_type == "Synastry":
397
+ dropin = 160
398
+ else:
399
+ dropin = 120
400
+
401
+ x1 = sliceToX(0, radius - dropin, offset) + dropin
402
+ y1 = sliceToY(0, radius - dropin, offset) + dropin
403
+ x2 = sliceToX(0, (radius - (dropin - 3)), offset) + (dropin - 3)
404
+ y2 = sliceToY(0, (radius - (dropin - 3)), offset) + (dropin - 3)
405
+ output += f'<line x1="{x1}" y1="{y1}" x2="{x2}" y2="{y2}" style="stroke: {available_planets_setting[i]["color"]}; stroke-width: 2px; stroke-opacity:.6;"/>'
406
+
407
+ return output