Masking by condition
Below is an example on how to use tricontourf
with masking to obtain a concave shape without interpolated portions outside the data. It relies on the ability to mask the data depending on a condition.
import matplotlib.pyplot as plt
import matplotlib.tri as tri
import numpy as np
# create some data
rawx = np.random.rand(500)
rawy = np.random.rand(len(rawx))
cond01 = (rawx-1)**2 + rawy**2 <=1
cond02 = (rawx-0.7)**2 + rawy**2 >0.3
x = rawx[cond01 & cond02]
y = rawy[cond01 & cond02]
f = lambda x,y: np.sin(x*4)+np.cos(y)
z = f(x,y)
# now, x,y are points within a partially concave shape
triang0 = tri.Triangulation(x, y)
triang = tri.Triangulation(x, y)
x2 = x[triang.triangles].mean(axis=1)
y2 = y[triang.triangles].mean(axis=1)
#note the very obscure mean command, which, if not present causes an error.
#now we need some masking condition.
# this is easy in this case where we generated the data according to the same condition
cond1 = (x2-1)**2 + y2**2 <=1
cond2 = (x2-0.7)**2 + (y2)**2 >0.3
mask = np.where(cond1 & cond2,0,1)
# apply masking
triang.set_mask(mask)
fig, (ax, ax2) = plt.subplots(ncols=2, figsize=(6,3))
ax.set_aspect("equal")
ax2.set_aspect("equal")
ax.tricontourf(triang0, z, cmap="Oranges")
ax.scatter(x,y, s=3, color="k")
ax2.tricontourf(triang, z, cmap="Oranges")
ax2.scatter(x,y, s=3, color="k")
ax.set_title("tricontourf without mask")
ax2.set_title("tricontourf with mask")
ax.set_xlim(0,1)
ax.set_ylim(0,1)
ax2.set_xlim(0,1)
ax2.set_ylim(0,1)
plt.show()
Masking by maximum side-length
If you do not have access to the exact condition, but have a maximum side-length (distance) between points, the following would be a solution. It would mask out all triangles for which at least one side is longer than some maximum distance. This can be well applied if the point density is rather high.
import matplotlib.pyplot as plt
import matplotlib.tri as tri
import numpy as np
# create some data
rawx = np.random.rand(500)
rawy = np.random.rand(len(rawx))
cond01 = (rawx-1)**2 + rawy**2 <=1
cond02 = (rawx-0.7)**2 + rawy**2 >0.3
x = rawx[cond01 & cond02]
y = rawy[cond01 & cond02]
f = lambda x,y: np.sin(x*4)+np.cos(y)
z = f(x,y)
# now, x,y are points within a partially concave shape
triang1 = tri.Triangulation(x, y)
triang2 = tri.Triangulation(x, y)
triang3 = tri.Triangulation(x, y)
def apply_mask(triang, alpha=0.4):
# Mask triangles with sidelength bigger some alpha
triangles = triang.triangles
# Mask off unwanted triangles.
xtri = x[triangles] - np.roll(x[triangles], 1, axis=1)
ytri = y[triangles] - np.roll(y[triangles], 1, axis=1)
maxi = np.max(np.sqrt(xtri**2 + ytri**2), axis=1)
# apply masking
triang.set_mask(maxi > alpha)
apply_mask(triang2, alpha=0.1)
apply_mask(triang3, alpha=0.3)
fig, (ax1, ax2, ax3) = plt.subplots(ncols=3, figsize=(9,3))
ax1.tricontourf(triang1, z, cmap="Oranges")
ax1.scatter(x,y, s=3, color="k")
ax2.tricontourf(triang2, z, cmap="Oranges")
ax2.scatter(x,y, s=3, color="k")
ax3.tricontourf(triang3, z, cmap="Oranges")
ax3.scatter(x,y, s=3, color="k")
ax1.set_title("tricontourf without mask")
ax2.set_title("with mask (alpha=0.1)")
ax3.set_title("with mask (alpha=0.3)")
for ax in (ax1, ax2, ax3):
ax.set(xlim=(0,1), ylim=(0,1), aspect="equal")
plt.show()
As can be seen, finding the correct parameter (alpha
) here is may need some tweaking.