Skip to content

geomotif.motifs.curves

The named curves: the ones that earned a name before they earned a use.

Hearts, lemniscates, ovals, astroids, cycloids -- shapes that turn up in optics, in mechanisms and on tombstones, and that people go looking for by name. Each one is a formula and nothing else, so each one is a base class and three lines.

Two conventions hold throughout this module.

size is the curve's largest extent. A curve with a single free scale takes size, and at size=100 its bounding box measures 100 across its longer axis -- so a heart and a butterfly composed at the same size come out the same size. Curves whose shape depends on the ratio of two numbers (:class:CassiniOval, :class:Limacon, :class:Folium) take those two numbers directly instead, because a third scale knob would only be a way of saying the same thing twice.

center is the curve's own origin, not the middle of its bounding box: the cusp of a cardioid, the crossing point of a lemniscate, the first contact point of a cycloid. That is the point the formula is written about, and the point that stays put when you change the other parameters. Call :meth:~geomotif.Design.fit if what you want is the box centered.

None of these takes a rotation. Turning a design is :meth:~geomotif.Design.transformed with :meth:~geomotif.Affine.rotate, and a per-motif copy of it would only be a worse one.

Classes:

Name Description
Heart

A heart, in either of the two shapes that go by the name.

Cardioid

The heart-shaped r = 1 + cos(theta): one circle rolled around another.

Lemniscate

Bernoulli's lemniscate: the infinity symbol.

LemniscateOfGerono

Gerono's lemniscate: the other figure eight, x**4 = x**2 - y**2.

CassiniOval

Points whose distances to two foci multiply to a constant.

Limacon

Pascal's snail, r = b + a*cos(theta), inner loop and all.

Folium

The leaf curve r = cos(theta) * (4*a*sin(theta)**2 - b).

Butterfly

Temple Fay's butterfly: twelve revolutions that draw two wings.

FishCurve

A fish, tail and all, from x = cos(t) - sin(t)**2 / sqrt(2).

BowCurve

The bow, x**4 == x**2*y - y**3: two loops pinched at the origin.

Astroid

The four-cusped star x**(2/3) + y**(2/3) == 1.

Deltoid

The three-cusped tricuspoid, Euler's curve of 1745.

Nephroid

The kidney: the two-cusped epicycloid, r = 3cos(t) - cos(3t).

Cornoid

The cornoid, x = cos(t)cos(2t), y = sin(t)(2 + cos(2t)).

Cochleoid

The snail shell r = sin(theta) / theta, coiling in on itself.

Cycloid

The path of a point on a rolling wheel's rim.

Trochoid

The path of a point fixed to a rolling wheel, on the rim or off it.

Witch

The witch of Agnesi: a bell curve with an exact algebraic definition.

Heart dataclass

Heart(size: float = 100.0, form: HeartForm = 'classic', center: Point = (0.0, 0.0), *, resolution: int | None = None)

Bases: ParametricMotif

A heart, in either of the two shapes that go by the name.

"classic" is the valentine: the 16*sin(t)**3 curve, with the dimple on top and a proper point at the bottom. "cardioid" is r = 1 - sin(theta), which is the same cardioid as :class:Cardioid stood on end -- rounder, symmetrical, and the one that falls out of the maths rather than out of a greetings card.

Parameters:

Name Type Description Default
size float

Largest extent of the curve.

100.0
form ('classic', 'cardioid')

Which heart to draw.

"classic"
center (float, float)

The curve's own origin, which for both forms is the dimple between the two lobes.

(0.0, 0.0)

Cardioid dataclass

Cardioid(size: float = 100.0, center: Point = (0.0, 0.0), *, resolution: int | None = None)

Bases: ParametricMotif

The heart-shaped r = 1 + cos(theta): one circle rolled around another.

An epicycloid with a single cusp, and the shape of the bright caustic in a coffee cup. Also the limiting case of :class:Limacon where the inner loop has shrunk to a point.

Parameters:

Name Type Description Default
size float

Largest extent of the curve.

100.0
center (float, float)

The cusp, which is where the curve's own origin sits.

(0.0, 0.0)

Lemniscate dataclass

Lemniscate(size: float = 100.0, center: Point = (0.0, 0.0), *, resolution: int | None = None)

Bases: ParametricMotif

Bernoulli's lemniscate: the infinity symbol.

The locus of points whose distances to two foci multiply to a constant -- the one case of :class:CassiniOval where the two lobes have just met. Drawn from its rational parametrization rather than from r**2 = a**2 * cos(2*theta), which goes imaginary over half its range and would have to be stitched together from two arcs.

Parameters:

Name Type Description Default
size float

Largest extent of the curve: the full width across both lobes.

100.0
center (float, float)

The crossing point in the middle.

(0.0, 0.0)

LemniscateOfGerono dataclass

LemniscateOfGerono(size: float = 100.0, center: Point = (0.0, 0.0), *, resolution: int | None = None)

Bases: ParametricMotif

Gerono's lemniscate: the other figure eight, x**4 = x**2 - y**2.

Fatter and blunter than :class:Lemniscate, and far easier to say: x = cos(t), y = sin(t)*cos(t). Worth having both -- they are drawn interchangeably and they are not the same curve.

Parameters:

Name Type Description Default
size float

Largest extent of the curve: the full width across both lobes.

100.0
center (float, float)

The crossing point in the middle.

(0.0, 0.0)

CassiniOval dataclass

CassiniOval(a: float = 70.0, b: float = 80.0, center: Point = (0.0, 0.0), *, resolution: int | None = None)

Bases: MultiCurveMotif

Points whose distances to two foci multiply to a constant.

An ellipse adds those distances; a Cassini oval multiplies them, and the difference is a shape that changes topology as the constant crosses the focal separation. Cassini proposed it for planetary orbits and was wrong, which has not stopped it being the more interesting curve.

Three regimes, and the class draws each one honestly:

  • b > a -- a single closed loop, either an oval or, once b < a*sqrt(2), the pinched peanut.
  • b < a -- two separate loops, one around each focus, returned as two strokes rather than one path with an invented bridge between them.
  • b == a -- the loops have just touched, and the curve is Bernoulli's lemniscate. Rejected here, because that case is :class:Lemniscate and it draws it better.

Parameters:

Name Type Description Default
a float

Half the distance between the foci, which sit at (-a, 0) and (a, 0) relative to center.

70.0
b float

Square root of the constant product. Compare it to a to pick the regime above.

80.0
center (float, float)

Midpoint between the foci.

(0.0, 0.0)

Limacon dataclass

Limacon(a: float = 100.0, b: float = 60.0, center: Point = (0.0, 0.0), *, resolution: int | None = None)

Bases: ParametricMotif

Pascal's snail, r = b + a*cos(theta), inner loop and all.

One knob spans a whole family. abs(a) > abs(b) gives the looped limacon, whose inner loop is drawn correctly because a negative radius reflects onto the opposite ray rather than being clipped away. a == b is the :class:Cardioid. abs(b) >= 2*abs(a) is convex, with not even a dimple left.

Parameters:

Name Type Description Default
a float

Amplitude of the cosine term: how lopsided the curve is.

100.0
b float

Constant term: the radius the curve would have if a were zero.

60.0
center (float, float)

The pole the radius is measured from.

(0.0, 0.0)

Folium dataclass

Folium(a: float = 100.0, b: float = 100.0, center: Point = (0.0, 0.0), *, resolution: int | None = None)

Bases: ParametricMotif

The leaf curve r = cos(theta) * (4*a*sin(theta)**2 - b).

Three named shapes live in two numbers: b == a is the trifolium's three petals, b == 4*a is the single-petalled simple folium, and b == 0 is the bifolium's two. Anything between them is a legitimate intermediate.

Half a revolution draws the whole thing. The other half retraces it, because r(theta + pi) == -r(theta) and a negative radius lands back on the ray it came from.

Parameters:

Name Type Description Default
a float

Scale of the petals.

100.0
b float

Petal count, in effect: see the shapes listed above.

100.0
center (float, float)

The pole all the petals meet at.

(0.0, 0.0)

Butterfly dataclass

Butterfly(size: float = 100.0, center: Point = (0.0, 0.0), *, resolution: int | None = None)

Bases: ParametricMotif

Temple Fay's butterfly: twelve revolutions that draw two wings.

r = exp(cos(theta)) - 2*cos(4*theta) + sin(theta/12)**5. The last term has twelve times the period of the rest, so the curve takes twelve turns to close and each turn lays down a slightly different outline -- which is the whole trick, and why it is the one transcendental doodle everybody recognizes.

Parameters:

Name Type Description Default
size float

Largest extent of the curve.

100.0
center (float, float)

The body, which is where the pole sits.

(0.0, 0.0)

FishCurve dataclass

FishCurve(size: float = 100.0, center: Point = (0.0, 0.0), *, resolution: int | None = None)

Bases: ParametricMotif

A fish, tail and all, from x = cos(t) - sin(t)**2 / sqrt(2).

The negative pedal of an ellipse at a particular eccentricity, which is a dry way of saying that the tail crosses itself exactly where a tail should.

Parameters:

Name Type Description Default
size float

Largest extent of the curve, nose to tail.

100.0
center (float, float)

The curve's own origin, just behind the head.

(0.0, 0.0)

BowCurve dataclass

BowCurve(size: float = 100.0, center: Point = (0.0, 0.0), *, resolution: int | None = None)

Bases: ParametricMotif

The bow, x**4 == x**2*y - y**3: two loops pinched at the origin.

Rational all the way through -- x = t - t**3, y = t**2 - t**4 -- so it needs no trigonometry and no domain surgery.

Parameters:

Name Type Description Default
size float

Largest extent of the curve: the full width across both loops.

100.0
center (float, float)

The pinch point where the two loops meet.

(0.0, 0.0)

Astroid dataclass

Astroid(size: float = 100.0, center: Point = (0.0, 0.0), *, resolution: int | None = None)

Bases: ParametricMotif

The four-cusped star x**(2/3) + y**(2/3) == 1.

A hypocycloid with four cusps, and the envelope of a ladder sliding down a wall -- which is why it turns up in string art without anyone having set out to draw it.

Parameters:

Name Type Description Default
size float

Largest extent of the curve: cusp to opposite cusp.

100.0
center (float, float)

The middle, equidistant from all four cusps.

(0.0, 0.0)

Deltoid dataclass

Deltoid(size: float = 100.0, center: Point = (0.0, 0.0), *, resolution: int | None = None)

Bases: ParametricMotif

The three-cusped tricuspoid, Euler's curve of 1745.

The hypocycloid a wheel traces rolling inside a ring three times its size, and the shape of the caustic you get reflecting parallel light off the inside of a cup.

Parameters:

Name Type Description Default
size float

Largest extent of the curve.

100.0
center (float, float)

The middle, equidistant from all three cusps.

(0.0, 0.0)

Nephroid dataclass

Nephroid(size: float = 100.0, center: Point = (0.0, 0.0), *, resolution: int | None = None)

Bases: ParametricMotif

The kidney: the two-cusped epicycloid, r = 3cos(t) - cos(3t).

The caustic of a circle lit from infinity, which is the bright cusp of light in a teacup that everyone has seen and few have named.

Parameters:

Name Type Description Default
size float

Largest extent of the curve, across the two lobes.

100.0
center (float, float)

The middle, on the line joining the cusps.

(0.0, 0.0)

Cornoid dataclass

Cornoid(size: float = 100.0, center: Point = (0.0, 0.0), *, resolution: int | None = None)

Bases: ParametricMotif

The cornoid, x = cos(t)cos(2t), y = sin(t)(2 + cos(2t)).

An oval with a pair of cusped loops tucked inside it, one near each end. A closed sextic, and one of the few named curves that looks like nothing else in the catalog.

Parameters:

Name Type Description Default
size float

Largest extent of the curve, along the oval.

100.0
center (float, float)

The middle, between the two inner loops.

(0.0, 0.0)

Cochleoid dataclass

Cochleoid(size: float = 100.0, loops: int = 4, center: Point = (0.0, 0.0), *, resolution: int | None = None)

Bases: ParametricMotif

The snail shell r = sin(theta) / theta, coiling in on itself.

Every loop touches the pole and every loop is smaller than the last, so the whole family of them nests inside the first. The curve is symmetric about the x-axis -- r(-theta) == r(theta) -- so it is drawn from -loops turns to +loops, in one stroke through the point at theta = 0.

Parameters:

Name Type Description Default
size float

Largest extent of the curve. Set by the outermost loop, so adding loops makes the picture busier rather than bigger.

100.0
loops int

Loops drawn on each side of the axis.

4
center (float, float)

The pole every loop passes through.

(0.0, 0.0)

Cycloid dataclass

Cycloid(radius: float = 40.0, arches: int = 3, center: Point = (0.0, 0.0), *, resolution: int | None = None)

Bases: ParametricMotif

The path of a point on a rolling wheel's rim.

The brachistochrone and the tautochrone at once: the curve a bead slides down fastest, and the curve it takes the same time to slide down from anywhere. Seventeenth-century mathematicians fought over it enough for it to be nicknamed the Helen of geometers.

Parameters:

Name Type Description Default
radius float

Radius of the rolling wheel. Each arch is tau * radius long and 2 * radius tall.

40.0
arches int

How many arches to roll out.

3
center (float, float)

Where the first arch touches the ground.

(0.0, 0.0)

Trochoid dataclass

Trochoid(radius: float = 40.0, arm: float = 60.0, arches: int = 3, center: Point = (0.0, 0.0), *, resolution: int | None = None)

Bases: ParametricMotif

The path of a point fixed to a rolling wheel, on the rim or off it.

arm < radius is the curtate trochoid, the gentle wave a point inside the wheel traces. arm > radius is the prolate one, whose overhanging point runs backwards once per revolution and cuts a loop. arm == radius is exactly the :class:Cycloid.

Parameters:

Name Type Description Default
radius float

Radius of the rolling wheel.

40.0
arm float

Distance from the wheel's center to the traced point.

60.0
arches int

How many revolutions to roll out.

3
center (float, float)

Where the wheel's center starts, projected onto the ground.

(0.0, 0.0)

Witch dataclass

Witch(radius: float = 50.0, extent: float = 3.0, center: Point = (0.0, 0.0), *, resolution: int | None = None)

Bases: ParametricMotif

The witch of Agnesi: a bell curve with an exact algebraic definition.

y = 8a**3 / (x**2 + 4a**2), constructed from a circle of radius a sitting on the origin. Named a witch by a translator who mistook versiera for avversiera; the curve has been stuck with it since.

It approaches its asymptote without reaching it, so it has to be cut off somewhere -- that is what extent is for.

Parameters:

Name Type Description Default
radius float

Radius of the generating circle. The peak sits at 2 * radius.

50.0
extent float

How far out to draw, in units of the peak's height. The curve has fallen to a fifth of its height by extent = 2.

3.0
center (float, float)

The point on the asymptote directly below the peak.

(0.0, 0.0)