Skip to content

Commit 64ea05f

Browse files
committed
Fix #147
1 parent 573b26b commit 64ea05f

5 files changed

Lines changed: 84 additions & 8 deletions

File tree

examples/charts/composite_chart.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from kerykeion.astrological_subject import AstrologicalSubject
2+
from kerykeion.composite_subject_factory import CompositeSubjectFactory
3+
from kerykeion.charts.kerykeion_chart_svg import KerykeionChartSVG
4+
5+
# Create astrological subjects
6+
first = AstrologicalSubject("John Lennon", 1940, 10, 9, 18, 30, "Liverpool", "GB")
7+
second = AstrologicalSubject("Paul McCartney", 1942, 6, 18, 15, 30, "Liverpool", "GB")
8+
9+
# Generate composite chart data
10+
composite_chart = CompositeSubjectFactory(first, second)
11+
print(composite_chart.get_midpoint_composite_subject_model().model_dump_json(indent=4))
12+
13+
# Create and save the composite chart as an SVG
14+
angelina = AstrologicalSubject("Angelina Jolie", 1975, 6, 4, 9, 9, "Los Angeles", "US", lng=-118.15, lat=34.03, tz_str="America/Los_Angeles")
15+
brad = AstrologicalSubject("Brad Pitt", 1963, 12, 18, 6, 31, "Shawnee", "US", lng=-96.56, lat=35.20, tz_str="America/Chicago")
16+
17+
composite_subject_factory = CompositeSubjectFactory(angelina, brad)
18+
composite_subject_model = composite_subject_factory.get_midpoint_composite_subject_model()
19+
composite_chart = KerykeionChartSVG(composite_subject_model, "Composite")
20+
composite_chart.makeSVG()

kerykeion/composite_subject_factory.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
get_kerykeion_point_from_degree,
99
get_planet_house,
1010
circular_mean,
11-
calculate_moon_phase
11+
calculate_moon_phase,
12+
circular_sort
1213
)
1314

1415

@@ -132,17 +133,22 @@ def _calculate_midpoint_composite_points_and_houses(self):
132133
house_degree_list_ut = []
133134
for house in self.first_subject.houses_names_list:
134135
house_lower = house.lower()
135-
self[house_lower] = get_kerykeion_point_from_degree(
136+
house_degree_list_ut.append(
136137
circular_mean(
137138
self.first_subject[house_lower]["abs_pos"],
138139
self.second_subject[house_lower]["abs_pos"]
139-
),
140-
house,
140+
)
141+
)
142+
house_degree_list_ut = circular_sort(house_degree_list_ut)
143+
144+
for house_index, house_name in enumerate(self.first_subject.houses_names_list):
145+
house_lower = house_name.lower()
146+
self[house_lower] = get_kerykeion_point_from_degree(
147+
house_degree_list_ut[house_index],
148+
house_name,
141149
"House"
142150
)
143-
house_degree_list_ut.append(self[house_lower]["abs_pos"])
144151

145-
house_degree_list_ut = sorted(house_degree_list_ut)
146152

147153
# Planets
148154
common_planets = []

kerykeion/utilities.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,3 +390,53 @@ def calculate_moon_phase(moon_abs_pos: float, sun_abs_pos: float) -> LunarPhaseM
390390
}
391391

392392
return LunarPhaseModel(**lunar_phase_dictionary)
393+
394+
395+
def circular_sort(degrees: list[Union[int, float]]) -> list[Union[int, float]]:
396+
"""
397+
Sort a list of degrees in a circular manner, starting from the first element
398+
and progressing clockwise around the circle.
399+
400+
Args:
401+
degrees: A list of numeric values representing degrees
402+
403+
Returns:
404+
A list sorted based on circular clockwise progression from the first element
405+
406+
Raises:
407+
ValueError: If the list is empty or contains non-numeric values
408+
"""
409+
# Input validation
410+
if not degrees:
411+
raise ValueError("Input list cannot be empty")
412+
413+
if not all(isinstance(degree, (int, float)) for degree in degrees):
414+
invalid = next(d for d in degrees if not isinstance(d, (int, float)))
415+
raise ValueError(f"All elements must be numeric, found: {invalid} of type {type(invalid).__name__}")
416+
417+
# If list has 0 or 1 element, return it as is
418+
if len(degrees) <= 1:
419+
return degrees.copy()
420+
421+
# Save the first element as the reference
422+
reference = degrees[0]
423+
424+
# Define a function to calculate clockwise distance from reference
425+
def clockwise_distance(angle: Union[int, float]) -> Union[int, float]:
426+
# Normalize angles to 0-360 range
427+
ref_norm = reference % 360
428+
angle_norm = angle % 360
429+
430+
# Calculate clockwise distance
431+
distance = angle_norm - ref_norm
432+
if distance < 0:
433+
distance += 360
434+
435+
return distance
436+
437+
# Sort the rest of the elements based on circular distance
438+
remaining = degrees[1:]
439+
sorted_remaining = sorted(remaining, key=clockwise_distance)
440+
441+
# Return the reference followed by the sorted remaining elements
442+
return [reference] + sorted_remaining

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "kerykeion"
3-
version = "4.25.2"
3+
version = "4.25.3"
44
authors = ["Giacomo Battaglia <kerykeion.astrology@gmail.com>"]
55
description = "A python library for astrology."
66
license = "AGPL-3.0"

tests/charts/svg/Angelina Jolie and Brad Pitt Composite Chart - Composite Chart.svg

Lines changed: 1 addition & 1 deletion
Loading

0 commit comments

Comments
 (0)