-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathall.r
1698 lines (1534 loc) · 59 KB
/
all.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
#' Evaluate aesthetics by group
#'
#' aesply splits a data frame into pieces, evaluates a list of aesthetics within
#' each piece, and combines the results into a new data frame. Each aesthetic in
#' the list must return either a single value per piece or a single value per
#' row in the piece.
#'
#' @keywords internal
#' @param data a data frame
#' @param .vars a vector of variables to split data by. Each element should be
#' the name of a variable in data saved as a character string.
#' @param aesthetics an object of class uneval, usually the output of
#' \code{\link[ggplot2]{aes}}
#' @return a data frame
#' @export
aesply <- function(data, .var, aesthetics) {
no.ply <- unlist(lapply(aesthetics, first_name)) == "I"
if (!any(no.ply)) {
return(compact(eval.plyr(aesthetics, data, .var)))
}
all.aes <- lapply(aesthetics[no.ply], remove_I)
evaled <- compact(eval.quoted(all.aes, data))
lengths <- vapply(evaled, length, integer(1))
n <- if (length(lengths) > 0) max(lengths) else 0
wrong <- lengths != 1 & lengths != n
if (any(wrong)) {
stop("Aesthetics must either be length one, or the same length as the data",
"Problems:", paste(aesthetics[wrong], collapse = ", "),
call. = FALSE)
}
data <- cbind(data, evaled)
all.data <- data.frame(evaled)
aesthetics[no.ply] <- lapply(names(aesthetics)[no.ply], as.name)
compact(eval.plyr(aesthetics, data, .var))
}
#' replace I() with identity
#'
#' remove_I searches through an expression for the \code{\link{I}} function and
#' replaces it with \code{\link{identity}}. remove_I is used when an aesthetic
#' has been surrounded with I() to prevent groupwise calculation.
#' @param expr an expression
remove_I <- function(expr) {
Identity <- function(x) {
if (x == "I") x <- quote(identity)
x
}
as.call(lapply(expr, Identity))
}
#' Evaluate a list of expressions by group
#'
#' Evaluates qoted variables by group in a data frame. Based on
#' \code{\link[plyr]{eval.quoted}}.
#' @keywords internal
#' @param exprs a list of expressions
#' @param data a data frame
#' @param by a vector of character strings that specify variable names in data.
#' data will be split into groups based on the unique combinations of the values
#' of these variables within the data frame. exprs will be evaluated separately
#' for each group.
#' @param enclos an environment
#' @return a data frame formed by combining the results of evaluating exprs in
#' each group of data
#' @export
eval.plyr <- function (exprs, data = NULL, by = NULL, enclos = NULL,
try = FALSE) {
if (is.numeric(exprs))
return(envir[exprs])
qenv <- if (is.quoted(exprs))
attr(exprs, "env")
else parent.frame()
if (is.null(data))
data <- qenv
if (is.data.frame(data) && is.null(enclos))
enclos <- qenv
if (try) {
results <- failwith(NULL, ddply, quiet = TRUE) (data, by, apply_maps,
exprs, qenv)
} else {
results <- ddply(data, by, apply_maps, exprs, qenv)
}
results
}
#' Calculate aesthetic values for a data frame
#'
#' apply_maps evaluates a mapping within a data frame to calculate aesthetic
#' values. apply_maps is intended to be used in conjunction with
#' \code{\link[plyr]{ddply}}. If each mapping returns a single value,
#' apply_mapping will return a single row of data. This provides a convenient
#' way to reduce a set of geoms, similar to using \code{\link[plyr]{summarise}}
#' with ddply.
#' @keywords internal
#' @param data a data frame
#' @param mapping an object of class uneval, usually the output of
#' \code{\link[ggplot2]{aes}}
#' @param enclos and environment
#' @return a data frame
#' @export
apply_maps <- function(data, mapping, enclos = parent.frame()) {
map <- null_omit(mapping)
vars <- llply(map, eval, envir = data, enclos)
n <- nrow(data)
vars <- lapply(vars, condense)
lengths <- unlist(lapply(vars, length))
wrong <- lengths != 1 & lengths != n
if (any(wrong)) {
stop(paste(
"Aesthetics must either be length one, or the same length as the data",
"Problems:", paste(names(wrong)[wrong], collapse = ", ")),
call. = FALSE)
}
data.frame(vars)
}
#' reduce a single valued vector to a single element
#' @keywords internal
#' @param var a vector
#' @return a vector of length one if var only contains one unique value, var
#' otherwise
condense <- function(var) {
if (length(unique(var)) == 1) {
return(unique(var))
}
var
}
#' Coxcomb glyphs
#'
#' geom_coxcomb draws the type of glyph commonly called a coxcomb plot or polar
#' area plot, popularized by Florence Nightingale.
#'
#' @param mapping The aesthetic mapping, usually constructed with
#' \code{\link[ggplot2]{aes}}. Only needs to be set at the layer level if you
#' are overriding the plot defaults.
#' @param data A layer specific dataset - only needed if you want to override
#' the plot defaults
#' @param stat The statistical transformation to use for this layer.
#' @param position The position adjustment to use for overlapping points in this
#' layer
#' @param npoints the number of points to use when drawing the arcs with line
#' segments. Defaults to 10.
#' @param na.rm If FALSE (the default), removes missing values with a warning.
#' If TRUE, silently removes missing variables.
#' @param ... other arguments passed on to \code{\link[ggplot2]{layer}}. This
#' can include aesthetics whose values you want to set, not map. See
#' \code{\link[ggplot2]{layer}} for more details.
#'
#' @section Aesthetics
#' geom_coxcomb understands the following aesthetics: x, y, colour, fill, size,
#' linetype, weight, and alpha.
#'
#' @export
geom_coxcomb <- function(mapping = NULL, data = NULL, stat = "bin",
position = "identity", npoints = 10, na.rm = FALSE, ...) {
GeomCoxcomb$new(mapping = mapping, data = data, stat = stat,
position = position, npoints = npoints, na.rm = na.rm, ...)
}
GeomCoxcomb <- proto(ggplot2:::Geom, {
objname <- "coxcomb"
default_stat <- function(.) StatBin
default_pos <- function(.) PositionIdentity
default_aes <- function(.) aes(colour=NA, fill="grey20", size=0.5, linetype=1,
weight = 1, alpha = NA)
required_aes <- c("x")
reparameterise <- function(., df, params) {
df <- transform(df,
xmin = 2 * pi / max(x) * (x - 1),
xmax = 2 * pi / max(x) * x)
# to create equal areas
adjust_y <- function(df) {
if (length(df$y) == 1) {
df$ymin <- 0
df$ymax <- df$y
return(df)
}
span <- df$xmax[1] - df$xmin[1]
df$y[1] <- sqrt(2 / span * df$y[1])
for ( i in 2:length(df$y)) {
df$y[i] <- sqrt(2 / span * df$y[i - 1])
}
df$ymax <- cumsum(df$y)
df$ymin <- c(0, df$ymax[-length(df$y)])
df
}
df <- ddply(df, c("x", "PANEL"), adjust_y)
df$section <- id(df[c("x", "group")], drop = TRUE)
# create polygon points
poly_curve <- function(df, npoints) {
non.pos <- setdiff(names(df), c(.x_aes, .y_aes, "count", "ndensity",
"ncount", "density"))
theta <- seq(df$xmin, df$xmax, length = npoints)
theta <- c(theta, theta[length(theta):1])
r <- c(rep(df$ymin, npoints), rep(df$ymax, npoints))
x <- r*cos(theta)
y <- r*sin(theta)
df <- df[1, non.pos]
row.names(df) <- NULL
df <- cbind(df, x, y)
rbind(df, df[1, ])
}
ddply(df, c("section", "group", "PANEL"), poly_curve, params$npoints)
}
draw <- draw_groups <- function(., data, scales, coordinates, ...) {
polys <- dlply(data, c("section", "PANEL"), function(df) {
ggname("polygon", gTree(children=gList(
with(coord_munch(coordinates, df, scales),
polygonGrob(x, y, default.units="native",
gp=gpar(col=colour, fill=alpha(fill, alpha), lwd=size * .pt,
lty=linetype))
)
)))
})
ggname("coxcomb", do.call("grobTree", polys))
}
guide_geom <- function(.) "polygon"
draw_legend <- function(., data, ...) {
data <- aesdefaults(data, .$default_aes(), list(...))
with(data, grobTree(
rectGrob(gp = gpar(col = colour, fill = alpha(fill, alpha),
lty = linetype)),
linesGrob(gp = gpar(col = colour, lwd = size * .pt,
lineend="butt", lty = linetype))
))
}
new <- function(., mapping = NULL, data = NULL, stat = NULL,
position = NULL, npoints = 10, na.rm = FALSE, ...) {
missing <- !(c("angle") %in% names(mapping))
if (any(missing)) {
stop("Missing required aesthetic: angle", call. = FALSE)
}
names(mapping)[names(mapping) == "angle"] <- "x"
mapping$section <- coxcomb_sections(mapping)
lyr <- do.call("layer", list(mapping = mapping, data = data, stat = stat,
geom = ., position = position, na.rm = na.rm, ...))
lyr$geom_params$npoints <- npoints
lyr
}
})
# ensures that continuous fill, alpha, and colour variables generate groups at
# build time as if they were discrete
coxcomb_sections <- function(mapping) {
sections <- mapping[c("alpha", "fill", "colour")]
sections <- sections[!unlist(lapply(sections, is.null))]
names(sections) <- NULL
if (is.null(sections)) return(NULL)
as.call(c(quote(interaction), sections))
}#' Star glyphs
#'
#' geom_star draws the type of glyph commonly called a star plot, radar plot,
#' or polar plot.
#'
#' @param mapping The aesthetic mapping, usually constructed with
#' \code{\link[ggplot2]{aes}}. Only needs to be set at the layer level if you
#' are overriding the plot defaults.
#' @param data A layer specific dataset - only needed if you want to override
#' the plot defaults
#' @param stat The statistical transformation to use for this layer.
#' @param position The position adjustment to use for overlapping points in this
#' layer
#' @param na.rm If FALSE (the default), removes missing values with a warning.
#' If TRUE, silently removes missing variables.
#' @param ... other arguments passed on to \code{\link[ggplot2]{layer}}. This
#' can include aesthetics whose values you want to set, not map. See
#' \code{\link[ggplot2]{layer}} for more details.
#'
#' @section Aesthetics
#' geom_coxcomb understands the following aesthetics: x, y, colour, fill, size,
#' linetype, weight, and alpha.
#'
#' @export
geom_star <- function(mapping = NULL, data = NULL, stat = "identity",
position = "identity", na.rm = FALSE, ...) {
GeomStar$new(mapping = mapping, data = data, stat = stat,
position = position, ...)
}
GeomStar <- proto(ggplot2:::Geom, {
objname <- "star"
# turn cartesian coordinates polar
reparameterise <- function(., df, params) {
# scale x to be between 0 and 2*pi
df$theta <- unlist(rescale_2pi(df["angle"]))
df$r <- unlist(rescale_01(df["r"]))
include_origin <- function(data) {
data <- data[order(data$theta, data$r), ]
if (data$theta[1] > 0.01) {
first <- data[1, ]
first$theta <- 0
first$r <- 0
data <- rbind(first, data)
}
if (data$theta[length(data$theta)] < 6.27) {
last <- data[length(data$theta), ]
last$theta <- 6.28
last$r <- 0
data <- rbind(data, last)
}
data
}
df <- ddply(df, c("group", "PANEL"), include_origin)
df$x <- df$r * cos(df$theta) + df$x
df$y <- df$r * sin(df$theta) + df$y
df
}
draw <- function(., data, scales, coordinates, ...) {
data <- data[order(data$theta, data$r), ]
ggname(.$my_name(),
gTree(children = gList(
with(coord_munch(coordinates, data, scales),
polygonGrob(x, y, default.units = "native",
gp = gpar(col = colour, fill = alpha(fill, alpha),
lwd = size * .pt, lty = linetype)
)
)
))
)
}
default_stat <- function(.) StatIdentity
default_aes <- function(.) {
aes(weight = 1, colour = "grey20", fill = "NA", alpha = NA,
linetype = "solid", size = 0.5)
}
required_aes <- c("x", "y", "r", "angle")
guide_geom <- function(.) "polygon"
draw_legend <- function(., data, ...) {
data <- aesdefaults(data, .$default_aes(), list(...))
with(data, grobTree(
rectGrob(gp = gpar(col = colour, fill = alpha(fill, alpha),
lty = linetype)),
linesGrob(gp = gpar(col = colour, lwd = size * .pt,
lineend="butt", lty = linetype))
))
}
new <- function(., mapping = NULL, data = NULL, stat = NULL,
position = NULL, na.rm = FALSE, ...) {
missing <- !(c("x", "y", "r", "angle") %in% names(mapping))
if (any(missing)) {
stop(paste("Missing required aesthetics for geom_star:",
paste(c("x", "y", "r", "angle")[missing], collapse = ", ")),
call. = FALSE)
}
do.call("layer", list(mapping = mapping, data = data, stat = stat,
geom = ., position = position, na.rm = na.rm, ...))
}
})#' @include glyphs-class.r
NULL
check_glayer <- function(object) {
errors <- character()
if (!is.proto(object@layer)) {
msg <- "glayer must be a proto object."
errors <- c(errors, msg)
}
if (!("embed" %in% ls(object@layer))) {
msg <- "glayers should contain an `embed' variable. Try building with glyph() or grid()"
errors <- c(errors, msg)
}
if (length(errors) == 0)
TRUE
else
errors
}
#' @exportClass environment
#' @exportClass proto
setOldClass(c("proto", "environment"))
#' glayer class
#'
#' glayers are layers made with glyphmaps methods. They are equivalent to the
#' layers made by ggplot2 functions in all ways except that they contain extra
#' information that is used to divide the data into subplots and locate those
#' subplots witihn the layer when plotting.
#'
#' @name glayer-class
#' @rdname glayer-class
#' @exportClass glayer
#' @aliases show,glayer-method
#' @aliases c,glayer-method
#' @aliases rep,glayer-method
#' @aliases ls,glayer-method
#' @aliases [,glayer-method
#' @aliases [<-,glayer-method
#' @aliases $,glayer-method
#' @aliases $<-,glayer-method
#' @aliases +,ggplot,glayer-method
#' @aliases +,glyphs,glayer-method
#' @aliases ggtransform,glayer-method
setClass("glayer", representation(layer = "proto"), validity = check_glayer)
#' @export
setMethod("show", signature(object = "glayer"), function(object) {
print(object@layer)
})
#' @export
setMethod("c", signature(x = "glayer"), function(x, ...){
# c(get_layer(x), unlist(lapply(list(...), get_layer)))
stop("object of type 'glayer' is not subsettable")
})
#' @export
setMethod("rep", signature(x = "glayer"), function(x, ...){
stop("object of type 'glayer' is not subsettable")
})
#' @export
setMethod("[", signature(x = "glayer"),
function(x, i, j, ..., drop = TRUE) {
new("glayer", layer = x@layer[i])
}
)
#' @export
setMethod("[<-", signature(x = "glayer"), function(x, i, j, ..., value) {
x@layer[i] <- value
x
})
#' @export
setMethod("$", signature(x = "glayer"), function(x, name) {
slot(x, "layer")[[name]]
})
#' @export
setMethod("$<-", signature(x = "glayer"), function(x, name, value) {
slot(x, "layer")[[name]] <- value
x
})
#' @export
setMethod("+", signature(e1 = "ggplot", e2 = "glayer"),
function(e1, e2) {
glyph_plot(e1 + e2@layer)
}
)
#' @export
setMethod("+", signature(e1 = "glyphs", e2 = "glayer"),
function(e1, e2) {
glyph_plot([email protected] + e2@layer)
}
)
#' @export
setGeneric("ls")
#' @export
setMethod("ls", signature(name = "glayer"),
function(name, pos = -1, envir = as.environment(pos), all.names = FALSE, pattern) {
ls(slot(name, "layer"), all.names)
})
#' Create a glayer object
#'
#' glayer gives a ggplot2 layer object the S4 class glayer, see
#' \code{\link{glayer-class}}. ggplot layer objects are usually non-specific
#' \code{\link[proto]{proto}} class objects. A layer should contain an embed
#' variable before being given the class 'glayer.' See the function bodies of
#' \code{\link{glyph}} and \code{\link{grid}} for examples.
#'
#' @export glayer
#' @param layer a proto object that can be used as a layer by the
#' \code{\link[ggplot2]{ggplot2}} package (i.e, ggplot() + layer should return a
#' graph).
glayer <- function(layer) {
new("glayer", layer = layer)
}
#' Is an object (functionally) a glayer?
#'
#' Tests whether an object is or ever was a glayer.
#' @param x an R object
#' @return logical
is.glayer <- function(x) {
"embed" %in% ls(x)
}#' Build a glayer for rendering
#'
#' glayer_build takes a glyph layer (class glayer), and performs all steps
#' necessary to produce an object that can be rendered. This function outputs
#' two pieces: a list of data frames (one for each layer), and a panel object,
#' which contain all information about axis limits, breaks, etc.
#'
#' If the glayer is accompanied by regular layers, glayer_build will be used in
#' conjunction with \code{\link{glyph_build}} to build the plot for rendering.
#'
#' @keywords internal
#' @param layer an object of class glayer
#' @seealso \code{\link{print.glyphs}} and \code{\link{glyph_build}} for
#' functions that contain the complete set of steps for generating a glyphs plot
#' @export
glayer_build <- function(layer) {
if (!("embed" %in% ls(layer))) {
stop("layer does not have embedded subplots")
}
layer <- layer_clone(layer)
layer$data <- layer$assign_glyphs(layer$data)
minor <- ggplot_build(ggplot() + layer + facet_wrap("GLYPH"))
### combine subplots (minor) into single plot
# data
data <- unpanel(minor$data[[1]])
data <- layer$combine_glyphs(data)
data$PANEL <- 1L
# panel
xspan <- range(unlist(data[names(data) %in% .x_aes]))
yspan <- range(unlist(data[names(data) %in% .y_aes]))
panel <- ggplot_build(qplot(xspan, yspan))$panel
# scales
scales <- minor$plot$scales$scales
scales[[which_x(scales)]] <- panel$x_scales[[1]]
scales[[which_y(scales)]] <- panel$y_scales[[1]]
# axis labels
if (!is.null(layer$embed$major.aes)) {
labels <- labs(layer$embed$major.aes)
minor$plot$options$labels[c("x", "y")] <- labels[c("x", "y")]
}
# make build
minor$data <- list(data)
minor$panel <- panel
minor$plot$facet <- facet_null()
minor$plot$scales$scales <- scales
minor
}
#' Format data from a facet plot to use in a glyph plot
#'
#' unpanel replaces the PANEL variable of a data frame with a GLYPH variable. It
#' adjusts the data frame's group variable to retain the grouping information
#' provided by the PANEL variable.
#'
#' @param df A data frame. Should be the output of a facetted plot built with
#' \code{\link[ggplot2]{ggplot_build}}
#' @return A modified data frame. See Details.
unpanel <- function(df) {
if (!is.null(df$group)) {
df$group <- interaction(df$group, df$PANEL)
}
df$GLYPH <- as.numeric(as.character(df$PANEL))
df$PANEL <- NULL
df
}
#' find x scale
#'
#' which_x picks out the scale that controls x from a list of scales
#' @param scales A list of ggplot2 scales
which_x <- function(scales) {
vars <- names_scales(scales)
which(vars == "x")
}
#' find y scale
#'
#' which_y picks out the scale that controls y from a list of scales
#' @param scales A list of ggplot2 scales
which_y <- function(scales) {
vars <- names_scales(scales)
which(vars == "y")
}
#' Returns the first aes of a scale, to use as an identifier for the scale
#' @param scales a list of ggplot2 scales
names_scales <- function(scales) {
unlist(lapply(scales, function(s) s$aesthetics[[1]]))
}
#' Turn an ordinary layer into a layer of embedded subplots
#'
#' glyph turns an ordinary layer into a set of glyphs. Each glyph is a plot that
#' inherits the mappings, stat, and parameters of the initial layer. The mappings
#' and stat for each glyph are keyed to a subset of the layer's data. The data
#' is divided into subsets according to the glyph.by variable. Each subset is
#' represented by one glyph. Glyphs are mapped to a pair of major x and y axes
#' by major.aes. To allow interpretation, these major axes should correspond to
#' the x and y aesthetics for any other layers in the plot.
#'
#' If a layer contains no data, glyph will use the global data set for the plot.
#' This is the data set specified in \code{\link{ggplot}}.
#'
#' @param layer a ggplot2 layer object. See \code{\link[ggplot2]{layer}}.
#' @param major.aes An aesthetic mapping, usually constructed with
#' \code{\link[ggplot2]{aes}}. This mapping determines where in the major x and
#' y axes each glyph will be position. Only x and y aesthetics will be used. All
#' other aesthetics will be ignored - consider placing them in the layer's aes
#' mapping instead.
#' @param glyph.by variables to split the layer's data by, stored as a character
#' vector. Similar to .variables in \code{\link[plyr]{ddply}}. Rows in the
#' layer's data set will be assigned to subsets based on unique combinations of
#' the variables in the glyph.by vector. Each subset will be represented by a
#' single glyph mapped to the data within the subset.
#' @param width numeric or rel object. The width of each glyph. If width is
#' numeric, the glyph will be drawn with a width equal to width units on the x
#' axis. If width is of class rel (see \code{\link{rel-class}}), glyph will
#' attempt to assign an inuitive width based on the number of total glyphs and
#' their placement within the plot. The width can be scaled relative to this
#' intuitive width by changing the value of the rel object.
#' @param height numeric or rel object. The height of each glyph. If height is
#' numeric, the glyph will be drawn with a height equal to height units on the x
#' axis. If height is of class rel (see \code{\link{rel-class}}), glyph will
#' attempt to assign an inuitive height based on the number of total glyphs and
#' their placement within the plot. The height can be scaled relative to this
#' intuitive height by changing the value of the rel object.
#' @param x_scale function. The scaling to use for the x axis within each glyph.
#' If x_scale equals \code{\link{identity}}(default), the x limits within each
#' glyph will correspond to the range of x across all glyphs. This aids comparison
#' because each glyph will use the same scale. If x_scale equals \code{\link{free}},
#' each glyph will use its own x scale. The limits of this scale will be set to
#' the range of x values in that glyph.
#' @param y_scale function. y_scale behaves the same as x_scale but controls the
#' scales for the y axis within each glyph.
#' @param merge.overlaps logical. If TRUE sets of glyphs that are connected by
#' overlapping boundaries will be merged and plotted as a single glyph. This new
#' glyph combines the data of the overlapping glyphs and is plotted in the
#' centered location of the glyphs (maen x and y values).
#' @param reference function. Function used to create reference objects for
#' glyphs. If NULL, no reference objects are used. Reference objects are plotted
#' on a layer beneath the glyphs. They provide a consistent frame of reference to
#' aid comparisons between the glyphs. Functions that create reference objects
#' include \code{\link{ref_box}}, \code{\link{ref_hline}}, \code{\link{ref_vline}},
#' and \code{\link{ref_points}}.
#' @param ply.aes logical. If TRUE (default) aesthetics are calculated separately
#' for each group, as with \code{\link{ply_aes}}. If FALSE aesthetics are
#' calculated based on entire data set for the layer.
#' @parma .ref internal argument used for plotting reference objects.
#' @return an object of class glayer
#' @export
glyph <- function(layer, major.aes, glyph.by = NULL, width = rel(0.95),
height = rel(0.95), x_scale = identity, y_scale = identity,
merge.overlaps = FALSE, reference = NULL, ply.aes = TRUE, .ref = FALSE) {
missing <- c(is.null(major.aes$x), is.null(major.aes$y))
if (any(missing)) {
stop(paste("Missing required aesthetic in major.aes:",
paste(c("x", "y")[missing], collapse = ", ")))
}
if (is.null(glyph.by)) {
stop("Missing required argument in glyph: glyph.by", call. = FALSE)
}
if (!is.function(glyph.by)) {
glyph.by <- group_by(glyph.by)
}
layer <- layer_clone(layer)
layer$embed <- list(width = width, height = height,
x_scale = x_scale, y_scale = y_scale, merge.overlaps = merge.overlaps,
major.aes = major.aes[c("x", "y")], glyph.by = glyph.by)
layer$assign_glyphs <- assign_glyphs
layer$combine_glyphs <- combine_glyphs
if (.ref) layer$combine_glyphs <- combine_refs
#layer$compute_aesthetics <- plyr_aesthetics
if (is.null(reference)) {
if (ply.aes) {
ply_aes(glayer(layer))
} else {
glayer(layer)
}
} else {
ref.layer <- reference(layer, "glyph", major.aes, glyph.by, width, height,
merge.overlaps)
if (ply.aes) {
list(ref.layer, ply_aes(glayer(layer)))
} else {
list(ref.layer, glayer(layer))
}
}
}
#' Assigns glyph membership to rows
#'
#' assign_glyphs assigns each row in a layer's data set to a glyph during
#' \code{\link{glayer_build}}. assign_glyphs sets final width and heights when
#' width and heights are passed as rel objects. It computes and the position
#' aesthetics for each glyph and stores them in the layer's embed variable to be
#' used by combine_glyphs. assign_glyphs also handles merging when
#' merge.overlaps = TRUE in a \code{\link{glyph}} call.
assign_glyphs <- function(., data) {
# major x and y
data$GLYPH <- embed$glyph.by(data)
globals <- aesply(data, "GLYPH", embed$major.aes)
too.many <- c(length(unique(globals$x)) > length(unique(globals$GLYPH)),
length(unique(globals$y)) > length(unique(globals$GLYPH)))
if (any(too.many)) {
message(paste("Major", paste(c("x", "y")[too.many], collapse = " and "),
"return more than one value per glyph. Only using first."))
globals <- unique(ddply(globals, "GLYPH", transform, x = x[1], y = y[1]))
}
# parse width, height
width <- embed$width
height <- embed$height
if (is.rel(width)) {
.$embed$width <- width <- max(resolution(vet(globals$x), zero = FALSE) *
unclass(width), (diff(range(vet(globals$x))) + unclass(width)) /
length(unique(globals$x)) * unclass(width))
}
if (is.rel(height)) {
.$embed$height <- height <- max(resolution(vet(globals$y), zero = FALSE) *
unclass(height), (diff(range(vet(globals$y))) + unclass(height)) /
length(unique(globals$y)) * unclass(height))
}
if (embed$merge) {
# search for overlapping glyphs, combine
data$.gid <- factor(data$GLYPH)
merge.key <- merge_overlaps(globals, embed$width, embed$height)
data$GLYPH <- merge.key[data$GLYPH]
globals <- aesply(data, "GLYPH", embed$major.aes)
.$mapping <- add_gid(mapping)
too.many <- c(length(unique(globals$x)) > length(unique(globals$GLYPH)),
length(unique(globals$y)) > length(unique(globals$GLYPH)))
if (any(too.many)) {
message(paste("Major", paste(c("x", "y")[too.many], collapse = " and "),
"return more than one value per glyph. Only using first."))
globals <- unique(ddply(globals, "GLYPH", transform, x = x[1], y = y[1]))
}
}
.$embed$globals <- globals
data
}
#' Calculate final positions in a glayer
#'
#' combine_glyphs calculates the final positions for every location in a glayer.
#' glayer_build first builds each glyph separately as if it were a facet. If
#' plotted, these glyphs would overlap with each other. combine_glyph relocates
#' each glyph based on the global positions stored in the layer's embed variable.
combine_glyphs <- function(., data) {
data <- join(data, globalize(embed$globals), by = "GLYPH")
xvar <- get_xs(data)
yvar <- get_ys(data)
# scale if necessary
if (!identical(embed$x_scale, identity) ||
!identical(embed$y_scale, identity)) {
data <- ddply(data, "GLYPH", function(df) {
df[xvar] <- embed$x_scale(df[xvar])
df[yvar] <- embed$y_scale(df[yvar])
df
})
}
# update x and y related variables
# don't scale individually or xmin and xmax's will end up on top of
# one another
data[xvar] <- vet(data$X) + rescale_11(data[xvar]) * embed$width/2
data[yvar] <- vet(data$Y) + rescale_11(data[yvar]) * embed$height/2
data$X <- NULL
data$Y <- NULL
data
}
#' combine_refs relocates reference objects within a layer. It works exactly like
#' combine_glyphs but does not rescale the x and y variables for the reference
#' object.
combine_refs <- function(., data) {
data <- join(data, globalize(embed$globals), by = "GLYPH")
xvar <- get_xs(data)
yvar <- get_ys(data)
# scale if necessary
if (!identical(embed$x_scale, identity) ||
!identical(embed$y_scale, identity)) {
data <- ddply(data, "GLYPH", function(df) {
df[xvar] <- embed$x_scale(df[xvar])
df[yvar] <- embed$y_scale(df[yvar])
df
})
}
# update x and y related variables
# don't scale individually or xmin and xmax's will end up on top of
# one another
data[xvar] <- vet(data$X) + data[xvar] * embed$width/2
data[yvar] <- vet(data$Y) + data[yvar] * embed$height/2
data$X <- NULL
data$Y <- NULL
data
}
#' Ensure that an object is numeric
#'
#' vet tests whether an object is a factor or character string. If so it
#' attempts to coerce the variable to numeric.
#'
#' @keywords internal
#' @param x an R object
#' @return a numeric object
#' @export
vet <- function(x) {
if (is.character(x)) {
x <- as.numeric(factor(x))
}
if (is.factor(x)) {
x <- as.numeric(x)
}
x
}
#' rename global x and y variables in capitals
#' @keywords internal
#' @param obj a data.frame
#' @export
globalize <- function(obj){
names(obj)[names(obj) == "x"] <- "X"
names(obj)[names(obj) == "y"] <- "Y"
obj
}
#' Include .gid in groupings
#'
#' add_gid intelligently adds the .gid variable to the group slot of an uneval
#' object. If the group slot is NULL, add_gid sets group = .gid. If the group
#' slot already contains a mapping, add_gid adds .gid to this mapping with
#' interaction().
#'
#' @keywords internal
#' @param aes_group the group value of an uneval object
#' @export
add_gid <- function(maps) {
if (is.null(maps$group)) {
maps$group <- as.name(".gid")
} else {
maps$group <- as.call(list(quote(interaction), as.name(".gid"),
maps$group))
}
maps
}#' Build a glyphs object for rendering
#'
#' glyph_build takes a glyph plot object (class glyphs), and performs all steps
#' necessary to produce an object that can be rendered. This function outputs
#' two pieces: a list of data frames (one for each layer), and a panel object,
#' which contain all information about axis limits, breaks, etc.
#'
#' @keywords internal
#' @param layer an object of class glayer
#' @seealso \code{\link{print.glyphs}} for functions that contain the complete
#' set of steps for generating a glyphs plot
#' @export
glyph_build <- function(plot){
if (length(plot$layers) == 0) stop("No layers in plot", call.=FALSE)
if (!identical(plot$facet, facet_null())) {
stop("glyphs do not support facetting", call. = FALSE)
}
plot <- ggplot2:::plot_clone(plot)
layers <- plot$layers
layers <- propogate_data(layers, plot$data)
# separate into glayers and normal layers
gls <- unlist(lapply(layers, is.glayer))
if (all(!gls)) return(ggplot_build(plot))
if (all(gls) && sum(gls) == 1) return(glayer_build(layers[[gls]]))
glayers <- layers[gls]
plot$layers <- layers[!gls]
gl.order <- seq_along(layers)[gls]
nl.order <- seq_along(layers)[!gls]
# build normal layers
normal <- NULL
if (length(plot$layers) > 0) {
normal <- ggplot_build(plot)
}
# build glyph layers (embedded plots)
embedded <- list()
for (i in seq_along(glayers)) {
embedded[[i]] <- glayer_build(glayers[[i]])
}
### combine the builds
# plot
build <- embedded[[1]]
# data
# take care to order
edata <- lapply(embedded, function(bd) bd$data[[1]])
data <- list()
data[gl.order] <- edata
data[nl.order] <- normal$data
# panel
xspan <- range(unlist(lapply(data, function(df) df[names(df) %in% .x_aes])))
yspan <- range(unlist(lapply(data, function(df) df[names(df) %in% .y_aes])))
panel <- ggplot_build(qplot(xspan, yspan))$panel
# scales
# collect all unique scales
scales <- build$plot$scales$scales
scales[[which_x(scales)]] <- panel$x_scales[[1]]
scales[[which_y(scales)]] <- panel$y_scales[[1]]
scale.names <- names_scales(scales)
for (i in seq_along(embedded[-1])) {
escales <- embedded[[i + 1]]$plot$scales$scales
unique <- !(names_scales(escales) %in% scale.names)
scales <- c(scales, escales[unique])
scale.names <- names_scales(scales)
}
nscales <- normal$plot$scales$scales
unique <- !(names_scales(nscales) %in% scale.names)
scales <- c(scales, nscales[unique])
# layers
# take care to order
gl.layers <- build$plot$layers
for (i in seq_along(embedded[-1])) {
gl.layers <- c(gl.layers, embedded[[i + 1]]$plot$layers)
}
layers[gl.order] <- gl.layers
layers[nl.order] <- normal$plot$layers
# labels
# collect all unique labels
labels <- build$plot$option$labels
for (i in seq_along(embedded[-1])) {
new.labels <- embedded[[i+1]]$plot$options$labels
unique <- !(names(new.labels) %in% names(labels))
labels <- c(labels, new.labels[unique])
}
norm.labels <- normal$plot$options$labels
unique <- !(names(norm.labels) %in% names(labels))
labels <- c(labels, norm.labels[unique])
# make build
build$data <- data
build$panel <- panel
build$plot$scales$scales <- scales
build$plot$layers <- layers
build$plot$options$labels <- labels
build
}
#' Ensure each layer contains a data set
#'
#' propogate_data checks each layer for a data set. If none is found it assigns
#' a copy of the plot level data set to the layer. propogate_data avoids the
#' side effects of ggplot2:::map_layout, which performs a similar function.
#' @param layers ggplot2 layer objects
#' @param plot_data the global data set for a ggplot2 plot
propogate_data <- function(layers, plot_data) {
ensure_data <- function(layer){
if (inherits(layer$data, "waiver")) {
layer$data <- plot_data
}
layer
}
lapply(layers, ensure_data)
}check_glyphs <- function(object) {