2020from . import ctables , wx_symbols
2121from ._mpl import TextCollection
2222from .cartopy_utils import import_cartopy
23+ from .skewt import SkewT
2324from .station_plot import StationPlot
2425from ..calc import reduce_point_density , smooth_n_point , zoom_xarray
2526from ..package_tools import Exporter
@@ -627,8 +628,35 @@ def copy(self):
627628 return copy .copy (self )
628629
629630
631+ class PanelTraits (MetPyHasTraits ):
632+ """Represent common traits for panels."""
633+
634+ title = Unicode ()
635+ title .__doc__ = """A string to set a title for the figure.
636+
637+ This trait sets a user-defined title that will plot at the top center of the figure.
638+ """
639+
640+ title_fontsize = Union ([Int (), Float (), Unicode ()], allow_none = True , default_value = None )
641+ title_fontsize .__doc__ = """An integer or string value for the font size of the title of the
642+ figure.
643+
644+ This trait sets the font size for the title that will plot at the top center of the figure.
645+ Accepts size in points or relative size. Allowed relative sizes are those of Matplotlib:
646+ 'xx-small', 'x-small', 'small', 'medium', 'large', 'x-large', 'xx-large'.
647+ """
648+
649+ plots = List (Any ())
650+ plots .__doc__ = """A list of handles that represent the plots (e.g., `ContourPlot`,
651+ `FilledContourPlot`, `ImagePlot`, `SkewPlot`) to put on a given panel.
652+
653+ This trait collects the different plots, including contours and images, that are intended
654+ for a given panel.
655+ """
656+
657+
630658@exporter .export
631- class MapPanel (Panel , ValidationMixin ):
659+ class MapPanel (Panel , PanelTraits , ValidationMixin ):
632660 """Set figure related elements for an individual panel.
633661
634662 Parameters that need to be set include collecting all plotting types
@@ -650,14 +678,6 @@ class MapPanel(Panel, ValidationMixin):
650678 `matplotlib.figure.Figure.add_subplot`.
651679 """
652680
653- plots = List (Any ())
654- plots .__doc__ = """A list of handles that represent the plots (e.g., `ContourPlot`,
655- `FilledContourPlot`, `ImagePlot`) to put on a given panel.
656-
657- This trait collects the different plots, including contours and images, that are intended
658- for a given panel.
659- """
660-
661681 _need_redraw = Bool (default_value = True )
662682
663683 area = Union ([Unicode (), Tuple (Float (), Float (), Float (), Float ())], allow_none = True ,
@@ -713,21 +733,6 @@ class MapPanel(Panel, ValidationMixin):
713733 provided by user. Use `None` value for 'ocean', 'lakes', 'rivers', and 'land'.
714734 """
715735
716- title = Unicode ()
717- title .__doc__ = """A string to set a title for the figure.
718-
719- This trait sets a user-defined title that will plot at the top center of the figure.
720- """
721-
722- title_fontsize = Union ([Int (), Float (), Unicode ()], allow_none = True , default_value = None )
723- title_fontsize .__doc__ = """An integer or string value for the font size of the title of the
724- figure.
725-
726- This trait sets the font size for the title that will plot at the top center of the figure.
727- Accepts size in points or relative size. Allowed relative sizes are those of Matplotlib:
728- 'xx-small', 'x-small', 'small', 'medium', 'large', 'x-large', 'xx-large'.
729- """
730-
731736 @validate ('area' )
732737 def _valid_area (self , proposal ):
733738 """Check that proposed string or tuple is valid and turn string into a tuple extent."""
@@ -921,6 +926,123 @@ def copy(self):
921926 return copy .copy (self )
922927
923928
929+ @exporter .export
930+ class SkewtPanel (PanelTraits , Panel ):
931+ """A class to collect skewt plots and set complete figure related settings (e.g., size)."""
932+
933+ parent = Instance (PanelContainer , allow_none = True )
934+
935+ ylimits = Tuple (Int (), Int (), default_value = (1000 , 100 ), allow_none = True )
936+ ylimits .__doc__ = """A tuple of y-axis limits to plot the skew-T.
937+
938+ Order is in higher pressure to lower pressure."""
939+
940+ xlimits = Tuple (Int (), Int (), default_value = (- 40 , 40 ), allow_none = True )
941+ xlimits .__doc__ = """A tuple of x-axis limits to plot the skew-T.
942+
943+ Order is lower temperature to higher temperature."""
944+
945+ ylabel = Unicode (default_value = 'pressure [hPa]' )
946+ ylabel .__doc__ = """A string to plot for the y-axis label.
947+
948+ Defaults to 'pressure [hPa]'"""
949+
950+ xlabel = Unicode (default_value = 'temperature [\N{DEGREE SIGN} C]' )
951+ xlabel .__doc__ = """A string to plot for the y-axis label.
952+
953+ Defaults to 'temperature [C]'"""
954+
955+ @observe ('plots' )
956+ def _plots_changed (self , change ):
957+ """Handle when our collection of plots changes."""
958+ for plot in change .new :
959+ plot .parent = self
960+ plot .observe (self .refresh , names = ('_need_redraw' ))
961+ self ._need_redraw = True
962+
963+ @observe ('parent' )
964+ def _parent_changed (self , _ ):
965+ """Handle when the parent is changed."""
966+ self .ax = None
967+
968+ @property
969+ def ax (self ):
970+ """Get the :class:`matplotlib.axes.Axes` to draw on.
971+
972+ Creates a new instance if necessary.
973+
974+ """
975+ # If we haven't actually made an instance yet, make one with the right size and
976+ # map projection.
977+ if getattr (self , '_ax' , None ) is None :
978+ self ._ax = SkewT (self .parent .figure , rotation = 45 )
979+
980+ return self ._ax
981+
982+ @ax .setter
983+ def ax (self , val ):
984+ """Set the :class:`matplotlib.axes.Axes` to draw on.
985+
986+ Clears existing state as necessary.
987+
988+ """
989+ if getattr (self , '_ax' , None ) is not None :
990+ self ._ax .cla ()
991+ self ._ax = val
992+
993+ def refresh (self , changed ):
994+ """Refresh the drawing if necessary."""
995+ self ._need_redraw = changed .new
996+
997+ def draw (self ):
998+ """Draw the panel."""
999+ # Only need to run if we've actually changed.
1000+ if self ._need_redraw :
1001+
1002+ skew = self .ax
1003+
1004+ # Set the extent as appropriate based on the limits.
1005+ xmin , xmax = self .xlimits
1006+ ymax , ymin = self .ylimits
1007+ skew .ax .set_xlim (xmin , xmax )
1008+ skew .ax .set_ylim (ymax , ymin )
1009+ skew .ax .set_xlabel (self .xlabel )
1010+ skew .ax .set_ylabel (self .ylabel )
1011+
1012+ # Draw all of the plots.
1013+ for p in self .plots :
1014+ with p .hold_trait_notifications ():
1015+ p .draw ()
1016+
1017+ skew .plot_labeled_skewt_lines ()
1018+
1019+ # Use the set title or generate one.
1020+ title = self .title or ',\n ' .join (plot .name for plot in self .plots )
1021+ skew .ax .set_title (title , fontsize = self .title_fontsize )
1022+ self ._need_redraw = False
1023+
1024+ def __copy__ (self ):
1025+ """Return a copy of this SkewPanel."""
1026+ # Create new, blank instance of MapPanel
1027+ cls = self .__class__
1028+ obj = cls .__new__ (cls )
1029+
1030+ # Copy each attribute from current MapPanel to new MapPanel
1031+ for name in self .trait_names ():
1032+ # The 'plots' attribute is a list.
1033+ # A copy must be made for each plot in the list.
1034+ if name == 'plots' :
1035+ obj .plots = [copy .copy (plot ) for plot in self .plots ]
1036+ else :
1037+ setattr (obj , name , getattr (self , name ))
1038+
1039+ return obj
1040+
1041+ def copy (self ):
1042+ """Return a copy of the panel."""
1043+ return copy .copy (self )
1044+
1045+
9241046class SubsetTraits (MetPyHasTraits ):
9251047 """Represent common traits for subsetting data."""
9261048
@@ -2205,3 +2327,186 @@ def _build(self):
22052327
22062328 # Finally, draw the label
22072329 self ._draw_label (label , lon , lat , fontcolor , fontoutline , offset )
2330+
2331+
2332+ @exporter .export
2333+ class SkewtPlot (MetPyHasTraits , ValidationMixin ):
2334+ """A class to set plot charactersitics of skewt data."""
2335+
2336+ temperature_variable = List (Unicode ())
2337+ temperature_variable .__doc__ = """A list of string names for plotting variables from dictinary-like object.
2338+
2339+ No order in particular is needed, however, to shade cape or cin the order of temperature,
2340+ dewpoint temperature, parcel temperature is required."""
2341+
2342+ vertical_variable = Unicode ()
2343+ vertical_variable .__doc__ = """A string with the vertical variable name (e.g., 'pressure').
2344+
2345+ """
2346+
2347+ linecolor = List (Unicode (default_value = 'black' ))
2348+ linecolor .__doc__ = """A list of color names corresponding to the parameters in `temperature_variables`.
2349+
2350+ A list of the same length as `temperature_variables` is preferred, otherwise, colors will
2351+ repeat. The default value is 'black'."""
2352+
2353+ linestyle = List (Unicode (default_value = 'solid' ))
2354+ linestyle .__doc__ = """A list of line style names corresponding to the parameters in `temperature_variables`.
2355+
2356+ A list of the same length as `temperature_variables` is preferred, otherwise, colors will
2357+ repeat. The default value is 'solid'."""
2358+
2359+ linewidth = List (Union ([Int (), Float ()]), default_value = [1 ])
2360+ linewidth .__doc__ = """A list of linewidth values corresponding to the parameters in `temperature_variables`.
2361+
2362+ A list of the same length as `temperature_variables` is preferred, otherwise, colors will
2363+ repeat. The default value is 1."""
2364+
2365+ shade_cape = Bool (default_value = False )
2366+ shade_cape .__doc__ = """A boolean (True/False) on whether to shade the CAPE for the sounding.
2367+
2368+ This parameter uses the default settings from MetPy for plotting CAPE. In order to shade
2369+ CAPE, the `temperature_variables` attribute must be in the order of temperature, dewpoint
2370+ temperature, parcel temperature. The default value is `False`."""
2371+
2372+ shade_cin = Bool (default_value = False )
2373+ shade_cin .__doc__ = """A boolean (True/False) on whether to shade the CIN for the sounding.
2374+
2375+ This parameter uses the default settings from MetPy for plotting CIN using the dewpoint,
2376+ so only the CIN between the surface and the LFC is filled. In order to shade CIN, the
2377+ `temperature_variables` attribute must be in the order of temperature, dewpoint
2378+ temperature, parcel temperature. The default value is `False`."""
2379+
2380+ wind_barb_variables = List (default_value = [None ], allow_none = True )
2381+ wind_barb_variables .__doc__ = """A list of string names of the u- and v-components of the wind.
2382+
2383+ This attribute requires two string names in the order u-component, v-component for those
2384+ respective variables stored in the dictionary-like object."""
2385+
2386+ wind_barb_color = Unicode ('black' , allow_none = True )
2387+ wind_barb_color .__doc__ = """A string declaring the name of the color to plot the wind barbs.
2388+
2389+ The default value is 'black'."""
2390+
2391+ wind_barb_length = Int (default_value = 7 , allow_none = True )
2392+ wind_barb_length .__doc__ = """An integer value for defining the size of the wind barbs.
2393+
2394+ The default value is 7."""
2395+
2396+ wind_barb_skip = Int (default_value = 1 )
2397+ wind_barb_skip .__doc__ = """An integer value for skipping the plotting of wind barbs.
2398+
2399+ The default value is 1 (no skipping)."""
2400+
2401+ wind_barb_position = Float (default_value = 1.0 )
2402+ wind_barb_position .__doc__ = """A float value for defining location of the wind barbs on the plot.
2403+
2404+ The float value describes the location in figure space. The default value is 1.0."""
2405+
2406+ parent = Instance (Panel )
2407+ _need_redraw = Bool (default_value = True )
2408+
2409+ def clear (self ):
2410+ """Clear the plot.
2411+
2412+ Resets all internal state and sets need for redraw.
2413+
2414+ """
2415+ if getattr (self , 'handle' , None ) is not None :
2416+ self .handle .ax .cla ()
2417+ self .handle = None
2418+ self ._need_redraw = True
2419+
2420+ @observe ('parent' )
2421+ def _parent_changed (self , _ ):
2422+ """Handle setting the parent object for the plot."""
2423+ self .clear ()
2424+
2425+ @observe ('temperature_variable' , 'vertical_variable' , 'wind_barb_variables' )
2426+ def _update_data (self , _ = None ):
2427+ """Handle updating the internal cache of data.
2428+
2429+ Responds to changes in various subsetting parameters.
2430+
2431+ """
2432+ self ._xydata = None
2433+ self .clear ()
2434+
2435+ # Can't be a Traitlet because notifications don't work with arrays for traits
2436+ # notification never happens
2437+ @property
2438+ def data (self ):
2439+ """Dictionary-like data that contains the fields to be plotted."""
2440+ return self ._data
2441+
2442+ @data .setter
2443+ def data (self , val ):
2444+ self ._data = val
2445+ self ._update_data ()
2446+
2447+ @property
2448+ def name (self ):
2449+ """Generate a name for the plot."""
2450+ ret = ''
2451+ ret += ' and ' .join ([self .x_variable ])
2452+ return ret
2453+
2454+ @property
2455+ def xydata (self , var ):
2456+ """Return the internal cached data."""
2457+ if getattr (self , '_xydata' , None ) is None :
2458+ # Use a copy of data so we retain all of the original data passed in unmodified
2459+ self ._xydata = self .data
2460+ return self ._xydata [var ]
2461+
2462+ def draw (self ):
2463+ """Draw the plot."""
2464+ if self ._need_redraw :
2465+ if getattr (self , 'handle' , None ) is None :
2466+ self ._build ()
2467+ self ._need_redraw = False
2468+
2469+ @observe ('linecolor' , 'linewidth' , 'linestyle' , 'wind_barb_color' , 'wind_barb_length' ,
2470+ 'wind_barb_position' , 'wind_barb_skip' , 'shade_cape' , 'shade_cin' )
2471+ def _set_need_rebuild (self , _ ):
2472+ """Handle changes to attributes that need to regenerate everything."""
2473+ # Because matplotlib doesn't let you just change these properties, we need
2474+ # to trigger a clear and re-call of contour()
2475+ self .clear ()
2476+
2477+ def _build (self ):
2478+ """Build the plot by calling needed plotting methods as necessary."""
2479+ data = self .data
2480+ y = data [self .vertical_variable ]
2481+ if len (self .temperature_variable ) != len (self .linewidth ):
2482+ self .linewidth *= len (self .temperature_variable )
2483+ if len (self .temperature_variable ) != len (self .linecolor ):
2484+ self .linecolor *= len (self .temperature_variable )
2485+ if len (self .temperature_variable ) != len (self .linestyle ):
2486+ self .linestyle *= len (self .temperature_variable )
2487+ for i in range (len (self .temperature_variable )):
2488+ x = data [self .temperature_variable [i ]]
2489+
2490+ self .parent .ax .plot (y , x , self .linecolor [i ], linestyle = self .linestyle [i ],
2491+ linewidth = self .linewidth [i ])
2492+
2493+ if self .wind_barb_variables [0 ] is not None :
2494+ u = data [self .wind_barb_variables [0 ]]
2495+ v = data [self .wind_barb_variables [1 ]]
2496+ barb_skip = slice (None , None , self .wind_barb_skip )
2497+ self .parent .ax .plot_barbs (y [barb_skip ], u [barb_skip ], v [barb_skip ],
2498+ y_clip_radius = 0 , xloc = self .wind_barb_position )
2499+
2500+ if self .shade_cape :
2501+ self .parent .ax .shade_cape (data [self .vertical_variable ],
2502+ data [self .temperature_variable [0 ]],
2503+ data [self .temperature_variable [2 ]])
2504+ if self .shade_cin :
2505+ self .parent .ax .shade_cin (data [self .vertical_variable ],
2506+ data [self .temperature_variable [0 ]],
2507+ data [self .temperature_variable [2 ]],
2508+ data [self .temperature_variable [1 ]])
2509+
2510+ def copy (self ):
2511+ """Return a copy of the plot."""
2512+ return copy .copy (self )
0 commit comments