Grouped bar plot with multiple labels in x-axis - bar-chart

I am trying to replicate something close to the following graph in gnuplot as I need to use it on a latex paper. I have tried a lot but I cannot make the two-line labels at the bottom. Could you please guide me? Also, how is it possible to have the % character as part of a label in the x-axis? Latex complains about it.
The data are in the following format (example). Each different color corresponds to different method. Blue is method 1 (m1), orange is method 2 (m2), and brown is method 3 (m3)
#% system1-m1 system1-m2 system1-m3 system2-m1 ...
0.5% 16 8 15 6
1% 15 17 16 8
2% 12 10 20 15
Thanks
Edit
My code so far is as follows:
set rmargin 0
set key outside tmargin center top horizontal width 3
set border
set grid
set boxwidth 0.8
set style fill solid 1.00
set xtics nomirror rotate by 0
set format y '%1.f'
set yrange [0 to 22]
set ylabel 'Gain (\%)'
set ytics 0, 5
set style data histograms
set label 1 at -0.3, -4 '|---------System 1------------|'
set label 2 at 2.7, -4 '|---------System 2------------|'
plot "./data/metrics.dat" using 2:xtic(1) title 'Method 1' ,\
"" using 3 title 'Method 2', \
"" using 4 title 'Method 3',
And I have modified the .dat file as
0.5 16 8 15
1.0 15 17 16
2.0 12 10 20
0.5 13 6 4
1.0 11 13 13
2.0 14 12 14
because I cannot make it print the % character. The output graph is
As you can see it is not scalable. I have to put labels by hand (trial and error) and also the labels below the x-axis do not contain the % character.

We've been close: set format x '%.1f\%%'. The following works for me with cairolatex terminal (check help cairolatex).
Code:
### percent sign for tic label in TeX
reset session
set term cairolatex
set output 'SO70029830.tex'
set title 'Some \TeX\ or \LaTeX\ title: $a^2 + b^2 = c^2$'
set format x '%.1f\%%'
plot x
set output
### end of code
Result: (screenshot)
Addition:
Sorry, I forgot the second part of your question: the labels.
Furthermore, in your graph you are using xtic(1) as tic labels, i.e. text format, so the command set format x '%.1f\%%' from my answer above will not help here. One possible solution would be to create and use your special TeX label like this:
myTic(col) = sprintf('%.1f\%%',column(col))
plot $Data using 2:xtic(myTic(1))
For the labels, I would use arrows and labels. Each histogram is placed at integer numbers starting from 0. So, the arrows have to go from x-values -0.5 to 2.5 and from 2.5 to 5.5. The labels are placed at x-value 1 and 4. There is certainly room for improvements.
Code:
### tic labels with % for TeX and lines/labels
reset session
set term cairolatex
set output 'SO70029830.tex'
$Data <<EOD
0.5 16 8 15
1.0 15 17 16
2.0 12 10 20
0.5 13 6 4
1.0 11 13 13
2.0 14 12 14
EOD
set rmargin 0
set key outside center top horizontal width 3
set border
set grid
set boxwidth 0.8
set style fill solid 1.00
set xtics nomirror rotate by 0
set format y '%1.f'
set yrange [0 to 22]
set ylabel 'Gain (\%)'
set ytics 0, 5
set style data histograms
set bmargin 4
set arrow 1 from -0.5, screen 0.05 to 2.5, screen 0.05 heads size 0.05,90
set label 1 at 1, screen 0.05 'System 1' center offset 0,-0.7
set arrow 2 from 2.5, screen 0.05 to 5.5, screen 0.05 heads size 0.05,90
set label 2 at 4, screen 0.05 'System 2' center offset 0,-0.7
myTic(col) = sprintf('%.1f\%%',column(col))
plot $Data using 2:xtic(myTic(1)) title 'Method 1' ,\
"" using 3 title 'Method 2', \
"" using 4 title 'Method 3',
set output
### enf of code
Result: (screenshot from LaTeX document)

As an alternative to the answer of #theozh there is already a build-in function called newhistogram that directly allows to place labels below the x-axis.
While working on an an answer that involves newhistogram I discovered a bug with horizontal key layout, which is now fixed thanks to Ethan. So, with the newest development version of gnuplot at hand I am able to offer a solution that allows for more finetuning like the ability to change the inter-group spacing.
set terminal cairolatex standalone colour header '\usepackage{siunitx}' size 25cm, 7cm
# generate some random data in your format
N = 7
set print $MYDATA
do for [i=1:N] {
print sprintf('0.5 %f %f %f', rand(0)*20, rand(0)*20, rand(0)*20)
print sprintf('1.0 %f %f %f', rand(0)*20, rand(0)*20, rand(0)*20)
print sprintf("2.0 %f %f %f", rand(0)*20, rand(0)*20, rand(0)*20)
}
unset print
# define the look
set style data histograms
set style fill solid 1.00
set boxwidth 0.8
set key horizontal outside t c width 1
set xr [-1:27]
set xtics nomirror
set ytics out 5 nomirror
set grid y # I don't think vertical grid lines are needed here
set ylabel 'Gain/\%'
set rmargin 0.01
set bmargin 3
As for the tic marks, I adapted #theozh's answer a bit – since you are using LaTeX already, you might as well parse the numbers through siunitx, which will ensure correct spacing between numbers and the unit:
myTic(col) = sprintf('\SI{%.1f}{\%}',column(col))
The vertical separation marks like in the screenshot you provided can be created iteratively:
do for [i=1:N+1] {set arrow i from first -1+(i-1)*4, graph 0 to first -1+(i-1)*4, screen 0 lw 2 nohead}
Now for the actual plot command:
plot newhistogram "System 1" offset 0,-0.5 lt 1, for [i=1:3] $MYDATA using (column(i+1)):xtic(myTic(1)) every ::0::2 title sprintf('Method %.0f',i), \
newhistogram "System 2" offset 0,-0.5 lt 1 at 4, for [i=1:3] $MYDATA using (column(i+1)):xtic(myTic(1)) every ::3::5 not, \
newhistogram "System 3" offset 0,-0.5 lt 1 at 8, for [i=1:3] $MYDATA using (column(i+1)):xtic(myTic(1)) every ::6::8 not, \
newhistogram "System 4" offset 0,-0.5 lt 1 at 12, for [i=1:3] $MYDATA using (column(i+1)):xtic(myTic(1)) every ::9::11 not, \
newhistogram "System 5" offset 0,-0.5 lt 1 at 16, for [i=1:3] $MYDATA using (column(i+1)):xtic(myTic(1)) every ::12::14 not, \
newhistogram "System 6" offset 0,-0.5 lt 1 at 20, for [i=1:3] $MYDATA using (column(i+1)):xtic(myTic(1)) every ::15::17 not, \
newhistogram "System 7" offset 0,-0.5 lt 1 at 24, for [i=1:3] $MYDATA using (column(i+1)):xtic(myTic(1)) every ::18::20 not
That looks very nasty, what's going on here?
newhistogram creates a new group of histogram boxes, its first argument is a string that is put below the x axis. It is also told to reset the linetype counter to 1.
Then the three columns of the data are plotted iteratively, but not all lines at once, but only the first three lines, with corresponding key entries.
Then another newhistogram is created and it is told to start at the x value 4 (which would be the default anyway). Now the next three lines are plotted, and so.
Now, every time newhistogram is called an empty line is added to key, hence making trouble with the key placement. Therefore the new keyword introduced by Ethan is
set style histogram nokeyseparators
which will disable this behaviour.
As you see, the spaces between the groups are larger than inside. You might want to change the numbers in newhistogram at ... and adjust the calculation of vertical line positions accordingly.
The plot command is of course highly repetitive, and it would be nice to make it an iterative call. Unfortunately, iterations that span multiple objects are not possible within a plot call. However, it is possible to iteratively put the plot command string together (excessively using string concatenation .) and then plot it.
A = 'newhistogram "System '
B = '" offset 0,-0.5 lt 1'
C = 'for [i=1:3] $MYDATA using (column(i+1)):xtic(myTic(1)) every ::'
myplotstring = A.'1'.B.', '.C."0::2 title sprintf('Method %.0f',i),"
do for [i=2:N] {myplotstring = myplotstring.A.i.B.'at '.(4*(i-1)).', '.C.(3*i-3).'::'.(3*i-1).' not, '}
plot #myplotstring

Related

Gnuplot timecolumn with few measurements. One data with "day overflow"

I did a few measurements for discharging batteries.
Now i wanted to plot them with Gnuplot. The problem is, that one of the measurements startet very late, that I have an "overflow" from my 24hours-clock.
Here an example of my problem-.csv-measurement data:
Time;Voltage;Current;Charge;Power;Energy;Temperature
...
23:59:54;3.2387;0.6989;0.039;2.264;0.127;22.0
23:59:55;3.2387;0.6989;0.039;2.264;0.128;22.0
23:59:56;3.2387;0.6989;0.039;2.264;0.129;22.0
23:59:57;3.2387;0.6989;0.04;2.264;0.129;22.0
23:59:58;3.2387;0.6992;0.04;2.264;0.13;22.0
23:59:59;3.2386;0.6989;0.04;2.263;0.13;22.0
00:00:00;3.2386;0.6992;0.04;2.264;0.131;22.0
00:00:01;3.2386;0.6989;0.04;2.263;0.132;22.0
00:00:02;3.2386;0.6992;0.041;2.264;0.132;22.0
00:00:04;3.2386;0.6992;0.041;2.264;0.133;22.5
00:00:05;3.2386;0.6989;0.041;2.263;0.134;22.5
00:00:06;3.2386;0.6989;0.041;2.263;0.134;22.5
00:00:07;3.2386;0.6989;0.041;2.263;0.135;22.0
the other datas dont measured between two days.
With the following Gnuplot-code:
set grid
set title 'Entladungen der Batterie über Zeit'
set tics nomirror
set title font ",12"
set ylabel 'U/V' font ",12"
set key box font ",12"
set xtics time
set xlabel 'time' font ",12"
set border 11
set border lw 2
set xtics font ",8"
set term wxt size 1200, 460
myFmt = "%H:%M:%S"
set datafile separator ";"
set format x "%tH:%M:%S" #timedate
set yrange[2.6:3.7]
plot 'Entadung_4,2A_Temp_22,5C°.csv' u (t=timecolumn(1,myFmt), $0==0?t01=t:0, t-t01):2 every ::2::1061 lt 7 lc "blue" with lines ti "Entladung bei 3C", \
'Entadung_2,8A_Temp_22,5C°.csv' u (t=timecolumn(1,myFmt), $0==0?t02=t:0, t-t02):2 every ::2::1604 lt 7 lc "web-green" with lines ti "Entladung bei 2C", \
'Entadung_1,4A_Temp_22,0C°.csv' u (t=timecolumn(1,myFmt), $0==0?t03=t:0, t-t03):2 every ::2::3267 lt 7 lc "red" with lines ti "Entladung bei 1C",\
'Entadung_0,7A_Temp_22,0C°.csv' u (t=timecolumn(1,myFmt), $0==0?t04=t:0, t-t04):2 every ::2::6696 lt 7 lc "yellow" with lines ti "Entladung bei 0,5C", \
'Entadung_0,28A_Temp_22,0C°.csv' u (t=timecolumn(1,myFmt), $0==0?t05=t:0, t-t05):2 every ::2::16977 lt 7 lc "black" with lines ti "Entladung bei 0,2C",\
'Entadung_0,14A_Temp_22,5C°.csv' u (t=timecolumn(1,myFmt), $0==0?t06=t:0, t-t06):2 every ::2::33731 lt 7 lc "violet" with lines ti "Entladung bei 0,1C"
I got this output:
The plot takes the 23 hours and subtract ist from every timestep.
So i get negative time...
Anybody know, how i can use an offset just for this one data, that the others stay untouched?
I was expecting, that the yellow graph lays between the red and the black graph.
Unfortunately, you don't record the date as well. If you had you wouldn't have this problem.
But you can account for midnight crossings with a little addition.
assign t1=NaN and td=0 before the plot command
within the plot command (loop), assign t0=t1 and directly afterwards t1=timecolumn(...). So, t0 always holds the previous value and t1 the current value
every time t1<t0 (i.e. crossing midnight) add one day to td, i.e. td=td+secsPerDay (number of seconds per day) and add td to your plotting value.
furthermore, if you use %tH as x-timeformat, hours will not wrap at 24 hours, in case your measurement runs for longer than 24 hours (check help time_specifiers).
Script:
### account for midnight crossings
reset session
$Data <<EOD
23:59:00 3.7
01:59:00 3.6
12:00:00 3.5
23:00:00 3.4
02:30:00 3.3
13:00:00 3.2
23:00:00 3.1
03:00:00 3.0
14:00:00 2.9
EOD
myFmt = "%H:%M:%S"
secsPerDay = 3600*24
set xlabel "hours:min"
set format x "%tH:%M" timedate
set multiplot layout 2,1
plot $Data u (t=timecolumn(1,myFmt), $0==0? t01=t:0, t-t01):2 w lp pt 7 lc "red" ti "as is"
plot t1=(td=0,NaN) $Data u (t0=t1,t1=timecolumn(1,myFmt), $0==0? t01=t1:0, \
t1<t0?td=td+secsPerDay:0, t1-t01+td):2 w lp pt 7 lc "blue" ti "add day offset"
unset multiplot
### end of script
Result:

How can I use TCL linear algebra package for setting the elemnt of a matrix

I am trying to use the ::math::linearalgebra:: package to do some simnple eigenvalue calculation for testing. The following code works and produces the desired result:
package require math
package require math::linearalgebra
set Mat [::math::linearalgebra::mkMatrix 8 8 0.0]
puts "a single row is: [::math::linearalgebra::getrow $Mat 0 ] "
However when I try to chnage an element of matrix Mat I get an error:
set Mat [::math::linearalgebra::mkMatrix 8 8 0.0]
::math::linearalgebra::setelem $Mat 0 1 1.0]
puts "a single row is: [::math::linearalgebra::getrow $Mat 0 ] "
The error is:
can't read "mat": no such variable
while executing "lset mat $row $col $newvalue"
(procedure "::math::linearalgebra::setelem" line 4)
How do I modify the elements of the created matrix if not with ::setelem?
Thanks
Per the manual, you have to give the name of the matrix. Thus you should do:
set Mat [::math::linearalgebra::mkMatrix 8 8 0.0]
::math::linearalgebra::setelem Mat 0 1 1.0

Use column from CSV as a category label for plotting column chart using gnuplot

I have a CSV file looking like:
frameNo dataSeg paritySeg frameType
0 17 3 k
1 2 1 d
2 3 1 d
3 3 1 d
4 3 1 d
5 2 1 d
6 3 1 d
7 3 1 d
8 4 1 d
I'm able to plot stacked column diagram showing number of data and parity segments per frame. Looks like this:
What I'd like to add to it, however, is paint differently those columns (both data and parity) which have "k" marker in the last column. Basically, distinguish between two categories - "d" and "k".
Is that possible using gnuplot?
Here's the script I'm using:
set style histogram rowstacked;
set style data histograms;
set style fill solid;
set datafile separator "\t";
set terminal png size 2500,1500 enhanced font ",30";
set title "";
set tics font ",25";
set xlabel "Frame #" font ",25";
set ylabel "# of segments" font ",25";
set key outside;
set xrange [0:];
plot "segments.csv" using 2 t "Data", "" using 3 t "Parity";'
You could impose a custom condition on the columns being plotted and supply an invalid value (signaling to skip the particular data point) if this condition is not met:
set terminal pngcairo size 1200,600 enhanced font ",30";
set output 'test.png'
set style histogram rowstacked;
set style data histograms;
set style fill solid;
#set datafile separator "\t";
set title "";
set tics font ",25";
set xlabel "Frame #" font ",25";
set ylabel "# of segments" font ",25";
set key outside;
set xrange [0:];
fName = 'segments.csv'
plot \
fName using (strcol(4) eq 'd'?$2:1/0) t "Data d" lc rgb '#666666', \
fName using (strcol(4) eq 'd'?$3:1/0) t "Parity d" lc rgb '#ff0000', \
fName using (strcol(4) eq 'k'?$2:1/0) t "Data k" lc rgb '#000000', \
fName using (strcol(4) eq 'k'?$3:1/0) t "Parity k" lc rgb '#990000'
this would give (using the sample data in your question):

Gnuplot stats does not work as expected: max value not right

Assuming to have the following 4 datasets:
a.csv
1,1
2,3
3,5
5,6
6,9
7,9
8,10
9,12
10,13
b.csv
1,1
2,5
3,10
5,15
6,20
7,25
8,30
9,35
10,40
c.csv
1,1
2,10
3,100
5,1000
6,2000
7,5000
8,10000
9,20000
10,50000
d.csv
1,1
2,20
3,300
5,5000
6,9000
7,10000
8,15000
9,30000
10,100000
In Gnuplot I've tried to run the command stats on each of them to get the maximum value for x and y (i.e., columns 1 and 2) and to set the corresponding xrange & yrange. Unfortunately, the result is not the one I've expected.
Here is the full script:
#!/usr/bin/env gnuplot
set terminal latex
set term pngcairo enhanced size 1500,800
set output 'plot.png'
set multiplot layout 2,2
set xlabel 't' font ',16'
set ylabel '#pkt' font ',16'
set grid xtics lt 0 lw 1 lc rgb "#333333"
set grid ytics lt 0 lw 1 lc rgb "#333333"
set xtics font ',14'
set ytics font ',14'
set key font ',12'
set title font ',20'
set datafile separator ','
###
set title '(a)'
stats "a.csv" using 1:2 name "a"
set xrange [0:a_max_x]
set yrange [0:a_max_y+a_max_y*0.5]
plot "a.csv" using 1:2 title 'v1' with lines linewidth 3 linecolor rgb 'blue'
###
set title '(b)'
stats "b.csv" using 1:2 name "b"
set xrange [0:b_max_x]
set yrange [0:b_max_y+b_max_y*0.5]
plot "b.csv" using 1:2 title 'v1' with lines linewidth 3 linecolor rgb 'blue'
###
set title '(c)'
stats "c.csv" using 1:2 name "c"
set xrange [0:c_max_x]
set yrange [0:c_max_y+c_max_y*0.5]
plot "c.csv" using 1:2 title 'v1' with lines linewidth 3 linecolor rgb 'blue'
###
set title '(d)'
stats "d.csv" using 1:2 name "d"
set xrange [0:d_max_x]
set yrange [0:d_max_y+d_max_y*0.5]
plot "d.csv" using 1:2 title 'v1' with lines linewidth 3 linecolor rgb 'blue'
###
unset multiplot
and the result:
As you can see, maximum values in the plots b, c and d are not correct. Indeed, the verbose output of stats returns:
[...]
Maximum: 10.0000 [8] 13.0000 [8]
[...]
Maximum: 5.0000 [3] 15.0000 [3]
[...]
Maximum: 2.0000 [1] 10.0000 [1]
[...]
Maximum: 1.0000 [0] 1.0000 [0]
[...]
Apparently, only stats for the plot a is right. Is there anything wrong in my script?
You need you reinitialize xrange and yrange after setting them each time, because otherwise stats finds some of you points outside the range you have previously set and does not take them into account. It's the last line below:
set title '(a)'
stats "a.csv" using 1:2 name "a"
set xrange [0:a_max_x]
set yrange [0:a_max_y+a_max_y*0.5]
plot "a.csv" using 1:2 title 'v1' with lines linewidth 3 linecolor rgb 'blue'
set xrange [*:*] ; set yrange [*:*] # <--- This line after each plot will fix your issue
In your case there is no need to use stats in order to set the ranges.
Your requirements are:
Use tight limits for the xrange and the yrange. You get this with set autoscale fix.
Extend the maximum of the yrange by 50%. That is achieved with set offsets 0,0,graph 0.5,0:
#!/usr/bin/env gnuplot
set term pngcairo enhanced size 1500,800
set output 'plot.png'
set multiplot layout 2,2
set xlabel 't' font ',16'
set ylabel '#pkt' font ',16'
set grid xtics ytics lt 0 lw 1 lc rgb "#333333"
set tics font ',14'
set key font ',12'
set title font ',20'
set datafile separator ','
set style data lines
set style line 1 linewidth 3 linecolor rgb 'blue'
###
set title '(a)'
set autoscale fix
set offset 0,0,graph 0.5,0
plot "a.csv" using 1:2 title 'v1' linestyle 1
###
set title '(b)'
plot "b.csv" using 1:2 title 'v1' linestyle 1
###
set title '(c)'
plot "c.csv" using 1:2 title 'v1' linestyle 1
###
set title '(d)'
plot "d.csv" using 1:2 title 'v1' linestyle 1
###
unset multiplot
One further comment: If you're going to use a LaTeX-based terminal for your actual image, don't use latex, but rather epslatex, cairolatex, context or lua tikz, which are all much better regarding the supported features and quality.

Fill a region on a graph with no outline?

I'd like to fill a region on a graph plotted with octave, without any outline:
The fill command accepts a color argument that it respects for the filled area, but it doesn't seem to accept the 'LineColor' property to change the color of the line it draws around the filled area...
e.g.
fill([1 2 3 3 2 1], [1 0.5 1 -1 -1 -1], [0.9,0.9,0.9]); # line is black
fill([1 2 3 3 2 1], [1 0.5 1 -1 -1 -1], [0.9,0.9,0.9], 'LineColor', 'r') # hangs
I'm using octave-3.4.0 on OS X.
The patch command should do the job
verts = [0.2 0.4; ...
0.2 0.8; ...
0.8 0.8; ...
0.8 0.4];
faces = [1 2 3 4];
p = patch('Faces',faces,'Vertices',verts,'FaceColor','b','EdgeColor','none');
Of course you could also place it in one line ... ;-)