Legend outside plot does not work with plotyy in Octave - plot

I am trying to create a plot in Octave (using v4.4.1 on Windows) using plotyy and putting the legend outside the plot (because the data covers all the usable space inside the graph). The following MVCE should reproduce the issue fairly well:
% Generate some random data to reproduce the issue
data = rand(1000,10);
data(:,1:8) = data(:,1:8)-0.5;
data(:,9:10) = data(:,9:10)+30;
timedate = linspace(737310,737313,size(data,1));
data_labels={'1';'2';'3';'4';'5';'6';'7';'8';'9';'10'};
% Plot the data
figure('Name','MVCE','Position',[300 200 1000 600])
[ax,h1,h2] = plotyy(timedate,data(:,1:8),timedate,data(:,9:10));
set(h2,'Visible','on');
datetick(ax(1),'x','HH:MM:SS')
datetick(ax(2),'x','HH:MM:SS')
ylim(ax(1),[-1 1])
ylim(ax(2),[20 50])
xlabel('Date & time')
ylabel(ax(1),'Something')
ylabel(ax(2),'Something else')
title('plotyy graph with legend problem')
[hl,hlo] = legend([h1;h2],data_labels,'location','eastoutside');
grid on
This the output of the code using the gnuplot graphics toolkit:
As you can see, the legend does not go outside the plot, and the second y axis is not visible (it looks like part of the plot is actually truncated).
I have tried using the qt and fltk graphics toolkits, which give issues of their own:
With qt graphics toolkit
With fltk graphics toolkit
Can anoybody suggest a fix or at least workaround? Does the same issue also happen in MATLAB or is it Octave-specific?
EDIT
Using the suggestion in Tasos' answer, I managed to almost make it work with gnuplot:
% Plot the data
figure('Name','MVCE','Position',[300 200 1000 600])
[ax,h1,h2] = plotyy(timedate,data(:,1:8),timedate,data(:,9:10));
set(h2,'Visible','on');
datetick(ax(1),'x','HH:MM:SS')
datetick(ax(2),'x','HH:MM:SS')
ylim(ax(1),[-1 1])
ylim(ax(2),[20 50])
ax1Pos = get(ax(1), 'position');
ax2Pos = get(ax(2), 'position');
ax1Pos(3) = ax1Pos(3) * 0.73;
ax2Pos(3) = ax2Pos(3) * 0.73;
set(ax(1), 'position', ax2Pos);
set(ax(2), 'position', ax2Pos);
xlabel('Date & time')
ylabel(ax(1),'Something')
ylabel(ax(2),'Something else')
title('plotyy graph with legend problem')
[hl,hlo] = legend([h1;h2],data_labels,'location','eastoutside');
pos = get(hl,'Position');
pos(1) = 0.9;
set(hl,'Position',pos)
grid on
Which produces:
Apart from the fact that the legend overlays with the second y axis label (which it doesn't on my screen, only when printing to jpg), the problem is that Octave appears to plot two legends on top of each other for some reason: one with the first set of data attached to the first set of axes, and one with the complete set of data, for both axes right on top of the first legend. This is obviously wrong, and trying to set the Visible property of hl to off deletes both legends, not just the one.

UPDATED: deals with both legend placement and OpenGL precision affecting graph.
Regarding the problem of the legend not appearing exactly in the position you want it to, you can manually adjust the position of all axes involved in a figure, to place them exactly where you want.
Regarding the problem of OpenGL being unable to deal with the precision involved when adding small numbers to a large number, plot the graph with only the small numbers involved, and then simply adjust the xticklabels to correspond to the numbers you desire.
Full code below:
% Generate some random data to reproduce the issue
data = rand(1000,10);
data(:,1:8) = data(:,1:8)-0.5;
data(:,9:10) = data(:,9:10)+30;
t_offset = 737310;
timedate = linspace(0,3,size(data,1));
data_labels={'1';'2';'3';'4';'5';'6';'7';'8';'9';'10'};
% Plot the data
figure('Name','MVCE','Position',[300 200 1000 600])
[ax,h1,h2] = plotyy(timedate,data(:,1:8),timedate,data(:,9:10));
set(h2,'Visible','on');
ylim(ax(1),[-1 1])
ylim(ax(2),[20 50])
ylabel(ax(1),'Something')
ylabel(ax(2),'Something else')
title('plotyy graph with legend problem')
[hl,hlo] = legend([h1;h2],data_labels,'location','eastoutside');
set(hl, 'position', get(hl, 'position') .* [0.975, 1, 0.975, 1] )
grid on
ax1Pos = get(ax(1), 'position'); ax2Pos = get(ax(2), 'position');
ax1Pos(3) = ax1Pos(3) * 0.95; ax2Pos(3) = ax2Pos(3) * 0.95;
set(ax(1), 'position', ax2Pos); set(ax(2), 'position', ax2Pos);
XTicks = get(ax(1), 'xtick');
set(ax(1), 'xticklabel', datestr(XTicks + t_offset, 'HH:MM:SS'))
xlabel('Date & time')
set(ax(2), 'xtick', []);
Output:

Related

Julia Plotting: delete and modify existing lines

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.

spplot/lattice: objects not drawn/overdrawn

I have a grid and I want to produce a map out of this grid with some map elements (scale, north arrow, etc). I have no problem drawing the grid and the coloring I need, but the additional map elements won't show on the map. I tried putting first=TRUE to the sp.layout argument according to the sp manual, but still no success.
I reproduced the issue with the integrated meuse dataset, so you may just copy&paste that code. I use those package versions: lattice_0.20-33 and sp_1.2-0
library(sp)
library(lattice) # required for trellis.par.set():
trellis.par.set(sp.theme()) # sets color ramp to bpy.colors()
alphaChannelSupported = function() {
!is.na(match(names(dev.cur()), c("pdf")))
}
data(meuse)
coordinates(meuse)=~x+y
data(meuse.riv)
library(gstat, pos = match(paste("package", "sp", sep=":"), search()) + 1)
data(meuse.grid)
coordinates(meuse.grid) = ~x+y
gridded(meuse.grid) = TRUE
v.uk = variogram(log(zinc)~sqrt(dist), meuse)
uk.model = fit.variogram(v.uk, vgm(1, "Exp", 300, 1))
meuse[["ff"]] = factor(meuse[["ffreq"]])
meuse.grid[["ff"]] = factor(meuse.grid[["ffreq"]])
zn.uk = krige(log(zinc)~sqrt(dist), meuse, meuse.grid, model = uk.model)
zn.uk[["se"]] = sqrt(zn.uk[["var1.var"]])
meuse.sr = SpatialPolygons(list(Polygons(list(Polygon(meuse.riv)),"meuse.riv")))
rv = list("sp.polygons", meuse.sr, fill = "lightblue")
sampling = list("sp.points", meuse.riv, color = "black")
scale = list("SpatialPolygonsRescale", layout.scale.bar(),
offset = c(180500,329800), scale = 500, fill=c("transparent","black"), which = 4)
text1 = list("sp.text", c(180500,329900), "0", cex = .5, which = 4)
text2 = list("sp.text", c(181000,329900), "500 m", cex = .5, which = 4)
arrow = list("SpatialPolygonsRescale", layout.north.arrow(),
offset = c(181300,329800),
scale = 400, which = 4)
library(RColorBrewer)
library(lattice)
trellis.par.set(sp.theme())
precip.pal <- colorRampPalette(brewer.pal(7, name="Blues"))
spplot(zn.uk, "var1.pred",
sp.layout = list(rv, sampling, scale, text1, text2),
main = "log(zinc); universal kriging standard errors",
col.regions=precip.pal,
contour=TRUE,
col='black',
pretty=TRUE,
scales=list(draw = TRUE),
labels=TRUE)
And that's how it looks...all naked:
So my questions:
Where is the scale bar, north arrow, etc hiding? Did I miss something? Every example I could find on the internet looks similar to that. On my own dataset I can see the scale bar and north arrow being drawn at first, but as soon as the grid is rendered, it superimposes the additional map elements (except for the scale text, that is shown on the map - not the bar and north arrow for some reason I don't seem to comprehend).
The error message appearing on the map just shows when I try to add the sampling locations sampling = list("sp.points", meuse.riv, color = "black"). Without this entry, the map shows without error, but also without additional map elements. How can I show the sampling points on the map (e.g. in circles whose size depends on the absolute value of this sampling point)?
This bothered me for many, many hours by now and I can't find any solution to this. In Bivand et al's textbook (2013) "Applied Spatial Data Analysis with R" I could read the following entry:
The order of items in the sp.layout argument matters; in principle objects
are drawn in the order they appear. By default, when the object of spplot has
points or lines, sp.layout items are drawn before the points to allow grids
and polygons drawn as a background. For grids and polygons, sp.layout
items are drawn afterwards (so the item will not be overdrawn by the grid
and/or polygon). For grids, adding a list element first = TRUE ensures that
the item is drawn before the grid is drawn (e.g. when filled polygons are added). Transparency may help when combining layers; it is available for the
PDF device and several other devices.
Function sp.theme returns a lattice theme that can be useful for plots
made by spplot; use trellis.par.set(sp.theme()) after a device is opened
or changed to make this effective.
However, also with this additional information I wasn't able to solve this problem. Glad for any hint!
The elements you miss are being drawn in panel four, which does not exist, so are not being drawn. Try removing the which = 4.
meuse.riv in your example is a matrix, which causes the error message, but should be a SpatialPoints object, so create sampling by:
sampling = list("sp.points", SpatialPoints(meuse.riv), color = "black")
When working from examples, my advice is to choose examples as close as possible to what you need, and only change one thing at a time.

Octave hist plot number format

I want to plot data with octaves hist() function. Unfortunately, as the numbers of the x-axis are quite large, they are displayed in scientific format, for example like 2e+007.
As this is a bit hard to read, I'd like them to be dislayed only as exponentials to the base 10, without the product or the e. So just like 10^5 for example. How can I achieve this?
When I plot data with the loglog() function, it uses the scale as I need it by default, but not for the hist().
EDIT:
To be a bit more preceise, I add some code and a picture of my plot.
NUM_SAMPLES = 10000;
% Open file.
input = fopen(filename);
x = [];
for i=[1:NUM_SAMPLES]
line = fgetl(input);
data = strsplit(line, ';');
x(end + 1) = str2num(data{1,2})/(1000);
endfor
% Close file.
fclose(input);
% Plot histogram.
figure('Position',[0,0,700,500]);
hist(x, 500);
So I just read some big numbers from my file and want to plot it with hist. Because the numbers are big, the automatically get displayed in loc scale and scientific format. Here you can see an example plot:
Unfortunately set(gca(), 'xscale', 'log'); doesn't change anything, so I think this plot is also consider log scale, but just a bad number format.
You can set the xticklabel or yticklabel manually, for example:
x = [1e7, 1e8, 1e9];
ax = gca();
set(ax, 'xticklabel', {'0','2e8','4e8','6e8','8e8','10e8'});
hist(x);
Alternatively, you can set the x axis to be in log scale:
set(ax, 'xscale', 'log');

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)

Using grconvertX/grconvertY in ggplot2

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.

Resources