Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

patch facecolor does not respect alpha value #1030

Closed
keflavich opened this issue Jul 20, 2012 · 13 comments
Closed

patch facecolor does not respect alpha value #1030

keflavich opened this issue Jul 20, 2012 · 13 comments

Comments

@keflavich
Copy link
Contributor

If you set the facecolor of a patch with an alpha value, e.g.

facecolor=(1,0,0,0.5)

the alpha value is ignored; only the patch's _alpha property is used. This makes it impossible to have a patch with an opaque edge and a (partially) transparent fill.

I've only tested this on the TkAgg backend.

@efiring
Copy link
Member

efiring commented Jul 20, 2012

This is an unfortunate side effect of the drawing model in mpl; a patch is drawn with a renderer Graphics Context, which has a single apha value that it uses for both the edge and the face. If the edge alpha is not zero, then it is used; else, the face alpha is used. If we keep this model, then the way to handle different edge and face alpha would be to split the draw up into two sequential operations, each with its own GC. The alternative would seem to be to modify the drawing model such that rgba would always be handled directly inside the backend, without using the GC to override any existing alpha in an rgba value.

@keflavich
Copy link
Contributor Author

That sounds like a difficult change. I don't really have any input on whether it should be done, since I don't know enough about the mpl internals.

Right now, my workaround is to duplicate the patch and make the facecolor of the second patch 'none' in order to get the edge fully opaque. However, this has a negative side-effect as well: in the legend, the patch does not match the figure. I think there's a way to work around that as well, but it would involve editing one of the patches within the legend directly.

@mdboom
Copy link
Member

mdboom commented Aug 3, 2012

"Rationalizing color and alpha handling" would make for a great MEP :) If there isn't a volunteer to write this up, I just might take a crack at it.

@tacaswell
Copy link
Member

@keflavich Is this still true? There was recently (~6-10 months) an bunch of work (by I think @cimarronm) on this sort of thing.

@tacaswell
Copy link
Member

This appears to work in current master. Please reopen if I miss-understood the OP request.

@keflavich
Copy link
Contributor Author

OK, glad to hear it, thanks @tacaswell - sorry I didn't get to check this. If I come across it again, I will reopen.

@tommycarstensen
Copy link

I still have this issue with python3 and matplotlib 1.5 when following this example and changing the alpha:
http://stackoverflow.com/a/16563397/778533

@tacaswell
Copy link
Member

Can you provide a minimal example? It is hard to tell exactly what you are doing from your comment and the link.

@tommycarstensen
Copy link

Of course. I should have done that from the beginning.

  1. Download and unzip:

http://www.naturalearthdata.com/http//www.naturalearthdata.com/download/10m/cultural/ne_10m_admin_0_countries.zip

  1. Run this code with python3 and check Angola, Afghanistan, Algeria and Albania afterwards (they are darker):
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap

import matplotlib as mpl
mpl.rcParams['font.size'] = 10.
mpl.rcParams['font.family'] = 'Verdana'
mpl.rcParams['axes.labelsize'] = 8.
mpl.rcParams['xtick.labelsize'] = 6.
mpl.rcParams['ytick.labelsize'] = 6.

fig = plt.figure(figsize=(11.7,8.3))
plt.subplots_adjust(left=0.05,right=0.95,top=0.90,bottom=0.05,wspace=0.15,hspace=0.05)
ax = plt.subplot(111)
x1 = -180.0
x2 = 180.
y1 = -60.
y2 = 85.

m = Basemap(resolution='i',projection='merc', llcrnrlat=y1,urcrnrlat=y2,llcrnrlon=x1,urcrnrlon=x2,lat_ts=(x1+x2)/2)
m.drawcountries(linewidth=0.5)
m.drawcoastlines(linewidth=0.5)

from matplotlib.collections import LineCollection
import shapefile

print('read')
sf = shapefile.Reader('ne_10m_admin_0_sovereignty.shp')
print('shapes')
shapes = sf.shapes()
print('records')
records = sf.records()
d_facecolors = {
    'Africa': ('#e41a1c',),
    'Asia': ('#377eb8',),
    'Europe': ('#4daf4a',),
    'South America': ('#984ea3',),
    'North America': ('#ffff33',),
    'Antarctica': 'white',
    'Seven seas (open ocean)': 'white',
    'Oceania': ('#ff7f00',),
    }
fields = sf.fields
for i, field in enumerate(fields):
    print(i, field)
index = fields.index(['ADM0_A3_WB', 'N', 6, 2])

import matplotlib



set_countries = set()
for record in records:
    set_countries.add(record[3])

for country in list(reversed(sorted(set_countries))):
    for record, shape in zip(records, shapes):
        continent = record[index]
        if record[3] != country:
            continue
        facecolors = [d_facecolors[record[index]]]
        lons, lats = zip(*shape.points)
        ## transpose the data and convert from lat/lon to meters
        data = np.array(m(lons, lats)).T

        ## initiate patches
        ptchs = [[]]
        i_ptchs = -1
        ## get points
        pts = np.array(shape.points)
        pts = data
        ## get parts
        prt = shape.parts
        ## extend length...???
        par = list(prt) + [pts.shape[0]]
        for pij in range(len(prt)):
            path = matplotlib.patches.Polygon(pts[par[pij]:par[pij+1]])

            ## Color Greenland like North America
            if country == 'Denmark' and lons[par[pij]] < 0:
                facecolors.append(d_facecolors['North America'])
                ptchs.append([])

            ## Color French Guiana as South America
            if country == 'France':
                if lons[par[pij]] < 0:
                    if len(facecolors) == 1:
                        facecolors.append(d_facecolors['South America'])
                        ptchs.append([])
                    i_ptchs = -1
                else:
                    i_ptchs = 0

            ptchs[i_ptchs].append(path)
    #        ptchs.append(matplotlib.patches.PathPatch(path))
            if country == 'France':
                print('France', len(ptchs), lons[par[pij]])
        for facecolor, ptch in zip(facecolors, ptchs):
            ax.add_collection(
                matplotlib.collections.PatchCollection(
                    ptch,
                    facecolor=facecolor,
                    edgecolor='k',
                    linewidths=.01,
                    alpha = 0.5,
                    clip_on = True,
                    match_original = True,
                    ),
    ##            set_visible = True,
                )
        continue

        if len(shape.parts) == 1:
            segs = [data,]
        else:
            segs = []
            for i in range(1,len(shape.parts)):
                index1 = shape.parts[i-1]
                index2 = shape.parts[i]
                if record[3] == 'Argentina':
                    print(i, index1, index2, len(shape.parts))
                segs.append(data[index1:index2])
                if record[3] == 'Argentina':
                    print(
                        i,
                        data[index1:index2][0],
                        data[index1:index2][-1],
                        data[index1:index2][0] == data[index1:index2][-1])

            segs.append(data[index2:])
            if record[3] == 'Argentina':
                print(index2, data[index2:][0], data[index2:][-1], data[index2:][0]==data[index2:][-1])
    #    print(record[3], segs[0][0], segs[-1][-1], segs[0][0]==segs[-1][-1])

        lines = LineCollection(segs,antialiaseds=(1,))
        lines.set_facecolors(facecolor)
        lines.set_edgecolors('k')
        lines.set_linewidth(0.1)
    #    ax.add_collection(lines)

plt.savefig('tutorial10.png', dpi=300)

@tacaswell
Copy link
Member

Does reproducing this depend on basemap?

@AndrewDDavis
Copy link

I've run afoul of this issue when trying to set the facecolor alpha of boxplot patches. As a workaround, one can set patch.set_alpha(None), and then the alpha of patch.set_facecolor(RGBA) will be respected.

@timhoffm
Copy link
Member

@AndrewDDavis does reproducing this depend on basemap? Could you post a minimal example on the bug?

@AndrewDDavis
Copy link

AndrewDDavis commented Jun 3, 2019

The issue arose for me when setting the patch colors in boxplots created by pandas df.plot.box(), which sets the alpha of the box patches to 1. Matplotlib's boxplot() did not have an issue for me. For example:

import numpy as np 
import matplotlib.pyplot as plt 
import pandas as pd

x = [np.random.randn(10) for _ in range(2)]

# matplotlib
fig, ax = plt.subplots()
bp = ax.boxplot(x, patch_artist=True)

box = bp['boxes'][0]
box.get_alpha()         # no result
box.set_facecolor([1., 0, 0, 0.5])
box.get_facecolor()     # (1.0, 0.0, 0.0, 0.5)  <- as expected

box.set_alpha(1)
box.get_facecolor()     # (1.0, 0.0, 0.0, 1) 

# pandas
df = pd.DataFrame(data=x).transpose()

fig2, ax2 = plt.subplots()
bp2 = df.plot.box(ax=ax2, patch_artist=True, return_type='dict')

box2 = bp2['boxes'][0]
box2.get_alpha()         # 1
box2.set_facecolor([1., 0, 0, 0.5])
box2.get_facecolor()     # (1.0, 0.0, 0.0, 1)  <- suprising!

box2.set_alpha(None)
box2.get_facecolor()    # (1.0, 0.0, 0.0, 0.5)

If you know that the patch's alpha value is set, and that it will supersede the facecolor value, this behavior is consistent. If you don't realize this, it's surprising when you set a facecolor and the alpha isn't respected.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants