pie
ExamplesDocs

Topics

  • Canvas
  • Reactivity
  • Broadcasting
  • Autodiff
  • Animation
  • Sorting
  • Line fit
  • Convex hull
  • Live convex hull
  • Dijkstra's algorithm
  • k-means
  • Gaussian mixture
  • PCA
  • SVM
  • Differential equations
  • Python
unitgridOpacitycanvasColororiginXoriginY

Live convex hull

The smallest convex shape around the points, like a rubber band around nails. Drag the points. Convex hull shows how it is found.

pie.clear()
pie.scalar(0, out='gridOpacity')
import numpy as np
coords = np.random.default_rng(3).uniform(-4, 4, size=(14, 2)).round(2)
pts = [pie.point(x, y, fill='crimson') for x, y in coords]

def cross(o, a, b):
    """Above 0 if b is left of the line from o to a, below 0 if right, 0 if on it."""
    return (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0])

def farther(o, a, b):
    """True if b is farther from o than a is."""
    return np.hypot(b[0] - o[0], b[1] - o[1]) > np.hypot(a[0] - o[0], a[1] - o[1])

def wrap(ps):
    """The positions in ps of the hull's corners, counterclockwise from the leftmost point."""
    start = min(range(len(ps)), key=lambda i: ps[i][0])
    hull = [start]
    for _ in range(len(ps)):
        here = ps[hull[-1]]
        best = (hull[-1] + 1) % len(ps)
        for r in range(len(ps)):
            turn = cross(here, ps[best], ps[r])
            # r is right of the line to best, or on it but farther: r is the better next corner.
            if turn < 0 or (turn == 0 and farther(here, ps[best], ps[r])):
                best = r
        if best == start:
            break
        hull.append(best)
    return hull

def padded(hull, n):
    """`hull`, made n long by repeating its first corner, so the array keeps its size as points move."""
    return hull + [hull[0]] * (n - len(hull))

def corners(*ps):
    return np.array(ps)[padded(wrap(ps), len(ps))]

def next_corners(*ps):
    """The corner after each corner, so each corner and its next one make an edge."""
    hull = wrap(ps)
    return np.array(ps)[padded(hull[1:] + hull[:1], len(ps))]

def describe(*ps):
    return f'{len(wrap(ps))} of {len(ps)} points are corners'

cs = pie.lift(corners, pie.Array[pie.Point])(*pts, hidden=True)
ns = pie.lift(next_corners, pie.Array[pie.Point])(*pts, hidden=True)
pie.triangle(cs[0], cs, ns, fill='rgba(13, 148, 136, 0.15)', stroke='none')
pie.line(cs, ns, stroke='teal')
pie.text(pie.lift(describe, str)(*pts), 0, 4.8, 14, stroke='#1c1917')
pie.text('Drag a point across the edge to add or drop a corner', 0, -4.8, 14, stroke='#1c1917')