R: legend boxes with more than one colour - r

I have a plot() where multiple colour shadings represent the same thing. I would like to add a legend that conveys this by having dual-coloured boxes (see example below). Is there any way to do this using legend() or a similar command? Alternatively, is there a way to identify the precise coordinates of these boxes so I can plot a polygon() triangle over it?
Note: legend() does return the coordinates of the outer box and the top left of each labels, but I am not sure if this is sufficient to calculate where the coloured boxes are.

This is a bit of a hack, but you could put two legends on top of another. Unfortunately, there is no left triangle pch which would have been exactly as you wanted.
plot(1)
legend("bottomright",c("Label 1", "Label 2"),pch=22,col=c("red","blue"),pt.bg=c("red","blue"), pt.cex=1.8)
legend("bottomright",c("Label 1", "Label 2"),pch=21,col=c("green","orange"),pt.bg=c("green","orange"))

A slightly dirty hack can allow you to get the legend() function to give you the necessary information. A smarter person than me would probably work out how legend() calculates box positioning and replicate this outside the function. Note that editing standard R functions is probably not recommended.
If you have not edited R functions yet, an easy (and temporary) way to access it, is typing
fix(legend)
Typing
rm(legend)
later will undo your changes.
Find this section that says fill <- rep and add the lines indicated by the comments:
fillList <- NULL ## added
if (mfill) {
if (plot) {
fill <- rep(fill, length.out = n.leg)
rect2(left = xt, top = yt + ybox/2, dx = xbox, dy = ybox,
col = fill, density = density, angle = angle,
border = border)
fillList <- data.frame(left = xt, top = yt + ybox/2, dx = xbox, dy = ybox) ## added
}
xt <- xt + dx.fill
}
Find the very last line and change it to
invisible(list(rect = list(w = w, h = h, left = left, top = top),
text = list(x = xt, y = yt), fillList=fillList)) ## modified
Now call legend via
output <- legend(...) ## replace ... with whatever you want to have as inputs
and the draw triangles using the information returned by legend() like so:
with(output$fillList[1,], { ## first box
polygon(c(left, left+dx, left+dx), c(top, top, top-dy), col=myColour, border=NA)
})

Related

non-linear 2d object transformation by horizontal axis

How can such a non-linear transformation be done?
here is the code to draw it
my.sin <- function(ve,a,f,p) a*sin(f*ve+p)
s1 <- my.sin(1:100, 15, 0.1, 0.5)
s2 <- my.sin(1:100, 21, 0.2, 1)
s <- s1+s2+10+1:100
par(mfrow=c(1,2),mar=rep(2,4))
plot(s,t="l",main = "input") ; abline(h=seq(10,120,by = 5),col=8)
plot(s*7,t="l",main = "output")
abline(h=cumsum(s)/10*2,col=8)
don't look at the vector, don't look at the values, only look at the horizontal grid, only the grid matters
####UPDATE####
I see that my question is not clear to many people, I apologize for that...
Here are examples of transformations only along the vertical axis, maybe now it will be more clear to you what I want
link Source
#### UPDATE 2 ####
Thanks for your answer, this looks like what I need, but I have a few more questions if I may.
To clarify, I want to explain why I need this, I want to compare vectors with each other that are non-linearly distorted along the horizontal axis .. Maybe there are already ready-made tools for this?
You mentioned that there are many ways to do such non-linear transformations, can you name a few of the best ones in my case?
how to make the function f() more non-linear, so that it consists, for example, not of one sinusoid, but of 10 or more. Тhe figure shows that the distortion is quite simple, it corresponds to one sinusoid
and how to make the function f can be changed with different combinations of sinusoids.
set.seed(126)
par(mar = rep(2, 4),mfrow=c(1,3))
s <- cumsum(rnorm(100))
r <- range(s)
gridlines <- seq(r[1]*2, r[2]*2, by = 0.2)
plot(s, t = "l", main = "input")
abline(h = gridlines, col = 8)
f <- function(x) 2 * sin(x)/2 + x
plot(s, t = "l", main = "input+new greed")
abline(h = f(gridlines), col = 8)
plot(f(s), t = "l", main = "output")
abline(h = f(gridlines), col = 8)
If I understand you correctly, you wish to map the vector s from the regular spacing defined in the first image to the irregular spacing implied by the second plot.
Unfortunately, your mapping is not well-defined, since there is no clear correspondence between the horizontal lines in the first image and the second image. There are in fact an infinite number of ways to map the first space to the second.
We can alter your example a bit to make it a bit more rigorous.
If we start with your function and your data:
my.sin <- function(ve, a, f, p) a * sin(f * ve + p)
s1 <- my.sin(1:100, 15, 0.1, 0.5)
s2 <- my.sin(1:100, 21, 0.2, 1)
s <- s1 + s2 + 10 + 1:100
Let us also create a vector of gridlines that we will draw on the first plot:
gridlines <- seq(10, 120, by = 2.5)
Now we can recreate your first plot:
par(mar = rep(2, 4))
plot(s, t = "l", main = "input")
abline(h = gridlines, col = 8)
Now, suppose we have a function that maps our y axis values to a different value:
f <- function(x) 2 * sin(x/5) + x
If we apply this to our gridlines, we have something similar to your second image:
plot(s, t = "l", main = "input")
abline(h = f(gridlines), col = 8)
Now, what we want to do here is effectively transform our curve so that it is stretched or compressed in such a way that it crosses the gridlines at the same points as the gridlines in the original image. To do this, we simply apply our mapping function to s. We can check the correspondence to the original gridlines by plotting our new curves with a transformed axis :
plot(f(s), t = "l", main = "output", yaxt = "n")
axis(2, at = f(20 * 1:6), labels = 20 * 1:6)
abline(h = f(gridlines), col = 8)
It may be possible to create a mapping function using the cumsum(s)/10 * 2 that you have in your original example, but it is not clear how you want this to correspond to the original y axis values.
Response to edits
It's not clear what you mean by comparing two vectors. If one is a non-linear deformation of the other, then presumably you want to find the underlying function that produces the deformation. It is possible to create a function that applies the deformation empirically simply by doing f <- approxfun(untransformed_vector, transformed_vector).
I didn't say there were many ways of doing non-linear transformations. What I meant is that in your original example, there is no correspondence between the grid lines in the original picture and the second picture, so there is an infinite choice for which gridines in the first picture correspond to which gridlines in the second picture. There is therefore an infinite choice of mapping functions that could be specified.
The function f can be as complicated as you like, but in this scenario it should at least be everywhere non-decreasing, such that any value of the function's output can be mapped back to a single value of its input. For example, function(x) x + sin(x)/4 + cos(3*(x + 2))/5 would be a complex but ever-increasing sinusoidal function.

R: Fill area under curve with Polygon()

Is there an universal way to fill the area under a curve with the polygon() function?
As far as I understand the answer in this post:
Fill under line curve I would assume that polygon(c(min(x), x, max(x)), c(min(y), y, min(y)) will always work, because it starts on the bottom left, goes up to the curve, down to the button right and then back to the beginning.
But it does not always work. Sometimes, I still get a graph like in this question: Why polygon in R works with a full curve but not with a half curve? where R simply draws a line and fills in under and above it.
So what would be an iput (or an other function) that will always work?
Maybe you need na.rm = TRUE for min(y)? Anyways, here is a function giving you the possibility to specify a horizontal border to which to fill:
fill_to_border <- function(f, from, to, y_border = 0, col = 1) {
curve_points <- curve(f, from, to)
polygon_x <- c(min(curve_points$x), curve_points$x, max(curve_points$x))
polygon_y <- c(y_border, curve_points$y, y_border)
polygon_points <- list(x = polygon_x, y = polygon_y)
polygon(polygon_points, col = col)
}
fill_to_border(dnorm, -4, 0)

Dot plots (as opposed to dotplots) in R

I am trying to write a function that will produce what I regard as a real dot plot (unlike the Cleveland variety, I require a univariate scatterplot with the dots stacked for (nearly) equal values). I have come close:
In this illustration, the dots you see are actually rotated text strings of lower-case "o"s. It is done this way because I need the dot spacing to stay constant if the plot is re-scaled. However, I'd like something better than lower-case "o"s, for example, filled dots instead of circles. This could be done if I could access the font that is used for the standard plotting symbols (pch = 1:25 in the plot function and relatives). Then I could make a text string with that font and get what's needed. Does anybody know how to do that?
PS - No, a histogram with lots of bins is not an acceptable substitute.
I did find a way to get the desired dot plot using low-level graphics parameters (namely "usr", the actual user coordinates of the plotting area, and "cxy", the character size). The recordGraphics() function wraps the part that needs to be changed when the graph is resized. Here's the function:
dot.plot = function(x, pch = 16, bins = 50, spacing = 1, xlab, ...) {
if(missing(xlab))
xlab = as.character(substitute(x))
# determine dot positions
inc = diff(pretty(x, n = bins)[1:2])
freq = table(inc * round(x / inc, 0))
xx = rep(as.numeric(names(freq)), freq)
yy = unlist(lapply(freq, seq_len))
# make the order of the dots the same as the order of the data
idx = seq_along(x)
idx[order(x)] = idx
xx = xx[idx]
yy = yy[idx]
# make a blank plot
plot(xx, yy, type = "n", axes = FALSE, xlab = xlab, ylab = "")
# draw scale
axis(1)
ylow = par("usr")[3]
abline(h = ylow) # extend to full width
# draw points and support resizing
recordGraphics({
yinc = 0.5 * spacing * par("cxy")[2]
points(xx, ylow + yinc * (yy - .5), pch = pch, ...)
},
list(),
environment(NULL))
invisible()
}
The spacing argument may be used if you want a tighter or looser gap between dots. An example...
with(iris, dot.plot(Sepal.Length, col = as.numeric(Species)))
This is a better solution than trying to do it with text, but also a little bit scary because of the warnings you see in the documentation for recordGraphics

Rgraphviz: edge labels outside plotting region

I am trying to plot a Rgraphviz object with two edge labels. Unfortunately the labels fall outside the plot. Here is my example:
require('Rgraphviz')
set.seed(123)
g1 <- randomGraph(letters[1:10], 1:4, 0.4)
eAttrs <- list()
eAttrs$label <- c("a~g" = "I have a very long label 1", "a~i" = "and a long label 2")
plot(g1, edgeAttrs = eAttrs)
Here is my plot:
I tried several things with no success:
1.
Set a larger bounding box
z <- agopen(g1, "foo")
z#boundBox#upRight#x <- z#boundBox#upRight#x + 300
z#boundBox#upRight#y <- z#boundBox#upRight#y + 300
plot(z, edgeAttrs = eAttrs)
2.
Decrease the label fontsize (not really what I want in my application, anyways)
eAttrs$labelfontsize=c("a~g"="3")
plot(g1, edgeAttrs = eAttrs)
3.
Change par attributes:
par(oma=c(10,10,10,10))
plot(g1, edgeAttrs = eAttrs)
4.
Change node, edge and general attributes from ?Rgraphviz::GraphvizAttributes
attrs <- list(graph=list(size=c(1, 1)))
attrs$edge$fontsize<-8
plot(g1, edgeAttrs = eAttrs, attrs=attrs)
None of my attempts seem to work. Does anyone have an idea?
The problem
Calling plot() on a graph object dispatches an S4 method (shown by getMethod("plot", "graph")), which in turn calls the function shown by typing getMethod("plot", "Ragraph"). That function contains the following rather unfortunate lines which, regardless of any related parameter settings you've made, will override them to reset the left and right margins to 0. Frustrating!
oldpars <- par(mai = c(sheight, 0, mheight, 0))
on.exit(par(oldpars), add = TRUE)
A workaround
One workaround is to construct a three panel layout in which the left and right panels are just there to provide a bit of buffering space. Turn off clipping, plot your graph object in the middle panel, and it then seems to work:
layout(matrix(1:3, nrow=1), widths=c(1,5,1))
par(xpd=NA) ## turn off all clipping
plot.new() ## blank plot in Panel 1
plot(g1, edgeAttrs = eAttrs) ## graph in Panel 2
plot.new() ## blank plot in Panel 3
I found another solution: In my original question I changed the size of the bounding box in a laid out graph I got with agopen. Plotting the laid out graph showed no edge labels at all. I found that it is possible to pass the edge attributes from the graph object to agopen and then change the bounding box:
require('Rgraphviz')
set.seed(123)
g1 <- randomGraph(letters[1:10], 1:4, 0.4)
eAttrs <- list()
eAttrs$label <- c("a~g" = "I have a very long label 1", "a~i" = "and a long label 2")
z <- agopen(g1, "foo", edgeAttr=eAttrs)
z#boundBox#botLeft#x <- z#boundBox#botLeft#x - 400 ##left
z#boundBox#upRight#x <- z#boundBox#upRight#x + 200 ##right
plot(z)
The plot:

aligning patterns across panels with gridExtra and grid.pattern()

The gridExtra package adds a grob of class "pattern" that lets one fill rectangles with patterns. For example,
library(gridExtra)
grid.pattern(pattern = 1)
creates a box filled with diagonal lines. I want to create a stack of panels in which each panel is filled with these diagonal lines. This is easy:
library(lattice); library(gridExtra)
examplePlot <- xyplot(
1 ~ 1 | 1:2,
panel = function () grid.pattern(pattern = 1),
layout = c(1, 2),
# Remove distracting visual detail
scales = list(x=list(draw=FALSE), y=list(draw=FALSE)),
strip = FALSE, xlab = '', ylab = ''
)
print(examplePlot)
The problem is that the diagonal lines aren't aligned across panels. That is, there is a visual "break" where the bottom of the first panel meets the top of the second panel: at that point, the lines don't line up. This is the problem that I want to fix.
I can eliminate most of the visual break by adding the argument pattern.offset = c(.2005, 0) to the grid.pattern call, and making sure that it applies only to the bottom panel. But this solution doesn't generalize. For example, if I change the pattern (e.g., by using the granularity argument to grid.pattern), this solution won't work. Is there a more general fix?
To make this work, you'll have to take charge of setting the panel.height argument used by print.trellis. (To see why, try resizing your plotting device after running your example code: as the size of the device and the panels changes, so does the matching/mismatching of the lines):
## Calculate vertical distance (in mm) between 45 degree diagonal lines
## spaced 5mm apart (the default distance for grid.pattern).
vdist <- 5 * sqrt(2)
nLines <- 8L ## can be any integer
panelHeight <- list(x = nLines*vdist, units = "mm", data = NULL)
## Plot it
print(examplePlot, panel.height=panelHeight)

Resources