Full grammar used for parsing x,y data graphs

Note that the grammar is followed by definitions of some Lisp functions called during parsing, especially to deal with the details of arrow-points (arrowheads).


;;;-*- Mode: Lisp; Package: DUS -*-

;;; -----------------------------
;;;
;;; Nikos Nikolakis
;;;
;;; created:    Oct. 1994
;;; last update: 2/28/95
;;; -----------------------------

#|

   An effort to parse a diagram through a declarative approach.

   This grammar works fine for diagrams with non-overlapping axis.
   The grammar has been extended to handle special cases like
   annotations, key-specifications and other objects inside a diagram.
 
|#


(defvar *tiny* 35) ;  it specifies the coincide predicate (old value 20)
 
(setf 
 
 *grammar*    ;; grammar object
 
 (defgrammar
  
;;; ****************** < Image > ******************

   ( Image -> Set ( Diagram ) );



;;; ****************** < Diagram > ******************

   ( Diagram -> Axis  X-Axis  Y-Axis  Data
      (Axis)
      (X-Axis   ($ :axis Axis))
      (Y-Axis   ($ :axis Axis))
      (Data     
       ($ (difference*  (contain Axis '?) 
                        (union* X-Axis Y-Axis))
          :x-ln (ln (X-Line Axis))
          :y-ln (ln (Y-Line Axis))
          :axis Axis)) );
      
;;; ****************** < Axis > ******************
   ( Axis -> X-Line   Y-Line 
      (X-Line)
      (Y-Line    (touch (left-endpoint X-Line) '?)
       :constraints 
       (coincide (left-endpoint X-Line)  (bottom-endpoint Y-Line))) );
   ( X-Line -> Line 
      (:additional-slots   (left-endpoint . (left-endpoint (Line self)))
                           (ln            . (a-length (Line self))))
      (:constraints       
       (horizp Line)  (long Line)) );
   ( Y-Line -> Line 
      (:additional-slots   (bottom-endpoint . (bottom-endpoint (Line self)))
                           (ln              . (a-length (Line self))))
      (:constraints       
       (vertp Line)   (long Line)) );
     
   

;;; ******************  < X-AXIS >  ******************

    ( X-Axis -> X-Axis-Line   X-Ticks   X-Labels  X-Annotation   X-Text
       (:optional X-Annotation X-Text)
       (X-Axis-Line  (X-Line (get-val axis)))
       (X-Ticks      ($ :x-line X-Axis-Line)  :constraints (>= (size X-Ticks) 3))
       (X-Labels     (below '? X-Axis-Line :strip t))
       (X-Annotation (difference* (near X-Axis-Line 700) (union* X-Ticks X-Labels))) ; label-size
       (X-Text       (near&below '? X-Labels (* 2 (height X-Labels)))
                                  ) )
 
 
    ( X-Ticks ->  X-or < (Own-X-Ticks     (touch  '? (get-val X-Line)))
                         (Remote-X-Ticks  (below  '? (get-val X-Line) :strip t)) > );
    ( Own-X-Ticks ->  Set ( Line )
       (:element-constraints 
        (vertp Line) (short Line)  (not (polylinep Line))
        (< (distance (endpoints Line) (Line (get-val X-Line))) 
           (max (* (a-length Line) .25) *tiny*)))
       (:constraint horiz-aligned-gen)
       (:largest t) );
    ( Remote-X-Ticks ->  Set ( Line )
       (:element-constraints 
        (vertp Line) (short Line)  (not (polylinep Line)))
       (:constraint horiz-aligned-gen)
       (:largest t) );
       
             
    ( X-Labels -> Set ( Text ) 
      (:element-constraints  (horizp Text)  (numeric-textp Text) )
      (:constraint horiz-aligned-gen)
      (:largest t) );                          
   
    
    ( X-Text -> Text
       (Text
        :constraints (horizp Text)  (not (numeric-textp Text))
        :select (max (text-length Text))) );
  
    ;;; The arrow points to the X-line (=> we don't need the pointed-gos
    ( X-Annotation ->  Arrow Text-set  
       (:optional Text)
       (:non-sharable  Arrow  Text-set)
       (Arrow)
       (Text-set   (near  (leave-pt Arrow) (* 3.5 (ln Arrow)))) );
    
;;; ****************** < Y-AXIS > ******************

    ( Y-Axis -> Y-Axis-Line  Y-Ticks  Y-Labels  Y-Text 
      (:optional Y-Ticks Y-Labels Y-text)
      (Y-Axis-Line       (Y-Line (get-val axis))) 
      (Y-Ticks           ($ :y-line Y-Axis-Line))
      (Y-Labels          (left '? Y-Axis-Line :entirely nil :strip t))
      (Y-Text            ($ (left '? (or Y-Labels  Y-Axis-Line))
                            :dist (width Y-Labels))
       :select (min (distance (center Y-Axis-Line) (center Y-Text)))) );
    

    ( Y-Ticks ->  X-or < (Own-Y-Ticks     (touch '? (get-val Y-Line)))
                         (Remote-Y-Ticks  (left '? (get-val Y-Line) :strip t)
                           :filter   (some* [Y-Labels] :in (touch '? Remote-Y-Ticks) :gen t)) > );


    ( Own-Y-Ticks ->  Set ( Line )
       (:element-constraints
        (horizp Line)  (short Line) 
        (not (polylinep Line))
        (< (distance (endpoints Line) (Line (get-val Y-Line)))
           (max (* (a-length Line) .25) *tiny*)))
       (:constraint vert-aligned-gen)
       (:largest t) )


    ( Remote-Y-Ticks ->  Set ( Line )
       (:element-constraints 
        (horizp Line) (short Line)  (not (polylinep Line)))
       (:constraint vert-aligned-gen) );

   
    ( Y-Labels -> Set ( Text ) 
      (:element-constraints (horizp Text) (numeric-textp Text) )
      (:constraint vert-aligned-gen)
      (:largest t) );
   

   ( Y-Text -> Set ( Text )
     (:element-constraints (vertp Text))
     (:constraint (close-gen :how-near (get-val dist))) );
                 


;;; ****************** < DATA > ******************

    ( Data -> Data-lines  Data-points  Annotations Key-specifications 
           (:optional Data-lines Data-points Annotations Key-specifications)
           (Key-specifications)
           (Data-lines)
           (Annotations  (difference* context Data-lines))
           (Data-points  (difference* context  Data-lines)) );

     
;;; ****************** < DATA-LINES > ******************
 
    ( Data-lines  -> set ( Data-line )
      (:element-constraints
       (> (a-length data-line) (* .25 (get-val x-ln)))));


    ( Data-line  -> set ( Line ) 
       (:element-constraints
        (not (polylinep Line))
        (not (or (and (horizp line) (> (a-length line)  (* .7 (get-val x-ln)))
                      (< (distance (left-endpoint line)
                                   (Line (Y-Line (get-val axis)))) *tiny*))
                 (and (vertp  line) (> (a-length line)  (* .7 (get-val y-ln)))))))
       (:constraint connected) ) ;


    ( Data-line  -> set ( Curve ) (:constraint connected)) ;


;;; ****************** < Data-points > ******************

    ( Data-Points -> set ( Data-Cluster ) ); ={ circle-dp, traingle-dp, rectangle-dp }

    
    ; An example of a data-cluster is the set of all circle-data points
    ( Data-Cluster -> set ( Data-point ) (:constraint same-type) ) ;


    ( Data-point -> circle
      (:constraints  (whitep circle)) ); a white circle  


    ( Data-point -> circle 
      (:constraints  (blackp circle)) ); a black circle  


    ( Data-point -> polygon  ; a rectangle
      (:constraints  (rectangle? polygon) (small polygon)) ) ; 


    ( Data-point -> line_1  line_2  line_3     ; a triangle
       (line_1)
       (line_2       (touch '? (right-endpoint line_1))
         :constraints  
         (different line_1 line_2) 
         (coincide (right-endpoint line_1) (left-endpoint line_2)))
       (line_3       (touch '? (right-endpoint line_2))
        :constraints
        (different line_2 line_3) (different line_1 line_3)
        (coincide  (right-endpoint line_2) (right-endpoint line_3))
        (coincide  (left-endpoint  line_1) (left-endpoint  line_3))) );


    ( Data-point -> line_1 line_2 line_3 line_4 ; a diamond
        (line_1
          :constraints (not (horizp line_1))  (not (vertp line_1)))
        (line_2   (touch '? (right-endpoint line_1))
          :constraints
          (< (distance (right-endpoint line_1) (left-endpoint line_2)) *tiny*)
          (not (horizp line_2)) 
          (not (vertp line_2)))
        (line_3  (touch '? (right-endpoint (line_2 obj)))
          :constraints 
          (< (distance (right-endpoint line_2) (right-endpoint line_3)) *tiny*)
          (different line_3  line_2))
        (line_4   (touch '? (left-endpoint line_3))
          :constraints
          (< (distance (left-endpoint line_3) (right-endpoint line_4)) *tiny*)
          (< (distance (left-endpoint line_1) (left-endpoint line_4)) *tiny*)
          (parallelp line_1 line_3)
          (above (right-endpoint line_1) (right-endpoint line_4))) );

;;; ****************** < Annotations > ******************

   (Annotations -> set ( Annotation ) );


   ;;; When a new set-context is specified (in a normal rule), it overwrires the previous one.
   ;;; //  extend relation for set rules.
   ;;; We could have done: (gen (intersect old-context new-fun))).
 
   (Annotation ->  Text-set  Arrow  Pointed
     (:optional Text Pointed)
     (Arrow)
     (Text-set   (intersect* (near  (leave-pt Arrow) (* 2 (ln Arrow)))
                             context) 
                 ;:select (min  (distance (center Text)  (line (arrow-back Arrow))))
                )
    (Pointed  (near (pt (arrow-head arrow)) (* 2 (a-length (Line (Arrow-back Arrow)))))) );


   ( Text-set -> set ( Text )
     (:element-constraints 
      (horizp Text) (not (numeric-textp text))) );


   ( Pointed -> set ( Object ) );


   ( Object -> Data-point );


   ( Object -> Text );
   

;;; ****************** < Arrow > ******************

   ( Arrow-back -> Line 
     (:additional-slots
      (left-endpoint  . (left-endpoint  (Line  self)))
      (right-endpoint . (right-endpoint (Line  self)))
      (ln             . (a-length (Line self))))
     (:constraints  (not (polylinep line))) ); <---

   
   ( Arrow-head -> Line_1  Line_2  ; works OK
     (:additional-slots 
      (pt . (get-arrow-head-pts self 'common-pt))
      (p1 . (get-arrow-head-pts self 'p1))
      (p2 . (get-arrow-head-pts self 'p2)))
     (Line_1
      :constraints (short Line_1) (not (polylinep Line_1)))
     (Line_2  (touch Line_1 '?)
      :constraints
      (left  (center Line_1) (center Line_2)) ; impose ordering
      (short Line_2)
      (not (polylinep Line_2))
      (same-length  Line_1  Line_2 :ratio 1.2)
      (< (distance (endpoints Line_1) (endpoints Line_2)) 40)) );


   ( Arrow -> Arrow-back Arrow-head ; works OK
           (:non-sharable  Arrow-head)
     (:additional-slots 
      (leave-pt . (if (> (distance (pt (Arrow-head self)) (left-endpoint (Arrow-back self)))
                         (distance (pt (Arrow-head self)) (right-endpoint (Arrow-back self))))
                    (left-endpoint (Arrow-back self)) (right-endpoint (Arrow-back self))))
      (reach-pt . (pt (arrow-head self)))
      (ln             . (ln (Arrow-back self))))
     (arrow-head)
     (arrow-back  (intersect* (near (pt arrow-head) (height Arrow-head)) context) ;;; <-- put line_1
      :constraints
      (in-angle (pt Arrow-head) (p1 Arrow-head) (p2 Arrow-head) (left-endpoint Arrow-back))
      (in-angle (pt Arrow-head) (p1 Arrow-head) (p2 Arrow-head) (right-endpoint Arrow-back))
      (< (distance (pt Arrow-head) (endpoints (line Arrow-back)))
         (* 1.5 (a-length (line_1 arrow-head))))
      (same-angle (angle (left-endpoint Arrow-back) (pt Arrow-head))
                  (angle (right-endpoint Arrow-back) (pt Arrow-head)))
      :select 
      (max (a-length (line Arrow-back)))) );



;;; ************** < Key-specifications > *******************
         
        ( Key-specification -> Expl Data-Points 
            (:non-sharable Data-Points)
            (Expl  :constraints (>= (size Expl) 2))
            (Data-Points  (near Expl 300)) );

        ( Expl -> Set ( Text )           ;  Left-Aligned text
            (:element-constraints  
             (horizp Text) (not (numeric-textp Text)) (> (text-length Text) 3))
            (:constraint (vert-aligned-gen :left t)) );

        ( Expl -> Set ( Text )            ;  Right-Aligned Text
           (:element-constraints  
            (horizp Text) (not (numeric-textp Text)) (> (text-length Text) 3))
           (:constraint (vert-aligned-gen :right t)) );

    
        ( Key-specifications -> Set ( Key-specification ) );
     
   )
 )

;;; -------------------------------------------------------
;;;   Terminal generators for non-primitive terminals 
;;; -------------------------------------------------------
;;;
(defun gen-x-axis-line (context)
  (list context))
;;;
(defun gen-y-axis-line (context)
  (list context)) 
;;;
(defun coincide (x y &key (dist *tiny*))
  (< (distance x y) dist))
;;; -------------------------------------------------------
;;;         Non-supported relations 
;;; -------------------------------------------------------
(defmethod a-length ((data-line data-line))
  (data-line-length data-line))
(defmethod data-line-length ((data-line data-line))
  (apply #'+ 
         (mapcar 
          #'(lambda (x) (distance (left-endpoint x) (right-endpoint x)))
          (value data-line))))
(defun distance-pt-from-data-line (x dl)
  (distance-pt-from-lines x (value dl)))
;;;
(defmethod get-arrow-head-pts ((ob arrow-head) type)
  
  (let* ((line1-end-pts (make-array '(2) 
                                    :initial-contents (a-line-terminators (line_1 ob))))
         (line2-end-pts (make-array '(2) 
                                    :initial-contents (a-line-terminators (line_2 ob))))
         (distance-array (make-array '(2 2) :initial-element nil)))
      
    (dotimes (i 2)
    (dotimes (j 2)
      (setf (aref distance-array i j) 
            (distance (aref line1-end-pts i) (aref line2-end-pts j)))))
    
    (let (common-pt f-p1 f-p2 min id-1 id-2);
      ;; common-pt corresponds to the "corner" point of the arrow head
      
      (setf min (aref distance-array 0 0))
      
      (dotimes (i 2)
      (dotimes (j 2)
        (when (<= (aref distance-array i j) min) 
          (setf min (aref distance-array i j))
          (setf id-1 i)
          (setf id-2 j))))
      
    (setf common-pt (aref line1-end-pts id-1))
    (setf f-p1 (aref line1-end-pts (- 1 id-1)))
    (setf f-p2 (aref line2-end-pts (- 1 id-2)))
    
    (cond ((equal type 'all) (list common-pt f-p1 f-p2))
          ((equal type 'common-pt) common-pt)
          ((equal type 'p1) f-p1)
          (t f-p2)))))

Next: How the Grammar Rules Drive the Parsing Process

Back to Index page