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()
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
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()