Skip to content

Commit

Permalink
Add copy method for declarative interface (Fixes Unidata#1719)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
23ccozad committed May 25, 2021
1 parent de4eaef commit 0eed012
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 1 deletion.
39 changes: 38 additions & 1 deletion src/metpy/plots/declarative.py
Expand Up @@ -4,6 +4,7 @@
"""Declarative plotting tools."""

import contextlib
import copy
from datetime import datetime, timedelta

import matplotlib.pyplot as plt
Expand Down Expand Up @@ -568,6 +569,10 @@ def show(self):
self.draw()
plt.show()

def copy(self):
"""Return a copy of the panel container."""
return copy.copy(self)


@exporter.export
class MapPanel(Panel):
Expand All @@ -579,7 +584,7 @@ class MapPanel(Panel):
projection, graphics area, and title.
"""

parent = Instance(PanelContainer)
parent = Instance(PanelContainer, allow_none=True)

layout = Tuple(Int(), Int(), Int(), default_value=(1, 1, 1))
layout.__doc__ = """A tuple that contains the description (nrows, ncols, index) of the
Expand Down Expand Up @@ -754,6 +759,27 @@ def draw(self):
self.ax.set_title(title)
self._need_redraw = False

def __copy__(self):
"""Return a copy of this MapPanel."""
# Create new, blank instance of MapPanel
cls = self.__class__
obj = cls.__new__(cls)

# Copy each attribute from current MapPanel to new MapPanel
for name in self.trait_names():
# The 'plots' attribute is a list.
# A copy must be made for each plot in the list.
if name == 'plots':
obj.plots = [copy.copy(plot) for plot in self.plots]
else:
setattr(obj, name, getattr(self, name))

return obj

def copy(self):
"""Return a copy of the panel."""
return copy.copy(self)


@exporter.export
class Plots2D(HasTraits):
Expand Down Expand Up @@ -880,6 +906,10 @@ def name(self):
ret += f'@{self.level:d}'
return ret

def copy(self):
"""Return a copy of the plot."""
return copy.copy(self)


@exporter.export
class PlotScalar(Plots2D):
Expand Down Expand Up @@ -1007,6 +1037,9 @@ class ColorfillTraits(HasTraits):
``None``.
"""

def copy(self):
return copy.copy(self)


@exporter.export
class ImagePlot(PlotScalar, ColorfillTraits):
Expand Down Expand Up @@ -1604,3 +1637,7 @@ def _build(self):
if self.vector_field_length is not None:
vector_kwargs['length'] = self.vector_field_length
self.handle.plot_barb(u, v, **vector_kwargs)

def copy(self):
"""Return a copy of the plot."""
return copy.copy(self)
34 changes: 34 additions & 0 deletions tests/plots/test_declarative.py
Expand Up @@ -1184,3 +1184,37 @@ def test_panel():

pc.panel = panel
assert pc.panel is panel


@needs_cartopy
def test_copy():
"""Test that the copy method works for all classes in `declarative.py`."""
# Copies of plot objects
objects = [ImagePlot(), ContourPlot(), FilledContourPlot(), BarbPlot(), PlotObs()]

for obj in objects:
obj.time = datetime.now()
copied_obj = obj.copy()
assert obj is not copied_obj
assert obj.time == copied_obj.time

# Copies of MapPanel and PanelContainer
obj = MapPanel()
obj.title = "Sample Text"
copied_obj = obj.copy()
assert obj is not copied_obj
assert obj.title == copied_obj.title

obj = PanelContainer()
obj.size = (10, 10)
copied_obj = obj.copy()
assert obj is not copied_obj
assert obj.size == copied_obj.size

# Copies of plots in MapPanels should not point to same location in memory
obj = MapPanel()
obj.plots = [PlotObs(), BarbPlot(), FilledContourPlot(), ContourPlot(), ImagePlot()]
copied_obj = obj.copy()

for i in range(len(obj.plots)):
assert obj.plots[i] is not copied_obj.plots[i]

0 comments on commit 0eed012

Please sign in to comment.