patch facecolor does not respect alpha value #1030

Closed
keflavich opened this Issue Jul 20, 2012 · 10 comments

Comments

Projects
None yet
5 participants
@keflavich
Contributor

keflavich commented Jul 20, 2012

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

This comment has been minimized.

Show comment Hide comment
@efiring

efiring Jul 20, 2012

Owner

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.

Owner

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

This comment has been minimized.

Show comment Hide comment
@keflavich

keflavich Jul 20, 2012

Contributor

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.

Contributor

keflavich commented Jul 20, 2012

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

This comment has been minimized.

Show comment Hide comment
@mdboom

mdboom Aug 3, 2012

Owner

"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.

Owner

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

This comment has been minimized.

Show comment Hide comment
@tacaswell

tacaswell Jan 16, 2014

Owner

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

Owner

tacaswell commented Jan 16, 2014

@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

This comment has been minimized.

Show comment Hide comment
@tacaswell

tacaswell Jan 22, 2014

Owner

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

Owner

tacaswell commented Jan 22, 2014

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

@tacaswell tacaswell closed this Jan 22, 2014

@keflavich

This comment has been minimized.

Show comment Hide comment
@keflavich

keflavich Jan 22, 2014

Contributor

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

Contributor

keflavich commented Jan 22, 2014

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

This comment has been minimized.

Show comment Hide comment
@tommycarstensen

tommycarstensen Jan 15, 2016

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

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

This comment has been minimized.

Show comment Hide comment
@tacaswell

tacaswell Jan 15, 2016

Owner

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

Owner

tacaswell commented Jan 15, 2016

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

@tommycarstensen

This comment has been minimized.

Show comment Hide comment
@tommycarstensen

tommycarstensen Jan 15, 2016

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)

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

This comment has been minimized.

Show comment Hide comment
@tacaswell

tacaswell Jan 15, 2016

Owner

Does reproducing this depend on basemap?

Owner

tacaswell commented Jan 15, 2016

Does reproducing this depend on basemap?

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