Yet another gnuplot bar chart (histogram) question. Starting from a flat list (could be any order), I want to create a normalized rowstacked and clustered bar chart.
There is a similar question with an answer, however, with no response of the OP and the suggestion in the answer does not cover my case.
Another question to the same topic, however, with answers without the desired solution.
Looking at the gnuplot homepage, gnuplot offers rowstacked histograms and clustered histograms. But apparently, no rowstacked and clustered histogram.
Although, there is newhistogram
, which however, places a separate bar chart on the x-axis next to the previous one. Not interlaced as I would like it to be.
So, if I use:
set style histogram rowstacked clustered
, the bar chart will be just clustered (see first graph below)set style histogram clustered rowstacked
, the bar chart will continue stacking (see second graph below)Apparently, the later keyword overwrites the former one. Furthermore, I thought I could use simply set style histogram rowstacked
and shift the first bar chart a bit to the left and the second one a bit to the right and use thinner bars, but apparently gnuplot doesn't allow something like:
plot for [COL=2:4] $Histo ($0-0.3):COL:xtic(1) index 0, \
for [COL=2:4] '' ($0+0.3):COL:xtic(1) index 1
This will result in an error: Too many columns in using specification
I know that in principle I could draw the bar chart from "scratch" with the plotting style with boxxyerror
, but why making things complicated if I maybe just overlooked a simple option?
Script:
### how to create a rowstacked AND clustered bar chart from a flat list
reset session
$Data <<EOD
# Mark Group Test Value
A Group1 Test1 23
B Group1 Test1 12
C Group1 Test1 14
A Group2 Test1 23
B Group2 Test1 9
C Group2 Test1 7
A Group1 Test2 11
B Group1 Test2 16
C Group1 Test2 19
A Group2 Test2 13
B Group2 Test2 24
C Group2 Test2 5
A Group1 Test3 4
B Group1 Test3 14
C Group1 Test3 17
A Group2 Test3 15
B Group2 Test3 8
C Group2 Test3 4
A Group1 Test4 10
B Group1 Test4 12
C Group1 Test4 16
A Group2 Test4 19
B Group2 Test4 20
C Group2 Test4 15
EOD
Marks = "A B C"
Groups = "Group1 Group2"
Tests = "Test1 Test2 Test3 Test4"
Mark(i) = word(Marks,i)
Group(i) = word(Groups,i)
Test(i) = word(Tests,i)
myFilter(col1,val1,col2,val2,col3,val3) = (strcol(col1) eq val1) && (strcol(col2) eq val2) && (strcol(col3) eq val3)
set print $Histo
do for [g=1:words(Groups)] {
print sprintf("# %s",Group(g))
print sprintf("X %s Sum",Marks)
do for [t=1:words(Tests)] {
Line = Test(t)
mySum = 0
do for [m=1:words(Marks)] {
value = 0
stats $Data u (myFilter(1,Mark(m),2,Group(g),3,Test(t)) ? value=column(4) : 0) nooutput
Line = Line.sprintf(" %g",value)
mySum = mySum + value
}
Line = Line.sprintf(" %g",mySum)
print Line
}
print ''; print '' # two empty lines
}
set print
print $Histo
set style data histogram
set style histogram clustered rowstacked # or: rowstacked clustered
set style fill solid 0.5
set yrange [0:]
set format y "%g%%"
set boxwidth 0.8
set grid y
set key noautotitle out title "Mark"
set offset 0.25,0.25,0,0
plot for [g=1:words(Groups)] for [col=2:words(Marks)+1] $Histo u \
(column(col)/(column(words(Marks)+2))*100):xtic(1) index g-1 ti columnheader(col)
### end of script
Result:
Datablock $Histo
:
# Group1
X A B C Sum
Test1 23 12 14 49
Test2 11 16 19 46
Test3 4 14 17 35
Test4 10 12 16 38
# Group2
X A B C Sum
Test1 23 9 7 39
Test2 13 24 5 42
Test3 15 8 4 27
Test4 19 20 15 54
set style histogram rowstacked clustered
set style histogram clustered rowstacked
Desired output should be something like this:
If I overlooked a simple solution which leads to the last plot, please let me know.
Here is a script which does what I wanted: a stacked and clustered bar chart from a flat list. It uses triple for
loops, a filter and the plotting style with boxxyerror
.
In order to easily change the grouping and focus, you need to define the following: The items from the flat list in strings preceded by the column number.
Marks = "1 A B C"
Groups = "2 Group1 Group2"
Tests = "3 Test1 Test2 Test3 Test4"
If you have 3 quantities you can arrange them in 3!=3*2*1=6
different ways.
Now, you can easily assign these strings to the items on x-axis level 0 (X0s
) and level 1 (X1s
) and items for y-axis (Y0s
). Furthermore, you define whether you want to normalize the bars or not.
X0s = Tests # x-axis group level 0
X1s = Groups # x-axis group level 1
Y0s = Marks # y-axis sum up
Normalize = 1 # 0=no, 1=yes
Script:
### create a stacked AND clustered bar chart from a flat list
reset session
$Data <<EOD
# Mark Group Test Value
A Group1 Test1 23
B Group1 Test1 12
C Group1 Test1 14
A Group2 Test1 23
B Group2 Test1 9
C Group2 Test1 7
A Group1 Test2 11
B Group1 Test2 16
C Group1 Test2 19
A Group2 Test2 13
B Group2 Test2 24
C Group2 Test2 5
A Group1 Test3 4
B Group1 Test3 14
C Group1 Test3 17
A Group2 Test3 15
B Group2 Test3 8
C Group2 Test3 4
A Group1 Test4 10
B Group1 Test4 12
C Group1 Test4 16
A Group2 Test4 19
B Group2 Test4 20
C Group2 Test4 15
EOD
Marks = "1 A B C"
Groups = "2 Group1 Group2"
Tests = "3 Test1 Test2 Test3 Test4"
colV = 4 # column for values
X0s = Tests # x-axis items level 0
X1s = Groups # x-axis items level 1
Y0s = Marks # y-axis sum up
Normalize = 1 # 0=no, 1=yes
myColors = "0xff0000 0x00ff00 0x0000ff 0xff00ff 0xffff00 0x00ffff"
# everthing goes automatic from here on
colX0 = int(word(X0s,1))
colX1 = int(word(X1s,1))
colY0 = int(word(Y0s,1))
X0(i) = word(X0s,i)
X1(i) = word(X1s,i)
Y0(i) = word(Y0s,i)
x0N = words(X0s)
x1N = words(X1s)
y0N = words(Y0s)
myColor(i) = int(word(myColors,i))
myBoxwidth = 0.8
myGap = 1.0
myFilter(col1,val1,col2,val2,col3,val3) = (strcol(col1) eq val1) && \
(strcol(col2) eq val2) && (strcol(col3) eq val3)
# create bar chart table
set print $BarChart
do for [x1i=2:x1N] {
print sprintf("# %s",X1(x1i))
print sprintf("Col%s Sum",Y0s)
do for [x0i=2:x0N] {
Line = X0(x0i)
mySum = 0
do for [y0i=2:y0N] {
value = 0
stats $Data u (myFilter(colY0,Y0(y0i),colX0,X0(x0i),colX1,X1(x1i)) ? \
value=column(colV) : 0) nooutput
Line = Line.sprintf(" %g",value)
mySum = mySum + value
}
Line = Line.sprintf(" %g",mySum)
print Line
}
print ''; print '' # two empty lines
}
set print
# print $BarChart
set xrange[-1:(x0N-2)*(x1N-1+myGap)+x1N-1]
set format x "\n"
set xtics nomirror
set yrange [0:]
if (Normalize) { set yrange [0:105] }
set format y (Normalize ? "%g%%" : "%g")
set grid y
set key noautotitle out
set style fill solid 0.5
myX0Pos(i) = column(0)*(x1N-1+myGap) + (x1N-2)/2. + 1e-6
myX0tic(col) = sprintf("\n%s", X0(int(column(col)+2)))
myTitle(col) = sprintf("%s", x0i==1 && x1i==1 ? Y0(col) : '')
plot for [x0i=1:x0N-1] for [x1i=1:x1N-1] for [y0i=1:y0N-1] $BarChart u \
((x0i-1)*(x1N-1+myGap)+x1i-1): \
(vt=column(y0N+1), v0=column(y0i+1), v1=((sum [i=2:y0i] column(i))+v0/2.)/(Normalize?vt/100:1)): \
(myBoxwidth/2.):(v0/2./(Normalize?vt/100:1)):xtic(X1(x1i+1)) index x1i-1 every ::x0i::x0i w boxxy \
lc rgb myColor(y0i) ti myTitle(y0i+1), \
'+' u (myX0Pos(0)):(NaN):xtic(myX0tic(0)) every ::::x0N-2 w p
### end of script
Result: (all possible 6 permutations merged into one image: left column unnormalized, right column normalized)