Search code examples
pythonmatplotlibplotcartopy

Not filled, white voids near boundaries of polar plot


I have tried to create a polar plot using matplotlib's contourf, but it produces some not-filled areas near the circle boundary, in just some sides of the plot. It seems, this problem is a common problem that we can see in many examples e.g. 1.

enter image description here

At first, I thought it may needs some interpolations and tried interpolating based on some available methods e.g. 2. But I couldn't produce a perfect plot. Interpolating using SciPy griddata with linear method solved the main issue but produce some shadows on the plot, and the cubic method result in some inappropriate colors (which shown the results incorrect).

enter image description here

Finally, I guess this issue may be related to figure size that I specified and the dpi that I used. With low dpi it was cured a lot, but will get a low quality png. when I deactivate the related line for specifying figure size (# plt.rcParams["figure.figsize"] = (19.2, 9.6)) with dpi=600, it is shown fairly correct, but not on the needed figure size.

enter image description here

The main question is how to solve the issue for saved files, with the desired specified figure size and dpi? It must be said that the problem is appearing on the saved files.

Besides the answer to solve the issue, I will be appreciated if any answer about these questions too:

  • Why it happens?

  • Why it happens just in some sides of the plot? In this example it is problematic just on the right side of the quarter, not the upside. This issue makes me doubt that the data is correctly shown on the circle for analysis. Is it OK?

  • Do we need interpolating on such data? If so, which algorithms will be the best for that which does not show the results incorrectly as cubic method in Scipy interpolation and without shadowing as the linear method? If choosing between algorithms is based on the case, how to decide for that? It will be very helpful if be explained with examples.

import numpy as np
from matplotlib import cm
import matplotlib.pyplot as plt


plt.rcParams["figure.figsize"] = (19.2, 9.6)
save_dpi = 600

Azimuth = np.tile(np.arange(0, 91, 10), 10)
Deviation = np.repeat(np.arange(0, 91, 10), 10)
color_data = np.array([2123, 2124, 2126, 2130, 2135, 2139, 2144, 2147, 2150, 2151, 2212,
                       2211, 2205, 2197, 2187, 2176, 2166, 2158, 2152, 2150, 2478, 2468,
                       2439, 2395, 2342, 2285, 2231, 2188, 2160, 2150, 2912, 2888, 2819,
                       2715, 2589, 2456, 2334, 2236, 2172, 2150, 3493, 3449, 3324, 3135,
                       2908, 2674, 2462, 2294, 2187, 2150, 4020, 4020, 3912, 3618, 3270,
                       2917, 2602, 2357, 2203, 2151, 4020, 4020, 4020, 4020, 3633, 3156,
                       2737, 2417, 2218, 2150, 4020, 4020, 4020, 4020, 3947, 3358, 2850,
                       2466, 2230, 2150, 4020, 4020, 4020, 4020, 4020, 3495, 2926, 2499,
                       2238, 2150, 4020, 4020, 4020, 4020, 4020, 3543, 2951, 2510, 2241,
                       2150])

ax = plt.subplot(projection='polar')
plt.xticks([])
plt.yticks([])

Az = np.unique(Azimuth)
Dev = np.unique(Deviation)

mAz, mDev = np.meshgrid(Az, Dev)

# way1: Original
Xi, Yi, Zi = np.deg2rad(mAz), mDev, color_data.reshape(mAz.shape)
contour_plot = ax.contourf(Xi, Yi, Zi, levels=256, cmap=cm.viridis_r, zorder=1)
ax.plot()
plt.savefig("way1.png", dpi=save_dpi)

# way2: Interpolation
# import scipy.interpolate as sci_int
# Xi = np.linspace(0, 2 * np.pi, 256, endpoint=True)
# Yi = np.linspace(0, 90, 256, endpoint=True)
# Zi = sci_int.griddata(np.stack((np.deg2rad(mAz), mDev), axis=2).reshape(len(Azimuth), 2), color_data,
#                                                                             (Xi[None, :], Yi[:, None]), method='linear')
# contour_plot = ax.contourf(Xi, Yi, Zi, levels=256, cmap=cm.viridis_r, zorder=1)
# ax.plot()
# plt.savefig("way2.png", dpi=save_dpi)

Tested on windows 10 by:
Python ver.: 3.8 & 3.10
Matplotlib ver.: 3.5.3 & 3.7.2


Solution

  • After a related discussion in matplotlib repo, it seems there is not any simple and conventional solution for that (perhaps cartopy have prepared something helpful); The contouring algorithm doesn't know that it is acting on a polar plot, so when the contours are drawn in polar space the polygons are approximated.

    This isn't actually a contouring issue, it is an issue of whether a straight line in one coordinate system should be transformed to a straight or non-straight line in another coordinate system. It applies equally to any polygon (e.g. just a simple triangle) specified in, for example, polar coordinates, and rendered in cartesian (screen) coordinates.

    Just for a sub-solution, I tried to produce more points (Azimuth & Deviation) and interpolate their corresponding color_data (the more points you have, the shorter the lines are and the closer this looks to a circular segment) to reduce/cover this shortcoming. In this regard, and for this example, Radial basis function (RBF) interpolation of SciPy get an acceptable answer with the following code (which could be developed/adjusted for such problems):

    from scipy.interpolate import RBFInterpolator
    
    coordinates_org = np.column_stack((Azimuth, Deviation))
    Az = np.linspace(0, 90, 181, endpoint=True)
    Dev = np.linspace(0, 90, 181, endpoint=True)
    Az_2D, Dev_2D = np.meshgrid(Az, Dev)
    coordinates_int = np.column_stack((Az_2D.ravel(), Dev_2D.ravel()))
    rbf = RBFInterpolator(coordinates_org, color_data, kernel="linear")
    color_data_int = rbf(coordinates_int).reshape(len(Az), len(Dev))
    contour_plot = ax.contourf(np.deg2rad(Az), Dev, color_data_int, levels=1000, cmap=cm.viridis_r)
    ax.plot()
    plt.savefig("way3.png", dpi=save_dpi)
    

    enter image description here

    I adjust this solution for another example by some modifications to compare its results with some various interpolation methods, which can be seen below. In this comparison, unfilled area above the circles for griddata might be filled using other levels than used, which was 1000 as I remember. Based on the comparison, this solution (RBF) produces better results than using griddata as way 2 in the question; Also, quintic method seems got the best:

    enter image description here