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 |