Increase space for long axis labels in radar chart - r

I want to create a radar chart with ggirahExtra::ggRadar. The problem is that I have long labels and they are clipped. I thought I could create more space between label and plot by adding margin = margin(0,0,2,0, "cm") to element_text in axis.text, but its not working.
Any ideas how to increase the label space are welcome (apart from making the font smaller).
Add: As #tjebo suggests in the comments, it might be easier, or maybe the only way to make it work, to change the underlying functions in ggRadar especially coord_radar. Any suggestions of how to do this are welcome.
library(ggplot2)
library(ggiraphExtra)
dat <- data.frame("Item_A_Long" = 2,
"Item_B_Very_Very_Long"= 0,
"Label_Item_C" = 1,
"Item_D_Label" = 4,
"Another_very_long_label" = 3)
ggRadar(dat,
aes(
x = c(Item_A_Long,
Item_B_Very_Very_Long,
Label_Item_C,
Item_D_Label,
Another_very_long_label)
),
legend.position = "top",
colour = "white",
rescale = FALSE,
use.label = FALSE
) +
scale_y_continuous(expand = c(0,0),
limits = c(0,4)
) +
theme(panel.background = element_rect(fill = "#001957"),
# adding margin = margin(0,0,2,0, "cm") to element_text below does not help
axis.text = element_text(color = "#FFFFFF"),
panel.grid.major.y = element_blank())
Created on 2021-04-30 by the reprex package (v0.3.0)

It's a matter of clipping. The problem is also the white standard background of your drawing device. Below a hacky workaround.
turn off clipping with a modified version of ggiraphExtra::coord_radar as well as ggiraphExtra::ggRadar. Note I have removed a (very) few bits from the original ggRadar function, so if you need all arguments, you'd need to modify the function yourself.
Turn all background elements blue
Superimpose all onto a pure blue background, I am using cowplot.
library(cowplot)
library(ggplot2)
p1 <- ggRadar2(dat,
aes(
x = c(
Item_A_Long,
Item_B_Very_Very_Long,
Label_Item_C,
Item_D_Label,
Another_very_long_label
)
),
colour = "white",
rescale = FALSE,
clip = "off"
) +
theme(
plot.background = element_rect(fill = "#001957", color = "#001957"),
panel.background = element_rect(fill = "#001957"),
# adding margin = margin(0,0,2,0, "cm") to element_text below does not help
axis.text = element_text(color = "#FFFFFF"),
panel.grid.major.y = element_blank()
)
p2 <-
ggplot() +
theme_void()+
theme(panel.background = element_rect(fill = "#001957"))
ggdraw(p2) + draw_plot(p1)
the modified functions
coord_radar2 <- function(theta = "x", start = 0, direction = 1, clip = "off") {
theta <- match.arg(theta, c("x", "y"))
r <- if (theta == "x") {
"y"
} else {
"x"
}
ggproto("CoordRadar", ggplot2::CoordPolar,
theta = theta,
r = r, start = start, clip = clip,
direction = sign(direction), is_linear = function(coord) TRUE
)
}
ggRadar2 <- function(data, mapping = NULL, rescale = TRUE, legend.position = "top",
colour = "red", alpha = 0.3, size = 3, ylim = NULL, scales = "fixed",
use.label = FALSE, interactive = FALSE, clip = "off", ...) {
data <- as.data.frame(data)
(groupname <- setdiff(names(mapping), c("x", "y")))
groupname
mapping
length(groupname)
if (length(groupname) == 0) {
groupvar <- NULL
}
else {
groupvar <- ggiraphExtra:::getMapping(mapping, groupname)
}
groupvar
facetname <- colorname <- NULL
if ("facet" %in% names(mapping)) {
facetname <- ggiraphExtra:::getMapping(mapping, "facet")
}
(colorname <- setdiff(groupvar, facetname))
if ((length(colorname) == 0) & !is.null(facetname)) {
colorname <- facetname
}
data <- ggiraphExtra:::num2factorDf(data, groupvar)
(select <- sapply(data, is.numeric))
if ("x" %in% names(mapping)) {
xvars <- ggiraphExtra:::getMapping(mapping, "x")
xvars
if (length(xvars) < 3) {
warning("At least three variables are required")
}
}
else {
xvars <- colnames(data)[select]
}
(xvars <- setdiff(xvars, groupvar))
if (rescale) {
data <- ggiraphExtra:::rescale_df(data, groupvar)
}
temp <- sjlabelled::get_label(data)
cols <- ifelse(temp == "", colnames(data), temp)
if (is.null(groupvar)) {
id <- ggiraphExtra:::newColName(data)
data[[id]] <- 1
longdf <- reshape2::melt(data, id.vars = id, measure.vars = xvars)
}
else {
cols <- setdiff(cols, groupvar)
longdf <- reshape2::melt(data, id.vars = groupvar, measure.vars = xvars)
}
temp <- paste0("plyr::ddply(longdf,c(groupvar,'variable'), dplyr::summarize,mean=mean(value,na.rm=TRUE))")
df <- eval(parse(text = temp))
colnames(df)[length(df)] <- "value"
df
groupvar
if (is.null(groupvar)) {
id2 <- ggiraphExtra:::newColName(df)
df[[id2]] <- "all"
id3 <- ggiraphExtra:::newColName(df)
df[[id3]] <- 1:nrow(df)
df$tooltip <- paste0(df$variable, "=", round(
df$value,
1
))
df$tooltip2 <- paste0("all")
p <- ggplot(data = df, aes_string(
x = "variable", y = "value",
group = 1
)) +
ggiraph::geom_polygon_interactive(aes_string(tooltip = "tooltip2"),
colour = colour, fill = colour, alpha = alpha
) +
ggiraph::geom_point_interactive(aes_string(
data_id = id3,
tooltip = "tooltip"
), colour = colour, size = size)
}
else {
if (!is.null(colorname)) {
id2 <- ggiraphExtra:::newColName(df)
df[[id2]] <- df[[colorname]]
}
id3 <- ggiraphExtra:::newColName(df)
df[[id3]] <- 1:nrow(df)
df$tooltip <- paste0(
groupvar, "=", df[[colorname]], "<br>",
df$variable, "=", round(df$value, 1)
)
df$tooltip2 <- paste0(groupvar, "=", df[[colorname]])
p <- ggplot(data = df, aes_string(
x = "variable", y = "value",
colour = colorname, fill = colorname, group = colorname
)) +
ggiraph::geom_polygon_interactive(aes_string(tooltip = "tooltip2"),
alpha = alpha
) +
ggiraph::geom_point_interactive(aes_string(
data_id = id3,
tooltip = "tooltip"
), size = size)
}
p
if (!is.null(facetname)) {
formula1 <- as.formula(paste0("~", facetname))
p <- p + facet_wrap(formula1, scales = scales)
}
p <- p + xlab("") + ylab("") + theme(legend.position = legend.position)
p <- p + coord_radar2(clip = clip)
if (!is.null(ylim)) {
p <- p + expand_limits(y = ylim)
}
p
p
}

You can use the labelled package to create labels with line breaks and then set label = TRUE in ggRadar(). You can add more than one break for super long labels.
library(ggplot2)
library(ggiraphExtra)
library(labelled)
dat <- data.frame("Item_A_Long" = 2,
"Item_B_Very_Very_Long"= 0,
"Label_Item_C" = 1,
"Item_D_Label" = 4,
"Another_very_long_label" = 3)
var_label(dat$Item_A_Long ) <- "Item \nA long"
var_label(dat$Item_B_Very_Very_Long ) <- "Item_B_\nVery_\nVery_Long"
var_label(dat$Label_Item_C ) <- "Label_\nItem_C "
var_label(dat$Item_D_Label ) <- "Item_\nD_Label"
var_label(dat$Another_very_long_label ) <- "Another_very_\nlong_label"
ggRadar(dat,
aes(
x = c(Item_A_Long,
Item_B_Very_Very_Long,
Label_Item_C,
Item_D_Label,
Another_very_long_label)
),
legend.position = "top",
colour = "white",
rescale = FALSE,
use.label = TRUE
) +
scale_y_continuous(expand = c(0,0),
limits = c(0,4)
) +
theme(panel.background = element_rect(fill = "#001957"),
# adding margin = margin(0,0,2,0, "cm") to element_text below does not help
axis.text = element_text(color = "#FFFFFF"),
panel.grid.major.y = element_blank())

Related

Formatting a two variable plot animation

I've seen other posts related to this error, but I'm really struggling to find the root cause of the error.
Basically, I'm trying to animate two lines A and B from values that are read through an Excel file. The plot is fine, but the error message appears when I try to animate it.
Here is the code:
library(tidyverse)
library(readxl)
library(here)
library(scales)
library(reshape)
library(stringr)
library(gganimate)
library(gifski)
real_format <- function(largest_with_cents = 100000) {
function(x) {
x <- round_any(x, 0.01)
if (max(x, na.rm = TRUE) < largest_with_cents &
!all(x == floor(x), na.rm = TRUE)) {
nsmall <- 2L
} else {
x <- round_any(x, 1)
nsmall <- 0L
}
str_c("R$", format(x, nsmall = nsmall, trim = TRUE, big.mark = ".", decimal.mark = ",", scientific = FALSE, digits=1L))
}
}
AB2000 <- read_excel(here("AxB_2000.xlsx"))
gg_base <- ggplot(data = AB2000) +
geom_line(aes(x = as.Date(Date), y = A, colour = "A", group = 1)) +
geom_line(aes(x = as.Date(Date), y = B, colour = "B", group = 1)) +
scale_color_manual(values = c("A" = "blue", "B" = "red"))
gg_base + labs(x = "Year",y = "") +
scale_y_continuous(expand = c(0,0),
breaks=seq(0,14000,1000),
labels=real_format()) +
theme_bw() + theme(panel.border = element_blank(),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
axis.line = element_line(colour = "black"),
legend.position="none")
gg_base.animation = gg_base + transition_reveal(Date)
animate(gg_base.animation, duration = 2, fps = 30, width = 800, height = 600)
anim_save("test.gif")
The excel table is the following:
I actually get an animation but it removes all the formatting. How can I keep the formatting from the plot?
I receive four message errors saying:
geom_line(): Each group consists of only one observation. ℹ Do you need to adjust the group aesthetic?
They don't appear if I don't animate (removing the three last lines of code).

Dendrogram with labels on the right side

I want a horizontal dendrogram with the variable names on the right side to display correlation coefficients. It would be nice if I could achieve it in some ggplot2-related package, since I want the diagram to be similar looking to my other graphics. scale_x_discrete(position="top) does not work, because then the labels disappear. These are my results so far:
library(ggplot2)
library(dplyr)
library(tidyr)
library(faux)
library(ggdendro)
# data
set.seed(5)
dat <- rnorm_multi(n = 100,
mu = c(0, 20, 20),
sd = c(1, 5, 5),
r = c(0.5, 0.5, 0.25),
varnames = c("A", "B", "C"),
empirical = FALSE)
# make correlation matrix
cor_matrix_before <- cor(dat, method="spearman")
# make dendrogram
tree <- hclust(as.dist(1 - cor_matrix_before**2))
ggdendrogram(tree) +
theme_light() +
theme(text = element_text(size=16)) +
xlab("") +
ylab("Spearmans rho squared") +
scale_y_reverse(breaks=seq(0,1,0.25), labels=rev(seq(0,1,0.25))) +
geom_hline(yintercept=0.7*0.7, col = "red") +
coord_flip()
(I stole the preparation of correlated variables from: https://cran.r-project.org/web/packages/faux/vignettes/rnorm_multi.html)
But this would be what I want (just a quick paint-montage):
EDIT: Thanks to #tjebo, this is my final solution (I removed all the parts that I did not need, look at his answer for a more generic answer):
tree <- hclust(as.dist(1 - cor_matrix_before**2))
data <- ggdendro::dendro_data(tree)
ggplot() +
geom_blank()+
geom_segment(data = segment(data), aes_string(x = "x", y = "y", xend = "xend", yend = "yend")) +
geom_hline(yintercept=0.7*0.7, col = "red") +
scale_x_continuous(breaks = seq_along(data$labels$label), labels = data$labels$label, position = "top") +
scale_y_reverse(breaks=seq(0,1,0.25), labels=rev(seq(0,1,0.25))) +
coord_flip() +
theme(axis.text.x = element_text(angle = angle, hjust = 1, vjust = 0.5),
axis.text.y = element_text(angle = angle, hjust = 1),
text = element_text(size=16, family="Calibri")) +
ylab("Spearmans rho squared") +
xlab("") +
theme_light()
If you want to avoid re-inventing the wheel and creating those dendrograms from scratch (i.e., if you wanna make use of high level ggdendrogram), then you won't get around changing the underlying function. ggdendro::ggdendrogram defines both y and x axis. You need to modify them in the function body. See comments in the code below.
library(tidyverse)
library(faux)
library(ggdendro)
set.seed(5)
dat <- rnorm_multi(
n = 100,
mu = c(0, 20, 20),
sd = c(1, 5, 5),
r = c(0.5, 0.5, 0.25),
varnames = c("A", "B", "C"),
empirical = FALSE
)
cor_matrix_before <- cor(dat, method = "spearman")
tree <- hclust(as.dist(1 - cor_matrix_before**2))
## re-define ggdendrogram. I think the easiest is add another argument for the axis position, see "x_lab"
ggdendrogram2 <- function(data, segments = TRUE, labels = TRUE, leaf_labels = TRUE,
rotate = FALSE, theme_dendro = TRUE, x_lab = "bottom", ...) {
dataClass <- if (inherits(data, "dendro")) {
data$class
} else {
class(data)
}
angle <- if (dataClass %in% c("dendrogram", "hclust")) {
ifelse(rotate, 0, 90)
} else {
ifelse(rotate, 90, 0)
}
hjust <- if (dataClass %in% c("dendrogram", "hclust")) {
ifelse(rotate, 1, 1)
} else {
0.5
}
if (!ggdendro::is.dendro(data)) {
data <- ggdendro::dendro_data(data)
}
p <- ggplot() +
geom_blank()
if (segments && !is.null(data$segments)) {
p <- p + geom_segment(data = segment(data), aes_string(
x = "x",
y = "y", xend = "xend", yend = "yend"
))
}
if (leaf_labels && !is.null(data$leaf_labels)) {
p <- p + geom_text(
data = leaf_label(data), aes_string(
x = "x",
y = "y", label = "label"
), hjust = hjust, angle = angle,
...
)
}
if (labels) {
p <- p + scale_x_continuous(
breaks = seq_along(data$labels$label),
labels = data$labels$label,
# and this is where you add x_lab
position = x_lab
)
}
if (rotate) {
p <- p + coord_flip()
p <- p + scale_y_continuous()
} else {
p <- p + scale_y_continuous()
}
if (theme_dendro) {
p <- p + theme_dendro()
}
p <- p + theme(axis.text.x = element_text(
angle = angle,
hjust = 1, vjust = 0.5
)) + theme(axis.text.y = element_text(
angle = angle,
hjust = 1
))
p
}
ggdendrogram2(tree, x_lab = "top", rotate = TRUE)
Created on 2021-07-28 by the reprex package (v2.0.0)

How to change the colors of the dots in the graph? ggpubr package

I am using the ggerrorplot () function of the ggpubr package to create the graph below. My question is whether there is any way to change the colors of the dots without changing the color of the point that represents the mean and standard deviation? Observe the image:
My code:
# loading packages
library(ggpubr)
# Create data frame
GROUP <- c()
TEST <- c()
VALUE <- c()
for (i in 0:100) {
gp <- c('Group1','Group2','Group1 and Group2')
ts <- c('Test1','Test2')
GROUP <- append(GROUP, sample(gp, 1))
TEST <- append(TEST, sample(ts, 1))
VALUE <- append(VALUE, sample(1:200, 1))
}
df <- data.frame(GROUP, TEST, VALUE)
# Seed
set.seed(123)
# Plot
ggerrorplot(df, x = "GROUP", y = "VALUE",
desc_stat = "mean_sd",
add = c("jitter"),
color = "TEST",
palette = "jco",
add.params = list(size = 0.2),
order = c('Group1','Group2','Group1 and Group2')
) +
labs(x = '', y = 'Values\n') +
theme(legend.title = element_blank())
Can you accomplish this by simply passing in color to add.params?
# loading packages
library(ggpubr)
#> Loading required package: ggplot2
# Create data frame
GROUP <- c()
TEST <- c()
VALUE <- c()
for (i in 0:100) {
gp <- c('Group1','Group2','Group1 and Group2')
ts <- c('Test1','Test2')
GROUP <- append(GROUP, sample(gp, 1))
TEST <- append(TEST, sample(ts, 1))
VALUE <- append(VALUE, sample(1:200, 1))
}
df <- data.frame(GROUP, TEST, VALUE)
# Seed
set.seed(123)
# Plot
ggerrorplot(df, x = "GROUP", y = "VALUE",
desc_stat = "mean_sd",
add = c("jitter"),
color = "TEST",
palette = "jco",
add.params = list(size = 0.2, color = "red"),
order = c('Group1','Group2','Group1 and Group2')
) +
labs(x = '', y = 'Values\n') +
theme(legend.title = element_blank())
Created on 2021-03-10 by the reprex package (v0.3.0)
Another potential workaround - replicate the plot using ggplot() and geom_linerange(), e.g.
library(ggpubr)
library(ggsci)
library(cowplot)
# Create data frame
GROUP <- c()
TEST <- c()
VALUE <- c()
for (i in 0:100) {
gp <- c('Group1','Group2','Group1 and Group2')
ts <- c('Test1','Test2')
GROUP <- append(GROUP, sample(gp, 1))
TEST <- append(TEST, sample(ts, 1))
VALUE <- append(VALUE, sample(1:200, 1))
}
df <- data.frame(GROUP, TEST, VALUE)
# Seed
set.seed(123)
data_summary <- function(data, varname, groupnames){
require(plyr)
summary_func <- function(x, col){
c(mean = mean(x[[col]], na.rm=TRUE),
sd = sd(x[[col]], na.rm=TRUE))
}
data_sum<-ddply(data, groupnames, .fun=summary_func,
varname)
data_sum <- rename(data_sum, c("mean" = varname))
return(data_sum)
}
df2 <- data_summary(df, varname = "VALUE", groupnames = c("TEST", "GROUP"))
# Plot
p1 <- ggplot(df, aes(x = factor(GROUP, levels = c('Group1','Group2','Group1 and Group2')),
y = VALUE, color = TEST)) +
geom_jitter(shape = 21, fill = "black", stroke = 0,
position = position_jitterdodge(jitter.width = 0.2)) +
geom_linerange(data = df2, aes(ymin=VALUE-sd, ymax=VALUE+sd),
position=position_dodge(width = .75)) +
geom_point(data = df2, aes(y = VALUE), size = 3,
position = position_dodge(width = 0.75)) +
scale_color_jco() +
labs(x = '', y = 'Values\n') +
theme_classic(base_size = 14) +
theme(legend.title = element_blank(),
legend.position = "top")
p2 <- ggerrorplot(df, x = "GROUP", y = "VALUE",
desc_stat = "mean_sd",
add = c("jitter"),
color = "TEST",
palette = "jco",
add.params = list(size = 0.2),
order = c('Group1','Group2','Group1 and Group2')
) +
labs(x = '', y = 'Values\n') +
theme(legend.title = element_blank())
cowplot::plot_grid(p1, p2, nrow = 1, ncol = 2, labels = "AUTO")
When you plot them side-by-side you can see that they aren't exactly the same, but this might work for you nonetheless.
Edit
An advantage of this approach is that you can adjust the 'fill' scale separately if you don't want all the dots to be the same colour, but you do want them to be different to the lines, e.g.
p1 <- ggplot(df, aes(x = factor(GROUP, levels = c('Group1','Group2','Group1 and Group2')),
y = VALUE, color = TEST)) +
geom_jitter(aes(fill = TEST), shape = 21, stroke = 0,
position = position_jitterdodge(jitter.width = 0.2)) +
geom_linerange(data = df2, aes(ymin=VALUE-sd, ymax=VALUE+sd),
position=position_dodge(width = .75)) +
geom_point(data = df2, aes(y = VALUE), size = 3,
position = position_dodge(width = 0.75)) +
scale_color_jco() +
scale_fill_npg() +
labs(x = '', y = 'Values\n') +
theme_classic(base_size = 14) +
theme(legend.title = element_blank(),
legend.position = "top")
p2 <- ggerrorplot(df, x = "GROUP", y = "VALUE",
desc_stat = "mean_sd",
add = c("jitter"),
color = "TEST",
palette = "jco",
add.params = list(size = 0.2),
order = c('Group1','Group2','Group1 and Group2')
) +
labs(x = '', y = 'Values\n') +
theme(legend.title = element_blank())
cowplot::plot_grid(p1, p2, nrow = 1, ncol = 2, labels = "AUTO")

Grid as bars in ggplot

A common layout in many sites is to draw the grid as shaded bars:
I'm doing this with this function:
grid_bars <- function(data, y, n = 5, fill = "gray90") {
breaks <- pretty(data[[y]], n)
len <- length(breaks)-1
all_bars <- data.frame(
b.id = rep(1:len, 4),
b.x = c(rep(-Inf, len), rep(Inf, len*2), rep(-Inf, len)),
b.y = c(rep(breaks[-length(breaks)], 2), rep(breaks[-1], 2))
)
bars <- all_bars[all_bars$b.id %in% (1:len)[c(FALSE, TRUE)], ]
grid <- list(
geom_polygon(data = bars, aes(b.x, b.y, group = b.id),
fill = fill, colour = fill),
scale_y_continuous(breaks = breaks),
theme(panel.grid = element_blank())
)
return(grid)
}
#-------------------------------------------------
dat <- data.frame(year = 1875:1972,
level = as.vector(LakeHuron))
ggplot(dat, aes(year, level)) +
grid_bars(dat, "level", 10) +
geom_line(colour = "steelblue", size = 1.2) +
theme_classic()
But it needs to specify data and y again. How to take those directly from the ggplot?
After having a look at the options for extending ggplot2 in Hadley Wickham's book on ggplot2 you probably have to set up your own Geom or Stat layer to achieve the desired result. This way you can access the data and aesthetics specified in ggplot() or even pass different data and aesthetics to your fun. Still a newbie in writing extensions for ggplot2 but a first approach may look like so:
library(ggplot2)
# Make bars dataframe
make_bars_df <- function(y, n) {
breaks <- pretty(y, n)
len <- length(breaks) - 1
all_bars <- data.frame(
group = rep(1:len, 4),
x = c(rep(-Inf, len), rep(Inf, len * 2), rep(-Inf, len)),
y = c(rep(breaks[-length(breaks)], 2), rep(breaks[-1], 2))
)
all_bars[all_bars$group %in% (1:len)[c(FALSE, TRUE)], ]
}
# Setup Geom
geom_grid_bars_y <- function(mapping = NULL, data = NULL, stat = "identity",
position = "identity", na.rm = FALSE, show.legend = NA,
inherit.aes = TRUE, n = 5, ...) {
layer(
geom = GeomGridBarsY, mapping = mapping, data = data, stat = stat,
position = position, show.legend = show.legend, inherit.aes = inherit.aes,
params = list(n = n, ...)
)
}
GeomGridBarsY <- ggproto("GeomGridBarsY", Geom,
required_aes = c("y"),
default_aes = aes(alpha = NA, colour = NA, fill = "gray90", group = NA,
linetype = "solid", size = 0.5, subgroup = NA),
non_missing_aes = aes("n"),
setup_data = function(data, params) {
transform(data)
},
draw_group = function(data, panel_scales, coord, n = n) {
bars <- make_bars_df(data[["y"]], n)
# setup data for GeomPolygon
## If you want this to work with facets you have to take care of the PANEL
bars$PANEL <- factor(1)
# Drop x, y, group from data
d <- data[ , setdiff(names(data), c("x", "y", "group"))]
d <- d[!duplicated(d), ]
# Merge information in data to bars
bars <- merge(bars, d, by = "PANEL")
# Set color = fill
bars[["colour"]] <- bars[["fill"]]
# Draw
grid::gList(
ggplot2::GeomPolygon$draw_panel(bars, panel_scales, coord)
)
},
draw_key = draw_key_rect
)
grid_bars <- function(n = 5, fill = "gray90") {
list(
geom_grid_bars_y(n = n, fill = fill),
scale_y_continuous(breaks = scales::pretty_breaks(n = n)),
theme(panel.grid = element_blank())
)
}
dat <- data.frame(year = 1875:1972,
level = as.vector(LakeHuron))
ggplot(dat, aes(year, level)) +
grid_bars(n = 10, fill = "gray95") +
geom_line(colour = "steelblue", size = 1.2) +
theme_classic()
Just for reference:
A first and simple approach to get grid bars one could simply adjust the size of the grid lines via theme() like so:
# Simple approach via theme
ggplot(dat, aes(year, level)) +
geom_line(colour = "steelblue", size = 1.2) +
scale_y_continuous(breaks = scales::pretty_breaks(n = 10)) +
theme_classic() +
theme(panel.grid.major.y = element_line(size = 8))
Created on 2020-06-14 by the reprex package (v0.3.0)

R Shiny ggiraph and d3heatmap Compatibility Issues

I'm trying to add an interactive heatmap to my Shiny app, but I also have interactive graphs using ggiraph. I'm currently using the d3heatmap package, but the heatmaps don't render in the app. I've created a toy example to illustrate this:
library(shiny)
library(ggiraph)
library(d3heatmap)
ui <- fluidPage(
d3heatmapOutput('d3'),
ggiraphOutput('gg')
)
server <- function(input, output, session) {
# Create heatmap
output$d3 <- renderD3heatmap({
d3heatmap(matrix(1:100, nrow = 100, ncol = 100))
})
# Create ggiraph
output$gg <- renderggiraph({
p <- ggplot(iris, aes(x = Sepal.Length, y = Petal.Width,
color = Species, tooltip = iris$Species) ) +
geom_point_interactive()
ggiraph(code = {print(p)})
})
}
shinyApp(ui = ui, server = server)
Together, only the ggiraph renders, but the heatmap does not. However, if you comment out the ggiraph code, the heatmap renders. I tried switching the order of loading the packages, but that still didn't work.
I'm currently running on R 3.2.2 (I have to use this version because the company servers only run on this version, and neither my manager nor I have the authority to update it). I tried downloading the shinyheatmap, heatmaply, and heatmap.2 packages, but because of versioning issues, the installations were unsuccessful.
So right now, I've just used pheatmap to create the heatmaps, but they aren't interactive (i.e., I can't get values when I hover over individual cells, and I can't zoom in). Is there any workaround for this, or are there other interactive heatmap packages out there that would work? I'd like to avoid changing all of my ggiraph graphs to plotly graphs since there are a lot of them in my code.
Please let me know if there's any other information you need. Any suggestions would be much appreciated!
(just to let you know I am the author of ggiraph)
There is a conflict between ggiraph and d3heatmap because ggiraph is using d3.js version 4 and d3heatmap is using D3.js version 3. I don't think there is a solution to solve that conflict.
However, building an interactive heatmap with ggplot2/ggiraph is not that difficult. See below:
library(dplyr)
library(tidyr)
library(ggplot2)
library(ggiraph)
library(ggdendro)
# mydata <- cor(mtcars)
mydata <- matrix(runif(2500, min = -2, max = 2), ncol = 50)
row.names(mydata) <- paste0("row_", seq_len(nrow(mydata)))
colnames(mydata) <- paste0("col_", seq_len(ncol(mydata)))
# dendrogram for rows
hc <- hclust(dist(mydata), "ave")
dhr <- as.dendrogram(hc)
order_r <- rownames(mydata)[hc$order]
# dendrogram for columns
hc <- hclust(dist(t(mydata)), "ave")
dhc <- as.dendrogram(hc)
order_c <- colnames(mydata)[hc$order]
# the data
expr_set <- bind_cols(
data_frame(rowvar = rownames(mydata)),
as.data.frame(mydata)
)
expr_set <- gather(expr_set, colvar, measure, -rowvar)
expr_set$rowvar <- factor( expr_set$rowvar, levels = order_r )
expr_set$colvar <- factor( expr_set$colvar, levels = order_c )
expr_set <- arrange(expr_set, rowvar, colvar)
# get data for dendrograms - IMHO, ggdendro is the hero here...
data_c <- dendro_data(dhc, type = "rectangle")
data_c <- segment(data_c) %>% mutate(
y = y + length(order_r) + .5,
yend = yend + length(order_r) + .5
)
data_r <- dendro_data(dhr, type = "rectangle")
data_r <- segment(data_r)
data_r <- data_r %>%
mutate( x_ = y + length(order_c) + .5,
xend_ = yend + length(order_c) + .5,
y_ = x,
yend_ = xend )
expr_set <- expr_set %>%
mutate(
tooltip = sprintf("Row: %s<br/>Col: %s<br/>measure: %.02f",
rowvar, colvar, measure) ,
data_id = sprintf("%s_%s", rowvar, colvar)
)
# all data are tidy and can be now used with ggplot
p <- ggplot(data = expr_set, aes(x = colvar, y = rowvar) ) +
geom_tile_interactive(aes(fill = measure, tooltip = tooltip, data_id = data_id), colour = "white") +
scale_fill_gradient(low = "white", high = "#BC120A") +
geom_segment(
data = data_c,
mapping = aes(x = x, y = yend, xend = xend, yend = y),
colour = "gray20", size = .2) +
geom_segment(
data = data_r,
mapping = aes(x = x_, y = y_, xend = xend_, yend = yend_),
colour = "gray20", size = .2) +
coord_equal()
# cosmetics
p <- p + theme_minimal() +
theme(
legend.position = "right",
panel.grid.minor = element_line(color = "transparent"),
panel.grid.major = element_line(color = "transparent"),
axis.ticks.length = unit(2, units = "mm"),
plot.title = element_text(face = "bold", hjust = 0.5, size = 12),
axis.title = element_text(size = 9, colour = "gray30"),
axis.text.y = element_text(hjust = 1, size = 5, colour = "gray40"),
axis.text.x = element_text(angle = 90, hjust = 1, size = 5, colour = "gray40"),
legend.title=element_text(face = "bold", hjust = 0.5, size=8),
legend.text=element_text(size=6)
)
ggiraph(ggobj = p)
Hope it helps
I know that this question is answered some time ago but I've ran into the same problem and i was not able to use ggplot2 because it was just to slow to work with my Shiny application. The heatmaply package is allot faster and easier to implement. I performed a mini-benchmark (n= 20).
with ggplot2 took an average time of 64 seconds. With heatmaply it took only 2 seconds. both methods use the 'ave' method of hclust.I hope this is helpfull.
mini-benchmark n= 20 of ggplot vs heatmaply
here is the code i used:
library(tidyr)
library(ggplot2)
library(ggiraph)
library(ggdendro)
library(heatmaply)
# mydata <- cor(mtcars)
create_data <- function(){
df <- matrix(runif(2500, min = -2, max = 2), ncol = 50)
row.names(df) <- paste0("row_", seq_len(nrow(df)))
colnames(df) <- paste0("col_", seq_len(ncol(df)))
return(df)
}
gg2heat <- function(mydata){
# dendrogram for rows
hc <- hclust(dist(mydata), "ave")
dhr <- as.dendrogram(hc)
order_r <- rownames(mydata)[hc$order]
# dendrogram for columns
hc <- hclust(dist(t(mydata)), "ave")
dhc <- as.dendrogram(hc)
order_c <- colnames(mydata)[hc$order]
# the data
expr_set <- bind_cols(
data_frame(rowvar = rownames(mydata)),
as.data.frame(mydata)
)
expr_set <- gather(expr_set, colvar, measure, -rowvar)
expr_set$rowvar <- factor( expr_set$rowvar, levels = order_r )
expr_set$colvar <- factor( expr_set$colvar, levels = order_c )
expr_set <- arrange(expr_set, rowvar, colvar)
# get data for dendrograms - IMHO, ggdendro is the hero here...
data_c <- dendro_data(dhc, type = "rectangle")
data_c <- segment(data_c) %>% mutate(
y = y + length(order_r) + .5,
yend = yend + length(order_r) + .5
)
data_r <- dendro_data(dhr, type = "rectangle")
data_r <- segment(data_r)
data_r <- data_r %>%
mutate( x_ = y + length(order_c) + .5,
xend_ = yend + length(order_c) + .5,
y_ = x,
yend_ = xend )
expr_set <- expr_set %>%
mutate(
tooltip = sprintf("Row: %s<br/>Col: %s<br/>measure: %.02f",
rowvar, colvar, measure) ,
data_id = sprintf("%s_%s", rowvar, colvar)
)
# all data are tidy and can be now used with ggplot
p <- ggplot(data = expr_set, aes(x = colvar, y = rowvar) ) +
geom_tile_interactive(aes(fill = measure, tooltip = tooltip, data_id = data_id), colour = "white") +
scale_fill_gradient(low = "white", high = "#BC120A") +
geom_segment(
data = data_c,
mapping = aes(x = x, y = yend, xend = xend, yend = y),
colour = "gray20", size = .2) +
geom_segment(
data = data_r,
mapping = aes(x = x_, y = y_, xend = xend_, yend = yend_),
colour = "gray20", size = .2) +
coord_equal()
# cosmetics
p <- p + theme_minimal() +
theme(
legend.position = "right",
panel.grid.minor = element_line(color = "transparent"),
panel.grid.major = element_line(color = "transparent"),
axis.ticks.length = unit(2, units = "mm"),
plot.title = element_text(face = "bold", hjust = 0.5, size = 12),
axis.title = element_text(size = 9, colour = "gray30"),
axis.text.y = element_text(hjust = 1, size = 5, colour = "gray40"),
axis.text.x = element_text(angle = 90, hjust = 1, size = 5, colour = "gray40"),
legend.title=element_text(face = "bold", hjust = 0.5, size=8),
legend.text=element_text(size=6)
)
ggiraph(ggobj = p)
}
htmp_gg <- c()
htmp_maply <-c()
for (i in 1:20){
df <- create_data()
time_gg <- (system.time(gg2heat(df)))[3]
htmp_gg<- append(htmp_gg, values = time_gg)
time_heatmaply <- (system.time(heatmaply::heatmaply(df, hclust_method = 'ave')))[3]
htmp_maply<- append(htmp_maply, values = time_heatmaply)
rm(df)
}
score <- data.frame(htmp_gg, htmp_maply)%>% gather(key = 'method', value = 'time')
p <- ggplot(score, aes(x = method, y = time, fill = method))+geom_violin()+ stat_summary(fun.y=median, geom="point", size=2, color="black")
print(p)

Resources