gca is limited to a single scope - scope

S/w : Octave v7.1.0
Background:
I picked the code example given at 15.2.4 docs.octave/.../Multiple-Plots and modified it to use multiple figures
As can be seen, this code separates the implementation in 2 different for loops - say 2 different blocks (I don't know about octave, 'am just using from terminology of C lang)
The above separation causes issue when using legend() in 2nd block (error thrown attached below)
So, it means that some variable which legend() uses to assign legends is not available in 2nd block i.e. is lost to the previous block, while other variables explicitly initiated like hax etc are not lost
Questions:
Is my diagnosis regarding the issue (scopes and lost of value) right?
What implicit variables/values are required by legend()? 15.2.3 docs.octave/.../Plot-Annotations says gca but how to pass that across scopes? Manual of legend for reference: * 15.3.3.4 docs.octave/.../Legend-Properties
Why does the example on the site separates the logic in 2 blocks when it's prone to such bugs? Is there any upside to this? And which one would be the recommendation in general case scenario?
I was not able to find this error by debugging or normal thinking. The point regarding scope didn't come even after so long as all the other variables like hax, or figure are still working. So, what are the ways with which I could have actually found this error mechanically? Like in C the gcc's -W flags shows pretty good technical descriptions of the issue.
Materials:
The error shown with buggy code:
error: legend: no valid object to label
error: called from
legend>parse_opts at line 770 column 7
legend at line 210 column 8
isolated_code at line 20 column 9
The buggy code:
/!\ Beware, don't blindly copy paste, as it may result in lost of workspace figures/variables due to close all; clear all; commands in first line
close all; clear all;
legend_n = char ( ["1"; "2"; "3"] );
x = 1:3;
y = rand (6,3);
for i_loop = 1:2
figure();
hax(i_loop) = newplot ();
hold (hax(i_loop), "on");
endfor
for i_loop = 1:2
for i_plot = 1:3
iy = (i_loop - 1)*3 + i_plot;
plot (hax(i_loop), x,y(iy,:) ...
# , sprintf( "x-;%s;" , legend_n(i_plot, :) )
, "x-", "displayname", legend_n(i_plot,:)
);
legend ();
# legend (hax(i_loop), i_plot);
endfor
endfor
Working code:
Merge the 2 blocks of for together, i.e. remove their separating end for \n for.
diff -u of buggy and working code gives:
## -9,8 +9,6 ##
figure();
hax(i_loop) = newplot ();
hold (hax(i_loop), "on");
-endfor
-for i_loop = 1:2
for i_plot = 1:3
iy = (i_loop - 1)*3 + i_plot;
plot (hax(i_loop), x,y(iy,:) ...
Full copy of working code as requested in comments:
close all; clear all;
legend_n = char ( ["1"; "2"; "3"] );
x = 1:3;
y = rand (6,3);
for i_loop = 1:2
figure();
hax(i_loop) = newplot ();
hold (hax(i_loop), "on");
for i_plot = 1:3
iy = (i_loop - 1)*3 + i_plot;
plot (hax(i_loop), x,y(iy,:) ...
# , sprintf( "x-;%s;" , legend_n(i_plot, :) )
, "x-", "displayname", legend_n(i_plot,:)
);
legend ();
# legend (hax(i_loop), i_plot);
endfor
endfor

legend, plot and other axis related functions do their function in current axis of current figure unless you tell them otherwise. When you create figure windows before plotting, the last axis in your last figure is your current axis. So if you want to do plotting or show legend in any of the previous figure/axis then you should use the relevant figure/axis handle to do so.
You should call legend when you are done plotting on an axis unless you are making an animation in which you want to see the legend updating as the plot entries change. Otherwise you will unnecessarily be calling legend multiple times.
Your first code after making the above mentioned fixes:
legend_n = ["1"; "2"; "3"];
%No need to call char in above line as you already have a char array
x = 1:3;
y = rand (6,3);
for i_loop = 1:2
figure();
hax(i_loop) = newplot ();
hold (hax(i_loop), "on");
endfor
for i_loop = 1:2
for i_plot = 1:3
iy = (i_loop - 1)*3 + i_plot;
plot (hax(i_loop), x,y(iy,:) ...
, "x-", "displayname", legend_n(i_plot,:));
endfor
legend(hax(i_loop)); %calling legend with the relevant axis handle
endfor
In your second code, legend is being called in the current axis/figure and hence you get no error. But you should call it after the end of your inner for loop.

Related

Stacking curves on plot/graph/canvas iteratively

With a set of data files. I would like to performs series of operations on each file (such as fitting) and stack the resulting curves continiously along with my analysis (to see how each curves fit on the bigger picture). I wrote the following code snippet
reset
PATH = 'XRP_'
nmin = 1
nmax = 20
f(x) = log10(x); h(x) = a*x + b
name(i) = sprintf(PATH.'%04d/data_main_ddnls_twod_mlce.dat', i)
set xrange [0:7]
start = 0
set fit
do for [i=nmin:nmax]{
fit [4:] h(x) name(i) using (f($1)):(f($4)) via a, b
if (start==0){
plot name(i) using (f($1)):(f($4)) w l title sprintf("%04d", i)
} else {
replot name(i) using (f($1)):(f($4)) w l title sprintf("%04d", i)
}
start = start + 1
pause -1
}
# Add the slope
replot (1./5.)*x + 0.5 lc 'black' lw 3 dt 2
unset fit
# pause -1
Instead of stacking all the previous curves + the current one, it plots only the current curve i-times (see loop of code). For instance, after 10 iterations it plots only the 10th datafile, 10 times (see legends on picture)
How can I fix this?
The reason your plot behaves the way it does, and example (1) from theozh does also, is that "replot f(x)" acts by tacking ", f(x)" onto the end of the previous plot command. By putting it in a loop you are basically creating the successive commands
plot f(x,i)
plot f(x,i), f(x,i)
plot f(x,i), f(x,i), f(x,i)
...
Yes the value of i might change each time, but nevertheless each plot command produces multiple copies of the same thing.
Alternative solution: I don't normally recommend multiplot mode for creating a single output, but in this case it may be the best option.
# force identical margins even if the range changes
set margins screen 0.1, screen 0.9, screen 0.1, screen 0.9
# ... same prelimary stuff as shown in the question
# revised loop using multiplot rather than replot
set multiplot
do for [i=nmin:nmax]{
fit [4:] h(x) name(i) using (f($1)):(f($4)) via a, b
plot name(i) using (f($1)):(f($4)) w l \
title sprintf("%04d", i) at screen 0.9, screen 1.0 - 0.02*i
unset tics
}
unset multiplot
Note that you cannot use auto-generated title placement because each of the multiplot iterations will put the title in the same place. So instead we use the form "title foo at ". Similarly it is better to turn off tic generation after the first pass so that you don't redraw the tics and labels each time through the loop.
Indeed, a strange behaviour which I also would not have expected. See the minimal examples below.
Version 1: basically your attempt. Not the expected result. I also don't know
why.
Version 2: the expected result. Basically the same but not in a loop.
Version 3: the expected result, although in a loop but using eval.
Not very satisfying but at least some solution. Hopefully, others will have better solutions or explanations.
### plotting in a loop
reset session
set colorsequence classic
# Version 1
set title "Version 1"
do for [i=1:5] {
if (i==1) { plot x**i }
else { replot x**i noautoscale }
}
pause -1
# Version 2
set title "Version 2"
plot x**1
replot x**2 noautoscale
replot x**3 noautoscale
replot x**4 noautoscale
replot x**5 noautoscale
pause -1
# Version 3
set title "Version 3"
do for [i=1:5] {
if (i==1) { cmd = sprintf("plot x**%d",i) }
else { cmd = sprintf("replot x**%d noautoscale",i) }
eval cmd
}
### end of code

How to plot lines parallel to the x-axis with a certain offset given by data in an input file with gnuplot

I calculated the eigenvalues of the Hamiltonian for the 1D-hydrogen atom in atomic units with the Fourier-Grid-Hamiltonian method in a nice little Fortran program.
All the eigenvalues found between -1 and 0 (the bound states) are saved into a file line by line like this:
-0.50016671392950229
-0.18026105614262633
-0.11485673263086937
-4.7309305955423042E-002
-4.7077108902158216E-002
As the number of found eigenvalues differs depends on the stepsize my program uses, the number of entries in the file can vary (in theory, there are infinite ones).
I now want to plot the values from the file as a line parallel to the x-axis with the offset given by the values read from file.
I also want to be able to plot the data only up to a certain line number, as the values get really close to each other the further you come to zero and they cannot be distinguished by eye anymore.
(Here e.g. it would make sence to plot the first four entries, the fifth is already too close to the previous one)
I know that one can plot lines parallel to the x axis with the command plot *offset* but I don't know how to tell gnuplot to use the data from the file. So far I had to manually plot the values.
As a second step I would like to plot the data only in a certain x range, more concrete between the points of intersection with the harmonic potential used for the numeric solution V(x) = -1/(1+abs(x))
The result should look like this:
scheme of the desired plot (lookalike)
The closest I got to, was with
plot -1/(1+abs(x)),-0.5 title 'E0',-0.18 title 'E1', -0.11 title 'E2'
which got me the following result:
my plot
Hope you guys can help me, and I'm really curios whether gnuplot actually can do the second step I described!
As for the first part of your question, you can for example use the xerrorbars plotting style as:
set terminal pngcairo
set output 'fig.png'
unset key
set xr [-1:1]
set yr [-1:0]
unset bars
plot '-' u (0):($1<-0.1?$1:1/0):(1) w xerrorbars pt 0 lc rgb 'red'
-0.50016671392950229
-0.18026105614262633
-0.11485673263086937
-4.7309305955423042E-002
-4.7077108902158216E-002
e
The idea here is to:
interpret the energies E as points with coordinates (0,E) and assign to each of them an x-errorbar of width 1 (via the third part of the specification (0):($1<-0.1?$1:1/0):(1))
"simulate" the horizontal lines with x-errorbars. To this end, unset bars and pt 0 ensure that Gnuplot displays just plain lines.
consider only energies E<-0.1, the expressions $1<-0.1?$1:1/0 evaluates otherwise to an undefined value 1/0 which has the consequence that nothing is plotted for such E.
plot '-' with explicit values can be of course replaced with, e.g., plot 'your_file.dat'
This produces:
For the second part, it mostly depends how complicated is your function V(x). In the particular case of V(x)=-1/(1+|x|), one could infer directly that it's symmetric around x=0 and calculate the turning points explicitly, e.g.,
set terminal pngcairo
set output 'fig.png'
fName = 'test.dat'
unset key
set xr [-10:10]
set yr [-1:0]
unset bars
f(x) = -1 / (1+abs(x))
g(y) = (-1/y - 1)
plot \
f(x) w l lc rgb 'black', \
fName u (0):($1<-0.1?$1:1/0):(g($1)) w xerrorbars pt 0 lc rgb 'red', \
fName u (0):($1<-0.1?$1:1/0):(sprintf("E%d", $0)) w labels offset 0, char 0.75
which yields
The idea is basically the same as before, just the width of the errorbar now depends on the y-coordinate (the energy). Also, the labels style is used in order to produce explicit labels.
Another approach may be to get data from "energy.dat" (as given in the question) with system and cat commands (so assuming a Un*x-like system...) and select V(x) and E at each x via max:
set key bottom right
set yr [-1:0.2]
set samples 1000
Edat = system( "cat energy.dat" )
max(a,b) = ( a > b ) ? a : b
V(x) = -1/(1+abs(x))
plot for [ E in Edat ] \
max(V(x),real(E)) title sprintf("E = %8.6f", real(E)) lw 2, \
V(x) title "V(x) = -1/(1+|x|)" lc rgb "red" lw 2
If we change the potential to V(x) = -abs(cos(x)), the plot looks pretty funny (and the energy levels are of course not correct!)
More details about the script:
max is not a built-in function in Gnuplot, but a user-defined function having two formal arguments. So for example, we may define it as
mymax( p, q ) = ( p > q ) ? p : q
with any other names (and use mymax in the plot command). Next, the ? symbol is a ternary operator that gives a short-hand notation for an if...else construct. In a pseudo-code, it works as
function max( a, b ) {
if ( a > b ) then
return a
else
return b
end
}
This way, max(V(x),real(E)) selects the greater value between V(x) and real(E) for any given x and E.
Next, Edat = system( "cat energy.dat" ) tells Gnuplot to run the shell command "cat energy.dat" and assign the output to a new variable Edat. In the above case, Edat becomes a string that contains a sequence of energy values read in from "energy.dat". You can check the contents of Edat by print( Edat ). For example, it may be something like
Edat = "-0.11 -0.22 ... -0.5002"
plot for [ E in Edat ] ... loops over words contained in a string Edat. In the above case, E takes a string "-0.11", "-0.22", ..., "-0.5002" one-by-one. real(E) converts this string to a floating-point value. It is used to pass E (a character string) to any mathematical function.
The basic idea is to draw a truncated potential above E, max(V(x),E), for each value of E. (You can check the shape of such potential by plot max(V(x),-0.5), for example). After plotting such curves, we redraw the potential V(x) to make it appear as a single potential curve with a different color.
set samples 1000 increases the resolution of the plot with 1000 points per curve. 1000 is arbitrary, but this seems to be sufficient to make the figure pretty smooth.

Plotting Average curve for points in gnuplot

[Current]
I am importing a text file in which the first column has simulation time (0~150) the second column has the delay (0.01~0.02).
1.000000 0.010007
1.000000 0.010010
2.000000 0.010013
2.000000 0.010016
.
.
.
149.000000 0.010045
149.000000 0.010048
150.000000 0.010052
150.000000 0.010055
which gives me the plot:
[Desired]
I need to plot an average line on it like shown in the following image with red line:
Here is a gnuplot only solution with sample data:
set table "test.data"
set samples 1000
plot rand(0)+sin(x)
unset table
You should check the gnuplot demo page for a running average. I'm going to generalize this demo in terms of dynamically building the functions. This makes it much easier to change the number of points include in the average.
This is the script:
# number of points in moving average
n = 50
# initialize the variables
do for [i=1:n] {
eval(sprintf("back%d=0", i))
}
# build shift function (back_n = back_n-1, ..., back1=x)
shift = "("
do for [i=n:2:-1] {
shift = sprintf("%sback%d = back%d, ", shift, i, i-1)
}
shift = shift."back1 = x)"
# uncomment the next line for a check
# print shift
# build sum function (back1 + ... + backn)
sum = "(back1"
do for [i=2:n] {
sum = sprintf("%s+back%d", sum, i)
}
sum = sum.")"
# uncomment the next line for a check
# print sum
# define the functions like in the gnuplot demo
# use macro expansion for turning the strings into real functions
samples(x) = $0 > (n-1) ? n : ($0+1)
avg_n(x) = (shift_n(x), #sum/samples($0))
shift_n(x) = #shift
# the final plot command looks quite simple
set terminal pngcairo
set output "moving_average.png"
plot "test.data" using 1:2 w l notitle, \
"test.data" using 1:(avg_n($2)) w l lc rgb "red" lw 3 title "avg\\_".n
This is the result:
The average lags quite a bit behind the datapoints as expected from the algorithm. Maybe 50 points are too many. Alternatively, one could think about implementing a centered moving average, but this is beyond the scope of this question.
And, I also think that you are more flexible with an external program :)
Here's some replacement code for the top answer, which makes this also work for 1000+ points and much much faster. Only works in gnuplot 5.2 and later I guess
# number of points in moving average
n = 5000
array A[n]
samples(x) = $0 > (n-1) ? n : int($0+1)
mod(x) = int(x) % n
avg_n(x) = (A[mod($0)+1]=x, (sum [i=1:samples($0)] A[i]) / samples($0))
Edit
The updated question is about a moving average.
You can do this in a limited way with gnuplot alone, according to this demo.
But in my opinion, it would be more flexible to pre-process your data using a programming language like python or ruby and add an extra column for whatever kind of moving average you require.
The original answer is preserved below:
You can use fit. It seems you want to fit to a constant function. Like this:
f(x) = c
fit f(x) 'S1_delay_120_LT100_LU15_MU5.txt' using 1:2 every 5 via c
Then you can plot them both.
plot 'S1_delay_120_LT100_LU15_MU5.txt' using 1:2 every 5, \
f(x) with lines
Note that this is technique can be used with arbitrary functions, not just constant or lineair functions.
I wanted to comment on Franky_GT, but somehow stackoverflow didn't let me.
However, Franky_GT, your answer works great!
A note for people plotting .xvg files (e.g. after doing analysis of MD simulations), if you don't add the following line:
set datafile commentschars "##&"
Franky_GT's moving average code will result in this error:
unknown type in imag()
I hope this is of use to anyone.
For gnuplot >=5.2, probably the most efficient solution is using an array like #Franky_GT's solution.
However, it uses the pseudocolumn 0 (see help pseudocolumns). In case you have some empty lines in your data $0 will be reset to 0 which eventually might mess up your average.
This solution uses an index t to count up the datalines and a second array X[] in case a centered moving average is desired. Datapoints don't have to be equidistant in x.
At the beginning there will not be enough datapoints for a centered average of N points so for the x-value it will use every second point and the other will be NaN, that's why set datafile missing NaN is necessary to plot a connected line at the beginning.
Code:
### moving average over N points
reset session
# create some test data
set print $Data
y = 0
do for [i=1:5000] {
print sprintf("%g %g", i, y=y+rand(0)*2-1)
}
set print
# average over N values
N = 250
array Avg[N]
array X[N]
MovAvg(col) = (Avg[(t-1)%N+1]=column(col), n = t<N ? t : N, t=t+1, (sum [i=1:n] Avg[i])/n)
MovAvgCenterX(col) = (X[(t-1)%N+1]=column(col), n = t<N ? t%2 ? NaN : (t+1)/2 : ((t+1)-N/2)%N+1, n==n ? X[n] : NaN) # be aware: gnuplot does integer division here
set datafile missing NaN
plot $Data u 1:2 w l ti "Data", \
t=1 '' u 1:(MovAvg(2)) w l lc rgb "red" ti sprintf("Moving average over %d",N), \
t=1 '' u (MovAvgCenterX(1)):(MovAvg(2)) w l lw 2 lc rgb "green" ti sprintf("Moving average centered over %d",N)
### end of code
Result:

How to plot data with multiple records in one row

I want to plot JCAMP-DX formatted spectrum.
It has multiple records for y axis in one row and specified increment for x axis.
Simple example: linear plot (1,1) to (12,12)
1 1 2 3
4 4 5 6
7 7 8 9
10 10 11 12
First column represents x axis and second to fourth column represent y axis with each consequent y data belonging to x incremented by one. I can plot it with command:
plot "test.gnuplot" using 1:2 linecolor "black" with dots, "test.gnuplot" using ($1+1):3 linecolor "black" with dots, "test.gnuplot" using ($1+2):4 linecolor "black" with dots
However, the spectrum is much more complicated and I would like to plot it with lines, which is not possible using above mentioned method (lines wouldn´t connect and would create ugly intersections at nonlinear regions of plot).
For now I plot just the second column (using 1:2), but that lowers the resolution.
I want to avoid using external filters (awk etc.) and editing the input file (vim etc.).
real data (skip first 35 lines -- data specification): http://webbook.nist.gov/cgi/cbook.cgi?JCAMP=C7664417&Index=1&Type=IR
You want to avoid external tools, but maybe creating a temporary file with gnuplot itself is acceptable?
I have taken the real data from webbook.nist.gov, and I have removed the comment lines and the last data line which has less y values than the other lines.
This is my suggestion:
datafile = "7664-41-7-IR.jdx2"
dx = 0.935253
col_count=6
# Build a function that will create a new datafile by converting
# single lines of the form "x y1 y2 y3 ..." into multiple
# lines of the form "x y1", "x+dx y2", "x+2*dx y3", ...
#
# We will call this function later for each input line and append
# the new data values.
all_command = "all = sprintf(\"%s"
do for [i=2:col_count] {
all_command = all_command."%f %f\n"
}
all_command = all_command."\", all"
do for [t=2:col_count] {
all_command = all_command.", column(1)+dx*(".t."-2), column(".t.")"
}
all_command = all_command.")"
# Just to check:
print all_command
# Now we call the function for each input line. The variable "all" will contain
# the "expanded" data. Note, the "plot" command is a dummy plot.
all = ""
plot datafile using 1:( #all_command, 1)
# Generate the temporary data file
set print "temp_file.dat"
print all
plot datafile w p, "temp_file.dat" w l
This a part of the output:
For counting the lines generically please check this question.

Gnuplot Function with Multiple Statements

Question
Is it possible to define functions which have multiple statements defined within?
Context
I want to automate some of the calculations involved in creating stacked plots by defining functions. In particular, I was hoping to have something like
mp_setup(bottom_margin, top_margin) = \
set tmargin 0; \
set bmargin 0; \
mp_available_height = 1.0 - top_margin - bottom_margin; \
mp_current_height = bottom_margin;
mp_plot(plot_height) = \
mp_plot_size = plot_height * mp_available_height; \
set origin 0,mp_current_height; \
set size 1,mp_plot_size; \
mp_current_height = mp_current_height + mp_plot_size;
with the intended usage being:
...
set multiplot
mp_setup(0.05, 0.05)
mp_plot(1.0/3.0)
plot ...
mp_plot(2.0/3.0)
plot ...
and this should automatically result in the plots being nicely stacked without me having to calculate the origin and size for each graph.
Problem
The way of defining the functions above doesn't work because it seems like the parsing of the function definition ends at the first occurrence of ;; but these semicolons are necessary in order to separate each statement (otherwise, we have set tmargin 0 set bmargin 0... which is invalid).
It also seems like Gnuplot doesn't support any way of grouping statement (like {...} in C/C++); or at least, I have never come across it.
Possible Solution
The only method I know to store multiple functions and evaluate them is using macros:
mp_setup = "<as above>"
mp_plot = "<as above>"
But the issue here is that macros do not allow for arguments to be passed in and instead each variable would have to be declared beforehand as follows:
...
set multiplot
top_margin = 0.05
bottom_margin = 0.05
#mp_setup
plot_height = 1.0/3.0
#mp_plot
plot ...
plot_height = 2.0/3.0
#mp_plot
plot ...
This solution, although it should work, is not quite as elegant.
Is there no other way of doing this?
No, it isn't possible to define such functions. In gnuplot user-defined functions cannot contain set, unset or other commands. Only those kind of expressions are allowed, which return numerical or string variables. Here, you can have several expressions, separated by comma:
a = 0
f(x) = (a = a + 1, a + x)
print f(1)
print f(1)
Besides your solution to use macros (#var), I prefer constructing strings inside the function and calling eval:
set_margin(s, v) = sprintf('set %smargin at screen %f;', s, v)
set_margins(l, r, b, t) = set_margin('l', l).set_margin('r', r).set_margin('b', b).set_margin('t', t)
eval(set_margins(0.1, 0.95, 0.15, 0.98))
For your specific case of a multiplot layout you can also see Removing blank gap in gnuplot multiplot.
You can do like that
mp_setup(bottom_margin, top_margin)=(tmargin=0,bmargin=0,mp_available_height=1.0 -top_margin-bottom_margin,mp_current_height=bottom_margin)
test:
print mp_setup(0.05,0.05)
==> 0.05
As you mentioned, the grouping statement in the function is not supporting yet.

Resources