proplot icon indicating copy to clipboard operation
proplot copied to clipboard

contour label does not work well in geographic projections

Open IAP-FuYaNan opened this issue 3 years ago • 5 comments

Description

I wanted to plot a contour map in a geographic projection, and the plotting went just fine. However, when I tried to add labels to contours, the results were quite confusing.

Steps to reproduce

A "Minimal, Complete and Verifiable Example" will make it much easier for maintainers to help you.

# your code here
# we should be able to copy-paste this into python and exactly reproduce your bug

import proplot as pplt
import pandas as pd
import numpy as np

state = np.random.RandomState(51423)
data = state.rand(6, 6)
data = pd.DataFrame(data, index=pd.Index(['a', 'b', 'c', 'd', 'e', 'f']))

lat = np.linspace(40, 5, 6)
lon = np.linspace(90, 135, 6)

fig = pplt.figure(
    refwidth = 4.5,
    )
ax = fig.subplots(
    proj = 'cyl',
    )
ax.contour(
    lon,
    lat,
    data,
    labels = True,
    )
ax.format(
    lonlim = (90, 135),
    latlim = (5, 40),
    lonlines = 15,
    latlines = 10,
    lonlabels = 'b',
    latlabels = 'l',
    )

IAP-FuYaNan avatar Feb 17 '22 13:02 IAP-FuYaNan

Thanks for the report, not sure what's going on here.

In your example, the fraction of contour lines removed for inline labels is too much:

iTerm2 e4f8V0

In a matplotlib-only equivalent, a more appropriate fraction is removed:

import matplotlib.pyplot as pet
import proplot as pplt
import pandas as pd
import numpy as np

state = np.random.RandomState(51423)
data = state.rand(6, 6)
data = pd.DataFrame(data, index=pd.Index(["a", "b", "c", "d", "e", "f"]))

lat = np.linspace(40, 5, 6)
lon = np.linspace(90, 135, 6)

fig = plt.figure(
    # refwidth=4.5,
)
ax = fig.subplots(
    # proj="cyl",
    subplot_kw={"projection": pplt.PlateCarree()},
)
m = ax.contour(
    lon,
    lat,
    data,
    # labels=True,
    # labels_kw={'inline_spacing': 0},
)
ax.clabel(m, inline_spacing=0)
ax.set_extent([90, 135, 5, 40])
# ax.format(
#     lonlim=(90, 135),
#     latlim=(5, 40),
#     lonlines=15,
#     latlines=10,
#     lonlabels="b",
#     latlabels="l",
# )

iTerm2 BdR5WU

lukelbd avatar Feb 17 '22 21:02 lukelbd

This typically happens when the axes limits change after clabel is called

jklymak avatar Jun 02 '22 07:06 jklymak

This typically happens when the axes limits change after clabel is called

Thank you for your tip. Based on your tip, I set format before displaying, and problem solved, thank you !

IAP-FuYaNan avatar Jun 13 '22 13:06 IAP-FuYaNan

Thanks for the catch @jklymak. Though not sure why this seems to happen after calling clabel when adjusting limits with in proplot with lonlim/latlim, but not when adjusting limits in matplotlib/cartopy with set_extent (result of testing).

Maybe it can be improved -- will leave this open.

lukelbd avatar Mar 29 '23 19:03 lukelbd

Figured it out -- this is related to proplot's default use of a global map extent, compared to cartopy's default use of map extents that conform to plotted content.

In the "dev" version, to ensure the right label spacing whether or not you set limits before/after the call to clabel, you can revert to cartopy's default behavior using pplt.rc['geo.extent'] = 'auto' or by passing fig.subplots(..., extent='auto') (note this was pplt.rc['cartopy.autoextent'] = True and fig.subplots(..., autoextent=True) in v0.9.X, but has been changed -- the old setting/keyword now raise a warning).

Here's with initial global extent:

import matplotlib.pyplot as pet
import proplot as pplt
import numpy as np
state = np.random.RandomState(51423)
data = state.rand(6, 6)
lat = np.linspace(40, 5, 6)
lon = np.linspace(90, 135, 6)
fig = pplt.figure(width=5)
ax = fig.subplots(proj='cyl', extent='globe', land=True)
m = ax.contour(lon, lat, data)
ax.clabel(m, inline_spacing=0)
ax.set_extent([90, 135, 5, 40])

iTerm2 Q1k3qd tmpf3myvfpr

And here's with "auto" extent:

import matplotlib.pyplot as pet
import proplot as pplt
import numpy as np
state = np.random.RandomState(51423)
data = state.rand(6, 6)
lat = np.linspace(40, 5, 6)
lon = np.linspace(90, 135, 6)
fig = pplt.figure(width=5)
ax = fig.subplots(proj='cyl', extent='auto', land=True)
m = ax.contour(lon, lat, data)
ax.clabel(m, inline_spacing=0)
ax.set_extent([90, 135, 5, 40])

iTerm2 jxE34h tmprdwvqht0

I think a workaround might be to delay the following set_global() call until the figure gets drawn for the first time, and then only call it if the user has not explicitly updated the lon/lat limits:

https://github.com/proplot-dev/proplot/blob/0e18babf43193a44f038928f008e873ad95f271c/proplot/axes/geo.py#L944-L952

lukelbd avatar Mar 31 '23 07:03 lukelbd