Unidata / MetPy

MetPy is a collection of tools in Python for reading, visualizing and performing calculations with weather data.
https://unidata.github.io/MetPy/
BSD 3-Clause "New" or "Revised" License
1.25k stars 416 forks source link

Contour Labels and Declarative Plotting #1322

Open kgoebber opened 4 years ago

kgoebber commented 4 years ago

The Problem: There has been an issue with plotting contour labels for some time with the declarative syntax, but I wasn't able to diagnose it more fully until now. This is different than the other contour labelling issue that is being resolved within Cartopy itself. I have some fake data examples to illustrate.

The following example gives the expected behavior:

import cartopy.crs as ccrs
import numpy as np
import matplotlib.pyplot as plt

heights = np.linspace(4500, 6000, 90)[:, None]*np.ones((90, 360))
global_heights = np.concatenate((heights, heights[::-1]))

xx, yy = np.meshgrid(np.arange(0, 360, 1), np.arange(90, -90, -1))

plt.figure(1, figsize=(10,8))

ax = plt.subplot(111, projection=ccrs.NorthPolarStereo(central_longitude=-100))

# Set Extent Early
ax.set_extent([-180, 180, 10, 90], ccrs.PlateCarree())

cs = ax.contour(xx, yy, global_heights,
                list(range(0, 10000, 120)), colors='black',
                transform=ccrs.PlateCarree())
plt.clabel(cs, inline_spacing=10, fmt='%0.0f')

ax.coastlines()

plt.show()

early_set_extent

But now, if you set the extent at a later point in the code you get the following behavior...

import cartopy.crs as ccrs
import numpy as np
import matplotlib.pyplot as plt

heights = np.linspace(4500, 6000, 90)[:, None]*np.ones((90, 360))
global_heights = np.concatenate((heights, heights[::-1]))

xx, yy = np.meshgrid(np.arange(0, 360, 1), np.arange(90, -90, -1))

plt.figure(2, figsize=(10,8))

ax = plt.subplot(111, projection=ccrs.NorthPolarStereo(central_longitude=-100))

cs = ax.contour(xx, yy, global_heights,
                list(range(0, 10000, 120)), colors='black',
                transform=ccrs.PlateCarree())
plt.clabel(cs, inline_spacing=10, fmt='%0.0f')

ax.coastlines()

# Set Extent Late
ax.set_extent([-180, 180, 10, 90], ccrs.PlateCarree())

plt.savefig('late_set_extent.png', dpi=150, bbox_inches='tight')
plt.show()

late_set_extent

Yowzah! Look at those missing contour pieces just for labelling the lines!

It turns out that the current behavior in the MetPy declarative syntax is to set the extent late and give output that looks like this:

test

The Answer:

So a PR is forthcoming on this issue with will just move the setting of the extent earlier in the draw process - no other changes needed to get a consistent behavior. There are currently three failing tests as a result of changing this order in how declarative draws the images. Two are based on contour labelling (will replace those images for sure), one is not. The one that is not is an ImagePlot() that has subtle changes after this change.

All of this is kinda disturbing as I don't know why this ordering produces different results, but alas it does. It might lie somewhere upstream in Cartopy?

dopplershift commented 4 years ago

Re-opening so I remember to upstream this somewhere.

sgdecker commented 3 years ago

@kgoebber is the "other contour labelling issue that is being resolved within Cartopy itself" the issue where an export to a PostScript file loses the contour labels?

Example: bug

This is the result of PanelContainer.save('bug.ps') where using PanelContainer.show() looks fine (as does saving to a PNG).

Or should I submit a new issue for this?

dopplershift commented 3 years ago

@sgdecker No, we've not seen that before. However, I have trouble believing MetPy itself could be causing it and that it's not an issue in matplotlib (or Cartopy). Can you share the code you used to generate and the bug.ps file you generated?

sgdecker commented 3 years ago

This gist contains both the ps file and the code to generate it.