How to fill the space between two arcs and two segments in matplotlib?

Viewed 50

I know this kind of questions has been posted several times, but hear me out first before you decide anything.

In short, I want to turn this:

enter image description here

Into this:

enter image description here

All the curves are a sixth of a circle, the three poly-lines approximate three arms of a spiral, I need to fill all regions defined by two arms and two arcs.

I had viewed a bunch of questions on this site dealing similar problems, all of them mentioned ax.fill_between method, but it involves NumPy arrays, and I did not use NumPy arrays to do the calculations.

The function that calculates the things is the following (minus the bounding box part):

def polygon_spiral(unit, iterations, num_colors=12):
    step = 1530/num_colors
    palette = ['#'+spectrum_position(round(step*i), 1) for i in range(num_colors)]
    colors = [palette[0]]
    radius = unit*cos(30)/1.5
    y1 = -radius/2
    x1 = -unit/2
    x2 = unit/2
    points = [(0, radius), (x2, y1), (x1, y1)]
    polygons = [points]
    lines = [[(0, 0), (x1/2, radius/4)], [(0, 0), (0, y1)], [(0, 0), (x2/2, radius/4)]]
    side = 4
    left_start, left_end = points[0], points[2]
    down_start, down_end = points[2], points[1]
    right_start, right_end = points[1], points[0]
    for i in range(iterations):
        left = make_polygon(left_start, left_end, side)
        down = make_polygon(down_start, down_end, side)
        right = make_polygon(right_start, right_end, side)
        polygons.append(left)
        polygons.append(down)
        polygons.append(right)
        colors.extend([palette[(i+1)%num_colors]]*3)
        half = (side/2).__ceil__()
        left_start, left_end = left[half-1:half+1]
        lines[0].append(mid(left_start, left_end))
        down_start, down_end = down[half-1:half+1]
        lines[1].append(mid(down_start, down_end))
        right_start, right_end = right[half-1:half+1]
        lines[2].append(mid(right_start, right_end))
        side += 1

    indices = {0: (1, 2), 1: (0, 2), 2: (0, 1)}
    arcs = []
    for i in range(iterations+1, 0, -1):
        points = [lines[j][i] for j in range(3)]
        (x0, y0), (x1, y1), (x2, y2) = points
        dx = x1 - x0
        dy = y1 - y0
        r = (dx*dx + dy*dy)**.5
        
        for k, v in indices.items():
            a, b = points[k]
            thetas = []
            for h in v:
                c, d = points[h]
                angle = atan2(c - a, d - b)
                if angle < 0:
                    angle += 360
                thetas.append(angle)
            theta1, theta2 = thetas
            if theta1 > theta2:
                theta1, theta2 = theta2, theta1
            if not np.isclose(theta2 - theta1, 60):
                theta1, theta2 = theta2 - 360, theta1
            arcs.append([(a, b), r, theta1, theta2])
    
    return {'polygons': polygons, 'lines': lines, 'arcs': arcs, 'colors': colors}

You can see the full script on GitHub.

The point is the lines are stored as three lists of coordinates (stored as (x, y) tuples) of discrete vertices, and they are drawn via matplotlib.collections.LineCollection, and the arcs are described by the coordinate of its center, its radius, theta1 and theta2, and drawn via matplotlib.patches.Arc.

I did not use arrays anywhere in this script, how can I fill the areas defined by two arcs and two arms without using arrays and fill_between, and if that's not possible, how do I do the same calculations in a vectorized way and use fill_between to fill the areas?

1 Answers

I solved it again.

enter image description here

Basically, I filled the regions using polygons.

Each of the regions are enclosed by four lines, two straight lines and two arcs. Both arcs span 60°.

In the arcs list, the indices of the outer and inner arcs that enclose the area have a difference of 3.

So I took each pairs of arcs with index difference of 3, and made a polygon from them.

How did I make the polygons?

The arcs span 60°, I sampled 61 points on the first arc, each pair of adjacent points have an angular difference of 1°, and sampled 61 points on the second arc in the other direction:

L = len(arcs)
for i in range(3):
    level = 0
    for first, second in zip(arcs[i:L:3], arcs[i+3:L:3]):
        f = 1 - level / iterations
        r, g, b = colorsys.hsv_to_rgb(hues[i], f, f)
        strip_colors.append('#{:02x}{:02x}{:02x}'.format(round(r*255), round(g*255), round(b*255)))
        vertices = []
        (a, b), r, theta1, theta2 = first
        for n in range(61):
            angle = theta1+n
            vertices.append((a+r*cos(angle), b+r*sin(angle)))
        (a, b), r, theta1, theta2 = second
        for n in range(61):
            angle = theta2-n
            vertices.append((a+r*cos(angle), b+r*sin(angle)))
        strips.append(vertices)
        level += 1

The full code is here.

Related