Does somebody know – in Matplotlib – how to produce a good-looking filled contour plot with semi-transparent colors? If contourf()
is passed a colormap with semi-transparent colors, it produces small gaps between the filled areas:
According to the docs, this is not a bug ("contourf()
[...] does not draw the polygon edges"). To draw the edges, it is suggested to "add line contours with calls to contour()
". But that doesn't look good either as the edges become too opaque:
You can play around with the linewidth
argument of contour()
, but that doesn't help much. Any ideas?
Here's the code that reproduces the problem (I use the object-oriented API, but the result is the same with pyplot
):
import matplotlib
import numpy as np
from matplotlib.figure import Figure
from matplotlib.backends.backend_agg import FigureCanvasAgg
# generate some data
shape = (100, 100)
x_rng = np.linspace(-1, 1, shape[1])
y_rng = np.linspace(-1, 1, shape[0])
x, y = np.meshgrid(x_rng, y_rng)
z = np.sqrt(x**2 + y**2)
# create figure
width_inch, height_inch = 5, 5 # results in 500x500px with dpi=100
fig = Figure()
fig.set_size_inches((width_inch, height_inch))
FigureCanvasAgg(fig)
ax = fig.add_axes([0., 0., 1., 1.])
ax.set_axis_off()
# define some colors with alpha < 1
alpha = 0.9
colors = [
(0.1, 0.1, 0.5, alpha), # dark blue
(0.0, 0.7, 0.3, alpha), # green
(0.9, 0.2, 0.7, alpha), # pink
(0.0, 0.0, 0.0, alpha), # black
(0.1, 0.7, 0.7, alpha), # light blue
]
cmap = matplotlib.colors.ListedColormap(colors)
levels = np.array(np.linspace(0, z.max(), len(colors)))
norm = matplotlib.colors.BoundaryNorm(levels, ncolors=cmap.N)
# contourf plot produces small gaps between filled areas
cnt = ax.contourf(x, y, z, levels, cmap=cmap, norm=norm,
antialiased=True, linecolor='none')
# this fills the gaps, but it makes them too opaque
# ax.contour(x, y, z, levels, cmap=cmap, norm=norm,
# antialiased=True)
# the same is true for this trick:
# for c in cnt.collections:
# c.set_edgecolor("face")
filename = "/tmp/contourf.png"
fig.savefig(filename, dpi=100, transparent=True, format="png")
PS: The same plot looks good with the SVG backend.
PPS: pcolormesh()
has a similar problem:
ax.pcolormesh(x, y, z, cmap=cmap, norm=norm,
edgecolor="face", antialiased=True)
I don't know if this solves your problem, as 'good-looking' is somewhat subjective, but the only way to get an image that doesn't have any antialiasing issues, or jagged-edges, when zoomed in to that level would be to use a vector format, like SVG, EPS, etc (as you point out).
SVG (Scalable vector graphics)
If you turn off antialiasing, you can get rid of the blurring of edges and the 'opaqueness', but you will get a jagged-edge appearance to the circles at high zoom levels. You could try increasing the dpi to say, 300dpi, and saving as a tiff:
Tiff, 300dpi, antialiasing=False
Anti-aliasing is going to blend your image along the circle boundaries, so mixing the transparent pink and green is going produce a darker looking color, which might give the impression of looking more opaque, even if the transparency is notionally the same.
I got different-looking results from commenting in/out the following sections of your code, but the results are too subjective to say which is better looking:
Set the contours but not the edgecolor:
ax.contour(x, y, z, levels, cmap=cmap, norm=norm,
antialiased=True)
# the same is true for this trick:
# for c in cnt.collections:
# c.set_edgecolor("face")
Don't set the contours, but set the edge color:
# ax.contour(x, y, z, levels, cmap=cmap, norm=norm,
# antialiased=True)
# the same is true for this trick:
for c in cnt.collections:
c.set_edgecolor("face")
But it's up to interpretation whether they look good. Another thing I found was that my image viewer has it's own anti-aliasing feature built in, so you might want to turn this off when trying to make comparisons.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With