R: ggplot2 How to detect plot contents are outside of plot window - r

Is it somehow possible to detect programmatically, that a part of the plot is not in the plot window any more?
Take the following code as example - clearly some points are cut off.
df <- data.frame(input = c(rep(3,100), runif(200)))
ggplot(data = df, aes(x = input)) + geom_dotplot( )
If I increase the height of the plot window - more and more points become visible. Does this mean, that the information for these additional objects is somehow available in the plot object and just not displayed, because it doesn't fit in the window?
I've found layer_data(gg,1) - but nothing in there seems like the information I need.
How could I access this information (in order to adjust plot window size accordingly)?

Related

Stop plot edges being cut off in bipartite package (plotweb)

With the plotweb() function in the bipartite package in R, I've created a network, but some of the labels are too long for the plot area. As a result, they are being cut off at the top and bottom (I've included a picture).
I'm trying to make it fit in the plot, or if not possible, to be able to export it as an image without the edges being cut off.
I've tried par(mar=c(), but that doesn't seem to do anything. ybig() can allow the top half to fit in, but it doesn't change the bottom section.
See photo: labels being cut off from the web
It seems that the argument y.lim could be adjusted in such cases. From help(plotweb), about y.lim:
[...] Useful if labels are plotted outside the plotting region and for multitrophic plots [...]
Here is an example:
library(bipartite)
data(Safariland)
# Forge some long names/labels
cn <- colnames(Safariland)
rn <- rownames(Safariland)
colnames(Safariland) <- paste(cn, cn)
rownames(Safariland) <- paste(rn, rn)
# plotweb with trying to fit the labels - tweak y.lim until you get it right
plotweb(Safariland, text.rot = 90, y.lim = c(-1.5, 4))

Proper line labelling within canvas in ggplot2

Problem description
I'm writing a function that outputs a line plot ggplot2 object. I would like to have an argument that can control whether to add labels at the end of each line. A visual example is found here. The difficulty lies in the variable length of line labels. Ideally, the function will be smart enough to figure out the proper extra space to expand for the labels on the right.
In base R, there is a function graphics::strwidth that computes how many inches needed for the passed in string. I was wondering if there is a way that can do one step further, i.e. maps the string length to that with respect to the data scale. A dummy example is provided below for better explanation.
A dummy example
library(directlabels)
library(reshape2)
library(ggplot2)
dts <- cbind(`my long group 1` = mdeaths, `my long group 2` = fdeaths, time = 1:length(mdeaths))
ddf <- melt(as.data.frame(dts), id = "time")
names(ddf) <- c("time", "group", "deaths")
plot_wo_label <- ggplot(ddf, aes(x = time, y = deaths, group = group)) + geom_line()
plot_with_label <- plot_wo_label + geom_dl(aes(label = group), method = list(dl.combine('last.points')))
Plot with line label
As we can see above, the long line labels ('my long group 1' and 'my long group 2') get truncated due to the margin space. An ad hoc solution is to use xlim to expand the right edge of x-axis by trial and error. But that certainly is not an option in my case.
I know there are posted solutions by turning off clipping (like here), however, I imagine that some of the lines may end early and having a label at the canvas edge far away from the line end may cause difficulty to associate the labels with its corresponding lines.
So if there is a way to figure out how much space a string of arbitrary length will occupy on the x-axis (in the dummy example, the "duration" of label "my long group 1" in the "time" axis), that would be very helpful. But this is just one possible direction in my mind, other solutions are welcome and greatly appreciated!
Thanks!
The difficulty is that the absolute sizes of the text labels stay the same when you change the rendered output size of the plot. As a result, when you make the rendered size of the plot larger, the text labels span a smaller fraction of the plot area, and vice versa.
There's probably a way to generate the plot, dig into the grob structure of the plot to get the label width in plot coordinates, and then use scale_x_continuous to adjust the plot's x limits to include all of the label text. Unfortunately, I'm not sure how to do that, but hopefully someone else will come along who does.
For now, here's a demonstration of the issue. (I've switched to geom_text to place the labels, as I don't think directlabels is necessary here.):
library(tidyverse)
ggplot(ddf, aes(x = time, y = deaths, group = group)) +
geom_line() +
geom_text(data=ddf %>% group_by(group) %>% filter(time==max(time)),
aes(label=group), hjust=0, position=position_nudge(x=0.5)) +
scale_x_continuous(limits=c(0,1.15*max(ddf$time)))
Here's a screenshot of two saved versions of the plot, one version saved as a 700x350 pixel png file and the other saved as a 500x250 pixel png file. You can see that the absolute font sizes are the same, even though the sizes of the plots are different.

Zooming the ggplot or normal plot in R

I am trying to zoom the plot that I have got. But i couldn't find any proper result. Here is my original plot!
plt <- ggplot(Df, aes(x = Lon, y = Lat, colour = AC), pch = 17) +geom_point()
plt + geom_path(arrow= arrow(), colour="grey") + scale_size(range = 1)
I have tried to do it using dygraph, zoom packages but couldn't succeed.
Note that I do not want to use xlim and ylim over here.
If i understand correctly, you want to zoom in and out in the graphics device (window) and not zoom the plot itself, as is shown here Limit ggplot2 axes without removing data (outside limits): zoom
Probably you're best bet is still the zoom package. This package has several functions that may be useful to you, like inout.zoom() and sq.zoom(). If this doesn't work for you, please be more specific of what it doesn't do.
R-studio offers some options:
1. There is a zoom button above the plot window, that shows the graph in a bigger window. I some cases this will be enough, however in your case it will not be because you have many points cluttered together.
2. In R-studio, press EXPORT-SAVEtoPDF-PREVIEW. This will open a preview in acrobat reader where you can zoom in and out as much as you like.
What I do in this case is render it additionally to a png file at high resolution and then I can examine it with other tools at my leisure. Code with filename and 1000x1000 resolution is like this:
gfname <- sprintf("plotname-%d.png",version)
png(gfname,width=1000,height=1000)
print(plt)
jnk <- dev.off()
ggsave(p,filename=mypath,width=13.66,height=7.05,limitsize = FALSE)
worked for me in getting the zommed plots saved within a loop.
p- plot

How can I resize maps to fill the plot window?

I started using R's map() function to plot maps. I noticed that when I resize the plot window, the image does not scale to fill the window. How can I get the map image to automatically resize bigger or smaller, depending on how big I drag my window?
I am using R version 3.0.2 on MacOS.
For example, here is a map where I've dragged the plot window smaller and bigger. Notice that the map image's size does not change.
library(maps)
map("state")
On the other hand, the usual plot() command does resize the graphic to fit the window.
plot(1:100, 201:300)
It takes a bit of work, but by converting the maps object to a SpatialPolygonsDataFrame, and then spplot()'ing that, you can get a dynamically resizing map.
FWIW, I suspect this works better because spplot() is based on grid (via lattice), and the grid graphical system supports much more sophisticated ways of handling dimensions within plot objects than does R's base graphical system.
library(maps)
library(maptools) ## For map2SpatialPolygons()
## Convert data from a "maps" object to a "SpatialPolygonsDataFrame" object
mp <- map("state", fill = TRUE, plot = FALSE)
SP <- map2SpatialPolygons(mp, IDs = mp$names,
proj4string = CRS("+proj=longlat +datum=WGS84"))
DATA <- data.frame(seq_len(length(SP)), row.names = names(SP))
SPDF <- SpatialPolygonsDataFrame(SP, data = DATA)
## Plot it
spplot(SPDF, col.regions = "transparent", colorkey = FALSE,
par.settings = list(axis.line = list(col = "transparent")))
Here are a couple of screenshots to show that it works:
I note that Josh has provided an acceptable solution to the problem, but it might be useful to understand why map() has the behaviour you describe. Essentially it comes down to map() setting the size of the plotting region based on the current size & aspect ratio of the device (the figure region more specifically) at draw time.
As such, one solution, without converting to another format, as Josh nicely demonstrates, is just to redraw the map after you've rescaled the device to the desired size. You could avoid some guesswork by doing a couple of computations based on the aspect ratio of par("usr") and then set the device to a width that is compatible with that aspect ratio.
Probably more hassle than #Josh's solution, but it does explain the behaviour. A more detailed description of the issue is given below.
The reason that the drawn map doesn't "fill" the device (up to the specified margins) is due to the code in map() setting the size of the plotting region to have a particular aspect ratio based on the size of the device etc. The resulting plotting region is the sized such that it fits within the device, but preserves the correct aspect ratio so may not entirely fill it.
The key section of code is this:
else {
par(mar = mar)
p <- par("fin") - as.vector(matrix(c(0, 1, 1,
0, 0, 1, 1, 0), nrow = 2) %*% par("mai"))
par(pin = p)
p <- par("pin")
p <- d * min(p/d)
par(pin = p)
d <- d * myborder + ((p/min(p/d) - d)/2)/aspect
usr <- c(xrange, yrange) + rep(c(-1, 1), 2) *
rep(d, c(2, 2))
par(usr = usr)
}
with d defined slightly earlier as:
d <- c(diff(xrange), diff(yrange)) * (1 + 2 * myborder) *
aspect
(for the example you give). The second line of the else branch is getting the current size of the figure region in inches. The figure region is the size on the device of the region containing the margins and the plot region but not any outer margin. In effect, if there is no outer margin active, this code is grabbing the size of the device (and makes an adjustment). This result is then use to set the size of the plotting region, which gets updated.
The intention seems to be to take the size of the current figure region, and use that to update the region into which the map is drawn. The size of that plotting region is in that sense controlled via the aspect ratio of the device; if you start with a wide but short window, then the computed plotting region will not need to use all the available width (if it did the aspect ratio would be wrong) and hence the plotting region is set to a size smaller than the available space.
As to why this doesn't update when you resize the window, well that is because at draw-time the size of the plotting region is set absolutely in inches. If you resize the device, the size of the plotting region remains the same and hence the map gets cropped if you shrink the device sufficiently, or uses less and less of the device space if you enlarge the device.

Use wordlayout results for ggplot geom_text

The R package wordcloud has a very useful function which is called wordlayout. It takes initial positions of words and their respective sizes an rearranges them in a way that they do not overlap. I would like to use the results of this functions to do a geom_text plot in ggplot.
I came up with the following example but soon realized that there seems to be a big difference betweetn cex (wordlayout) and size (geom_plot) since words in graphics package appear way larger.
here is my sample code. Plot 1 is the original wordcloud plot which has no overlaps:
library(wordcloud)
library(tm)
library(ggplot2)
samplesize=100
textdf <- data.frame(label=sample(stopwords("en"),samplesize,replace=TRUE),x=sample(c(1:1000),samplesize,replace=TRUE),y=sample(c(1:1000),samplesize,replace=TRUE),size=sample(c(1:5),samplesize,replace=TRUE))
#plot1
plot.new()
pdf(file="plot1.pdf")
textplot(textdf$x,textdf$y,textdf$label,textdf$size)
dev.off()
#plot2
ggplot(textdf,aes(x,y))+geom_text(aes(label = label, size = size))
ggsave("plot2.pdf")
#plot3
new_pos <- wordlayout(x=textdf$x,y=textdf$y,words=textdf$label,cex=textdf$size)
textdf$x <- new_pos[,1]
textdf$y <- new_pos[,2]
ggplot(textdf,aes(x,y))+geom_text(aes(label = label, size = size))
ggsave("plot3.pdf")
#plot4
textdf$x <- new_pos[,1]+0.5*new_pos[,3]#this is the way the wordcloud package rearranges the positions. I took this out of the textplot function
textdf$y <- new_pos[,2]+0.5*new_pos[,4]
ggplot(textdf,aes(x,y))+geom_text(aes(label = label, size = size))
ggsave("plot4.pdf")
is there a way to overcome this cex/size difference and reuse wordlayout for ggplots?
cex stands for character expansion and is the factor by which text is magnified relative the default, specified by cin - set on my installation to 0.15 in by 0.2 in: see ?par for more details.
#hadley explains that ggplot2 sizes are measured in mm. Therefore cex=1 would correspond to size=3.81 or size=5.08 depending on if it is being scaled by the width or height. Of course, font selection may cause differences.
In addition, to use absolute sizes, you need to have the size specification outside the aes otherwise it considers it a variable to map to and choose the scale itself, eg:
ggplot(textdf,aes(x,y))+geom_text(aes(label = label),size = textdf$size*3.81)
Sadly I think you're going to find the short answer is no! I think the package handles the text vector mapping differently from ggplot2, so you can tinker with size and font face/family, etc. but will struggle to replicate exactly what the package is doing.
I tried a few things:
1) Try to plot the grobs from textdata using annotation_custom
require(plyr)
require(grid)
# FIRST TRY PLOT INDIVIDUAL TEXT GROBS
qplot(0:1000,0:1000,geom="blank") +
alply(textdf,1,function(x){
annotation_custom(textGrob(label=x$label,0,0,c("center","center"),gp=gpar(cex=x$size)),x$x,x$x,x$y,x$y)
})
2) Run the wordlayout() function which should readjust the text, but difficult to see for what font (similarly doesn't work)
# THEN USE wordcloud() TO GET CO-ORDS
plot.new()
wordlayout(textdf$x,textdf$y,words=textdf$label,cex=textdf$size,xlim=c(min(textdf$x),max(textdf$x)),ylim=c(min(textdf$y),max(textdf$y)))
plotdata<-cbind(data.frame(rownames(w)),w)
colnames(plotdata)=c("word","x","y","w","h")
# PLOT WORDCLOUD DATA
qplot(0:1000,0:1000,geom="blank") +
alply(plotdata,1,function(x){
annotation_custom(textGrob(label=x$word,0,0,c("center","center"),gp=gpar(cex=x$h*40)),x$x,x$x,x$y,x$y)
})
Here's a cheat if you just want to overplot other ggplot functions on top of it (although the co-ords don't seem to match up exactly between the data and the plot). It basically images the wordcloud, removes the margins, and under-plots it at the same scale:
# make a png file of just the panel
plot.new()
png(filename="bgplot.png")
par(mar=c(0.01,0.01,0.01,0.01))
textplot(textdf$x,textdf$y,textdf$label,textdf$size,xaxt="n",yaxt="n",xlab="",ylab="",asp=1)
dev.off()
# library to get PNG file
require(png)
# then plot it behind the panel
qplot(0:1000,0:1000,geom="blank") +
annotation_custom(rasterGrob(readPNG("bgplot.png"),0,0,1,1,just=c("left","bottom")),0,1000,0,1000) +
coord_fixed(1,c(0,1000),c(0,1000))

Resources