(* ;;-*-LISP-*- KEEP EMACS HAPPY ********************************
*
*     PLOT
*
****************************************************************)

(* RECORD CURVE (BRUSH COLOR CLOSE KNOTS DASHED)
          (RECORD BRUSH (BRWIDTH . SHAPE)))

(RECORD PLOTINFO (XAXIS YAXIS PATHS CURVES))

(RECORD AXIS (MIN MAX INCREMENT LABEL FONT))

(RECORD TRANSLATE (MU BU MV BV))


(* ****************************************************************
*
*     The simplest way to call PLOT is to pass it a
*(function or a path) and a window.
*
****************************************************************)

(DEFEXPR (PLOT.FN FN XMIN XMAX (OPTIONAL NPOINTS 100) 
		  (OPTIONAL WINDOW (CREATEW)))
  (PROG (YMIN YMAX K X Y PATH XAXIS YAXIS PLOTINFO)
    (SETQ YMIN MAX.FLOAT)
    (SETQ YMAX MIN.FLOAT)
    (FOR I FROM 0 TO NPOINTS
     DO (SETQ K (/$ I NPOINTS))
     (SETQ X (+$ (x$ (-$ 1.0 K) XMIN) (x$ K XMAX)))
     (SETQ Y (APPLY* FN X))
     (SETQ YMIN (MIN YMIN Y))
     (SETQ YMAX (MAX YMAX Y))
     (PUSH PATH 
	   (CREATE POSITION
		   XCOORD ← X
		   YCOORD ← Y)))
    (SETQ PATH (DREVERSE PATH))
    (SETQ XAXIS
	  (CREATE AXIS
		  MIN ← XMIN
		  MAX ← XMAX))
    (SETQ YAXIS
	  (CREATE AXIS
		  MIN ← YMIN
		  MAX ← YMAX))
    (SETQ PLOTINFO
	  (CREATE PLOTINFO
		  XAXIS ← XAXIS
		  YAXIS ← YAXIS
		  PATHS ← (LIST PATH)))
    (PLOT PLOTINFO NIL WINDOW)
))

(DEFEXPR (PLOT.PATH PATH (OPTIONAL WINDOW (CREATEW)))
  (PROG (XMIN XMAX YMIN YMAX XAXIS YAXIS PLOTINFO)
    (SETQ YMIN MAX.FLOAT)
    (SETQ YMAX MIN.FLOAT)
    (SETQ XMIN (POSITION.XCOORD (CAR PATH)))
    (FOR POINT IN PATH
     DO (SETQ YMIN (MIN (POSITION.YCOORD POINT) YMIN))
     (SETQ YMAX (MAX (POSITION.YCOORD POINT) YMAX))
     FINALLY (SETQ XMAX (POSITION.XCOORD POINT)))
    (SETQ XAXIS
	  (CREATE AXIS
		  MIN ← XMIN
		  MAX ← XMAX))
    (SETQ YAXIS
	  (CREATE AXIS
		  MIN ← YMIN
		  MAX ← YMAX))
    (SETQ PLOTINFO
	  (CREATE PLOTINFO
		  XAXIS ← XAXIS
		  YAXIS ← YAXIS
		  PATHS ← (LIST PATH)))
    (PLOT PLOTINFO NIL WINDOW)
))

(DEFEXPR (PLOT PLOTINFO REGION (OPTIONAL WINDOW (CREATEW)))
  (PROG (XAXIS YAXIS TRANSLATE)
    (COND ((NULL REGION)
	   (* Default REGION. *)
	   (SETQ REGION
		 (CREATE REGION
			 LEFT ← 0
			 BOTTOM ← 0
			 WIDTH ← (WINDOWPROP WINDOW 'WIDTH)
			 HEIGHT ← (WINDOWPROP WINDOW 'HEIGHT)))))
    (* Plot PLOTINFO. *)
    (SETQ XAXIS (PLOTINFO.XAXIS PLOTINFO))
    (SETQ YAXIS (PLOTINFO.YAXIS PLOTINFO))
    (SETQ TRANSLATE (\PLOT.TRANSLATE XAXIS YAXIS REGION))
    (\PLOT.XAXIS XAXIS YAXIS TRANSLATE WINDOW)
    (\PLOT.YAXIS XAXIS YAXIS TRANSLATE WINDOW)
    (FOR PATH IN (PLOTINFO.PATHS PLOTINFO)
     DO (\PLOT.ADDPATH PATH TRANSLATE WINDOW))
    (FOR CURVE IN (PLOTINFO.CURVES PLOTINFO)
     DO (\PLOT.ADDCURVE CURVE TRANSLATE WINDOW))
    (* Return TRANSLATE in case user would like to call
       \PLOT.ADDPATH or \PLOT.ADDCURVE directly. *)
    (RETURN TRANSLATE)
))

(DEFEXPR (\PLOT.XAXIS XAXIS YAXIS TRANSLATE WINDOW)
  (PROG (XMIN XMAX XINC YMIN YMAX 
	 UMIN UMAX VORIGIN UORIGIN U)
    (SETQ XMAX (AXIS.MAX XAXIS))
    (SETQ XMIN (AXIS.MIN XAXIS))
    (SETQ YMAX (AXIS.MAX YAXIS))
    (SETQ YMIN (AXIS.MIN YAXIS))
    (COND ((>$ (x$ YMIN YMAX) 0.0)(RETURN)))
    (* Coordinates of axis endpoints *)
    (SETQ UMIN (\PLOT.ROUND (\PLOT.TRANSLATE.XCOORD XMIN TRANSLATE)))
    (SETQ UMAX (\PLOT.ROUND (\PLOT.TRANSLATE.XCOORD XMAX TRANSLATE)))
    (SETQ UORIGIN (\PLOT.ROUND (\PLOT.TRANSLATE.XCOORD 0.0 TRANSLATE)))
    (SETQ VORIGIN (\PLOT.ROUND (\PLOT.TRANSLATE.YCOORD 0.0 TRANSLATE)))
    (* Extremes *)
    (\PLOT.TEXT XMAX
	       (AXIS.FONT XAXIS)
	       (CREATE POSITION
		       XCOORD ← UMAX
		       YCOORD ← VORIGIN)
	       'NNE
	       WINDOW)
    (\PLOT.TEXT 0
	       (AXIS.FONT XAXIS)
	       (CREATE POSITION
		       XCOORD ← UORIGIN
		       YCOORD ← VORIGIN)
	       'NW
	       WINDOW)
    (\PLOT.TEXT XMIN
	       (AXIS.FONT XAXIS)
	       (CREATE POSITION
		       XCOORD ← UMIN
		       YCOORD ← VORIGIN)
	       'NNW
	       WINDOW)
    (* Label .. do this before drawing line to avoid erasing *)
    (COND ((AXIS.LABEL XAXIS)
	   (\PLOT.TEXT (AXIS.LABEL XAXIS)
		       (AXIS.FONT XAXIS)
		       (CREATE POSITION
			       XCOORD ← UMAX
			       YCOORD ← VORIGIN)
		       'SSE
		       WINDOW)))
    (* Solid line *)
    (DRAWLINE UMIN VORIGIN UMAX VORIGIN NIL NIL WINDOW)
    (* Tic marks *)
    (SETQ XINC (AXIS.INCREMENT XAXIS))
    (COND ((NULL XINC)(RETURN)))
    (FOR I FROM (\PLOT.CEILING (/$ XMIN XINC))
     TO (\PLOT.FLOOR (/$ XMAX XINC))
     DO (SETQ U (\PLOT.TRANSLATE.XCOORD (x$ I XINC) TRANSLATE))
     (DRAWLINE U (1- VORIGIN) U (1+ VORIGIN) NIL NIL WINDOW))
))

(DEFEXPR (\PLOT.YAXIS XAXIS YAXIS TRANSLATE WINDOW)
  (PROG (XMIN XMAX YMIN YMAX YINC
	 MAX VMIN UORIGIN VORIGIN V)
    (SETQ XMAX (AXIS.MAX XAXIS))
    (SETQ XMIN (AXIS.MIN XAXIS))
    (SETQ YMAX (AXIS.MAX YAXIS))
    (SETQ YMIN (AXIS.MIN YAXIS))
    (COND ((>$ (x$ XMIN XMAX) 0.0)(RETURN)))
    (* Coordinates of axis endpoints *)
    (SETQ VMIN (\PLOT.ROUND (\PLOT.TRANSLATE.YCOORD YMIN TRANSLATE)))
    (SETQ VMAX (\PLOT.ROUND (\PLOT.TRANSLATE.YCOORD YMAX TRANSLATE)))
    (SETQ VORIGIN (\PLOT.ROUND (\PLOT.TRANSLATE.YCOORD 0.0 TRANSLATE)))
    (SETQ UORIGIN (\PLOT.ROUND (\PLOT.TRANSLATE.XCOORD 0.0 TRANSLATE)))
    (* Extremes *)
    (\PLOT.TEXT YMAX
	       (AXIS.FONT YAXIS)
	       (CREATE POSITION
		       XCOORD ← UORIGIN
		       YCOORD ← VMAX)
	       'ENE
	       WINDOW)
    (\PLOT.TEXT 0
	       (AXIS.FONT YAXIS)
	       (CREATE POSITION
		       XCOORD ← UORIGIN
		       YCOORD ← VORIGIN)
	       'SE
	       WINDOW)
    (\PLOT.TEXT YMIN
	       (AXIS.FONT YAXIS)
	       (CREATE POSITION
		       XCOORD ← UORIGIN
		       YCOORD ← VMIN)
	       'ESE
	       WINDOW)
    (* Label *)
    (COND ((AXIS.LABEL YAXIS)
	   (\PLOT.TEXT (AXIS.LABEL YAXIS)
		       (AXIS.FONT YAXIS)
		       (CREATE POSITION
			       XCOORD ← UORIGIN
			       YCOORD ← VMAX)
		       'WNW
		       WINDOW)))
    (* Solid line *)
    (DRAWLINE UORIGIN VMIN UORIGIN VMAX NIL NIL WINDOW)
    (* Tic marks *)
    (SETQ YINC (AXIS.INCREMENT YAXIS))
    (COND ((NULL YINC)(RETURN)))
    (FOR I FROM (\PLOT.CEILING (/$ YMIN YINC))
     TO (\PLOT.FLOOR (/$ YMAX YINC))
     DO (SETQ V (\PLOT.TRANSLATE.YCOORD (x$ I YINC) TRANSLATE))
     (DRAWLINE (1- UORIGIN) V (1+ UORIGIN) V NIL NIL WINDOW))
))

(DEFEXPR (\PLOT.TEXT TEXT FONT POS (OPTIONAL DIRECTION 'SW) WINDOW)
  (* DIRECTION = compass direction of point at POS wrt position of
     TEXT *)
  (PROG (XCOORD YCOORD WIDTH HEIGHT)
    (SETQ XCOORD (POSITION.XCOORD POS))
    (SETQ YCOORD (POSITION.YCOORD POS))
    (SETQ WIDTH (STRINGWIDTH TEXT FONT))
    (SETQ HEIGHT (FONTPROP FONT 'HEIGHT))
    (SELECTQ DIRECTION
      ((NW NNW N NNE NE)(SETQ YCOORD (-$ YCOORD (+$ HEIGHT 3.0))))
      ((WNW ENE)(SETQ YCOORD (-$ YCOORD HEIGHT)))
      ((W E)(SETQ YCOORD (-$ YCOORD (/$ HEIGHT 2.0))))
      ((WSW ESE)(* Do nothing *))
      ((SW SSW S SSE SE)(SETQ YCOORD (+$ YCOORD 3.0)))
      (SHOULDNT))
    (SELECTQ DIRECTION
      ((NE ENE E ESE SE)(SETQ XCOORD (-$ XCOORD (+$ WIDTH 3.0))))
      ((NNE SSE)(SETQ XCOORD (-$ XCOORD WIDTH)))
      ((N S)(SETQ XCOORD (-$ XCOORD (/$ WIDTH 2.0))))
      ((NNW SSW)(* Do nothing *))
      ((NW WNW W WSW SW)(SETQ XCOORD (+$ XCOORD 3.0)))
      (SHOULDNT))
    (MOVETO XCOORD YCOORD WINDOW)
    (DSPFONT FONT WINDOW)
    (PRIN1 TEXT WINDOW)
))

(DEFEXPR (\PLOT.ADDPATH POINTS TRANSLATE WINDOW)
  (PROG ()
    (SETQ POINTS (\PLOT.TRANSLATE.POINTS POINTS TRANSLATE))
    (FOR PT1 IN POINTS
     AS PT2 IN (CDR POINTS)
     WHEN PT2
     DO (DRAWBETWEEN PT1 PT2 NIL NIL WINDOW))
))

(DEFEXPR (\PLOT.ADDCURVE CURVE TRANSLATE WINDOW)
  (PROG (POINTS)
    (SETQ POINTS (\PLOT.TRANSLATE.POINTS (CURVE.KNOTS CURVE) TRANSLATE))
    (DRAWCURVE POINTS 
	       (CURVE.CLOSE CURVE)
	       (CURVE.BRUSH CURVE)
	       (CURVE.DASHED CURVE)
	       WINDOW)
))


(* ****************************************************************
*
*     TRANSLATION
*
****************************************************************)

(DEFEXPR (\PLOT.TRANSLATE XAXIS YAXIS REGION)
  (* User = XY system and WINDOW = UV system *)
  (* U=MU*X+BU and V=MV*Y+BV *)
  (PROG (XMAX XMIN YMAX YMIN DELTAX DELTAY
	 UMIN VMIN UMAX VMAX MU BU MV BV TRANSLATE)
    (* XY system. *)
    (SETQ XMAX (AXIS.MAX XAXIS))
    (SETQ XMIN (AXIS.MIN XAXIS))
    (SETQ YMAX (AXIS.MAX YAXIS))
    (SETQ YMIN (AXIS.MIN YAXIS))
    (SETQ DELTAX (-$ XMAX XMIN))
    (SETQ DELTAY (-$ YMAX YMIN))
    (* UV system. *)
    (SETQ UMIN (+$ (REGION.LEFT REGION) 0.5))
    (SETQ VMIN (+$ (REGION.BOTTOM REGION) 0.5))
    (SETQ UMAX (+$ (REGION.RIGHT REGION) 0.5))
    (SETQ VMAX (+$ (REGION.TOP REGION) 0.5))
    (* Calc TRANSLATE. *)
    (SETQ MU (/$ (-$ UMAX UMIN) DELTAX))
    (SETQ BU (/$ (-$ (x$ XMAX UMIN) (x$ XMIN UMAX)) DELTAX))
    (SETQ MV (/$ (-$ VMAX VMIN) DELTAY))
    (SETQ BV (/$ (-$ (x$ YMAX VMIN) (x$ YMIN VMAX)) DELTAY))
    (SETQ TRANSLATE
	  (CREATE TRANSLATE
		  MU ← MU
		  BU ← BU
		  MV ← MV
		  BV ← BV))
    (RETURN TRANSLATE)
))

(DEFEXPR (\PLOT.TRANSLATE.POINTS POINTS TRANSLATE)
  (FOR POINT IN POINTS
   COLLECT (\PLOT.TRANSLATE.POINT POINT TRANSLATE)))

(DEFEXPR (\PLOT.TRANSLATE.POINT POINT TRANSLATE)
  (CREATE POSITION
	  XCOORD ← (\PLOT.TRANSLATE.XCOORD
		    (POSITION.XCOORD POINT)
		    TRANSLATE)
	  YCOORD ← (\PLOT.TRANSLATE.YCOORD
		    (POSITION.YCOORD POINT)
		    TRANSLATE)))

(DEFEXPR (\PLOT.TRANSLATE.XCOORD X TRANSLATE)
  (+$ (x$ (TRANSLATE.MU TRANSLATE) X)
      (TRANSLATE.BU TRANSLATE)))

(DEFEXPR (\PLOT.TRANSLATE.YCOORD Y TRANSLATE)
  (+$ (x$ (TRANSLATE.MV TRANSLATE) Y)
      (TRANSLATE.BV TRANSLATE)))


(* ****************************************************************
*
*     ARITHMETIC FUNCTIONS
*
****************************************************************)

(DEFEXPR (\PLOT.ROUND N)
  (\PLOT.FLOOR (+$ N 0.5)))

(DEFEXPR (\PLOT.FLOOR N)
  (COND ((OR (=$ N (FIX N))(>=$ N 0.0))(FIX N))
	(T (1- (FIX N)))))

(DEFEXPR (\PLOT.CEILING N)
  (- (\PLOT.FLOOR (- N))))

(DEFEXPR (\PLOT.MAX NS)
  (* Safer than MAX, which can't have more than 80 args *)
  (PROG (ANSWER)
    (SETQ ANSWER MIN.FLOAT)
    (FOR N IN NS
     WHEN (>$ N ANSWER)
     DO (SETQ ANSWER N))
    (RETURN ANSWER)))

(DEFEXPR (\PLOT.MIN NS)
  (* Safer than MIN, which can't have more than 80 args *)
  (PROG (ANSWER)
    (SETQ ANSWER MAX.FLOAT)
    (FOR N IN NS
     WHEN (<$ N ANSWER)
     DO (SETQ ANSWER N))
    (RETURN ANSWER)))

STOP