R adding legend and directlabels to ggplot2 contour plot - r

I have a raster map that I want to plot using ggplot2 using a continuous scale and labeled isolines on top of that.
For that I'm using the directlabels package and am close to getting what I want but I can't get both the legend and the labeled isolines on the same map
The following code reproduces my problem:
install.packages(c('ggplot2', 'directlabels'))
library('ggplot2')
library('directlabels')
df <- expand.grid(x=1:100, y=1:100)
df$z <- df$x * df$y
# Plot 1: this plot is fine but without contours
p <- ggplot(aes(x=x, y=y, z=z), data = df) +
geom_raster(data=df, aes(fill=z)) +
scale_fill_gradient(limits=range(df$z), high = 'white', low = 'red')
p
# Plot 2: This plot adds the isolines but no labels and it also adds a second legend for level which I don't want
p <- p + geom_contour(aes(colour = ..level..), color='gray30', na.rm=T, show.legend=T)
p
# Plot 3: This plot has the labeled isolines but it removes the z legend that I want to show
direct.label(p, list("bottom.pieces", colour='black'))
Plot 1
Plot 2
Plot 3
I would like to have the coloured raster in the background, with it's color legend on the side and the labeled isolines on top. Is there a way to do this?
Also is there a way to get the labels placed in the middle of the isolines instead of the bottom or top?
Thanks in advance
Pablo

First, fixing the issue to do with the legends.
library(ggplot2)
library(directlabels)
df <- expand.grid(x=1:100, y=1:100)
df$z <- df$x * df$y
p <- ggplot(aes(x=x, y=y, z=z), data = df) +
geom_raster(data=df, aes(fill=z), show.legend = TRUE) +
scale_fill_gradient(limits=range(df$z), high = 'white', low = 'red') +
geom_contour(aes(colour = ..level..)) +
scale_colour_gradient(guide = 'none')
p1 = direct.label(p, list("bottom.pieces", colour='black'))
p1
There aren't too many options for positioning the labels. One possibility is angled.boxes, but the fill colour might not be too nice.
p2 = direct.label(p, list("angled.boxes"))
p2
To change the fill colour to transparent (using code from here.
p3 = direct.label(p, list("far.from.others.borders", "calc.boxes", "enlarge.box",
box.color = NA, fill = "transparent", "draw.rects"))
p3
And to move the labels off the contour lines:
p4 = direct.label(p, list("far.from.others.borders", "calc.boxes", "enlarge.box",
hjust = 1, vjust = 1, box.color = NA, fill = "transparent", "draw.rects"))
p4

Related

How to stack (overlap) legend items in ggplot2

I was wondering if it is possible to "stack" the items of the legend on a ggplot2 map (or any package that allows to produce the same result).
For spatial (sf, etc.) objects there is at least one package, mapsf, that produces the desired output, but I would like to produce this type of legend also for non-spatial objects (dataframes/tibble, etc.).
See here a reprex:
# A regular plot with ggplot2
library(ggplot2)
# A plot
ggplot(mtcars, aes(wt, mpg)) +
geom_point(aes(size = drat))
# A map with the circles of the legend "stacked" (overlapping points)
# Need to conver to sf
mtcars_sf <- sf::st_as_sf(mtcars, coords = c("wt", "mpg"))
library(mapsf)
# See the legend on the top-right corner (blue arrow)
mf_map(x = mtcars_sf)
mf_map(x = mtcars_sf, var = "drat", type = "prop",
inches = .2)
As Allan said, there is (to my knowledge) no general way to do this. But you can hack this together for your custom graph. Thomas Pedersen's awesome packages ggforce and patchwork are your friend.
I am essentially faking a legend. The challenge is to get the right dimensions between main plot and legend, and to stitch the legend in a reasonable way to the main plot. This is achieved by using carefully chosen radii on geom_ellipse, setting the coordinate ratio accordingly, and adjusting the coordinate limits from both plots. More comments in the code.
library(ggplot2)
library(ggforce)
library(patchwork)
## we cannot just use geom_point,
## because the size of the circles need to correspond to the legend later
## you will need to play around with this constant
r_constant <- 25
mtcars$r <- mtcars$drat / r_constant
## this is to make the points round - it will have an effect on the panel dimension
ellipse_fac <- .1
p <-
ggplot(mtcars) +
geom_ellipse(aes(x0 = wt, y0 = mpg, a = r, b = r / ellipse_fac, angle = 0), fill = "darkred") +
theme(legend.position = "none") +
coord_equal(ellipse_fac)
## for the legend, chose rounded values from the radius range
unique_r <- unique(plyr::round_any(mtcars$r, .1))
y_circles <- floor(min(mtcars$mpg))
## I'm sorting the radii decreasingly, so that the circles overlap correctly
circles <- data.frame(x = 0, r = sort(unique_r, decreasing = TRUE))
## the segment / label poistion is also arbitrary
x_lab <- max(circles$r) + .1
y_seg <- y_circles + 2 * circles$r / ellipse_fac
p_leg <-
ggplot(circles) +
geom_ellipse(aes(x0 = x, y0 = y_circles + r / ellipse_fac, a = r, b = r / ellipse_fac, angle = 0), fill = "darkred", alpha = .5) +
geom_segment(aes(x = x, xend = x_lab, y = y_seg, yend = y_seg)) +
geom_text(aes(x = x_lab, y = y_seg, label = r), hjust = 0) +
## you need to set the ylimits similar to the main plot for control of legend position
coord_equal(ratio = ellipse_fac, ylim = range(mtcars$mpg), clip = "off") +
theme_void() +
## also need to set a margin
theme(plot.margin = margin(r = .2, unit = "in"))
p + p_leg
I've added an alpha just for aesthetic reasons

R 'cowplot' neatly produce gridded plot with shared (common) legends and unique legends

See my related question and the accepted answer here.
I am trying to produce a plot similar to that in the accepted answer i.e. a gridded plot with a shared common legend and a different unique legend attached to each plot on the grid.
Specifically, I want a 3 row, 1 column grid with 1 plot on each row. Like this:
Which was produced with the following code:
library (ggplot2)
library(gridExtra)
library (grid)
library(cowplot)
diamonds2 <- diamonds[sample(nrow(diamonds), 500), ]
# 3 ggplot plot objects with multiple legends 1 common legend and 3 unique legends
p1<- ggplot(diamonds2, aes(x=price, y= depth, color= clarity , shape= cut )) +
geom_point(size=5) + labs (shape = "unique legend", color = "common legend")
p2 <- ggplot(diamonds2, aes(x=price, y= depth, color= clarity , shape= color )) +
geom_point(size=5) + labs (shape = "unique legend", color = "common legend")
p3 <- ggplot(diamonds2, aes(x=price, y= depth, color= clarity , shape= clarity )) +
geom_point(size=5) + labs (shape = "unique legend", color = "common legend")
cowplot::plot_grid(
cowplot::plot_grid(
p1 + scale_color_discrete(guide = FALSE),
p2 + scale_color_discrete(guide = FALSE),
p3 + scale_color_discrete(guide = FALSE),
nrow=3, ncol = 1))
But with a shared legend which relates to the color = argument of each plot object.
I've tried many variations of the below code and have added/adjusted/removed various arguments/parameters in consultation with the cowplot documentation but I cannot get a neat plot like the one above with the shared legend at the bottom (or anywhere useful!) - everything I have attempted returns a crowded plot like below.
Adaption of the above code to include the shared legend :
cowplot::plot_grid(
cowplot::plot_grid(
p1 + scale_color_discrete(guide = FALSE),
p2 + scale_color_discrete(guide = FALSE),
p3 + scale_color_discrete(guide = FALSE),
nrow=3, ncol = 1
),
cowplot::get_legend(p1 + scale_shape(guide = FALSE) + theme(legend.position = "bottom")), nrow=3)
Which results in a crowded plot like this with a lot of empty space:
Could anyone suggest where I might be going wrong?
Each call to plot_grid splits your plotting area. Here, you are nesting two calls to plot_grid, and you are asking for 3 rows in each. cowplot therefore splits the plotting area in two equal parts:
in the top part, it puts your scatter plot
in the bottom part, your legend takes the first row, with nothing in the bottom two rows creating a lot of empty space while squishing your scatter plots.
You can specify the relative height of each of your plotting area giving more space for the scatter plots, and less space for the legend at the bottom. For instance for 85% plots, and 15% legend:
cowplot::plot_grid(
cowplot::plot_grid(
p1 + scale_color_discrete(guide = FALSE),
p2 + scale_color_discrete(guide = FALSE),
p3 + scale_color_discrete(guide = FALSE),
ncol = 1, align = "v"
),
cowplot::get_legend(p1 + scale_shape(guide = FALSE) +
theme(legend.position = "bottom")),
ncol=1, rel_heights=c(.85, .15))
which produces :

Average line for 2D Histogram?

I am a very new user of "R" and have a question.I am currently working on making 2D Histograms on R. The material necessarily does not matter but how do I plot an average line on the 2D Histogram. The code I am running is this:
load("mydatabin.RData")
# Color housekeeping
library(RColorBrewer)
rf <- colorRampPalette(rev(brewer.pal(11,'Spectral')))
r <- rf(32)
# Create normally distributed data for plotting
x <- mydata$AGE
y <- mydata$BP
df <- data.frame(x,y)
# Plot
plot(df, pch=16, col='black', cex=0.5)
This gives me a scatter plot and then to turn it into a 2D Histogram I do:
library(ggplot2)
# Default call (as object)
p <- ggplot(df, aes(x,y))
h3 <- p + stat_bin2d()
h3
# Default call (using qplot)
qplot(x,y,data=df, geom='bin2d')
After this I do:
h3 <- p + stat_bin2d(bins=25) + scale_fill_gradientn(colours=r)
h3
to add color.
Therefore, from here how do I plot an average line of the data.
And if anyone can tell me how to plot a heat map that looks like this using mydatebin.RData:
Thanks.
You can use geom_hline or geom_vline in ggplot2, passing y/xintercept as a parameter to draw a line. In your case, the parameter can be an average of one of your column to draw an average line. See the code for the example.
I also played around and tried two different ways to draw 2D histograms. Yours seems better and more precise, though I removed colorBrewer.
library(ggplot2)
# Create normally distributed data for plotting
x <- rnorm(10000)
y <- rnorm(10000)
df <- data.frame(x,y)
# stat_density2d way, with average lines
p1 <- ggplot(df,aes(x=x,y=y))+
stat_density2d(aes(fill=..level..), geom="polygon") +
scale_fill_gradient(low="navy", high="yellow") +
# Here go average lines
geom_hline(yintercept = mean(df$y), color = "red") +
geom_vline(xintercept = mean(df$x), color = "red") +
# Just to remove grid and set background color
theme(line = element_blank(),
panel.background = element_rect(fill = "navy"))
p1
# stat_bin2d way, with average lines
p2 <- ggplot(df, aes(x,y)) +
stat_bin2d(bins=50) +
scale_fill_gradient(low="navy", high="yellow") +
# Here go average lines
geom_hline(yintercept = mean(df$y), color = "red") +
geom_vline(xintercept = mean(df$x), color = "red") +
# Just to remove grid and set background color
theme(line = element_blank(),
panel.background = element_rect(fill = "navy"))
p2

ggplot2: how to create correct legend after using scale_xx_manual

I have a plot with three different lines. I want one of those lines to have points on as well. I also want the two lines without points to be thicker than the one without points. I have managed to get the plot I want, but I the legend isn't keeping up.
library(ggplot2)
y <- c(1:10, 2:11, 3:12)
x <- c(1:10, 1:10, 1:10)
testnames <- c(rep('mod1', 10), rep('mod2', 10), rep('meas', 10))
df <- data.frame(testnames, y, x)
ggplot(data=df, aes(x=x, y=y, colour=testnames)) +
geom_line(aes(size=testnames)) +
scale_size_manual("", values=c(0.5,1,1)) +
geom_point(aes(alpha=testnames), size=5, shape=4) +
scale_alpha_manual("", values=c(1, 0, 0))
I can remove the second (black) legend:
ggplot(data = df, aes(x=x, y=y, colour=testnames)) +
geom_line(aes(size=testnames)) +
scale_size_manual("", values=c(0.5,1,1), guide='none') +
geom_point(aes(alpha=testnames), size=5, shape=4) +
scale_alpha_manual("", values=c(1, 0.05, 0.05), guide='none')
But what I really want is a merge of the two legends - a legend with colours, cross only on the first variable (meas) and the lines of mod1 and mod2 thicker than the first line. I have tried guide and override, but with little luck.
You don't need transparency to hide the shapes for mod1 and mod2. You can omit these points from the plot and legend by setting their shape to NA in scale_shape_manual:
ggplot(data = df, aes(x = x, y = y, colour = testnames, size = testnames)) +
geom_line() +
geom_point(aes(shape = testnames), size = 5) +
scale_size_manual(values=c(0.5, 2, 2)) +
scale_shape_manual(values=c(8, NA, NA))
This gives the following plot:
NOTE: I used some more distinct values in the size-scale and another shape in order to better illustrate the effect.

overlaying plots in ggplot2

How to overlay one plot on top of the other in ggplot2 as explained in the following sentences? I want to draw the grey time series on top of the red one using ggplot2 in R (now the red one is above the grey one and I want my graph to be the other way around). Here is my code (I generate some data in order to show you my problem, the real dataset is much more complex):
install.packages("ggplot2")
library(ggplot2)
time <- rep(1:100,2)
timeseries <- c(rep(0.5,100),rep(c(0,1),50))
upper <- c(rep(0.7,100),rep(0,100))
lower <- c(rep(0.3,100),rep(0,100))
legend <- c(rep("red should be under",100),rep("grey should be above",100))
dataset <- data.frame(timeseries,upper,lower,time,legend)
ggplot(dataset, aes(x=time, y=timeseries)) +
geom_line(aes(colour=legend, size=legend)) +
geom_ribbon(aes(ymax=upper, ymin=lower, fill=legend), alpha = 0.2) +
scale_colour_manual(limits=c("grey should be above","red should be under"),values = c("grey50","red")) +
scale_fill_manual(values = c(NA, "red")) +
scale_size_manual(values=c(0.5, 1.5)) +
theme(legend.position="top", legend.direction="horizontal",legend.title = element_blank())
Convert the data you are grouping on into a factor and explicitly set the order of the levels. ggplot draws the layers according to this order. Also, it is a good idea to group the scale_manual codes to the geom it is being applied to for readability.
legend <- factor(legend, levels = c("red should be under","grey should be above"))
c <- data.frame(timeseries,upper,lower,time,legend)
ggplot(c, aes(x=time, y=timeseries)) +
geom_ribbon(aes(ymax=upper, ymin=lower, fill=legend), alpha = 0.2) +
scale_fill_manual(values = c("red", NA)) +
geom_line(aes(colour=legend, size=legend)) +
scale_colour_manual(values = c("red","grey50")) +
scale_size_manual(values=c(1.5,0.5)) +
theme(legend.position="top", legend.direction="horizontal",legend.title = element_blank())
Note that the ordering of the values in the scale_manual now maps to "grey" and "red"

Resources