Related
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 increase the Textsize in my legend:
As you can see in the picture, the size of the text is too small.
Changing cex just increases the whole legend, which doesn't look well.
Is there a way to increase just the text size in the legend ?
Right now my plot code looks like this:
par(font.main=3, font.lab=1, font.sub=1, cex.main=2, cex.lab=1.7, cex.sub=1.2)
plot(plot.DE$Monat,cumsum(log10(1+plot.DE$`12.2`)),type="l",col="blue",main="Momentum-Performance Deutschland",ylab="Portfolio Wert",xlab="Jahr",ylim=c(-0.5,2.5),yaxt ="n"
,cex.main=1.5,cex.lab=1,cex.axis=1)
lines(plot.DE$Monat,cumsum(log10(1+plot.DE$`12.7`)),type="l",col="green")
lines(plot.DE$Monat,cumsum(log10(1+plot.DE$`6.2`)),type="l",col="red")
lines(plot.DE$Monat,cumsum(log10(1+plot.DE$Markt)),type="l",col="yellow")
legend("topleft",legend = c("12-2","12-7","6-2","Markt"),col = c("blue","green","red","yellow"),adj = c(0, 0.5),pt.cex = cex,lty=1, cex=1,bg="grey",y.intersp = 0.8,x.intersp = 1.2)
axis(2, at=seq(0,2,by=1),labels=c("1$","10$","100$"), col.axis="black", las=2,cex.axis=1)
Set pt.cex = 1 inside legend and then you can change cex without changing the whole legend size:
Example (cex=1.5):
par(font.main=3, font.lab=1, font.sub=1, cex.main=2, cex.lab=1.7, cex.sub=1.2)
plot(plot.DE$Monat,cumsum(log10(1+plot.DE$`12.2`)),type="l",col="blue",main="Momentum-Performance Deutschland",ylab="Portfolio Wert",xlab="Jahr",ylim=c(-0.5,2.5),yaxt ="n"
,cex.main=1.5,cex.lab=1,cex.axis=1)
lines(plot.DE$Monat,cumsum(log10(1+plot.DE$`12.7`)),type="l",col="green")
lines(plot.DE$Monat,cumsum(log10(1+plot.DE$`6.2`)),type="l",col="red")
lines(plot.DE$Monat,cumsum(log10(1+plot.DE$Markt)),type="l",col="yellow")
legend("topleft",legend = c("12-2","12-7","6-2","Markt"),col = c("blue","green","red","yellow"),pt.cex = 1, cex=1.5,adj = c(0, 0.5),lty=1, ,bg="grey",y.intersp = 0.8,x.intersp = 1.2)
axis(2, at=seq(0,2,by=1),labels=c("1$","10$","100$"), col.axis="black", las=2,cex.axis=1)
I'm trying to add a customized legend to my ggplot but the legend boxes have lines at an angle. I want to change that angle to 0 degrees. Is there any way to do that? Following is the code for an example plot.
ggplot()+geom_abline(aes(color="black",,slope=1,intercept = 0))+
geom_abline(aes(color="red",slope=0.5,intercept = 0))+
scale_color_manual(values=c("black"="black","red"="red"))
We can see that the lines in the legend boxes are slightly inclined and I want to make them horizontal.
You can change how the lines are drawn in the key: I changed y0 and y1 of the segmentsGrob, so that they are in the center (=0.5). (ps have a look at GeomAbline$draw_key before you change it)
library(ggplot2)
library(grid)
GeomAbline$draw_key <- function(data, params, size)
{
segmentsGrob(0, 0.5, 1, 0.5, gp = gpar(col = alpha(data$colour,
data$alpha), lwd = data$size * .pt, lty = data$linetype,
lineend = "butt"))
}
ggplot() + geom_abline(aes(color="black",slope=1,intercept = 0))+
geom_abline(aes(color="red",slope=0.5,intercept = 0))+
scale_color_manual(values=c("black"="black","red"="red"))
I'm trying to add a customized legend to my ggplot but the legend boxes have lines at an angle. I want to change that angle to 0 degrees. Is there any way to do that? Following is the code for an example plot.
ggplot()+geom_abline(aes(color="black",,slope=1,intercept = 0))+
geom_abline(aes(color="red",slope=0.5,intercept = 0))+
scale_color_manual(values=c("black"="black","red"="red"))
We can see that the lines in the legend boxes are slightly inclined and I want to make them horizontal.
You can change how the lines are drawn in the key: I changed y0 and y1 of the segmentsGrob, so that they are in the center (=0.5). (ps have a look at GeomAbline$draw_key before you change it)
library(ggplot2)
library(grid)
GeomAbline$draw_key <- function(data, params, size)
{
segmentsGrob(0, 0.5, 1, 0.5, gp = gpar(col = alpha(data$colour,
data$alpha), lwd = data$size * .pt, lty = data$linetype,
lineend = "butt"))
}
ggplot() + geom_abline(aes(color="black",slope=1,intercept = 0))+
geom_abline(aes(color="red",slope=0.5,intercept = 0))+
scale_color_manual(values=c("black"="black","red"="red"))
I'm looking for a way to control the line thickness of text plotted in R without having the dimensions of the characters change. Here's an example (not using R):
The middle word has a thickness of twice the top, yet the dimensions are the same (so no scaling happened). The bottom word is actually two words: a red word overlain on a heavy white word, to create color separation (especially useful for annotating a busy plot).
Here's a set of commands I threw together to try and replicate the figure above:
png("font.png",width=1.02, height=1.02, units="in", res=150)
par(ps=10, font=1, bg="light gray", col="black", mai=rep(0.02,4), pin=c(1,1))
plot.new()
box()
text(0.5,0.85,"FONT",cex=1)
text(0.5,0.6,"FONT",cex=2)
text(0.5,0.3,"FONT",cex=2,col="white")
text(0.5,0.3,"FONT",cex=1,col="red")
text(0.5,0.1,"FONT",cex=1, font=2, col="white")
text(0.5,0.1,"FONT",cex=1, font=1, col="red")
dev.off()
giving:
So the effect is the same as changing the font-face to bold, but the size difference is not big enough to be noticeable when overlain. The par help page doesn't appear to have a specific setting for this. Anyone have any ideas?
Note changing size in ggplot2 doesn't produce the effect I want either, last time I checked.
You could try adding multiple versions of the text slightly shifted in a circular pattern,
library(grid)
stextGrob <- function (label, r=0.02, x = unit(0.5, "npc"), y = unit(0.5, "npc"),
just = "centre", hjust = NULL, vjust = NULL, rot = 0, check.overlap = FALSE,
default.units = "npc", name = NULL, gp = gpar(), vp = NULL){
let <- textGrob("a", gp=gp, vp=vp)
wlet <- grobWidth(let)
hlet <- grobHeight(let)
tg <- textGrob(label=label, x=x, y=y, gp=gpar(col="red"),
just = just, hjust = hjust, vjust = vjust, rot = rot,
check.overlap = check.overlap,
default.units = default.units)
tgl <- c(lapply(seq(0, 2*pi, length=36), function(theta){
textGrob(label=label,x=x+cos(theta)*r*wlet,
y=y+sin(theta)*r*hlet, gp=gpar(col="white"),
just = just, hjust = hjust, vjust = vjust, rot = rot,
check.overlap = check.overlap,
default.units = default.units)
}), list(tg))
g <- gTree(children=do.call(gList, tgl), vp=vp, name=name, gp=gp)
}
grid.stext <- function(...){
g <- stextGrob(...)
grid.draw(g)
invisible(g)
}
grid.newpage()
grid.rect(gp=gpar(fill="grey"))
grid.stext("Yeah", gp=gpar(cex=4))
There's a version using base graphics lurking in the archives of R-help, from which this is inspired.
Another option using a temporary postscript file, converted to a shape by grImport,
library(grImport)
cat("%!PS
/Times-Roman findfont
100 scalefont
setfont
newpath
0 0 moveto
(hello) show", file="hello.ps")
PostScriptTrace("hello.ps", "hello.xml")
hello <- readPicture("hello.xml")
grid.rect(gp=gpar(fill="grey"))
grid.picture(hello,use.gc = FALSE, gp=gpar(fill="red", lwd=8, col="white"))
I imagine something similar could be done with a temporary raster graphic file, blurred by some image processing algorithm and displayed as raster below the text.
You could try:
text(...,"FONT", vfont = c('serif','bold'))
Although I'm not sure how you'd do the third version of FONT.