Skip to content

Commit 527a156

Browse files
authored
Merge pull request #226 from thackl/develop
Adding new features align() and geom_link_curved() and solving some smaller open issues
2 parents 2a0d644 + ea9216d commit 527a156

27 files changed

+341
-102
lines changed

DESCRIPTION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Package: gggenomes
22
Title: A Grammar of Graphics for Comparative Genomics
3-
Version: 1.1.0
3+
Version: 1.1.0.9000
44
Authors@R: c(
55
person("Thomas", "Hackl", email = "t.hackl@rug.nl", role = c("aut", "cre")),
66
person("Markus J.", "Ankenbrand", email = "iimog@iimog.org", role = c("aut")),

NAMESPACE

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ export(add_links)
9090
export(add_seqs)
9191
export(add_subfeats)
9292
export(add_sublinks)
93+
export(align)
9394
export(as_feats)
9495
export(as_links)
9596
export(as_seqs)
@@ -126,6 +127,7 @@ export(geom_gene_note)
126127
export(geom_gene_tag)
127128
export(geom_gene_text)
128129
export(geom_link)
130+
export(geom_link_curved)
129131
export(geom_link_label)
130132
export(geom_link_line)
131133
export(geom_seq)

News.md renamed to NEWS.md

File renamed without changes.

R/align.R

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
#' Align genomes relative to target genes, feats, seqs, etc.
2+
#'
3+
#' Align your genomes relative to target features, such as genes or regions of
4+
#' interest. Use the `...` argument to indicate a subset of features in one
5+
#' track as targets. If multiple features are selected per bin, they are
6+
#' treated as a single feature spanning from the leftmost to the rightmost end.
7+
#' The genomes will be shifted so that the targets features align according to
8+
#' the `.justify`.
9+
#' @param x gggenomes object
10+
#' @param ... filter expression to identify target features in target track.
11+
#' Works like `dplyr::filter()` <[`data-masking`][rlang::args_data_masking]>.
12+
#' @param .track_id track to pull from, default "genes"
13+
#' @param .justify alignment position, one of "left", "center", "right" or
14+
#' numeric between 0 and 1, default "left"
15+
#' @return gggenomes object with shifted coordinates.
16+
#' @export
17+
#' @examples
18+
#' library(patchwork)
19+
#' p <- gggenomes(emale_genes, links = emale_ava) +
20+
#' geom_link() +
21+
#' geom_gene(aes(fill = name)) +
22+
#' scale_fill_brewer(palette = "Dark2", na.value = "cornsilk3") +
23+
#' geom_bin_label()
24+
#'
25+
#' pp <-
26+
#' # left-align on MCP gene
27+
#' p |> align(name == "MCP") +
28+
#' # left-align on MCP gene after flipping bins
29+
#' p |> sync() |> align(name == "MCP") +
30+
#' # right-align on MCP gene
31+
#' p |> align(name == "MCP", .justify = "right") +
32+
#' # center-align on MCP + pri-hel gene
33+
#' p |> sync() |> align(name %in% c("MCP", "pri-hel"), .justify = "center") |>
34+
#' # and highlight the feature block we are aligning to
35+
#' locate(name %in% c("MCP", "pri-hel"), .expand = 0, .max_dist = 1e6) +
36+
#' geom_feat(data = feats(loci), color = "plum3", alpha = .5, linewidth = 5)
37+
#' # center-align by fraction on MCP and pri-hel gene (hjust-like behaviour)
38+
#' p |> align(name %in% c("MCP", "pri-hel"), .justify = .5) +
39+
#' # right-align by fraction after flipping
40+
#' p |> sync() |> align(name %in% c("MCP", "pri-hel"), .justify = 1)
41+
#'
42+
#' pp + plot_layout(guides = "collect") & geom_vline(xintercept = 0, linetype = 2)
43+
#'
44+
#' # multi contig
45+
#' s0 <- tibble::tibble(
46+
#' bin_id = c("A", "B", "B", "B", "C", "C", "C"),
47+
#' seq_id = c("a1", "b1", "b2", "b3", "c1", "c2", "c3"),
48+
#' length = c(1e4, 6e3, 2e3, 1e3, 3e3, 3e3, 3e3)
49+
#' )
50+
#'
51+
#' p <- gggenomes(seqs = s0) +
52+
#' geom_seq(aes(color = bin_id), size = 3) +
53+
#' geom_bin_label() +
54+
#' geom_seq_label() +
55+
#' expand_limits(color = c("A", "B", "C"))
56+
#'
57+
#' pp <-
58+
#' # center on everything - just omit ...
59+
#' p |> align(.track_id = "seqs", .justify = .5) +
60+
#' # right-align on contig ending in "2"
61+
#' # NOTE: there is no 2nd contig in bin A, so nothing is aligned there
62+
#' p |> align(stringr::str_detect(seq_id, "2"), .track_id = "seqs", .justify = "right")
63+
#'
64+
#' pp + plot_layout(guides = "collect") & geom_vline(xintercept = 0, linetype = 2)
65+
align <- function(x, ..., .track_id = "genes", .justify = "left"){
66+
if(is.character(.justify)){
67+
.justify <- c(left=0, center=.5, right=1)[.justify]
68+
}
69+
if(is.na(.justify)){stop("align required")}
70+
71+
a <- x |>
72+
pull_track(.track_id = .track_id, ...) |>
73+
dplyr::group_by(bin_id) |>
74+
dplyr::summarize(x = ((1-.justify) * min(c(x, xend))) + (.justify * max(c(x, xend))))
75+
shift(x, bins = a$bin_id, by=-a$x)
76+
}

R/clusters.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
#' geom_gene(aes(fill = ifelse(is.na(cluster_id), NA,
1818
#' stringr::str_glue("{cluster_id} [{cluster_size}]")
1919
#' ))) +
20-
#' scale_fill_discrete("COGs")
20+
#' scale_fill_discrete(name="COGs")
2121
#'
2222
add_clusters <- function(x, ..., .track_id = "genes") {
2323
UseMethod("add_clusters")

R/feats.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ as_feats.tbl_df <- function(x, seqs, ..., everything = TRUE) {
5858
) %>%
5959
mutate(strand = strand_chr(.data$strand))
6060

61-
x %<>% swap_if(.data$start > .data$end, .data$start, .data$end)
61+
x %<>% swap_if(start > end, start, end)
6262

6363
layout_feats(x, seqs, ...)
6464
}

R/geom.R

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -189,10 +189,17 @@ geom_bin_label <- function(
189189
mapping = NULL, data = bins(), hjust = 1, size = 3,
190190
nudge_left = 0.05, expand_left = 0.20, expand_x = NULL, expand_aes = NULL,
191191
yjust = 0, ...) {
192-
default_aes <- aes_(
193-
y = ~ ymin * yjust + ymax * (1 - yjust),
194-
x = ~ pmin(x, xend) - max_width(x, xend) * nudge_left, label = ~bin_id
192+
# default_aes <- aes_(
193+
# y = ~ ymin * yjust + ymax * (1 - yjust),
194+
# x = ~ pmin(x, xend) - max_width(x, xend) * nudge_left, label = ~bin_id
195+
# )
196+
197+
default_aes <- aes(
198+
y = ymin * yjust + ymax * (1 - yjust),
199+
x = pmin(x, xend) - max_width(x, xend) * nudge_left,
200+
label = bin_id
195201
)
202+
196203
mapping <- aes_intersect(mapping, default_aes)
197204
r <- list(geom_text(
198205
mapping = mapping, data = data,
@@ -203,7 +210,7 @@ geom_bin_label <- function(
203210
r[[2]] <- expand_limits(x = expand_x)
204211
} else if (!is.na(expand_left)) {
205212
expand_aes <- NULL
206-
default_expand_aes <- aes_(y = ~y, x = ~ x - abs(min(x) - max(xend)) * expand_left)
213+
default_expand_aes <- aes(y = y, x = x - abs(min(x) - max(xend)) * expand_left)
207214
expand_aes <- aes_intersect(expand_aes, default_expand_aes)
208215
r[[2]] <- geom_blank(mapping = expand_aes, data = data)
209216
}

R/geom_gene.R

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ makeContent.genetree <- function(x) {
259259
cds_exons <- cds_data %>%
260260
dplyr::group_by(id) %>%
261261
dplyr::summarize(
262-
dplyr::across(c(-.data$x, -.data$xend, -.data$y), first),
262+
dplyr::across(c(-x, -xend, -y), first),
263263
exons = list(exon_polys(.data$x, .data$xend, .data$y, height, arrow_width, arrow_height))
264264
)
265265
}
@@ -271,7 +271,7 @@ makeContent.genetree <- function(x) {
271271
rna_exons <- rna_data %>%
272272
dplyr::group_by(id) %>%
273273
dplyr::summarize(
274-
dplyr::across(c(-.data$x, -.data$xend, -.data$y), first),
274+
dplyr::across(c(-x, -xend, -y), first),
275275
exons = list(exon_polys(.data$x, .data$xend, .data$y, rna_height, rna_arrow_width, rna_arrow_height))
276276
)
277277
}
@@ -293,7 +293,7 @@ makeContent.genetree <- function(x) {
293293
dplyr::group_by(id) %>%
294294
dplyr::filter(n() > 1) %>%
295295
dplyr::summarize(
296-
dplyr::across(c(-.data$x, -.data$xend, -.data$y), first),
296+
dplyr::across(c(-x, -xend, -y), first),
297297
introns = list(intron_polys(.data$x, .data$xend, .data$y, intron_height))
298298
)
299299

@@ -415,5 +415,5 @@ unnest_exons <- function(x) {
415415
exons = list(exon_spans(x, xend, .data$introns)),
416416
x = NULL, xend = NULL, introns = NULL
417417
) %>%
418-
unnest(.data$exons)
418+
unnest(exons)
419419
}

R/geom_link.R

Lines changed: 78 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
#' Draw links between genomes
22
#'
3-
#' @description Draws connections between genomes, such as genome/gene/protein
4-
#' alignments and gene/protein clusters. `geom_link()` draws links as filled
5-
#' polygons, `geom_link_line()` draws a single connecting line.
3+
#' @description Draw connections between genomes, such as genome/gene/protein
4+
#' alignments and gene/protein clusters. `geom_link()` and
5+
#' `geom_link_curved()` create filled polygons between regions,
6+
#' `geom_link_line()` a single connecting line.
67
#'
78
#' Note that by default only links between adjacent genomes are computed and
89
#' shown. To compute and show all links between all genomes, set
@@ -19,6 +20,9 @@
1920
#' @inheritParams ggplot2::geom_polygon
2021
#' @param offset distance between seq center and link start. Use two values
2122
#' `c(<offset_top>, <offset_bottom>)` for different top and bottom offsets
23+
#' @param curve curvature of the link. If `NA` or `0`, the link edges will be
24+
#' straight. For curved links, higher values lead to stronger curvature.
25+
#' Typical values are between `5` and `15`.
2226
#' @return A ggplot2 layer with links.
2327
#' @export
2428
#' @examples
@@ -41,41 +45,57 @@
4145
#'
4246
#' library(patchwork) # combine plots in one figure
4347
#' p1 + p2 + p3 + p4 + plot_layout(nrow = 1)
48+
#'
49+
#' q0 <- gggenomes(emale_genes, emale_seqs) |>
50+
#' add_clusters(emale_cogs) +
51+
#' geom_seq() + geom_gene()
52+
#'
53+
#' qq <-
54+
#' # link gene clusters with polygon
55+
#' q0 + geom_link(aes(fill = cluster_id)) +
56+
#' # link with curved polygons (bezier-like)
57+
#' q0 + geom_link_curved(aes(fill = cluster_id)) +
58+
#' # link gene clusters with lines
59+
#' q0 + geom_link_line(aes(color = cluster_id))
60+
#'
61+
#' qq + plot_layout(nrow = 1, guides = "collect")
4462
geom_link <- function(
4563
mapping = NULL, data = links(), stat = "identity",
4664
position = "identity", na.rm = FALSE, show.legend = NA, inherit.aes = TRUE,
47-
offset = 0.15, ...) {
65+
offset = 0.15, curve = NA, ...) {
4866
if (length(offset) == 1) offset <- offset[c(1, 1)]
4967

50-
default_aes <- aes(y = .data$y, x = .data$x, xend = .data$xend, yend = .data$yend, xmin = .data$xmin, xmax = .data$xmax)
68+
default_aes <- aes(y = .data$y, x = .data$x, xend = .data$xend,
69+
yend = .data$yend, xmin = .data$xmin, xmax = .data$xmax)
5170
mapping <- aes_intersect(mapping, default_aes)
5271

5372
layer(
5473
geom = GeomLink, mapping = mapping, data = data, stat = stat,
5574
position = position, show.legend = show.legend, inherit.aes = inherit.aes,
56-
params = list(na.rm = na.rm, offset = offset, ...)
75+
params = list(na.rm = na.rm, offset = offset, curve = curve, ...)
5776
)
5877
}
5978
#' @rdname geom_link
79+
#' @return A ggplot2 layer with links.
80+
#' @export
81+
geom_link_curved <- function(
82+
mapping = NULL, data = links(), stat = "identity",
83+
position = "identity", na.rm = FALSE, show.legend = NA, inherit.aes = TRUE,
84+
offset = 0.15, curve = 10, ...){
85+
86+
geom_link(mapping = mapping, data = data, stat = stat, position = position,
87+
show.legend = show.legend, inherit.aes = inherit.aes, na.rm = na.rm,
88+
offset = offset, curve = curve, ...)
89+
}
90+
#' @rdname geom_link
91+
#' @return A ggplot2 layer with links.
6092
#' @export
61-
#' @examples
62-
#' q0 <- gggenomes(emale_genes, emale_seqs) |>
63-
#' add_clusters(emale_cogs) +
64-
#' geom_seq() + geom_gene()
65-
#'
66-
#' # link gene clusters with polygon
67-
#' q1 <- q0 + geom_link(aes(fill = cluster_id))
68-
#'
69-
#' # link gene clusters with lines
70-
#' q2 <- q0 + geom_link_line(aes(color = cluster_id))
71-
#'
72-
#' q1 + q2 + plot_layout(nrow = 1, guides = "collect")
73-
#'
7493
geom_link_line <- function(
7594
mapping = NULL, data = links(), stat = "identity",
7695
position = "identity", na.rm = FALSE, show.legend = NA, inherit.aes = TRUE,
7796
...) {
78-
default_aes <- aes(y = .data$y, yend = .data$yend, x = (.data$x + .data$xend) / 2, xend = (.data$xmin + .data$xmax) / 2)
97+
default_aes <- aes(y = .data$y, yend = .data$yend,
98+
x = (.data$x + .data$xend) / 2, xend = (.data$xmin + .data$xmax) / 2)
7999
mapping <- aes_intersect(mapping, default_aes)
80100

81101
layer(
@@ -88,18 +108,20 @@ geom_link_line <- function(
88108
GeomLink <- ggproto(
89109
"GeomLink", Geom,
90110
default_aes = aes(
91-
colour = "honeydew3", fill = "honeydew3", size = 0.5, linetype = 1,
111+
colour = "honeydew3", fill = "honeydew3", linewidth = 0.5, linetype = 1,
92112
alpha = 0.7
93113
),
94114
required_aes = c("x", "xend", "y", "xmin", "xmax", "yend"),
95-
draw_panel = function(self, data, panel_params, coord, linejoin = "mitre", offset = c(0.15, 0.15)) {
115+
draw_panel = function(self, data, panel_params, coord, linejoin = "mitre",
116+
offset = c(0.15, 0.15), curve = NA) {
96117
if (TRUE) { # !coord$is_linear()) {
97118
aesthetics <- setdiff(
98119
names(data), c("x", "xend", "y", "xmin", "xmax", "yend")
99120
)
100121
polys <- lapply(split(data, seq_len(nrow(data))), function(row) {
101-
poly <- link_to_poly(row$x, row$xend, row$y, row$xmin, row$xmax, row$yend, offset)
102-
aes <- vctrs::data_frame(row[aesthetics])[rep(1, 5), ]
122+
poly <- link_to_poly(row$x, row$xend, row$y, row$xmin, row$xmax,
123+
row$yend, offset, curve)
124+
aes <- vctrs::data_frame(row[aesthetics])[rep(1, nrow(poly)), ]
103125
GeomPolygon$draw_panel(cbind(poly, aes), panel_params, coord)
104126
})
105127

@@ -109,7 +131,7 @@ GeomLink <- ggproto(
109131
draw_key = draw_key_polygon
110132
)
111133

112-
link_to_poly <- function(x, xend, y, xmin, xmax, yend, offset) {
134+
link_to_poly <- function(x, xend, y, xmin, xmax, yend, offset, curve = NA) {
113135
if (y > yend) {
114136
y <- y - offset[1]
115137
yend <- yend + offset[2]
@@ -118,9 +140,35 @@ link_to_poly <- function(x, xend, y, xmin, xmax, yend, offset) {
118140
yend <- yend - offset[1]
119141
}
120142

121-
vctrs::data_frame(
122-
.name_repair = "minimal",
123-
y = c(y, y, yend, yend, y),
124-
x = c(x, xend, xmax, xmin, x)
125-
)
143+
if (is.na(curve) || curve == 0) {
144+
vctrs::data_frame(
145+
.name_repair = "minimal",
146+
y = c(y, yend, yend, y),
147+
x = c(xend, xmax, xmin, x)
148+
)
149+
} else {
150+
z1 <- sigmoid(xend, y, xmax, yend, curve)
151+
z2 <- sigmoid(xmin, yend, x, y, curve)
152+
vctrs::data_frame(
153+
.name_repair = "minimal",
154+
y = c(z1$y, z2$y),
155+
x = c(z1$x, z2$x)
156+
)
157+
}
126158
}
159+
160+
sigmoid <- function(x1, y1, x2, y2, curve = 10, breaks = 100) {
161+
# parameter along the segment
162+
t <- seq(0, 1, length.out = breaks)
163+
# linear interpolation for y
164+
y <- y1 + t * (y2 - y1)
165+
166+
# logistic “S” curve between 0 and 1
167+
s <- 1 / (1 + exp(-curve * (t - 0.5)))
168+
s <- (s - min(s)) / (max(s) - min(s)) # normalize 0–1
169+
170+
# x follows sigmoid between endpoints
171+
x <- x1 + s * (x2 - x1)
172+
173+
vctrs::data_frame(x=x, y=y)
174+
}

R/gggenomes.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@
8989
#' geom_feat(linewidth= 6, position = "identity") + # terminal inverted repeats
9090
#' geom_feat(
9191
#' data = feats(emale_ngaros), color = "turquoise4", alpha = .3,
92-
#' position = "strand", size = 16
92+
#' position = "strand", linewidth = 16
9393
#' ) +
9494
#' geom_feat_note(aes(label = type),
9595
#' data = feats(emale_ngaros),

0 commit comments

Comments
 (0)