' ******************************************************************
' Plotting routines : axes, grid, titles
' ******************************************************************

' ------------------------------------------------------------------
' Constants
' ------------------------------------------------------------------

#DEFINE LinScale 0
' Linear scale

#DEFINE LogScale 1
' Logarithmic scale

#DEFINE MaxDec 10
' Max. number of decimal places for axis labels

#DEFINE HorizGrid 1
' Horizontal grid

#DEFINE VertiGrid 2
' Vertical grid

#DEFINE BothGrid 3
' Horizontal + vertical

' ------------------------------------------------------------------
' Global variables
' ------------------------------------------------------------------

COMMON SHARED AS INTEGER CharWidth, CharHeight
' Character Width and Height (pixels)

COMMON SHARED AS INTEGER XminPixel, YminPixel, XmaxPixel, YmaxPixel
' Window coordinates in pixels

COMMON SHARED AS DOUBLE Xmin, Ymin, Xmax, Ymax
' Window coordinates in user units

COMMON SHARED AS DOUBLE XStep, YStep
' Steps on axes

COMMON SHARED AS INTEGER XScale, YScale
' Axis scales

COMMON SHARED AS DOUBLE FactX, FactY
' Scale factors

COMMON SHARED AS INTEGER FgCol
' Save foreground color

' ------------------------------------------------------------------
' External functions
' ------------------------------------------------------------------

DECLARE FUNCTION MathErr() AS INTEGER
' Returns the error code from the last procedure call

DECLARE FUNCTION Exp10(X AS DOUBLE) AS DOUBLE
' 10^X

DECLARE FUNCTION Log10(X AS DOUBLE) AS DOUBLE
' Decimal log

DECLARE FUNCTION Round(X           AS DOUBLE,_
                       Digit_Count AS INTEGER = 0) AS DOUBLE
' Rounds X to Digit_Count decimal places

DECLARE FUNCTION Ceil(X AS DOUBLE) AS INTEGER
' Ceiling function

DECLARE FUNCTION Floor(X AS DOUBLE) AS INTEGER
' Floor function

DECLARE FUNCTION Xpixel(X AS DOUBLE) AS INTEGER
' Converts user abscissa X to screen coordinate

DECLARE FUNCTION Ypixel(X AS DOUBLE) AS INTEGER
' Converts user ordinate Y to screen coordinate

DECLARE SUB Interval(      X1     AS DOUBLE,  _
                           X2     AS DOUBLE,  _
                           MinSub AS INTEGER, _
                           MaxSub AS INTEGER, _
                     BYREF XMin   AS DOUBLE,  _
                     BYREF XMax   AS DOUBLE,  _
                     BYREF XStep  AS DOUBLE)
' Computes an appropriate interval for a set of values

DECLARE FUNCTION FloatToStr(X    AS DOUBLE,       _
                            Ntot AS INTEGER = 10, _
                            Ndec AS INTEGER = 4,  _
                            E    AS INTEGER = 0) AS STRING
' Converts a floating point number to a string

DECLARE FUNCTION RemZero(S AS STRING) AS STRING
' Remove non-significant zeroes

' ******************************************************************

SUB AutoScale(      X()   AS DOUBLE,  _
                    Scale AS INTEGER, _
              BYREF XMin  AS DOUBLE,  _
              BYREF XMax  AS DOUBLE,  _
              BYREF XStep AS DOUBLE)
' ------------------------------------------------------------------
' Automatic scale determination
' ------------------------------------------------------------------

  DIM AS INTEGER I, Lb, Ub
  DIM AS DOUBLE  X1, X2

  Lb = LBOUND(X)
  Ub = UBOUND(X)

  ' Minimum and maximum of X()
  X1 = X(Lb) : X2 = X1
  FOR I = Lb TO Ub
    IF X(I) < X1 THEN 
      X1 = X(I)
    ELSEIF X(I) > X2 THEN
      X2 = X(I)
    END IF
  NEXT I

  ' Linear scale
  IF Scale = LinScale THEN
    Interval X1, X2, 2, 6, XMin, XMax, XStep
    EXIT SUB
  END IF

  ' Logarithmic scale
  XMin = 1D-3 : XMax = 1D3 : XStep = 10
  IF X1 <= 0 THEN EXIT SUB
  XMin = INT(Log10(X1)) : IF X1 < 1 THEN XMin = XMin - 1
  XMax = INT(Log10(X2)) : IF X2 > 1 THEN XMax = XMax + 1
  XMin = 10 ^ XMin
  XMax = 10 ^ XMax
END SUB

SUB SetOxScale(Scale  AS INTEGER = LinScale, _
               OxMin  AS DOUBLE  = 0, _
               OxMax  AS DOUBLE  = 1, _
               OxStep AS DOUBLE  = 0.2)
' ------------------------------------------------------------------
' Sets the scale on the Ox axis
' ------------------------------------------------------------------

  XScale = Scale
  SELECT CASE Scale
    CASE LinScale
      IF OxMin < OxMax THEN Xmin = OxMin : Xmax = OxMax
      IF OxStep > 0 THEN XStep = OxStep
    CASE LogScale
      IF OxMin > 0 AND OxMin < OxMax THEN 
        Xmin = Floor(Log10(OxMin))
        Xmax = Ceil(Log10(OxMax))
      END IF
      XStep = 1
  END SELECT
  FactX = (XmaxPixel - XminPixel) / (Xmax - Xmin)
END SUB

SUB SetOyScale(Scale  AS INTEGER = LinScale, _
               OyMin  AS DOUBLE  = 0, _
               OyMax  AS DOUBLE  = 1, _
               OyStep AS DOUBLE  = 0.2)
' ------------------------------------------------------------------
' Sets the scale on the Oy axis
' ------------------------------------------------------------------

  YScale = Scale
  SELECT CASE Scale
    CASE LinScale
      IF OyMin < OyMax THEN Ymin = OyMin : Ymax = OyMax
      IF OyStep > 0 THEN YStep = OyStep
    CASE LogScale
      IF OyMin > 0 AND OyMin < OyMax THEN 
        Ymin = Floor(Log10(OyMin))
        Ymax = Ceil(Log10(OyMax))
      END IF
      YStep = 1
  END SELECT
  FactY = (YmaxPixel - YminPixel) / (Ymax - Ymin)
END SUB

SUB PlotOxAxis(Y   AS DOUBLE  = 0, _
               Dec AS INTEGER = 4)
' ------------------------------------------------------------------
' Plots the Ox axis
' ------------------------------------------------------------------
' Input parameters : 
'
' Y   : Ordinate at which the axis is drawn
' Dec : Max. number of decimals for labels (0..MaxDec)
' ------------------------------------------------------------------

  DIM AS INTEGER I, J, N, NDec, Wp, Xp, Yp, Xs, Ys
  DIM AS DOUBLE  W, X
  DIM AS STRING  S

  IF XScale = LinScale THEN Yp = Ypixel(Y) ELSE Yp = Ypixel(Log10(Y))
  IF Dec < 0 OR Dec > MaxDec THEN NDec = MaxDec ELSE NDec = Dec

  LINE (XminPixel, Yp)-(XmaxPixel, Yp)

  N = (Xmax - Xmin) / XStep     ' Number of intervals

  FOR I = 0 TO N
    X = Xmin + I * XStep        ' Tick mark position

    Xp = Xpixel(X)

    LINE (Xp, Yp)-(Xp, Yp + 6)  ' Tick mark

    IF XScale = LogScale AND I < N THEN  
    ' Subdivisions between 2 tick marks on the log scale
      FOR J = 2 TO 9
        W = X + Log10(INT(J))
        Wp = Xpixel(W)
        LINE (Wp, Yp)-(Wp, Yp + 4)
      NEXT J
    END IF

    IF XScale = LogScale THEN W = 10 ^ X ELSE W = X  

    S = FloatToStr(W, NDec + 10, NDec, 0)  ' Label
    S = RemZero(TRIM(S))
    Xs = Xp - LEN(S) * CharWidth \ 2       ' Label coordinate
    Ys = Yp + 6 + CharHeight \ 2
    DRAW STRING (Xs, Ys), S, FgCol         ' Print label
  NEXT I
END SUB

SUB PlotOyAxis(X   AS DOUBLE  = 0, _
               Dec AS INTEGER = 4)
' ------------------------------------------------------------------
' Plots the Oy axis
' ------------------------------------------------------------------
' Input parameters : 
'
' X   : Abscissa at which the axis is drawn
' Dec : Max. number of decimals for labels (0..MaxDec)
' ------------------------------------------------------------------

  DIM AS INTEGER I, J, N, NDec, Wp, Xp, Yp, Xs, Ys
  DIM AS DOUBLE  W, Y
  DIM AS STRING  S

  IF YScale = LinScale THEN Xp = Xpixel(X) ELSE Xp = Xpixel(Log10(X))
  IF Dec < 0 OR Dec > MaxDec THEN NDec = MaxDec ELSE NDec = Dec

  LINE (Xp, YminPixel)-(Xp, YmaxPixel)

  N = (Ymax - Ymin) / YStep     ' Number of intervals

  FOR I = 0 TO N
    Y = Ymin + I * YStep        ' Tick mark position

    Yp = Ypixel(Y)
    LINE (Xp, Yp)-(Xp - 6, Yp)  ' Tick mark

    IF YScale = LogScale AND I < N THEN  
    ' Subdivisions between 2 tick marks on the log scale
      FOR J = 2 TO 9
        W = Y + Log10(INT(J))
        Wp = Ypixel(W)
        LINE (Xp, Wp)-(Xp - 4, Wp)
      NEXT J
    END IF

    IF YScale = LogScale THEN W = 10 ^ Y ELSE W = Y

    S = FloatToStr(W, NDec + 10, NDec, 0)   ' Label
    S = RemZero(TRIM(S))
    Xs = Xp - (LEN(S) + 1) * CharWidth - 4  ' Label coordinate
    Ys = Yp - CharHeight \ 2 + 1
    DRAW STRING (Xs, Ys), S, FgCol          ' Print label
  NEXT I
END SUB

SUB PlotGrid(Grid AS INTEGER = BothGrid)
' ------------------------------------------------------------------
' Plots a grid
' ------------------------------------------------------------------
' Input parameter : 
'
' Grid : Type of grid (HorizGrid, VertiGrid, BothGrid)
' ------------------------------------------------------------------

  DIM AS INTEGER I, N, X, Y

  IF Grid = VertiGrid OR Grid = BothGrid THEN
    N = (Xmax - Xmin) / XStep
    FOR I = 1 TO N - 1
      X = Xpixel(Xmin + I * XStep)
      LINE (X, YminPixel)-(X, YmaxPixel), , , &H8888
    NEXT I
  END IF

  IF Grid = HorizGrid OR Grid = BothGrid THEN
    N = (Ymax - Ymin) / YStep
    FOR I = 1 TO N - 1
      Y = Ypixel(Ymin + I * YStep)
      LINE (XminPixel, Y)-(XmaxPixel, Y), , , &H8888
    NEXT I
  END IF
END SUB

SUB WriteTitles(GraphTitle AS STRING, _
                XTitle     AS STRING, _
                YTitle     AS STRING)
' ------------------------------------------------------------------
' Writes graph and axes titles
' ------------------------------------------------------------------

  DIM AS INTEGER X, Y

  IF GraphTitle <> "" THEN
    X = (XminPixel + XmaxPixel - LEN(GraphTitle) * CharWidth) \ 2
    Y = YminPixel - 4 * CharHeight
    DRAW STRING (X, Y), GraphTitle, FgCol
  END IF

  IF XTitle <> "" THEN
    X = (XminPixel + XmaxPixel - LEN(XTitle) * CharWidth) \ 2
    Y = YmaxPixel + 3 * CharHeight
    DRAW STRING (X, Y), XTitle, FgCol
  END IF

  IF YTitle <> "" THEN
    X = XminPixel
    Y = YminPixel - 1.5 * CharHeight
    DRAW STRING (X, Y), YTitle, FgCol
  END IF
END SUB
