require "numru/gphys"
require "numru/dcl"
require "numru/misc"
require "date"

############################################################

=begin
=module NumRu::GGraph and others in ggraph.rb

==Index
* ((<module NumRu::GGraph>))
  * ((<margin_info>))
    Sets the strings to appear in the bottom margin.
  * ((<title>))
    Shows title by (({DCL.uxmttl('t',string,0.0)})).
    Graphic methods such as ((<contour>)) calls this by default.
  * ((<annotate>))
    Show texts on the right margin of the viewport.
    Graphic methods such as ((<contour>)) calls this by default.
  * ((<fig>))
    Define a figure by setting viewport, window, and coordinate transform 
    id (itr) from two 1D VArrays (({xax})) and (({yax})). 
  * ((<set_fig>))
    Change the default option values for ((<fig>)).
  * ((<next_fig>))
    Set the option values effective only in the next call of ((<fig>))
    (cleared then).
  * ((<axes>))
    Draw axes using (by default) info in (({xax})) and (({yax})) if non-nil.
  * ((<set_axes>))
    Change the default option values for ((<axes>)).
  * ((<next_axes>))
    Set the option values effective only in the next call of ((<axes>))
    (cleared then).
  * ((<map_trn?>))
    Returns whether the current coordinate transformation is a map projection.
  * ((<map>))
    (For map projection) Draws map grid and/or lim and/or coast lines etc.
  * ((<set_map>))
    Change the default option values for ((<map>)).
  * ((<next_map>))
    Set the option values effective only in the next call of ((<map>))
  * ((<line>))
    Plot a poly-line by selecting the first dimension (with GPhys#first1D)
    if (({gphys})) is more than 2D.
  * ((<mark>))
    Similar to ((<line>)) but plots marks instead of drawing a poly-line.
  * ((<contour>))
    Contour plot by selecting the first 2 dimensions (with GPhys#first2D)
    if (({gphys})) is more than 3D.
  * ((<set_contour_levels>))
    Set contour levels for ((<contour>)) explicitly by values with the option (({levels})).
  * ((<clear_contour_levels>))
    Clear contour levels set by ((<set_contour_levels>)).
  * ((<set_linear_contour_options>))
    Change the default option values regarding linear contour level
    setting in ((<contour>)).
  * ((<next_linear_contour_options>))
    Similar to ((<set_linear_contour_options>)) but the setting
    is effective only for the next call of ((<contour>)).
  * ((<tone>))
    Color tone or shading by selecting the first 2 dimensions 
    (with GPhys#first2D) if (({gphys})) is more than 3D.
  * ((<color_bar >)) Color bar
  * ((<set_tone_levels>))
    Set tone levels for ((<tone>)) explicitly by values.
  * ((<clear_tone_levels>))
    Clear tone levels set by ((<set_tone_levels>)).
  * ((<set_linear_tone_options>))
    Change the default option values regarding linear tone level
    setting in ((<tone>)).
  * ((<next_linear_tone_options>))
    Similar to ((<set_linear_tone_options>)) but the setting
    is effective only for the next call of ((<tone>)).
  * ((<vector>)) 2-D vector plot using DCL_Ext::((<flow_vect>))

* ((<module NumRu::DCLExt>))
  * ((<gl_set_params>))
    Calls (({DCL.glpset})) multiple times (for each key and val of (({hash}))).
  * ((<sg_set_params>))
    Calls (({DCL.sgpset})) multiple times (for each key and val of (({hash}))).
  * ((<sl_set_params>))
    Calls (({DCL.slpset})) multiple times (for each key and val of (({hash}))).
  * ((<sw_set_params>))
    Calls (({DCL.swpset})) multiple times (for each key and val of (({hash}))).
  * ((<uz_set_params>))
    Calls (({DCL.uzpset})) multiple times (for each key and val of (({hash}))).
  * ((<ul_set_params>))
    Calls (({DCL.ulpset})) multiple times (for each key and val of (({hash}))).
  * ((<uc_set_params>))
    Calls (({DCL.ucpset})) multiple times (for each key and val of (({hash}))).
  * ((<uu_set_params>))
    Calls (({DCL.uupset})) multiple times (for each key and val of (({hash}))).
  * ((<us_set_params>))
    Calls (({DCL.uspset})) multiple times (for each key and val of (({hash}))).
  * ((<ud_set_params>))
    Calls (({DCL.udpset})) multiple times (for each key and val of (({hash}))).
  * ((<ud_set_linear_levs>))
    Set contour levels with a constant interval
  * ((<ud_set_contour>))
    Set contours of at specified levels.
  * ((<ud_add_contour>))
    Same as ((<ud_set_contour>)), but does not clear the contour levels that have
    been set.
  * ((<ue_set_params>))
    Calls (({DCL.uepset})) multiple times (for each key and val of (({hash}))).
  * ((<ue_set_linear_levs>))
    Set tone levels with a constant interval
  * ((<ue_set_tone>))
    Set tone levels and patterns.
  * ((<ue_add_tone>))
    Same as ((<ue_set_tone>)), but does not clear the tone levels that have
    been set.
  * ((<ug_set_params>))
    Calls (({DCL.ugpset})) multiple times (for each key and val of (({hash}))).
    See ((<gl_set_params>)) for usage.
  * ((<um_set_params>))
    Calls (({DCL.umpset})) multiple times (for each key and val of (({hash}))).

  * ((<set_unit_vect_options>))
  * ((<next_unit_vect_options>))
  * ((<unit_vect>)) Show the "unit vector", which indicate the vector scaling.
  * ((<flow_vect>)) 2D Vector plot. Unlike (({DCL::ugvect})), scaling are made in term of the physical (or "U") coordinate.
  * ((<color_bar>)) Color Bar

=module NumRu::GGraph

A graphic library for GPhys using RubyDCL.

This module uses GPhys but is not a part of it.
More specifically, even though this module is included in
the GPhys distribution, the class NumRu::GPhys knows nothing about it,
and GGraph accesses GPhys only though public methods.
So GGraph is not an insider, and you can make another graphic 
library if you like.

==Module Functions

---margin_info(program=nil, data_source=nil, char_height=nil, date=nil, xl=0.0, xr=0.0, yb=nil, yt=0.0)

    Sets the strings to appear in the bottom margin.

    This method sets margin widths as DCL.slmgn(xl, xr, yb, yt),
    and sets the 1st and 2nd margin strings as (({program}))
    and (({data})).

    ARGUMENTS
    * program (String or nil) : String to be put on the left side in
      the bottom margin. This is meant to represent the name of the
      execution program. Therefore, if it is nil, the full path of 
      $0 is used.

    * data_source (String or nil) : String to be put on the right side in
      the bottom margin. This is meant to represent the data file name or the
      directory in which the data are situated.
      If nil, the full path of the current directory is used
      (but nothing is shown if it is equal to the directory of the program).

    * date (true, false, nil) : whether to put todays date --
      true: always, nil: if program.length is short enough, false: never

    * char_height (Float or nil) : height of the string to appear
      in the V coordinate. If nil, internally defined.

    * xl, xr, yb, yl (Float --- nil is available for yb) : margin
      size in the V coordinate. The margin is set as
      DCL.slmgn(xl, xr, yb, yt). If (({yb})) is nil, it is determined
      internally as (({2.0 * char_height})).

---title(string)
    Shows title by (({DCL.uxmttl('t',string,0.0)})).
    Graphic methods such as ((<contour>)) calls this by default.

    RETURN VALUE
    * nil

---annotate(str_ary)
    Show texts on the right margin of the viewport.
    Graphic methods such as ((<contour>)) calls this by default.

    ARGUMENTS
    * str_ary (Array of String) : 

    RETURN VALUE
    * nil

---fig(xax=nil, yax=nil, options=nil)
    Define a figure by setting viewport, window, and coordinate transform 
    id (itr) from two 1D VArrays (({xax})) and (({yax})) (which are
    not needed if a map projection is specified with the optional 
    parameter 'itr'). 

    (({DCL.grfrm})) or (({DCL.grfig})) is called depending options provided.

    ARGUMENTS
    * xax (VArray): (Used only if not map projection)
      A VArray representing the x (horizontal) coordinate
      of the figure.
      The x range of the window (UXMIN & UYMAX in DCL) are determined
      with the max and min of (({xax.val})). By default, 
      the min and max values are assigned to the left and right
      boundaries, respectively, but it is reversed if (({xax})) has
      a 'positive' attribute and its value is 'down' etc (see (({options}))).
    * yax (VArray): (Used only if not map projection)
      Similar to (({xax})) but for the y (vertical) coordinate
      of the figure.
    * options (Hash) : options to change the default behavior if specified.
      It is a Hash with option names (String) as keys and their values.
      Options are interpreted by a NumRu::Misc::KeywordOptAutoHelp,
      so you can shorten the keys (by omitting tails) as long as it is 
      unambiguous.
       option name   default value   # description:
       "new_frame"   true    # whether to define a new frame by DCL.grfrm
                             # (otherwise, DCL.grfig is called)
       "itr"         1       # coordinate transformation number
       "viewport"    [0.2, 0.8, 0.2, 0.8]    # [vxmin, vxmax, vymin, vymax]
       "window"      nil     # (for itr<10,>50) [uxmin, uxmax, uymin, uymax]
       "xreverse"    "positive:down,units:hPa"       # (for itr<10,>50) Assign
                             # max value to UXMIN and min value to UXMAX if
                             # condition is satisfied (nil:never, true:always,
                             # String: when an attibute has the value specified
                             # ("key:value,key:value,..")
       "yreverse"    "positive:down,units:hPa"       # (for itr<10,>50) Assign
                             # max value to UYMIN and min value to UYMAX if
                             # condition is satisfied (nil:never, true:always,
                             # String: when an attibute has the value specified
                             # ("key:value,key:value,..")
       "similar"'    nil     # (for rectangular curvilinear coordinate only)
			     # 3-element float array for similar transformation
                             # in a rectangular curvilinear coordinate, which 
			     # is fed in DCL:grssim:[simfac,vxoff,vyoff],where 
			     # simfac and [vxoff,vyoff] represent scaling 
			     # factor and origin shift, respectively.
       "map_axis"    nil     # (for all map projections) 3-element float
                             # array to be fed in DCL::umscnt: [uxc, uxy, rot],
                             # where [uxc, uyc] represents the tangential point
                             # (or the pole at top side for cylindrical
                             # projections), and rot represents the rotation
                             # angle. If nil, internally determined. (units:
                             # degrees)
       "map_radius"  nil     # (for itr>=20: conical/azimuhal map
                             # projections) raidus around the tangential point.
                             # (units: degrees)
       "map_lat_range"  nil  # (for itr<20: cylindrical map projections)
                             # latitudinal range to draw the map. By default
                             # (nil), the map is drawn between 75E and 75S.
                             # (units: degres)
       "help"        false   # show help message if true

    RETURN VALUE
    * nil

    POSSIBLE EXCEPTIONS
    * those from NumRu::DCL if any / TypeError if any
    * options has a key that does not match any of the option names.
    * options has a key that is ambiguous

---set_fig(options)
    Change the default option values for ((<fig>)).

    ARGUMENTS
    * options (Hash) : The usage is the same as (({options})) for ((<fig>)).

    RETURN VALUE
    * a Hash containing the values replaced (the ones before calling this
      method)

    POSSIBLE EXCEPTIONS
    * see ((<fig>)).

---next_fig(options)
    Set the option values effective only in the next call of ((<fig>))
    (cleared then).

    These value are overwritten if specified explicitly in the next
    call of ((<fig>)).

    ARGUMENTS
    * options (Hash) : The usage is the same as (({options})) for ((<fig>)).

    RETURN VALUE
    * nil

    POSSIBLE EXCEPTIONS
    * see ((<fig>)).

---axes(xax=nil, yax=nil, options=nil)
    Draw axes using (by default) info in (({xax})) and (({yax})) if non-nil.

    ARGUMENTS
    * xax (nil or VArray): if non-nil, attributes 'long_name' and 'units' 
      are read to define (({xtitle})) and (({xunits})) (see below).
      These are overwritten by explicitly specifying (({xtitle})) and 
      (({xunits})).
    * yax (nil or VArray): if non-nil, attributes 'long_name' and 'units' 
      are read to define (({ytitle})) and (({yunits})) (see below).
      These are overwritten by explicitly specifying (({ytitle})) and 
      (({yunits})).
    * options (Hash) : options to change the default behavior if specified.
      It is a Hash with option names (String) as keys and their values.
      Options are interpreted by a NumRu::Misc::KeywordOptAutoHelp,
      so you can shorten the keys (by omitting tails) as long as it is 
      unambiguous.
       option name   default value   # description:
       "xside"       "tb"    # Where to draw xaxes (combination of t, b and u)
       "yside"       "lr"    # Where to draw yaxes (combination of l, r and u)
       "xtitle"      nil     # Title of x axis (if nil, internally determined)
       "ytitle"      nil     # Title of y axis (if nil, internally determined)
       "xunits"      nil     # Units of x axis (if nil, internally determined)
       "yunits"      nil     # Units of y axis (if nil, internally determined)
       "xtickint"    nil     # Interval of x axis tickmark
                             #                 (if nil, internally determined)
       "ytickint"    nil     # Interval of y axis tickmark
                             #                 (if nil, internally determined)
       "xlabelint"   nil     # Interval of x axis label
                             #                 (if nil, internally determined)
       "ylabelint"   nil     # Interval of y axis label
                             #                 (if nil, internally determined)
       "help"        false   # show help message if true

    RETURN VALUE
    * nil

    POSSIBLE EXCEPTIONS
    * those from NumRu::DCL if any / TypeError if any
    * options has a key that does not match any of the option names.
    * options has a key that is ambiguous

---set_axes(options)
    Change the default option values for ((<axes>)).

    ARGUMENTS
    * options (Hash) : The usage is the same as (({options})) for ((<axes>)).

    RETURN VALUE
    * a Hash containing the values replaced (the ones before calling this
      method)

    POSSIBLE EXCEPTIONS
    * see ((<axes>)).

---next_axes(options)
    Set the option values effective only in the next call of ((<axes>))
    (cleared then).

    These value are overwritten if specified explicitly in the next
    call of ((<axes>)).

    ARGUMENTS
    * options (Hash) : The usage is the same as (({options})) for ((<axes>)).

    RETURN VALUE
    * nil

    POSSIBLE EXCEPTIONS
    * see ((<axes>)).

---sim_trn?
    Returns whether the current coordinate transformation is a rectangular 
    curvelinear coordinate. A coordinate transformation must have been 
    established with ((<fig>)) or (({DCL::grstrf})).
    Mainly for internal usage, but a user can use it too.

    RETURN VALUE
    * true or false

---polar_coordinate_boundaries(xax=nil,yax=nil,yax_cyclic=nil)
    Draw boundaries in a polar coordinate. 

    ARGUMENTS
    * xax (VArray): Grid points of the radial coordinate.
    * yax (VArray): Grid points of the azimuthal coordinate.
    * yax_cyclic  : Should give true value when the azimuthal coordinate 
      is cyclic. 

    RETURN VALUE
    * nil

---map_trn?
    Returns whether the current coordinate transformation is a map projection.

    A coordinate transformation must have been established
    with ((<fig>)) or (({DCL::grstrf})).
    Mainly for internal usage, but a user can use it too.

    RETURN VALUE
    * true or false

---map(options=nil)
    (For map projection) Draws map grid and/or lim and/or coast lines etc.

    ARGUMENTS
    * options (Hash) : options to change the default behavior if specified.
      It is a Hash with option names (String) as keys and their values.
      Options are interpreted by a NumRu::Misc::KeywordOptAutoHelp,
      so you can shorten the keys (by omitting tails) as long as it is 
      unambiguous.
        option name   default value   # description:
        "lim"         true    # draw map lim (t or f)
        "grid"        true    # draw map grid (t or f)
        "vpt_boundary" false  # draw viewport boundaries (f, t or
                              # 1,2,3.., representing the line width)
        "wwd_boundary" false  # draw worksation window boundaries (f, t
                              # or 1,2,3.., representing the line width)
        "coast_world" false   # draw world coast lines (t or f)
        "border_world" false  # draw nation borders (t or f)
        "plate_world" false   # draw plate boundaries (t or f)
        "state_usa"   false   # draw state boundaries of US (t or f)
        "coast_japan" false   # draw japanese coast lines (t or f)
        "pref_japan"  false   # draw japanese prefecture boundaries (t or
                              # f)
        "help"        false   # show help message if true

    RETURN VALUE
    * nil

    POSSIBLE EXCEPTIONS
    * if called when the coordinate tansformation has not been established or
      the transformation is not a map projection.
    * those from NumRu::DCL if any / TypeError if any
    * options has a key that does not match any of the option names.
    * options has a key that is ambiguous

---set_map(options)
    Change the default option values for ((<map>)).

    ARGUMENTS
    * options (Hash) : The usage is the same as (({options})) for ((<map>)).

    RETURN VALUE
    * a Hash containing the values replaced (the ones before calling this
      method)

    POSSIBLE EXCEPTIONS
    * see ((<map>)).

---next_map(options)
    Set the option values effective only in the next call of ((<map>))
    (cleared then).

    These value are overwritten if specified explicitly in the next
    call of ((<map>)).

    ARGUMENTS
    * options (Hash) : The usage is the same as (({options})) for ((<map>)).

    RETURN VALUE
    * nil

    POSSIBLE EXCEPTIONS
    * see ((<map>)).

---line(gphys, newframe=true, options=nil)
    Plot a poly-line by selecting the first dimension (with GPhys#first1D)
    if (({gphys})) is more than 2D.

    ARGUMENTS
    * gphys (GPhys) : a GPhys whose data is plotted.
    * newframe (true/false) : if true, calls ((<fig>)), ((<axes>)),
      ((<title>)), and ((<annotate>)) internally; if false, only
      the poly-line is drawn (overlaid to the exiting figure).
    * options (Hash) : options to change the default behavior if specified.
      It is a Hash with option names (String) as keys and their values.
      Options are interpreted by a NumRu::Misc::KeywordOptAutoHelp,
      so you can shorten the keys (by omitting tails) as long as it is 
      unambiguous.
       option name   default value   # description:
       "title"       nil     # Title of the figure(if nil, internally
                             # determined)
       "annotate"    true    # if false, do not put texts on the right
                             # margin even when newframe==true
       "exchange"    false   # whether to exchange x and y axes
       "index"       1       # line/mark index
       "type"        1       # line type
       "label"       nil     # if a String is given, it is shown as the label
       "help"        false   # show help message if true

    RETURN VALUE
    * nil

---mark(gphys, newframe=true, options=nil)
    Similar to ((<line>)) but plots marks instead of drawing a poly-line.

    ARGUMENTS
    * gphys (GPhys) : a GPhys whose data is plotted.
    * newframe (true/false) : if true, calls ((<fig>)), ((<axes>)),
      ((<title>)), and ((<annotate>)) internally; if false, only
      the poly-line is drawn (overlaid to the exiting figure).
    * options (Hash) : options to change the default behavior if specified.
      It is a Hash with option names (String) as keys and their values.
      Options are interpreted by a NumRu::Misc::KeywordOptAutoHelp,
      so you can shorten the keys (by omitting tails) as long as it is 
      unambiguous.
       option name   default value   # description:
       "title"       nil     # Title of the figure(if nil, internally
                             # determined)
       "annotate"    true    # if false, do not put texts on the right
                             # margin even when newframe==true
       "exchange"    false   # whether to exchange x and y axes
       "index"       1       # mark index
       "type"        2       # mark type
       "size"        0.01    # marks size
       "help"        false   # show help message if true

    RETURN VALUE
    * nil

---contour(gphys, newframe=true, options=nil)
    Contour plot by selecting the first 2 dimensions (with GPhys#first2D)
    if (({gphys})) is more than 3D.

    Contour levels are determined as follows:
    * contour levels are set in this method if not set by
      ((<set_contour_levels>)) or the option (({"levels"})) is specified 
      explicitly.
    * When contour levels are set in this method, the option (({"levels"})) 
      has the highest precedence. If it is specified, options
      (({"index"})), (({"line_type"})), (({"label"})), and (({"label_height"}))
      are used.
      If (({"levels"})) are not specified, contour levels with a linear
      increment are set by using the options (({"min"})), (({"max"})),
      (({"nlev"})), (({"interval"})), (({"nozero"})), (({"coloring"})), 
      (({"clr_min"})), and (({"clr_max"})), which are interpreted by
      DCLExt::((<ud_set_linear_levs>)). The default values 
      of the linear level setting can be changed with
      ((<set_linear_contour_options>)).

    ARGUMENTS
    * gphys (GPhys) : a GPhys whose data is plotted.
    * newframe (true/false) : if true, calls ((<fig>)), ((<axes>)) (or ((<map>))),
      ((<title>)), and ((<annotate>)) internally; if false, only
      the poly-line is drawn (overlaid to the exiting figure).
    * options (Hash) : options to change the default behavior if specified.
      It is a Hash with option names (String) as keys and their values.
      Options are interpreted by a NumRu::Misc::KeywordOptAutoHelp,
      so you can shorten the keys (by omitting tails) as long as it is 
      unambiguous.
       option name   default value   # description:
       "title"       nil     # Title of the figure(if nil, internally
                             # determined)
       "annotate"    true    # if false, do not put texts on the right
                             # margin even when newframe==true
       "transpose"   false   # if true, exchange x and y axes
       "min"         nil     # minimum contour value
       "max"         nil     # maximum contour value
       "nlev"        nil     # number of levels
       "interval"    nil     # contour interval
       "nozero"      nil     # delete zero contour
       "coloring"    false   # set color contours with ud_coloring
       "clr_min"     13      # (if coloring) minimum color id
       "clr_max"     100     # (if coloring) maximum color id
       "help"        false   # show help message if true
       "levels"      nil     # contour levels (Array/NArray of Numeric)
       "index"       nil     # (if levels) line index(es) (Array/NArray of
                             # integers, Integer, or nil)
       "line_type"   nil     # (if levels) line type(s) (Array/NArray of
                             # integers, Integer, or nil)
       "label"       nil     # (if levels) contour label(s) (Array/NArray of
                             # String, String, true, false, nil). nil is
                             # recommended.
       "label_height"  nil   # (if levels) label height(s) 
                             # (Array/NArray of Numeric, Numeric, or nil).
                             #  nil is recommended.

    RETURN VALUE
    * nil

---set_contour_levels(options)
    Set contour levels for ((<contour>)) explicitly by values with the option (({levels})).

    ARGUMENTS
    * options (Hash) : options to change the default behavior.
      The option (({"levels"})) is mandatory (so it is not optional!).
      Supported options are (({"levels"})), (({"index"})), 
      (({"line_type"})), (({"label"})), and (({"label_height"})).
      See ((<contour>)) for their description.

---clear_contour_levels
    Clear contour levels set by ((<set_contour_levels>)).

---set_linear_contour_options(options)
    Change the default option values regarding linear contour level
    setting in ((<contour>)).

    ARGUMENTS
    * options (Hash) : The usage is the same as (({options})) 
      for ((<contour>)) but supported options here are limited to
      (({"min"})), (({"max"})), (({"nlev"})), (({"interval"})), 
      (({"nozero"})), (({"coloring"})), (({"clr_min"})), and (({"clr_max"})). 

    RETURN VALUE
    * a Hash containing the values replaced (the ones before calling this
      method)

---next_linear_contour_options(options)
    Similar to ((<set_linear_contour_options>)) but the setting
    is effective only for the next call of ((<contour>)).

---tone(gphys, newframe=true, options=nil)
    Color tone or shading by selecting the first 2 dimensions 
    (with GPhys#first2D) if (({gphys})) is more than 3D.

    Tone levels are determined as follows:
    * Tone levels are set in this method if not set by
      ((<set_tone_levels>)) or the option (({"levels"})) (and
      optionally, (({"patterns"}))) is (are) specified explicitly.
    * When contour levels & patterns are set in this method, 
       * (({"levels"})) has the highest precedence. If it is specified,
         tone levels and patterns are determined by DCLExt::((<ue_set_tone>)).
         Here, tone patterns can be specified with the option (({"patterns"})).
       * Currently, option (({"patterns"})) is effective only if (({"levels"}))
         is specified. Otherwise, it is ignored and patterns are
         determined internally (by using DCL.uegtlb).
       * If not, a linear tone levels are set if (({"ltone"=true}))
         (this is the default), or shading is made if (({"ltone"=false})).
         Shading is determined by following the parameters in the UDPACK
         in DCL. Therefore, coloring is made if DCL.udpget('ltone')==true
         regardless the option (({"ltone"=false})) here.
         When linear levels are set in this method, options
         (({"min"})), (({"max"})), (({"nlev"})), and (({"interval"}))
         are used if specified, which are interpreted by 
         DCLExt::((<ue_set_linear_levs>)). 
         Their default values can be changed by
         ((<set_linear_tone_options>)).

    ARGUMENTS
    * gphys (GPhys) : a GPhys whose data is plotted.
    * newframe (true/false) : if true, calls ((<fig>)), ((<axes>)) (or ((<map>))),
      ((<title>)), and ((<annotate>)) internally; if false, only
      the poly-line is drawn (overlaid to the exiting figure).
    * options (Hash) : options to change the default behavior if specified.
      It is a Hash with option names (String) as keys and their values.
      Options are interpreted by a NumRu::Misc::KeywordOptAutoHelp,
      so you can shorten the keys (by omitting tails) as long as it is 
      unambiguous.
       option name   default value   # description:
       "title"       nil     # Title of the figure(if nil, internally
                             # determined)
       "annotate"    true    # if false, do not put texts on the right
                             # margin even when newframe==true
       "ltone"       true    # Same as udpack parameter ltone
       "tonf"        false   # Use DCL.uetonf instead of DCL.uetone
       "tonc"        false   # Use DCL.uetonc instead of DCL.uetone
       "clr_min"     nil     # if an integer (in 10..99) is specified, used as
                             # the minimum color number. (the same can be done
                             # by setting the uepack parameter "icolor1")
       "clr_max"     nil     # if an integer (in 10..99) is specified, used as
                             # the maximum color number. (the same can be done
                             # by setting the uepack parameter "icolor2")
       "transpose"   false   # if true, exchange x and y axes
       "min"         nil     # minimum contour value
       "max"         nil     # maximum contour value
       "nlev"        nil     # number of levels
       "interval"    nil     # contour interval
       "help"        false   # show help message if true
       "levels"      nil     # tone levels  (Array/NArray of Numeric). Works
                             # together with patterns
       "patterns"    nil     # tone patters (Array/NArray of Numeric). Works
                             # together with levels

    RETURN VALUE
    * nil

---color_bar (options=nil)
    * Descroption:
      Draws color bars. Calls (({DCLext.color_bar})).

---set_tone_levels(options)
    Set tone levels for ((<tone>)) explicitly by values.

    ARGUMENTS
    * options (Hash) : options to change the default behavior.
      Supported options are (({"levels"})) and (({"patterns"})).
      Both of them must be specified explicitly (so they are 
      not optional!).

---clear_tone_levels
    Clear tone levels set by ((<set_tone_levels>)).

---set_linear_tone_options(options)
    Change the default option values regarding linear tone level
    setting in ((<tone>)).

    ARGUMENTS
    * options (Hash) : The usage is the same as (({options})) 
      for ((<tone>)) but supported options here are limited to
      (({"min"})), (({"max"})), (({"nlev"})), and (({"interval"})).

    RETURN VALUE
    * a Hash containing the values replaced (the ones before calling this
      method)

---next_linear_tone_options(options)
    Similar to ((<set_linear_tone_options>)) but the setting
    is effective only for the next call of ((<tone>)).

---vector(fx, fy, newframe=true, options=nil)
    2-D vector plot using DCL_Ext::((<flow_vect>)),
    which scales vectors in physical ("U") coordinates.

    ARGUMENTS
    * fx, fy (GPhys) : represent vectors
    * newframe (true/false) : if true, calls ((<fig>)), ((<axes>)) (or ((<map>))),
      ((<title>)), and ((<annotate>)) internally; if false, only
      the poly-line is drawn (overlaid to the exiting figure).
    * options (Hash) : options to change the default behavior if specified.
      It is a Hash with option names (String) as keys and their values.
      Options are interpreted by a NumRu::Misc::KeywordOptAutoHelp,
      so you can shorten the keys (by omitting tails) as long as it is 
      unambiguous.
       option name   default value   # description:
       "title"       nil     # Title of the figure(if nil, internally
                             # determined)
       "annotate"    true    # if false, do not put texts on the right
                             # margin even when newframe==true
       "transpose"   false   # if true, exchange x and y axes
       "flow_vect"   true    # If true, use DCLExt::flow_vect to draw
                             # vectors; otherwise, DCL::ugvect is used.
       "xintv"       1       # (Effective only if flow_vect) interval of data
                             # sampling in x
       "yintv"       1       # (Effective only if flow_vect) interval of data
                             # sampling in y
       "factor"      1.0     # (Effective only if flow_vect) scaling factor to
                             # strech/reduce the arrow lengths
       "unit_vect"   false   # Show the unit vector
       "max_unit_vect"       false   # (Effective only if flow_vect &&
                             # unit_vect) If true, use the maximum arrows to
                             # scale the unit vector; otherwise, normalize in V
                             # coordinate.
       "help"        false   # show help message if true

    RETURN VALUE
    * nil

=module NumRu::DCLExt

Collection of various compound DCL functions for convenience.
This module is to be separated but temporarily included in ggraph.rb
while it is premature.

==Index
MATH1
* ((<glpack>))
GRPH1
* ((<sgpack>))
* ((<slpack>))
* ((<swpack>))
GRPH2
* ((<uzpack>))
* ((<ulpack>))
* ((<ucpack>))
* ((<uupack>))
* ((<uspack>))
* ((<udpack>))
* ((<uepack>))
* ((<ugpack>))
* ((<umpack>))

==Module Functions

===glpack
---gl_set_params(hash)
    Calls (({DCL.glpset})) multiple times (for each key and val of (({hash}))).

    ARGUMENTS
    * hash (Hash) : combinations of parameter names and values for udpset

    RETURN VALUE
    * a Hash containing the parameter names and their old values that were 
      replaced.

    EXAMPLES
    * You can modify parameters temporarily as follows.

        before = DCLExt.gl_set_params({'lmiss'=>true,'rmiss'=>9999.0})
        ....
        DCLExt.gl_set_params(before)     # reset the change

===sgpack
---sg_set_params(hash)
    Calls (({DCL.sgpset})) multiple times (for each key and val of (({hash}))).

    See ((<gl_set_params>)) for usage.

===slpack
---sl_set_params(hash)
    Calls (({DCL.slpset})) multiple times (for each key and val of (({hash}))).

    See ((<gl_set_params>)) for usage.

===swpack
---sw_set_params(hash)
    Calls (({DCL.swpset})) multiple times (for each key and val of (({hash}))).

    See ((<gl_set_params>)) for usage.

===uzpack
---uz_set_params(hash)
    Calls (({DCL.uzpset})) multiple times (for each key and val of (({hash}))).

    See ((<gl_set_params>)) for usage.

===ulpack
---ul_set_params(hash)
    Calls (({DCL.ulpset})) multiple times (for each key and val of (({hash}))).

    See ((<gl_set_params>)) for usage.

===ucpack
---uc_set_params(hash)
    Calls (({DCL.ucpset})) multiple times (for each key and val of (({hash}))).

    See ((<gl_set_params>)) for usage.

===uupack
---uu_set_params(hash)
    Calls (({DCL.uupset})) multiple times (for each key and val of (({hash}))).

    See ((<gl_set_params>)) for usage.

===uspack
---us_set_params(hash)
    Calls (({DCL.uspset})) multiple times (for each key and val of (({hash}))).

    See ((<gl_set_params>)) for usage.

===udpack
---ud_set_params(hash)
    Calls (({DCL.udpset})) multiple times (for each key and val of (({hash}))).

    ARGUMENTS
    * hash (Hash) : combinations of parameter names and values for udpset

    RETURN VALUE
    * a Hash containing the parameter names and their old values that were 
      replaced.

    EXAMPLES
    * You can modify parameters temporarily as follows.

        before = DCLExt.ud_set_params('indxmj'=>4,'lmsg'=>false)
        DCL.udcntz(data)
        DCLExt.ud_set_params(before)     # reset the change

---ud_set_linear_levs(v, options)
    Set contour levels with a constant interval

    ARGUMENTS
    * v : Data values to be fed to udcnt[rz]
    * options (Hash) : option specification by keys and values. Available
      options are 
          name   default value   description
          'min'      nil       minimum contour value (Numeric)
          'max'      nil       maximum contour value (Numeric)
          'nlev'     nil       number of levels (Integer)
          'interval' nil       contour interval (Numeric)
          'nozero'   nil       delete zero contour (true/false)
          'coloring' false     set color contours with ud_coloring (true/false)
          'clr_min'  13        (if coloring) minimum color id (Integer)
          'clr_max'  100       (if coloring) maximum color id (Integer)
      Here, (({interval})) has a higher precedence over (({nlev})).
      Since all the default values are nil, only those explicitly specified
      are interpreted. If no option is provided, the levels generated will
      be the default ones set by udcnt[rz] without any level specification.

---ud_set_contour(levels,index=nil,line_type=nil,label=nil,label_height=nil)
    Set contours of at specified levels.

    Normally you do not have to specify (({label})) and (({label_height})).

    It calls DCL.udsclv for each level. So the arguments are basically
    the same as DCL.udsclv, but only levels are mandatory here.

    ARGUMENTS
    * levels (Array, NArray, or Numeric) : contour levels to be set.
      If Numeric, a single level is set.
    * index (Array of integers, Integer, or nil) : 
      index(es) of the contours. If it is an Array and its length is
      shorter than that of (({levels})), the same Array is repeated (so 
      for instance [1,1,3] is interpreted as [1,1,3,1,1,3,1,1,3,...]).
      If it is a single Integer, all the contour will have the same index. 
     If nil, the value of 'indxmn' is used.
    * line_type (Array of integers, Integer, or nil) : 
      line type(s) of the contours. If it is an Array and its length is
      shorter than that of (({levels})), the same Array is repeated.
      the length must agree with that of (({levels})).
      If it is a single Integer, all the contour will have the same type. 
      If nil, set to be 1.
    * label (Array of String, String, true, false, nil) :
      Label(s) of the contours. If it is an Array and its length is
      shorter than that of (({levels})), the same Array is repeated.
      the length must agree with that of (({levels})).
      If  it is a single String, all the contour will have the same label. 
      If true, all the contours will have the labels representing the levels.
      If false, no label will be drawn (set to "").
      If nil, same as true for the contours whose index is equal to "INDXMJ",
      and same as false otherwise.
    * label_height (Array of Numeric, Numeric, or nil) :
      Heigh of Labels. Normally you do not have to use this.
      If it is an Array and its length is
      shorter than that of (({levels})), the same Array is repeated.
      If nil, the default value ("RSIZEL") is used for non-empty labels.
      If a single Numeric, the same value is used for all the contours.
      Note that it is recommended to not to use this parameter but
      use DCL.udpset('RZISEL'. label_height), since a positive value
      here always means to draw labels even when the label is empty.

    RETURN VALUE
    * nil

---ud_add_contour(levels,index=nil,line_type=nil,label=nil,label_height=nil)
    Same as ((<ud_set_contour>)), but does not clear the contour levels that have
    been set.

===uepack
---ue_set_params(hash)
    Calls (({DCL.uepset})) multiple times (for each key and val of (({hash}))).

    See ((<gl_set_params>)) for usage.

---ue_set_linear_levs(v, options)
    Set tone levels with a constant interval

    ARGUMENTS
    * v : Data values to be fed to udcnt[rz]
    * options (Hash) : option specification by keys and values. Available
      options are 
          name   default value   description
          'min'      nil         minimum tone level (Numeric)
          'max'      nil         maximum tone level (Numeric)
          'nlev'     nil         number of levels (Integer)
          'interval' nil         tone-level interval (Numeric)
      Here, (({interval})) has a higher precedence over (({nlev})).
      Since all the default values are nil, only those explicitly specified
      are interpreted. If no option is provided, the levels generated will
      be the default ones set by udcnt[rz] without any level specification.

---ue_set_tone(levels, patterns)
    Set tone levels and patterns.

    patterns are set between levels as follows:

     when (levels.length == patterns.length+1)

       levels[0]  |  levels[1]  |  levels[2]  ...  |  levels[-2]  |  levels[-1]
              patterns[0]   patterns[1]   ...  patterns[-2]   patterns[-1]

     when (levels.length == patterns.length)

       levels[0]  |  levels[1]  |  levels[2]  ...  |  levels[-1]  |  +infty
              patterns[0]   patterns[1]   ...  patterns[-2]   patterns[-1]

     when (levels.length == patterns.length-1)

       -infty  |  levels[0]  |  levels[1]  ...  |  levels[-1]  |  +infty
           patterns[0]   patterns[1]   ...  patterns[-2]   patterns[-1]

     else
       error (exception raised)

    ARGUMENTS
    * levels (Array or NArray of Numeric) : tone levels. Its length must be
      either 1 larger than, equal to, or 1 smaller than the length of patterns
    * patterns (Array or NArray of Numeric) : tone patterns

    RETURN VALUE
    * nil

---ue_add_tone(levels, patterns)
    Same as ((<ue_set_tone>)), but does not clear the tone levels that have
    been set.

===ugpack
---ug_set_params(hash)
    Calls (({DCL.ugpset})) multiple times (for each key and val of (({hash}))).
    See ((<gl_set_params>)) for usage.

===umpack
---um_set_params(hash)
    Calls (({DCL.umpset})) multiple times (for each key and val of (({hash}))).

    See ((<gl_set_params>)) for usage.

==Original methods:

===Vectors
---unit_vect( vxfxratio, vyfyratio, fxunit=nil, fyunit=nil, options=nil )

    Show the "unit vector", which indicate the vector scaling.

    ARGUMENTS
    * vxfxratio (Float) : (V cood length)/(actual length) in x
    * vyfyratio (Float) : (V cood length)/(actual length) in y
    * fxunit (Float) : If specified, x unit vect len
    * fyunit (Float) : If specified, y unit vect len
    * options (Hash) : options to change the default behavior if specified.
      It is a Hash with option names (String) as keys and their values.
      Options are interpreted by a NumRu::Misc::KeywordOptAutoHelp,
      so you can shorten the keys (by omitting tails) as long as it is 
      unambiguous.
       option name   default value   # description:
       "vxunit"      0.05    # x unit vect len in V coord. Used only when
                             # fxunit is omitted (default)
       "vyunit"      0.05    # y unit vect len in V coord. Used only when
                             # fyunit is omitted (default)
       "vxuloc"      nil     # Starting x position of unit vect
       "vyuloc"      nil     # Starting y position of unit vect
       "vxuoff"      0.05    # Specify vxuloc by offset from right-bottom
                             # corner
       "vyuoff"      0.0     # Specify vyuloc by offset from right-bottom
                             # corner
       "inplace"     true    # Whether to print labels right by the unit
                             # vector (true) or below the x axis (false)
       "rsizet"      nil     # Label size(default taken from uz-parameter
                             # 'rsizel1')
       "index"       3       #  Line index of the unit vector
       "help"        false   # show help message if true

---set_unit_vect_options(options)
    Change the default option values for ((<unit_vect>)).

---next_unit_vect_options(options)
    Set the option values effective only in the next call of ((<unit_vect>))

---flow_vect( fx, fy, factor=1.0, xintv=1, yintv=1)

    2D Vector plot. Unlike (({DCL::ugvect})), scaling are made in term of the physical (or "U") coordinate.

    This method is meant to substitute (({DCL::ugvect})). The scaling 
    is made in terms of the U coordinate. This method is suitable to
    show vectors such as velocity, since the arrow direction represets
    the direction in the U coordinate. Also, one can re-scale the
    vector length easily by using the argument (({factor})).

    Currently, this method is not compatible with map projection, 
    since it calls (({DCL::ugvect})) internally.

    ARGUMENTS
    * fx, fy (2D NArray or Array) : the vector field.
    * factor (Integer) : factor to change the arrow length.
      By default, arrows are scaled so that the longest one
      match the grid interval.
    * xintv, yintv (Interger) : interval to thin out (({fx})) and (({fy})),
      respectively. Useful if the grid points are too many.

===Color bars
---set_color_bar_options(options)
    To set options of ((<color_bar>)) effective in the rest.

---color_bar(options=nil)
    * Descroption:
      Draws color bars

    * Example
      Here is the simplest case, where no argument is given to color_bar.

        DCL.uetone(hoge)
        DCL.usdaxs
        ...
        DCL.color_bar

      This draws a color bar by using the levels and tone patterns(colors)
      set previously. There are many parameters you can set manually,
      as introduced below:

    * Description of options
       option name   default value   # description:
       "levels"      nil     # tone levels (if omitted, latest ones are used)
       "patterns"    nil     # tone patterns (~colors) (if omitted, latest
                             # ones are used)
       "voff"        nil     # how far is the bar from the viewport in the V
                             # coordinate
       "vcent"       nil     # center position of the bar in the V coordinate
                             # (VX or VY)
       "vlength"     0.3     # bar length in the V coordinate
       "vwidth"      0.02    # bar width in the V coordinate
       "landscape"   false   # if true, horizonlly long (along x axes)
       "portrait"    true    # if true, vertically long (along y axes)
       "left"        false   # place the bar in the left (effective if
                             # portrait)
       "top"         false   # place the bar at the top (effective if portrait)
       "tickintv"    1       # 0,1,2,3,.. to specify how frequently the
                             # dividing tick lines are drawn (0: no tick lines,
                             # 1:every time, 2:ever other:,...)
       "labelintv"   nil     # 1,2,3,.. to specify how frequently labels are
                             # drawn (1:every time, 2:ever other:,... default:
                             # internally determined)
       "charfact"    0.9     # factor to change the label character size
                             # (relative to 'rsizel1')
       "log"         false   # 
       "constwidth"  false   # if true, each color is drawn with the same width
       "index"       nil     # line index of bar frame
                             # (so far, valid only when "constwidth")
       "help"        false   # show help message if true

=end
############################################################

module NumRu

  module DCLExt
    # to be included in the RubyDCL distribution

    module_function

    #<<< for many packages >>>

    %w!gl sg sl sw uz ul uc uu us ud ue ug um!.each do |pkg|
      eval <<-EOS, nil, __FILE__, __LINE__+1
        def #{pkg}_set_params(hash)
          before = Hash.new
          hash.each{|k,v|
            before[k]=DCL.#{pkg}pget(k)
            if(v.is_a? String) then
              DCL.#{pkg}cset(k,v)
            else
              DCL.#{pkg}pset(k,v)
            end
          }
          before
        end
      EOS
    end

    #<<< module data >>>

    @@empty_hash = Hash.new

    #<<< udpack >>>

    def ud_coloring(clr_min=13, clr_max=100)
      # change the colors of existing contours to make a gradation
      # (rainbow colors with the default color map).
      nlev = DCL.udqcln
      cont_params = Array.new
      for i in 1..nlev
        cont_params.push( DCL.udqclv(i) )   # => [zlev,indx,ityp,clv,hl]
      end
      DCL.udiclv     # clear the contours

      colors = clr_min + 
               NArray.int(nlev).indgen! * (clr_max-clr_min) / nlev

      cont_params.sort!
      for i in 0...nlev
        cont_params[i][1] += colors[i]*10   # indx += colors[i]*10
        DCL.udsclv(*cont_params[i])
      end
    end

    def ud_set_linear_levs(v, options=nil)
      #Accepted options
      #  name        default  description
      #  'min'       nil      minimum contour value (Numeric)
      #  'max'       nil      maximum contour value (Numeric)
      #  'nlev'      nil      number of levels (Integer)
      #  'interval'  nil      contour interval (Numeric)
      #  'nozero'    false    delete zero contour (true/false)
      #  'coloring'  false    set color contours with ud_coloring (true/false)
      #  'clr_min'   13       (if coloring) minimum color id (Integer)
      #  'clr_max'   100      (if coloring) maximum color id (Integer)
      options = @@empty_hash if !options
      raise TypeError, "options must be a Hash" if !options.is_a?(Hash)
      min = options['min']
      max = options['max']
      nlev = options['nlev']
      interval = options['interval']
      nozero = options['nozero']
      if interval
        dx = interval
      elsif nlev
        dx = -nlev
      else
        dx = 0
      end
      if min || max
        min = v.min if !min
        max = v.max if !max
        DCL.udgcla(min, max, dx)
      else
        DCL.udgclb(v, dx)
      end
      if nozero
        DCL.uddclv(0.0)
      end
      if options['coloring']
        clr_min = ( options['clr_min'] || 13 )
        clr_max = ( options['clr_max'] || 100 )
        ud_coloring( clr_min, clr_max )
      end
    end

    def ud_set_contour(*args)
      DCL.udiclv
      ud_add_contour(*args)
    end

    def ud_add_contour(levels,index=nil,line_type=nil,label=nil,label_height=nil)

      # < check levels >
      case levels
      when Array, NArray
        # This is expected. Nothing to do.
      when Numric
        levels = [levels]
      else
        raise ArgumentError, "invalid level specification (#{levels})"
      end

      nlev = levels.length

      # < index >
      case index
      when Array
        raise ArgumentError, "index is an empty array" if index.length == 0
        while (index.length < nlev )
          index += index
        end
      when Numeric
        index = [index]*nlev
      when nil
        index = [DCL.udpget('indxmn')]*nlev
      else
        raise ArgumentError, "unsupported index type (#{index.class})"
      end

      # < line_type >
      case line_type
      when Array
        raise ArgumentError, "line_type is an empty array" if line_type.length == 0
        while (line_type.length < nlev )
          line_type += line_type
        end
      when Numeric
        line_type = [line_type]*nlev
      when nil
        line_type = [1]*nlev
      else
        raise ArgumentError, "unsupported index type (#{index.class})"
      end

      # < label >
      case label
      when Array
        raise ArgumentError, "label is an empty array" if label.length == 0
        while (label.length < nlev )
          label += label
        end
      when String
        label = [label]*nlev
      when false
        label = [""]*nlev
      when true
        label = (0...nlev).collect{|i| 
            DCL.udlabl(levels[i])
        }
      when nil
        indxmj = DCL.udpget('indxmj')
        label = (0...nlev).collect{|i| 
          if index[i]==indxmj
            DCL.udlabl(levels[i])
          else
            ""
          end
        }
      else
        raise ArgumentError, "unsupported index type (#{index.class})"
      end

      # < label_height >
      case label_height
      when Array
        raise ArgumentError, "label_height is an empty array" if label_height.length == 0
        while (label_height.length < nlev )
          label_height += label_height
        end
      when Numeric
        label_height = [label_height]*nlev
      when nil
        label_height = label.collect{|lv| lv=="" ? 0.0 : DCL.udpget('rsizel')}
      else
        raise ArgumentError, "unsupported index type (#{index.class})"
      end

      # < set levels >

      for i in 0...nlev
        DCL.udsclv(levels[i],index[i],line_type[i],label[i],label_height[i])
      end
      nil
    end

    #<<< uepack >>>

    def ue_set_linear_levs(v, options=nil)
      #  'min'       nil      minimum tone level (Numeric)
      #  'max'       nil      maximum tone level (Numeric)
      #  'nlev'      nil      number of levels (Integer)
      #  'interval'  nil      tone-level interval (Numeric)
      options = @@empty_hash if !options
      raise TypeError, "options must be a Hash" if !options.is_a?(Hash)
      min = options['min']
      max = options['max']
      nlev = options['nlev']
      interval = options['interval']
      if interval
        dx = interval
      elsif nlev
        dx = -nlev
      else
        dx = 0
      end
      if min || max
        min = v.min if !min
        max = v.max if !max
        DCL.uegtla(min, max, dx)
      else
        DCL.uegtlb(v, dx)
      end
    end

    def ue_set_tone(levels, patterns)
      DCL.ueitlv
      ue_add_tone(levels, patterns)
    end

    def ue_add_tone(levels, patterns)

      # < check types >

      if !levels.is_a?(Array) && !levels.is_a?(NArray)
        raise TypeError, "levels: Array or NArray expected (#{levels.inspect})"
      end
      if !patterns.is_a?(Array) && !patterns.is_a?(NArray)
        raise TypeError, "patterns: Array or NArray expected (#{patterns.inspect})"
      end

      # < set levels >

      nlev = levels.length
      npat = patterns.length

      case (nlev - npat)
      when 1
        for i in 0...nlev-1
          DCL.uestlv(levels[i],levels[i+1],patterns[i])
        end
      when 0
        for i in 0...nlev-1
          DCL.uestlv(levels[i],levels[i+1],patterns[i])
        end
        DCL.uestlv(levels[-1],DCL.glpget('rmiss'),patterns[-1])
      when -1
        DCL.uestlv(DCL.glpget('rmiss'),levels[0],patterns[0])
        for i in 1...nlev
          DCL.uestlv(levels[i-1],levels[i],patterns[i])
        end
        DCL.uestlv(levels[-1],DCL.glpget('rmiss'),patterns[-1])
      else
        raise ArgumentError, 
          "lengths of levels(#{nlev}) and patterns(#{npat}) are inconsistent"
      end
      nil
    end

    ############################################################
    # RELATIVELY INDEPENDENT OF DCL SUBLIBRARIES
    ############################################################

    # <<< flow vector package >>>

    def __truncate(float, order=2)
      # truncate (round) a floating number with the number digits
      # specified by "order".
      # e.g., if order=3, -0.012345 => -0.0123;  6.6666 => 6.67
      exponent = 10**(-Math::log10(float.abs).floor+order-1)
      (float * exponent).round.to_f/exponent
    end

    @@unit_vect_options = Misc::KeywordOptAutoHelp.new(
      ['vxunit', 0.05, "x unit vect len in V coord. Used only when fxunit is omitted (default)"],
      ['vyunit', 0.05, "y unit vect len in V coord. Used only when fyunit is omitted (default)"],
      ['vxuloc', nil, "Starting x position of unit vect"],
      ['vyuloc', nil, "Starting y position of unit vect"],
      ['vxuoff', 0.05, "Specify vxuloc by offset from right-bottom corner"],
      ['vyuoff', 0.0, "Specify vyuloc by offset from right-bottom corner"],
      ['inplace',true, "Whether to print labels right by the unit vector (true) or below the x axis (false)"],
      ['rsizet', nil, "Label size(default taken from uz-parameter 'rsizel1')"],
      ['index',  3," Line index of the unit vector"]
    )

    def set_unit_vect_options(options)
      @@unit_vect_options.set(options)
    end

    @@next_unit_vect_options = nil
    def next_unit_vect_options(options)
      if options.is_a?(Hash)
        @@next_unit_vect_options = options
      else
        raise TypeError,"Hash expected"
      end
      nil
    end

    def unit_vect( vxfxratio,   # (V cood length)/(actual length) in x
                   vyfyratio,   # (V cood length)/(actual length) in y
                   fxunit=nil,  # If specified, x unit vect len
                   fyunit=nil,  # If specified, y unit vect len
                   options=nil )
      #< options >
      if @@next_unit_vect_options
        options = ( options ? @@next_unit_vect_options.update(options) : 
                              @@next_unit_vect_options )
        @@next_unit_vect_options = nil
      end
      opt = @@unit_vect_options.interpret(options)
      vxunit = opt['vxunit']
      vyunit = opt['vyunit']
      vxuloc = opt['vxuloc']
      vyuloc = opt['vyuloc']
      rsizet = opt['rsizet']
      index = opt['index']
      
      #< unit vector >
      if fxunit
        vxunit = vxfxratio * fxunit
      else
        fxunit = vxunit / vxfxratio
      end
      if fyunit
        vyunit = vyfyratio * fyunit
      else
        fyunit = vyunit / vyfyratio
      end
      fxunit = __truncate( (uxusv=fxunit) )
      fyunit = __truncate( (uyusv=fyunit) )
      vxunit = vxunit * (fxunit/uxusv)
      vyunit = vyunit * (fyunit/uyusv)
      if !(vxuloc && vyuloc)
        vx0,vx1,vy0,vy1 = DCL.sgqvpt
        vxuloc = vx1 + opt['vxuoff'] if !vxuloc
        vyuloc = vy0 + opt['vyuoff'] if !vyuloc
      end
      DCL.sglazv( vxuloc, vyuloc,  vxuloc+vxunit, vyuloc,        1, index )
      DCL.sglazv( vxuloc, vyuloc,  vxuloc,        vyuloc+vyunit, 1, index )

      #< labelling >
      sfxunit = sprintf("%.2g",fxunit)
      sfyunit = sprintf("%.2g",fyunit)
      rsizet = DCL.uzpget('rsizel1') if !rsizet 
      if opt['inplace']
        DCL.sgtxzv(vxuloc, vyuloc-1.2*rsizet,
                   sfxunit, rsizet, 0, -1, index)
        DCL.sgtxzv(vxuloc+1.2*rsizet, vyuloc+0.5*rsizet,
                   sfyunit, rsizet, 90, -1, index)
      else
        msg= "UNIT VECTOR X:#{sfxunit} Y:#{sfyunit}"
        before = uz_set_params({'rsizec1'=>rsizet})
        DCL.uxsttl('b',' ',0.0)
        DCL.uxsttl('b',msg,0.0)
        uz_set_params(before)
      end
    end

    def flow_vect( fx, fy, factor=1.0, xintv=1, yintv=1)
      raise ArgumentError,"Expect 2D arrays" if fx.rank != 2 || fy.rank != 2
      raise ArgumentError,"fx.shape != fy.shape" if fx.shape != fy.shape
      raise ArgumentError,"xintv must be a positive integer" if xintv < 0
      raise ArgumentError,"yintv must be a positive integer" if yintv < 0
      nx, ny = fx.shape
      if xintv >= 2
        idx = NArray.int(nx/xintv).indgen!*xintv  # [0,xintv,2*xintv,..]
        fx = fx[idx, true]
        fy = fy[idx, true]
      end
      if yintv >= 2
        idx = NArray.int(ny/yintv).indgen!*yintv  # [0,yintv,2*yintv,..]
        fx = fx[true, idx]
        fy = fy[true, idx]
      end
      nx, ny = fx.shape  # again, because of xintv & yintv 
      vx0,vx1,vy0,vy1 = DCL.sgqvpt
      ux0,ux1,uy0,uy1 = DCL.sgqwnd
      dvx = (vx1-vx0)/nx
      dvy = (vy1-vy0)/ny
      ax = (vx1-vx0)/(ux1-ux0)   # factor to convert from U to V coordinate
      ay = (vy1-vy0)/(uy1-uy0)   # factor to convert from U to V coordinate
      fxmx = fx.abs.max
      fymx = fy.abs.max
      raise "fx has no data or all zero" if fxmx == 0
      raise "fy has no data or all zero" if fymx == 0
      cn = [ dvx/(ax*fxmx),  dvy/(ay*fymx) ].min  # normarization constant
      vxfxratio = factor*cn*ax
      vyfyratio = factor*cn*ay
      before = ug_set_params( {'LNRMAL'=>false, 'LMSG'=>false,
                               'XFACT1'=>1.0, 'YFACT1'=>1.0} )
      DCL.ugvect( vxfxratio*fx, vyfyratio*fy )
      ug_set_params( before )
      unit_vect_info = [ vxfxratio, vyfyratio, fxmx, fymx ]
      return unit_vect_info
    end

    ######################################

    # <<< color bar >>>
      
    @@color_bar_options =  Misc::KeywordOptAutoHelp.new(
      ["levels",  nil, "tone levels (if omitted, latest ones are used)"],
      ["patterns", nil, "tone patterns (~colors) (if omitted, latest ones are used)"],
      ["voff", nil, "how far is the bar from the viewport in the V coordinate"],
      ["vcent",nil, "center position of the bar in the V coordinate (VX or VY)"],
      ["vlength", 0.3, "bar length in the V coordinate"],
      ["vwidth", 0.02, "bar width in the V coordinate"],
      ["landscape", false, "if true, horizonlly long (along x axes)"],
      ["portrait", true, "if true, vertically long (along y axes)"],
      ["left", false,"place the bar in the left (effective if portrait)"],
      ["top",  false,"place the bar at the top (effective if portrait)"],
      ["tickintv", 1, "0,1,2,3,.. to specify how frequently the dividing tick lines are drawn (0: no tick lines, 1:every time, 2:ever other:,...)"],
      ["labelintv", nil, "1,2,3,.. to specify how frequently labels are drawn (1:every time, 2:ever other:,... default: internally determined)"],
      ["charfact", 0.9, "factor to change the label character size (relative to 'rsizel1')"],
      ["log"   ,   false,  ""],
      ["constwidth", false, "if true, each color is drawn with the same width(length)"],
      ["index", nil, "index of bar frame (so far, valid only when constwidth) "]
    )

    def set_color_bar_options(options)
      @@color_bar_options.set(options)
    end

    def color_bar(options=nil)

      opt = @@color_bar_options.interpret(options)

      levels = opt['levels']
      patterns = opt['patterns']

      if (levels.nil? && !patterns.nil?) || (!levels.nil? && patterns.nil?) then
        raise "levels and patterns must set at same time\n"
      end

      landscape = opt["landscape"] || !opt["portrait"]
      portrait  = ! landscape

      miss = DCL::glrget("rmiss")

      if !levels.nil? then

        nlevel = levels.length
        levels = NArray.to_na(levels) if levels.class==Array
        if patterns.length != (nlevel-1) then
          raise "patterns.length must be equal levels.length - 1\n"
        end

        DCL::ueitlv
        DCL::uestln(levels,patterns)
        nton = patterns.length

      else

        nton = DCL::ueqntl
        if nton==0 then
          raise "no tone patern was set\n"
        end
        lev1 = Array.new
        lev2 = Array.new
        patterns = Array.new
        for n in 0..nton-1
          tlev1,tlev2,ipat = DCL::ueqtlv(n+1)
          lev1.push(tlev1)
          lev2.push(tlev2)
          patterns.push(ipat)
        end

        #levels = lev1+lev2
        #levels = levels.uniq.sort
        #levels.delete(miss)
        #if levels.ne(levels.sort).any? then
        #  raise "levels is not in order\n"
        #end

        levels = lev1.push(lev2[-1])
        levels = NArray.to_na(levels)
        patterns = NArray.to_na(patterns)

      end

      vx1, vx2, vy1, vy2 = DCL.sgqvpt

      vwidth = opt["vwidth"]
      vlength = opt["vlength"]

      if portrait
        if !opt["left"]
          voff =  opt["voff"] || 
              DCL.uzpget('roffyr') + DCL.uzpget('pad1')*DCL::uzrget("rsizec2")
          vxmin = vx2 + voff
          vxmax = vx2 + voff + vwidth
        else
          voff =  opt["voff"] ? -opt["voff"] : \
              DCL.uzpget('roffyl') - DCL.uzpget('pad1')*DCL::uzrget("rsizec2")
          vxmax = vx1 + voff
          vxmin = vx1 + voff - vwidth
        end
        vymin =( opt["vcent"] ? opt["vcent"]-vlength/2 : vy1 )
        vymax =( opt["vcent"] ? opt["vcent"]+vlength/2 : vy1+vlength )
      else
        vxmin =( opt["vcent"] ? opt["vcent"]-vlength/2 : (vx1+vx2)/2-vlength/2 )
        vxmax =( opt["vcent"] ? opt["vcent"]+vlength/2 : (vx1+vx2)/2+vlength/2 )
        if opt["top"]
          # top
          voff =  opt["voff"] || 
               DCL.uzpget('roffxt') + DCL.uzpget('pad1')*DCL::uzrget("rsizec2")
          vymin = vy2 + voff
          vymax = vy2 + voff + vwidth
        else
          # bottom
          voff =  opt["voff"] ? -opt["voff"] : \
               DCL.uzpget('roffxb') - DCL.uzpget('pad1')*DCL::uzrget("rsizec2")
          vymax = vy1 + voff
          vymin = vy1 + voff - vwidth
        end
      end

      min = levels[levels.ne(miss).where].min
      max = levels[levels.ne(miss).where].max
      if levels[0] == miss
        inf0 = true
        dummy1,dummy2,ipat0 = DCL::ueqtlv(1)
      else
        inf0 = false
      end
      if levels[-1] == miss
        inf1 = true
        dummy1,dummy2,ipat1 = DCL::ueqtlv(nton)
      else
        inf1 = false
      end

      lclip = DCL::sgpget("lclip")
      DCL::sgpset("lclip", false) if lclip

      if opt["constwidth"]

        nlev = levels.length
        npat = patterns.length

        if opt["index"]
          index = opt["index"]
        else
          index = DCL::uzpget("indext2")
        end

        if portrait then

          vy = (NArray.sfloat(npat+1).indgen!)*(vymax-vymin)/npat + vymin

          for i in 0..npat-1
            if( i==0 && inf0 )
              DCL::sgtnzv([(vxmin+vxmax)/2,vxmax,vxmin],[vy[0],vy[1],vy[1]],patterns[0])
              DCL::sgplzv([vxmin,(vxmin+vxmax)/2],[vy[1],vy[0]],1,index)
              DCL::sgplzv([vxmax,(vxmin+vxmax)/2],[vy[1],vy[0]],1,index)
            elsif( i==npat-1 && inf1 )
              DCL::sgtnzv([vxmin,vxmax,(vxmax+vxmin)/2],[vy[npat-1],vy[npat-1],vy[npat]],patterns[npat-1])
              DCL::sgplzv([vxmin,(vxmax+vxmin)/2],[vy[npat-1],vy[npat]],1,index)
              DCL::sgplzv([vxmax,(vxmax+vxmin)/2],[vy[npat-1],vy[npat]],1,index)
            else
              DCL::sgtnzv([vxmin,vxmax,vxmax,vxmin],[vy[i],vy[i],vy[i+1],vy[i+1]],patterns[i])
              DCL::sgplzv([vxmin,vxmin],[vy[i],vy[i+1]],1,index)
              DCL::sgplzv([vxmax,vxmax],[vy[i],vy[i+1]],1,index)
            end
          end

        else
          vx = (NArray.sfloat(npat+1).indgen!)*(vxmax-vxmin)/npat + vxmin

          for i in 0..npat-1
            if( i==0 && inf0 )
              DCL::sgtnzv([vx[0],vx[1],vx[1]],[(vymin+vymax)/2,vymax,vymin],patterns[0])
              DCL::sgplzv([vx[1],vx[0]],[vymin,(vymin+vymax)/2],1,index)
              DCL::sgplzv([vx[1],vx[0]],[vymax,(vymin+vymax)/2],1,index)
            elsif( i==npat-1 && inf1 )
              DCL::sgtnzv([vx[npat-1],vx[npat-1],vx[npat]],[vymin,vymax,(vymax+vymin)/2],patterns[npat-1])
              DCL::sgplzv([vx[npat-1],vx[npat]],[vymin,(vymax+vymin)/2],1,index)
              DCL::sgplzv([vx[npat-1],vx[npat]],[vymax,(vymax+vymin)/2],1,index)
            else
              DCL::sgtnzv([vx[i],vx[i],vx[i+1],vx[i+1]],[vymin,vymax,vymax,vymin],patterns[i])
              DCL::sgplzv([vx[i],vx[i+1]],[vymin,vymin],1,index)
              DCL::sgplzv([vx[i],vx[i+1]],[vymax,vymax],1,index)
            end
          end


        end

      else

        nbar =100
        bar = NArray.float(nbar,2)
        for i in 0..nbar-1
          bar[i,true] = min + (max-min).to_f/(nbar-1)*i
        end

        xb = DCL::uzlget("labelxb")
        yl = DCL::uzlget("labelyl")
        if portrait then
          xmin = 0.0
          xmax = 1.0
          ymin = min
          ymax = max
          DCL::uzlset("labelxb",false)
          DCL::uzlset("labelyl",true)
          bar = bar.transpose(-1,0)
          DCL::uwsgxa([0,1])
          DCL::uwsgya(bar[0,true])
        else
          xmin = min
          xmax = max
          ymin = 0.0
          ymax = 1.0
          DCL::uzlset("labelxb",true)
          DCL::uzlset("labelyl",false)
          DCL::uwsgxa(bar[true,0])
          DCL::uwsgya([0,1])
        end

        type = 1
        if opt["log"] then
          type +=1
          type +=1 if !portrait
        end

        DCL::grfig
        DCL::grsvpt(vxmin,vxmax,vymin,vymax)
        DCL::grswnd(xmin,xmax,ymin,ymax)
        DCL::grstrn(type)
        DCL::grstrf

        DCL::uetone(bar)

      end # end of constwidth

      # < set ticking and labeling levels >

      if opt["labelintv"]
        labelintv = opt["labelintv"]
      else
        ntn = nton
        ntn -= 1 if inf0
        ntn -= 1 if inf1
        if portrait
          labelintv = (ntn-1) / 9  + 1
        else
          labelintv = (ntn-1) / 5  + 1
        end
      end

      tickintv = opt["tickintv"]
      tickintv = labelintv if tickintv <= 0
      eps = 1e-5
      dummy = -9.9e-38
      dz = dzp = dzc = dummy
      idu = Array.new
      (1...levels.length).each do |i|
        dzc = (levels[i] - levels[i-1]).abs
        if (dzc-dzp).abs <= eps * [dzc.abs,dzp.abs].max
          dz = dzc  # set dz if two consecutive inrements are the same 
          idu.push( i-1 )
        end
        dzp = (levels[i] - levels[i-1]).abs
      end
      if idu.length > 0
        idumin = idu.min - 1
        idumax = idu.max + 1
      else
        idumin = 0
        idumax = levels.length-1
      end
      if dz != dummy
#        if idumin == 1 and levels[0] != miss  
#          # to correct non-uniform intv at the beginning
#          levels[0] = levels[1] - dz
#          idumin = 0
#          min = levels[0]
#        end
#        if idumax == levels.length-2 and levels[-1] != miss
#          # to correct non-uniform intv at the end
#          levels[-1] = levels[-2] + dz
#          idumax = levels.length-1
#          max = levels[-1]
#        end
        # use the algorithm used in DCL.udgcla
        offs_tick = ( (-levels[idumin]/dz).round % tickintv + idumin ) % tickintv
        offs_label = ( (-levels[idumin]/dz).round % labelintv + idumin ) % labelintv
      else
        md = 0
        if ( (idx=levels.eq(0.0).where).length > 0 )
          md = idx[0] % labelintv
        else
          a = levels[0...([labelintv,levels.length].min)]
          b = a * 10**( -NMath.log10(a.abs).floor.min )
          (0...b.length).each{|i| md=i if (b[i].round-b[i]).abs < 1e-5 }
        end
        offs_tick = (md % tickintv)
        offs_label = md
      end

      if opt["constwidth"]

        charfact = opt["charfact"]
        rsizel = DCL::uzrget("rsizel1")

        if portrait then
          if voff > 0
            cent = -1
            vxlabel = vxmax+0.01
          else
            cent = 1
            vxlabel = vxmin-0.01
          end
          for i in 0..nlev-1
            if levels[i] != miss
              if (i % labelintv) == offs_label
                begin
                  char = DCL::chval(DCL::uyqfmt,levels[i])
                  DCL::sgtxzr(vxlabel,vy[i],char,charfact*rsizel,0,cent,2)
                rescue
                  DCL::sgtxzr(vxlabel,vy[i],levels[i].to_s,charfact*rsizel,0,cent,2)
                end
              end
              if (i % tickintv) == offs_tick
                DCL::sgplzv([vxmin,vxmax],[vy[i],vy[i]],1,index)
              end
            end
          end
        else
          if voff > 0
            vylabel = vymax+0.015
          else
            vylabel = vymin-0.015
          end

          for i in 0..nlev-1
            if levels[i] != miss
              if (i % labelintv) == offs_label
                begin
                  char = DCL::chval(DCL::uxqfmt,levels[i])
                  DCL::sgtxzr(vx[i],vylabel,char,charfact*rsizel,0,0,2)
                rescue
                  DCL::sgtxzr(vx[i],vylabel,levels[i].to_s,charfact*rsizel,0,0,2)
                end
              end
              if (i % tickintv) == offs_tick
                DCL::sgplzv([vx[i],vx[i]],[vymin,vymax],1,index)
              end
            end
          end
        end

      else

        tick1 = Array.new
        tick2 = Array.new
        for i in 0..levels.length-1
          if i>=idumin && i<=idumax
            tick1.push(levels[i]) if (i % tickintv) == offs_tick 
            tick2.push(levels[i]) if (i % labelintv) == offs_label
          end
        end

        charfact = opt["charfact"]
        rsizel = DCL::uzrget("rsizel1")
        DCL::uzrset("rsizel1",charfact*rsizel)
        rsizet = DCL::uzrget("rsizet1")

        if portrait then
          if voff > 0
            before = uz_set_params( {'labelyl'=>false, 'labelyr'=>true,
                                     'icentyr'=>-1.0} )
          else
            before = uz_set_params( {'labelyl'=>true, 'labelyr'=>false,
                                     'icentyl'=>1.0} )
                  end
          DCL::uzrset("rsizet1",vwidth)
          DCL::uxaxdv("t",1,1)
          DCL::uxaxdv("b",1,1)
          DCL::uyaxnm("l",tick1,tick2)
          DCL::uyaxnm("r",tick1,tick2)
          uz_set_params(before)
          if inf1 
            vy3 = [vymax, vymax+vwidth*2.25, vymax]
            vx3 = [vxmax, (vxmax+vxmin)/2, vxmin]
            DCL.sgtnzv(vx3,vy3,ipat1)
            DCL.sgplzv(vx3,vy3,1,3)
          end
          if inf0 
            vy3 = [vymin, vymin-vwidth*2.25, vymin]
            vx3 = [vxmax, (vxmax+vxmin)/2, vxmin]
            DCL.sgtnzv(vx3,vy3,ipat0)
            DCL.sgplzv(vx3,vy3,1,3)
          end
        else
          if voff > 0
            before = uz_set_params( {'labelxt'=>true, 'labelxb'=>false} )
          else
            before = uz_set_params( {'labelxt'=>false, 'labelxb'=>true} )
          end
          DCL::uzrset("rsizet1",vwidth)
          DCL::uxaxnm("t",tick1,tick2)
          DCL::uxaxnm("b",tick1,tick2)
          DCL::uyaxdv("l",1,1)
          DCL::uyaxdv("r",1,1)
          uz_set_params(before)
          if inf0 
            vx3 = [vxmin, vxmin-vwidth*2.25, vxmin]
            vy3 = [vymax, (vymax+vymin)/2, vymin]
            DCL.sgtnzv(vx3,vy3,ipat0)
            DCL.sgplzv(vx3,vy3,1,3)
          end
          if inf1 
            vx3 = [vxmax, vxmax+vwidth*2.25, vxmax]
            vy3 = [vymax, (vymax+vymin)/2, vymin]
            DCL.sgtnzv(vx3,vy3,ipat1)
            DCL.sgplzv(vx3,vy3,1,3)
          end
        end

        DCL::uzrset("rsizel1",rsizel)
        DCL::uzrset("rsizet1",rsizet)

        DCL::uzlset("labelxb",xb)
        DCL::uzlset("labelyl",yl)

        DCL::grsvpt(vx1,vx2,vy1,vy2)

      end
      DCL::sgpset("lclip", true) if lclip
    end

  end

  ####################################################################
  ####################################################################
  ####################################################################

  module GGraph
    
    class << self
      ## private methods in the module
      def __shorten_path(str,maxlen)
        return str if str.length <= maxlen
        astr = str.split(/\//)
        while(str.length > maxlen)
          astr.length!=2 ? at = (astr.length)/2 : at = 0
          astr.delete_at( at )
          (a = astr.dup)[at, 0] = '...'    # insert(at,'...') if Ruby>=1.8
          str = a.join('/')
        end
        str
      end
      private :__shorten_path
    end

    module_function

    def margin_info(program=nil, data_source=nil, char_height=nil, date=nil,
                    xl=0.0, xr=0.0, yb=nil, yt=0.0)

      program = File.expand_path($0)       if !program
      if date
        program = __shorten_path(program,77) + '  ' + Date.today.to_s
      else
        program = __shorten_path(program,99)
        if date.nil?
          program = program + '  ' + Date.today.to_s if program.length < 77
        end
      end
      if !data_source
        data_source = Dir.pwd
        data_source = '' if data_source == program.sub(/\/[^\/]+$/,'')
      end                
      data_source = __shorten_path(data_source,99)
      sz = 1.0/( program.length + data_source.length )
      char_height = [0.008, sz].min        if !char_height
      yb = 2.0 * char_height               if !yb

      DCL.slmgn(xl, xr, yb, yt)
      DCL.slsttl(program,     'b', -1.0, -1.0, char_height, 1)
      DCL.slsttl(data_source, 'b',  1.0, -1.0, char_height, 2)
      nil
    end

    def color_bar(*args)
      DCLExt.color_bar(*args)
    end

    def title(string)
      if string
        if map_trn?
          v = DCL::sgqvpt
          txhgt = DCL.uzpget('rsizec2')     # rsizec2: larger text
          vx = (v[0]+v[1])/2
          vy = v[3] + (0.5 + DCL.uzpget('pad1')) * txhgt
          DCL::sgtxzr(vx , vy, string, txhgt, 0, 0, 3)
        else
          DCL.uxmttl('t',string,0.0) 
        end
      end
      nil
    end

    def annotate(str_ary)
      charsize = 0.7 * DCL.uzpget('rsizec1')
      dvx = 0.01
      dvy = charsize*1.5
      raise TypeError,"Array expected" if ! str_ary.is_a?(Array)
      vxmin,vxmax,vymin,vymax = DCL.sgqvpt
      vx = vxmax + dvx
      vy = vymax - charsize/2
      str_ary.each{|str|
        DCL::sgtxzr(vx,vy,str,charsize,0,-1,1)
        vy -= dvy
      }
      nil
    end

    @@fig = Misc::KeywordOptAutoHelp.new(
      ['new_frame',true,      'whether to define a new frame by DCL.grfrm (otherwise, DCL.grfig is called)'],
      ['itr',     1,     'coordinate transformation number'],
      ['viewport',[0.2,0.8,0.2,0.8], '[vxmin, vxmax, vymin, vymax]'],
      ['window',  nil,               '(for itr<10,>50) [uxmin, uxmax, uymin, uymax]'],
      ['xreverse','positive:down,units:hPa', '(for itr<10,>50) Assign max value to UXMIN and min value to UXMAX if condition is satisfied (nil:never, true:always, String: when an attibute has the value specified ("key:value,key:value,..")'],
      ['yreverse','positive:down,units:hPa', '(for itr<10,>50) Assign max value to UYMIN and min value to UYMAX if condition is satisfied (nil:never, true:always, String: when an attibute has the value specified ("key:value,key:value,..")'],
      # for 5<=itr<=7 (Rectangular Curvilinear Coordinates)
      ['similar', nil, '3-element float array for similar transformation in a rectangular curvilinear coordinate, which is fed in DCL:grssim:[simfac,vxoff,vyoff],  where simfac and [vxoff,vyoff] represent scaling factor and origin shift, respectively.'],
      # for 10<=itr<=50 (map projection):
      ['map_axis', nil, '(for all map projections) 3-element float array to be fed in DCL::umscnt: [uxc, uxy, rot], where [uxc, uyc] represents the tangential point (or the pole at top side for cylindrical projections), and rot represents the rotation angle. If nil, internally determined. (units: degrees)'],
      ['map_radius', nil, '(for itr>=20: conical/azimuhal map projections) raidus around the tangential point. (units: degrees)'],
      ['map_lat_range', nil, '(for itr<20: cylindrical map projections) latitudinal range to draw the map. By default (nil), the map is drawn between 75E and 75S. (units: degres)']
    )
    def set_fig(options)
      @@fig.set(options)
    end

    @@next_fig = nil
    def next_fig(options)
      if options.is_a?(Hash)
        @@next_fig = options
      else
        raise TypeError,"Hash expected"
      end
      nil
    end

    def map_trn?
      itr = DCL.sgqtrn
      case itr
      when 1..9,51..99
        false
      else
        true
      end
    end

    def sim_trn?
      itr = DCL.sgqtrn
      case itr
      when 5..7
        true
      else
        false
      end
    end

    def fig(xax=nil, yax=nil, options=nil)

      # xax and yax are needed (i.e. Axis objects) if not map projection

      if @@next_fig
        options = ( options ? @@next_fig.update(options) : @@next_fig )
        @@next_fig = nil
      end
      opts = @@fig.interpret(options)

      if opts['new_frame']
        DCL.grfrm
      else
        DCL.grfig
      end
      DCL.grsvpt(*opts['viewport'])

      DCL.grstrn(opts['itr'])

      case opts['itr']
      when 1..9,51..99
        # all but for map projection

        raise(ArgumentError, "xax and yax must be provided") if !xax or !yax

        if !opts['window']
          if (xreverse=opts['xreverse']).is_a?(String)
            atts = opts['xreverse'].split(',').collect{|v| v.split(':')}
            xreverse = false
            atts.each{|key,val| 
              xreverse = ( xax.get_att(key) == val )
              break if xreverse
            }
          end
          if (yreverse=opts['yreverse']).is_a?(String)
            atts = opts['yreverse'].split(',').collect{|v| v.split(':')}
            yreverse = false
            atts.each{|key,val| 
              yreverse = ( yax.get_att(key) == val )
              break if yreverse
            }
          end
          if xreverse
            xrange = [ xax.max, xax.min ]
          else
            xrange = [ xax.min, xax.max ]
          end
          if yreverse
            yrange = [ yax.max, yax.min ]
          else
            yrange = [ yax.min, yax.max ]
          end
          window=[xrange[0], xrange[1], yrange[0], yrange[1]]
        else
          window=opts['window']
        end

	unless opts['itr']==5 then
	  DCL.grswnd(*window)
	else
          if ! opts['similar'] 
	    vxmin,vxmax,vymin,vymax=DCL.sgqvpt
            simfac = (vymax-vymin)/(xrange[1]*2)
	    similar = [simfac,0.0,0.0]
	  else
	    similar=opts['similar'] 
	  end
          DCL.grssim(*similar)
	end

      when 10..15 
        map_axis = opts['map_axis'] || [180.0, 0.0, 0.0]
        map_lat_range = opts['map_lat_range'] || [-75,75] 
        if map_lat_range.is_a?(Range)
          map_lat_range = [map_lat_range.first, map_lat_range.end]
        end
        DCL::umscnt( *map_axis )
        case ( map_axis[2] - map_axis[1] ) % 360
        when 0
          tyrange = map_lat_range
        when 180
          tyrange = [ -map_lat_range[1], -map_lat_range[0] ]
        else
          tyrange = [ -75, 75 ]      # latange is ignored
        end
        DCL::grstxy(-180.0,180.0,tyrange[0],tyrange[1])
        sv = DCL.umpget('lglobe')
        DCL.umpset('lglobe', true)
        DCL::umpfit
        DCL.umpset('lglobe', sv)
      when 20..33 
        map_axis = opts['map_axis'] || [180.0, 90.0, 0.0]
        map_radius = opts['map_radius'] || 70.0
        DCL::umscnt( *map_axis )
        case opts['itr']
        when 31
          v = opts['viewport']
          vptsize = [ v[1]-v[0], v[3]-v[2] ].min
          DCL::grssim( vptsize*0.25/Math::tan(0.5*Math::PI*map_radius/180.0), 0.0, 0.0 )
        when 22
          v = opts['viewport']
          vptsize = [ v[1]-v[0], v[3]-v[2] ].min
          DCL::grssim( vptsize*0.35*(90.0/map_radius), 0.0, 0.0 )
          DCL::grstxy(-180.0, 180.0, 90.0-map_radius, 90.0)
        else
          DCL::grstxy(-180.0, 180.0, 90.0-map_radius, 90.0)
        end
        sv = DCL.umpget('lglobe')
        DCL.umpset('lglobe', true)
        DCL::umpfit
        DCL.umpset('lglobe', sv)
      else
        raise "unsupported transformation number: #{opts['itr']}"
      end

      DCL.grstrf
      nil
    end

    @@axes = Misc::KeywordOptAutoHelp.new(
      ['xside',  'tb',  'Where to draw xaxes (combination of t, b and u)'],
      ['yside',  'lr',  'Where to draw yaxes (combination of l, r and u)'],
      ['xtitle',  nil,  'Title of x axis (if nil, internally determined)'],
      ['ytitle',  nil,  'Title of y axis (if nil, internally determined)'],
      ['xunits',  nil,  'Units of x axis (if nil, internally determined)'],
      ['yunits',  nil,  'Units of y axis (if nil, internally determined)'],
      ['xtickint',  nil,
            'Interval of x axis tickmark (if nil, internally determined)'],
      ['ytickint',  nil,
            'Interval of y axis tickmark (if nil, internally determined)'],
      ['xlabelint',  nil,
               'Interval of x axis label (if nil, internally determined)'],
      ['ylabelint',  nil,
               'Interval of y axis label (if nil, internally determined)']
    )
    def set_axes(options)
      @@axes.set(options)
    end

    @@next_axes = nil
    def next_axes(options)
      if options.is_a?(Hash)
        @@next_axes = options
      else
        raise TypeError,"Hash expected"
      end
      nil
    end

    def axes(xax=nil, yax=nil, options=nil)
      if @@next_axes
        options = ( options ? @@next_axes.update(options) : @@next_axes )
        @@next_axes = nil
      end
      opts = @@axes.interpret(options)
      if opts['xside'].length > 0
        xai = _get_axinfo(xax)
        DCL.uscset('cxttl', (opts['xtitle'] || xai['title']) )
        DCL.uscset('cxunit',(opts['xunits'] || xai['units']) )
        DCL.uspset('dxt', opts['xtickint'])  if(opts['xtickint'])
        DCL.uspset('dxl', opts['xlabelint']) if(opts['xlabelint'])
        opts['xside'].split('').each{|s|     # scan('.') also works
          DCL.usxaxs(s)
        }
      end
      if opts['yside'].length > 0
        yai = _get_axinfo(yax)
        DCL.uscset('cyttl', (opts['ytitle'] || yai['title']) )
        DCL.uscset('cyunit', (un=(opts['yunits'] || yai['units'])) )
        DCL.uspset('dxt', opts['ytickint'])  if(opts['ytickint'])
        DCL.uspset('dxl', opts['ylabelint']) if(opts['ylabelint'])
        opts['yside'].split('').each{|s|   # scan('.') also works
          DCL.usyaxs(s)
          if s=='l' && un && un != ''
            DCL.uzpset('roffxt', DCL.uzpget('rsizec1')*1.5 )
          end
        }
      end
      nil
    end

    def _get_axinfo(vary)
      if vary.nil?
        hash = {
                'title'=>'',
                'units'=>''
               }
      else
        raise "Not 1D" if vary.rank!=1
        hash = {
                'title'=>(vary.get_att('long_name') || vary.name), #.gsub('_','' )
                'units'=>(vary.get_att('units') || '')             #.gsub('_',' ')
               }
      end
      hash 
    end

    def polar_coordinate_boundaries(xax=nil,yax=nil,yax_cyclic=nil)
    
      slln=DCL.sgpget('LLNINT') ; slgc=DCL.sgpget('LGCINT')
      DCL.sgpset('LLNINT',true) ; DCL.sgpset('LGCINT',true)

      xmin=xax.min ; xmax=xax.max ; ymin=yax.min ; ymax=yax.max
      unless yax_cyclic
        DCL.sgplzu([xmin,xmax],[ymin,ymin],1,5)
        DCL.sgplzu([xmin,xmax],[ymax,ymax],1,5)
      end

      DCL.sgplzu([xmin,xmin],[ymin,ymax],1,5)
      DCL.sgplzu([xmax,xmax],[ymin,ymax],1,5)

      DCL.sgpset('LLNINT',slln) ; DCL.sgpset('LGCINT',slgc)
      nil
    end

    @@map = Misc::KeywordOptAutoHelp.new(
      ['lim',          true,  'draw map lim (t or f)'],
      ['grid',         true,  'draw map grid (t or f)'],
      ['vpt_boundary', false,  'draw viewport boundaries (f, t or 1,2,3.., representing the line width)'],
      ['wwd_boundary', false,  'draw worksation window boundaries (f, t or 1,2,3.., representing the line width)'],
      ['coast_world',  false,  'draw world coast lines (t or f)'],
      ['border_world', false,  'draw nation borders (t or f)'],
      ['plate_world',  false,  'draw plate boundaries (t or f)'],
      ['state_usa',  false,    'draw state boundaries of US (t or f)'],
      ['coast_japan',  false,  'draw japanese coast lines (t or f)'],
      ['pref_japan',   false,  'draw japanese prefecture boundaries (t or f)']
    )

    def set_map(options)
      @@map.set(options)
    end

    @@next_map = nil
    def next_map(options)
      if options.is_a?(Hash)
        @@next_map = options
      else
        raise TypeError,"Hash expected"
      end
      nil
    end

    def map(options=nil)
      if @@next_map
        options = ( options ? @@next_map.update(options) : @@next_map )
        @@next_map = nil
      end
      opts = @@map.interpret(options)
      DCL.umplim if opts['lim']
      DCL.umpgrd if opts['grid']
      if opts['vpt_boundary']
        if opts['vpt_boundary'].is_a?(Integer)
          idx = opts['vpt_boundary']
        else
          idx = 1
        end
        DCL.slpvpr( idx )
      end
      if opts['wwd_boundary']
        if opts['wwd_boundary'].is_a?(Integer)
          idx = opts['wwd_boundary']
        else
          idx = 1
        end
        DCL.slpwwr( idx )
      end
      DCL.umpmap('coast_world') if opts['coast_world']
      DCL.umpmap('border_world') if opts['border_world']
      DCL.umpmap('plate_world') if opts['plate_world']
      DCL.umpmap('state_usa') if opts['state_usa']
      DCL.umpmap('coast_japan') if opts['coast_japan']
      DCL.umpmap('pref_japan') if opts['pref_japan']
      nil
    end

    def line(gphys, newframe=true, options=nil)
      if newframe!=true && newframe!=false
        raise ArgumentError, "2nd arg (newframe) must be true or false"
      end
      if ! defined?(@@line_options)
        @@line_options = Misc::KeywordOptAutoHelp.new(
          ['title', nil, 'Title of the figure(if nil, internally determined)'],
          ['annotate', true, 'if false, do not put texts on the right margin even when newframe==true'],
          ['exchange', false, 'whether to exchange x and y axes'],
          ['index', 1, 'line/mark index'],
          ['type', 1, 'line type'],
          ['label', nil, 'if a String is given, it is shown as the label']
        )
      end
      opts = @@line_options.interpret(options)
      gp = gphys.first1D
      if !opts['exchange']
        x = gp.coord(0)
        y = gp.data
      else
        y = gp.coord(0)
        x = gp.data
      end
      if newframe
        fig(x, y)
        axes(x, y)
        title( (opts['title'] || gp.data.get_att('long_name')) )
        annotate(gp.lost_axes) if opts['annotate']
      end
      if opts['label']
        lcharbk = DCL.sgpget('lchar')
        DCL.sgpset('lchar',true)
        DCL.sgsplc(opts['label'])
      end
      DCL.uulinz(x.val, y.val, opts['type'], opts['index'])
      DCL.sgpset('lchar',lcharbk) if opts['label']
      nil
    end

    def mark(gphys, newframe=true, options=nil)
      if newframe!=true && newframe!=false
        raise ArgumentError, "2nd arg (newframe) must be true or false"
      end
      if ! defined?(@@mark_options)
        @@mark_options = Misc::KeywordOptAutoHelp.new(
          ['title', nil, 'Title of the figure(if nil, internally determined)'],
          ['annotate', true, 'if false, do not put texts on the right margin even when newframe==true'],
          ['exchange', false, 'whether to exchange x and y axes'],
          ['index', 1, 'mark index'],
          ['type', 2, 'mark type'],
          ['size', 0.01, 'marks size']
        )
      end
      opts = @@mark_options.interpret(options)
      gp = gphys.first1D
      if !opts['exchange']
        x = gp.coord(0)
        y = gp.data
      else
        y = gp.coord(0)
        x = gp.data
      end
      if newframe
        fig(x, y, opts['fig_params'])
        axes(x, y)
        title( (opts['title'] || gp.data.get_att('long_name')) )
        annotate(gp.lost_axes) if opts['annotate']
      end
      DCL.uumrkz(x.val, y.val, opts['type'], opts['index'], opts['size'])
      nil
    end

    @@linear_contour_options =  Misc::KeywordOptAutoHelp.new(
      ['min',      nil,       'minimum contour value'],
      ['max',      nil,       'maximum contour value'],
      ['nlev',     nil,       'number of levels'],
      ['interval', nil,       'contour interval'],
      ['nozero',   nil,       'delete zero contour'],
      ['coloring', false,     'set color contours with ud_coloring'],
      ['clr_min',  13,        '(if coloring) minimum color id'],
      ['clr_max',  100,       '(if coloring) maximum color id']
    )
    def set_linear_contour_options(options)
      @@linear_contour_options.set(options)
    end
    @@next_linear_contour_options = nil
    def next_linear_contour_options(options)
      if options.is_a?(Hash)
        @@next_linear_contour_options = options
      else
        raise TypeError,"Hash expected"
      end
      nil
    end

    @@contour_levels =  Misc::KeywordOptAutoHelp.new(
      ['levels',   nil,   'contour levels (Array/NArray of Numeric)'],
      ['index',    nil,   '(if levels) line index(es) (Array/NArray of integers, Integer, or nil)'],
      ['line_type',nil,   '(if levels) line type(s) (Array/NArray of integers, Integer, or nil)'],
      ['label',    nil,   '(if levels) contour label(s) (Array/NArray of String, String, true, false, nil). nil is recommended.'],
      ['label_height',nil,'(if levels) label height(s) (Array/NArray of Numeric, Numeric, or nil). nil is recommended.']
    )
    @@set_contour_levels=false
    def set_contour_levels(options)
      opts = @@contour_levels.interpret(options)
      _set_contour_levels_(opts)
    end
    def _set_contour_levels_(opts)
      raise ArgumentError, "'levels' must be explicitly set" if !opts['levels']
      DCLExt.ud_set_contour(opts['levels'],opts['index'],opts['line_type'],
                  opts['label'],opts['label_height'])
      @@set_contour_levels=true
      nil
    end

    def clear_contour_levels
      @@set_contour_levels=false
      DCL.udiclv   # this may not be needed
      nil
    end

    def contour(gphys, newframe=true, options=nil)
      if newframe!=true && newframe!=false
        raise ArgumentError, "2nd arg (newframe) must be true or false"
      end
      if ! defined?(@@contour_options)
        @@contour_options = Misc::KeywordOptAutoHelp.new(
          ['title', nil, 'Title of the figure(if nil, internally determined)'],
          ['annotate', true, 'if false, do not put texts on the right margin even when newframe==true'],
          ['transpose', false, 'if true, exchange x and y axes'],
          @@linear_contour_options,
          @@contour_levels
        )
      end
      if @@next_linear_contour_options
        options = ( options ? @@next_linear_contour_options.update(options) : 
                              @@next_linear_contour_options )
        @@next_linear_contour_options = nil
      end
      opts = @@contour_options.interpret(options)
      gp = gphys.first2D
      gp = gp.transpose(1,0) if opts['transpose']
      xax = gp.coord(0)
      yax = gp.coord(1)
      if newframe
        fig(xax, yax)
      end
      if sim_trn? \
        && gp.coord(1).get_att('topology') == "circular" \
	&& modulo=gp.coord(1).get_att('modulo')
          gp = gp.cyclic_ext(1, modulo)       # cyclic extention with 2nd dim.
          yax = gp.coord(1)       
	  yax_cyclic = true
      else
      	  yax_cyclic = false
      end
      if map_trn?
        gp = gp.cyclic_ext(0, 360.0)  #cyclic extention with lon if appropriate
        xax = gp.coord(0)
      end
      if newframe
        if map_trn?
          map
	elsif sim_trn?
	  polar_coordinate_boundaries(xax,yax,yax_cyclic)
        else
          axes(xax, yax)
        end
        title( (opts['title'] || gp.data.get_att('long_name')) )
        annotate(gp.lost_axes) if opts['annotate']
      end
      if opts['levels']
        backup = @@set_contour_levels
        _set_contour_levels_(opts)
        @@set_contour_levels = backup
      elsif !@@set_contour_levels
        DCLExt.ud_set_linear_levs(gp.data.val, opts)
      end
      DCL.uwsgxa(xax.val)
      DCL.uwsgya(yax.val)
      DCL.udcntz(gp.data.val)
      nil
    end

    @@linear_tone_options =  Misc::KeywordOptAutoHelp.new(
      ['min',      nil,       'minimum contour value'],
      ['max',      nil,       'maximum contour value'],
      ['nlev',     nil,       'number of levels'],
      ['interval', nil,       'contour interval']
    )
    def set_linear_tone_options(options)
      @@linear_tone_options.set(options)
    end
    @@next_linear_tone_options = nil
    def next_linear_tone_options(options)
      if options.is_a?(Hash)
        @@next_linear_tone_options = options
      else
        raise TypeError,"Hash expected"
      end
      nil
    end

    @@tone_levels =  Misc::KeywordOptAutoHelp.new(
      ['levels',   nil,   'tone levels  (Array/NArray of Numeric). Works together with patterns'],
      ['patterns', nil,   'tone patters (Array/NArray of Numeric). Works together with levels']
    )
    @@set_tone_levels=false
    def set_tone_levels(options)
      opts = @@tone_levels.interpret(options)
      _set_tone_levels_(opts)
    end
    def _set_tone_levels_(opts)
      if !opts['levels'] && !opts['patterns']
      end
      if opts['levels']
        levels = opts['levels']
      else
        raise ArgumentError,"'levels' must be explicitly set"
      end
      if opts['patterns']
        patterns = opts['patterns']
      else
        nlev = levels.length - 1
        itpat = DCL.ueiget('itpat')
        iclr1 = DCL.ueiget('icolor1')
        iclr2 = DCL.ueiget('icolor2')
        patterns = (0...nlev).collect{|i|
          (((iclr2-iclr1)/(nlev-1.0)*i+iclr1).round) * 1000 + itpat
        }
      end
      DCLExt.ue_set_tone(levels,patterns)
      @@set_tone_levels=true
      nil
    end
    def clear_tone_levels
      @@set_tone_levels=false
      nil
    end

    def tone(gphys, newframe=true, options=nil)
      if newframe!=true && newframe!=false
        raise ArgumentError, "2nd arg (newframe) must be true or false"
      end
      if ! defined?(@@tone_options)
        @@tone_options = Misc::KeywordOptAutoHelp.new(
          ['title', nil, 'Title of the figure(if nil, internally determined)'],
          ['annotate', true, 'if false, do not put texts on the right margin even when newframe==true'],
          ['ltone', true, 'Same as udpack parameter ltone'],
          ['tonf', false, 'Use DCL.uetonf instead of DCL.uetone'],
          ['tonc', false, 'Use DCL.uetonc instead of DCL.uetone'],
          ['clr_min', nil,  'if an integer (in 10..99) is specified, used as the mimimum color number. (the same can be done by setting the uepack parameter "icolor1")'],
          ['clr_max', nil,  'if an integer (in 10..99) is specified, used as the maximum color number. (the same can be done by setting the uepack parameter "icolor2")'],
          ['transpose', false, 'if true, exchange x and y axes'],
          @@linear_tone_options,
          @@tone_levels
        )
      end
      opts = @@tone_options.interpret(options)

      if opts['clr_min']
        icolor1_bak = DCL.uepget('icolor1')
        DCL.uepset('icolor1', opts['clr_min'])
      end
      if opts['clr_max']
        icolor2_bak = DCL.uepget('icolor2')
        DCL.uepset('icolor2', opts['clr_max'])
      end
      gp = gphys.first2D
      gp = gp.transpose(1,0) if opts['transpose']
      xax = gp.coord(0)
      yax = gp.coord(1)
      if newframe
        fig(xax, yax)
      end
      if sim_trn? \
        && gp.coord(1).get_att('topology') == "circular" \
	&& modulo=gp.coord(1).get_att('modulo')
          gp = gp.cyclic_ext(1, modulo)       # cyclic extention with 2nd dim.
          yax = gp.coord(1)       
	  yax_cyclic = true
      else
	  yax_cyclic = false
      end
      if map_trn?
        gp = gp.cyclic_ext(0, 360.0)  #cyclic extention with lon if appropriate
        xax = gp.coord(0)
      end
      DCL.uwsgxa(xax.val)
      DCL.uwsgya(yax.val)
      if opts['levels'] 
        backup = @@set_tone_levels
        _set_tone_levels_(opts)
        @@set_tone_levels = backup
        if opts['tonf'] && opts['ltone']
          DCL.uetonf(gp.data.val)
        elsif opts['tonc'] && opts['ltone']
          DCL.uetonc(gp.data.val)
        else
          DCL.uetone(gp.data.val)
        end
      elsif opts['patterns']
        raise "option 'patterns' is not effective unless 'levels' is specified"
      elsif !@@set_tone_levels && opts['ltone']
        DCL.ueitlv
        DCLExt.ue_set_linear_levs(gp.data.val, opts)
        if opts['tonf']
          DCL.uetonf(gp.data.val)
        elsif opts['tonc']
          DCL.uetonc(gp.data.val)
        else
          DCL.uetone(gp.data.val)
        end
      else
        DCL.ueitlv if !@@set_tone_levels
        if opts['tonf'] && opts['ltone']
          DCL.uetonf(gp.data.val)
        elsif opts['tonc'] && opts['ltone']
          DCL.uetonc(gp.data.val)
        else
          DCL.uetone(gp.data.val)
        end
      end
      if newframe
        # axes should be drawn after uetone
        if map_trn?
          map
        elsif sim_trn?
	  polar_coordinate_boundaries(xax,yax,yax_cyclic)
	else
          axes(xax, yax)
        end
        title( (opts['title'] || gp.data.get_att('long_name')) )
        annotate(gp.lost_axes) if opts['annotate']
      end
      DCL.uepset('icolor1', icolor1_bak) if opts['clr_min']
      DCL.uepset('icolor2', icolor2_bak) if opts['clr_max']
      nil
    end

    # < vector >

    def set_unit_vect_options(options)
      DCLExt.set_unit_vect_options(options)
    end
    def next_unit_vect_options(options)
      DCLExt.next_unit_vect_options(options)
    end

    def vector(fx, fy, newframe=true, options=nil)
      if newframe!=true && newframe!=false
        raise ArgumentError, "2nd arg (newframe) must be true or false"
      end
      if ! defined?(@@vector_options)
        @@vector_options = Misc::KeywordOptAutoHelp.new(
          ['title', nil, 'Title of the figure(if nil, internally determined)'],
          ['annotate', true, 'if false, do not put texts on the right margin even when newframe==true'],
          ['transpose', false, 'if true, exchange x and y axes'],
          ['flow_vect', true, 'If true, use DCLExt::flow_vect to draw vectors; otherwise, DCL::ugvect is used.'],
          ['xintv', 1, '(Effective only if flow_vect) interval of data sampling in x'],
          ['yintv', 1, '(Effective only if flow_vect) interval of data sampling in y'],
          ['factor', 1.0, '(Effective only if flow_vect) scaling factor to strech/reduce the arrow lengths'],
          ['unit_vect', false, 'Show the unit vector'],
          ['max_unit_vect', false, '(Effective only if flow_vect && unit_vect) If true, use the maximum arrows to scale the unit vector; otherwise, normalize in V coordinate.']
        )
      end
      opts = @@vector_options.interpret(options)
      fx = fx.first2D.copy
      fy = fy.first2D.copy
      sh = fx.shape
      if sh != fy.shape
        raise ArgumentError, "shapes of fx and fy do not agree with each other"
      end
      fx = fx.transpose(1,0) if opts['transpose']
      fy = fy.transpose(1,0) if opts['transpose']
      if ((xi=opts['xintv']) >= 2)
        idx = NArray.int(sh[0]/xi).indgen!*xi     # [0,xi,2*xi,..]
        fx = fx[idx, true]
        fy = fy[idx, true]
      end
      if ((yi=opts['xintv']) >= 2)
        idx = NArray.int(sh[1]/yi).indgen!*yi     # [0,yi,2*yi,..]
        fx = fx[true, idx]
        fy = fy[true, idx]
      end
      xax = fx.coord(0)
      yax = fy.coord(1)
      if newframe
        fig(xax, yax)
      end
      if map_trn?
        fx = fx.cyclic_ext(0, 360.0)  #cyclic extention with lon if appropriate
        fy = fy.cyclic_ext(0, 360.0)  #cyclic extention with lon if appropriate
        xax = fx.coord(0)
      end
      if newframe
        if opts['title']
          ttl = opts['title']
        else
          fxnm = fx.data.get_att('long_name') || fx.name
          fynm = fy.data.get_att('long_name') || fy.name
          ttl =   '('+fxnm+','+fynm+')'
        end
        if map_trn?
          map
        else
          axes(xax, yax)
        end
        title(ttl)
        annotate(fx.lost_axes) if opts['annotate']
      end
      DCL.uwsgxa(xax.val)
      DCL.uwsgya(yax.val)
      if opts['flow_vect']
        uninfo = DCLExt.flow_vect(fx.val, fy.val, opts['factor'] )
        if opts['unit_vect']
          if opts['max_unit_vect']
            DCLExt.unit_vect(*uninfo)
          else
            DCLExt.unit_vect(*uninfo[0..1])
          end
        end
      else
        before=DCLExt.ug_set_params({'lunit'=>true}) if opts['unit_vect']
        DCL.ugvect(fx.val, fy.val)
        DCLExt.ug_set_params(before) if opts['unit_vect']
      end
      nil
    end


  end

end

if $0 == __FILE__
  include NumRu

  # < read command line option if any >

  if ARGV.length == 1
    iws = ARGV[0].to_i
  else
    iws = 1
  end

  # < show default parameters >
  xdummy = ydummy = nil
  begin
    print "** GGraph::fig options **\n"
    GGraph.fig(xdummy, ydummy, 'help'=>true)
  rescue
  end
  begin
    print "** GGraph::axes options **\n"
    GGraph.axes(xdummy, ydummy, 'help'=>true)
  rescue
  end
  gp_dummy = nil
  begin
    print "** GGraph::line options **\n"
    GGraph.line(gp_dummy,true,'help'=>true)
  rescue
  end
  begin
    print "** GGraph::mark options **\n"
    GGraph.mark(gp_dummy,true,'help'=>true)
  rescue
  end
  begin
    print "** GGraph::contour options **\n"
    GGraph.contour(gp_dummy,true,'help'=>true)
  rescue
  end
  begin
    print "** GGraph::tone options **\n"
    GGraph.tone(gp_dummy,true,'help'=>true)
  rescue
  end

  #< graphic test / demonstration >
  file = '../../testdata/T.jan.nc'
  gphys = GPhys::NetCDF_IO.open(file, 'T')
  DCL.gropn(iws)
  DCL.sldiv('y',2,2)
  DCL.sgpset('lcntl', false)
  DCL.sgpset('lfull',true)
  DCL.sgpset('lfprop',true)
  DCL.uzfact(0.9)
  #/ graph 1 /
  GGraph.set_fig('viewport'=>[0.25,0.75,0.12,0.62])
  GGraph.line(gphys.cut(true,35,true).average(0), true)
  #/ graph 2 /
  GGraph.next_fig('itr'=>2)
  GGraph.next_axes('yunits'=>'','xunits'=>'')
  GGraph.line(gphys.cut(true,35,true).average(0), true, 
              'exchange'=>true, 'index'=>3, 'title'=>'TEMERATURE', 'annotate'=>false)
  GGraph.mark(gphys.cut(true,35,true).average(0), false, 
              'exchange'=>true, 'type'=>3)
  #/ graph 3 /
  GGraph.contour(gphys)
  #/ graph 4 /
  GGraph.next_fig('itr'=>2)
  GGraph.contour(gphys.cut(135,true,true))
  #/ graph 5 /
  GGraph.set_axes('xunits'=>'', 'yunits'=>'')
  GGraph.contour(gphys,true, 'min'=>0, 'coloring'=>true)
  #/ graph 6 /
  GGraph.set_fig('viewport'=>[0.2,0.8,0.15,0.55])
  GGraph.contour(gphys,true, 'nozero'=>true)
  #/ graph 7 /
  GGraph.contour(gphys,true, 'min'=>10, 'int'=>3)
  DCL.udpset('lmsg',false)
  GGraph.contour(gphys,false, 'max'=>-10, 'int'=>3)
  DCL.udpset('lmsg',true)
  #/ graph 8 /
  GGraph.set_contour_levels('levels'=>[-10,-5,0,5,10],'index'=>[3,1],
                        'line_type'=>2)
  GGraph.contour(gphys)
  GGraph.clear_contour_levels
  #/ graph 9 /
  GGraph.contour(gphys, true, 'levels'=>[0,10,20], 'index'=>3)
  #/ graph 10 /
  GGraph.set_linear_contour_options('nlev'=>24)
  GGraph.next_linear_contour_options('coloring'=>true)
  GGraph.contour(gphys)
  #/ graph 11 /
  GGraph.tone(gphys, true, 'min'=>0)
  sv = DCLExt.ud_set_params('indxmj'=>1,'indxmn'=>1)
  GGraph.contour(gphys, false)
  DCLExt.ud_set_params(sv)
  GGraph.color_bar
  #/ graph 12 /
  GGraph.tone(gphys, true, 'ltone'=>false)
  GGraph.contour(gphys, false)
  #/ graph 13 /
  GGraph.next_fig('itr'=>3)
  GGraph.contour(gphys[1..-1,true,true])
  #/ graph 14 /
  GGraph.tone(gphys, true, 'levels'=>[-20,-10,0,10,20],'patterns'=>[12999,30999,45999,65999,75999,85999])
  GGraph.color_bar('vlength'=>0.25,'landscape'=>true)
  #/ graph 15 /
  GGraph.tone(gphys, true, 'levels'=>[-20,-10,0,10,20],'patterns'=>[30999,45999,65999,75999,85999])
  DCLExt.color_bar('vlength'=>0.25, 'left'=>true)
  #/ graph 16 /
  GGraph.tone(gphys, true, 'levels'=>[-20,-10,0,10,20],'patterns'=>[30999,45999,65999,75999])
  #DCLExt.color_bar('vlength'=>0.25, 'left'=>true)
  DCLExt.color_bar('vlength'=>0.25, 'levels'=>[-20,-10,0,10,20],'patterns'=>[30999,45999,65999,75999])
  #/ graph 17 /
  GGraph.set_fig('viewport'=>[0.3,0.7,0.1,0.6])
  GGraph.contour(gphys,true, 'min'=>0, 'coloring'=>true, 'transpose'=>true)
  GGraph.tone(gphys, false, 'levels'=>[10,20], 'patterns'=>[80999], 'transpose'=>true)
  #/ graph 18 /
  GGraph.set_fig('viewport'=>[0.25,0.75,0.15,0.6])
  GGraph.vector(gphys+10,gphys+10,true,
        {'unit_vect'=>true, 'xintv'=>2, 'yintv'=>2})
  #/ graph 19 /
  GGraph.next_unit_vect_options({'inplace'=>false,'vxuloc'=>0.17,'vyuloc'=>0.07})
  GGraph.vector(gphys+10,gphys-10,true,
        {'unit_vect'=>true, 'max_unit_vect'=>true, 'xintv'=>2, 'yintv'=>2})
  #/ graph 20 /
  GGraph.vector(gphys+10,gphys+10,true,
        {'unit_vect'=>true, 'flow_vect'=>false, 'xintv'=>2, 'yintv'=>2})

  #GGraph.color_bar('help'=>true)
  #/ graph 21 /
  GGraph.set_fig('viewport'=>[0.2,0.8,0.15,0.55])
  GGraph.tone(gphys, true, 'levels'=>[-16,-4,-1,0,1,4,16],'patterns'=>[12999,21999,30999,45999,65999,75999,85999,95999])
  GGraph.color_bar('constwidth'=>true,'vlength'=>0.6,'landscape'=>true,'labelintv'=>1)
  GGraph.color_bar('constwidth'=>true,'vlength'=>0.35,'index'=>1)

  #/ graph 22 /

  # -- mercator -- 
  GGraph.next_fig('itr'=>11)
  GGraph.set_map('coast_world'=>true)
  GGraph.tone(gphys, true)
  GGraph.color_bar

  #/ graph 23 /

  # -- orthographic -- 
  GGraph.set_fig('viewport'=>[0.25,0.75,0.1,0.6])
  GGraph.next_fig('itr'=>30,'map_axis'=>[135,60,0],'map_radius'=>60)
  GGraph.tone(gphys, true)
  sv = DCLExt.ud_set_params('indxmj'=>991,'indxmn'=>991)
  GGraph.contour(gphys, false)
  DCLExt.ud_set_params(sv)
  GGraph.color_bar('landscape'=>true) # ,'vlength'=>0.6

  #/ graph 24 /

  # -- polar stero -- 
  GGraph.next_fig('itr'=>31)
  DCL.sgpset('lclip',true)
  GGraph.next_map('vpt_boundary'=>3)
  GGraph.tone(gphys, true)
  GGraph.color_bar

  DCL.grcls

end
