/
zenplot.R
1214 lines (1152 loc) · 57.2 KB
/
zenplot.R
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
## Unfolding and zenplots
##' @title Unfold the hypercube and produce all information concerning the zenpath
##' and zenplot layout
##' @family creating zenplots
##' @name unfold
##' @aliases unfold
##' @description The \code{unfold()} function imagines each pair of variables/dimensions
##' as a "face" of a high dimensional cube. These faces are "unfolded" from one 2d space
##' or "face" to the next about the 1d face or "edge" they share. The \code{unfold()}
##' function takes, as first argument, \code{nfaces},
##' the number of 2d plots/spaces to be "unfolded" and produces the zenpath and
##' zenplot layout required for the function zenplot(). Laying out these pairs
##' with a zenplot is what is alluded to as an "unfolding" of (at least a part of)
##' the high dimensional space.
##' @usage
##' unfold(nfaces, turns = NULL,
##' n2dcols = c("letter", "square", "A4", "golden", "legal"),
##' method = c("tidy", "double.zigzag", "single.zigzag", "rectangular"),
##' first1d = TRUE, last1d = TRUE, width1d = 1, width2d = 10)
##' @param nfaces The number of faces of the hypercube to unfold
##' @param turns A \code{\link{character}} vector (of length two times the
##' number of variables to be plotted minus 1) consisting of \code{"d"},
##' \code{"u"}, \code{"r"} or \code{"l"} indicating the turns out of the
##' current plot position; if \code{NULL}, the \code{turns} are
##' constructed.
##' @param n2dcols number of columns of 2d plots (\eqn{\ge 1}{>= 1})
##' or one of \code{"letter"}, \code{"square"}, \code{"A4"},
##' \code{"golden"} or \code{"legal"} in which case a similar layout is constructed.
##' Note that \code{n2dcols} is ignored if \code{!is.null(turns)}.
##' @param method The type of zigzag plot (a \code{\link{character}}).
##'
##' Available are:
##' \describe{
##' \item{\code{tidy}:}{more tidied-up \code{double.zigzag}
##' (slightly more compact placement of plots towards the end).}
##' \item{\code{double.zigzag}:}{zigzag plot in the form of a
##' flipped \dQuote{S}. Along this path, the plots
##' are placed in the form of an \dQuote{S} which is rotated
##' counterclockwise by 90 degrees.}
##' \item{\code{single.zigzag}:}{zigzag plot in the form of a
##' flipped \dQuote{S}.}
##' \item{\code{rectangular}:}{plots that fill the page from
##' left to right and top to bottom. This is useful (and most compact)
##' for plots that do not share an axis.}
##' }
##' Note that \code{method} is ignored if \code{turns} are provided.
##' @param first1d A \code{\link{logical}} indicating whether the first one-dimensional (1d)
##' plot should be plotted.
##' @param last1d A \code{\link{logical}} indicating whether the last one-dimensional (1d)
##' plot should be plotted
##' @param width1d A graphical parameter > 0 giving the width of 1d plots.
##' @param width2d A graphical parameter > 0 giving the width of 2d plots.
##' @return A \code{\link{list}} describing the unfolded path and its layout
##' as a list of named components:
##' \describe{
##' \item{\code{path}:}{the path of the unfolding, itself given
##' as a structured \code{\link{list}} having components
##' \describe{
##' \item{\code{turns}:}{the sequence of turns
##' -- each being one of \dQuote{l} (for left), \dQuote{r} (for right),
##' \dQuote{d} (for down), and \dQuote{u} (for up) --
##' required to move from the current plot location in the display to the next along
##' the unfolded path.}
##' \item{\code{positions}:}{the path as a matrix of \code{(x, y)} positions giving
##' the indices in the \code{occupancy} matrix of each plot in the path.}
##' \item{\code{occupancy}:}{A rectangular array whose cells indicate the positions
##' of the plots on the page.}
##' }
##' }
##' \item{\code{layout}:}{the details of the visual layout of the plots and given
##' as a structured \code{\link{list}} having components
##' \describe{
##' \item{\code{orientations}:}{a vector indicating the orientation of each of the
##' displays in order -- \dQuote{h} for horizontal, \dQuote{v} for vertical, and
##' \dQuote{s} for square.}
##' \item{\code{dimensions}:}{a vector giving the dimensionality of each
##' plot in order.}
##' \item{\code{vars}:}{A matrix of the variable indices to be used in each plot -- \code{x}
##' being the horizontal variable and \code{y} the vertical.}
##' \item{\code{layoutWidth}:}{A positive integer giving the display width of
##' a 2d plot.}
##' \item{\code{layoutHeight}:}{A positive integer giving the display height of
##' a 2d plot.}
##' \item{\code{boundingBoxes}:}{A matrix of 4 columns giving locations (\code{left},
##' \code{right}, \code{bottom}, and \code{top}) of the box which bound each of the
##' plots in order.}
##' }
##' }
##' }
##' @author Marius Hofert and Wayne Oldford
##' @export
##' @note Although \code{unfold()} is probably rather rarely used directly by a user,
##' it provides insight into how zenplots are constructed.
##' @examples
##' dim <- 20
##' unfolding <- unfold(nfaces = dim -1)
##' names(unfolding)
unfold <- function(nfaces, turns = NULL,
n2dcols = c("letter", "square", "A4", "golden", "legal"),
method = c("tidy", "double.zigzag", "single.zigzag", "rectangular"),
first1d = TRUE, last1d = TRUE, width1d = 1, width2d = 10)
{
## Checking
stopifnot(nfaces >= 0, is.logical(first1d), is.logical(last1d), length(width1d) == 1,
length(width2d) == 1, width1d >= 0, width2d >= 0)
if(nfaces == 0 && (!first1d || !last1d))
stop("'first1d' or 'last1d' can only be FALSE if 'nfaces' is >= 1.")
if(is.character(n2dcols)) n2dcols <- n2dcols_aux(nfaces, method = n2dcols)
if(is.null(turns)) { # turns not provided => use n2dcols and method
stopifnot(length(n2dcols) == 1, n2dcols >= 1)
if(nfaces >= 2 && n2dcols < 2)
stop("If nfaces >= 2, n2dcols must be >= 2.")
method <- match.arg(method)
} else { # turns provided
## If the turns are provided, we should check them *before* calling
## get_path(). Otherwise (see below), we check them after they
## have been constructed by get_path()
turn_checker(turns, n2dplots = nfaces, first1d = first1d, last1d = last1d)
}
## 1) Construct the path (= turns, positions in the occupancy matrix
## and the occupancy matrix)
path <- get_path(turns, n2dplots = nfaces, n2dcols = n2dcols,
method = method, first1d = first1d, last1d = last1d)
## If 'turns' is not provided, extract them now (for checking and computing
## the layout via get_layout())
if(is.null(turns)) {
turns <- path$turns
turn_checker(turns, n2dplots = nfaces, first1d = first1d, last1d = last1d)
}
## 2) Determine the layout
layout <- get_layout(turns, n2dplots = nfaces, first1d = first1d, last1d = last1d,
width1d = width1d, width2d = width2d)
## Return
list(path = path, layout = layout)
}
## Set up hidden (not found on ls()) environment in R_GlobalEnv for burst object 'x'
.zenplots_burst_envir <- new.env(hash = FALSE, parent = emptyenv()) # define the environment to cache the burst x
##' @title Main function to create a zenplot
##' @family creating zenplots
##' @name zenplot
##' @aliases zenplot
##' @description Constructs and draws a zigzag expanded navigation plot for a
##' graphical exploratory analysis of a path of variables. The result is an
##' alternating sequence of one-dimensional (1d) and two-dimensional (2d) plots
##' laid out in a zigzag-like structure so that each consecutive pair of 2d plots has one of its
##' variates (or coordinates) in common with that of the 1d plot appearing between them.
##' @usage
##' zenplot(x, turns = NULL,
##' first1d = TRUE, last1d = TRUE,
##' n2dcols = c("letter", "square", "A4", "golden", "legal"),
##' n2dplots = NULL,
##' plot1d = c("label", "points", "jitter", "density", "boxplot", "hist",
##' "rug", "arrow", "rect", "lines", "layout"),
##' plot2d = c("points", "density", "axes", "label", "arrow", "rect", "layout"),
##' zargs = c(x = TRUE, turns = TRUE, orientations = TRUE,
##' vars = TRUE, num = TRUE, lim = TRUE, labs = TRUE,
##' width1d = TRUE, width2d = TRUE,
##' ispace = match.arg(pkg) != "graphics"),
##' lim = c("individual", "groupwise", "global"),
##' labs = list(group = "G", var = "V", sep = ", ", group2d = FALSE),
##' pkg = c("graphics", "grid", "loon"),
##' method = c("tidy", "double.zigzag", "single.zigzag", "rectangular"),
##' width1d = if(is.null(plot1d)) 0.5 else 1,
##' width2d = 10,
##' ospace = if(pkg == "loon") 0 else 0.02,
##' ispace = if(pkg == "graphics") 0 else 0.037,
##' draw = TRUE,
##' ...)
##' @param x A data object of "standard forms", being a \code{\link{vector}}, or a \code{\link{matrix}},
##' or a \code{\link{data.frame}}, or a \code{\link{list}} of any of these.
##' In the case of a list, the components of \code{x} are interpreted as
##' groups of data which are visually separated by a two-dimensional
##' (group) plot.
##' @param turns A \code{\link{character}} vector (of length two times the
##' number of variables to be plotted minus 1) consisting of \code{"d"},
##' \code{"u"}, \code{"r"} or \code{"l"} indicating the turns out of the
##' current plot position; if \code{NULL}, the \code{turns} are
##' constructed (if \code{x} is of the "standard form" described above).
##' @param first1d A \code{\link{logical}} indicating whether the first
##' one-dimensional plot is included.
##' @param last1d A \code{\link{logical}} indicating whether the last
##' one-dimensional plot is included.
##' @param n2dcols number of columns of 2d plots (\eqn{\ge 1}{>= 1})
##' or one of \code{"letter"}, \code{"square"}, \code{"A4"},
##' \code{"golden"} or \code{"legal"}
##' in which case a similar layout is constructed.
##' Note that \code{n2dcols} is ignored if \code{!is.null(turns)}.
##' @param n2dplots The number of 2d plots.
##' @param plot1d A \code{\link{function}} to use to return a
##' one-dimensional plot constructed with package \code{pkg}.
##' Alternatively, a \code{\link{character}} string of an existing
##' function.
##' For the defaults provided, the corresponding functions
##' are obtained when appending \code{_1d_graphics}, \code{_1d_grid}
##' or \code{_1d_loon} depending on which \code{pkg} is used.
##'
##' If \code{plot1d = NULL}, then no 1d plot is produced in the \code{zenplot}.
##' @param plot2d A \code{\link{function}} returning a two-dimensional plot
##' constructed with package \code{pkg}.
##' Alternatively, a \code{\link{character}} string of an existing
##' function. For the defaults provided, the corresponding functions
##' are obtained when appending \code{_2d_graphics}, \code{_2d_grid}
##' or \code{_2d_loon} depending on which \code{pkg} is used.
##'
##' As for \code{plot1d}, \code{plot2d} omits 2d plots if \code{plot2d = NULL}.
##' @param zargs A fully named \code{\link{logical}} \code{\link{vector}}
##' indicating whether the respective arguments are (possibly) passed to
##' \code{plot1d()} and \code{plot2d()} (if the latter contain the
##' formal argument \code{zargs}, which they typically do/should, but
##' see below for an example in which they do not).
##'
##' \code{zargs} can maximally contain all variables as given in the default.
##' If one of those variables does not appear in \code{zargs}, it is
##' treated as \code{TRUE} and the corresponding arguments are passed
##' on to \code{plot1d} and \code{plot2d}. If one of them is set to
##' \code{FALSE}, the argument is not passed on.
##' @param lim (x-/y-)axis limits. This can be a \code{\link{character}} string
##' or a \code{numeric(2)}.
##'
##' If \code{lim = "groupwise"} and \code{x} does not contain groups,
##' the behaviour is equivalent to \code{lim = "global"}.
##'
##' @param labs The plot labels to be used; see the argument \code{labs} of
##' \code{\link{burst}()} for the exact specification.
##' \code{labs} can, in general, be anything as long as \code{plot1d}
##' and \code{plot2d} know how to deal with it.
##' @param pkg The R package used for plotting (depends on how the
##' functions \code{plot1d} and \code{plot2d} were constructed;
##' the user is responsible for choosing the appropriate package
##' among the supported ones).
##' @param method The type of zigzag plot (a \code{\link{character}}).
##'
##' Available are:
##' \describe{
##' \item{\code{tidy}:}{more tidied-up \code{double.zigzag}
##' (slightly more compact placement of plots towards the end).}
##' \item{\code{double.zigzag}:}{zigzag plot in the form of a
##' flipped \dQuote{S}. Along this path, the plots
##' are placed in the form of an \dQuote{S} which is rotated
##' counterclockwise by 90 degrees.}
##' \item{\code{single.zigzag}:}{zigzag plot in the form of a
##' flipped \dQuote{S}.}
##' \item{\code{rectangular}:}{plots that fill the page from
##' left to right and top to bottom. This is useful (and most compact)
##' for plots that do not share an axis.}
##' }
##' Note that \code{method} is ignored if \code{turns} are provided.
##' @param width1d A graphical parameter > 0 giving the width of 1d plots.
##' @param width2d A graphical parameter > 0 giving the height of 2d plots.
##' @param ospace The outer space around the zenplot. A vector
##' of length four (bottom, left, top, right),
##' or one whose values are repeated to be of length four,
##' which gives the outer space between the device region and
##' the inner plot region around the zenplot.
##'
##' Values should be in \eqn{[0,1]} when \code{pkg} is \code{"graphics"} or
##' \code{"grid"}, and as number of pixels when\code{pkg} is \code{"loon"}.
##' @param ispace The inner space in \eqn{[0,1]} between the each figure region
##' and the region of the (1d/2d) plot it contains.
##' Again, a vector of length four (bottom, left, top, right) or a shorter one
##' whose values are repeated to produce a vector of length four.
##' @param draw A \code{\link{logical}} indicating whether a the \code{zenplot}
##' is immediately displayed (the default) or not.
##' @param ... arguments passed to the drawing functions for both \code{plot1d} and
##' \code{plot2d}. If you need to pass certain arguments only to one
##' of them, say, \code{plot2d}, consider providing your own
##' \code{plot2d}; see the examples below.
##' @return (besides plotting) invisibly returns a list having additional classnames
##' marking it as a zenplot and a zenPkg object (with Pkg being one of Graphics,
##' Grid, or Loon, so as to identify the
##' package used to construct the plot).
##'
##' As a list it contains at least
##' the path and layout (see \code{\link{unfold}} for details).
##'
##' Depending on the graphics package \code{pkg} used, the returned list
##' includes additional components. For \code{pkg = "grid"},
##' this will be the whole plot as a \code{\link[grid]{grob}} (grid object).
##' For \code{pkg = "loon"}, this will be the whole plot as a
##' \code{loon} plot object as
##' well as the toplevel \code{tk} object in which the plot appears.
##'
##' @author Marius Hofert and Wayne Oldford
##' @seealso All provided default \code{plot1d} and \code{plot2d} functions.
##'
##' \code{\link{extract_1d}()} and \code{\link{extract_2d}()}
##' for how \code{zargs} can be split up into a list of columns and corresponding
##' group and variable information.
##'
##' \code{\link{burst}()} for how \code{x} can be split up into all sorts of
##' information useful for plotting (see our default \code{plot1d} and \code{plot2d}).
##' \code{\link{vport}()} for how to construct a viewport for
##' (our default) \pkg{grid} (\code{plot1d} and \code{plot2d}) functions.
##'
##' \code{\link{extract_pairs}()}, \code{\link{connect_pairs}()},
##' \code{\link{group}()} and \code{\link{zenpath}()} for
##' (zen)path-related functions.
##'
##' The various vignettes for additional examples.
##' @keywords hplot
##' @export
##' @examples
##' ### Basics #####################################################################
##'
##' ## Generate some data
##' n <- 1000 # sample size
##' d <- 20 # dimension
##' set.seed(271) # set seed (for reproducibility)
##' x <- matrix(rnorm(n * d), ncol = d) # i.i.d. N(0,1) data
##'
##' ## A basic zenplot
##' res <- zenplot(x)
##' uf <- unfold(nfaces = d - 1)
##' ## `res` and `uf` is not identical as `res` has specific
##' ## class attributes.
##' for(name in names(uf)) {
##' stopifnot(identical(res[[name]], uf[[name]]))
##' }
##'
##' ## => The return value of zenplot() is the underlying unfold()
##'
##' ## Some missing data
##' z <- x
##' z[seq_len(n-10), 5] <- NA # all NA except 10 points
##' zenplot(z)
##'
##' ## Another column with fully missing data (use arrows)
##' ## Note: This could be more 'compactified', but is technically
##' ## more involved
##' z[, 6] <- NA # all NA
##' zenplot(z)
##'
##' ## Lists of vectors, matrices and data frames as arguments (=> groups of data)
##' ## Only two vectors
##' z <- list(x[,1], x[,2])
##' zenplot(z)
##'
##' ## A matrix and a vector
##' z <- list(x[,1:2], x[,3])
##' zenplot(z)
##'
##' ## A matrix, NA column and a vector
##' z <- list(x[,1:2], NA, x[,3])
##' zenplot(z)
##' z <- list(x[,1:2], cbind(NA, NA), x[,3])
##' zenplot(z)
##' z <- list(x[,1:2], 1:10, x[,3])
##' zenplot(z)
##'
##' ## Without labels or with different labels
##' z <- list(A = x[,1:2], B = cbind(NA, NA), C = x[,3])
##' zenplot(z, labs = NULL) # without any labels
##' zenplot(z, labs = list(group = NULL, group2d = TRUE)) # without group labels
##' zenplot(z, labs = list(group = NULL)) # without group labels unless groups change
##' zenplot(z, labs = list(var = NULL)) # without variable labels
##' zenplot(z, labs = list(var = "Variable ", sep = " - ")) # change default labels
##'
##' ## Example with a factor
##' zenplot(iris)
##' zenplot(iris, lim = "global") # global scaling of axis
##' zenplot(iris, lim = "groupwise") # acts as 'global' here (no groups in the data)
##'
##'
##' ### More sophisticated examples ################################################
##'
##' ## Note: The third component (data.frame) naturally has default labels.
##' ## zenplot() uses these labels and prepends a default group label.
##' z <- list(x[,1:5], x[1:10, 6:7], NA,
##' data.frame(x[seq_len(round(n/5)), 8:19]), cbind(NA, NA), x[1:10, 20])
##' zenplot(z, labs = list(group = "Group ")) # change the group label (var and sep are defaults)
##' ## Alternatively, give z labels
##' names(z) <- paste("Group", LETTERS[seq_len(length(z))]) # give group names
##' zenplot(z) # uses given group names
##' ## Now let's change the variable labels
##' z. <- lapply(z, function(z.) {
##' if(!is.matrix(z.)) z. <- as.matrix(z.)
##' colnames(z.) <- paste("Var.", seq_len(ncol(z.)))
##' z.
##' }
##' )
##' zenplot(z.)
##'
##'
##' ### A dynamic plot based on 'loon' (if installed and R compiled with tcl support)
##'
##' \dontrun{
##' if(requireNamespace("loon", quietly = TRUE))
##' zenplot(x, pkg = "loon")
##' }
##'
##'
##' ### Providing your own turns ###################################################
##'
##' ## A basic example
##' turns <- c("l","d","d","r","r","d","d","r","r","u","u","r","r","u","u","l","l",
##' "u","u","l","l","u","u","l","l","d","d","l","l","d","d","l","l",
##' "d","d","r","r","d","d")
##' zenplot(x, plot1d = "layout", plot2d = "layout", turns = turns) # layout of plot regions
##' ## => The tiles stick together as ispace = 0.
##' zenplot(x, plot1d = "layout", plot2d = "layout", turns = turns,
##' pkg = "grid") # layout of plot regions with grid
##' ## => Here the tiles show the small (default) ispace
##'
##' ## Another example (with own turns and groups)
##' zenplot(list(x[,1:3], x[,4:7]), plot1d = "arrow", plot2d = "rect",
##' turns = c("d", "r", "r", "r", "r", "d",
##' "d", "l", "l", "l", "l", "l"), last1d = FALSE)
##'
##'
##' ### Providing your own plot1d() or plot2d() ####################################
##'
##' ## Creating a box
##' zenplot(x, plot1d = "label", plot2d = function(zargs)
##' density_2d_graphics(zargs, box = TRUE))
##'
##' ## With grid
##' \donttest{
##' zenplot(x, plot1d = "label", plot2d = function(zargs)
##' density_2d_grid(zargs, box = TRUE), pkg = "grid")
##' }
##'
##' ## An example with width1d = width2d and where no zargs are passed on.
##' ## Note: This could have also been done with 'rect_2d_graphics(zargs, col = ...)'
##' ## as plot1d and plot2d.
##' myrect <- function(...) {
##' plot(NA, type = "n", ann = FALSE, axes = FALSE, xlim = 0:1, ylim = 0:1)
##' rect(xleft = 0, ybottom = 0, xright = 1, ytop = 1, ...)
##' }
##' zenplot(matrix(0, ncol = 15),
##' n2dcol = "square", width1d = 10, width2d = 10,
##' plot1d = function(...) myrect(col = "royalblue3"),
##' plot2d = function(...) myrect(col = "maroon3"))
##'
##' ## Colorized rugs as plot1d()
##' basecol <- c("royalblue3", "darkorange2", "maroon3")
##' palette <- colorRampPalette(basecol, space = "Lab")
##' cols <- palette(d) # different color for each 1d plot
##' zenplot(x, plot1d = function(zargs) {
##' rug_1d_graphics(zargs, col = cols[(zargs$num+1)/2])
##' }
##' )
##'
##' ## With grid
##' library(grid) # for gTree() and gList()
##' \donttest{
##' zenplot(x, pkg = "grid", # you are responsible for choosing the right pkg (cannot be tested!)
##' plot1d = function(zargs)
##' rug_1d_grid(zargs, col = cols[(zargs$num+1)/2]))
##' }
##'
##' ## Rectangles with labels as plot2d() (shows how to overlay plots)
##' ## With graphics
##' ## Note: myplot2d() could be written directly in a simpler way, but is
##' ## based on the two functions here to show how they can be combined.
##' zenplot(x, plot1d = "arrow", plot2d = function(zargs) {
##' rect_2d_graphics(zargs)
##' label_2d_graphics(zargs, add = TRUE)
##' })
##'
##' ## With grid
##' \donttest{
##' zenplot(x, pkg = "grid", plot1d = "arrow", plot2d = function(zargs)
##' gTree(children = gList(rect_2d_grid(zargs),
##' label_2d_grid(zargs))))
##' }
##'
##' ## Rectangles with labels outside the 2d plotting region as plot2d()
##' ## With graphics
##' zenplot(x, plot1d = "arrow", plot2d = function(zargs) {
##' rect_2d_graphics(zargs)
##' label_2d_graphics(zargs, add = TRUE, xpd = NA, srt = 90,
##' loc = c(1.04, 0), adj = c(0,1), cex = 0.7)
##' })
##'
##' ## With grid
##' \donttest{
##' zenplot(x, pkg = "grid", plot1d = "arrow", plot2d = function(zargs)
##' gTree(children = gList(rect_2d_grid(zargs),
##' label_2d_grid(zargs, loc = c(1.04, 0),
##' just = c("left", "top"),
##' rot = 90, cex = 0.45))))
##' }
##'
##' ## 2d density with points, 1d arrows and labels
##' zenplot(x, plot1d = function(zargs) {
##' rect_1d_graphics(zargs)
##' arrow_1d_graphics(zargs, add = TRUE, loc = c(0.2, 0.5))
##' label_1d_graphics(zargs, add = TRUE, loc = c(0.8, 0.5))
##' }, plot2d = function(zargs) {
##' points_2d_graphics(zargs, col = adjustcolor("black", alpha.f = 0.4))
##' density_2d_graphics(zargs, add = TRUE)
##' })
##'
##' ## 2d density with labels, 1d histogram with density and label
##' ## Note: The 1d plots are *improper* overlays here as the density
##' ## plot does not know the heights of the histogram. In other
##' ## words, both histograms and densities use the whole 1d plot
##' ## region but are not correct relative to each other in the
##' ## sense of covering the same are. For a *proper* overlay
##' ## see below.
##' zenplot(x,
##' plot1d = function(zargs) {
##' hist_1d_graphics(zargs)
##' density_1d_graphics(zargs, add = TRUE,
##' border = "royalblue3",
##' lwd = 1.4)
##' label_1d_graphics(zargs, add = TRUE,
##' loc = c(0.2, 0.8),
##' cex = 0.6, font = 2,
##' col = "darkorange2")
##' },
##' plot2d = function(zargs) {
##' density_2d_graphics(zargs)
##' points_2d_graphics(zargs, add = TRUE,
##' col = adjustcolor("black", alpha.f = 0.3))
##' }
##' )
##'
##'
##' ### More sophisticated examples ################################################
##'
##' ### Example: Overlaying histograms with densities (the *proper* way)
##' \donttest{
##' ## Define proper 1d plot for overlaying histograms with densities
##' hist_with_density_1d <- function(zargs)
##' {
##' ## Extract information and data
##' num <- zargs$num # plot number (among all 1d and 2d plots)
##' turn.out <- zargs$turns[num] # turn out of current position
##' horizontal <- turn.out == "d" || turn.out == "u"
##' # the indices of the 'x' variable to be displayed in the current plot
##' ii <- plot_indices(zargs)
##' label <- paste0("V", ii[1]) # label
##' srt <- if(horizontal) 0 else if(turn.out == "r") -90 else 90 # label rotation
##' x <- zargs$x[,ii[1]] # data
##' lim <- range(x) # data limits
##' ## Compute histogram information
##' breaks <- seq(from = lim[1], to = lim[2], length.out = 21)
##' binInfo <- hist(x, breaks = breaks, plot = FALSE)
##' binBoundaries <- binInfo$breaks
##' widths <- diff(binBoundaries)
##' heights <- binInfo$density
##' ## Compute density information
##' dens <- density(x)
##' xvals <- dens$x
##' keepers <- (min(x) <= xvals) & (xvals <= max(x)) # keep those within the range of the data
##' x. <- xvals[keepers]
##' y. <- dens$y[keepers]
##' ## Determine plot limits and data
##' if(turn.out == "d" || turn.out == "l") { # flip density/histogram
##' heights <- -heights
##' y. <- -y.
##' }
##' if(horizontal) {
##' xlim <- lim
##' xlim.bp <- xlim - xlim[1] # special for barplot(); need to shift the bars
##' ylim <- range(0, heights, y.)
##' ylim.bp <- ylim
##' x <- c(xlim[1], x., xlim[2]) - xlim[1] # shift due to plot region set up by barplot()
##' y <- c(0, y., 0)
##' } else {
##' xlim <- range(0, heights, y.)
##' xlim.bp <- xlim
##' ylim <- lim
##' ylim.bp <- ylim - ylim[1] # special for barplot(); need to shift the bars
##' x <- c(0, y., 0)
##' y <- c(xlim[1], x., xlim[2]) - ylim[1] # shift due to plot region set up by barplot()
##' }
##' ## Determining label position relative to the zenpath
##' loc <- c(0.1, 0.6)
##'
##' # when walking downwards, change both left/right and up/down
##' if(turn.out == "d") loc <- 1-loc
##'
##' # when walking to the right, coordinates change and 2nd is flipped
##' if(turn.out == "r") {
##' loc <- rev(loc)
##' loc[2] <- 1-loc[2]
##' }
##'
##' # when walking to the left, coordinates change and 1st is flipped
##' if(turn.out == "l") {
##' loc <- rev(loc)
##' loc[1] <- 1-loc[1]
##' }
##' ## Plotting
##' barplot(heights, width = widths, xlim = xlim.bp, ylim = ylim.bp,
##' space = 0, horiz = !horizontal, main = "", xlab = "", axes = FALSE) # histogram
##' polygon(x = x, y = y, border = "royalblue3", lwd = 1.4) # density
##' opar <- par(usr = c(0, 1, 0, 1)) # switch to relative coordinates for text
##' on.exit(par(opar))
##' text(x = loc[1], y = loc[2], labels = label, cex = 0.7, srt = srt, font = 2,
##' col = "darkorange2") # label
##' }
##'
##' ## Zenplot
##' zenplot(x,
##' plot1d = "hist_with_density_1d",
##' plot2d = function(zargs) {
##' density_2d_graphics(zargs)
##' points_2d_graphics(zargs,
##' add = TRUE,
##' col = adjustcolor("black", alpha.f = 0.3))
##' }
##' )
##' }
##'
##' ### Example: A path through pairs of a grouped t copula sample
##'
##' \donttest{
##' ## 1) Build a random sample from a 17-dimensional grouped t copula
##' d. <- c(8, 5, 4) # sector dimensions
##' d <- sum(d.) # total dimension
##' nu <- rep(c(12, 1, 0.25), times = d.) # d.o.f. for each dimension
##' n <- 500 # sample size
##' set.seed(271)
##' Z <- matrix(rnorm(n * d), ncol = n) # (d,n)-matrix
##' P <- matrix(0.5, nrow = d, ncol = d)
##' diag(P) <- 1
##' L <- t(chol(P)) # L: LL^T = P
##' Y <- t(L %*% Z) # (n,d)-matrix containing n d-vectors following N(0,P)
##' U. <- runif(n)
##' W <- sapply(nu, function(nu.) 1/qgamma(U., shape = nu./2, rate = nu./2)) # (n,d)-matrix
##' X <- sqrt(W) * Y # (n,d)-matrix
##' U <- sapply(1:d, function(j) pt(X[,j], df = nu[j])) # (n,d)-matrix
##'
##' ## 2) Plot the data with a pairs plot, colorizing the groups
##' cols <- matrix("black", nrow = d, ncol = d) # colors
##' start <- c(1, cumsum(head(d., n = -1))+1) # block start indices
##' end <- cumsum(d.) # block end indices
##' for(j in seq_along(d.)) cols[start[j]:end[j], start[j]:end[j]] <- basecol[j] # colors
##' diag(cols) <- NA # remove colors corresponding to diagonal entries
##' cols <- as.vector(cols) # convert to a vector
##' cols <- cols[!is.na(cols)] # remove NA entries corresponding to diagonal
##' count <- 0 # panel number
##' my_panel <- function(x, y, ...) # panel function for colorizing groups
##' { count <<- count + 1; points(x, y, pch = ".", col = cols[count]) }
##' pairs(U, panel = my_panel, gap = 0,
##' labels = as.expression( sapply(1:d, function(j) bquote(italic(U[.(j)]))) ))
##'
##' ## 3) Zenplot of a random path through all pairs, colorizing the respective group
##' ## Define our own points_2d_grid() for colorizing the groups
##' my_points_2d_grid <- function(zargs, basecol, d.) {
##' r <- extract_2d(zargs) # extract information from zargs
##' x <- r$x
##' y <- r$y
##' xlim <- r$xlim
##' ylim <- r$ylim
##' num2d <- zargs$num/2
##' vars <- as.numeric(r$vlabs[num2d:(num2d+1)]) # two variables to be plotted
##' ## Alternatively, we could have used ord[r$vars[num2d:(num2d+1)]] with
##' ## the order 'ord' (see below) being passed to my_points_2d_grid()
##' col <- if(all(1 <= vars & vars <= d.[1])) { basecol[1] } else {
##' if(all(d.[1]+1 <= vars & vars <= d.[1]+d.[2])) { basecol[2] } else {
##' if(all(d.[1]+d.[2]+1 <= vars & vars <= d)) basecol[3] else "black"
##' }
##' } # determine the colors
##' vp <- vport(zargs$ispace, xlim = xlim, ylim = ylim, x = x, y = y) # viewport
##' pointsGrob(x = x[[1]], y = y[[1]], pch = 21, size = unit(0.02, units = "npc"),
##' name = "points_2d", gp = gpar(col = col), vp = vp)
##' }
##' ## Plot a random permutation of columns via a zenplot
##' ## Note: We set column labels here, as otherwise the labels can only
##' ## show *indices* of the variables to be plotted, i.e., the column
##' ## number in U[,ord], and not the original column number in U (which
##' ## is what we want to see in order to see how our 'path' through
##' ## the pairs of variables looks like).
##' colnames(U) <- 1:d
##' set.seed(1)
##' (ord <- sample(1:d, size = d)) # path; 1:d would walk parallel to the secondary diagonal
##' zenplot(U[,ord], plot1d = "layout", plot2d = "layout", pkg = "grid") # layout
##' zenplot(U[,ord], # has correct variable names as column names
##' pkg = "grid",
##' plot1d = function(zargs) arrow_1d_grid(zargs, col = "grey50"),
##' plot2d = function(zargs)
##' gTree(children = gList(
##' my_points_2d_grid(zargs, basecol = basecol, d. = d.),
##' rect_2d_grid(zargs, width = 1.05, height = 1.05,
##' col = "grey50", lty = 3),
##' label_2d_grid(zargs, loc = c(1.06, -0.03),
##' just = c("left", "top"), rot = 90, cex = 0.45,
##' fontface = "bold") )))
##' ## => The points are colorized correctly (compare with the pairs plot).
##' }
##'
##'
##' ### Using ggplot2 ##############################################################
##'
##' ## Although not thoroughly tested, in principle ggplot2 can also be used via
##' ## pkg = "grid" as follows.
##' \donttest{
##' library(ggplot2)
##'
##' ## Define our own 2d plot
##' my_points_2d_ggplot <- function(zargs, extract2d = TRUE)
##' {
##' if(extract2d) {
##' r <- extract_2d(zargs) # extract results from zargs
##' df <- data.frame(r$x, r$y) # data frame
##' names(df) <- c("x", "y")
##' cols <- zargs$x[,"Species"]
##' } else {
##' ii <- plot_indices(zargs) # the indices of the variables to be plotted
##' irs <- zargs$x # iris data
##' df <- data.frame(x = irs[,ii[1]], y = irs[,ii[2]]) # data frame
##' cols <- irs[,"Species"]
##' }
##' num2d <- zargs$num/2 # plot number among all 2d plots
##' p <- ggplot() + geom_point(data = df, aes(x = x, y = y, colour = cols),
##' show.legend = num2d == 3) +
##' labs(x = "", y = "") # 2d plot
##' if(num2d == 3) p <- p + theme(legend.position = "bottom", # legend for last 2d plot
##' legend.title = element_blank())
##' ggplot_gtable(ggplot_build(p)) # 2d plot as grob
##' }
##'
##' ## Plotting
##' iris. <- iris
##' colnames(iris.) <- gsub("\\\\.", " ", x = colnames(iris)) # => nicer 1d labels
##' zenplot(iris., n2dplots = 3, plot2d = "my_points_2d_ggplot", pkg = "grid")
##' zenplot(iris., n2dplots = 3,
##' plot2d = function(zargs) my_points_2d_ggplot(zargs, extract2d = FALSE),
##' pkg = "grid")
##' }
##'
##'
##' ### Providing your own data structure ##########################################
##'
##' \donttest{
##' ## Danger zone: An example with a new data structure (here: a list of *lists*)
##' ## Note: - In this case, we most likely need to provide both plot1d and plot2d
##' ## (but not in this case here since arrow_1d_graphics() does not depend
##' ## on the data structure)
##' ## - Note that we still make use of zargs here.
##' ## - Also note that the variables are not correctly aligned anymore:
##' ## In the ggplot2 examples we guaranteed this by plot_indices(),
##' ## but here we don't. This then still produces our layout but the
##' ## x/y axis of adjacent plots might not be the same anymore. This is
##' ## fine if only a certain order of the plots is of interest, but
##' ## not a comparison between adjacent plots.
##' z <- list(list(1:5, 2:1, 1:3), list(1:5, 1:2))
##' zenplot(z, n2dplots = 4, plot1d = "arrow", last1d = FALSE,
##' plot2d = function(zargs, ...) {
##' r <- unlist(zargs$x, recursive = FALSE)
##' num2d <- zargs$num/2 # plot number among 2d plots
##' x <- r[[num2d]]
##' y <- r[[num2d + 1]]
##' if(length(x) < length(y)) x <- rep(x, length.out = length(y))
##' else if(length(y) < length(x)) y <- rep(y, length.out = length(x))
##' plot(x, y, type = "b", xlab = "", ylab = "")
##' }, ispace = c(0.2, 0.2, 0.1, 0.1))
##' }
##'
##'
##' ### Zenplots based on 3d lattice plots #########################################
##'
##' \donttest{
##' library(lattice)
##' library(grid)
##' library(gridExtra)
##'
##' ## Build a list of cloud() plots (trellis objects)
##' ## Note:
##' ## - 'grid' problem: Without print(), the below zenplot() may fail (e.g.,
##' ## in fresh R sessions) with: 'Error in UseMethod("depth") :
##' ## no applicable method for 'depth' applied to an object of class "NULL"'
##' ## - col = "black" inside scales is needed to make the ticks show
##' mycloud <- function(x, num) {
##' lim <- extendrange(0:1, f = 0.04)
##' print(cloud(x[, 3] ~ x[, 1] * x[, 2], xlim = lim, ylim = lim, zlim = lim,
##' xlab = substitute(U[i.], list(i. = num)),
##' ylab = substitute(U[i.], list(i. = num + 1)),
##' zlab = substitute(U[i.], list(i. = num + 2)),
##' zoom = 1, scales = list(arrows = FALSE, col = "black"),
##' col = "black",
##' par.settings = list(standard.theme(color = FALSE),
##' axis.line = list(col = "transparent"),
##' clip = list(panel = "off"))))
##' }
##' plst.3d <- lapply(1:4, function(i)
##' mycloud(x[,i:(i+2)], num = i)) # list of trellis objects
##'
##' ## Preparing the zenplot
##' num <- length(plst.3d)
##' ncols <- 2
##' turns <- c(rep("r", 2*(ncols-1)), "d", "d",
##' rep("l", 2*(ncols-1)), "d")
##' plot2d <- function(zargs) {
##' num2d <- (zargs$num+1)/2
##' vp <- vport(zargs$ispace, xlim = 0:1, ylim = 0:1)
##' grob(p = zargs$x[[num2d]], vp = vp, cl = "lattice") # convert trellis to grid object
##' ## Note: For further plots, Work with
##' ## gTree(children = gList(grob(zargs$x[[num2d]], vp = vp,
##' ## cl = "lattice")))
##' }
##'
##' ## Zenplot
##' ## Note: We use a list of *plots* here already (not data)
##' zenplot(plst.3d, turns = turns, n2dplots = num, pkg = "grid", first1d = FALSE,
##' last1d = FALSE, plot1d = "arrow_1d_grid", plot2d = plot2d)
##' }
zenplot <- function(x, turns = NULL, first1d = TRUE, last1d = TRUE,
n2dcols = c("letter", "square", "A4", "golden", "legal"),
n2dplots = NULL,
plot1d = c("label", "points", "jitter", "density", "boxplot",
"hist", "rug", "arrow", "rect", "lines", "layout"),
plot2d = c("points", "density", "axes", "label", "arrow",
"rect", "layout"),
zargs = c(x = TRUE, turns = TRUE, orientations = TRUE,
vars = TRUE, num = TRUE, lim = TRUE, labs = TRUE,
width1d = TRUE, width2d = TRUE,
ispace = match.arg(pkg) != "graphics"),
lim = c("individual", "groupwise", "global"),
labs = list(group = "G", var = "V", sep = ", ", group2d = FALSE),
pkg = c("graphics", "grid", "loon"),
method = c("tidy", "double.zigzag", "single.zigzag", "rectangular"),
width1d = if(is.null(plot1d)) 0.5 else 1, width2d = 10,
ospace = if(pkg == "loon") 0 else 0.02,
ispace = if(pkg == "graphics") 0 else 0.037,
draw = TRUE, ...)
{
### Check and define basic variables ###########################################
## Check whether 'x' is of standard form and check 'n2dplots'
if(is.standard(x) && is.null(n2dplots)) {
n2dplots <- num_cols(x) - 1
} else {
if(is.null(n2dplots))
stop("'n2dplots' must be provided if 'x' is not a vector, matrix, data.frame or list of such.")
}
if(!is.numeric(n2dplots) || n2dplots < 0 || (n2dplots %% 1 != 0))
stop("'n2dplots' must be a nonnegative number.")
## Check zargs
nms <- names(zargs)
if(!is.logical(zargs) || is.null(nms) || any(nms == ""))
stop("'zargs' has to be a (fully) named, logical vector.")
if(!all(nms %in% c("x", "turns", "orientations", "vars", "num", "lim", "labs", "width1d", "width2d", "ispace")))
stop("The only valid components of 'zargs' are \"x\", \"turns\", \"orientations\", \"vars\", \"num\", \"lim\", \"labs\", \"width1d\", \"width2d\" or \"ispace\".")
## Check lim
if(is.character(lim)) {
lim <- match.arg(lim)
} else {
if(!(is.numeric(lim) && length(lim) == 2))
stop("'lim' must be a character string or numeric(2).")
}
## Default for n2dcols
if(is.character(n2dcols))
n2dcols <- n2dcols_aux(n2dplots, method = n2dcols)
## Check logicals and turns
stopifnot(is.logical(first1d), is.logical(last1d), is.logical(draw))
if(n2dplots == 0 && (!first1d || !last1d))
stop("'first1d' or 'last1d' can only be FALSE if 'n2dplots' is >= 1.")
if(is.null(turns)) { # turns not provided => use n2dcols and method
stopifnot(length(n2dcols) == 1, n2dcols >= 1)
if(n2dplots >= 2 && n2dcols < 2)
stop("If the number of 2d plots is >= 2, n2dcols must be >= 2.")
method <- match.arg(method)
} else { # turns provided
## Check length of 'turns'
turn_checker(turns, n2dplots = n2dplots, first1d = first1d, last1d = last1d)
}
## Check width1d, width2d
## Note: Use the same defaults in the respective *_1d/2d_graphics/grid functions
stopifnot(length(width1d) == 1, width1d > 0, length(width2d) == 1, width2d > 0)
## Check pkg
## Note: If you provide your own function, you have to choose 'pkg' accordingly
pkg <- match.arg(pkg)
if(pkg == "grid" && !requireNamespace("grid", quietly = TRUE))
stop("Package 'grid' is not available.")
if(pkg == "loon" && !requireNamespace("loon", quietly = TRUE))
stop("Package 'loon' is not available.")
## Check plot1d
if(missing(plot1d)) plot1d <- match.arg(plot1d)
if(!is.null(plot1d)) {
if(is.character(plot1d)) {
if(plot1d %in% eval(formals(zenplot)$plot1d)) # we don't use partial matching here as this could conflict with a user's provided string
plot1d <- paste(plot1d, "1d", pkg, sep = "_")
## Note: see below for plot2d
} else {
if(!is.function(plot1d))
stop("'plot1d' has to be either a character string or a function.")
}
if(!plot_exists(plot1d))
stop("Function provided as argument 'plot1d' does not exist.")
} # => plot1d either NULL, "<defaults>_<pkg>", a string of an existing function or an existing function
## Check plot2d
if(missing(plot2d)) plot2d <- match.arg(plot2d)
if(!is.null(plot2d)) {
if(is.character(plot2d)) {
if(plot2d %in% eval(formals(zenplot)$plot2d)) # we don't use partial matching here as this could conflict with a user's provided string
plot2d <- paste(plot2d, "2d", pkg, sep = "_")
## Note: We don't throw an error in the 'else' case as the user can provide
## a string of an existing or self-defined function as well. This may
## lead to problems. For example, if plot2d = "lines" (does not exist
## as one of the provided options), R's 1d lines() function is used
## (which of course fails).
} else {
if(!is.function(plot2d))
stop("'plot2d' has to be either a character string or a function")
}
if(!plot_exists(plot2d))
stop("Function provided as argument 'plot2d' does not exist.")
} # => plot2d either NULL, "<defaults>_<pkg>", a string of an existing function or an existing function
## Check ospace and ispace
if(length(ospace) != 4) ospace <- rep(ospace, length.out = 4)
if(length(ispace) != 4) ispace <- rep(ispace, length.out = 4)
### 1) Get arguments, variable names etc., call unfold(), determine layout #####
## Get '...' arguments
.args <- list(...)
## Call unfold() to compute the path and corresponding layout
## Note: This is *independent* of the data
pathLayout <- unfold(n2dplots, turns = turns, n2dcols = n2dcols, method = method,
first1d = first1d, last1d = last1d,
width1d = width1d, width2d = width2d)
path <- pathLayout$path
layout <- pathLayout$layout
bbs <- layout$boundingBoxes
vars <- layout$vars # 2-column matrix of plot variables (= indices)
dims <- layout$dimensions
orientations <- layout$orientations
layoutWidth <- layout$layoutWidth
layoutHeight <- layout$layoutHeight
turns <- path$turns
nPlots <- nrow(bbs)
stopifnot(nPlots == nrow(vars)) # fail-safe programming
## Determine layout
fg.rows <- unique(bbs[,c("bottom", "top"), drop = FALSE])
fg.rows <- fg.rows[order(fg.rows[,1], decreasing = TRUE),, drop = FALSE]
fg.cols <- unique(bbs[,c("left", "right"), drop = FALSE])
fg.cols <- fg.cols[order(fg.cols[,1], decreasing = FALSE),, drop = FALSE]
fg.nrow <- nrow(fg.rows)
fg.ncol <- nrow(fg.cols)
heights <- (fg.rows[, "top"] - fg.rows[,"bottom"]) / layoutHeight
widths <- (fg.cols[,"right"] - fg.cols[, "left"]) / layoutWidth
### 2) Determine formal arguments of plot1d() and plot2d() to be passed ########
## Decide whether to add the object named arg to the argument list zargs
add_to_zargs <- function(arg) {
exists.arg <- arg %in% names(zargs)
!exists.arg || (exists.arg && zargs[[arg]]) # if not appearing in zargs or appearing and TRUE, add it to the argument list zargs (only if set to FALSE, they are omitted)
}
## Determine whether the formal argument 'zargs' needs to be constructed,
## filled and passed on to plot1d() and plot2d()
## 1d plots
zargs1d <- list()
if(!is.null(plot1d) && "zargs" %in% names(eval(formals(plot1d)))) { # if 'zargs' is a formal argument of plot1d()
if(add_to_zargs("x")) zargs1d <- c(zargs1d, list(x = x)) # the original data object
if(add_to_zargs("turns")) zargs1d <- c(zargs1d, list(turns = turns)) # the vector of turns
if(add_to_zargs("orientations")) zargs1d <- c(zargs1d, list(orientations = orientations)) # the vector of orientations
if(add_to_zargs("vars")) zargs1d <- c(zargs1d, list(vars = vars)) # the 2-column matrix of plot variables
if(add_to_zargs("lim")) zargs1d <- c(zargs1d, list(lim = lim)) # character string containing the plot limits or plot limits themselves
if(add_to_zargs("labs")) zargs1d <- c(zargs1d, list(labs = labs)) # the argument 'labs' of zenplot()
if(add_to_zargs("width1d")) zargs1d <- c(zargs1d, list(width1d = width1d)) # the width of the 1d plots
if(add_to_zargs("width2d")) zargs1d <- c(zargs1d, list(width2d = width2d)) # the width of the 2d plots
if(add_to_zargs("num")) zargs1d <- c(zargs1d, list(num = NULL)) # current plot number
if(add_to_zargs("ispace")) zargs1d <- c(zargs1d, list(ispace = ispace)) # the inner space
}
## 2d plots
zargs2d <- list()
if(!is.null(plot2d) && "zargs" %in% names(eval(formals(plot2d)))) { # if 'zargs' is a formal argument of plot2d()
if(add_to_zargs("x")) zargs2d <- c(zargs2d, list(x = x)) # the original data object
if(add_to_zargs("turns")) zargs2d <- c(zargs2d, list(turns = turns)) # the vector of turns
if(add_to_zargs("orientations")) zargs2d <- c(zargs2d, list(orientations = orientations)) # the vector of orientations
if(add_to_zargs("vars")) zargs2d <- c(zargs2d, list(vars = vars)) # the 2-column matrix of plot variables
if(add_to_zargs("lim")) zargs2d <- c(zargs2d, list(lim = lim)) # character string containing the plot limits or plot limits themselves
if(add_to_zargs("labs")) zargs2d <- c(zargs2d, list(labs = labs)) # the argument 'labs' of zenplot()
if(add_to_zargs("width1d")) zargs2d <- c(zargs2d, list(width1d = width1d)) # the width of the 1d plots
if(add_to_zargs("width2d")) zargs2d <- c(zargs2d, list(width2d = width2d)) # the width of the 2d plots
if(add_to_zargs("num")) zargs2d <- c(zargs2d, list(num = NULL)) # current plot number
if(add_to_zargs("ispace")) zargs2d <- c(zargs2d, list(ispace = ispace)) # the inner space
}
### Plot #######################################################################