0

I'm looking for advice on how to create a plot using Matplotlib patches where the transparency can be applied uniformly to all patches. Specifically, if I have overlapping patches, I would like the alpha value to be applied to the union of the two patches, rather than applied individually. The intersection region should look the same as the the individual structures and if there are differences in the patch definition (such as color), the last patch added to the collection should take precedence.

Below is a simple example of what doesn't work.

import matplotlib.pylab as plt
import matplotlib as mpl

f, (ax1, ax2) = plt.subplots(1, 2, tight_layout=True)

# assign alpha to individual patches
patch1 = mpl.patches.Rectangle((0.4, 0.4), .5, .5, alpha=0.5)
patch2 = mpl.patches.Rectangle((0.1, 0.1), .5, .5, alpha=0.5)
ax1.add_patch(patch1)
ax1.add_patch(patch2)
ax1.set_title('individual patches')

# try assigning alpha to collection
patch3 = mpl.patches.Rectangle((0.4, 0.4), .5, .5)
patch4 = mpl.patches.Rectangle((0.1, 0.1), .5, .5)
collection = mpl.collections.PatchCollection([patch3, patch4], alpha=0.5)
ax2.add_collection(collection)
ax2.set_title('patch collection')

# overlap region is darker
plt.show()

Alpha overlap

Based on some other discussions online, I have looked into some other techniques, such rendering an image from the intersection with alpha=1 and then plotting this image with alpha < 1, but because the image would be quite large in my application, I'd prefer to use geometric primitives, such as Patches.

Any ideas on how to make this work?

twhughes
  • 456
  • 6
  • 17
  • do you need to use alpha (i.e. are there other plot elements you need the transparency for?) If not, and you are just plotting these patches, you could achieve what you are asking by just plotting a solid colour without using the alpha channel – tmdavison Oct 05 '21 at 16:29
  • Unfortunately I do need alpha as these patches are being applied on top of a pcolormesh or imshow that I want to be visible across the whole image. – twhughes Oct 05 '21 at 16:37
  • Does this answer your question? [matplotlib Circle patch with alpha produces overlap of edge and facecolor](https://stackoverflow.com/q/53803531/7758804) – Trenton McKinney Oct 05 '21 at 17:54
  • Unfortunately not, but I had read through that a few times to try to understand it. In that question, the edge was overlapping with the face, which is different from my case where to patches are intersecting. – twhughes Oct 05 '21 at 21:03
  • 1
    Does this answer your question? [Plot unions of polygons in matplotlib](https://stackoverflow.com/questions/34475431/plot-unions-of-polygons-in-matplotlib) – Stef Oct 07 '21 at 09:37
  • That's very helpful, thanks. I might have to switch to plotly for some of this. However, one caveat is that each patch will have potentially different face color, so that may or may not be supported. In the link it looks like you need to do the union before setting face color and alpha which might be a problem. – twhughes Oct 07 '21 at 15:38
  • @Stef thanks for your pointing me in the right direction, I came up with a solution that is face color aware, answered below. Appreciate the help! – twhughes Oct 07 '21 at 17:23

1 Answers1

1

In case it helps anyone, I came up with a solution based on a modification of this discussion.

enter image description here

import matplotlib.pylab as plt
import shapely.geometry as sg
import shapely.ops as so
from typing import List

class Structure:
    """ stores a shapely shape and a facecolor """
    def __init__(self, shape, fc):
        self.shape = shape
        self.fc = fc

#constructing the first rect as a polygon
bot_left = Structure(shape=sg.box(0.1,0.1,0.6,0.6), fc='blue')

#a shortcut for constructing a rectangular polygon
top_right = Structure(shape=sg.box(0.4,0.4,0.9,0.9), fc='red')

#constructing the first rect as a polygon
top_left = Structure(shape=sg.box(0.1,0.4,0.6,0.9), fc='brown')

#constructing the first rect as a polygon
bot_right = Structure(shape=sg.box(0.4,0.05,0.9,0.6), fc='blue')

def overlap_union(structures: List[Structure]) -> List[Structure]:
    """ returns polygons """
    structs_exist = []
    for s in structures:
        for _s in structs_exist:
            intersection = s.shape & _s.shape
            _s.shape = _s.shape - intersection
        structs_exist.append(s)
    return structs_exist


def overlap_union_merge(structures: List[Structure]) -> List[Structure]:
    """ merges two polygons if they intersect with same facecolor """
    structs_exist = []
    for s in structures:
        append_this_struct = True
        for _s in structs_exist:
            if s.shape & _s.shape:
                if s.fc == _s.fc:
                    _s.shape = _s.shape | (s.shape - _s.shape)
                    append_this_struct = False
                else:
                    _s.shape = _s.shape - s.shape
        if append_this_struct:
            structs_exist.append(s)
    return structs_exist


structs_before = [bot_left, top_right, top_left, bot_right]

plot_edges = True
ec = 'k' if plot_edges else 'none'

_, (ax1, ax2, ax3) = plt.subplots(1, 3, tight_layout=True, figsize=(15, 5))

for struct in structs_before:
    xs, ys = struct.shape.exterior.xy
    ax1.fill(xs, ys, alpha=0.4, fc=struct.fc, ec=ec)
    ax1.set_title('naive (no union)')

structs_after = overlap_union(structs_before)
for struct in structs_after:
    xs, ys = struct.shape.exterior.xy
    ax2.fill(xs, ys, alpha=0.4, fc=struct.fc, ec=ec)
    ax2.set_title('simple version')

structs_after_merge = overlap_union_merge(structs_before)
for struct in structs_after_merge:
    xs, ys = struct.shape.exterior.xy
    ax3.fill(xs, ys, alpha=0.4, fc=struct.fc, ec=ec)
    ax3.set_title('merged version')

for ax in (ax1, ax2, ax3):
    ax.set_xlim(0, 1)
    ax.set_ylim(0,1)
plt.show()
twhughes
  • 456
  • 6
  • 17