20
20
from . import ctables , wx_symbols
21
21
from ._mpl import TextCollection
22
22
from .cartopy_utils import import_cartopy
23
+ from .skewt import SkewT
23
24
from .station_plot import StationPlot
24
25
from ..calc import reduce_point_density , smooth_n_point , zoom_xarray
25
26
from ..package_tools import Exporter
@@ -627,8 +628,35 @@ def copy(self):
627
628
return copy .copy (self )
628
629
629
630
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
+
630
658
@exporter .export
631
- class MapPanel (Panel , ValidationMixin ):
659
+ class MapPanel (Panel , PanelTraits , ValidationMixin ):
632
660
"""Set figure related elements for an individual panel.
633
661
634
662
Parameters that need to be set include collecting all plotting types
@@ -650,14 +678,6 @@ class MapPanel(Panel, ValidationMixin):
650
678
`matplotlib.figure.Figure.add_subplot`.
651
679
"""
652
680
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
-
661
681
_need_redraw = Bool (default_value = True )
662
682
663
683
area = Union ([Unicode (), Tuple (Float (), Float (), Float (), Float ())], allow_none = True ,
@@ -713,21 +733,6 @@ class MapPanel(Panel, ValidationMixin):
713
733
provided by user. Use `None` value for 'ocean', 'lakes', 'rivers', and 'land'.
714
734
"""
715
735
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
-
731
736
@validate ('area' )
732
737
def _valid_area (self , proposal ):
733
738
"""Check that proposed string or tuple is valid and turn string into a tuple extent."""
@@ -921,6 +926,123 @@ def copy(self):
921
926
return copy .copy (self )
922
927
923
928
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
+
924
1046
class SubsetTraits (MetPyHasTraits ):
925
1047
"""Represent common traits for subsetting data."""
926
1048
@@ -2205,3 +2327,186 @@ def _build(self):
2205
2327
2206
2328
# Finally, draw the label
2207
2329
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