# Metview Macro

# **************************** LICENSE START ***********************************
#
# Copyright 2013 ECMWF. This software is distributed under the terms
# of the Apache License version 2.0. In applying this license, ECMWF does not
# waive the privileges and immunities granted to it by virtue of its status as
# an Intergovernmental Organization or submit itself to any jurisdiction.
#
# ***************************** LICENSE END ************************************

# **************************************************************************
# Function      : mvl_plot_scm_data
#
# Syntax        : mvl_plot_scm_data (definition)
#
# Author (date) : Iain Russell (05/12/2012)
#
# Category      : VISUAL
#
# OneLineDesc   : Creates plots interactively or in batch from SCM netCDF data
#
# Description   : Creates plots interactively or in batch from SCM netCDF data
#
# Parameters    : The input definition can contain some or all of the following
#                 members. Note that it is valid to provide an empty definition,
#                 in which case all input will be taken from the user interface.
#                   mode - 'interactive' (user interface pops up) or 'batch'
#                   netcdf_1 - path to the 'main' netCDF file
#                   netcdf_2 - path to the 'comparison' netCDF file
#                   title_1 - title to appear in the legend for the first data
#                   title_2 - title to appear in the legend for the comparison data
#                   comparison_mode - 'overlay' or 'difference'
#                   output_mode - 'postscript', 'png' or 'screen'
#                   output_path - only if mode='batch'. Do not supply an extension.
#                   output_ps_mode - 'single' file,or 'split' into multiple files
#                   param_selection - 'list', 'file' or 'all'
#                   param_list_path - path to a text file containing each parameter name on a new line
#                   param_list - if param_selection is 'list'
#                   y_min_list - list of bottom levels for the y-axis. Each entry should be a number or 'auto'.
#                   y_max_list - list of top levels for the y-axis. Each entry should be a number or 'auto'.
#                   x_min_list - list of minimums for the x-axis. Each entry should be a number or 'auto'.
#                   x_max_list - list of maximums for the x-axis. Each entry should be a number or 'auto'.
#                   value_min_list - list of minimums for the values Each entry should be a number or 'auto'. Only for matrix plots.
#                   value_max_list - list of maximums for the values Each entry should be a number or 'auto'. Only for matrix plots.
#                   grid - 'on' to plot the axis grids, 'off' otherwise
#
# Return Value  : None
#
# Dependencies  : None
#
# Example Usage : 
#                 # default: pop up a user interface to get user input to the plot
#                   scm_plot_params = ()
#                   mvl_plot_scm_data(scm_plot_params)
#
#                 # batch usage: supply parameters here; no user interface
#                   scm_plot_params = 
#                   (
#                       mode            : 'batch',
#                       netcdf_1        : 'scm_out.nc',
#                       netcdf_2        : 'scm_out_2.nc',
#                       title_1         : 'Expt 1',
#                       title_2         : 'Expt 2',
#                       comparison_mode : 'overlay',
#                       plot_type       : 'profile',  # 'profile' is the only accepted string here; anthing else means 'not profile'
#                       output_mode     : 'ps',
#                       output_path     : 'scm_out',
#                       output_ps_mode  : 'split',
#                       param_selection : 'list',
#                       param_list_path : nil,
#                       param_list      : ['t_skin', 'top_lwrad_clr', 'u_wind_10m'],
#                       times           : [0, 'to', 'last', 'by', 180]
#                   )
#
#                   mvl_plot_scm_data(scm_plot_params)
#
# **************************************************************************

function mvl_plot_scm_data

    args = ()

    arglist = arguments()

    if count(arglist) <> 0 then
        args = arglist[1]
    end if

print(arglist)
    # sanity check on the input arguments

    if args.mode <> nil then
        if args.mode <> 'interactive' and args.mode <> 'batch' then
            fail("Argument 'mode' should be 'interactive' or 'batch'. It is ", args.mode)
        end if
    end if

    if args.comparison_mode <> nil then
        if args.comparison_mode <> 'overlay' and args.comparison_mode <> 'difference' then
            fail("Argument 'comparison_mode' should be 'overlay' or 'difference'. It is ", args.comparison_mode)
        end if
    end if

    if args.mode <> nil then
        if args.output_mode <> 'postscript' and args.output_mode <> 'png' and args.output_mode <> 'screen' then
            fail("Argument 'output_mode' should be 'postscript', 'png' or 'screen'. It is ", args.output_mode)
        end if
    end if

    if args.output_ps_mode <> nil then
        if args.output_ps_mode <> 'single' and args.output_ps_mode <> 'split' then
            fail("Argument 'output_ps_mode' should be 'single' or 'split'. It is ", args.output_ps_mode)
        end if
    end if

    if args.param_selection <> nil then
        if args.param_selection <> 'list' and args.param_selection <> 'file' and args.param_selection <> 'all' then
            fail("Argument 'param_selection' should be 'list', 'file' or 'all'. It is ", args.param_selection)
        end if
    end if


    # fill in the argument definition with defaults if not specified

    if args.mode = nil then
        args.mode = 'interactive'
    end if

    if args.comparison_mode = nil then
        args.comparison_mode = 'overlay'
    end if

    if args.param_selection = nil then
        args.param_selection = 'list'
    end if

    if args.output_path = nil then
        args.output_path = 'scm_out'
    end if

    if args.output_mode = nil then
        args.output_mode = 'screen'
    end if

    if args.output_ps_mode = nil then
        args.output_ps_mode = 'single'
    end if

    if args.title_1 = nil then
        args.title_1 = ''
    end if

    if args.title_2 = nil then
        args.title_2 = ''
    end if

    
    # make output_path into an absolute path

    args.output_path = make_path_absolute(args.output_path)


    # when running from the command line, we have to force certain options

    if runmode() = 'batch' then
        args.mode        = 'batch'
        if args.output_mode = 'screen' then
            args.output_mode = 'postscript'
        end if
    end if


    #print(args)


    if args.mode = 'interactive' then

        # ---------------- #
        # interactive mode #
        # ---------------- #


        ui_comparison_mode = option_menu
        (
            name    : "comparison mode",
            values  : ["overlay", "difference"],
            default : args.comparison_mode
        )

        ui_netcdf_1 = icon
        (
            name  : "input data 1",
            class : "NETCDF"
        )

        ui_netcdf_2 = icon
        (
            name  : "input data 2",
            class : "NETCDF"
        )

        input = dialog([ui_comparison_mode, ui_netcdf_1, ui_netcdf_2])

        compmode  = input['comparison mode']
        netcdf_1  = input['input data 1']
        netcdf_2  = input['input data 2']

        if netcdf_1 = nil then
            fail('Must supply at least one netCDF icon as input')
        end if


        # ------------------------------------------
        # user dialog 3 - ask for the parameter(s)
        # ------------------------------------------

        # parse the parameters from the input netCDF file

        vardef = parse_netcdf_vars(netcdf_1)

        t = generate_vars_help(netcdf_1, vardef)

        dialog_options = nil

        ui_param_selection_mode = option_menu
        (
            name    : "parameter selection",
            values  : ["list", "file", "all"],
            default : args.param_selection
        )

        ui_param_list = any
        (
            name    : "parameter list",
            default : 't',
            help    : "help_script",
            #help_script_command : "echo list of forward-slash-separated parameter names"
            #help_script_command : "
            help_script_command : "echo list of forward-slash-separated parameter names; cat " & t
        )

        ui_param_file = icon
        (
            name  : "parameter file",
            class : "NOTE"
        )

        ui_param_output_mode = option_menu
        (
            name    : "output mode",
            values  : ["screen", "postscript", "png"],
            default : args.output_mode
        )

        outpath = args.output_path

        ui_outpath = any
        (
            name    : "output file path",
            default : outpath
        )


        input = dialog([ui_param_selection_mode, ui_param_list, ui_param_file, ui_param_output_mode, ui_outpath])

        param_selection_mode = input["parameter selection"]

        if param_selection_mode = 'list' then   # user provided an inline list of parameters
            selected_vars = input['parameter list']

        else if param_selection_mode = 'file' then # user gave a file with a list of parameters to plot
            parmlist = input["parameter file"]

            if parmlist <> nil then 
                parmlistpath = parmlist.PATH
            else
                fail("Parameter selection was set to 'file', but no icon was supplied")
            end if

        else # put all the available variable indexes into a list - we will try to plot them all
            selected_vars = vardef.vars
        end if


        y_max_list = nil
        y_min_list = nil

        x_min_list = nil
        x_max_list = nil

        value_min_list = nil
        value_max_list = nil

        grid = 'off'

        outmode  = input["output mode"]
        outpath  = input["output file path"]

    else

        # ---------- #
        # batch mode #
        # ---------- #

        compmode = args.comparison_mode

        netcdf_1_path = args.netcdf_1
        if netcdf_1_path = nil then
            fail('Must supply netcdf_1')
        else
            netcdf_1_path = make_path_absolute(netcdf_1_path)
            netcdf_1 = read(netcdf_1_path)
        end if

        netcdf_2 = nil
        netcdf_2_path = args.netcdf_2
        if netcdf_2_path <> nil then
            netcdf_2_path = make_path_absolute(netcdf_2_path)
            netcdf_2 = read(netcdf_2_path)
        end if


        param_selection_mode = args.param_selection

        if param_selection_mode = 'list' then   # user provided an inline list of parameters
            selected_vars = args.param_list
        else if param_selection_mode = 'file' then # user gave a file with a list of parameters to plot
            parmlistpath = param_list_path
        else # put all the available variable indexes into a list - we will try to plot them all
            selected_vars = vars
        end if


        plot_type = args.plot_type
        times     = args.times

        y_max_list = args.y_max_list
        y_min_list = args.y_min_list

        x_min_list = args.x_min_list
        x_max_list = args.x_max_list

        value_min_list = args.value_min_list
        value_max_list = args.value_max_list

        grid = args.grid


        outmode   = args.output_mode
        outpath   = args.output_path
        outpsmode = args.output_ps_mode


        # Read netcdf file
        file_nc = netcdf_1


        # parse the parameters from it
        vardef = parse_netcdf_vars(file_nc)


    end if # interactive or batch




    # ----------------------------------------- #
    # User input obtained, start the processing #
    # ----------------------------------------- #


    # read the list of parameters from a file?
    if param_selection_mode = 'file' then
        parmlistpath = parmlist.PATH
        selected_vars = read(parmlistpath)
    end if


    # (hopefully temporary) mesaure: since we can't yet plot multiple parameters
    # interactively, we will have to plot just the first one

    if outmode = 'screen' then
        if count(selected_vars) > 1 then
            print('Can only plot 1 variable in interactive mode - will plot just the first one')
            selected_vars = [selected_vars[1]]
        end if
    end if


    # construct a list of variable indexes from their names
    plot_var_indexes = nil
    for i = 1 to count(selected_vars) do
        index = find(vardef.vars, selected_vars[i])
        if index = nil then
            fail('Could not find variable ' & selected_vars[i] & ' in netCDF file')
        else
            plot_var_indexes = plot_var_indexes & [index]
        end if
    end for


    print('Plotting parameters: ', selected_vars, ' ; indexes: ', plot_var_indexes)


    global text_scaling = 1.2 # most drivers use the default font sizes


    if outmode = 'postscript' then
        print('Will generate output plot in ', outpath)
        if outpsmode = 'split' then
            split = (output_ps_split : 'on')
        else
            split = ()
        end if
        psfile = ps_output(output_name : outpath, split)
        setoutput(psfile)
    end if


    if outmode = 'png' then
        print('Will generate output plot in multiple files: ', outpath)
        text_scaling = 2.0
        pngfile = png_output(output_name : outpath, output_width : 1200)
        setoutput(pngfile)
    end if


    # plots can have restrictions on their axes' limits

    if y_min_list <> nil then
        if count(y_min_list) <> count(selected_vars) then
            fail('y_min_list should have the same number of entries as the number of parameters.')
        end if
    end if

    if y_max_list <> nil then
        if count(y_max_list) <> count(selected_vars) then
            fail('y_max_list should have the same number of entries as the number of parameters.')
        end if
    end if

    if x_min_list <> nil then
        if count(x_min_list) <> count(selected_vars) then
            fail('x_min_list should have the same number of entries as the number of parameters.')
        end if
    end if

    if x_max_list <> nil then
        if count(x_max_list) <> count(selected_vars) then
            fail('x_max_list should have the same number of entries as the number of parameters.')
        end if
    end if

    if value_min_list <> nil then
        if count(value_min_list) <> count(selected_vars) then
            fail('value_min_list should have the same number of entries as the number of parameters.')
        end if
    end if

    if value_max_list <> nil then
        if count(value_max_list) <> count(selected_vars) then
            fail('value_max_list should have the same number of entries as the number of parameters.')
        end if
    end if


    if (x_min_list <> nil and x_max_list = nil) or (x_min_list = nil and x_max_list <> nil)
    then
        fail('Cannot specify only one of x_min_list and x_max_list - provide both or neither.')
    end if


    # if plotting profiles, then expand the time steps
    # e.g.
    #   0/to/last/by/180
    #   900,1800,2700
    #   0/to/2700

    if plot_type = 'profile' then

        if type(times) <> 'list' then
            times = [times]
        end if


        first_time = times[1] * 60  # convert from minutes (user) to seconds (file)


        if count(times) > 1 then
            if type(times[2]) = 'string' then   # ASSUME a 'TO' b  (1,2,3)
                second_time = times[3]

                if type(second_time) = 'string' then # ASSUME a 'TO' 'LAST'
                    setcurrent(netcdf_1, vardef.time_var_index)  # get the last time
                    all_times = values(netcdf_1)
                    second_time = all_times[count(all_times)]  # already in seconds
                end if

                if count(times) > 4 then  # ASSUME a 'TO' b 'BY' s
                    timestep = times[5] * 60  # # convert from minutes (user) to seconds (file)

                    times = nil # construct the list of times
                    for t = first_time to second_time by timestep do
                        times = times & [t]
                    end for
                else
                    # no step given - use all available times between first and second
                    times_to_use = (all_times >= first_time) * (all_times <= second_time)
                    times = filter(all_times, times_to_use)
                end if

            else  # 'times' is just a list of times which we can use directly

                times = times * 60  # convert from minutes (user) to seconds (file)

            end if

        else  # just a single time
            times = times * 60  # convert from minutes (user) to seconds (file)
        end if

        print('Computed times: ', times)

    end if  # profile



    # convert seconds to days
    #setcurrent(file_nc, 4)
    #file_nc = file_nc / (60*60*24)



    # decide whether to plot both netCDF files in overlay mode, or to compute
    # the difference and plot that by itself

    nc_files = nil

    if compmode = 'overlay' then
        if netcdf_1 <> nil then nc_files = nc_files & [netcdf_1] end if
        if netcdf_2 <> nil then nc_files = nc_files & [netcdf_2] end if
    else
        a = 1 # for syntax correctness
        if netcdf_2 = nil then
            fail('Must supply a second netCDF icon')
        end if

        #setcurrent(netcdf_1, i)
        #setcurrent(netcdf_2, i)
        nc_files = [netcdf_1]# - netcdf_2]
    end if




    graph_line_colours = ['blue', 'red']



    for j = 1 to count(plot_var_indexes) do

        i = plot_var_indexes[j]

        if not(i in vardef.reserved_var_indexes) and i<>nil then

            # -------------------------------------------
            # plotting - gather plot info for each netCDF
            # -------------------------------------------

            to_plot        = nil
            all_yvals      = nil
            plot_this_var  = 0
            second_axis    = 0 # will be plot a second axis? In practice, this is a pressure axis
            pres_positions = 0
            pres_labels    = 0
            pres_title     = 0

            for n = 1 to count(nc_files) do

                file_nc = nc_files[n]

                # look at this variable to see what its dimensions are
                setcurrent(file_nc, i)
                dim_names = dimension_names(file_nc)
                #print(i, tab, dim_names)
                if count(dim_names) = 1 then
                    xdim_name = dim_names[1]
                        plot_this_var = 1

                        if compmode = 'difference' then
                            setcurrent(netcdf_2, i)
                            file_nc = file_nc - netcdf_2
                        end if


                        # visualiser to plot the data
                        line_plot = netcdf_visualiser
                        (
                            netcdf_plot_type      : "xy_points",
                            netcdf_x_variable     : xdim_name,
                            netcdf_y_variable     : vardef.vars[i],
                            netcdf_value_variable : vardef.vars[i],
                            netcdf_data           : file_nc
                        )

                        # visual definition to apply to the plot
                        line_visdef = mgraph
                        (
                            legend            : 'on',
                            graph_line_colour : graph_line_colours[n]
                        )

                        to_plot = to_plot & [line_plot, line_visdef]

                        # we'll need to combine all the actual values for later
                        all_yvals = all_yvals & values(file_nc)

                        y_axis_title = ''
                    #else
                        #print ('Variable ', vardef.vars[i], ' has BAD dims ', xdim_name)
                    #end if

                else  # 2D matrix or profile plot

                    a = 1 # for syntax

                    if args.mode = 'batch' or  n = 1  or compmode = 'difference' then

                        #xdim_index = find(vardef.reserved_var_sizes, dims[1])
                        #if xdim_index <> nil then
                            plot_this_var = 1
                            xdim_name = dim_names[1] #vardef.reserved_var_names[xdim_index]
                            ydim_name = dim_names[2] #vardef.reserved_var_names[ydim_index]
                            ydim_idx = find(vardef.reserved_var_names, ydim_name)

                            # if y-axis is model levels, then we should plot a pressure axis too
                            if ydim_name = 'nlev' then  # full pressure levels
                                second_axis = 1
                                pres_var = 'pressure_f'
                            else if ydim_name = 'nlevp1' then # half pressure levels
                                second_axis = 1
                                pres_var = 'pressure_h'
                            end if

                            if ydim_idx <> nil then

                                if compmode = 'difference' then
                                    setcurrent(netcdf_2, i)
                                    file_nc = file_nc - netcdf_2
                                end if


                                if plot_type = 'profile' then   # PROFILE CURVES

                                    xdim_name =  vardef.vars[i]
                                    ydim_name = dim_names[2]
                                    ydim_idx = find(vardef.reserved_var_names, ydim_name)

                                    rgb1_start = [0.0, 0.0, 0.5]
                                    rgb1_end   = [0.2, 0.8, 1.0]

                                    rgb2_start = [0.8, 0.0, 0.0]
                                    rgb2_end   = [1.0, 0.8, 0.1]

                                    colours_1 = interpolate_colours_rgb(rgb1_start, rgb1_end, count(times))
                                    colours_2 = interpolate_colours_rgb(rgb2_start, rgb2_end, count(times))

                                    for ti = 1 to count(times) do

                                        ncvis = netcdf_visualiser
                                        (
                                            netcdf_plot_type         : "xy_points",
                                            netcdf_x_variable        : vardef.vars[i],
                                            netcdf_y_variable        : ydim_name,
                                            netcdf_value_variable    : vardef.vars[i],
                                            netcdf_dimension_setting : ["time/"&times[ti]],
                                            netcdf_data              : file_nc
                                        )

                                        if ti = 1 then profile_legend = 'on'
                                        else           profile_legend = 'off'
                                        end if

                                        if n = 1 then   # first netCDF file
                                            rgb = colours_1[ti]
                                        else           # second netCDF file
                                            rgb = colours_2[ti]
                                        end if

                                        rgb = "RGB(" & round (rgb[1], 5) & "," & round (rgb[2], 5) & "," & round (rgb[3], 5) & ")"


                                        if times[ti] = 0 then
                                            line_thickness = 3 * text_scaling
                                            line_style = 'dash'
                                        else
                                            line_thickness = 1 * text_scaling
                                            line_style = 'solid'
                                        end if
                                        #print(rgb)

                                        gp = mgraph(legend               : profile_legend,
                                                    graph_line_colour    : rgb,
                                                    graph_line_thickness : line_thickness,
                                                    graph_line_style     : line_style)

                                        to_plot = to_plot & [ncvis, gp]

                                    end for

                                else   # MATRIX


                                    # visualiser to plot the data
                                    matrix_plot = netcdf_visualiser
                                    (
                                        netcdf_plot_type      : "xy_matrix",
                                        netcdf_x_variable     : xdim_name,
                                        netcdf_y_variable     : ydim_name,
                                        netcdf_value_variable : vardef.vars[i],
                                        netcdf_data           : file_nc
                                    )

                                    # visual definition to apply to the plot
                                    if n = 1 then   # first netCDF, shaded

                                        # user-defined min/max contour levels?
                                        min_max_value_def = ()
                                        min_value = 'auto'
                                        if value_min_list <> nil then
                                             min_value = value_min_list[j]
                                        end if
                                        if type(min_value) = 'number' then
                                            min_max_value_def.contour_min_level = min_value
                                        end if

                                        max_value = 'auto'
                                        if value_max_list <> nil then
                                             max_value = value_max_list[j]
                                        end if
                                        if type(max_value) = 'number' then
                                            min_max_value_def.contour_max_level = max_value
                                        end if

                                        matrix_cont = mcont
                                        (
                                            legend                         : "on",
                                            contour                        : "off",
                                            contour_highlight              : "off",
                                            contour_shade                  : "on",
                                            contour_shade_method           : "area_fill",
                                            contour_shade_max_level_colour : "red",
                                            contour_shade_min_level_colour : "blue",
                                            contour_shade_colour_direction : "clockwise",
                                            contour_label                  : "off",
                                            min_max_value_def
                                        )
                                    else # 2nd data
                                        matrix_cont = mcont
                                        (
                                            contour                 : "on",
                                            contour_line_colour     : 'black',
                                            contour_line_thickness  : 2,
                                            contour_highlight       : "off",
                                            contour_label           : "on"
                                        )
                                    end if

                                    to_plot = to_plot & [matrix_plot, matrix_cont]


                                end if

                                # store the y values so we can later get a nice y-axis
                                ydim_index = find(vardef.vars, ydim_name)
                                setcurrent(file_nc, ydim_index)

                                all_yvals = all_yvals & values(file_nc)


                                # y-axis title
                                attrs = attributes(file_nc)
                                y_axis_title = attrs.long_name
                                if y_axis_title = nil then y_axis_title = '' end if
                            else
                                print ('Variable ', vardef.vars[i], ' has BAD dims ', ydim_name)
                                plot_this_var = 0
                            end if
                        #else
                        #    print ('Variable ', vardef.vars[i], ' has BAD dims ', xdim_name)
                        #end if

                    else

                        print('Cannot (yet) overlay plots in interactive mode - not plotting ', vardef.vars[i])
                        plot_this_var = 0

                    end if # args.mode = 'batch', etc

                end if  # 1D/2D

            end for  # for each netCDF



            if plot_this_var then


                # ----------------------------------
                # get information for the plot title
                # ----------------------------------
                setcurrent(file_nc, i)
                attrs = attributes(file_nc)

                gattrs         = global_attributes(file_nc)
                att_title      = gattrs.title
                att_start_day  = gattrs.start_day
                att_start_hour = gattrs.start_hour

                title_line_1 = att_title
                title_line_2 = 'Start day: ' & att_start_day & ' Start hour: ' & att_start_hour
                title_line_3 = ''
                if attrs.long_name <> nil then
                    title_line_3 = title_line_3 & attrs.long_name
                end if
                if attrs.units <> nil then
                    title_line_3 = title_line_3 & ' (' & attrs.units & ')'
                end if
                if compmode = 'difference' then
                    title_line_3 = title_line_3 & ' [difference]'
                end if

                title = mtext
                (
                    text_line_count : 3,
                    text_line_1     : title_line_1,
                    text_line_2     : title_line_2,
                    text_line_3     : title_line_3,
                    text_font_size  : 0.35 * text_scaling
                )



                # ------------------------------
                # get information for the x-axis
                # ------------------------------

                xdim_index = find(vardef.vars, xdim_name)
                setcurrent(file_nc, xdim_index)
                attrs   = attributes(file_nc)
                x_name  = xdim_name
                x_units = attrs.units
                if x_units = nil then
                    x_units_text = ''
                else
                    x_units_text = ' (' & x_units & ')'
                end if
                x_axis_title = x_name & x_units_text

                xmin = 'auto'
                xmax = 'auto'
                if x_min_list <> nil then
                    xmin = x_min_list[j]
                    xmax = x_max_list[j]
                end if


                # ------------------------------
                # get information for the y-axis
                # ------------------------------

                num_dims = count(dim_names)

                min_level = 'auto'
                if y_min_list <> nil then
                     min_level = y_min_list[j]
                end if

                max_level = 'auto'
                if y_max_list <> nil then
                     max_level = y_max_list[j]
                end if


                # figure out the 'global' min/max for all data files (if not user specified)
                if max_level = 'auto' then
                    if num_dims = 1 then ymax = maxvalue(all_yvals)
                    else                 ymax = minvalue(all_yvals) # 2D (height) plots are upside down
                    end if
                else 
                    ymax = max_level
                end if

                if min_level = 'auto' then
                    if num_dims = 1 then ymin = minvalue(all_yvals)
                    else                 ymin = maxvalue(all_yvals) # 2D (height) plots are upside down
                    end if
                else
                    ymin = min_level
                end if



                if num_dims = 1 then
                    adjustment = 0.1 * (ymax - ymin)  # leave a 10% gap around the y limits
                    if ymin <> 0 then ymin = ymin - adjustment end if
                    ymax = ymax + adjustment

                    if ymin = ymax then
                        ymin = ymin - 1
                        ymax = ymax + 1
                    end if
                end if


                if second_axis then   # plot a pressure axis on the right?
                    pres_index = find(vardef.vars, pres_var)
                    if pres_index <> nil then
                        pres_title = 'Pressure (hPa)'
                        setcurrent(file_nc, pres_index)
                        all_pres = values(file_nc)
                        d = dimensions(file_nc)
                        pres = all_pres[1,d[count(d)]] # get the pressures for the 1st time step
                        num_pres = count(pres)
                        pres_positions = nil
                        pres_labels    = nil
                        for pi = num_pres to 1 by -10 do
                            tpres = pres[pi]/100 # 'tpres' = 'this pressure'
                            rpres = round(tpres, min(3-log10(tpres),2)) # rounded pressure
                            pres_positions = pres_positions & [pi]
                            pres_labels    = pres_labels & [rpres]
                        end for
                    else
                        second_axis = 0 # pressure variable not found - do not plot 2nd axis
                    end if
                end if


                # define the legend
                if num_dims = 1 or plot_type = 'profile' then
                    legend_curves = mlegend
                    (
                        legend_display_type     : "disjoint",
                        legend_text_composition : "user_text_only",
                        legend_text_font_size   : 0.35 * text_scaling,
                        legend_user_lines       : [args.title_1, args.title_2]
                    )
                    to_plot = to_plot & [legend_curves]
                else
                    legend_plot = mlegend
                    (
                        legend_text_font_size   : 0.32 * text_scaling
                    )
                    to_plot = to_plot & [legend_plot]
                end if


                # -----------
                # do the plot
                # -----------

                dw = scm_build_layout(ymin, ymax, xmin, xmax, x_axis_title, y_axis_title,
                                      second_axis, pres_positions, pres_labels, pres_title,
                                      grid)

                if second_axis then
                    plot(dw[2])
                end if

                plot(dw[1], to_plot, title)

                newpage(dw)

            end if  # plot_this_var

        end if #if not(i in reserved_var_indexes)
    end for  



    function scm_get_var_sizes (nc: netcdf, all_vars: list, vars: list)

        sizes = nil

        for i = 1 to count(vars) do
            index = find(all_vars, vars[i])
            setcurrent(nc, index)
            dims = dimensions(nc)
            sizes = sizes & [dims[1]]
        end for

        return sizes

    end scm_get_var_sizes


    function scm_build_layout(ymin, ymax, xmin, xmax, xtitle, ytitle,
                              second_axis, pres_positions, pres_labels, pres_title,
                              grid)


        grid_def = 
        (
            axis_grid             : grid,
            axis_grid_colour      : 'black',
            axis_grid_line_style  : 'dash',
            axis_grid_thickness   : 1 * text_scaling
        )


        horizontal_axis = maxis
        (
            axis_title_text        : xtitle,
            axis_title_height      : 0.4 * text_scaling,
            axis_tick_label_height : 0.3 * text_scaling,
            grid_def
        )


        vertical_axis = maxis
        (
            axis_orientation  : 'vertical',
            axis_title_text   : ytitle,
            axis_title_height : 0.4 * text_scaling,
            axis_tick_label_height : 0.3 * text_scaling,
            grid_def
        )


        if xmin = 'auto' then
            x_def = (x_automatic  : 'on')
        else
            x_def = (x_min : xmin, x_max : xmax)
        end if


        if second_axis then subpage_x_position = 8
        else                subpage_x_position = 11 # smaller to make room for second axis
        end if


        # view & page definition
        cview = cartesianview
        (  
            x_def,
            #y_automatic  : 'on'
            y_min : ymin,
            y_max : ymax,
            horizontal_axis    : horizontal_axis,
            vertical_axis      : vertical_axis,
            subpage_x_position : subpage_x_position
        )

        page = plot_page
        (  
            view   : cview
        )  

        pages = [page]


        # second (ie pressure) axis to be plotted on the right?
        if second_axis then

            pres_axis = maxis
            (
                axis_orientation            : "vertical",
                axis_position               : "right",
                axis_type                   : "position_list",
                axis_tick_position_list     : pres_positions,
                axis_tick_label_type        : "label_list",
                axis_tick_label_list        : pres_labels,
                #axis_tick_label_frequency   : 20,
                axis_title_height      : 0.4 * text_scaling,
                axis_tick_label_height : 0.3 * text_scaling,
                axis_title_text             : pres_title

            )
#            print(pres_positions)
#            print(pres_labels)
            empty_h_axis = maxis
            (
                axis_orientation : "horizontal",
                axis_line       : "off",
                axis_title      : "off",
                axis_tick       : "off",
                axis_tick_label : "off"
            )
            cview2 = cartesianview
            (  
                x_min              : 1,  # dummy values
                x_max              : 2,  # dummy values
                y_min              : ymin,
                y_max              : ymax,
                vertical_axis      : pres_axis,
                horizontal_axis    : empty_h_axis,
                subpage_frame      : 'off',
                subpage_x_position : subpage_x_position
            )
            page2 = plot_page
            (  
                view   : cview2
            )
            pages = pages & [page2]
        end if



        # superpage definition 
        display_window = plot_superpage
        (  
            layout_size         : "a4", 
            layout_orientation  : "landscape", 
            custom_width        : 29.0, 
            custom_height       : 21.0, 
            pages               : pages 
         ) 

        return display_window

    end scm_build_layout



    function make_path_absolute(path: string)

        first_char = substring(path, 1, 1)
        if first_char <> '/' then
            path = getenv('PWD') & "/" & path
        end if
        return path

    end make_path_absolute


    function parse_netcdf_vars(nc: netcdf)

        def = ()  # we will returns a definition will various bits of information

        # get the list of variables in the file
        def.vars = variables(nc)


        # get the lists of 'special' variables
        nlev_var_index   = find(def.vars, 'nlev')
        nlevp1_var_index = find(def.vars, 'nlevp1')
        nlevs_var_index  = find(def.vars, 'nlevs')
        time_var_index   = find(def.vars, 'time')

        if (nlev_var_index = nil or nlevp1_var_index = nil or nlevs_var_index = nil or time_var_index = nil) then
            fail ('ERROR: Expected to find nlev, nlevp1, nlevs and time variables in netCDF file ', nc_filename)
        end if


        def.reserved_var_indexes = [nlev_var_index, nlevp1_var_index, nlevs_var_index, time_var_index]
        def.reserved_var_names   = ['nlev', 'nlevp1', 'nlevs', 'time']
        def.reserved_var_sizes   = scm_get_var_sizes (nc, def.vars, def.reserved_var_names)
        def.time_var_index       = time_var_index


        # store their names to be used on the y-axis
        reserved_var_long_names = nil
        for j = 1 to count(def.reserved_var_indexes) do
            idx = def.reserved_var_indexes[j]
            setcurrent(nc, idx)
            attrs = attributes(nc)
            reserved_var_long_names = reserved_var_long_names & [attrs.long_name]
        end for

        def.reserved_var_long_names = reserved_var_long_names


        return def

    end parse_netcdf_vars




    function generate_vars_help(nc: netcdf, def: definition)


        # parse the file to get the lists of available 1 and 2d variables

        vars_1d = nil
        vars_2d = nil
        for i = 1 to count(def.vars) do
            #print(i, tab, def.vars[i])
            if not(i in def.reserved_var_indexes) then
                setcurrent(nc, i)
                dims = dimensions(nc)
                if count(dims) = 1 then
                    vars_1d = vars_1d & [def.vars[i]]
                else if count(dims) = 2 then
                    #if dims[2] = 91 then
                       vars_2d = vars_2d & [def.vars[i]]
                    #end if
                end if
            end if
        end for

        vars_1d = vars_1d
        vars_2d = vars_2d


        t = tmpfile()
        f = file(t)

        write(f, "1D variables", newline)
        write(f, "------------", newline)
        for i = 1 to count(vars_1d) do
            write(f, vars_1d[i], newline)
        end for

        write(f, newline)
        write(f, "2D variables", newline)
        write(f, "------------", newline)
        for i = 1 to count(vars_2d) do
            write(f, vars_2d[i], newline)
        end for


        f = 0

        return t
    end generate_vars_help


    # ----------------------------------------------------------------------------
    # Function      : interpolate_colours_rgb
    #
    # Description   : Given two endpoint colours, interpolates between them,
    #                 returning a list of 'num_slices' colours including the
    #                 endpoints.
    #                 This version does the interpolation in RGB space.
    #
    # ----------------------------------------------------------------------------

    function interpolate_colours_rgb (lRGB1 : list, lRGB2 : list, num_slices : number)

        lColours = nil
        lNew     = [0, 0, 0]

        for i = 0 to num_slices do
	        lNew [1] = lRGB1[1] + i * (lRGB2[1] - lRGB1[1]) / num_slices
	        lNew [2] = lRGB1[2] + i * (lRGB2[2] - lRGB1[2]) / num_slices
	        lNew [3] = lRGB1[3] + i * (lRGB2[3] - lRGB1[3]) / num_slices

            lColours = lColours & [lNew]
        end for

        return lColours

    end interpolate_colours_rgb



end mvl_plot_scm_data

