Skip to content
44 changes: 17 additions & 27 deletions pygmt_helper/plotting.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
import pygmt
import xarray as xr
from scipy import interpolate
from shapely import geometry
from qcore import point_in_polygon

GMT_DATA = pooch.create(
Expand Down Expand Up @@ -290,41 +289,30 @@ def gen_region_fig(
# Merge with default
plot_kwargs = copy.deepcopy(DEFAULT_PLT_KWARGS) | (plot_kwargs or {})

if title:
plot_kwargs["frame_args"] = plot_kwargs.get("frame_args", []) + [
f"+t{title}".replace(" ", r"\040")
]

if subtitle:
plot_kwargs["frame_args"] = plot_kwargs.get("frame_args", []) + [
f"+s{subtitle}".replace(" ", r"\040")
]
config_options = {"COLOR_NAN": plot_kwargs["water_color"]} | (config_options or {})
pygmt.config(**config_options)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Modifying the global GMT configuration directly can lead to unexpected side effects in other parts of the code that might use pygmt. It is safer to use a context manager (with pygmt.config(...)) to temporarily apply these settings only for the duration of this function's execution. This will prevent the configuration from 'leaking' and affecting other plots.

This would require some restructuring. For example:

def gen_region_fig(...):
    # ... (setup code before config)

    config_options = {'COLOR_NAN': plot_kwargs['water_color']} | (config_options or {})
    
    if fig is None:
        fig = pygmt.Figure()

    with pygmt.config(**config_options):
        # ... (all plotting logic using fig and pygmt)
    
    return fig

This change would make the function more robust and self-contained.

Copy link
Contributor Author

@lispandfound lispandfound Aug 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I tossed up using this in a with context before making the PR. In my opinion, this is not a good idea because many of the config options are set in gen_region_fig with the expectation that they persist outside the invocation of the function. For example, fonts. I also thought about just setting the COLOR_NAN locally and everything else globally. @claudio525 what do you think? Would the user be surprised if the NaN colour is the ocean? Maybe it will make them think something is wrong?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is fine as is for now, it matches with the current implementation. If this turns out to be an issue down the line, then I think we do a refactor, and change all config_options to be applied using a context manager, but don't think there is a good reason to do this now.


if fig is None:
fig = pygmt.Figure()

if config_options:
pygmt.config(**config_options)

water_color = plot_kwargs["water_color"]
plot_kwargs["frame_args"] = plot_kwargs.get("frame_args", []) + [f"+g{water_color}"]
fig.basemap(
region=region if region else "NZ",
projection=projection,
frame=plot_kwargs["frame_args"],
)

# Plot coastline and background water
bg_region = region if region else fig.region
water_bg = geopandas.GeoSeries(
geometry.LineString(
[
(bg_region[0], bg_region[2]),
(bg_region[1], bg_region[2]),
(bg_region[1], bg_region[3]),
[bg_region[0], bg_region[3]],
]
)
)
fig.plot(water_bg, fill=plot_kwargs["water_color"], straight_line=True)
title_args = []
if title:
title_args.append(f"+t{title}")
if subtitle:
title_args.append(f"+s{subtitle}")

if title:
Copy link

Copilot AI Aug 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The condition if title: is checked twice (lines 307 and 312), but the second check should be if title_args: to avoid calling basemap with empty frame args when only subtitle is provided.

Suggested change
if title:
if title_args:

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I knew the AI would flag this. Nope it is intentional. See the GMT docs

+ttitle to place the string given in title centered above the plot frame [default is no title].
+ssubtitle (requires +ttitle) to place the string given in subtitle beneath the title [default is no subtitle].

Note that +s requires +t, ergo subtitle= requires title=. Thus checking if title_args is non-empty leaves open the subtle bug that a user might call subtitle= and not title=. Hence, we check if title is not None. In that case, title_args is also not None. Likewise, if title is None then we should ignore the values in title_args even if subtitle is set.

fig.basemap(frame=title_args)

# Plot coastline
fig.plot(
data=map_data.coastline_df,
pen=f"{plot_kwargs['coastline_pen_width']}p,{plot_kwargs['coastline_pen_color']}",
Expand Down Expand Up @@ -379,14 +367,16 @@ def gen_region_fig(
continuous=plot_kwargs["topo_cmap_continous"],
cmap=plot_kwargs["topo_cmap"],
reverse=plot_kwargs["topo_cmap_reverse"],
# Some CPTs define their own COLOR_NAN, but we wish to use the
# water colour
no_bg=True,
)

# Plot topography
fig.grdimage(
grid=topo_grid,
shading=topo_shading_grid,
cmap=True,
nan_transparent=True,
)

# Plot inland water
Expand Down
1 change: 0 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ pyarrow
pygmt
xarray
scipy
shapely
qcore @ git+https://github.com/ucgmsim/qcore.git
pytest
pytest-cov
Expand Down