Module snowpat.SnowLense

Expand source code
from .SnowLense import plot, help
from .plot_snowpack import SnowpackPlotter
from .plotting import plotProfile
from .Utils import show_figure

__all__ = ['plot', 'help', 'SnowpackPlotter', 'show_figure', 'plotProfile']

Sub-modules

snowpat.SnowLense.SnowLense
snowpat.SnowLense.Utils
snowpat.SnowLense.plot_helpers_snowpack
snowpat.SnowLense.plot_snowpack
snowpat.SnowLense.plotting

Functions

def help(snowpack_file: Optional[SnowpackReader] = None, smetfile: Optional[SMETFile] = None, profile: Optional[snowpat.snowpackreader.Snowpack.Snowpack] = None, **kwargs)

Display help information for different plot types based on the input.

Parameters: - snowpack: Optional[spr.SnowpackReader] (default: None) - A snowpack reader object. - smet: Optional[smet.SMETFile] (default: None) - A SMET file object. - profile: Optional[spr.Snowpack] (default: None) - A snowpack object. - **kwargs: Additional keyword arguments.

Returns: None

Expand source code
def help(snowpack_file: Optional[spr.SnowpackReader] = None, smetfile: Optional[smet.SMETFile] = None, profile:Optional[spr.Snowpack] = None, **kwargs):
    """
    Display help information for different plot types based on the input.

    Parameters:
    - snowpack: Optional[spr.SnowpackReader] (default: None) - A snowpack reader object.
    - smet: Optional[smet.SMETFile] (default: None) - A SMET file object.
    - profile: Optional[spr.Snowpack] (default: None) - A snowpack object.
    - **kwargs: Additional keyword arguments.

    Returns:
    None
    """
    def assign_arg(arg):
        nonlocal snowpack_file, smetfile, profile
        if isinstance(arg, spr.SnowpackReader):
            snowpack_file = arg
            smetfile = None
            profile = None            
        elif isinstance(arg, smet.SMETFile):
            smetfile = arg  
            snowpack_file = None
            profile = None
        elif isinstance(arg, spr.Snowpack):
            profile = arg
            snowpack_file = None    
            smetfile = None
        elif arg is None:
            pass
        else:
            raise TypeError(f"Arguments need to be one of: [SNOWPACK, SMET, PROFILE]; got: {type(arg).__name__}")
        
    if sum(x is not None for x in [snowpack_file, smetfile, profile]) != 1:
        raise ValueError("Only one of snowpack, smet, or profile should be provided")
    

    assign_arg(snowpack_file)
    assign_arg(smetfile)
    assign_arg(profile)
    _check_args(snowpack_file, smetfile, profile)

    plot_type = None
    if snowpack_file: plot_type = PlotType.PRO
    elif smetfile: plot_type = PlotType.SMET
    elif profile: plot_type = PlotType.PROFILE
    if plot_type:
        _check_kwargs(plot_type,help=True, **kwargs)
    else:
        _print_help(PlotType.PRO)
        _print_help(PlotType.SMET)
        _print_help(PlotType.PROFILE)    
def plot(snowpack_file: Optional[SnowpackReader] = None, smetfile: Optional[SMETFile] = None, profile: Optional[snowpat.snowpackreader.Snowpack.Snowpack] = None, **kwargs)

Plot function for visualizing Snowpack or SMET data. Only one of snowpack, smet, or profile should be provided.

Parameters: - snowpack: Optional[spr.SnowpackReader], the snowpack data to plot. - smet: Optional[smet.SMETFile], the SMET file to plot. - profile: Optional[spr.Snowpack], the snowpack profile to plot. - **kwargs: Additional keyword arguments for customizing the plot.

Returns: - fig: matplotlib.figure.Figure, the generated plot figure. - plotter: SnowpackPlotter, the plotter object used for generating the plot. (None if profile is provided)

use plot("snowpat") to show the snowpat logo

Expand source code
def plot(snowpack_file: Optional[spr.SnowpackReader] = None, smetfile: Optional[smet.SMETFile] = None, profile:Optional[spr.Snowpack] = None, **kwargs):
    """
    Plot function for visualizing Snowpack or SMET data.
    Only one of snowpack, smet, or profile should be provided.

    Parameters:
    - snowpack: Optional[spr.SnowpackReader], the snowpack data to plot.
    - smet: Optional[smet.SMETFile], the SMET file to plot.
    - profile: Optional[spr.Snowpack], the snowpack profile to plot.
    - **kwargs: Additional keyword arguments for customizing the plot.

    Returns:
    - fig: matplotlib.figure.Figure, the generated plot figure.
    - plotter: SnowpackPlotter, the plotter object used for generating the plot. (None if profile is provided)


    use plot("snowpat") to show the snowpat logo
    """
    if snowpack_file == "snowpat": 
        show_logo()
        return None, None
    
    def assign_arg(arg):
        nonlocal snowpack_file, smetfile, profile
        if isinstance(arg, spr.SnowpackReader):
            snowpack_file = arg
            smetfile = None
            profile = None            
        elif isinstance(arg, smet.SMETFile):
            smetfile = arg  
            snowpack_file = None
            profile = None
        elif isinstance(arg, spr.Snowpack):
            profile = arg
            snowpack_file = None    
            smetfile = None
        elif arg is None:
            pass
        else:
            raise TypeError(f"Arguments need to be one of: [SNOWPACK, SMET, PROFILE]; got: {type(arg).__name__}")
    
    assign_arg(snowpack_file)
    assign_arg(smetfile)
    assign_arg(profile)
    _check_args(snowpack_file, smetfile, profile)
    
    outfile = None
    if "outfile" in kwargs:
        outfile = kwargs.pop("outfile")
    
    if snowpack_file:
        _check_kwargs(PlotType.PRO, **kwargs)
        plotter = SnowpackPlotter(snowpack_file)
        if "profile_on" in kwargs:
            date = kwargs.pop("profile_on")
            plotter.plotProfileOn(date, **kwargs)
        else:
            plotter.plot(**kwargs)
        if outfile:
            plotter.save(outfile)
    elif smetfile:
        _check_kwargs(PlotType.SMET, **kwargs)
        raise NotImplementedError("Plotting from SMET files is not yet implemented")
    elif profile:
        _check_kwargs(PlotType.PROFILE, **kwargs)
        return plotProfile(profile, out=outfile, **kwargs), None
    else:
        raise ValueError("Either snowpack, smet or profile must be provided")   
    return plotter.latest_fig, plotter     
def plotProfile(profile: snowpat.snowpackreader.Snowpack.Snowpack, out: str = None, ind_mfcrust=True, standardized_limits: bool = True) ‑> matplotlib.figure.Figure
Expand source code
def plotProfile(profile:Snowpack, out:str = None, ind_mfcrust = True, standardized_limits:bool=True)->plt.Figure:
    data = _aggregate_data(profile)
    if not data:
        return None
    fig = plt.figure()
    ax = fig.add_subplot(111)
    ax.yaxis.tick_right()
    ax.yaxis.set_label_position("right")
    ax.set_ylabel("Height (cm)")

    if standardized_limits:
        ymax = 250 if data["0501"][-1] < 250 else data["0501"][-1]
        ax.set_ylim(0, ymax)
    
    mpl.rcParams['hatch.linewidth'] = 2.0
    
    _plot_hardness(ax, data, ind_mfcrust, profile.isNewton, profile.old_hardness)
    _plot_temperature(ax, data, standardized_limits)
    _plot_stability_index(ax, data)
    
    plt.close(fig)
    if out:
        fig.savefig(out)
    return fig
def show_figure(fig: matplotlib.figure.Figure)

A helper function, to easily make a figure available to be displayed with plt.show .

Parameters: fig (matplotlib.figure.Figure): The figure to be displayed.

Expand source code
def show_figure(fig:plt.Figure):
    """
    A helper function, to easily make a figure available to be displayed with plt.show .

    Parameters:
    fig (matplotlib.figure.Figure): The figure to be displayed.

    """
    dummy = plt.figure()
    new_manager = dummy.canvas.manager
    new_manager.canvas.figure = fig
    fig.set_canvas(new_manager.canvas)

Classes

class SnowpackPlotter (snowpack: SnowpackReader, savefile=None)

Initializes an instance of the SnowpackPlotter class.

Args

snowpack : SnowpackReader
The snowpack reader object.
savefile : str, optional
The path to save the plot. Defaults to None.
Expand source code
class SnowpackPlotter:
    def __init__(self, snowpack: SnowpackReader, savefile=None):
        """
        Initializes an instance of the SnowpackPlotter class.

        Args:
            snowpack (SnowpackReader): The snowpack reader object.
            savefile (str, optional): The path to save the plot. Defaults to None.
        """
        if not isinstance(snowpack, SnowpackReader):
            raise ValueError(f"A SnowpackReader object has to be provided, got {type(snowpack).__name__}")
        
        self.savefile = savefile
        self.snowpack = snowpack
        self.latest_fig: Optional[plt.Figure] = None
        self.plot_helper = PlotHelper()
        
        # internal variables
        self.n_cols = 1
                    
    def plot(self, var_codes:List[str] = ["0513"],**kwargs) -> plt.Figure:
        """
        Plots the data for the given variable codes.

        A figure is created with subplots for each variable code.

        Args:
            var_codes (list): A list of variable codes to plot.
            **kwargs: Arbitrary keyword arguments for additional configuration.

        Returns:
            matplotlib.pyplot.Figure: The created figure.
            
        Optional keyword arguments:
        - start (datetime): The start date for the plot.
        - stop (datetime): The stop date for the plot.
        - resolution (str): The resolution for the plot.
        - num_ticks (int): The number of ticks on the x-axis.
        - cmap (Union[mcolors.Colormap, Dict[str, mcolors.Colormap]]): The colormap for the plot.
        - norm (Union[mcolors.Normalize, Dict[str, mcolors.Normalize]]): The normalization for the plot.
        - cbar_label (Union[str, Dict[str,str]]): The colorbar label for the plot.
        - n_cols (int): The number of columns for the subplots.
        - title (str): The title for the plot.
        - vmin (Union[int, Dict[str,int]]): The minimum value for the colormap.
        - vmax (Union[int, Dict[str,int]]): The maximum value for the colormap.
        - adjust_data (Callable[[str,np.ndarray],np.ndarray]): A function for adjusting the data.
        - single_ticks (bool): Whether to use a single x-axis/y-axis for all subplots. (default: True)
        - set_ylabel (bool): Whether to set the y-axis label. (default: True)
        - colorbar (bool): Whether to display the colorbar. (default: True)
        - ind_mfcrust (bool): Whether to indicate the MFCrust specifically. (default: True)
        - mfcrust_color (bool): Plot the meltcrust in orange instead of read. (default: False)
        - subtitle (Dict[str,str]): A dictionary with subtitles for each variable code.

        All other keyword arguments are passed to the pcolormesh function.


        Raises:
            ValueError: If any of the variable codes are not valid.
            RuntimeError: If there is a problem during plotting.
        """
        dates = self._init_data(var_codes)
        
        kwargs = self._parse_kwargs(**kwargs)

        plot_dates, tick_dates = self._aggregateData(var_codes, dates)


        # calculate the number of rows and cols for the subplots
        n_subplots = len(var_codes)
        n_rows = n_subplots // self.plot_helper.n_cols
        if n_subplots % self.plot_helper.n_cols != 0:
            n_rows += 1
        
        # adjust the figure size so it stays nicely looking within a grid
        fig_x = 6.4 * (1+ 2.0 * (self.plot_helper.n_cols-1)) if self.plot_helper.n_cols > 1 else 6.4
        fig_y = 4.8 * (1 + 1.0* (n_rows - 1)) if n_rows > 1 and self.plot_helper.n_cols > 1 else 4.8 * (1 + 0.2* (n_rows - 1))
        fig, axes = plt.subplots(n_rows, self.plot_helper.n_cols, figsize=(fig_x,fig_y), sharex=self.plot_helper.single_ticks, sharey=self.plot_helper.single_ticks)
        
        # add some space between the subplots, to make space for ticks and cbars
        wspace = 0.4 if self.plot_helper.n_cols > 1 and not self.plot_helper.single_ticks else 0.3
        hspace = 0.4 if n_rows > 1 else 0.2
        fig.subplots_adjust(hspace=hspace, wspace=wspace)
        
        if self.plot_helper.title:
            fig.suptitle(self.plot_helper.title, fontsize=16)
        
        # set the indexes at which subplot the x/y labels should be set
        if self.plot_helper.n_cols > 1 and n_rows > 1:
            ids_ylabels = [i for i in range(1, n_subplots+1) if i % self.plot_helper.n_cols == 1]
            ids_xlabels = [i for i in range(n_subplots-self.plot_helper.n_cols+1, n_subplots+1)]
        elif self.plot_helper.n_cols == 1 and n_rows > 1:
            ids_ylabels = [i for i in range(1, n_subplots+1)]
            ids_xlabels = [n_subplots]
        elif self.plot_helper.n_cols > 1 and n_rows == 1:
            ids_ylabels = [1]
            ids_xlabels = [i for i in range(1, n_subplots+1)]
        else:
            ids_xlabels = [1]
            ids_ylabels = [1]

        row = 0
        col = 0
        for sub_id in range(1, n_subplots+1):
            var_code = var_codes[sub_id-1]

            # if it is only one subplot, axes is not an array
            if isinstance(axes, mpl.axes.Axes):
                ax = axes
            else:
                # we need to get the correct axes object, whether it is a 1D or 2D array
                ax = axes[row, col] if self.plot_helper.n_cols > 1 and n_rows > 1 else axes[row]            
            row += 1 if (sub_id % self.plot_helper.n_cols == 1) or (self.plot_helper.n_cols == 1 or n_rows == 1) else 0
            col = (col+1) % self.plot_helper.n_cols           
            
            cmap = self.plot_helper.getCmap(var_code)
            norm = self.plot_helper.getNorm(var_code)
            cmap.set_bad(ax.get_facecolor())
            
            mpl.rcParams['hatch.linewidth'] = 3.0
            plot_dates_len = len(plot_dates)

            for date_id, plot_date in enumerate(plot_dates):
                # set the bar to the correct date, i.e. the x boundaries
                x = [plot_date, plot_dates[date_id+1]] if date_id < plot_dates_len-1 else [plot_date, plot_date + pd.Timedelta(days=1)]
                profile = self.snowpack.get_profile_on(plot_date)
                # get the layer boundaries, which will be the y boundaries
                h = profile.get_param("0501") # "0501" are the layer boundaries
                # the data in the cells
                data = profile.get_param(var_code, return_missing=True)
                data = self.plot_helper.handleData(var_code, data)
                if h.size <= 1: continue

                # we need to handle missing values
                mesh = np.array([data,])
                conditions = mesh== -999
                masked_mesh = np.ma.masked_where(conditions, mesh)     

                cplot = ax.pcolormesh(x, h, masked_mesh.transpose(), norm=norm, cmap=cmap, **kwargs)
                if var_code == "0513":
                    # this function will add the crust lines to the plot, if it any is set to true
                    _add_crust_lines(ax, x, h, mesh, self.plot_helper.ind_mfcrust, self.plot_helper.mfcrust_color)
           
            # set the x and y limits, for better visualization
            ax.set_xlim(self.plot_helper.start, self.plot_helper.stop)
            hmax = self.snowpack.get_profile_on(self.plot_helper.stop).get_param("0501")[-1]
            ymax = hmax if hmax > 250 else 250
            ymin,_ = ax.get_ylim()
            ax.set_ylim(ymin, ymax)            
            
                
            self._setColorbar(ax, cplot, var_code, fig)
            self._set_title(ax, var_code)
                        
            # set xticks
            if (self.plot_helper.single_ticks and sub_id in ids_xlabels) or not self.plot_helper.single_ticks:
                if self.plot_helper.num_ticks:
                    ax.set_xticks(tick_dates)
                    labels = [mdates.num2date(label).strftime('%Y-%m-%d') for label in ax.get_xticks()]
                    ax.set_xticklabels(labels, rotation=30)
                else:
                    custom_formatter = CustomFormatter('%m-%d', '%m-%d\n%Y')
                    ax.xaxis.set_major_formatter(ticker.FuncFormatter(custom_formatter))
                ax.set_xlabel("Date")
            if (self.plot_helper.single_ticks and sub_id in ids_ylabels) or not self.plot_helper.single_ticks and self.plot_helper.set_ylabel:
                ax.set_ylabel("HS [cm]")

                    
        self.latest_fig = fig   
        plt.close(fig)
        return self.latest_fig
        
    def save(self, outfile: str = None):
        """
        Save the latest figure to an output file.

        Args:
            outfile (str, optional): The path to the output file. If not provided,
                the figure will be saved to the default savefile specified during
                object initialization.

        Raises:
            ValueError: If no output file is provided.
        """
        if outfile:
            self.latest_fig.savefig(outfile)
        elif self.savefile:
            self.latest_fig.savefig(self.savefile)
        else:
            raise ValueError("No output file provided")
        
    def getSubplot(self, index: int) -> plt.Axes:
        """
        Retrieve a subplot from the latest figure.

        Parameters:
            index (int): The index of the subplot to retrieve.

        Returns:
            plt.Axes: The requested subplot.

        """
        n_cols = self.plot_helper.n_cols
        row = (index-1) // n_cols if n_cols > 1 else index
        col = (index-1) % n_cols if n_cols > 1 else 0
        return self.latest_fig.get_axes()[row] if n_cols == 1 else self.latest_fig.get_axes()[row, col]
    
    def getLatestFigure(self)->plt.Figure:
        return self.latest_fig

    def plotProfileOn(self, date:datetime, **kwargs):
        profile = self.snowpack.get_profile_on(date)
        self.latest_fig =  plotProfile(profile, **kwargs)
        return self.latest_fig
        
    def show(self):
            """
            Displays the latest figure of the snowpack.

            """
            show_figure(self.latest_fig)

    def _parse_kwargs(self, **kwargs):
        if "start" in kwargs:
            self.plot_helper.start = kwargs.pop("start")
        if "stop" in kwargs:
            self.plot_helper.stop = kwargs.pop("stop")
        if "resolution" in kwargs:
            self.plot_helper.resolution = kwargs.pop("resolution")
        if "cbar_label" in kwargs:
            cbar_label = kwargs.pop("cbar_label")
            if cbar_label is None:
                self.plot_helper.set_cbar_label = False
            else:
                self.plot_helper.cbar_label = cbar_label
        if "n_cols" in kwargs:
            self.plot_helper.n_cols = kwargs.pop("n_cols")
        if "num_ticks" in kwargs:
            self.plot_helper.num_ticks = kwargs.pop("num_ticks")      
        if "cmap" in kwargs:
            cmap = kwargs.pop("cmap")
            if isinstance(cmap, mcolors.Colormap):
                self.plot_helper.cmap = cmap
            elif isinstance(cmap, dict):
                self.plot_helper.cmap_dict = cmap
            else:
                raise ValueError("cmap must be a mcolors.Colormap object or a dict with var_codes as keys and mcolors.Colormap objects as values")
        if "norm" in kwargs:
            norm = kwargs.pop("norm")
            if isinstance(norm, mcolors.Normalize):
                self.plot_helper.norm = norm
            elif isinstance(norm, dict):
                self.plot_helper.norm_dict = norm
            else:
                raise ValueError("norm must be a mcolors.Normalize object or a dict with var_codes as keys and mcolors.Normalize objects as values")
        if "title" in kwargs:
            self.plot_helper.title = kwargs.pop("title")
        if "vmin" in kwargs and "vmax" in kwargs:
            vmin = kwargs.pop("vmin")
            vmax = kwargs.pop("vmax")
            if isinstance(vmin, int) and isinstance(vmax, int):
                self.plot_helper.norm = mcolors.Normalize(vmin=vmin, vmax=vmax)
            elif isinstance(vmin, dict) and isinstance(vmax, dict):
                self.plot_helper.norm_dict = {var_code: mcolors.Normalize(vmin=vmin[var_code], vmax=vmax[var_code]) for var_code in vmin.keys()}
        elif ("vmin" in kwargs and not "vmax" in kwargs) or (not "vmin" in kwargs and "vmax" in kwargs):
            raise ValueError("Both vmin and vmax must be provided")
        if "adjust_data" in kwargs:
            self.plot_helper.adjust_data = kwargs.pop("adjust_data")
        if "subtitle" in kwargs:
            self.plot_helper.subtitle = kwargs.pop("subtitle")
            
        self.plot_helper.single_ticks = kwargs.pop("single_ticks", True)
        self.plot_helper.set_ylabel = kwargs.pop("set_ylabel", True)
        self.plot_helper.colorbar = kwargs.pop("colorbar", True)
        self.plot_helper.ind_mfcrust = kwargs.pop("ind_mfcrust", True)
        self.plot_helper.mfcrust_color = kwargs.pop("mfcrust_color", False)
        
        return kwargs
                   
    
    def _init_data(self, var_codes):
        for var_code in var_codes:
            if not self.snowpack.DataCodes.get(var_code):
                raise ValueError(f"Invalid variable code: {var_code}")
        dates = self.snowpack.get_all_dates()
        self.plot_helper.start = dates[0]
        self.plot_helper.stop = dates[-1]
        return dates
        
    def _aggregateData(self, var_codes, dates):
        start = self.plot_helper.start
        stop = self.plot_helper.stop
        if self.plot_helper.resolution:
            dates = pd.date_range(start, stop, freq=self.plot_helper.resolution)
            
        plot_dates = []
        for date in dates:
            profile = self.snowpack.get_profile_on(date)
            if profile:
                plot_dates.append(date)

                    
        tick_dates = None
        if self.plot_helper.num_ticks:
            tick_dates = pd.date_range(start, stop, freq=(stop-start)/self.plot_helper.num_ticks)
            if (stop-start) > pd.Timedelta(days=1):
                tick_dates = tick_dates.floor("D")
            else:
                tick_dates = tick_dates.floor("H")
        
        return plot_dates, tick_dates

    def _setColorbar(self, ax:plt.Axes, cplot:QuadMesh, var_code:str, fig:plt.Figure):
        if self.plot_helper.colorbar:
            ax_pos = ax.get_position()
            cax = fig.add_axes([ax_pos.x1+0.01, ax_pos.y0, 0.02, ax_pos.height])
            cbar = plt.colorbar(cplot, cax=cax)
            
            label, is_available = self.plot_helper.getCbarLabel(var_code)
            
            if is_available:
                cbar.set_label(label, fontsize=10)
            else:
                name = self.snowpack.DataCodes.get(var_code, var_code)
                unit = self.snowpack.units.get(var_code, "b.E.")
                label = name + "\n[" + unit + "]"
                cbar.set_label(label, fontsize=10)
                
            if var_code == "0513":
                cbar.set_ticks([1,2,3,4,5,6,7,8,9])
                cbar.set_ticklabels(['PP', 'DF', 'RG', 'FC', 'DH', 'SH', 'MF', 'IF', 'FCxr'])
                cbar.minorticks_off()

    def _set_title(self, ax:plt.Axes, var_code:str):
        title, is_available = self.plot_helper.getSubtitle(var_code)
        if is_available:
            ax.set_title(title)

Methods

def getLatestFigure(self) ‑> matplotlib.figure.Figure
Expand source code
def getLatestFigure(self)->plt.Figure:
    return self.latest_fig
def getSubplot(self, index: int) ‑> matplotlib.axes._axes.Axes

Retrieve a subplot from the latest figure.

Parameters

index (int): The index of the subplot to retrieve.

Returns

plt.Axes
The requested subplot.
Expand source code
def getSubplot(self, index: int) -> plt.Axes:
    """
    Retrieve a subplot from the latest figure.

    Parameters:
        index (int): The index of the subplot to retrieve.

    Returns:
        plt.Axes: The requested subplot.

    """
    n_cols = self.plot_helper.n_cols
    row = (index-1) // n_cols if n_cols > 1 else index
    col = (index-1) % n_cols if n_cols > 1 else 0
    return self.latest_fig.get_axes()[row] if n_cols == 1 else self.latest_fig.get_axes()[row, col]
def plot(self, var_codes: List[str] = ['0513'], **kwargs) ‑> matplotlib.figure.Figure

Plots the data for the given variable codes.

A figure is created with subplots for each variable code.

Args

var_codes : list
A list of variable codes to plot.
**kwargs
Arbitrary keyword arguments for additional configuration.

Returns

matplotlib.pyplot.Figure
The created figure.

Optional keyword arguments: - start (datetime): The start date for the plot. - stop (datetime): The stop date for the plot. - resolution (str): The resolution for the plot. - num_ticks (int): The number of ticks on the x-axis. - cmap (Union[mcolors.Colormap, Dict[str, mcolors.Colormap]]): The colormap for the plot. - norm (Union[mcolors.Normalize, Dict[str, mcolors.Normalize]]): The normalization for the plot. - cbar_label (Union[str, Dict[str,str]]): The colorbar label for the plot. - n_cols (int): The number of columns for the subplots. - title (str): The title for the plot. - vmin (Union[int, Dict[str,int]]): The minimum value for the colormap. - vmax (Union[int, Dict[str,int]]): The maximum value for the colormap. - adjust_data (Callable[[str,np.ndarray],np.ndarray]): A function for adjusting the data. - single_ticks (bool): Whether to use a single x-axis/y-axis for all subplots. (default: True) - set_ylabel (bool): Whether to set the y-axis label. (default: True) - colorbar (bool): Whether to display the colorbar. (default: True) - ind_mfcrust (bool): Whether to indicate the MFCrust specifically. (default: True) - mfcrust_color (bool): Plot the meltcrust in orange instead of read. (default: False) - subtitle (Dict[str,str]): A dictionary with subtitles for each variable code.

All other keyword arguments are passed to the pcolormesh function.

Raises

ValueError
If any of the variable codes are not valid.
RuntimeError
If there is a problem during plotting.
Expand source code
def plot(self, var_codes:List[str] = ["0513"],**kwargs) -> plt.Figure:
    """
    Plots the data for the given variable codes.

    A figure is created with subplots for each variable code.

    Args:
        var_codes (list): A list of variable codes to plot.
        **kwargs: Arbitrary keyword arguments for additional configuration.

    Returns:
        matplotlib.pyplot.Figure: The created figure.
        
    Optional keyword arguments:
    - start (datetime): The start date for the plot.
    - stop (datetime): The stop date for the plot.
    - resolution (str): The resolution for the plot.
    - num_ticks (int): The number of ticks on the x-axis.
    - cmap (Union[mcolors.Colormap, Dict[str, mcolors.Colormap]]): The colormap for the plot.
    - norm (Union[mcolors.Normalize, Dict[str, mcolors.Normalize]]): The normalization for the plot.
    - cbar_label (Union[str, Dict[str,str]]): The colorbar label for the plot.
    - n_cols (int): The number of columns for the subplots.
    - title (str): The title for the plot.
    - vmin (Union[int, Dict[str,int]]): The minimum value for the colormap.
    - vmax (Union[int, Dict[str,int]]): The maximum value for the colormap.
    - adjust_data (Callable[[str,np.ndarray],np.ndarray]): A function for adjusting the data.
    - single_ticks (bool): Whether to use a single x-axis/y-axis for all subplots. (default: True)
    - set_ylabel (bool): Whether to set the y-axis label. (default: True)
    - colorbar (bool): Whether to display the colorbar. (default: True)
    - ind_mfcrust (bool): Whether to indicate the MFCrust specifically. (default: True)
    - mfcrust_color (bool): Plot the meltcrust in orange instead of read. (default: False)
    - subtitle (Dict[str,str]): A dictionary with subtitles for each variable code.

    All other keyword arguments are passed to the pcolormesh function.


    Raises:
        ValueError: If any of the variable codes are not valid.
        RuntimeError: If there is a problem during plotting.
    """
    dates = self._init_data(var_codes)
    
    kwargs = self._parse_kwargs(**kwargs)

    plot_dates, tick_dates = self._aggregateData(var_codes, dates)


    # calculate the number of rows and cols for the subplots
    n_subplots = len(var_codes)
    n_rows = n_subplots // self.plot_helper.n_cols
    if n_subplots % self.plot_helper.n_cols != 0:
        n_rows += 1
    
    # adjust the figure size so it stays nicely looking within a grid
    fig_x = 6.4 * (1+ 2.0 * (self.plot_helper.n_cols-1)) if self.plot_helper.n_cols > 1 else 6.4
    fig_y = 4.8 * (1 + 1.0* (n_rows - 1)) if n_rows > 1 and self.plot_helper.n_cols > 1 else 4.8 * (1 + 0.2* (n_rows - 1))
    fig, axes = plt.subplots(n_rows, self.plot_helper.n_cols, figsize=(fig_x,fig_y), sharex=self.plot_helper.single_ticks, sharey=self.plot_helper.single_ticks)
    
    # add some space between the subplots, to make space for ticks and cbars
    wspace = 0.4 if self.plot_helper.n_cols > 1 and not self.plot_helper.single_ticks else 0.3
    hspace = 0.4 if n_rows > 1 else 0.2
    fig.subplots_adjust(hspace=hspace, wspace=wspace)
    
    if self.plot_helper.title:
        fig.suptitle(self.plot_helper.title, fontsize=16)
    
    # set the indexes at which subplot the x/y labels should be set
    if self.plot_helper.n_cols > 1 and n_rows > 1:
        ids_ylabels = [i for i in range(1, n_subplots+1) if i % self.plot_helper.n_cols == 1]
        ids_xlabels = [i for i in range(n_subplots-self.plot_helper.n_cols+1, n_subplots+1)]
    elif self.plot_helper.n_cols == 1 and n_rows > 1:
        ids_ylabels = [i for i in range(1, n_subplots+1)]
        ids_xlabels = [n_subplots]
    elif self.plot_helper.n_cols > 1 and n_rows == 1:
        ids_ylabels = [1]
        ids_xlabels = [i for i in range(1, n_subplots+1)]
    else:
        ids_xlabels = [1]
        ids_ylabels = [1]

    row = 0
    col = 0
    for sub_id in range(1, n_subplots+1):
        var_code = var_codes[sub_id-1]

        # if it is only one subplot, axes is not an array
        if isinstance(axes, mpl.axes.Axes):
            ax = axes
        else:
            # we need to get the correct axes object, whether it is a 1D or 2D array
            ax = axes[row, col] if self.plot_helper.n_cols > 1 and n_rows > 1 else axes[row]            
        row += 1 if (sub_id % self.plot_helper.n_cols == 1) or (self.plot_helper.n_cols == 1 or n_rows == 1) else 0
        col = (col+1) % self.plot_helper.n_cols           
        
        cmap = self.plot_helper.getCmap(var_code)
        norm = self.plot_helper.getNorm(var_code)
        cmap.set_bad(ax.get_facecolor())
        
        mpl.rcParams['hatch.linewidth'] = 3.0
        plot_dates_len = len(plot_dates)

        for date_id, plot_date in enumerate(plot_dates):
            # set the bar to the correct date, i.e. the x boundaries
            x = [plot_date, plot_dates[date_id+1]] if date_id < plot_dates_len-1 else [plot_date, plot_date + pd.Timedelta(days=1)]
            profile = self.snowpack.get_profile_on(plot_date)
            # get the layer boundaries, which will be the y boundaries
            h = profile.get_param("0501") # "0501" are the layer boundaries
            # the data in the cells
            data = profile.get_param(var_code, return_missing=True)
            data = self.plot_helper.handleData(var_code, data)
            if h.size <= 1: continue

            # we need to handle missing values
            mesh = np.array([data,])
            conditions = mesh== -999
            masked_mesh = np.ma.masked_where(conditions, mesh)     

            cplot = ax.pcolormesh(x, h, masked_mesh.transpose(), norm=norm, cmap=cmap, **kwargs)
            if var_code == "0513":
                # this function will add the crust lines to the plot, if it any is set to true
                _add_crust_lines(ax, x, h, mesh, self.plot_helper.ind_mfcrust, self.plot_helper.mfcrust_color)
       
        # set the x and y limits, for better visualization
        ax.set_xlim(self.plot_helper.start, self.plot_helper.stop)
        hmax = self.snowpack.get_profile_on(self.plot_helper.stop).get_param("0501")[-1]
        ymax = hmax if hmax > 250 else 250
        ymin,_ = ax.get_ylim()
        ax.set_ylim(ymin, ymax)            
        
            
        self._setColorbar(ax, cplot, var_code, fig)
        self._set_title(ax, var_code)
                    
        # set xticks
        if (self.plot_helper.single_ticks and sub_id in ids_xlabels) or not self.plot_helper.single_ticks:
            if self.plot_helper.num_ticks:
                ax.set_xticks(tick_dates)
                labels = [mdates.num2date(label).strftime('%Y-%m-%d') for label in ax.get_xticks()]
                ax.set_xticklabels(labels, rotation=30)
            else:
                custom_formatter = CustomFormatter('%m-%d', '%m-%d\n%Y')
                ax.xaxis.set_major_formatter(ticker.FuncFormatter(custom_formatter))
            ax.set_xlabel("Date")
        if (self.plot_helper.single_ticks and sub_id in ids_ylabels) or not self.plot_helper.single_ticks and self.plot_helper.set_ylabel:
            ax.set_ylabel("HS [cm]")

                
    self.latest_fig = fig   
    plt.close(fig)
    return self.latest_fig
def plotProfileOn(self, date: , **kwargs)
Expand source code
def plotProfileOn(self, date:datetime, **kwargs):
    profile = self.snowpack.get_profile_on(date)
    self.latest_fig =  plotProfile(profile, **kwargs)
    return self.latest_fig
def save(self, outfile: str = None)

Save the latest figure to an output file.

Args

outfile : str, optional
The path to the output file. If not provided, the figure will be saved to the default savefile specified during object initialization.

Raises

ValueError
If no output file is provided.
Expand source code
def save(self, outfile: str = None):
    """
    Save the latest figure to an output file.

    Args:
        outfile (str, optional): The path to the output file. If not provided,
            the figure will be saved to the default savefile specified during
            object initialization.

    Raises:
        ValueError: If no output file is provided.
    """
    if outfile:
        self.latest_fig.savefig(outfile)
    elif self.savefile:
        self.latest_fig.savefig(self.savefile)
    else:
        raise ValueError("No output file provided")
def show(self)

Displays the latest figure of the snowpack.

Expand source code
def show(self):
        """
        Displays the latest figure of the snowpack.

        """
        show_figure(self.latest_fig)