Home > front end >  Apply Alpha Uniformly on Intersecting Matplotlib Patches
Apply Alpha Uniformly on Intersecting Matplotlib Patches

Time:10-08

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?

CodePudding user response:

In case it helps anyone, I came up with a solution based on a modification of 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()
  • Related