Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

copy() method for declarative interface #1719

Closed
dopplershift opened this issue Feb 18, 2021 · 5 comments · Fixed by #1884
Closed

copy() method for declarative interface #1719

dopplershift opened this issue Feb 18, 2021 · 5 comments · Fixed by #1884
Assignees
Labels
Area: Plots Pertains to producing plots Type: Enhancement Enhancement to existing functionality
Milestone

Comments

@dopplershift
Copy link
Member

It would be helpful if you could copy an object (e.g. ContourPlot) and then be able to modify the attributes that need to differ. This could help reduce repetition in some map generation.

@dopplershift dopplershift added Type: Enhancement Enhancement to existing functionality Area: Plots Pertains to producing plots labels Feb 18, 2021
@dopplershift dopplershift added this to the 1.1.0 milestone Feb 18, 2021
@dopplershift
Copy link
Member Author

Of use for this will be the trait_names method from traitlets.

@kgoebber
Copy link
Collaborator

So the thinking here would be to have some structure at the end for the user that might look something like the following...

from datetime import datetime, timedelta

from metpy.plots import declarative
from metpy.units import units
import xarray as xr

# Set the date/time of the model run
# Update time to within the past two weeks
date = datetime(2021, 5, 19, 12)

# Remote access to the dataset from the UCAR site
ds = xr.open_dataset('https://thredds.ucar.edu/thredds/dodsC/grib'
                     f'/NCEP/GFS/Global_onedeg/GFS_Global_onedeg_{date:%Y%m%d}_{date:%H%M}.grib2')

# Subset data to be just over the U.S. for plotting purposes
ds = ds.sel(lat=slice(70,10), lon=slice(360-150, 360-55))

cntr = declarative.ContourPlot()
cntr.data = ds
cntr.field = 'Geopotential_height_isobaric'
cntr.level = 500 * units.hPa
cntr.time = date
cntr.contours = list(range(0, 10000, 60))
cntr.clabels = True

cntr2 = cntr.copy()
cntr2.field = 'Temperature_isobaric'
cntr2.contours = list(range(-40, 41, 5))

# Set the attributes for the map
# and put the contours on the map
panel = declarative.MapPanel()
panel.area = [-125, -74, 20, 55]
panel.projection = 'lcc'
panel.layers = ['states', 'coastline', 'borders']
panel.title = f'500-hPa Geopotential Heights at {cntr.time}'
panel.plots = [cntr, cntr2]

# Set the attributes for the panel
# and put the panel in the figure
pc = declarative.PanelContainer()
pc.size = (15, 15)
pc.panels = [panel]

# Show the figure
pc.show()

The idea is that there is a reduced number of attribute settings for the second set of contours. Less typing would ostensibly lead to less errors (at least one hopes!).

@23ccozad
Copy link
Contributor

It turns out that Python has a built-in copy() method that can be used on any object. I've tested it with several objects from the declarative interface and it appears to work properly. To create a copy, make sure to import copy, and then the general form is copy_of_obj_A = copy.copy(obj_A).

In the above example from @kgoebber, importing copy, and then changing cntr2 = cntr.copy() to cntr2 = copy.copy(cntr) should do the trick.

@kgoebber
Copy link
Collaborator

Neat! It does work.

I think it would be great if we made this a true method off of the various plotting objects (e.g., ContourPlot, BarbPlot, FilledContourPlot, MapPanel, etc) as that would reduce the cognitive load for the end user who has a greater likelihood of attempting a copy method off of the various objects (e.g., like numpy arrays).

@23ccozad
Copy link
Contributor

23ccozad commented May 20, 2021

I've been able to make some simple adjustments to the declarative interface to include a copy() method in each of those classes (such as ContourPlot, BarbPlot, FilledContourPlot, MapPanel, etc). Basically, I use copy.copy() under the hood, so that the user only needs to tack on a .copy() for the object they want to copy. Hopefully can get a PR with that code up here soon.

On another note, I discovered that copying a MapPanel causes some issue with Traitlets that ends up throwing a TraitError when attempting to show the panel. The following is just one case, but it appears to be happening with almost any MapPanel that was created by copying another MapPanel.

%matplotlib inline

from datetime import datetime
import copy

import pandas as pd

from metpy.cbook import get_test_data
import metpy.plots as mpplots
from metpy.units import units

data = pd.read_csv(get_test_data('UPA_obs.csv', as_file_obj=False))

# Plotting the Observations
obs = mpplots.PlotObs()
obs.data = data
obs.time = datetime(1993, 3, 14, 0)
obs.level = 500 * units.hPa
obs.fields = ['temperature', 'dewpoint', 'height']
obs.locations = ['NW', 'SW', 'NE']
obs.formats = [None, None, lambda v: format(v, '.0f')[:3]]
obs.vector_field = ('u_wind', 'v_wind')
obs.reduce_points = 0

# Add map features for the particular panel
panel = mpplots.MapPanel()
panel.area = (-124, -72, 20, 53)
panel.projection = 'lcc'
panel.layers = ['coastline', 'borders', 'states', 'land', 'ocean']
panel.plots = [obs]

# Creating copy of MapPanel
panel2 = copy.copy(panel)

# Collecting panels for complete figure
pc = mpplots.PanelContainer()
pc.size = (15, 10)
pc.panels = [panel2]

# Showing the results
pc.show()  # <-------- TraitError is thrown here
TraitError: The 'parent' trait of a MapPanel instance expected a PanelContainer, not the NoneType None.

It would be helpful to know if other people are getting this same error or have any insights into a potential solution.

23ccozad added a commit to 23ccozad/MetPy that referenced this issue May 25, 2021
MapPanel needed an explicity written __copy__() to make sure the copy process worked correctly. All other classes in declarative.py did not need it. A .copy() method is also provided for ease of use.
23ccozad added a commit to 23ccozad/MetPy that referenced this issue May 25, 2021
MapPanel needed an explicity written __copy__() to make sure the copy process worked correctly. All other classes in declarative.py did not need it. A .copy() method is also provided for ease of use.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area: Plots Pertains to producing plots Type: Enhancement Enhancement to existing functionality
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants