Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

matplotlib Circle patch with alpha produces overlap of edge and facecolor

I'm fairly new to matplotlib and python in general and what I'm trying to do is rather basic. However, even after quite some googling time I cannot find a solution for this:

Here is the problem:

I'd like to draw a circle with different color of the border and the face, i.e. set edgecolor and facecolor differently. I'd also like to have an alpha-channel, i.e. alpha=0.5. Now while all of this works fine, the resulting circle does not have a single border color, but 2 borders are drawn. One, the outer, in the color I specified for edgecolor and the other in a color I assume to be a combination between the edgecolor and the facecolor.

Here is my code:

from matplotlib import pyplot as plt
point = (1.0, 1.0)
c = plt.Circle(point, 1, facecolor='green', edgecolor='orange', linewidth=15.0, alpha=0.5)
fig, ax = plt.subplots()
ax.add_artist(c)
plt.show()

And here is an illustration:

circle with 2 border colors

Ok, this might be a minor thing, but this 2nd border drives me nuts!

Am I doing something wrong? Is that just the way it is? Any help would be much appreciated.

like image 747
STreu Avatar asked Dec 16 '18 15:12

STreu


1 Answers

To some extent, yes, this is just the way it is. The point is that the edge is literally drawn on the edge of the circle. This means that half of the edge width is drawn on top of the face of the circle and the other half outside. If now you set alpha<1.0 you see, as you concluded correctly, an overlap of the facecolor and the edgecolor.

However, you can get rid of that 'extra border'. Below are 2 ways how to do so which one works best for you depends on what exactly you want to do.

1st Suggestion:

The simplest, IMHO, is to only set alpha for the facecolor. This can be done by setting the alpha channel for the facecolor directly and omit the alpha argument in the call of Circle. You can set the alpha-channel with the colorConverter:

from matplotlib.colors import colorConverter
import matplotlib.pyplot as plt

fig, ax = plt.subplots(figsize=(8, 8))
ax.axis('equal')
ax.set_xlim(0,1)
ax.set_ylim(0,1)
# Here you set alpha for the faceolor
fc = colorConverter.to_rgba('green', alpha=0.5)
point = (1.0, 1.0)
r = 1.0
lw = 15.0
ec = 'orange'
# NOTE: do not set alpha when calling Circle!
c = plt.Circle(point, r,fc=fc, ec=ec, lw=lw)
ax.add_artist(c)
plt.show()

enter image description here

2nd Suggestion

A more elaborated option is to 'wipe' the edge of the circle with a white edge after only plotting the face, then only plot the edge. With this approach both colours appear with the alpha channel. But note that also in this case any object that lies 'under' the edge will be completely masked by the edge:

import matplotlib.pyplot as plt
from matplotlib.collections import PatchCollection

point = (1.0, 1.0)
r = 1.0
alpha = 0.5
lw = 15.0
fc = 'green'
ec = 'orange'
# First only draw the facecolor
c_face = plt.Circle(point, r, alpha=alpha, fc=fc, lw=0.0)
# Draw a non-transparent white edge to wipe the facecolor where they overlap
c_wipe = plt.Circle(point, r, alpha=1.0, ec='white', fc='none', lw=lw)
# Now draw only the edge
c_edge = plt.Circle(point, r, alpha=alpha, fc='none', ec=ec, lw=lw)
circle_patch = PatchCollection([c_face, c_wipe, c_edge], match_original=True)
fig, ax = plt.subplots(figsize=(8, 8))
ax.axis('equal')
ax.set_xlim(0,1)
ax.set_ylim(0,1)
ax.add_artist(circle_patch)
plt.show()

enter image description here


Here is a gist that deals with this issue following the 2nd suggestion. Simply download the mod_patch.py file and off you go.

Here is how it can be used:

import matplotlib.pyplot as plt
from mod_patch import get_Patch
fig, ax = plt.subplots(figsize=(8,8))
c = get_Patch(plt.Circle, (0,0.5), 0.5, facecolor='green', edgecolor='orange', alpha=0.5, lw=15)
ax.add_artist(c)
r = get_Patch(plt.Rectangle, (0.5,0), 0.5, 0.5, facecolor='green', edgecolor='orange', alpha=0.5, lw=15)
ax.add_artist(r)
plt.show()

enter image description here

For completeness, here the definition of get_Patch:

from matplotlib.collections import PatchCollection


def get_Patch(a_Patch, *args, **kwargs):
    background_color = kwargs.pop(
        'bgc',
        kwargs.pop('background_color', 'white')
    )
    alpha = kwargs.get('alpha', 1.0)
    patches = []
    lw = kwargs.get('lw', kwargs.get('linewidth', 0.0))
    if alpha < 1.0 and lw:
        color = kwargs.get('c', kwargs.get('color', None))
        fc = kwargs.get('facecolor', kwargs.get('fc', None))
        ec = kwargs.get('edgecolor', kwargs.get('ec', None))
        face_kwargs = dict(kwargs)
        face_kwargs['fc'] = fc if fc is not None else color
        face_kwargs['lw'] = 0.0
        p_face = a_Patch(*args, **face_kwargs)
        patches.append(p_face)
        wipe_kwargs = dict(kwargs)
        wipe_kwargs['fc'] = 'none'
        wipe_kwargs['ec'] = background_color
        wipe_kwargs['alpha'] = 1.0
        p_wipe = a_Patch(*args, **wipe_kwargs)
        patches.append(p_wipe)
        edge_kwargs = dict(kwargs)
        edge_kwargs['fc'] = 'none'
        edge_kwargs['ec'] = ec if ec is not None else color
        p_edge = a_Patch(*args, **edge_kwargs)
        patches.append(p_edge)
    else:
        p_simple = a_Patch(*args, **kwargs)
        patches.append(p_simple)
    return PatchCollection(patches, match_original=True)
like image 50
jojo Avatar answered Nov 01 '22 11:11

jojo