In Stata I can add text to a plot at given coordinates, e.g.:
clear
set obs 10
gen x = rnormal()
gen y = rnormal()
twoway scatter y x, text(0.8 0.8 "Some text")
The result is "Some text" at coordinates (0.8, 0.8):
I want to add a similar annotation in Julia with Gadfly. I found Guide.annotation that can add layers of graphs and I could not figure out how to apply it to text instead of shapes. The documentation mentions at the top:
Overlay a plot with an arbitrary Compose graphic.
but the Compose link shows a website in Chinese.
How can I add a text label (or caption, or annotation) with Gadfly?
You can check the guide on Compose in julia. In the example in your link they use Circle, but you just as easily can use:
text(x, y, value)
or using the linked example code:
Pkg.add.(["Gadfly", "Compose"])
using Gadfly, Compose;
plot(x = rand(10),
y = rand(10),
Guide.annotation(compose(context(), text(0.8, 0.8, "Some text"))))
in the link I provided they redirect to a source file for the comprehensive list:
These are basic constructors for the in-built forms - see src/form.jl for more constructors.
polygon(points)
rectangle(x0, y0, width, height)
circle(x, y, r)
ellipse(x, y, x_radius, y_radius)
text(x, y, value)
line(points)
curve(anchor0, ctrl0, ctrl1, anchor1)
bitmap(mime, data, x0, y0, width, height)
Related
Two questions in one: Given a line plotted in Julia, how can I
delete it from the plot and legend (without clearing the whole plot)
change its properties (such as color, thickness, opacity)
As a concrete example in the code below, how can I 1. delete previous regression lines OR 2. change their opacity to 0.1?
using Plots; gr()
f = x->.3x+.2
g = x->f(x)+.2*randn()
x = rand(2)
y = g.(x)
plt = scatter(x,y,c=:orange)
plot!(0:.1:1, f, ylim=(0,1), c=:green, alpha=.3, linewidth=10)
anim = Animation()
for i=1:200
r = rand()
x_new, y_new = r, g(r)
push!(plt, x_new, y_new)
push!(x, x_new)
push!(y, y_new)
A = hcat(fill(1., size(x)), x)
coefs = A\y
plot!(0:.1:1, x->coefs[2]*x+coefs[1], c=:blue) # plot new regression line
# 1. delete previous line
# 2. set alpha of previous line to .1
frame(anim)
end
gif(anim, "regression.gif", fps=5)
I tried combinations of delete, pop! and remove but without success.
A related question in Python can be found here: How to remove lines in a Matplotlib plot
Here is a fun and illustrative example of how you can use pop!() to undo plotting in Julia using Makie. Note that you will see this goes back in the reverse order that everything was plotted (think, like adding and removing from a stack), so deleteat!(scene.plots, ind) will still be necessary to remove a plot at a specific index.
using Makie
x = range(0, stop = 2pi, length = 80)
f1(x) = sin.(x)
f2(x) = exp.(-x) .* cos.(2pi*x)
y1 = f1(x)
y2 = f2(x)
scene = lines(x, y1, color = :blue)
scatter!(scene, x, y1, color = :red, markersize = 0.1)
lines!(scene, x, y2, color = :black)
scatter!(scene, x, y2, color = :green, marker = :utriangle, markersize = 0.1)
display(scene)
sleep(10)
pop!(scene.plots)
display(scene)
sleep(10)
pop!(scene.plots)
display(scene)
You can see the images above that show how the plot progressively gets undone using pop(). The key idea with respect to sleep() is that if we were not using it (and you can test this on your own by running the code with it removed), the fist and only image shown on the screen will be the final image above because of the render time.
You can see if you run this code that the window renders and then sleeps for 10 seconds (in order to give it time to render) and then uses pop!() to step back through the plot.
Docs for sleep()
I have to say that I don't know what the formal way is to accomplish them.
There is a cheating method.
plt.series_list stores all the plots (line, scatter...).
If you have 200 lines in the plot, then length(plt.series_list) will be 200.
plt.series_list[1].plotattributes returns a dictionary containing attributes for the first line(or scatter plot, depends on the order).
One of the attributes is :linealpha, and we can use it to modify the transparency of a line or let it disappear.
# your code ...
plot!(0:.1:1, x->coefs[2]*x+coefs[1], c=:blue) # plot new regression line
# modify the alpha value of the previous line
if i > 1
plt.series_list[end-1][:linealpha] = 0.1
end
# make the previous line invisible
if i > 2
plt.series_list[end-2][:linealpha] = 0.0
end
frame(anim)
# your code ...
You cannot do that with the Plots package. Even the "cheating" method in the answer by Pei Huang will end up with the whole frame getting redrawn.
You can do this with Makie, though - in fact the ability to interactively change plots was one of the reasons for creating that package (point 1 here http://makie.juliaplots.org/dev/why-makie.html)
Not sure about the other popular plotting packages for Julia.
I'm trying to create 3D plots of simulated tree roots in R. Here is an example of a root system growing over time:
This is essentially a 3D network of cylinders, where the cylinder diameter (and, optionally, color) represents the size of the root. The available data includes:
x, y, z of the root centroid
direction of "parent" root (e.g. +x, -x, +y, -y, +z, -z), although this information could be captured in several different ways, including by calculating the x, y, z of the parent directly prior to plotting.
size of root
Example 3D data is here, but here is my first attempt at it in just 2D using ggplot2::geom_spoke:
dat <- data.frame(x = c(0,1,-1,0,1,-1),
y = c(-1,-1,-1,-2,-2,-2),
biomass = c(3,1.5,1.5,1,1,1),
parent.dir = c("+y","-x","+x","+y","+y","+y"))
dat$parent.dir <- as.numeric(as.character(factor(dat$parent.dir,
levels = c("-x", "+x", "-y", "+y"),
labels = c(pi, 0, pi*3/2, pi/2))))
ggplot(dat, aes(x = x, y = y)) +
geom_point(x = 0, y = 0, size = 20) +
geom_spoke(radius = 1,
aes(angle = parent.dir,
size = biomass)) +
coord_equal()
I prefer a solution based in the ggplot2 framework, but I realize that there are not a ton of 3D options for ggplot2. One interesting approach could be to creatively utilize the concept of network graphs via the ggraph and tidygraph packages. While those packages only operate in 2D as far as I know, their developer has also had some interesting related ideas in 3D that could also be applied.
The rgl library in seems to be the go-to for 3D plots in R, but an rgl solution just seems so much more complex and lacks the other benefits of ggplot2, such as faceting by year as in the example, easily adjusting scales, etc.
Example data is here:
I don't understand the format of your data so I'm sure this isn't the display you want, but it shows how to draw a bunch of cylinders in rgl:
root <- read.csv("~/temp/root.csv")
segments <- data.frame(row.names = unique(root$parent.direction),
x = c(-1,0,1,0,0),
y = c(0,1,0,0,-1),
z = c(0,0,0,0.2,0))
library(rgl)
open3d()
for (i in seq_len(nrow(root))) {
rbind(root[i,2:4],
root[i,2:4] - segments[root$parent.direction[i],]) %>%
cylinder3d(radius = root$size[i]^0.3, closed = -2, sides = 20) %>%
shade3d(col = "green")
}
decorate3d()
This gives the following display (rotatable in the original):
You can pass each cylinder through addNormals if you want it to look smooth, or use sides = <some big number> in the cylinder3d to make them look rounder.
I am trying to plot a 3D space time cube in R and I want to have a basemap.
I am using rgl library. I know how to plot my data using x, y and z, where z is the time variable. I have also managed to download a map that I want to use as reference from openstreetmap, using the library in R. However, I cannot find a way to plot my data on the map in a 3D environment. I found the following code in several sites and as an answer to a similar question:
map3d <- function(map, ...){
if(length(map$tiles)!=1){stop("multiple tiles not implemented") }
nx = map$tiles[[1]]$xres
ny = map$tiles[[1]]$yres
xmin = map$tiles[[1]]$bbox$p1[1]
xmax = map$tiles[[1]]$bbox$p2[1]
ymin = map$tiles[[1]]$bbox$p1[2]
ymax = map$tiles[[1]]$bbox$p2[2]
xc = seq(xmin,xmax,len=ny)
yc = seq(ymin,ymax,len=nx)
colours = matrix(map$tiles[[1]]$colorData,ny,nx)
m = matrix(0,ny,nx)
surface3d(xc,yc,m,col=colours, ...)
}
However, I cannot really understand how it works.
Here's my code so far:
library(rgl)
library(ggplot2)
library(OpenStreetMap)
map <- openmap(c(53.5,73.6),c(15.7,134.7),type= 'esri-topo')
plot3d(x,y,z, col= colour) # to plot my data
autoplot(map) # to plot the map. though this is 2D
Again, I know how to plot my data on a 2D map. Confused with the 3D.
Any hints and tips on how to do this?
One option is to use the newish 'show2d' function in 'rgl'.
library(rgl)
library(OpenStreetMap)
library(raster)
map <- openmap(c(53.5,73.6),c(15.7,134.7),type= 'esri-topo')
## fake up some xyz
xyz <- expand.grid(x = map$bbox$p1,
y = map$bbox$p2,
z = 1:4)
plot3d(xyz, col = "black") # to plot my data
EDIT: this is wrong, it's only fitted to the bounding box
getting the orientation right is confusing, needs to be check with x, y, z arguments to show2d.
show2d(raster::plotRGB(raster(map)))
This function captures the normal plot expression, writes it to PNG and then texture maps it onto a quad in the scene.
I can't quite see how to control the position of the quad for the image texture with the x, y, z args - work in progress.
I am trying to figure out how to use grconvertX/grconvertX in ggplot. My ultimate goal is to to add annotation to a ggplot2 figure (and possibly lattice) with grid.text and grid.lines by going from user coordinates to device coordinates. I know it can be done with grobs but I am wondering if there is an easier way.
The following code allows me to pass values from user coordinates to ndc coordinates and use those values to annotate the plot with grid.text.
graphics.off() # close graphics windows
library(grid)
library(gridBase)
test= data.frame(
x = c(1,2,3),
y = c(12,10,3),
n = c(75,76,73)
)
par(mar = c(13,5,2,3))
plot(test$y ~ test$x,type="b", ann=F)
for (i in 1:nrow(test))
{
X=grconvertX(i , from="user", to="ndc")
grid.text(x=X, y =0.2, label=paste("GRID.text at\nuser.x=", i, "\n", "ndc.x=", (signif( X, 5)) ) )
grid.lines(x=c(X, X), y = c(0.28, 0.33) )
}
#add some code to save as PDF ...
The code is based on the solution from one of my previous posts: Mixing X and Y coordinate systems . You can see how x coordinates from the original plot were converted to ndc. The advantage of this approach is that I can use device coordinates for Y.
I assumed I could easily do the same in ggplot2 (and possibly in lattice).
library(ggplot2)
graphics.off() # close graphics windows
qplot(x=x, y=y, data=test)+geom_line()+ opts(plot.margin = unit(c(1,3,8,1), "lines"))
for (i in 1:nrow(test))
{
X=grconvertX(i , from="user", to="ndc")
grid.text(x=X, y =0.2, label=paste("GRID.text at\nuser.x=", i, "\n", "ndc.x=", (signif( X, 5)) ) )
grid.lines(x=c(X, X), y = c(0.28, 0.33) )
}
#add some code to save as PDF...
However, it does not work correctly. The coordinates seem to be a bit off. The vertical lines and text don't correspond to the tick labels on the plot. Can anybody tell me how to fix it? Thanks a lot in advance.
The grconvertX and grconvertY functions work with base graphics while ggplot2 uses grid graphics. In general the 2 different graphics engines don't play nicely together (though you have demonstrated using gridBase to help). Your first example works because you started with a base graphic so the user coordinate system exists with the base graph and grconvertX converts from it. In the second case the user coordinate system was never set in the base graphics, so it looks like it might use the default coordinates of 0,1 which are similar but not identical to the top viewport coordinates so you get something similar but not exactly correct (I am actually surprised that you did not get an error or warning
Generally for grid graphics the equivalent for converting between coordinates is to just create a new viewport with the coordinate system of interest (or push/pop to an existing viewport with the correct coordinate system), then add your annotations in that viewport.
Here is an example that creates your plot, then moves down to the viewport containing the main plot, creates a new viewport with the same dimensions but with clipping turned off, the x scale is based on the data and the y scale is 0,1, then adds some text accordingly:
library(ggplot2)
library(grid)
test= data.frame( x = c(1,2,3), y = c(12,10,3), n = c(75,76,73) )
qplot(x=x, y=y, data=test)+geom_line()+ opts(plot.margin = unit(c(1,3,8,1), "lines"))
current.vpTree()
downViewport('panel-3-4')
pushViewport(dataViewport( test$x, clip='off',yscale=c(0,1)))
for (i in 1:nrow(test)) {
grid.text(x=i, y = -0.2, default.units='native',
label=paste("GRID.text at\nuser.x=", i, "\n" ) )
grid.lines(x=c(i, i), y = c(-0.1, 0), default.units='native' )
}
One of the tricky things here is that ggplot2 does not set the viewport scales to match the data being plotted, but does the conversions itself. In this case setting the scale based on the x data worked, but if ggplot2 does something fancier then this might not work. What we would need is some way to get the back tranformed coordinates from ggplot2 to use in the call to grid.text.
I am drawing dotplot() using lattice or Dotplot() using Hmisc. When I use default parameters, I can plot error bars without small vertical endings
--o--
but I would like to get
|--o--|
I know I can get
|--o--|
when I use centipede.plot() from plotrix or segplot() from latticeExtra, but those solutions don't give me such nice conditioning options as Dotplot(). I was trying to play with par.settings of plot.line, which works well for changing error bar line color, width, etc., but so far I've been unsuccessful in adding the vertical endings:
require(Hmisc)
mean = c(1:5)
lo = mean-0.2
up = mean+0.2
d = data.frame (name = c("a","b","c","d","e"), mean, lo, up)
Dotplot(name ~ Cbind(mean,lo,up),data=d,ylab="",xlab="",col=1,cex=1,
par.settings = list(plot.line=list(col=1),
layout.heights=list(bottom.padding=20,top.padding=20)))
Please, don't give me solutions that use ggplot2...
I've had this same need in the past, with barchart() instead of with Dotplot().
My solution then was to create a customized panel function that: (1) first executes the original panel function ; and (2) then uses panel.arrows() to add the error bar (using a two-headed arrow, in which the edges of the head form a 90 degree angle with the shaft).
Here's what that might look like with Dotplot():
# Create the customized panel function
mypanel.Dotplot <- function(x, y, ...) {
panel.Dotplot(x,y,...)
tips <- attr(x, "other")
panel.arrows(x0 = tips[,1], y0 = y,
x1 = tips[,2], y1 = y,
length = 0.15, unit = "native",
angle = 90, code = 3)
}
# Use almost the same call as before, replacing the default panel function
# with your customized function.
Dotplot(name ~ Cbind(mean,lo,up),data=d,ylab="",xlab="",col=1,cex=1,
panel = mypanel.Dotplot,
par.settings = list(plot.line=list(col=1),
layout.heights=list(bottom.padding=20,top.padding=20)))