3

When looking closely at contour plots made with matplotlib, I noticed that smaller contours have inaccurate endpoints which do not close perfectly in PDF figures. Consider the minimal example:

plt.gca().set_aspect('equal')
x,y = np.meshgrid(np.linspace(-1,1,100), np.linspace(-1,1,100))
r = x*x + y*y
plt.contour(np.log(r))
plt.savefig("test.pdf")

The central part of the resulting test.pdf file, shown below, clearly shows the problem. Is there a way to solve this or is it a bug/intrinsic inaccuracy of matploplib?

enter image description here

Marco Lombardi
  • 545
  • 4
  • 18

2 Answers2

4

Disclaimer: this is more an explanation + hack than a real answer.

I believe that there is a fundamental problem the way matplotlib makes contour plots. Essentially, all contours are collections of lines (LineCollection), while they should be collection of possibly closed lines (PolyCollection). There might be good reasons why things are done this way, but in the simple example I made this choice clearly produces artifacts. A not-very-nice solution is to convert a posteriori all LineCollection's into PolyCollection's. This is what is done in the following code

from matplotlib.collections import PolyCollection

eps = 1e-5
plt.gca().set_aspect('equal')
x,y = np.meshgrid(np.linspace(-1,1,100), np.linspace(-1,1,100))
r = x*x + y*y
plt.contour(np.log(r/1.2))
ca = plt.gca()
N = len(ca.collections)
for n in range(N):
    c = ca.collections.pop(0)
    for s in c.get_segments():
        closed = (abs(s[0,0] - s[-1,0]) < eps) and (abs(s[0,1] - s[-1,1]) < eps)            
        p = PolyCollection([s], edgecolors=c.get_edgecolors(), 
                           linewidths=c.get_linewidths(), linestyles=c.get_linestyles(),
                           facecolors=c.get_facecolors(), closed=closed)
        ca.add_collection(p)
plt.savefig("test.pdf")

A zoom of the result obtained shows that everything is OK now:

enter image description here

Some care is taken to check if a contour is closed: in the present code, this is done with an approximate equality check for the first and last point: I am wandering if there is a better way to do this (perhaps matplotlib returns some data to check closed contours?). In any case, again, this is hack: I would be happy to hear if anyone has a better solution (or has a way to fix this within matplotlib).

Marco Lombardi
  • 545
  • 4
  • 18
0

So I just did the same thing, but got closed contours (see images). Did you check for any updates on the package?

import matplotlib.pyplot as plt
import numpy as np

plt.gca().set_aspect('equal')
x,y = np.meshgrid(np.linspace(-1,1,100), np.linspace(-1,1,100))
r = x*x + y*y
plt.contour(np.log(r))
plt.show()

Zoomed Out

Zoomed In

Megan
  • 29
  • 3
  • You are not making a PDF, which was part of the minimal example. Try to make a PDF file, then zoom its central region with your PDF viewer, and tell me if you still see closed contours, please. – Marco Lombardi Oct 07 '16 at 05:37
  • Although not a perfect solution, I found that when I rasterized the gaps became significantly smaller. http://stackoverflow.com/questions/12098331/rasterizing-multiple-elements-in-matplotlib http://www.astrobetter.com/blog/2014/01/17/slim-down-your-bloated-graphics/ – Megan Oct 07 '16 at 07:06
  • Rasterizing is not very nice: the good thing of PDF figures is that they allow vector graphics, which (at least in principle) scales very well when I zoom it. – Marco Lombardi Oct 07 '16 at 07:19