I have a linebreak in a legend in R and my problem is that the graphic looks not as expected.
My minimal example is as follows:
plot(1)
legendLabel<-c("t\nu ","tu","wh","trr\nni")
legend("top",legend=legendLabel,horiz=TRUE,fill=c("red","blue","gray","black"))
I would expect that the upper and lower margin of the legend is equal, but this is not the case.
As you can see in the attached image, the lower margin is smaller then the upper.
Does anybody have an idea how to fix it or can anyone tell me what the problem is?
Thanks.
OK, I believe I have a solution for you. I saved the information of the legend position in an object called ld and then create a polygon based on these coordinates. It's a bit tricky to understand, but I am basically expanding the polygon by a few pointsize lengths. In order to do this, I had to first get the character size in inches with par()$cin and covert the pointsize to these dimensions (divide by 72 and multiply by par()$ps. Then, convert this to the units of the plot by scaling with par()$usr to get character width in units (I think this is correct - in any case it works!). I added 3 of these units to the left of the ldcoordinates, 2 to the right, 1 up and 1 down. Here's the result and code:
plot(1)
legendLabel<-c("t\nu ","tu","wh","trr\nni")
ld <- legend("top",legend=legendLabel,horiz=TRUE,fill=c("red","blue","gray","black"), bty="n")
CIN <- par()$cin
PS <- par()$ps
USR <- par()$usr
CIN.USR <- c((CIN[1]/72*PS)/(USR[2]-USR[1]), (CIN[2]/72*PS)/(USR[4]-USR[3]))
xs <- c(ld$text$x[1], ld$text$x[1], ld$text$x[length(ld$text$x)], ld$text$x[length(ld$text$x)])
ys <- c(ld$text$y[1], ld$text$y[1], ld$text$y[length(ld$text$x)], ld$text$y[length(ld$text$x)])
polygon(
x = xs + c(-3*CIN.USR[1], -3*CIN.USR[1], 2*CIN.USR[1], 2*CIN.USR[1]),
y = ys+c(-1*CIN.USR[2], 1*CIN.USR[2], 1*CIN.USR[2], -1*CIN.USR[2])
)
Thanks to #'Marc in the box' I found a good working solution.
Disable the box as he told with bty="n" and then
ld<-legend("top",legend=legendLabel, cex=0.65, fill=colorNames, horiz=TRUE,bty="n")
height<-(ld$rect$top-ld$text$y[1])*2
xs <- c(ld$rect$left, ld$rect$left, ld$rect$left+ld$rect$w, ld$rect$left+ld$rect$w)
ys <- c(ld$rect$top, ld$rect$top-height, ld$rect$top-height, ld$rect$top)
polygon(x = xs , y = ys)
So I first calculated the distance between the top corner and the dataPoint and afterwords draw with this information a polygon.
Works quite general has far as I have seen.
Thanks.
Related
I'm having some trouble creating a perspective plot that looks exactly how I want it to look. In particular, I am trying to get the mesh not to be visible at all. If you look at the image on the left you can see faint lines running between the tiles. I want it looking like the right image with no lines visible:
I specifically want a solution with graphics::persp or other base R function. I am not interested in 3rd party packages like rgl.
I obtained the right by using polygon and specifying a border color to match the col color. If I leave border=NA with polygon I get the same result as with persp. However, it seems persp just takes the first border value and re-uses it, unlike polygon which matches colors to the polygons.
This is the code used to generate the image:
nr <- nc <- 10
mx <- matrix(numeric(nr * nc), nr)
par(mai=numeric(4))
col <- gray((row(mx[-1,-1]) * col(mx[-1,-1])/((nr-1)*(nc-1))))
par(mfrow=c(1,3), mai=c(0, 0, .25, 0), pty='s')
persp(
mx, phi=90, theta=0, border=NA, col=col, r=1e9, zlim=c(0,1),
axes=FALSE, box=FALSE
)
title('Persp border=NA')
persp(
mx, phi=90, theta=0, border=col, col=col, r=1e9, zlim=c(0,1),
axes=FALSE, box=FALSE
)
title('Persp border=col')
plot.new()
mxpoly.x <- rbind(
c(row(mx)[-nr, -nc]), c(row(mx)[-1, -nc]), c(row(mx)[-1, -1]),
c(row(mx)[-nr, -1]), NA
)
mxpoly.y <- rbind(
c(col(mx)[-nr, -nc]), c(col(mx)[-1, -nc]), c(col(mx)[-1, -1]),
c(col(mx)[-nr, -1]), NA
)
title('Polygon')
polygon(
((mxpoly.x - 1) / (max(mxpoly.x,na.rm=TRUE) - 1)),
((mxpoly.y - 1) / (max(mxpoly.y,na.rm=TRUE) - 1)),
col=col, border=col
)
That looks like a result of antialiasing. When each cell is drawn, the background is white, so antialiasing means the border pixels are drawn in a lighter colour.
On a Mac, you can fix this by turning antialiasing off. Your first example gives
by default, but if I open the graphics device using
quartz(antialias = FALSE)
and then run the identical code, I get
Turning off antialiasing can cause jagged edges, so this might not really be an acceptable solution to your real problem if it has diagonal lines.
You might be able to get things to work by drawing the surface twice with antialiasing: the first time will show borders, the second time might still show something, but should show less. However, persp() has no add = TRUE argument, so drawing things the second time is likely to be tricky.
If you're not on a Mac, you'll need to read about the device you're using to find if it allows control of antialiasing.
Edited to add: I tried modifying the C source to the persp function
to draw the surface 2 or 3 times. The boundaries were still slightly
visible when it was drawn twice, but invisible with 3 draws.
I will try 3D printing data to make some nice visual illustration for a binary classification example.
Here is my 3D plot:
require(rgl)
#Get example data from mtcars and normalize to range 0:1
fun_norm <- function(k){(k-min(k))/(max(k)-min(k))}
x_norm <- fun_norm(mtcars$drat)
y_norm <- fun_norm(mtcars$mpg)
z_norm <- fun_norm(mtcars$qsec)
#Plot nice big spheres with rgl that I hope will look good after 3D printing
plot3d(x_norm, y_norm, z_norm, type="s", radius = 0.02, aspect = T)
#The sticks are meant to suspend the spheres in the air
plot3d(x_norm, y_norm, z_norm, type="h", lwd = 5, aspect = T, add = T)
#Nice thick gridline that will also be printed
grid3d(c("x","y","z"), lwd = 5)
Next, I wanted to add a z=0 plane, inspired by this blog here describing the r2stl written by Ian Walker. It is supposed to be the foundation of the printed structure that holds everything together.
planes3d(a=0, b=0, c=1, d=0)
However, it has no volume, it is a thin slab with height=0. I want it to form a solid base for the printed structure, which is meant to keep everything together (check out the aforementioned blog for more details, his examples are great). How do I increase the thickness of my z=0 plane to achieve the same effect?
Here is the final step to exporting as STL:
writeSTL("test.stl")
One can view the final product really nicely using the open source Meshlab as recommended by Ian in the blog.
Additional remark: I noticed that the thin plane is also separate from the grids that I added on the -z face of the cube and is floating. This might also cause a problem when printing. How can I merge the grids with the z=0 plane? (I will be sending the STL file to a friend who will print for me, I want to make things as easy for him as possible)
You can't make a plane thicker. You can make a solid shape (extrude3d() is the function to use). It won't adapt itself to the bounding box the way a plane does, so you would need to draw it last.
For example,
example(plot3d)
bbox <- par3d("bbox")
slab <- translate3d(extrude3d(bbox[c(1,2,2,1)], bbox[c(3,3,4,4)], 0.5),
0,0, bbox[5])
shade3d(slab, col = "gray")
produces this output:
This still isn't printable (the points have no support), but it should get you started.
In the matlib package, there's a function regvec3d() that draws a vector space representation of a 2-predictor multiple regression model. The plot method for the result of the function has an argument show.base that draws the base x1-x2 plane, and draws it thicker if show.base >0.
It is a simple hack that just draws a second version of the plane at a small offset. Maybe this will be enough for your application.
if (show.base > 0) planes3d(0, 0, 1, 0, color=col.plane, alpha=0.2)
if (show.base > 1) planes3d(0, 0, 1, -.01, color=col.plane, alpha=0.1)
I'm trying to create a bubble chart using a set of data as follows:
X --> 10
Y --> 20
Z --> 5
Q --> 10
I simply need to have the biggest bubble (based on its number) to be at the centre (give or take) and the rest of the bubbles be around it without overlapping.
All of the R examples I have seen require a two dimensional dataset, and since the data I have are only one dimensional, I like to know if it's at all possible to create such graphs in R.
It would be great if someone could suggest me some useful hints or so. By the way for this task I need to use a SA tools so something like d3js is out of options. However, I am open to using a tool other than R.
I wasn't quite sure if this question should be asked in On Stack Overflow or Cross Validated, so if moderators believe it doesn't belong here, I'll remove it.
This should do, the main idea being that you sort by the value of the radius, so the first is the biggest, then shift the values around it (odd on one side, even on the other) so that the values are decreasing both ways.
Further explanations in the code.
library(plotrix)
library(RColorBrewer)
# Set the random seed, to get reproducible results
set.seed(54321)
# Generate some random values for the radius
num.circles <- 11
rd <- runif(num.circles, 1, 20)
df <- data.frame(labels=paste("Lbl", 1:num.circles), radius=rd)
# Sort by descending radius. The biggest circle is always row 1
df <- df[rev(order(df$radius)),]
# Now we want to put the biggest circle in the middle and the others on either side
# To do so we reorder the data frame taking the even values first reversed, then the odd values.
# This ensure the biggest circle is in the middle
df <- df[c(rev(seq(2, num.circles, 2)), seq(1, num.circles, 2)),]
# Space between the circles. 0.2 * average radius seems OK
space.between <- 0.2 * mean(df$radius)
# Creat an empty plot
plot(0, 0, "n", axes=FALSE, bty="n", xlab="", ylab="",
xlim=c(0, sum(df$radius)*2+space.between*num.circles),
ylim=c(0, 2.5 * max(df$radius)))
# Draw the circle at half the height of the biggest circle (plus some padding)
xx <- 0
mid.y <- max(df$radius) * 1.25
# Some nice degrading tones of blue
colors <- colorRampPalette(brewer.pal(8,"Blues"))(num.circles/2)
for (i in 1:nrow(df))
{
row <- df[i,]
x <- xx + row$radius + i*space.between
y <- mid.y
# Draw the circle
draw.circle(x, y, row$radius,
col=colors[abs(num.circles/2-i)])
# Add the label
text(x, y, row$labels, cex=0.6)
# Update current x position
xx <- xx + row$radius * 2
}
The result:
Live version on RFiddle.
I am having difficulty of producing X,Y coordinates of a circle and then drawing line segments to it. Basically what I want to do is draw 360 lines from the center of a circle to the outside of the circle in perfect spacing. This is how I am currently doing it but it is not working. If there is a different way to do this, that works great as well! Also I am hoping that degree 0 starts at the left side of the circle.
t <- seq(0,2*pi,length=360)
coords <- t(rbind( sin(t)*127.28125, cos(t)*127.28125))
plot(coords,type='n',xlim=c(-63.625625,63.625625),ylim=c(0,127.28125))
lines(coords)
deg=data.frame(coords[,1],coords[,2])
head(deg)
deg$count=1
deg$degree=1
for(i in 1:nrow(coords)){
if(deg$count[i]<=270){
deg$degree[i]=i-1+90-45
} else {
deg$degree[i]=i-1-270-45
}
}
names(deg)[1] <- "X"
names(deg)[2] <- "Y"
i=1
for(i in 1:19){
segments(0,0,deg$X[deg$degree==((5*(i-1)))],deg$Y[deg$degree==((5*(i-1)))])
cat(((5*(i-1))),'\t')
}
Update:
I am having some issues with where the lines get drawn. Basically as we go around the circle the errors get larger so when pi/2 radians happens and it is straight up, the value is slightly to the right of x=0. This may not be possible to get but thought I would ask to see if there was anyway to fix that! The 45 90 and 135 should all match on the lines.
How about this
th <- seq(0, 2*pi, length.out=360)
r <- 2
x <- r*cos(th)
y <- r*sin(th)
plot(x,y, type="n")
segments(0,0,x,y)
Basically i choose th and r in polar space and convert to Cartesian.
If you want to start with 0 on the left, use
x <- -r*cos(th)
instead.
How can I get the width and height of a character in usr coordinates? I found something on R-help, but that appears not to make it completely clear. I assumed that
plot(NULL, xlim=c(-1,1), ylim=c(-1,1))
h <- par()$cxy
rect(-h[1]/2, -h[2]/2, h[1]/2, h[2]/2)
text(0,0,"M")
would be the answer but the rectangle is slightly too big. Additionally I want the size also to respect different cex values. Thanks for your time!
I finally discovered an answer in the par docu:
cxy
R.O.; size of default character (width, height) in user coordinate
units. par("cxy") is par("cin")/par("pin") scaled to user coordinates.
Note that c(strwidth(ch), strheight(ch)) for a given string ch is
usually much more precise.
Using strwidth and strheight instead of par()$cxy gives much better results.
plot(NULL, xlim=c(-1,1), ylim=c(-1,1))
h <- c(strwidth("M"), strheight("M"))
rect(-h[1]/2, -h[2]/2, h[1]/2, h[2]/2)
text(0,0,"M")