-
Notifications
You must be signed in to change notification settings - Fork 0
/
tree.lisp
1253 lines (1122 loc) · 49.8 KB
/
tree.lisp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
(in-package :gk-trees)
(defparameter pretty-tree-horiz-space 1) ; this should be at least 0
(defparameter pretty-tree-height-mult 2) ; this should be at least 1
(defparameter pretty-tree-vertical-space 1) ; this should be at least 0
(defparameter pretty-tree-width-mult 2) ; this should be at least 1
;;; unicode set
(defparameter pretty-tree-horiz-char #\─)
(defparameter pretty-tree-vert-char #\│)
(defparameter pretty-tree-vert-end-char #\│)
(defparameter pretty-tree-left-corner-char #\╭)
(defparameter pretty-tree-right-corner-char #\╮)
(defparameter pretty-tree-top-corner-char #\╭)
(defparameter pretty-tree-bottom-corner-char #\╰)
(defparameter pretty-tree-down-char #\┬)
(defparameter pretty-tree-out-char #\├)
(defparameter pretty-tree-hnode-char #\┤)
(defparameter pretty-tree-vnode-char #\┴)
(defparameter pretty-tree-cross-char #\┼)
;;; ascii set
;; (defconst pretty-tree-horiz-char #\-)
;; (defconst pretty-tree-vert-char #\|)
;; (defconst pretty-tree-vert-end-char #\|)
;; (defconst pretty-tree-left-corner-char #\+)
;; (defconst pretty-tree-right-corner-char #\+)
;; (defconst pretty-tree-node-char #\^)
(defparameter tree-default-weight 1)
(defclass tree ()
((label
:initarg :label
:initform nil
:accessor label
:documentation "The label of this vertex.")
(children
:initarg :children
:initform (list)
:accessor children
:documentation "The children of this vertex.")
(edge-weights
:initarg :edge-weights
:initform (list)
:accessor edge-weights
:documentation "The edge-weights of the outgoing edges.")
(nleaves
:initarg :nleaves
:accessor nleaves
:documentation "The number of leaves.")))
(defmethod initialize-instance :after ((tree tree) &key)
;; ensure that there are enough edge weights
(cond
((numberp (edge-weights tree))
(setf (edge-weights tree) (make-list (length (children tree))
:initial-element (edge-weights tree))))
((consp (edge-weights tree))
(when (< (length (edge-weights tree)) (length (children tree)))
(setf (edge-weights tree)
(nconc (edge-weights tree)
(make-list (- (length (children tree))
(length (edge-weights tree)))
:initial-element
tree-default-weight)))))
(t
(setf (edge-weights tree) (make-list (length (children tree))
:initial-element
tree-default-weight))))
;; set nleaves
(setf (nleaves tree) (length (leafset tree))))
;;; methods for other lisp objects
(defmethod label (tree)
(format nil "~A" tree))
(defmethod children (tree)
nil)
(defmethod edge-weights (tree)
nil)
(defun treep (object)
(typep object 'tree))
(defun make-tree (tree)
"Makes a tree from a Lisp tree."
(if (consp tree)
(make-instance 'tree :children (mapcar #'make-tree tree))
(make-instance 'tree :label tree)))
(defun make-tree-phylip (string &key (truncate-labels nil) (index nil))
"Makes a tree from output from phylip. If an index is given then the labels
should be integers which will be translated to the nth (beginning with 1)
value in the index."
(let ((current-tree (list (make-tree nil)))
(current-string (make-array 10 :element-type 'character
:fill-pointer 0 :adjustable t)))
(loop for c across string do
(case c
(#\( (push (make-tree nil) current-tree)
(push (car current-tree) (children (cadr current-tree))))
(#\) (push (read-from-string current-string)
(edge-weights (car current-tree)))
(when (> (fill-pointer current-string) 0)
(setf (fill-pointer current-string) 0)
(pop current-tree)))
(#\, (when (> (fill-pointer current-string) 0)
(push (read-from-string current-string)
(edge-weights (car current-tree)))
(setf (fill-pointer current-string) 0)))
(#\: (when (> (fill-pointer current-string) 0)
(let ((label (copy-seq current-string)))
(when index
(dbg :mktrph "index~%")
(setf label (nth (1- (parse-integer label)) index)))
(when truncate-labels
(setf label (subseq label 0 (min (length label) truncate-labels))))
(push (reduce (lambda (s1 s2) (concatenate 'string s1 "_" s2))
(split-string label #\_))
(children (car current-tree)))
(setf (fill-pointer current-string) 0))))
(#\Tab nil)
(#\Newline nil)
(#\; nil)
(t (vector-push-extend c current-string))))
(car (children (car current-tree)))))
(defun tree-print (tree stream &optional (leafmap #'identity))
(print-tree-phylip tree stream leafmap)
(format stream ";~%"))
(defgeneric print-tree-phylip (tree stream &optional leafmap)
(:documentation "Prints tree in Newick format. If LEAFMAP is given it
should be a function that will be called to map the leaves."))
(defmethod print-tree-phylip ((tree tree) stream &optional (leafmap #'identity))
(when (consp (children tree))
(format stream "(")
(loop for cc on (children tree)
for ec on (edge-weights tree)
do
(print-tree-phylip (car cc) stream leafmap)
(format stream ":~f" (car ec))
(unless (endp (cdr cc))
(format stream ",")))
(format stream ")"))
(when (label tree)
(format stream "~a" (funcall leafmap (label tree)))))
(defmethod print-tree-phylip (tree stream &optional (leafmap #'identity))
(format stream "~A" (funcall leafmap tree)))
(defmethod print-object ((object tree) stream)
(print-unreadable-object (object stream :type t)
(format stream "with ~A leaves" (length (leafset object)))))
;;; Tree pretty printing
;;; Pretty printing could be done an obvious recursive fashion by printing
;;; each subtree into a grid of characters. But instead we do it line by
;;; line. This requires that bits of output from subtrees are interpolated
;;; together on each line of output. To do this we generate a closure for a
;;; tree which prints one line of its output to the given stream each time it
;;; is called. Then for a whole tree we just generate the printer closures
;;; for each child tree and call them in order.
;;; Remember to pass a tree from `canonicalise-tree' to get a canonical
;;; graphical representation from all pretty printing functions!
;;; TODO: This seems to work but it would be good to prove that it's correct
(defun pp-tree-width-multiplier (tree width)
"Calculates the width multiplier necessary to ensure TREE is no more than
WIDTH wide when pretty printing horizontally."
(if (= (tree-height tree) 0)
1
(let* ((printer (pp-tree-hprinter tree))
(original-width (tree-height tree))
(multiplier (/ width original-width))
actual-width)
;; now work out actual width when using that multiplier
(let ((pretty-tree-width-mult multiplier))
(setf actual-width
(loop repeat (pp-tree-h-height tree)
maximize (length (funcall printer nil)))))
(/ (- (* 2 width) actual-width) original-width))))
(defun pp-tree-print (tree &key (stream t) (vertical nil) (width 80))
(if vertical
(let* ((printer (pp-tree-printer tree))
(next-line (funcall printer nil)))
(loop while (string/= next-line "") do
(format stream "~A~%" next-line)
(setf next-line (funcall printer nil))))
(let ((printer (pp-tree-hprinter tree))
(pretty-tree-width-mult (pp-tree-width-multiplier tree width)))
(loop repeat (pp-tree-h-height tree) do
(funcall printer stream)
(format stream "~%")))))
(defgeneric pp-tree-width (tree))
(defmethod pp-tree-width ((tree tree))
(max 1
(length (format nil "~A" (label tree)))
(+ (reduce #'+ (mapcar #'pp-tree-width
(children tree)))
(* pretty-tree-horiz-space
(1- (length (children tree)))))))
(defmethod pp-tree-width (tree)
(length (format nil "~a" tree)))
(defgeneric pp-tree-height (tree))
(defmethod pp-tree-height ((tree tree))
(1+ (reduce #'max (mapcar #'+ (mapcar #'pp-tree-height (children tree))
(edge-weights tree))
:initial-value 0)))
(defmethod pp-tree-height (tree) 1)
(defgeneric pp-tree-h-height (tree))
(defmethod pp-tree-h-height ((tree tree))
(let ((nleaves (length (leafset tree))))
(+ nleaves (* (1- nleaves) pretty-tree-vertical-space))))
(defmethod pp-tree-h-height (tree)
1)
(defun pp-tree-label-line (label width children-widths)
"Makes a top line string for the label line of a tree. WIDTH is the width
of this tree, CHILDREN-WIDTHS is a list of widths of each child."
(if (>= (length label) width)
label
(let ((output (make-array width :element-type 'character
:fill-pointer 0 :initial-element #\Space))
(b #\Space) ; before
(d pretty-tree-left-corner-char) ; during
(a pretty-tree-horiz-char)) ; after
(loop
for cwidth on children-widths
for halfway = (/ (1- (car cwidth)) 2)
do
(loop repeat (ceiling halfway) do (format output "~C" b))
(format output "~C" d)
(when a
(loop repeat (+ (floor halfway) pretty-tree-horiz-space)
do (format output "~C" a)))
(if (and (cdr cwidth)
(cddr cwidth))
(setf b pretty-tree-horiz-char
d pretty-tree-down-char)
(setf b pretty-tree-horiz-char
d pretty-tree-right-corner-char
a nil)))
;; now put the label in
(let ((seq-start (ceiling (/ (- width (length label)) 2))))
(setf (subseq output seq-start (+ seq-start (length label)))
label))
(setf (fill-pointer output) width)
output)))
(defgeneric pp-tree-printer (tree)
(:documentation "Returns a closure which prints each successive line of
output for the tree to the given output, or returns it as a string if the
output is nil. Prints empty strings when the output is complete."))
(defmethod pp-tree-printer ((tree tree))
(assert (>= (length (edge-weights tree)) (length (children tree))))
(let* ((tree-label (if (label tree) (format nil "~A" (label tree))
(string pretty-tree-vnode-char)))
(tree-width (pp-tree-width tree))
(children-printers (mapcar #'pp-tree-printer (children tree)))
(children-next-line (mapcar (lambda (p) (funcall p nil))
children-printers))
(children-height-left (mapcar (lambda (w)
(1- (* w pretty-tree-height-mult)))
(edge-weights tree)))
(children-width (mapcar #'pp-tree-width (children tree)))
(children-total-width (+ (reduce #'+ children-width)
(* (1- (length children-width))
pretty-tree-horiz-space))))
(lambda (stream)
(let (output)
(cond
(tree-label ; print label line
(setf output
(pp-tree-label-line tree-label tree-width children-width))
(setf tree-label nil)
(format stream output))
((some (lambda (s) (string/= s "")) children-next-line)
(setf output (make-array tree-width :element-type 'character
:fill-pointer 0 :initial-element #\Space))
;; initial padding for long label
(incf (fill-pointer output)
(ceiling (/ (- tree-width children-total-width) 2)))
(loop ; print children
for cprinter on children-printers
for cnext-line on children-next-line
for cheight-left on children-height-left
for cwidth on children-width
for halfway = (/ (1- (car cwidth)) 2)
do
(cond
((> (car cheight-left) 0) ; vertical
(incf (fill-pointer output) (ceiling halfway))
(format output "~C" pretty-tree-vert-char)
(incf (fill-pointer output) (floor halfway))
(decf (car cheight-left)))
((string/= (car cnext-line) "") ; subtree
(format output (car cnext-line))
(setf (car cnext-line) (funcall (car cprinter) nil)))
(t ; nothing
(incf (fill-pointer output) (car cwidth))))
;; spacing
(when (cdr cprinter)
(incf (fill-pointer output) pretty-tree-horiz-space)))
(format stream output))
(t ; print nothing
(format stream "")))))))
(defmethod pp-tree-printer (tree)
(let ((line (format nil "~A" tree)))
(lambda (stream)
(let ((output line))
(when line
(setf line ""))
(format stream output)))))
(defun round-to-one (val)
(if (= 0 val)
(values 0 0)
(let ((rnd (max 1 (round val))))
(values rnd (- val rnd)))))
(defvar *rounded-off-amount* 0
"The amount rounded off in drawing the branch to this tree.")
(defgeneric pp-tree-hprinter (tree)
(:documentation "Returns a closure like pp-tree-printer but for printing
horizontally."))
(defmethod pp-tree-hprinter ((tree tree))
(assert (>= (length (edge-weights tree)) (length (children tree))))
(if (consp (children tree))
(let* ((tree-label (if (label tree) (format nil "~A" (label tree))
nil))
(tree-height (pp-tree-h-height tree))
(tree-height-left tree-height)
(children-printers (mapcar #'pp-tree-hprinter (children tree)))
(edge-weights (edge-weights tree))
(children-heights (mapcar #'pp-tree-h-height (children tree)))
(children-heights-left (copy-list children-heights))
(space-left 0)
(state :before))
(lambda (stream)
(let ((output (make-array 80 :element-type 'character
:fill-pointer 0 :initial-element #\Space)))
(multiple-value-bind (edge-length *rounded-off-amount*)
(round-to-one (+ (* (car edge-weights)
pretty-tree-width-mult)
*rounded-off-amount*))
;; label line
(cond
((= tree-height-left (ceiling (/ tree-height 2)))
;; print the label
(when (or (> (car edge-weights) 0)
(> (car children-heights) 1)
(> space-left 0))
(if tree-label
(format output (if children-printers
(subseq tree-label 0 1)
tree-label))
(if (and (= space-left 0)
(= (car children-heights-left)
(ceiling (/ (car children-heights) 2))))
(format output (string pretty-tree-cross-char))
(format output (string pretty-tree-hnode-char))))))
((> space-left 0)
(format output (string pretty-tree-vert-char)))
((= (car children-heights-left)
(ceiling (/ (car children-heights) 2)))
(if (or (> (car edge-weights) 0)
(> (car children-heights) 1))
(case state
(:before (format output (string pretty-tree-top-corner-char))
(setf state :during))
(:during (if (cdr children-heights)
(format output (string pretty-tree-out-char))
(progn
(format output
(string pretty-tree-bottom-corner-char))
(setf state :after)))))))
((eq state :during)
(format output (string pretty-tree-vert-char)))
(t
(format output " ")))
;; children
(if (> space-left 0)
(decf space-left)
(when children-printers
;; edge
(loop repeat (1- edge-length) do
(if (= (car children-heights-left)
(ceiling (/ (car children-heights) 2)))
(format output "~c" pretty-tree-horiz-char)
(incf (fill-pointer output))))
;; child
(funcall (car children-printers) output)
(decf (car children-heights-left))
(when (= 0 (car children-heights-left))
(pop children-printers)
(pop edge-weights)
(pop children-heights)
(pop children-heights-left)
(setf space-left pretty-tree-vertical-space)))))
(decf tree-height-left)
(format stream output))))
;; tree has no children
(pp-tree-hprinter (label tree))))
(defmethod pp-tree-hprinter (tree)
(let ((line (format nil "~A" tree)))
(lambda (stream)
(let ((output line))
(when line
(setf line ""))
(format stream output)))))
(defmethod pp-tree-height (tree)
(pp-tree-printer tree))
(defun vector-push-extend-vector (new-elements vector)
(loop for item across new-elements do
(vector-push-extend item vector))
vector)
(defun texify-string (string)
(let ((new-string (make-array (length string) :element-type 'character
:fill-pointer 0 :adjustable t)))
(loop for character across string do
(cond ((find character "&%$#_{}")
(vector-push-extend #\\ new-string)
(vector-push-extend character new-string))
((char= #\~ character)
(vector-push-extend-vector "\\textasciitilde" new-string))
((char= #\^ character)
(vector-push-extend-vector "\\textasciicircum" new-string))
((char= #\\ character)
(vector-push-extend-vector "\\textbackslash" new-string))
(t
(vector-push-extend character new-string))))
new-string))
(defparameter tikz-tree-print-root-width 0.02
"The width of the root where 1 is the width of the entire tree.")
(defparameter tikz-tree-print-scale-width 0.1
"The width of the scale bar where 1 is the width of the entire tree.")
(defgeneric tikz-tree-print (tree &optional output labelfun x y label)
(:documentation "Prints TikZ output for drawing a tree with (La)TeX. The
optional parameters should be left as default."))
(defmethod tikz-tree-print ((tree tree)
&optional (output t) (labelfun #'identity)
(x 0) (y 0) (label "r"))
(if (consp (children tree))
(progn
;; this tree's root
(if (label tree)
(format output "\\node[label={[label distance=-.2cm,font=\\footnotesize]below left:~A}] (~A) at (~F,~F) {};~%"
(funcall labelfun (label tree)) label x y)
(format output "\\node (~A) at (~F,~F) {};~%"
label x y))
(when (= x 0)
(format output "\\node (rr) at (~F,~F) {};~%"
(- (* (tree-height tree) tikz-tree-print-root-width))
y)
(format output "\\draw (~A.center) -- (rr.center);~%" label))
(loop with y = (+ y (/ (1- (length (leafset tree))) 2))
for child in (children tree)
for i from 1
for child-label = (format nil "~A~D" label i)
for child-height in (edge-weights tree)
for child-width = (1- (length (leafset child)))
do
;; print each child
(tikz-tree-print child
output
labelfun
(+ x child-height)
(- y (/ child-width 2))
child-label)
;; join root to child's root
(format output "\\draw (~A.center) |- (~A.center);~%"
label child-label)
(decf y (1+ child-width))))
;; tree is a leaf, print label
(tikz-tree-print (label tree) output labelfun x y label))
(when (= x 0)
;; print scale bar
(let ((x (tree-height tree))
(y (- (1+ (/ (length (leafset tree)) 2)))))
(format output "\\draw[|-|] (~F,~F) -- (~F,~F);~%"
x y
(* (- 1 tikz-tree-print-scale-width) x) y)
(format output "\\node at (~F,~F) {~,3F};~%"
(* (- 1 (/ tikz-tree-print-scale-width 2)) x) (1- y)
(* tikz-tree-print-scale-width x)))))
(defmethod tikz-tree-print (tree &optional (output t) (labelfun #'identity)
(x 0) (y 0) (label "r"))
(declare (ignore labelfun))
(format output "\\node[label={[font=\\small]right:~A}] (~A) at (~F,~F) {};~%"
(texify-string (format nil "~A" tree)) label x y))
(defparameter tikz-tree-print-polar-root-width 0.1
"The root tip length where 1 is the radius of the tree.")
(defparameter tikz-tree-print-polar-root-angle 45
"The angle of the root tip.")
(defparameter tikz-tree-print-polar-label-dist 0.01
"The distance between leaves and their labels where 1 is the radius of the tree.")
(defparameter tikz-tree-print-polar-scale-width 0.1
"The width of the scale bar where 1 is the radius of the entire tree.")
(defparameter tikz-tree-print-polar-bbox-expand
"\\useasboundingbox
let \\p0 = (current bounding box.south west), \\p1 = (current bounding box.north east)
in
({min(-\\x1,\\x0)},{min(-\\y1,\\y0)}) rectangle ({max(-\\x0,\\x1)},{max(-\\y0,\\y1});"
"Expands the bounding box of the TikZ picture so that point (0,0) is centred.")
(defun tikz-tree-print-polar-default-colourmap (leaf)
(declare (ignore leaf))
'(0 0 0))
(defgeneric tikz-tree-print-polar (tree &optional output leafmap colourmap
dist deg segment label labdist dist-mult)
(:documentation "Outputs TikZ for drawing a circle tree."))
(defmethod tikz-tree-print-polar ((tree tree) &optional (output t) (leafmap #'identity)
(colourmap #'tikz-tree-print-polar-default-colourmap)
(dist 0) (deg 0) (segment 360) (label "r")
(labdist nil) (dist-mult 1))
(setf deg (mod deg 360))
(unless labdist
(setf labdist (* tikz-tree-print-polar-label-dist (tree-height tree))))
(if (consp (children tree))
;; draw root tip
(progn
(when (= dist 0)
(format output "\\node (rr) at (0:0) {};~%")
(setf dist (* tikz-tree-print-polar-root-width (tree-height tree)))
(setf dist-mult (/ 1 (tree-height tree)))
(setf deg tikz-tree-print-polar-root-angle)
(format output "\\draw[color={~A}] (rr) -- (~F:~F);~%"
(tikz-rgb-colour (cluster-colour (mapcar colourmap (leafset tree))))
deg (* dist dist-mult)))
;; this tree's root
(format output "\\node (~A) at (~F:~F) {};~%" label deg (* dist dist-mult))
(let ((leaf-seg (/ segment (length (leafset tree))))
(leaf-colours (mapcar colourmap (leafset tree))))
(loop
for child in (children tree)
for i from 1
for child-label = (format nil "~A~D" label i)
for child-height in (edge-weights tree)
for child-seg = (* leaf-seg (length (leafset child)))
for child-colours = (mapcar colourmap (leafset child))
with child-deg = (- deg (/ segment 2))
do
(incf child-deg (/ child-seg 2))
;; print each child
(tikz-tree-print-polar child
output
leafmap
colourmap
(+ dist child-height)
child-deg
child-seg
child-label
labdist
dist-mult)
;; draw helper node
(format output "\\node (~Ah) at (~F:~F) {};~%" child-label child-deg (* dist dist-mult))
;; join root to child's root
(format output "\\draw[color={~A}] (~Ah.center) -- (~A.center);~%"
(tikz-rgb-colour (cluster-colour child-colours))
child-label child-label)
(incf child-deg (/ child-seg 2)))
;; draw arc at once
(format output "\\draw[color={~A}] (~F:~F) arc (~F:~F:~F);~%"
(tikz-rgb-colour (cluster-colour leaf-colours))
(+ (- deg (/ segment 2)) (/ (* leaf-seg (length (leafset (nth-child 0 tree)))) 2))
(* dist dist-mult)
(+ (- deg (/ segment 2)) (/ (* leaf-seg (length (leafset (nth-child 0 tree)))) 2))
(- (+ deg (/ segment 2)) (/ (* leaf-seg (length (leafset (last-child tree)))) 2))
(* dist dist-mult))))
;; tree is leaf
(tikz-tree-print-polar (label tree) output dist deg segment label labdist dist-mult))
(when (= segment 360)
;; print the scale bar
(let* ((x1 (/ (* tikz-tree-print-polar-scale-width (tree-height tree)) 2))
(x2 (- x1))
(y (- (+ (* (tree-height tree) 1.1)
(* tikz-tree-print-polar-root-width (tree-height tree))))))
(format output "\\draw[|-|] (~F,~F) -- (~F,~F);~%"
x1 y
x2 y)
(format output "\\node at (~F,~F) {~,3F};~%"
0 (- y (* (tree-height tree) 0.05))
(* tikz-tree-print-polar-scale-width (tree-height tree))))
;; expand bounding box so that (0:0) is at centre of image
(format output "~A~%" tikz-tree-print-polar-bbox-expand)))
(defmethod tikz-tree-print-polar (tree &optional (output t) (leafmap #'identity)
(colourmap #'tikz-tree-print-polar-default-colourmap)
(dist 0) (deg 0) (segment 360) (label "r")
(labdist nil) (dist-mult 1))
(declare (ignore segment))
(setf deg (mod deg 360))
(unless labdist
(setf labdist 0.1))
;; (format output "\\node[label={[rotate=~F]~A:~A}] (~A) at (~F:~F) {};~%"
;; (- (mod (+ deg 90) 180) 90)
;; (if (> (mod (+ deg 90) 360) 180) "left" "right")
;; (texify-string (format nil "~A" (funcall leafmap tree)))
;; label deg dist)
(format output "\\node (~A) at (~F:~F) {};~%"
label deg (* dist dist-mult))
(format output "\\node[anchor=~A,rotate=~F,color={~A}] (~Al) at (~F:~F) {~A};~%"
(if (> (mod (+ deg 90) 360) 180) "east" "west")
(- (mod (+ deg 90) 180) 90)
(tikz-rgb-colour (funcall colourmap tree))
label
deg
(+ (* dist dist-mult) labdist)
(texify-string (funcall leafmap tree))))
(defun make-colourmap-leaf-letters (letters colours)
"Makes a colourmap between list of letters and list of colours. The initial
letter of the name is used to define the colour."
(assert (= (length letters) (length colours)))
(let ((alist (mapcar #'cons letters colours)))
(lambda (name)
(let ((colour (assoc (elt name 0) alist)))
(if colour
(cdr colour)
(list 0 0 0))))))
(defun make-colourmap-alist (alist)
"Makes a colourmap from an alist mapping leaf names to group numbers."
(lambda (name)
(cdr (assoc name alist :test #'equal))))
(defun tikz-rgb-colour (rgb)
"Converts list of three colour values to a TikZ RGB string."
(if (every #'zerop rgb)
"black"
(apply #'format nil "rgb:red,~D;green,~D;blue,~D" rgb)))
(defun cluster-colour (colours)
"Makes a cluster colours"
(let ((l (length colours)))
(mapcar (lambda (c) (round (/ c l)))
(reduce (lambda (l1 l2) (mapcar #'+ l1 l2))
colours))))
(defgeneric leafset (tree))
(defmethod leafset ((tree tree))
(if (consp (children tree))
(reduce #'nconc (mapcar #'leafset (children tree)))
(list (label tree))))
(defmethod leafset (tree)
(list tree))
(defun make-proper-cherry (a ra b rb)
"Makes a proper cherry with values a,b edge weights ra, rb."
`((,a ,b) . (,ra ,rb)))
(defun make-cherry (&rest args)
"Makes a cherry with values and edge weights given. Arguments should be of
the form (val . weight)"
(when (< (length args) 1)
(error "A cherry must have at least one child!"))
(let ((cherry (cons nil nil)))
(loop for p in args do
(setf (car cherry) (cons (car p) (car cherry)))
(setf (cdr cherry) (cons (cdr p) (cdr cherry))))
(setf (car cherry) (nreverse (car cherry)))
(setf (cdr cherry) (nreverse (cdr cherry)))
cherry))
(defun vertex-degree (vertex)
(if (listp vertex)
(1+ (length (car vertex)))
1))
(defun cherry-binaryp (vertex)
(= (vertex-degree vertex) 3))
(defun tripletp (tree)
(and (cherry-binaryp tree)
(not (and (listp (left-child tree)) (listp (right-child tree))))
(or (cherry-binaryp (left-child tree))
(cherry-binaryp (right-child tree)))))
(defun make-ultrametric-triplet (a b c ab ac)
"Makes an ultrametric triplet ab|c with the edge weight given."
(when (>= ab ac)
(error "The distance ab must be less than the distance ac!"))
(make-proper-cherry
(make-proper-cherry a (/ ab 2) b (/ ab 2))
(- (/ ac 2) (/ ab 2))
c
(/ ac 2)))
;;; returns the two leaves of the cherry in a triplet
(defun triplet-get-cherry (triplet)
(when (not (tripletp triplet))
(error "Not a triplet!"))
(cond
((and (not (listp (left-child triplet)))
(cherry-binaryp (right-child triplet)))
(list (left-child (right-child triplet))
(right-child (right-child triplet))))
((and (not (listp (right-child triplet)))
(cherry-binaryp (left-child triplet)))
(list (left-child (left-child triplet))
(right-child (left-child triplet))))
(t
(error "Not triplet!"))))
(defun nth-child (n tree)
(assert (< n (length (children tree))))
(nth n (children tree)))
(defun nth-edge-weight (n tree)
(assert (< n (length (edge-weights tree))))
(nth n (edge-weights tree)))
(defun last-child (tree)
(car (last (children tree))))
(defun last-edge-weight (tree)
(car (last (edge-weights tree))))
(defun tree-total-width (tree)
(cond ((cherry-binaryp tree)
(+ (tree-total-width (left-child tree))
(tree-total-width (right-child tree))
(* pretty-tree-horiz-space 2)
1))
((= (vertex-degree tree) 2)
(tree-total-width (nth-child 0 tree)))
((listp tree)
(1+ (loop for v in (car tree) summing
(1+ (length (format nil "~a" v))))))
(t
(length (format nil "~a" tree)))))
;;; extent is the the length of the horizontal line between the node and the
;;; vertical bar (only for binary)
(defun tree-left-extent (tree)
(cond
((cherry-binaryp (left-child tree))
(+ (tree-total-width (right-child (left-child tree)))
(* pretty-tree-horiz-space 2)))
((= (vertex-degree (left-child tree)) 2)
(+
(tree-right-space (left-child tree))
pretty-tree-horiz-space))
(t
(+ (floor (/ (1- (tree-total-width (left-child tree))) 2))
pretty-tree-horiz-space))))
(defun tree-right-extent (tree)
(cond
((cherry-binaryp (right-child tree))
(+ (tree-total-width (left-child (right-child tree)))
(* pretty-tree-horiz-space 2)))
((= (vertex-degree (right-child tree)) 2)
(+
(tree-left-space (right-child tree))
pretty-tree-horiz-space))
(t
(+ (ceiling (/ (1- (tree-total-width (right-child tree))) 2))
pretty-tree-horiz-space))))
;;; space is the amount of space between the left/right edge of the bounding
;;; box of the tree to the vertical line
(defun tree-left-space (tree)
(cond
((cherry-binaryp (nth-child 0 tree))
(+ (tree-total-width (left-child (nth-child 0 tree)))
pretty-tree-horiz-space))
((= (vertex-degree (nth-child 0 tree)) 2)
(tree-left-space (nth-child 0 tree)))
(t
(ceiling (/ (1- (tree-total-width (nth-child 0 tree))) 2)))))
(defun tree-right-space (tree)
(cond
((cherry-binaryp (last-child tree))
(+ (tree-total-width (right-child (last-child tree)))
pretty-tree-horiz-space))
((= (vertex-degree (last-child tree)) 2)
(tree-right-space (last-child tree)))
(t
(floor (/ (1- (tree-total-width (right-child tree))) 2)))))
;;; this just makes sure the widths agree for a tree
(defun tree-width-test (tree)
(= (tree-total-width tree)
(+ (tree-left-space tree)
1
(tree-left-extent tree)
1
(tree-right-extent tree)
1
(tree-right-space tree))))
;;; total height of tree is longest path from root to a leaf in terms of edge
;;; weights
(defun tree-total-edge-height (tree)
(cond ((cherry-binaryp tree)
(max (+ (left-edge-weight tree)
(tree-total-edge-height (left-child tree)))
(+ (right-edge-weight tree)
(tree-total-edge-height (right-child tree)))))
((= (vertex-degree tree) 2)
(+ (nth-edge-weight 0 tree)
(tree-total-edge-height (nth-child 0 tree))))
(t
0)))
(defgeneric tree-height (tree))
(defmethod tree-height ((tree tree))
(loop
for child in (children tree)
for edge-weight in (edge-weights tree)
maximizing (+ (tree-height child) edge-weight)))
(defmethod tree-height (tree)
0)
;;; total height of pretty printed tree
(defun tree-total-height (tree)
(1+ (* (tree-total-edge-height tree) pretty-tree-height-mult)))
(defun pretty-print-tree (output tree)
"Pretty prints a tree."
(let ((matrix (make-array
(list (tree-total-height tree)
(tree-total-width tree))
:initial-element #\Space)))
;; fill the matrix
(put-tree-in-matrix tree 0 0 matrix)
;; print the matrix out
(loop for i from 0 to (1- (tree-total-height tree)) do
(loop for j from 0 to (1- (tree-total-width tree)) do
(format output "~a" (aref matrix i j)))
(format output "~%"))))
;;; pretty prints list of trees using up to 80 chars of horizontal space
(defun pretty-print-trees (output trees)
(let ((tree-list trees))
(loop while tree-list do
;; pop off as many as possible
(let ((this-row nil)
(total-width 0)
(horiz-space (max pretty-tree-horiz-space 1)))
(loop while tree-list do
(if (< (+ total-width (tree-total-width (car tree-list)) 1)
80)
(progn
(setf total-width
(+ total-width
(tree-total-width (car tree-list))
horiz-space))
(setf this-row (cons (car tree-list) this-row))
(setf tree-list (cdr tree-list)))
(return)))
;; process this row
(let* (
(total-height (loop for tr in this-row maximize
(tree-total-height tr)))
(current-pos 0)
(matrix (make-array
(list total-height
total-width)
:initial-element #\Space)))
(loop for tree in this-row do
(put-tree-in-matrix
tree (- total-height (tree-total-height tree))
current-pos matrix)
(setf current-pos (+ current-pos
(tree-total-width tree)
horiz-space)))
;; print the matrix out
(loop for i from 0 to (1- total-height) do
(loop for j from 0 to (1- total-width) do
(format output "~a" (aref matrix i j)))
(format output "~%")))))))
;;; this writes the tree into matrix recursively with its "canvas" beginning
;;; at top left
(defun put-tree-in-matrix (tree top left matrix)
(cond ((cherry-binaryp tree)
;; it's a binary internal node
(let* ((ls (tree-left-space tree))
(le (tree-left-extent tree))
(re (tree-right-extent tree))
(lh (* (left-edge-weight tree) pretty-tree-height-mult))
(rh (* (right-edge-weight tree) pretty-tree-height-mult))
(node (+ ls le 1)))
;; horizontal line
(setf (aref matrix top (+ left ls))
pretty-tree-left-corner-char) ;left end of left line
(loop for i from (+ left ls 1) to (+ left ls le) do
(setf (aref matrix top i)
pretty-tree-horiz-char)) ;left line
(setf (aref matrix top (+ left node))
pretty-tree-vnode-char) ;node
(loop for i from (+ left node 1) to (+ left node re) do
(setf (aref matrix top i)
pretty-tree-horiz-char)) ;right line
(setf (aref matrix top
(+ left node re 1))
pretty-tree-right-corner-char) ;right end of right line
;; vertical lines
(loop for j from (1+ top) to (+ top lh -2) do
(setf (aref matrix j (+ left ls))
pretty-tree-vert-char)) ;left line
(when (> lh 1)
(setf (aref matrix (+ top lh -1) (+ left ls))
pretty-tree-vert-end-char)) ;left end
(loop for j from (1+ top) to (+ top rh -2) do
(setf (aref matrix j (+ left ls le 2 re))
pretty-tree-vert-char)) ;right line
(when (> rh 1)
(setf (aref matrix (+ top rh -1) (+ left ls le 2 re))
pretty-tree-vert-end-char)) ;right end
;; print children
(put-tree-in-matrix (left-child tree) (+ top lh) left matrix)
(put-tree-in-matrix (right-child tree) (+ top rh)
(+ left node 1 pretty-tree-horiz-space)
matrix)))
((= (vertex-degree tree) 2)
;; a degree two internal node
(let* ((node (tree-left-space tree))
(h (* (nth-edge-weight 0 tree) pretty-tree-height-mult)))
;; node
(setf (aref matrix top (+ left node))
pretty-tree-vnode-char)
;; vertical line
(loop for j from (1+ top) to (+ top h -2) do
(setf (aref matrix j (+ left node))
pretty-tree-vert-char)) ;line
(when (> h 1)
(setf (aref matrix (+ top h -1) (+ left node))
pretty-tree-vert-end-char)) ;end
;; print child
(put-tree-in-matrix (nth-child 0 tree) (+ top h) left matrix)))
((consp tree)
;; it's a node with n>2 children (unresolved)
(let ((str (format nil "{~{~a~^,~}}" (car tree))))
(loop for i from 0 to (1- (length str)) do
(setf (aref matrix top (+ left i)) (elt str i)))))
(t
;; it's a leaf
(let ((str (format nil "~a" tree)))
(loop for i from 0 to (1- (length str)) do
(setf (aref matrix top (+ left i)) (elt str i)))))))
(defmacro utree (form)
"Makes an ultrametric tree."
(cond ((consp form)
`(make-cherry ,@(mapcar (lambda (f) `(cons (utree ,f)
,(if (consp f)
(- (first form)
(first f))
(first form))))
(rest form))))