From 98ebfac8331d885d2cc4deb094e55e552aae3a19 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 13 Jul 2024 13:32:13 +0100 Subject: [PATCH 1/5] Add Infinity Target Shape --- src/data_morph/shapes/factory.py | 1 + src/data_morph/shapes/points.py | 49 ++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/src/data_morph/shapes/factory.py b/src/data_morph/shapes/factory.py index 2b2db2af..024d38d2 100644 --- a/src/data_morph/shapes/factory.py +++ b/src/data_morph/shapes/factory.py @@ -45,6 +45,7 @@ class ShapeFactory: 'dots': points.DotsGrid, 'down_parab': points.DownParabola, 'heart': points.Heart, + 'infinity': points.Infinity, 'left_parab': points.LeftParabola, 'scatter': points.Scatter, 'right_parab': points.RightParabola, diff --git a/src/data_morph/shapes/points.py b/src/data_morph/shapes/points.py index 158be2a8..46e6e49b 100644 --- a/src/data_morph/shapes/points.py +++ b/src/data_morph/shapes/points.py @@ -131,6 +131,55 @@ def __init__(self, dataset: Dataset) -> None: ) +class Infinity(PointCollection): + """ + Class for the figure eight shape. + + .. plot:: + :scale: 75 + :caption: + This shape is generated using the panda dataset. + + from data_morph.data.loader import DataLoader + from data_morph.shapes.points import Infinity + + _ = Infinity(DataLoader.load_dataset('panda')).plot() + + Parameters + ---------- + dataset : Dataset + The starting dataset to morph into other shapes. + + Notes + ----- + The formula for the infinity shape is directly taken from Lemniscate of + Bernoulli equation. + `Infinity Curve `_: + + Weisstein, Eric W. "Lemniscate." From MathWorld-- + A Wolfram Web Resource. https://mathworld.wolfram.com/Lemniscate.html + """ + + def __init__(self, dataset: Dataset, scale: float = 0.75) -> None: + x_bounds = dataset.data_bounds.x_bounds + y_bounds = dataset.data_bounds.y_bounds + + x_shift = sum(x_bounds) / 2 + y_shift = sum(y_bounds) / 2 + + t = np.linspace(-3, 3, num=2000) + + x = (np.sqrt(2) * np.cos(t)) / (1 + np.square(np.sin(t))) + y = (np.sqrt(2) * np.cos(t) * np.sin(t)) / (1 + np.square(np.sin(t))) + + # scale by the half the widest width of the infinity + scale_factor = (x_bounds[1] - x_shift) * 0.75 + + super().__init__( + *np.stack([x * scale_factor + x_shift, y * scale_factor + y_shift], axis=1) + ) + + class LeftParabola(PointCollection): """ Class for the left parabola shape. From 92683cc8e67ef480b1c77835503f6a6f3e797d7b Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 13 Jul 2024 13:35:12 +0100 Subject: [PATCH 2/5] Update Infinity documentation --- src/data_morph/shapes/points.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/data_morph/shapes/points.py b/src/data_morph/shapes/points.py index 46e6e49b..d9c4473f 100644 --- a/src/data_morph/shapes/points.py +++ b/src/data_morph/shapes/points.py @@ -133,7 +133,7 @@ def __init__(self, dataset: Dataset) -> None: class Infinity(PointCollection): """ - Class for the figure eight shape. + Class for the infinity shape. .. plot:: :scale: 75 @@ -152,7 +152,7 @@ class Infinity(PointCollection): Notes ----- - The formula for the infinity shape is directly taken from Lemniscate of + The formula for the infinity shape is directly taken from Lemniscate of Bernoulli equation. `Infinity Curve `_: From 9d51b102559eb3877ebda5be915eb77a325ec096 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 13 Jul 2024 14:40:53 +0100 Subject: [PATCH 3/5] Implement Figure Eight --- src/data_morph/shapes/factory.py | 1 + src/data_morph/shapes/points.py | 105 +++++++++++++++++++++++++------ 2 files changed, 86 insertions(+), 20 deletions(-) diff --git a/src/data_morph/shapes/factory.py b/src/data_morph/shapes/factory.py index 024d38d2..6f2bc1b0 100644 --- a/src/data_morph/shapes/factory.py +++ b/src/data_morph/shapes/factory.py @@ -46,6 +46,7 @@ class ShapeFactory: 'down_parab': points.DownParabola, 'heart': points.Heart, 'infinity': points.Infinity, + 'figure_eight': points.FigureEight, 'left_parab': points.LeftParabola, 'scatter': points.Scatter, 'right_parab': points.RightParabola, diff --git a/src/data_morph/shapes/points.py b/src/data_morph/shapes/points.py index d9c4473f..65dc16ca 100644 --- a/src/data_morph/shapes/points.py +++ b/src/data_morph/shapes/points.py @@ -2,6 +2,7 @@ import itertools from numbers import Number +from typing import Tuple import numpy as np @@ -131,24 +132,9 @@ def __init__(self, dataset: Dataset) -> None: ) -class Infinity(PointCollection): +class _LemniscateBernoulli(PointCollection): """ - Class for the infinity shape. - - .. plot:: - :scale: 75 - :caption: - This shape is generated using the panda dataset. - - from data_morph.data.loader import DataLoader - from data_morph.shapes.points import Infinity - - _ = Infinity(DataLoader.load_dataset('panda')).plot() - - Parameters - ---------- - dataset : Dataset - The starting dataset to morph into other shapes. + Implements the Lemniscate of Bernoulli Equation. Notes ----- @@ -172,13 +158,92 @@ def __init__(self, dataset: Dataset, scale: float = 0.75) -> None: x = (np.sqrt(2) * np.cos(t)) / (1 + np.square(np.sin(t))) y = (np.sqrt(2) * np.cos(t) * np.sin(t)) / (1 + np.square(np.sin(t))) - # scale by the half the widest width of the infinity - scale_factor = (x_bounds[1] - x_shift) * 0.75 + scale_factor = (x_bounds[1] - x_shift) * scale + # Apply transforms + transformed_x, transformed_y = self._shift_bernoulli(x, y) super().__init__( - *np.stack([x * scale_factor + x_shift, y * scale_factor + y_shift], axis=1) + *np.stack( + [ + transformed_x * scale_factor + x_shift, + transformed_y * scale_factor + y_shift, + ], + axis=1, + ) ) + def _shift_bernoulli( + self, x: np.ndarray, y: np.ndarray + ) -> Tuple[np.ndarray, np.ndarray]: + return x, y + + +class Infinity(_LemniscateBernoulli): + """ + Implements the Lemniscate of Bernoulli Equation, + which by default creates an infinity shape. + + .. plot:: + :scale: 75 + :caption: + This shape is generated using the panda dataset. + + from data_morph.data.loader import DataLoader + from data_morph.shapes.points import LemniscateBernoulli + + _ = LemniscateBernoulli(DataLoader.load_dataset('panda')).plot() + + Parameters + ---------- + dataset : Dataset + The starting dataset to morph into other shapes. + + Notes + ----- + The formula for the infinity shape is directly taken from Lemniscate of + Bernoulli equation. + """ + + def __str__(self) -> str: + return 'infinity' + + +class FigureEight(_LemniscateBernoulli): + """ + Class for the Figure Eight shape using a + rotated Lemniscate of Bernoulli Equation. + + .. plot:: + :scale: 75 + :caption: + This shape is generated using the panda dataset. + + from data_morph.data.loader import DataLoader + from data_morph.shapes.points import FigureEight + + _ = FigureEight(DataLoader.load_dataset('panda')).plot() + + Parameters + ---------- + dataset : Dataset + The starting dataset to morph into other shapes. + + Notes + ----- + Implements the Lemniscate of Bernoulli Equation with a transform + to draw a figure eight shape. + + See Base class for implementation specifice details. + """ + + def _shift_bernoulli( + self, x: np.ndarray, y: np.ndarray + ) -> Tuple[np.ndarray, np.ndarray]: + return y, x + + def __str__(self) -> str: + return 'figure_eight' + class LeftParabola(PointCollection): """ From 82f870e162084a81a971100f03632afb3b76576e Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 13 Jul 2024 14:41:04 +0100 Subject: [PATCH 4/5] Add tests for Infinity and Figure Eight shapes --- tests/shapes/test_points.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/shapes/test_points.py b/tests/shapes/test_points.py index 842d831d..ab5e6f54 100644 --- a/tests/shapes/test_points.py +++ b/tests/shapes/test_points.py @@ -25,6 +25,8 @@ def test_distance(self, shape, test_point, expected_distance): Test the distance() method parametrized by distance_test_cases (see conftest.py). """ + # print(shape.points[67]) + print(shape.distance(*test_point)) assert pytest.approx(shape.distance(*test_point), abs=1e-5) == expected_distance @@ -81,6 +83,22 @@ class TestHeart(PointsModuleTestBase): ] +class TestInfinity(PointsModuleTestBase): + shape_name = 'infinity' + distance_test_cases = [ + [(20, 60), 3.694037796061944], + [(11.02368017, 68.01626706), 0.0], + ] + + +class TestFigureEight(PointsModuleTestBase): + shape_name = 'figure_eight' + distance_test_cases = [ + [(20, 60), 3.2674569898782337], + [(23.01626706, 56.02368017), 0.0], + ] + + class TestScatter(PointsModuleTestBase): """Test the Scatter class.""" From dbb735c08b3c9220e3d34e8f69a2da4359d6b134 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 13 Jul 2024 15:06:06 +0100 Subject: [PATCH 5/5] Make precommit changes --- src/data_morph/shapes/points.py | 59 +++++++++++++++++++++++++-------- tests/shapes/test_points.py | 4 +++ 2 files changed, 49 insertions(+), 14 deletions(-) diff --git a/src/data_morph/shapes/points.py b/src/data_morph/shapes/points.py index 65dc16ca..2a943c9f 100644 --- a/src/data_morph/shapes/points.py +++ b/src/data_morph/shapes/points.py @@ -136,6 +136,11 @@ class _LemniscateBernoulli(PointCollection): """ Implements the Lemniscate of Bernoulli Equation. + Parameters + ---------- + dataset : Dataset + The starting dataset to morph into other shapes. + Notes ----- The formula for the infinity shape is directly taken from Lemniscate of @@ -146,7 +151,7 @@ class _LemniscateBernoulli(PointCollection): A Wolfram Web Resource. https://mathworld.wolfram.com/Lemniscate.html """ - def __init__(self, dataset: Dataset, scale: float = 0.75) -> None: + def __init__(self, dataset: Dataset) -> None: x_bounds = dataset.data_bounds.x_bounds y_bounds = dataset.data_bounds.y_bounds @@ -158,7 +163,7 @@ def __init__(self, dataset: Dataset, scale: float = 0.75) -> None: x = (np.sqrt(2) * np.cos(t)) / (1 + np.square(np.sin(t))) y = (np.sqrt(2) * np.cos(t) * np.sin(t)) / (1 + np.square(np.sin(t))) - scale_factor = (x_bounds[1] - x_shift) * scale + scale_factor = (x_bounds[1] - x_shift) * 0.75 # Apply transforms transformed_x, transformed_y = self._shift_bernoulli(x, y) @@ -175,6 +180,24 @@ def __init__(self, dataset: Dataset, scale: float = 0.75) -> None: def _shift_bernoulli( self, x: np.ndarray, y: np.ndarray ) -> Tuple[np.ndarray, np.ndarray]: + """ + A method which allows for manipulation of the Lemniscate Points after + they have been generated. + + Parameters + ---------- + x : np.ndarray + The Lemniscate X points. + y : np.ndarray + The Lemniscate Y points. + + Returns + ------- + np.ndarray + The manipulated Lemniscate X points. + np.ndarray + The manipulated Lemniscate Y points. + """ return x, y @@ -189,14 +212,9 @@ class Infinity(_LemniscateBernoulli): This shape is generated using the panda dataset. from data_morph.data.loader import DataLoader - from data_morph.shapes.points import LemniscateBernoulli + from data_morph.shapes.points import Infinity - _ = LemniscateBernoulli(DataLoader.load_dataset('panda')).plot() - - Parameters - ---------- - dataset : Dataset - The starting dataset to morph into other shapes. + _ = Infinity(DataLoader.load_dataset('panda')).plot() Notes ----- @@ -223,11 +241,6 @@ class FigureEight(_LemniscateBernoulli): _ = FigureEight(DataLoader.load_dataset('panda')).plot() - Parameters - ---------- - dataset : Dataset - The starting dataset to morph into other shapes. - Notes ----- Implements the Lemniscate of Bernoulli Equation with a transform @@ -239,6 +252,24 @@ class FigureEight(_LemniscateBernoulli): def _shift_bernoulli( self, x: np.ndarray, y: np.ndarray ) -> Tuple[np.ndarray, np.ndarray]: + """ + A method which allows for manipulation of the Lemniscate Points after + they have been generated. + + Parameters + ---------- + x : np.ndarray + The Lemniscate X points. + y : np.ndarray + The Lemniscate Y points. + + Returns + ------- + np.ndarray + The manipulated Lemniscate X points. + np.ndarray + The manipulated Lemniscate Y points. + """ return y, x def __str__(self) -> str: diff --git a/tests/shapes/test_points.py b/tests/shapes/test_points.py index ab5e6f54..c770785e 100644 --- a/tests/shapes/test_points.py +++ b/tests/shapes/test_points.py @@ -84,6 +84,8 @@ class TestHeart(PointsModuleTestBase): class TestInfinity(PointsModuleTestBase): + """Test the Infinity class.""" + shape_name = 'infinity' distance_test_cases = [ [(20, 60), 3.694037796061944], @@ -92,6 +94,8 @@ class TestInfinity(PointsModuleTestBase): class TestFigureEight(PointsModuleTestBase): + """Test the FigureEight class.""" + shape_name = 'figure_eight' distance_test_cases = [ [(20, 60), 3.2674569898782337],