S-Dot Simple Example

You will note that I'm using the trivial-shell lisp library in order to pass commands through to graphviz.

(defun s-person-list ()

"Walk through the person list, returning the s-expression string desired for that person."

  (mapcar #'(lambda (x)
              `(node ((id ,(format nil "person~a" (id x)))
                      (label ,(name x))
                      (fontsize "9") (fontname "Arial"))))
          *person-list*))
(defun s-manager-edge-list ()

"Walk through the person list, and return the s-expression string for the edges"

   (mapcar #'(lambda (x)
         (unless (= (manager-id x) (id x))
           `(edge ((from ,(format nil "person~a" (manager-id x)))
             (to ,(format nil "person~a" (id x)))
             (fontsize "9") (fontname "Arial")))))
     *person-list*))
(defun reset-managers ()
  (setf *manager-list* nil)
  (mapcar #'(lambda (x) (when x (push x *manager-list*))) (s-manager-edge-list))
  (mapcar #'(lambda (x) (push x *manager-list*)) (s-person-list))
  (push '((rankdir "TB")) *manager-list*) (push 'graph *manager-list*)
  (let ((dot-file-name "/home/ss/projects/s-dot-sc/images/s-dot.dot")
      (graphic-file-name
       "/home/sc/projects/s-dot-sc/images/s-dot-manager-test.png"))
  (with-open-file (stream dot-file-name
                          :direction :output :if-exists :supersede)
    (s-dot->dot stream *manager-list*))
  (trivial-shell:shell-command
   (format nil "dot -Tpng -o ~a ~a" graphic-file-name dot-file-name))))

Distinguishing Data

Right now we have very little data to differentiate our team members from each other except for who reports to who. Without using distinguishing data, there is no way for Graphviz (or any other diagramming software) to visually distinguish either. We could take a couple of approaches here.

One method would be to create new subclasses of person and create new node methods for those new subclasses, e.g. depending on whether the team member had another team member reporting to them. These methods would be used in the s-person-list function against the person-list.

A second method would be to just write in conditional attributes into the graph-object-node methods

Under either method, we will need to know whether or not someone has subordinates.

OK, we already built a helper function for that: (has-subordinates-p (x))

Let's try the conditional attribute method first.

(defun s-person-list ()
  "Walk through the person list. returning the s-expression
string desired for that person."
  (mapcar #'(lambda (x)
              `(node ((id ,(format nil "person~a" (id x)))
                      (label ,(name x))
                      (fontsize "9") (fontname "Arial")
                      (shape ,(if (has-subordinates-p x) "octagon" "box"))
                      (style "filled")
                      (color ,(if (has-subordinates-p x)
                                 "#a05220" "yellow")))))
          *person-list*))

Graph the Project Structure

For diagramming the person to project relationship, we reuse the s-person-list function and need similar functions for the project list and

the person-project relationship list.

(defun s-project-list ()
   (mapcar #'(lambda (x)
               `(node ((id ,(format nil " project~a" (id x))) (label ,(name x))
                       (href "http://www.cnn.com") (fontsize "9")
                       (fontname "Arial")))) *project-list*))
(defun s-project-edge-list ()
   (mapcar #'(lambda (x) `(edge ((from ,(format nil " person~a" (person-id x)))
               (to ,(format nil " project~a" (project-id x)))
               (fontsize "9") (fontname "Arial")
               (label
          ,(format nil "~a" (percent-allocated x))))))
                *person-project-list*))
(defvar *total-list* ())
(mapcar #'(lambda (x) (push x *total-list*)) (s-project-edge-list))
(mapcar #'(lambda (x) (push x *total-list*)) (s-project-list))
(mapcar #'(lambda (x) (push x *total-list*)) (s-person-list))
(push '((rankdir "LR")) *total-list*)
(push 'graph *total-list*)
(let ((dot-file-name "/home/sc/projects/s-dot-sc/images/s-dot-project.dot")
      (graphic-file-name
       "/home/sc/projects/s-dot-sc/images/s-dot-project-test.png"))
  (with-open-file (stream dot-file-name
                          :direction :output :if-exists :supersede)
    (s-dot->dot stream *total-list*))
  (trivial-shell:shell-command
   (format nil "dot -Tpng -o ~a ~a" graphic-file-name dot-file-name)))