;;; -*- Mode: Lisp;  Package: Graph;  Syntax: ZetaLisp;  Base: 10. -*-

;;;  ****************
;;;  Actually Drawing and erasing things.
;;;  ****************

;;;  You specify the two corners of the rectangle.  We do the rest.
;;; This method takes care of translation, etc.
	    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defmethod (graph-display-mixin :draw-graph-rectangle)
	    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	   (x1 y1 width height)
  (let ((tx1 (send self ':translate-x x1))
	(ty1 (send self ':translate-y y1))
	(wid (// width zoomx))
	(hei (// height zoomy)))
    (send self ':draw-lines tv:alu-ior
	  tx1 ty1
	  (+ tx1 wid -1) ty1
	  (+ tx1 wid -1) (+ ty1 hei -1)
	  tx1 (+ ty1 hei -1)
	  tx1 ty1)))

;;; Draw a parallelogram. Specify the center, the width, the height and the slant.

(defmethod (graph-display-mixin :draw-graph-parallelogram)
	   (x-center y-center width height slant)
  (setq x-center (send self ':translate-x x-center)
	y-center (send self ':translate-y y-center)
	width (// width zoomx)
	height (// height zoomy))
  (let* ((offset (fixr (// (* height (cos slant) zoomy) (* (sin slant) zoomx))))
	 (width2 (// width 2.))
	 (height2 (// height 2.))
	 (upper (- y-center height2))
	 (lower (+ y-center height2))
	 (xleft (- x-center width2))
	 (xmidleft (+ xleft offset))
	 (xright (+ x-center width2))
	 (xmidright (- xright offset)))
    (send self ':draw-lines tv:alu-ior
	  xmidleft upper
	  xright upper
	  xmidright lower
	  xleft lower
	  xmidleft upper)))

;;; Erase a parallelogram. We are passed the center and size and slant of the 
;;; parallelogram (slant is ignored for now).
(defmethod (graph-display-mixin :erase-graph-parallelogram)
	   (x-center y-center width height ignore)
  (setq x-center (send self ':translate-x x-center)
	y-center (send self ':translate-y y-center)
	width (// width zoomx)
	height (// height zoomy))
  (send self ':draw-rectangle
	(1+ width)
	(1+ height)
	(- x-center (// width 2.))
	(- y-center (// height 2.))
	tv:alu-andca))

;;; Draw an ellipse. We first translate the world coordinates to screen coordiates.
;;; Then we check the special (and simplier) case that the ellipse is really a circle.
(defmethod (graph-display-mixin :draw-graph-ellipse)
	   (x y x-radius y-radius)
  (let ((x (send self ':translate-x x))
	(y (send self ':translate-y y))
	(x-radius (// x-radius zoomx))
	(y-radius (// y-radius zoomy)))
    (if (equal x-radius y-radius)
	(send self ':draw-circle x y x-radius tv:alu-ior)
	(send self ':draw-ellipse x y x-radius y-radius tv:alu-ior))))

;;; Erase an ellipse. First translate to screen coordiantes. Then just blank out the
;;; bounding box for the ellipse.
(defmethod (graph-display-mixin :erase-graph-ellipse)
	   (x y x-radius y-radius)
  (let ((x (send self ':translate-x x))
	(y (send self ':translate-y y))
	(x-radius (1+ (// x-radius zoomx)))
	(y-radius (1+ (// y-radius zoomy))))
    (send self ':draw-rectangle (+ x-radius x-radius) (+ y-radius y-radius)
	  (- x x-radius) (- y y-radius) tv:alu-andca)))

(defmethod (graph-display-mixin :draw-graph-line)
	    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	   (x1 y1 x2 y2)
  (send self ':draw-line
	(send self ':translate-x x1) (send self ':translate-y y1)
	(send self ':translate-x x2) (send self ':translate-y y2)
	tv:alu-ior))

(defmethod (graph-display-mixin :draw-graph-sausage)
	   (x y min-radius max-radius)
  (setq x (send self ':translate-x x)
	y (send self ':translate-y y)
	min-radius (// min-radius zoomy)
	max-radius (// max-radius zoomx))
  (let ((left-center-x (- x max-radius (- min-radius)))
	(right-center-x (+ x max-radius (- min-radius)))
	(upper-y (- y min-radius))
	(lower-y (+ y min-radius))
	(pi2 (// *pi* 2)))
    (send self ':draw-circular-arc left-center-x y min-radius pi2
	  (- pi2) tv:alu-ior)
    (send self ':draw-circular-arc right-center-x y min-radius (- pi2)
	  pi2 tv:alu-ior)
    (send self ':draw-line left-center-x upper-y right-center-x upper-y tv:alu-ior)
    (send self ':draw-line left-center-x lower-y right-center-x lower-y tv:alu-ior)))



       	    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defmethod (graph-display-mixin :erase-graph-line)
	    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	   (x1 y1 x2 y2)
  (send self ':draw-line
	(send self ':translate-x x1) (send self ':translate-y y1)
	(send self ':translate-x x2) (send self ':translate-y y2)
	tv:alu-andca))

(defmethod (graph-display-mixin :draw-graph-lines)
	   (&rest x-y-points)
  (lexpr-funcall self ':draw-lines tv:alu-ior
		 (loop for (x y . rest) on x-y-points by 'cddr
		       collect (send self ':translate-x x)
		       collect (send self ':translate-y y))))

(defmethod (graph-display-mixin :erase-graph-lines)
	   (&rest x-y-points)
  (lexpr-funcall self ':draw-lines tv:alu-andca
		 (loop for (x y . rest) on x-y-points by 'cddr
		       collect (send self ':translate-x x)
		       collect (send self ':translate-y y))))

(defmethod (graph-display-mixin :draw-dotted-graph-lines)
	   (draw-p &rest x-y-points)
  (loop for (x1 y1 x2 y2 . rest) on x-y-points by 'cddr
	until (null x2)
	do (send self ':draw-dashed-line
		 (clip (send self ':translate-x x1) 0 tv:width)
		 (clip (send self ':translate-y y1) 0 tv:height)
		 (clip (send self ':translate-x x2) 0 tv:width)
		 (clip (send self ':translate-y y2) 0 tv:height)
		 (if draw-p tv:alu-ior tv:alu-andca) 4 nil 0 2)))

(defmethod (graph-display-mixin :draw-dashed-graph-lines)
	   (draw-p &rest x-y-points)
  (loop for (x1 y1 x2 y2 . rest) on x-y-points by 'cddr
	until (null x2)
	do (send self ':draw-dashed-line
		 (clip (send self ':translate-x x1) 0 tv:width)
		 (clip (send self ':translate-y y1) 0 tv:height)
		 (clip (send self ':translate-x x2) 0 tv:width)
		 (clip (send self ':translate-y y2) 0 tv:height)
		 (if draw-p tv:alu-ior tv:alu-andca) 18. nil 0 12.)))

;;; Latest, greatest feature.  Now you can draw splines!!
(defmethod (graph-display-mixin :draw-spline-graph-line)
	   (draw-p width &rest x-y-points)
  ;; Reset the temporary arrays
  (setf (fill-pointer *spline-x-array*) 0)
  (setf (fill-pointer *spline-y-array*) 0)
  ;; Fill the arrays
  (loop for (x1 y1 . rest) on x-y-points by 'cddr
	until (null x1)
	do (array-push-extend *spline-x-array*
			      (send self ':translate-x x1))
	do (array-push-extend *spline-y-array*
			      (send self ':translate-y y1)))
    (send self ':draw-cubic-spline
	  *spline-x-array* *spline-y-array*
	  10. width (if draw-p tv:alu-ior tv:alu-andca)))


;;;  You specify the top-left corner of the box, and its width and
;;; height.
	    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defmethod (graph-display-mixin :draw-white-box)
	    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	   (x y width height)
  (send self ':draw-rectangle
	(1+ (// width zoomx)) (1+ (// height zoomy))
	(send self ':translate-x x) (send self ':translate-y y)
	tv:alu-andca))


;;;  Draws a string on a given Y level, centered between the two X coordinates.
;;; Under no circumstances will the string be printed outside the x boundaries.
;;; If necessary, the font will be changed, or the string will be truncated
;;; to insure this.  Fortunately, the Lisp Machine provides a primitive for
;;; this operation.  Here, we need only translate the coordinates.
;;;
;;;  The constants here are only used in this method.  Probably no need to 
;;; hide these elsewhere.  Don't fool with them.  Susan and John spent almost
;;; an hour trying to find the correct values.  The constants prevent text from
;;; wrapping around screen boundaries, appearing on the other side.
	    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defmethod (graph-display-mixin :draw-centered-graph-string)
	   ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	   (string left-x right-x y)
  (setq left-x (send self ':translate-x left-x)
	right-x (send self ':translate-x right-x)
	y (send self ':translate-y y))
  (when (<= 0 y (- tv:height (+ 20. (font-char-height tv:current-font))))
    (send self ':display-centered-string string
	  (min (max 0 left-x) (- tv:width 10.))
	  (min (max 0 right-x) (- tv:width 10.))
	  y)))


(defmethod (graph-display-mixin :draw-underlined-graph-string)
	   (string horizontal-p x1 y1 length &optional (leftp nil))
  (setq x1 (send self ':translate-x x1)
	y1 (send self ':translate-y y1)
	length (// length zoomx))
  (if leftp (setq x1 (- x1 length 10.)))
  (when ( 0 (- y1 (tv:font-baseline tv:current-font) 1.)
	   (- tv:height (+ 20. (font-char-height tv:current-font))))
    (send self ':string-out-explicit
	  string
	  (clip (+ x1 5.) 0 (- tv:width 10.))
	  (- y1 (tv:font-baseline tv:current-font) 1.)
	  (clip (+ x1 length 5.) 0 (- tv:width 10.))
	  #+LMI (+ y1 10)
	  tv:current-font tv:alu-ior)
    (unless horizontal-p
      (send self ':draw-dashed-line
	    (clip x1 0 tv:width)
	    y1
	    (clip (+ x1 length 5.) 0 tv:width)
	    y1 
	    tv:alu-ior 4.))))

(defmethod (graph-display-mixin :erase-underlined-graph-string)
	   (string horizontal-p x1 y1 length &optional (leftp nil))
  (setq x1 (send self ':translate-x x1)
	y1 (send self ':translate-y y1)
	length (// length zoomx))
  (if leftp (setq x1 (- x1 length 10.)))
  (when ( 0 (- y1 (tv:font-baseline tv:current-font) 1.)
	   (- tv:height (+ 20. (font-char-height tv:current-font))))
    (send self ':string-out-explicit
	  string
	  (clip (+ x1 5.) 0 (- tv:width 10.))
	  (- y1 (tv:font-baseline tv:current-font) 1.)
	  (clip (+ x1 length 5.) 0 (- tv:width 10.))
	  #+LMI (+ y1 10)
	  tv:current-font tv:alu-andca)
    (unless horizontal-p
      (send self ':draw-dashed-line
	    (clip x1 0 tv:width)
	    y1
	    (clip (+ x1 length 5.) 0 tv:width)
	    y1 
	    tv:alu-andca 4.))))
 
(defun clip (number min max)
  (min (max number min) max))
  
;;;draw a closed in triangle given the three endpoints
(defmethod (graph-display-mixin :draw-graph-triangle)
	   (x1 y1 x2 y2 x3 y3)
  (setq x1 (send self ':translate-x x1)
	y1 (send self ':translate-y y1)
	x2 (send self ':translate-x x2)
	y2 (send self ':translate-y y2)
	x3 (send self ':translate-x x3)
	y3 (send self ':translate-y y3))
  (send self ':draw-triangle x1 y1 x2 y2 x3 y3 tv:alu-ior))

;;erase a graph triangle.  Assume point 1 is left of point 2.
(defmethod (graph-display-mixin :erase-graph-triangle)
	   (x1 y1 x2 y2 x3 y3)
  (setq x1 (send self ':translate-x x1)
	y1 (send self ':translate-y y1)
	x2 (send self ':translate-x x2)
	y2 (send self ':translate-y y2)
	x3 (send self ':translate-x x3)
	y3 (send self ':translate-y y3))
  (send self ':draw-triangle x1 y1 x2 y2 x3 y3 tv:alu-andca))

;;;  ****************
;;;  Primitives to help drawing.
;;;  ****************

;;; Takes an x in the graphics world, and translates into screen coordinates.
	    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defmethod (graph-display-mixin :translate-x)
	    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	   (x)
  (fix (// (- x screen-x) zoomx)))

	    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defmethod (graph-display-mixin :translate-y)
	    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	   (y)
  (fix (// (- y screen-y) zoomy)))


(defmethod (graph-display-mixin :untranslate-x)
	   (x)
  (+ (* x zoomx) screen-x))

(defmethod (graph-display-mixin :untranslate-y)
	   (y)
  (+ (* y zoomy) screen-y))
