Skip to content

Commit b7a786c

Browse files
Add figure eight target shape (#265)
Co-authored-by: MauriceDeVr <[email protected]>
1 parent 9391f31 commit b7a786c

File tree

4 files changed

+75
-0
lines changed

4 files changed

+75
-0
lines changed

src/data_morph/shapes/factory.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
Club,
2929
DotsGrid,
3030
DownParabola,
31+
FigureEight,
3132
Heart,
3233
LeftParabola,
3334
RightParabola,
@@ -65,6 +66,7 @@ class ShapeFactory:
6566
Diamond,
6667
DotsGrid,
6768
DownParabola,
69+
FigureEight,
6870
Heart,
6971
HighLines,
7072
HorizontalLines,

src/data_morph/shapes/points/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from .club import Club
44
from .dots_grid import DotsGrid
5+
from .figure_eight import FigureEight
56
from .heart import Heart
67
from .parabola import DownParabola, LeftParabola, RightParabola, UpParabola
78
from .scatter import Scatter
@@ -12,6 +13,7 @@
1213
'Club',
1314
'DotsGrid',
1415
'DownParabola',
16+
'FigureEight',
1517
'Heart',
1618
'LeftParabola',
1719
'RightParabola',
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
"""Figure eight shape."""
2+
3+
import numpy as np
4+
5+
from ...data.dataset import Dataset
6+
from ..bases.point_collection import PointCollection
7+
8+
9+
class FigureEight(PointCollection):
10+
"""
11+
Class for the figure eight shape.
12+
13+
.. plot::
14+
:scale: 75
15+
:caption:
16+
This shape is generated using the panda dataset.
17+
18+
from data_morph.data.loader import DataLoader
19+
from data_morph.shapes.points import FigureEight
20+
21+
_ = FigureEight(DataLoader.load_dataset('panda')).plot()
22+
23+
Parameters
24+
----------
25+
dataset : Dataset
26+
The starting dataset to morph into other shapes. For datasets
27+
with larger *y* ranges than *x* ranges, the figure eight will be
28+
vertical; otherwise, it will be horizontal.
29+
30+
Notes
31+
-----
32+
This shape uses the formula for the `Lemniscate of Bernoulli
33+
<https://en.wikipedia.org/wiki/Lemniscate_of_Bernoulli>`_.
34+
"""
35+
36+
name = 'figure_eight'
37+
38+
def __init__(self, dataset: Dataset) -> None:
39+
x_shift, y_shift = dataset.data_bounds.center
40+
x_range, y_range = dataset.data_bounds.range
41+
42+
t = np.linspace(-3.1, 3.1, num=80)
43+
44+
focal_distance = max(x_range, y_range) * 0.3
45+
half_width = focal_distance * np.sqrt(2)
46+
47+
x = (half_width * np.cos(t)) / (1 + np.square(np.sin(t)))
48+
y = x * np.sin(t)
49+
50+
super().__init__(
51+
*np.stack([x, y] if x_range >= y_range else [y, x], axis=1)
52+
+ np.array([x_shift, y_shift])
53+
)

tests/shapes/test_points.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,3 +190,21 @@ class TestSpiral(PointsModuleTestBase):
190190
((25, 65), 1.3042797087884075),
191191
((-30, 100), 52.14470630148412),
192192
)
193+
194+
195+
class TestFigureEight(PointsModuleTestBase):
196+
"""Test the FigureEight class."""
197+
198+
shape_name = 'figure_eight'
199+
distance_test_cases = (
200+
((17.79641748, 67.34954701), 0),
201+
((21.71773824, 63.21594749), 0),
202+
((22.20358252, 67.34954701), 0),
203+
((19.26000438, 64.25495015), 0),
204+
((19.50182914, 77.69858052), 0),
205+
((0, 0), 55.70680898398098),
206+
((19, 61), 1.9727377843832639),
207+
((19, 64), 0.34685744033355576),
208+
((25, 65), 3.6523121397065657),
209+
((18, 40), 12.392782544116978),
210+
)

0 commit comments

Comments
 (0)