Issues with adding a Line and arrow - r

I'm trying to add a text, line and arrow to highlight a certain event in my plot. I followed the example I found and I do get an arrow and a line, however the quality looks very poor. The line should be smooth and the arrow sharp.
I'm using annotate() here for the line, the arrow and the text.
annotate(
"text", x="feb 2020", y=173000, label = "Text", vjust =1, size =4, col="#535353")+
annotate(
"curve", x="feb 2020", y=175000, xend="mrt 2020", yend=200000,
arrow= arrow(length= unit(0.2, "cm"), type = "closed" ), col="#535353", curvature=-0.4, size=1
)
I also tried using geom_curve() with similar settings, the result wasn't much better.
Thanks in advance!

This probably depends on the device. Compare and contrast the default png() device on windows versus the ragg::agg_png() device. The latter has nicer anti-aliasing properties.
Dummy plot:
library(ggplot2)
plt <- ggplot() +
geom_curve(aes(x = 1, y = 1, xend = 2, yend = 2),
curvature = 0.2,
arrow = arrow(type = "closed"))
The default windows png device:
png("default_windows.png", width = 200, height = 200)
print(plt)
dev.off()
With the ragg package:
ragg::agg_png("ragg_windows.png", width = 200, height = 200)
print(plt)
dev.off()

Related

Maintaining Aspect Ratio of Shapes and Images in ggplot/ ggimage

I'm trying to build a visualisation with both drawn shapes (e.g. using geom_circle) and images. In both cases, I want to be able to position them on the page specifically with coordinates, rather than using one of the built in chart types.
See update further down...
However, I can either get the circles with the correct aspect ratio (i.e. round not oval) or the images, but not both. In the example below, you can see the image is not shown as square when it is.
I have tried various things including coord_fixed, scale_size_identity and coord_cartesian, but to no avail. The overall output will be landscape, which is why I have set the cartesian limits as I have.
This is a simplified version. In the full version, I'll get the coordinates from the data (which I'm fine with).
images <-data.frame(url = c("https://upload.wikimedia.org/wikipedia/commons/d/de/Windows_live_square.JPG"))
ggplot(mpg) +
ggforce::geom_circle(aes(x0 = displ * 50 - 60, y0 = hwy, r=cty)) +
#scale_size_identity() +
# Add Image
ggimage::geom_image(data = images,
aes(x = 4, y = 20, image=url),
size = 0.4,
hjust = 0.0,
by="height"
) +
coord_cartesian(
xlim = c(0, 120),
ylim = c(0, 80),
expand = FALSE,
clip = "on"
)
Update following really helpful input from #tjebo and further investigation.
I have now found there are at least 4 ways to add images to plots, each with their own advantages and disadvantages. I've listed these below to help others on this search.
Draw basic shapes to which images can be added
g <- ggplot(mpg) +
ggforce::geom_circle(aes(x0 = displ * 50 - 60, y0 = hwy, r=cty))
Plot with ggtexture - multiple images - aspect defined by x and y max - min
https://rdrr.io/github/clauswilke/ggtextures/man/geom_textured_rect.html
g + ggtextures::geom_textured_rect(data = images,
aes(xmin = 20, xmax = 60,
ymin = 20, ymax = 60, image = url),
lty = "blank", # line type of blank to remove border
fill="white", # used to fill transparent areas of image
nrow = 1,
ncol = 1,
img_width = unit(1, "null"),
img_height = unit(1, "null"),
position = "identity") +
coord_equal() # A fixed scale coordinate system forces a specified ratio between the physical representation of data units on the axes.
Plot with ggimage - multiple images - aspect defined by device
g + ggimage::geom_image(data = images,
aes(x = 20, y = 40, image=url),
size = 0.2,
by="height"
)
Plot with cowplot - single image - freedom over aspect
Independent drawing surface and scale (0-1)
cowplot::ggdraw(g) +
cowplot::draw_image(images[[1, "url"]],
x = .5, y = .3, width = 0.5, height = 0.5)
Plot with annotation_custom (ggplot) - original aspect
Seems to use widest of width of height and centre on mid coordinates
image <- magick::image_read(images[[1, "url"]])
rasterImg <- grid::rasterGrob(image, interpolate = FALSE)
g + annotation_custom(rasterImg, xmin = 00, xmax =200, ymin = 10, ymax = 50)
I strongly feel you may have seen this thread: Can geom_image() from the ggimage package be made to preserve the image aspect ratio? - this was asked and answered by ggplot2 gurus such as Claus Wilke, Baptiste and Marco Sandri - it seems that ggimage is scaling to the device. Thus if you would like a square, you need to save to a device of square dimensions. Or, if you don't have a square image, of course, dimensions relative to your image used.
I used see::geom_point2 (no weird border), because I got the strong feeling that you have not used ggforce::geom_circle. Also mild changes where I added the aes call.
library(ggimage)
#> Loading required package: ggplot2
images <-data.frame(url = c("https://upload.wikimedia.org/wikipedia/commons/d/de/Windows_live_square.JPG"))
# g <-
ggplot(mpg) +
see::geom_point2(aes(x = displ, y = hwy, size = cty), alpha = 0.2) +
scale_size_identity() +
# Add Image
geom_image(data = images,
aes(x = 4, y = 20, image=url),
size = 0.4,
hjust = 0.0,
by = "height"
)
Using reprex, with both fig width and height set to 3 inches
Created on 2021-02-13 by the reprex package (v1.0.0)
A different approach would be to not use ggimage - e.g., use cowplot for custom annotation, makes it super simple to add an image.
library(ggplot2)
library(cowplot)
p <- ggplot(mpg) +
see::geom_point2(aes(x = displ, y = hwy, size = cty), alpha = 0.2) +
scale_size_identity()
ggdraw(p) +
draw_image("https://upload.wikimedia.org/wikipedia/commons/d/de/Windows_live_square.JPG",
x = .5, y = .3, width = 0.5, height = 0.5)
Created on 2021-02-13 by the reprex package (v1.0.0)
Or, use the ggtextures package, with a little tweak of the coordinate system
this discussion seems relevant
library(ggtextures)
library(ggplot2)
library(tibble)
img_df <- tibble(
xmin = 1, ymin = 1, xmax = 4, ymax = 4,
image = "https://upload.wikimedia.org/wikipedia/commons/d/de/Windows_live_square.JPG"
)
ggplot(mpg) +
see::geom_point2(aes(x = displ, y = hwy, size = cty), alpha = 0.2) +
geom_textured_rect(data = img_df,
aes(xmin = xmin, xmax = xmax,
ymin = ymin, ymax = ymax, image = image),
nrow = 1,
ncol = 1,
img_width = unit(1, "null"),
img_height = unit(1, "null"),
position = "identity") +
coord_equal() # this is then necessary...
Created on 2021-02-13 by the reprex package (v1.0.0)

Exact dimensions of linetype spacing and size

This is mostly a follow-up question on a previous one.
Given that in ggplot2 and grid there are different linetypes and spacings vary between line sizes, what is their relationship?
There are two things I do not quite understand.
How is the line size defined? If I were to draw a straight vertical line and substitute it by a rectangle, what should be the width of the rectangle to get the equivalent of the line's size? Especially, how does the lwd = 1 or lwd = 10 I pass to par()/gpar() relate to absolute dimensions (pixels, mm, inches, points)?
The gpar() documentation refers to the par() documentation which states the following:
The line width, a positive number, defaulting to 1. The interpretation is device-specific, and some devices do not implement line widths less than one.
Which is fair enough but I couldn't really find the necessary device specific documentation for common devices.
I think I might assume that the spacings of different linetypes are proportional to their size, but how exactly are the 'dotdash', 'dashed', 'dotted' etc. proportions of dash-length to spacing-length defined?
In the plot below, how can I predict or calculate the dash/spacing lengths in advance?
library(ggplot2)
df <- data.frame(
x = rep(c(0, 1), 4),
y = rep(1:4, each = 2),
size = rep(c(2, 10), each = 4),
linetype = rep(c(2,2,3,3), 2)
)
# The `I()` function automatically assigns identity scales
ggplot(df, aes(x, y, size = I(size), linetype = I(linetype))) +
geom_line(aes(group = y))
I think this is mostly a documentation question, so I'd be happy if you could point me to the correct pages. Otherwise, an answer to my two questions above or a demonstration thereof would also be nice.
EDIT: ggplot has a variable called .pt which they use often to multiply a line size with. That probably means that in grid the linesize is something / .pt, but in what units?
Another great question Teunbrand. I have a partial answer here which seems to give valid results but feels a bit imprecise.
The obvious way to get conversion between lwd and length units is to measure them programatically. For example, to check the lwd of the X11 device, you can do this:
library(grid)
x11()
grid.newpage()
# draw a thick black line that goes right across the page
grid.draw(linesGrob(x = unit(c(-0.1, 1.1), "npc"),
y = unit(c(0.5, 0.5), "npc"),
gp = gpar(lwd = 10)))
# Capture as a bitmap
bmp_line <- dev.capture()
# Work out the thickness of the line in pixels as proportion of page height
lwd_10_prop <- sum(bmp_line != "white")/length(bmp_line)
# Now draw a black rectGrob of known height with lwd of 0 and transparent for completeness
grid.newpage()
grid.draw(rectGrob(width = unit(1.1, "npc"),
height = unit(10, "mm"),
gp = gpar(lwd = 0, col = "#00000000", fill = "black")))
# Capture as a bitmap and measure the width as proportion of device pixels
bmp_rect <- dev.capture()
mm_10_prop <- sum(bmp_rect != "white")/length(bmp_rect)
# Get the ratio of lwd to mm
lwd_as_mm <- lwd_10_prop / mm_10_prop
dev.off()
lwd_as_mm
#> [1] 0.2702296
Which tells us that an lwd of 1 is 0.2702296 mm on this device
We can test this by plotting a red rectangle of our calculated width over a green line near the top of our page, then plotting the same green line over the same red rectangle near the bottom of the page. If and only if they are exactly the same width will we have a completely green line and a completely red line on our page:
grid.newpage()
grid.draw(linesGrob(x = unit(c(-0.1, 1.1), "npc"),
y = unit(c(0.75, 0.75), "npc"),
gp = gpar(lwd = 5, col = "green")))
grid.draw(rectGrob(y = unit(0.75, "npc"),
width = unit(1.1, "npc"),
height = unit(5 * lwd_as_mm, "mm"),
gp = gpar(lwd = 0, col = "#00000000", fill = "red")))
grid.draw(rectGrob(y = unit(0.25, "npc"),
width = unit(1.1, "npc"),
height = unit(5 * lwd_as_mm, "mm"),
gp = gpar(lwd = 0, col = "#00000000", fill = "red")))
grid.draw(linesGrob(x = unit(c(-0.1, 1.1), "npc"),
y = unit(c(0.25, 0.25), "npc"),
gp = gpar(lwd = 5, col = "green")))
Of course, we can improve precision by increasing the thickness of our lines when measuring how wide they are in pixels.
Although the result is supposed to be device-independent, it's worth noting that in the above example I took the results from the X11 device but plotted them in the rstudio device, so the equivalence seems to hold for both devices.

I want to adjust the graph area in ggplot2

I made the following graph using the geomnet package and ggplot2. Then exported it to a pdf. But the graph by itself seems to be larger than the graphing area. it seems to be framed in a small square, as you can see in this picture:
graph
I don't know how to change the size of the square that's framing my graph so that the net nodes will be shown fully in my pdf. Thanks in advance.
Here's the code i'm using, and a data example:
red_list<-data_frame(From=c("A","B","C","D","D"),To=c("C","C","D","Z","A"))
red_list%>%ggplot(aes(from_id=From,to_id=To))+
geom_net(layout.alg = "circle", labelon = TRUE,
size = 12, directed = TRUE, fontsize=2, vjust = 0.5, labelcolour = "grey80",
arrowsize = 1.5, linewidth = 0.5, arrowgap = 0.05, col="darkred",
selfloops = F, ecolour = "grey40") +
theme_net() +
theme(plot.title=element_text(hjust=.5),
plot.subtitle=element_text(hjust=.5))+
ggtitle(label=paste("Figura",i,sep=" "),subtitle = paste("Interacciones entre los sectores de",names(red_list)[i],by=" ")))
I'm not sure how well it will work with graphs, but I uselly play with coord_cartesian(xlim = c(...,...), ylim = c(...,...) to adjust the plotting area.

How to change the size of R plots in Jupyter?

I am wondering how to configure Jupyter to plot a smaller figure within R kernel.
I have tried using options(repr.plot.width = 1, repr.plot.height = 0.75, repr.plot.res = 300), but the result is kinda messy. It is changing the size of the plot R produced. Are there any ways I can directly configure the output graph size in Jupyter.
In other words, how can I change the size in the first figure to the size in the second figure, while not messing up the plot.
You need to manually set the tick size, marker size and text size. The text size and tick size can be set through the theme() function, while marker size through geom_point() function.
df_1 = data.frame(x=c(5, 6, 7, 8, 9), y = c(200, 225, 250, 270, 310))
options(repr.plot.width = 1, repr.plot.height = 0.75)
ggplot(df_1, aes(x = x, y = y)) + geom_point(size = 0.3) +
theme(text = element_text(size = 3), element_line(size = 0.1))
You should simply change the resolution of your plot. For instance, try repr.plot.res = 100 in:
options(repr.plot.width = 1, repr.plot.height = 0.75, repr.plot.res = 100)

How to position annotate text in the blank area of facet ggplot

How to annotate some text in the blank space within a odd numbered faceted ggplot.
Lets have a faceted ggplot with data as below with with 2 rows and 2 columns. So there is blank space in place of 2 row, 2nd column.
df<- data.frame(Ara = rep("XTX", each = 3),
Len = c(744, 750, 755),
Mon = c("Sep", "Oct","Nov"),
Value=c(11.224,10.15,4.23))
df
facetplot<-ggplot(df, aes(x=Value, y=Len, shape=Ara))+
geom_point(size=5.0)+
theme(legend.position = c(.7, .4), legend.direction="vertical")+
facet_wrap(~Mon,scales="free_x", nrow=2)
facetplot
Now i am trying to annotate some text in the space but could not ( as written in red in the image). I am looking for something similar to legend.position for annotated text. Do anyone has any idea on this.Or what would be the possible work around.
Thank you.
After you create your plot, simply use
print(facetplot)
grid.text("your text", x = 0.75, y = 0.25)
See ?grid.text for details on positioning. The default coordinate system is the entire screen device with (0,0) as the lower left and (1,1) as the upper right.
To modify the graphical parameter setting for grid.text such as font color, family, fontface and size ...
grid.text("your text", x = 0.6, y = 0.15, gp = gpar(col="red", fontsize = 20, family = "Times", fontface = "italic"))

Resources