9

In matplotlib, I would like draw an filled arc which looks like this:

Filled Arc Example

The following code results in an unfilled line arc:

import matplotlib.patches as mpatches
import matplotlib.pyplot as plt

fg, ax = plt.subplots(1, 1)

pac = mpatches.Arc([0, -2.5], 5, 5, angle=0, theta1=45, theta2=135)
ax.add_patch(pac)

ax.axis([-2, 2, -2, 2])
ax.set_aspect("equal")
fg.canvas.draw()

The documentation says that filled arcs are not possible. What would be the best way to draw one?

Dietrich
  • 5,241
  • 3
  • 24
  • 36

5 Answers5

15

@jeanrjc's solution almost gets you there, but it adds a completely unnecessary white triangle, which will hide other objects as well (see figure below, version 1).

This is a simpler approach, which only adds a polygon of the arc:

Basically we create a series of points (points) along the edge of the circle (from theta1 to theta2). This is already enough, as we can set the close flag in the Polygon constructor which will add the line from the last to the first point (creating a closed arc).

import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
import numpy as np

def arc_patch(center, radius, theta1, theta2, ax=None, resolution=50, **kwargs):
    # make sure ax is not empty
    if ax is None:
        ax = plt.gca()
    # generate the points
    theta = np.linspace(np.radians(theta1), np.radians(theta2), resolution)
    points = np.vstack((radius*np.cos(theta) + center[0], 
                        radius*np.sin(theta) + center[1]))
    # build the polygon and add it to the axes
    poly = mpatches.Polygon(points.T, closed=True, **kwargs)
    ax.add_patch(poly)
    return poly

And then we apply it:

fig, ax = plt.subplots(1,2)

# @jeanrjc solution, which might hide other objects in your plot
ax[0].plot([-1,1],[1,-1], 'r', zorder = -10)
filled_arc((0.,0.3), 1, 90, 180, ax[0], 'blue')
ax[0].set_title('version 1')

# simpler approach, which really is just the arc
ax[1].plot([-1,1],[1,-1], 'r', zorder = -10)
arc_patch((0.,0.3), 1, 90, 180, ax=ax[1], fill=True, color='blue')
ax[1].set_title('version 2')

# axis settings
for a in ax:
    a.set_aspect('equal')
    a.set_xlim(-1.5, 1.5)
    a.set_ylim(-1.5, 1.5)

plt.show()

Result (version 2):

enter image description here

Community
  • 1
  • 1
hitzg
  • 12,133
  • 52
  • 54
  • Just curious, why the downvote?! How does that not address the question? – hitzg Jun 05 '15 at 08:26
  • Well, I didn't downvote. I'm rather happy that you manage to improve my solution, although if you could detail a bit how you populate `points`, it would be even nicer. btw, if you downvoted my answer I can't see any reason why. – jrjc Jun 05 '15 at 08:55
  • No, I didn't downvote yours, in fact im going to upvote it. Your solution is nice too, I like that you were able to leverage existing functionality (the wedge). There was someone else on a downvoting spree (for no apparent reason). Regarding the population of `points` you are right, I'll make that part simpler. – hitzg Jun 05 '15 at 08:59
  • Thanks for addressing the covering up problem problem in @jeanrc's answer (To avoid up/down-voting issue: I am giving you +1). – Dietrich Jun 05 '15 at 09:19
10

You can use fill_between to achieve this

 import matplotlib.patches as mpatches
 import matplotlib.pyplot as plt
 import numpy as np

 fg, ax = plt.subplots(1, 1)

 r=2.
 yoff=-1
 x=np.arange(-1.,1.05,0.05)
 y=np.sqrt(r-x**2)+yoff

 ax.fill_between(x,y,0)

 ax.axis([-2, 2, -2, 2])
 ax.set_aspect("equal")
 fg.canvas.draw()

Play around with r and yoff to move the arc

enter image description here

EDIT:

OK, so you want to be able to plot arbitrary angles? You just need to find the equation of the chord, rather than using a flat line like above. Here's a function to do just that:

import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
import numpy as np

fg, ax = plt.subplots(1, 1)

col='rgbkmcyk'

def filled_arc(center,r,theta1,theta2):

    # Range of angles
    phi=np.linspace(theta1,theta2,100)

    # x values
    x=center[0]+r*np.sin(np.radians(phi))

    # y values. need to correct for negative values in range theta=90--270
    yy = np.sqrt(r-x**2)
    yy = [-yy[i] if phi[i] > 90 and phi[i] < 270 else yy[i] for i in range(len(yy))]

    y = center[1] + np.array(yy)

    # Equation of the chord
    m=(y[-1]-y[0])/(x[-1]-x[0])
    c=y[0]-m*x[0]
    y2=m*x+c

    # Plot the filled arc
    ax.fill_between(x,y,y2,color=col[theta1/45])

# Lets plot a whole range of arcs
for i in [0,45,90,135,180,225,270,315]:
    filled_arc([0,0],1,i,i+45)

ax.axis([-2, 2, -2, 2])
ax.set_aspect("equal")
fg.savefig('filled_arc.png')

And here's the output:

enter image description here

tmdavison
  • 64,360
  • 12
  • 187
  • 165
  • Just curious, why the downvote? Does this not answer the question? – tmdavison Jun 04 '15 at 21:30
  • 1
    I guess it's because although you manage to recreate the shape asked (in a clever way), it does not allow you to do a filled arc from `theta1=0` to `theta2=45` for instance. Or if you could, please describe a bit more. – jrjc Jun 05 '15 at 06:45
  • you can, you just need a bit of imagination. I'll update my answer now. – tmdavison Jun 05 '15 at 09:01
  • ``fill_between()`` is a nice idea. I only asked implicitly for the coordinate transformations, so it is valid (To avoid further rating confusions: I am giving you +1 for it). – Dietrich Jun 05 '15 at 09:07
  • Using `fill_between()` is a way of doing it too. Agreed. However, the result is the exact same `patch` as I create in my answer. – hitzg Jun 05 '15 at 09:14
  • 1
    thanks. hopefully the updated answer allows you to do your coordinate transformations – tmdavison Jun 05 '15 at 09:15
  • Thanks for the update. As already noted, your answer and @hitzg's answer are equal, though I going to accept hitzg's answer, because it came first and I am only allowed to choose one. – Dietrich Jun 06 '15 at 10:45
  • @Dietrich: Fair enough. Glad you got it doing what what want! – tmdavison Jun 06 '15 at 16:56
6

Here's a simpler workaround. Use the hatch argument in your mpatches.Arc command. If you repeat symbols with the hatch argument it increases the density of the patterning. I find that if you use 6 dashes, '-', or 6 dots, '.' (others probably also work), then it solidly fills in the arc as desired. When I run this

import matplotlib.patches as mpatches
import matplotlib.pyplot as plt 

plt.axes()
pac = mpatches.Arc([0, -2.5], 5, 5, 45, theta1=45, theta2=135, hatch = '......')
plt.gca().add_patch(pac)
pac.set_color('cyan')
plt.axis('equal')
plt.show()

I get this:

Arc filled with dense dot hatch and rotated 45 degrees just for show

Quebert
  • 461
  • 5
  • 3
  • This solution is image resolution dependent, with enough zoom is always possible to notice the points. – heracho Feb 10 '17 at 00:15
  • If I'm not wrong, this is the only answer in this thread that fill an ellipse arc and not a circle arc – heracho Feb 10 '17 at 00:17
  • Heracho, 1) True, unfortunately. That is why I introduced it as a "simpler workaround". Different hatching styles can help make it appear thicker. It's annoying that the edge color doesn't match the pseudo-solid color filling the shape. 2) Looks like this solution is still the only one that uses `mpatches.Arc`, so I suppose you are right! – Quebert Feb 16 '17 at 22:03
5

You can draw a wedge, and then hide part of it with a triangle:

import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
import numpy as np

def filled_arc(center, radius, theta1, theta2, ax, color):

    circ = mpatches.Wedge(center, radius, theta1, theta2, fill=True, color=color)
    pt1 = (radius * (np.cos(theta1*np.pi/180.)) + center[0],
           radius * (np.sin(theta1*np.pi/180.)) + center[1])
    pt2 = (radius * (np.cos(theta2*np.pi/180.)) + center[0],
           radius * (np.sin(theta2*np.pi/180.)) + center[1])
    pt3 = center
    pol = mpatches.Polygon([pt1, pt2, pt3], color=ax.get_axis_bgcolor(),
                           ec=ax.get_axis_bgcolor(), lw=2 )
    ax.add_patch(circ)
    ax.add_patch(pol)

and then you can call it:

fig, ax = plt.subplots(1,2)
filled_arc((0,0), 1, 45, 135, ax[0], "blue")
filled_arc((0,0), 1, 0, 40, ax[1], "blue")

and you get:

filled_arc

or:

fig, ax = plt.subplots(1, 1)
for i in range(0,360,45):
    filled_arc((0,0), 1, i, i+45, ax, plt.cm.jet(i))

and you get:

enter image description here

HTH

jrjc
  • 21,103
  • 9
  • 64
  • 78
  • 1
    While this gets the job done in this basic example, it will give you unwanted results if you have other artists in your figure which you want to be below the arc (as the white triangle hides them too). – hitzg Jun 05 '15 at 07:57
  • I like the approach of combining wedge and triangle. As @hitzg noted there might be problems of covering other objects. +1 for the nice pictures – Dietrich Jun 05 '15 at 09:13
0

The command ax.get_axis_bgcolor() needs to be replaced by ax.get_fc() for newer matplotlib.

bguiz
  • 27,371
  • 47
  • 154
  • 243