Пример #1
0
    def gen_ylabel(self):
        r"""
        Generate automatic label for y-axis
        Uses attributes already set in class

        >>> TSP = TimeSeriesPlot()
        >>> TSP.phenomena_short_name = 'O3'
        >>> TSP.units = 'ug/m3'
        >>> label = TSP.gen_ylabel()
        >>> print(label)
        O3 ($\mu g\ m^{-3}$)
        """
        ylabel = ''
        if self.phenomena_short_name is not None:
            ylabel += self.phenomena_short_name
        if self.units is not None:
            units = plotting_functions.units_str(self.units)
            if self.phenomena_short_name is not None:
                ylabel += ' ('
            ylabel += units
            if self.phenomena_short_name is not None:
                ylabel += ')'

        return ylabel
Пример #2
0
def plot_regime_stats(ini_dict,
                      xstat_list=None,
                      ystat_list=None,
                      statsfile=None):
    """
    Produce scatter plots of each xstat vs each ystat, labelling points
    by corresponding weather regime.
    By default, picks out meanobs vs a selected group of useful stats.

    :param ini_dict: Dictionary of a :class:`inifile` object. Should contain:

                      * 'plot_dir' - location of statistics file to read in,
                        plus output location
                      * 'short_name_list'

    :param xstat_list: List of stats to plot on the x axis, in turn.
                       If set to None, defaults to ['meanobs']
    :param ystat_list: List of stats to plot on the y axis, in turn.
                       If set to None, defaults to
                       ['mnmb', 'fge', 'bias', 'meanmod']

    *Available stats for plotting:*
    'nsites': 'Number of sites',
    'n': 'Number of points',
    'correlation': 'Correlation',
    'bias': 'Bias',
    'nmb': 'Normalised Mean Bias',
    'mnmb': 'Modified Normalised Mean Bias',
    'nmge': 'Normalised Mean Gross Error',
    'fge': 'Fractional Gross Error',
    'rmse': 'Root Mean Square Error',
    'fac2': 'Factor of 2',
    'ioa': 'Index of Agreement',
    'threshold': 'Threshold',
    'ORSS': 'Odds Ratio Skill Score',
    'hitrate': 'Hitrate',
    'falsealarmrate': 'False Alarm Rate',
    'falsealarmratio': 'False Alarm Ratio',
    'o>=t_m>=t': 'Number Obs >= Threshold and Model >= Threshold',
    'o<t_m>=t': 'Number Obs < Threshold and Model >= Threshold',
    'o>=t_m<t': 'Number Obs >= Threshold and Model < Threshold',
    'o<t_m<t': 'Number Obs < Threshold and Model < Threshold',
    'maxobs': 'Maximum Observation Value',
    'maxmod': 'Maximum Model Value',
    'meanobs': 'Mean Observation Value',
    'meanmod': 'Mean Model Value',
    'sdobs': 'Standard Deviation of Observations',
    'sdmod': 'Standard Deviation of Model',
    'perc_correct': 'Percentage of Correct values',
    'perc_over': 'Percentage of Over-predicions',
    'perc_under': 'Percentage of Under-predictions',
    'units': 'Units'

    Firstly get some example data, and ensure that plot_dir is set.
    Also shorten the short_names required to be plotted.

    >>> import adaq_functions
    >>> ini_dict, sites_data, od, md_list = adaq_functions.get_exampledata(
    ... exampletype="full") # doctest: +ELLIPSIS
    Reading inifile .../example_data_5days.ini
    Number of sites:  5
    >>> import config
    >>> ini_dict['plot_dir'] = config.CODE_DIR + "/adaqdocs/figures/"
    >>> ini_dict['short_name_list'] = ['O3']
    >>> ini_dict['calc_stats_format_list'] = ['csv']

    Before any plotting can be done, first need to calculate some statistics:

    >>> statsfile = 'regime_stats'
    >>> tsstats = adaq_functions.calc_stats(
    ... ini_dict, od, md_list, statsfile_prefix=statsfile) # doctest: +ELLIPSIS
    Statistics saved to  .../regime_stats.csv

    Can now read in the file which contains statistics and plot these.
    >>> plot_regime_stats(ini_dict, ystat_list=['bias'],
    ... statsfile=statsfile) # doctest: +ELLIPSIS
    Statistics found at .../regime_stats.csv
    Saved figure  .../meanobs_vs_bias_O3.png
    """

    #Set up defaults
    if xstat_list is None:
        xstat_list = ['meanobs']
    if ystat_list is None:
        ystat_list = ['mnmb', 'fge', 'bias', 'meanmod']

    plotdir = ini_dict['plot_dir']
    if plotdir[-1] != '/':
        plotdir += '/'
    if statsfile is None:
        filein = plotdir + 'stats.csv'
    else:
        filein = plotdir + statsfile + '.csv'
    try:
        #Read in stats.csv file from plot directory
        stats = np.genfromtxt(filein,
                              dtype=str,
                              skip_header=1,
                              autostrip=True,
                              delimiter=',')
        print('Statistics found at ', filein)
    except:
        #If stats file not found in directory, raise error
        raise IOError("No statistics file found in plotdir for reading")

    #Initialise certain stats to carry units
    needs_units = ['meanobs', 'meanmod', 'maxobs', 'maxmod', 'bias', 'rmse']

    regimes = []
    #Pick out regime numbers from model label
    #Ignore first two columns, 'Phenomenon' and 'Statistic'
    for iregime in stats[0][2:]:
        if iregime[-2] in ['1', '2', '3']:
            regimes.append(iregime[-2:])
        else:
            regimes.append(iregime[-1])

    #Produce species-specific plots
    for short_name in ini_dict['short_name_list']:
        for xstat in xstat_list:
            for ystat in ystat_list:
                for item in stats:
                    #Create list of selected stat
                    if item[0] == short_name and item[1].split()[0] == xstat:
                        x = item[2:]
                    elif item[0] == short_name and item[1].split()[0] == ystat:
                        y = item[2:]
                    elif item[0] == short_name and item[1] == 'units':
                        units = item[2]
                        units = plotting_functions.units_str(units)

                plt.figure(figsize=(8, 7.25))
                plt.scatter(x, y, s=0)

                #Add units to axes labels if appropriate
                if xstat in needs_units:
                    xunits = ' (' + units + ')'
                else:
                    xunits = ''
                if ystat in needs_units:
                    yunits = ' (' + units + ')'
                else:
                    yunits = ''
                plt.xlabel(xstat + xunits)
                plt.ylabel(ystat + yunits)
                plt.title(short_name)

                #Use regime number instead of dot to indicate data point
                for i, regime in enumerate(regimes):
                    plt.annotate(regime, (x[i], y[i]),
                                 horizontalalignment='center',
                                 verticalalignment='center',
                                 color='teal')

                ymin, ymax = plt.ylim()
                xmin, xmax = plt.xlim()
                #Add axis line at y=0 if there's +ve and -ve data
                if ymin < 0 < ymax:
                    plt.axhline(color='gray')

                #Equalise axes when plotting similar stats
                if xstat == 'meanobs' and ystat == 'meanmod':
                    plt.ylim(min(xmin, ymin), max(xmax, ymax))
                    plt.xlim(min(xmin, ymin), max(xmax, ymax))

                filename = xstat + '_vs_' + ystat + '_' + short_name + '.png'
                plt.savefig(plotdir + filename)

                print('Saved figure ', plotdir + filename)
                plt.close()
Пример #3
0
    def plot(self):
        """
        Plot quantile-quantile plot.
        Returns figure object for further plotting if needed.
        """

        if not self.lines:
            raise ValueError(
                "Quantile-Quantile plot: no lines have been added")

        self.get_percentiles()

        self.fig = plt.figure()
        ax = plt.gca()

        axis_max = np.nanmax(self.xpercentiles)
        axis_min = np.nanmin(self.xpercentiles)

        for line in self.lines:

            if line['marker'] is None:
                #Ensure a marker is set
                line['marker'] = 'o'

            if 'ypercentiles' not in line:
                #ypercentiles were not calculated due to all nan data
                #therefore cannot plot this line.
                continue

            plt.scatter(self.xpercentiles,
                        line['ypercentiles'],
                        color=np.atleast_1d(line['colour']),
                        label=line['label'],
                        linewidth=line['linewidth'],
                        marker=line['marker'])
            axis_max = np.nanmax([axis_max, np.nanmax(line['ypercentiles'])])
            axis_min = np.nanmin([axis_min, np.nanmin(line['ypercentiles'])])

        #Set x/y lims
        if axis_min > 0 and axis_min < axis_max / 25.:
            #Ensure zero is plotted if close to zero.
            axis_min = 0
        ax.set_xlim([axis_min, axis_max])
        ax.set_ylim([axis_min, axis_max])

        #Add 1-1 line
        if self.one2one:
            plt.plot([axis_min, axis_max], [axis_min, axis_max],
                     color='k',
                     linestyle='-')

        #Add title
        if self.title is None:
            self.gen_title()
        ax.set_title(self.title)

        #Add axis labels
        if self.xlabel is None:
            self.xlabel = self.xcube.attributes['label']
            if self.units is not None:
                units = plotting_functions.units_str(self.units)
                self.xlabel += ' (' + units + ')'
        ax.set_xlabel(self.xlabel)

        if self.ylabel is None:
            if self.units is not None:
                units = plotting_functions.units_str(self.units)
            if len(self.lines) == 1:
                self.ylabel = self.lines[0]['cube'].attributes['label']
                if self.units is not None:
                    self.ylabel += ' (' + units + ')'
            else:
                self.ylabel = self.phenomena_short_name + ' (' + units + ')'
        ax.set_ylabel(self.ylabel)

        #Add legend if needed
        if len(self.lines) > 1:
            plotting_functions.add_legend_belowaxes(scatterpoints=1)

        #Add gridlines
        if self.gridlines:
            plt.grid()

        # Apply branding
        if self.mobrand:
            line_plot.add_mobranding()

        return self.fig
Пример #4
0
    def plot(self):
        """
        Plot histogram.
        Returns figure object for further plotting if needed
        """

        if not self.lines:
            raise ValueError("Histogram: no lines have been added")

        if self.fig is None:
            self.fig = plt.figure()
        ax = self.fig.add_subplot(111)

        if self.bin_edges is None:
            self.gen_binedges()

        max_x = None

        for line in self.lines:
            #Get a 1d array of data
            data = np.reshape(line['cube'].data, -1)
            if sum(np.isfinite(data)) == 0:
                #All nan data, so don't include this line
                warnings.warn('All nan data')
                continue
            returned_tuple = ax.hist(data,
                                     bins=self.bin_edges,
                                     histtype=self.histtype,
                                     normed=self.normed,
                                     color=line['colour'],
                                     label=line['label'],
                                     linewidth=line['linewidth'],
                                     range=[np.nanmin(data),
                                            np.nanmax(data)])

            if self.maxperc != 100:
                cumulative_sum = np.cumsum(returned_tuple[0])
                #Get maximum value to be 1
                cumulative_sum /= cumulative_sum[-1]
                indices = np.where(cumulative_sum * 100. > self.maxperc)
                if indices[0].size:
                    max_x_line = returned_tuple[1][indices[0][0]]
                    if max_x is None:
                        max_x = max_x_line
                    else:
                        max_x = np.max([max_x, max_x_line])

        #Add legend
        if self.legend:
            plotting_functions.add_legend_belowaxes()

        #Add title
        if self.title is None:
            self.gen_title()
        ax.set_title(self.title)

        #Add ylabel
        if self.ylabel is None:
            self.ylabel = 'Frequency'
        ax.set_ylabel(self.ylabel)

        #Add xlabel (units)
        if self.xlabel is None:
            if self.units is not None:
                units = plotting_functions.units_str(self.units)
                self.xlabel = units
            else:
                self.xlabel = ''
        ax.set_xlabel(self.xlabel)

        #Set maximum x limit
        if max_x is not None:
            ax.set_xlim(right=max_x)

        #Add gridlines
        if self.gridlines:
            plt.grid()

        # Apply branding
        if self.mobrand:
            line_plot.add_mobranding()

        return self.fig
Пример #5
0
def tsp_statistic(statscube_list,
                  statistic,
                  plotdir='./',
                  filesuffix='',
                  colours_list=None,
                  xcoordname=None):
    """
    Function to plot timeseries of a statistic, given a list of statistics
    cubes from :class:`TimeSeriesStats`.

    Note this routine is also called from the higher level
    :func:`adaq_plotting.plot_timeseries_of_stats`

    :param statscube_list: List of statistics cubes created by \
:meth:`timeseries_stats.TimeSeriesStats.convert_to_cube`
                           and then merged such that each cube has more than
                           one time coordinate.
    :param statistic: Name of statistic to plot. Should be one of the keys of
                      the :data:`timeseries_stats.STATS_INFO` dictionary
    :param plotdir: String - directory for plot to be saved.
    :param filesuffix: String to add to end of filename for specific naming
                       purposes. By default adds nothing.
    :param colours_list: List of colours to use to match in order against
                         cubes in statscube_list. If not set, defaults to
                         plotting_functions.COLOURS
    :param xcoordname: Name of coordinate to use on x-axis. If not set, defaults
                       to first dimension coordinate (usually time).

    Example usage:

    >>> import adaq_functions
    >>> ini_dict, stats_cubes = adaq_functions.get_exampledata(
    ... exampletype='stats') # doctest: +ELLIPSIS
    Reading inifile ...example_data_10days.ini
    >>> import config
    >>> plotdir = config.CODE_DIR + "/adaqdocs/figures"

    Extract a list of just the O3 cubes:

    >>> stats_cubes_o3 = []
    >>> for cubelist in stats_cubes:
    ...     stat_cube_o3 = cubelist.extract(iris.AttributeConstraint(
    ... short_name='O3'), strict=True)
    ...     stats_cubes_o3.append(stat_cube_o3)

    >>> print(stats_cubes_o3)
    [<iris 'Cube' of mass_concentration_of_ozone_in_air / (1) \
(istatistic: 32; time: 10)>]
    >>> print(stats_cubes_o3[0])
    mass_concentration_of_ozone_in_air / (1) (istatistic: 32; time: 10)
         Dimension coordinates:
              istatistic                                x         -
              time                                      -         x
         Auxiliary coordinates:
              statistic                                 x         -
              statistic_long_name                       x         -
              statistic_units                           x         -
         Attributes:
              Conventions: CF-1.5
              label: aqum_oper
              obs: AURN
              short_name: O3
    >>> print(stats_cubes_o3[0].coord('statistic').points) \
# doctest: +NORMALIZE_WHITESPACE
    ['mdi' 'nsites' 'npts' 'correlation' 'bias' 'nmb' 'mnmb' 'mge' 'nmge' \
'fge' 'rmse' 'fac2' 'ioa' 'threshold' 'orss' 'odds_ratio' 'hitrate' \
'falsealarmrate' 'falsealarmratio' 'o>=t_m>=t' 'o<t_m>=t' 'o>=t_m<t' \
'o<t_m<t' 'maxobs' 'maxmod' 'meanobs' 'meanmod' 'sdobs' 'sdmod' \
'perc_correct' 'perc_over' 'perc_under']

    Choose 'bias' from the list of available statistics and plot this:

    >>> tsp = tsp_statistic(stats_cubes_o3, 'bias', plotdir=plotdir)
    ... # doctest: +ELLIPSIS
    Saved figure  .../adaqdocs/figures/Timeseries_of_bias_O3.png

    .. image:: ../adaqdocs/figures/Timeseries_of_bias_O3.png
       :scale: 50%

    """

    if not colours_list:
        colours_list = plotting_functions.COLOURS[1:]  #Ignore black

    #Information dictionary about statistic
    statsinfo = timeseries_stats.STATS_INFO[statistic]

    #Set up TimeSeriesPlot
    tsp = TimeSeriesPlot()

    short_name = ''

    #Loop through each cube, adding it as a line to the plot
    for icube, statscube in enumerate(statscube_list):
        #Extract statistic
        cube = statscube.extract(iris.Constraint(statistic=statistic))
        if cube is not None:
            short_name = cube.attributes['short_name']
            if sum(np.isfinite(cube.data)) >= 2:
                #Need at least 2 valid (non-nan) points to produce
                # sensible plot (To draw a line between two points)

                #Label according to model only
                label = cube.attributes['label']

                if xcoordname is not None:
                    xcoord = cube.coord(xcoordname)
                elif 'time' in [c.name() for c in cube.coords()]:
                    #Default to time coord if possible
                    xcoord = cube.coord('time')
                else:
                    xcoord = None

                tsp.add_line(cube,
                             x=xcoord,
                             label=label,
                             colour=colours_list[icube])

    if not tsp.lines:
        warnings.warn("No Timeseries of " + statistic + " plot for " + \
                      short_name + " (Not enough obs&model points)")
        #Don't plot this statistic
        return tsp

    #Add statistic and units to y axis label
    #Get units from last cube
    units = cube.coord('statistic_units').points[0]
    tsp.ylabel = statsinfo['long_name']
    if units is not None and units != '1':
        #Add units to ylabel, converting to latex units if in LATEX,
        #otherwise leaving as units.
        tsp.ylabel += ' (' + plotting_functions.units_str(units) + ')'

    #Also add a perfect-value line if appropriate
    perfect_value = statsinfo.get('perfect_value', None)
    if perfect_value is not None:
        tsp.horiz_y = perfect_value

    #Set up title
    tsp.title = 'Time series of ' + statsinfo['long_name']
    tsp.title += '\n ' + tsp.phenomena_name.replace('_', ' ')

    #Generate plot
    tsp.plot()

    #Save plot
    filename = 'Timeseries_of_' + statistic + '_' + \
               short_name + filesuffix+'.png'
    tsp.save_fig(plotdir=plotdir, filename=filename)

    return tsp
def plot_cube_cross_section(cube,
                            waypoints,
                            short_name,
                            ini_dict,
                            bl_depth=None,
                            titleprefix=None,
                            plotdir=None,
                            filesuffix='',
                            tight=False,
                            verbose=1):
    """
    Plot vertical cross section of a cube along a given set of waypoints.

    Defaults (where parameters are set to None or 'default') are set to the
    defaults in :class:`field_layer.FieldLayer` or

    :class:`field_plot.FieldPlot`.

    :param cube: Iris cube which should contain X,Y and Z coordinates, plus
                 'short_name' and 'label' attributes.

    :param waypoints: List of dictionaries, whose keys are 'latitude' and
                      'longitude' and whose values are the lat/lon points that
                      should be included in the cross-section.

    :param short_name: string to match to short_name attribute in cubes.

    :param ini_dict: Dictionary of values used to define appearance of plot.

    :param bl_depth: Iris cube containing boundary layer depth. This should
                     contain X and Y coordinates, plus 'short_name' and 'label'
                     attributes. If given, the boundary layer height will also
                     be plotted on the cross-section plot.

    :param titleprefix: String to prefix to default title (time of plot).

    :param plotdir: Output directory for plot to be saved to.

    :param filesuffix: String to add to end of filename for specific naming
                       purposes. By default adds nothing.

    :param tight: If set to True, will adjust figure size to
                  be tight to bounding box of figure and minimise
                  whitespace. Note this will change size of output file
                  and may not be consistent amongst different plots
                  (particularly if levels on colorbar are different).

    :param verbose: Level of print output:

                    * 0 = No extra printing
                    * 1 = Standard level (recommended)
                    * 2 = Extra level for debugging

    :returns: Iris cube containing the section that was plotted. This will
              have additional coordinate 'i_sample_points' instead of X and Y
              coordinates.

    Load example cubes:

    >>> import config
    >>> import adaq_functions
    >>> sample_datadir = config.SAMPLE_DATADIR + 'aqum_output/oper/3d/'
    >>> tcube = iris.load_cube(sample_datadir +
    ... 'prodm_op_aqum_20170701_18.006.pp', 'air_temperature')
    >>> ini_dict = inifile.get_inidict(defaultfilename=
    ... 'adaqcode/adaq_vertical_plotting.ini') # doctest: +ELLIPSIS
    Reading inifile .../adaqcode/adaq_vertical_plotting.ini

    >>> tcube.attributes['short_name'] = 'T'
    >>> tcube.attributes['label'] = 'aqum'
    >>> tcube.coord('time').points = tcube.coord('time').bounds[:, 1]
    >>> print(tcube.summary(True)) # doctest: +NORMALIZE_WHITESPACE
    air_temperature / (K) (time: 2; model_level_number: 63; grid_latitude:
    182; grid_longitude: 146)
    >>> blcube = iris.load_cube(sample_datadir +
    ... 'prods_op_aqum_20170701_18.000.pp',
    ... 'atmosphere_boundary_layer_thickness')
    >>> blcube.attributes['short_name'] = 'bl_depth'
    >>> blcube.attributes['label'] = 'aqum'

    Set up dictionary of way points and then plot cross section

    >>> waypoints = [{'latitude':51.1, 'longitude':-0.6},
    ... {'latitude':51.7, 'longitude':0.2},
    ... {'latitude':52.0, 'longitude':-1.0}]
    >>> plotdir = config.CODE_DIR + "/adaqdocs/" + "figures/adaq_plotting"
    >>> section = plot_cube_cross_section(tcube, waypoints, 'T', ini_dict,
    ... bl_depth=blcube, plotdir=plotdir) # doctest: +ELLIPSIS
    Saved figure  .../CrossSection_aqum_T_201707020300.png
    Saved figure  .../CrossSection_aqum_T_201707020600.png

    .. image:: ../adaqdocs/figures/adaq_plotting/
               CrossSection_aqum_T_201707020600.png
       :scale: 75%

    >>> print(section.summary(True)) # doctest: +NORMALIZE_WHITESPACE
    air_temperature / (K) (time: 2; level_height: 26; i_sample_point: 30)

    """

    #Get some variables from ini_dict if possible.
    max_height = ini_dict.get('max_height', None)
    if max_height is not None:
        max_height = float(max_height)

    #If levels not already set, get levels from ini_dict if possible
    #(and corresponding number of levels)
    levels_dict = ini_dict.get('levels_dict', {})
    if short_name in levels_dict:
        levels = levels_dict[short_name]
    else:
        levels = ini_dict.get('levels_list', None)
    if levels is not None:
        levels = [float(v) for v in levels]
    if levels is not None:
        nlevels = len(levels)
    else:
        nlevels = ini_dict.get('nlevels', 10)

    cmap = ini_dict.get('cmap', 'YlGnBu')
    cbar_label = ini_dict.get('cbar_label', 'default')
    cbar = ini_dict.get('cbar', True)
    cbar_num_fmt = ini_dict.get('cbar_num_fmt', None)
    line_colours_list = ini_dict.get('line_colours_list', COLOURS)
    line_colour = line_colours_list[0]
    cbar_orientation = ini_dict.get('cbar_orientation', 'vertical')

    # Extract a section along the waypoints
    section = cube_functions.extract_section(cube, waypoints)

    #Plot against a sensible vertical coordinate
    if section.coords('model_level_number') and section.coords('level_height'):
        section.remove_coord('model_level_number')
        iris.util.promote_aux_coord_to_dim_coord(section, 'level_height')

    zcoordname, = cube_functions.guess_coord_names(section, ['Z'])
    if zcoordname is None:
        print('Not plotting cross section for ' +
              cube.attributes['short_name'] + ' from ' +
              cube.attributes['label'] + ' (no vertical coordinate)')
        return None
    scoordname = 'i_sample_point'

    #Limit section to maximum required height
    if max_height is not None:
        if section.coords('level_height'):
            section = section.extract(
                iris.Constraint(level_height=lambda c: c <= max_height))
        else:
            raise UserWarning('Cannot limit to max_height as level_height' +
                              'coordinate does not exist')
    if bl_depth:
        bl_depth_section = cube_functions.extract_section(bl_depth, waypoints)

    #---
    #Set up field layer
    flr = field_layer.FieldLayer(section)
    flr.set_layerstyle(plottype='pcolormesh',
                       colorscale='linear',
                       levels=levels,
                       nlevels=nlevels,
                       mask=True,
                       cmap=cmap)

    flr.cbar = cbar
    flr.cbar_orientation = cbar_orientation
    flr.cbar_label = cbar_label
    flr.cbar_num_fmt = cbar_num_fmt

    # Set colour for boundary layer line plots
    line_colour = line_colour if line_colour else 'black'

    #---
    #Loop over fields within layer (sample point & Z coords)
    for layer_slice in flr.layer_slice([scoordname, zcoordname]):
        fplt = field_plot.FieldPlot(ini_dict)
        #Don't plot fields which are entirely nan data
        if isinstance(layer_slice.cube.data, np.ma.MaskedArray):
            if np.sum(np.isfinite(layer_slice.cube.data.data)) == 0:
                #All nan data
                warnings.warn('All nan data, no gridded field plot created')
                continue
        else:
            #Not a masked array
            if np.sum(np.isfinite(layer_slice.cube.data)) == 0:
                #All nan data
                warnings.warn('All nan data, no gridded field plot created')
                continue
        fplt.add_layer(layer_slice)

        if titleprefix is None:
            titleprefix = ('Cross section for ' +
                           cube.name().replace('_', ' ') + '\n')
        fplt.titleprefix = titleprefix
        # Set a specific figsize for this type of plot:
        fplt.plot(figsize=[15.0, 6.0])

        # Get the coordinate that was actually used on the vertical axis.
        vert_axis_coord = iplt._get_plot_defn(flr.cube,
                                              iris.coords.BOUND_MODE,
                                              ndims=2).coords[0]

        plt.gca().set_ylabel(vert_axis_coord.name().replace('_', ' ') + ' (' +
                             units_str(str(vert_axis_coord.units)) + ')')
        if vert_axis_coord.has_bounds():
            plt.gca().set_ylim([0, vert_axis_coord.bounds[-1, 1]])
        plt.gca().set_xlabel('Section point')
        plt.gca().set_xlim([-0.5, 29.5])  # n_sample_points=30.

        # Also plot BL depth at the same time
        if bl_depth:
            time = layer_slice.cube.coord('time').units.num2date(
                layer_slice.cube.coord('time').points[0])
            bl_depth_slice = bl_depth_section.extract(
                iris.Constraint(time=time))
            if bl_depth_slice:  # found matching time
                # Plot
                iplt.plot(bl_depth_slice,
                          label='Boundary Layer Height',
                          color=line_colour,
                          linestyle='--')
                # Add label
                plt.gca().legend()

        fplt.save_fig(plotdir=plotdir,
                      fileprefix='CrossSection_',
                      filesuffix=filesuffix,
                      tight=tight,
                      verbose=verbose)

    return section