How to plot tree/graph/web data on gnuplot? - graph

I have a data-set that consist of edges and colors, and I want to plot them on a web-like manner, with lines and circles such as the picture below, and possibly with cluster coloring.
The data is organized like this:
point1a_x point1a_y color
point1b_x point1b_y color
point2a_x point2a_y color
point2b_x point2b_y color
(...)
point2n_x point2n_y color
point2n_x point2n_y color
How would I go about doing it on gnuplot?

Okay, so I figured it out myself and I'll leave the details here to help anyone with the same questions.
Single color graph with labels on the nodes:
This will generate a graph much like the one on the question, with lines connecting circles with labels inside.
plot 'edges.dat' u 1:2 with lines lc rgb "black" lw 2 notitle,\
'edges.dat' u 1:2:(0.6) with circles fill solid lc rgb "black" notitle,\
'edges.dat' using 1:2:($0) with labels tc rgb "white" offset (0,0) font 'Arial Bold' notitle
With little changes it can exaclty match the one on the question picture.
plot 'edges.dat' u 1:2 with lines lc rgb "black" lw 2 notitle,\
'edges.dat' u 1:2:(0.8) with circles linecolor rgb "white" lw 2 fill solid border lc lt 0 notitle, \
'edges.dat' using 1:2:($0) with labels offset (0,0) font 'Arial Bold' notitle
Cluster-colored graph:
unset colorbox
set palette model RGB defined ( 0 0 0 0 , 1 1 0 0 , 2 1 0.9 0, 3 0 1 0, 4 0 1 1 , 5 0 0 1 , 6 1 0 1 )
plot 'edges.dat' u 1:2:3 with lines lc palette notitle,\
'edges.dat' u 1:2:(0.15):3 with circles fill solid palette notitle
The data used on all plots follow this structure:
21.53 9.55 0
24.26 7.92 0
5.63 3.23 1
2.65 1.77 1
5.63 3.23 0
4.27 7.04 0
(...)

The accepted answer didn't quite work out for me. Here is how I had to change it:
The format of the input file
# A vertex has 3 fields: x coordinate, y coordnate and the label
# An edge consists of two points in consecutive lines
# There must be one or more blank lines between each edge.
21.53 9.55 A
24.26 7.92 B
5.63 3.23 C
2.65 1.77 D
5.63 3.23 C
4.27 7.04 E
#...
The big difference compared to the other answer is that the labels belong to vertices, not edges.
Also note that I changed the labels to letters instead of numbers. Labels can be any string and this makes it clearer that they are not sequential indexes in the example.
The plotting command
plot \
'edges.dat' using 1:2 with lines lc rgb "black" lw 2 notitle,\
'edges.dat' using 1:2:(0.6) with circles fill solid lc rgb "black" notitle,\
'edges.dat' using 1:2:3 with labels tc rgb "white" offset (0,0) font 'Arial Bold' notitle
Big change here is that now when plotting the labels we plot the 3rd field instead of the $0 field, which is a sequential number.

I came across this question and thought the data input can be made more user-friendly. In case you need to change x,y coordinates of a certain point you probably don't want to search through all connectors and change the coordinates everywhere accordingly. So, instead the example below is working with node IDs and connecting lines between two IDs.
The x,y coordinates and colors are stored in a string and the function strstrt() is used to create a lookup. The script below is a starting point and can be expanded, e.g. dashed lines, different point shapes, string labels, etc.
The datafile looks like this:
first block: IDs with x,y coordinates and color
second block, separated by two(!) blank lines: IDs to be connected and color of the line.
Data: SO20406346.dat
# ID x y PointColor
1 2.0 8.0 0xffaaaa
2 6.0 9.0 0xaaffaa
3 9.0 6.0 0xaaaaff
4 5.0 5.0 0xffaaff
5 6.0 2.0 0xffffaa
6 1.0 1.0 0xaaffff
73 9.2 1.3 0xcccccc
A 8.0 8.0 0xcccccc
XY 2.0 4.0 0xcccccc
# ID1 ID2 LineColor
1 4 0x0000ff
2 4 0x000000
3 4 0x00ff00
4 4 0x000000
5 4 0x000000
6 5 0xff0000
73 3 0xcccccc
73 4 0xcccccc
73 5 0xcccccc
A 2 0xcccccc
A 3 0xcccccc
XY 1 0xcccccc
XY 4 0xcccccc
XY 6 0xcccccc
Edit: changed to work with string "IDs" as well.
Script: (works for gnuplot>=4.6.0, March 2012)
### plot a node graph
reset
FILE = "SO20406346.dat"
IdIdxs = XYs = ' '
stats FILE u (IdIdxs=IdIdxs.sprintf("%s:%d ",strcol(1),$0), \
XYs=XYs.sprintf("%g %g ",$2,$3)) index 0 nooutput
Px(i) = real(word(XYs,2*i+1))
Py(i) = real(word(XYs,2*i+2))
getIdx(col) = (c0=strstrt(IdIdxs,sprintf(" %s:",strcol(col))), \
c1=strstrt(IdIdxs[c0+1:],' ')+c0, \
s0=IdIdxs[c0:c1], c2=strstrt(s0,':'), int(s0[c2+1:]))
set size ratio 1
set key noautotitle
set offsets 0.25,0.25,0.25,0.25
plot FILE index 1 u (idx0=getIdx(1),x0=Px(idx0)):(y0=Py(idx0)): \
(idx1=getIdx(2),Px(idx1)-x0):(Py(idx1)-y0):3 w vec lw 2 lc rgb var nohead, \
'' index 0 u 2:3:4 w p pt 7 ps 6 lc rgb var, \
'' index 0 u 2:3 w p pt 6 ps 6 lc rgb "black", \
'' index 0 u 2:3:1 w labels
### end of script
Result:

Related

Pie chart gnuplot

Does anyone have example code in gnuplot for a pie chart? I can't find any great example with a simple graph and the text around it and in it with the % signs that shows easily how much each part has of the circle.
Some example data:
Management frames 4596
Control frames 70173
Data frames 40347
TCP packets 36864
HTTP packets 525
ICMP packets 47
Total frames 115116
Updated:
After some years, I came again across this post, and I thought the code looked pretty messy. Therefore an attempt to improve and clean it up.
The following code is a bit different from the link I referenced above.
instead of a predefined color sequence or number codes in a separate list, the colors of the sections are given in the datablock (or datafile) next to the item/number by the names of predefined colors in gnuplot (see also https://stackoverflow.com/a/55736522/7295599). Because palette is used you can enter either colorname or hex-code, e.g. magenta or 0xff00ff.
the labels are aligned left or right depending on their position relative to 0.
you can choose the starting angle by PieStart and "rotation"-direction by PieDirection of the pie-chart
you can add individual radial and angular offsets for segments and labels
as you can see, there is no need for the total sum in the raw data. The total sum will be calculated automatically.
the reason why I define various functions f(n) which actually do not depend on n is to get the current values of other variables (at the time of calling the function) instead of passing a lot of parameters to the functions.
I hope you can adapt this code to your needs.
Code: (works with gnuplot>=5.0.0)
### pie-chart with labels with gnuplot
reset session
set size square
set angle degrees
set border 0
unset colorbox
unset tics
unset key
$Data <<EOD
# label value color SRoff SAoff LRoff LAoff
"Alpha" 85843 red 0 0 0 0
"Beta" 44000 green 0.2 45 0.2 0
"Gamma" 25399 blue 0 0 0 0
"Delta" 18451 magenta 0 0 0 0
"Epsilon" 12344 yellow 0 0 0 0
"Zeta" 11999 cyan 0 0 0 0
"Eta" 9000 orange 0 0 0 0
"Theta" 8500 0xb0f060 0 0 0.03 0
"Iota" 4711 dark-violet 0 0 0.12 0
EOD
colLabel = 1 # label
colValue = 2 # segment value
colColor = 3 # segment color, either color name or 0xRRGGBB value
colSegRoff = 4 # radial segment offset
colSegAoff = 5 # angular segment offset
colLabRoff = 6 # radial label offset
colLabAoff = 7 # angular label offset
# define a palette from colornames of the datafile/datablock in column colColor
set table $Dummy
myPalette = ''
plot $Data u (myPalette = myPalette.(myPalette eq '' ? '' : ', ').sprintf('%d "%s"',$0,strcol(colColor)),$0) with table
myPalette = '('.myPalette.')'
unset table
set palette defined #myPalette
stats $Data u colValue nooutput # get total sum from column colValue
TotalSum = STATS_sum
set xrange[-1.5:1.5]
set yrange[-1.5:1.5]
PieStart = 90 # 0 = 3 o'clock, 90 = 12 o'clock
PieDirection = -1 # -1 clockwise, 1 counterclockwise
Radius = 1.0
RadiusLabelOff = 0.05 # default radial label offset
SegPosX(n) = column(colSegRoff)*cos((a2+a1+column(colSegAoff))*0.5)
SegPosY(n) = column(colSegRoff)*sin((a2+a1+column(colSegAoff))*0.5)
LabPosX(n) = (Radius+RadiusLabelOff+column(colLabRoff))*cos((a2+a1+column(colLabAoff))*0.5)
LabPosY(n) = (Radius+RadiusLabelOff+column(colLabRoff))*sin((a2+a1+column(colLabAoff))*0.5)
a1=a2=PieStart
getAngles(n) = (a1=a2, a2=a2+sgn(PieDirection)*column(colValue)/TotalSum*360)
getLabel(n) = sprintf("%s %.1f%%", strcol(colLabel), column(colValue)/TotalSum*100)
set multiplot layout 2,1
plot $Data u (getAngles(0), SegPosX(0)):(SegPosY(0)):(Radius):(PieDirection<0?a2:a1):(PieDirection<0?a1:a2):($0) \
with circles fs solid 1.0 lc palette notitle,\
'' u ( getAngles(0), Align=LabPosX(0)):(LabPosY(0)): (Align>0? getLabel(0) : '') with labels font ",10" left, \
'' u ( getAngles(0), Align=LabPosX(0)):(LabPosY(0)): (Align<0? getLabel(0) : '') with labels font ",10" right
PieDirection = +1
a1=a2=PieStart
replot
unset multiplot
### end of code
Result:

Setting line opacity in gnuplot without using a hex code

I am using gnuplot to plot some lines and I would like to make some of these lines transparent. I know that this is possible within some terminals (e.g., aquaterm on Mac) using a command like this:
plot x lw 10 lc rgb "#77000000"
which would produce a half-transparent black line (i.e., grey) with the hex colour being black - '000000' and the alpha value being '77'. However, using this requires me to know the hex code for the colour that I want. Is it possible to specify the colour in a more standard way, for example using the colour name or number, and then to also specify the opacity level separately?
The type of command that I would be looking for would be something like:
plot x lw 10 lc rgb 'black' transparency '#77'
which does not work. Any ideas? I'm using gnuplot 5.
There is a workaround even for gnuplot 5.0. In gnuplot 5.2 you also could use arrays. Take the RGB color values from a list of defined colors and add your transparency (alpha). Of course, you have to get the color code from somewhere.
In gnuplot console type show colors and see all the 111 predefined colors in gnuplot. Basically, you could also define your own colors.
Have the gnuplot standard colors visualized here: https://stackoverflow.com/a/54659829/7295599
Code:
### add colors by name with transparency
reset session
ColorNames = "white black dark-grey red web-green web-blue dark-magenta dark-cyan dark-orange dark-yellow royalblue goldenrod dark-spring-green purple steelblue dark-red dark-chartreuse orchid aquamarine brown yellow turquoise grey0 grey10 grey20 grey30 grey40 grey50 grey60 grey70 grey grey80 grey90 grey100 light-red light-green light-blue light-magenta light-cyan light-goldenrod light-pink light-turquoise gold green dark-green spring-green forest-green sea-green blue dark-blue midnight-blue navy medium-blue skyblue cyan magenta dark-turquoise dark-pink coral light-coral orange-red salmon dark-salmon khaki dark-khaki dark-goldenrod beige olive orange violet dark-violet plum dark-plum dark-olivegreen orangered4 brown4 sienna4 orchid4 mediumpurple3 slateblue1 yellow4 sienna1 tan1 sandybrown light-salmon pink khaki1 lemonchiffon bisque honeydew slategrey seagreen antiquewhite chartreuse greenyellow gray light-gray light-grey dark-gray slategray gray0 gray10 gray20 gray30 gray40 gray50 gray60 gray70 gray80 gray90 gray100"
ColorValues = "0xffffff 0x000000 0xa0a0a0 0xff0000 0x00c000 0x0080ff 0xc000ff 0x00eeee 0xc04000 0xc8c800 0x4169e1 0xffc020 0x008040 0xc080ff 0x306080 0x8b0000 0x408000 0xff80ff 0x7fffd4 0xa52a2a 0xffff00 0x40e0d0 0x000000 0x1a1a1a 0x333333 0x4d4d4d 0x666666 0x7f7f7f 0x999999 0xb3b3b3 0xc0c0c0 0xcccccc 0xe5e5e5 0xffffff 0xf03232 0x90ee90 0xadd8e6 0xf055f0 0xe0ffff 0xeedd82 0xffb6c1 0xafeeee 0xffd700 0x00ff00 0x006400 0x00ff7f 0x228b22 0x2e8b57 0x0000ff 0x00008b 0x191970 0x000080 0x0000cd 0x87ceeb 0x00ffff 0xff00ff 0x00ced1 0xff1493 0xff7f50 0xf08080 0xff4500 0xfa8072 0xe9967a 0xf0e68c 0xbdb76b 0xb8860b 0xf5f5dc 0xa08020 0xffa500 0xee82ee 0x9400d3 0xdda0dd 0x905040 0x556b2f 0x801400 0x801414 0x804014 0x804080 0x8060c0 0x8060ff 0x808000 0xff8040 0xffa040 0xffa060 0xffa070 0xffc0c0 0xffff80 0xffffc0 0xcdb79e 0xf0fff0 0xa0b6cd 0xc1ffc1 0xcdc0b0 0x7cff40 0xa0ff20 0xbebebe 0xd3d3d3 0xd3d3d3 0xa0a0a0 0xa0b6cd 0x000000 0x1a1a1a 0x333333 0x4d4d4d 0x666666 0x7f7f7f 0x999999 0xb3b3b3 0xcccccc 0xe5e5e5 0xffffff"
myColor(c) = (idx=NaN, sum [i=1:words(ColorNames)] \
(c eq word(ColorNames,i) ? idx=i : idx), word(ColorValues,idx))
# add transparency (alpha) a=0 to 255 or 0x00 to 0xff
myTColor(c,a) = sprintf("0x%x%s",a, myColor(c)[3:])
set xrange[0:2*pi]
set samples 200
plot sin(x) w l lw 12 lc rgb myTColor("red",0xcc), \
sin(2*x) w l lw 12 lc rgb myTColor("green",0xcc), \
sin(3*x) w l lw 12 lc rgb myTColor("blue",0xcc)
### end of code
Result:
Addition:
If you even don't want long lists of color names and color hex-codes in your gnuplot script you could use the following: extract the values automatically from dummy palettes. For this, gnuplot has to plot test palette for each color. You could switch your terminal before the loop to set term unknown and after it back to your desired terminal.
I guess getting values from $PALETTE only works with gnuplot >=5.2.
Code: (Result same as above)
### add colors by name with transparency (without hex-color code list)
reset session
ColorNames = 'red green blue magenta yellow cyan' # must be existing gnuplot color names
# get the color values from dummy palettes
ColorValues = ''
RGBComp(c) = int(word($PALETTE[256],c+1)*0xff)
do for [i=1:words(ColorNames)] {
set palette defined (0 word(ColorNames,i))
test palette
RGB = sprintf("0x%02x%02x%02x",RGBComp(1),RGBComp(2),RGBComp(3))
ColorValues = ColorValues." ".RGB
}
myColor(c) = (idx=NaN, sum [i=1:words(ColorNames)] \
(c eq word(ColorNames,i) ? idx=i : idx), word(ColorValues,idx))
# add transparency (alpha) a=0 to 255 or 0x00 to 0xff
myTColor(c,a) = sprintf("0x%02x%s",a, myColor(c)[3:])
set xrange[0:2*pi]
set samples 200
plot sin(x) w l lw 12 lc rgb myTColor("red",0xcc), \
sin(2*x) w l lw 12 lc rgb myTColor("green",0xcc), \
sin(3*x) w l lw 12 lc rgb myTColor("blue",0xcc)
### end of code

How to plot a graph in gnuplot without specifying the coordinates of vertices?

I want to use gnuplot to plot relations rather than the exact coordinates.
Something like Igraph in R where I can do A->B without specifying the coordinates in space. I am using a gnuplot script specified on other SO answers inside the system call.
I want to integrate it with my ocaml compiler inside LLVM. If there are any suggestions on that as well, please let me know.
Thank you so much.
Optimizing graphs is a large and interesting field. And as #eush77 mentioned, Graphviz is a dedicated tool for this type of task.
Although, you could do something with gnuplot. There is a relatively simple algorithm which is based on attracting and repelling forces between vertices. Details can be found e.g. here and here.
First, the script places the vertices at random coordinates and then iterates to a final state (however, which is not always optimal).
You need to play with the constants c1,c2,c3,c4.
The example below used the gif terminal to visualize the iterations. If you are only interested in the final result, use another terminal and move the replot after the loop.
The script below is a starting point and can certainly be improved. Suggestions and comments are welcome.
Script: (works with gnuplot>=5.2.0, because of the use of arrays)
### plotting optimized graph
reset session
$Data <<EOD
# ID PointColor
1 0xffaaaa
2 0xaaffaa
3 0xaaaaff
4 0xffaaff
5 0xffffaa
6 0xaaffff
73 0xcccccc
A 0xcccccc
XY 0xcccccc
0 0xffffff
G 0xffffff
# ID1 ID2 LineColor
1 4 0x0000ff
2 4 0x000000
3 4 0x00ff00
5 4 0x000000
6 5 0xff0000
73 3 0xcccccc
73 4 0xcccccc
73 5 0xcccccc
A 2 0xcccccc
A 3 0xcccccc
2 1 0xcccccc
XY 4 0xcccccc
XY 6 0xcccccc
0 2 0xcccccc
0 XY 0xcccccc
G 0 0xcccccc
G A 0xcccccc
EOD
stats $Data u (column(-2)==0?Nv=int($0+1):Ne=int($0+1)) nooutput # get number of vertices and edges
array Vx[Nv]
array Vy[Nv]
array Vt[Nv]
array Vc[Nv]
stats $Data index 0 u (i=int($0)+1, Vt[i]=strcol(1), Vx[i]=rand(0)*10, Vy[i]=rand(0)*10, \
Vc[i]=int($2)) nooutput # random placement of vertices
Vidx(s) = sum[_i=1:|Vx|] ( Vt[_i] eq s ? _i : 0) # get index via lookup of vertex "name"
array E0[Ne]
array E1[Ne]
array Ec[Ne]
stats $Data index 1 u (i=int($0)+1, E0[i]=Vidx(strcol(1)), E1[i]=Vidx(strcol(2)), \
Ec[i]=int($3) ) nooutput # get edge point indices
set size ratio 1
set key noautotitle
set offsets 0.25,0.25,0.25,0.25
unset border
unset tics
set term gif animate delay 20
set output "SO43843240.gif"
plot E0 u (i=int($0+1), x0=Vx[E0[i]]):(y0=Vy[E0[i]]):(Vx[E1[i]]-x0):(Vy[E1[i]]-y0): \
(Ec[i]) w vec lw 2 lc rgb var nohead, \
Vx u (i=int($0+1),Vx[i]):(Vy[i]):(Vc[i]) w p pt 7 ps 6 lc rgb var, \
'' u (i=int($0+1),Vx[i]):(Vy[i]) w p pt 6 ps 6 lc rgb "black", \
'' u (i=int($0+1),Vx[i]):(Vy[i]):(Vt[i]) w labels
# parameters for force
c1 = 2
c2 = 1
c3 = 1
c4 = 0.2
fs(d) = c1*log(d/c2) # force spring attracting/repelling
fr(d) = c3/d # force repelling
dV(i,j) = sqrt((Vx[j]-Vx[i])**2 + (Vy[j]-Vy[i])**2)
set angle degrees
a(i,j) = atan2(Vy[j]-Vy[i], Vx[j]-Vx[i])
array Fx[Nv] # force in x
array Fy[Nv] # force in y
do for [n=1:500] {
set label 1 at screen 1.0,0.97 sprintf("Iteration: % 4d",n) right
# repelling forces
do for [i=1:Nv] {
Fx[i] = Fy[i] = 0 # initialize
do for [j=1:Nv] {
if (i!=j) {
f0 = fr(dV(i,j))
a0 = a(i,j)
Fx[i] = Fx[i] - f0*cos(a0)
Fy[i] = Fy[i] - f0*sin(a0)
}
}
}
# spring forces
do for [n=1:Ne] {
i = E0[n]
j = E1[n]
f0 = fs(dV(i,j))
a0 = a(i,j)
Fx[i]=Fx[i]+f0*cos(a0)
Fy[i]=Fy[i]+f0*sin(a0)
Fx[j]=Fx[j]-f0*cos(a0)
Fy[j]=Fy[j]-f0*sin(a0)
}
# add displacement
do for [i=1:Nv] {
Vx[i] = Vx[i] + c4*Fx[i]
Vy[i] = Vy[i] + c4*Fy[i]
}
stats Fy u 2 nooutput # get maximum change y
if (abs(c4*STATS_max)<0.005) { break } # exit loop when max. y-displacement below threshold
replot
}
set output
### end of script
Result: (animation from gif terminal)

drawing a perpendicular bisector of two points in gnuplot

Say, I have two points (x1,y1) and (x2,y2) on the same line . The midpoint of joining this two points is (x,y). Is it possible to draw a perpendicular bisector through (x,y) in gnuplot? How will I draw it?
It's simple math:
your line slope: slope = (y2 - y1) / (x2 - x1)
your line equation: line(x) = slope * (x - x1) + y1
the middle point (calling it xm,ym because x,y are reserved):
xm=(x1+x2)/2.0
ym=(y1+y2)/2.0
the perpendicular line equation: line_perp(x) = -(x-xm)/slope + ym
plot both: plot line(x), line_perp(x)
In case y2==y1 or x2==x1 i.e the two points are in horizontal/vertical line you can fix the script using an arrow:
if (y2==y1 || x2==x1) {
set arrow from xm, graph 0 to xm, graph 1 nohead
plot ym
} else {
plot line(x), line_perp(x)
}
Instead of going via y = a*x + b as in #bibi's and #user4489658's answers, where a could be infinite, I would go via the angle (check help atan2).
This covers vertical lines without extra treatment.
Make sure to use set size ratio -1 (check help size) that the perpendicular bisector really looks perpendicular in the graph.
Script:
### drawing perpendicular bisectors
reset session
$Data <<EOD
# x1 y1 x2 y2
10 20 15 20
10 10 20 20
20 10 20 15
12 10 18 12
10 12 12 18
EOD
set key out
set offset 1,1,1,1
set angle degrees
set size ratio -1
colX1 = 1
colY1 = 2
colX2 = 3
colY2 = 4
dx(n) = column(colX2)-column(colX1)
dy(n) = column(colY2)-column(colY1)
a0(n) = atan2(dy(n),dx(n)) + 90
xm(n) = (column(colX1)+column(colX2))*0.5
ym(n) = (column(colY1)+column(colY2))*0.5
Scaling = 0.2
L0(n) = Scaling*sqrt(dx(n)**2 + dy(n)**2)
getParams(n) = (dx0=L0(0)*cos(a0(0)), dy0=L0(0)*sin(a0(0)), x0=xm(0)-dx0, y0=ym(0)-dy0)
plot $Data u colX1:colY1 w p pt 7 lc "blue" ti "Start", \
'' u colX2:colY2 w p pt 7 lc "red" ti "End", \
'' u colX1:colY1:(dx(0)):(dy(0)) w vec lc "web-green" filled ti "Vector", \
'' u (getParams(0),x0):(y0):(2*dx0):(2*dy0) w vec lc "black" dt 3 nohead ti "\nperpendicular\nbisector"
### end of script
Result:
Equation of your line is
y=ax+b
a=(y2-y1)/(x2-x1)
b=(x2*y1-y2*x1)/(x2-x1)
Midpoint:
x3=(x1+x2)/2.;y3=(y1+y2)/2.
Equation of perpendicular line:
y-y3=-1./a*(x-x3)
y=-1./a*x+x3/a+y3
a2=-1./a
b2=x3/a+y3
gnuplot script:
x1=1.;y1=3.;x2=10.;y2=15.
a=(y2-y1)/(x2-x1)
b=(x2*y1-y2*x1)/(x2-x1)
x3=(x2+x1)/2.;y3=(y2+y1)/2.
a2=-1./a
b2=x3/a+y3
set arrow 1 from x1,y1 to x2,y2 nohead
plot [0:15][0:22] a2*x+b2

How to Create a Spider Plot in Gnuplot?

I would like to produce a spider (aka radar/star) plot using Gnuplot where different axes have independent scales. I am able to produce such a plot using OriginPro (commercial), but with Gnuplot I am only able to set a radar plot with uniform scale.
The (csv file) dataset looks like the following (first row is column labels):
# FEATURE, Product_A, Product_B, Product_C, Product_D
attribute_1, 2, 10, 7, 3.5
attribute_2, 1, 0.5, 3,4
attribute_3, 37, 58, 49, 72
attribute_4, 1985, 1992, 2006, 2010
attribute_5, 0.1, 0.5, 0.3, 0.8
and the plot I am looking for is this one: https://www.dropbox.com/s/uvqubzqvm6puhb8/spider.pdf -
As you can see each axis stands for a different attribute, and has its own scale.
I guess the Gnuplot starting code is:
set polar
set grid polar
set angles degrees
set size square
set style data filledcurves
But I don't know how to proceed. Any suggestions?
here's a hack attempt..
set nokey
set polar
set grid polar
set angles degrees
set size square
set style data lines
a1=0
a2=30
a3=100
a4=200
a5=300
set arrow nohead from 0,0 to first 10*cos(a1) , 10*sin(a1)
set arrow nohead from 0,0 to first 10*cos(a2) , 10*sin(a2)
set arrow nohead from 0,0 to first 10*cos(a3) , 10*sin(a3)
set arrow nohead from 0,0 to first 10*cos(a4) , 10*sin(a4)
set arrow nohead from 0,0 to first 10*cos(a5) , 10*sin(a5)
set xrange [-10:10]
set yrange [-10:10]
plot '-' using ($1==1?a1:($1==2?a2:($1==3?a3:($1==4?a4:($1==5?a5:$1))))):2 lt 2
1 4
2 8
3 6
4 9
5 5
1 4
Here is a suggestion for a "spider-plot" taken from my collection.
Since it uses data from a datablock instead from a file (because it's easier to address certain lines, e.g. via $Data[1]), therefore, it requires gnuplot >=5.2.0.
The actual data is in $Data and some settings for ranges and custom offset adjustments are in $Settings.
The number of axes is automatically adjusted if you add some more rows in $Data and $Settings. Data needs to be separated by whitespace, because the gnuplot function word(string,number) is used to extract some values.
I hope it is more or less self-explaining. Comments, report of bugs or improvements are welcome.
Code:
### spider plot/chart with gnuplot
# also known as: radar chart, web chart, star chart, cobweb chart,
# radar plot, web plot, star plot, cobweb plot, etc. ...
reset session
set size square
unset tics
set angles degree
set key top left
# Data
$Data <<EOD
SpiderData "Product A" "Product B" "Product C" "Product D"
Colors red green blue violet
"attribute 1" 2 10 7 3.5
"attribute 2" 1 0.5 3 4
"attribute 3" 37 58 49 72
"attribute 4" 1985 1992 2006 2010
"attribute 5" 0.1 0.5 0.3 0.8
EOD
HeaderLines = 2
# Settings for scale and offset adjustments
# axis min max tics axisLabelXoff axisLabelYoff ticLabelXoff ticLabelYoff
$Settings <<EOD
1 0 12 6 0.00 -0.02 -0.05 0.00
2 0 6 6 0.00 0.05 0.00 0.05
3 30 90 6 0.00 0.00 0.05 0.03
4 1980 2016 6 0.00 0.00 0.09 -0.02
5 0 1.2 6 0.00 0.05 0.00 -0.05
EOD
# General settings
DataColCount = words($Data[1])-1
AxesCount = |$Data|-HeaderLines
AngleOffset = 90
Max = 1
d=0.1*Max
Direction = -1 # counterclockwise=1, clockwise = -1
# Tic settings
TicCount = 6
TicValue(axis,i) = real(i)*(word($Settings[axis],3)-word($Settings[axis],2)) \
/ word($Settings[axis],4)+word($Settings[axis],2)
TicLabelPosX(axis,i) = PosX(axis,i/TicCount) + word($Settings[axis],7)
TicLabelPosY(axis,i) = PosY(axis,i/TicCount) + word($Settings[axis],8)
TicLen = 0.03
TicdX(axis,i) = 0.5*TicLen*cos(alpha(axis)-90)
TicdY(axis,i) = 0.5*TicLen*sin(alpha(axis)-90)
# Functions
alpha(axis) = (axis-1)*Direction*360.0/AxesCount+AngleOffset
PosX(axis,R) = R*cos(alpha(axis))
PosY(axis,R) = R*sin(alpha(axis))
Scale(axis,value) = real(value-word($Settings[axis],2))/(word($Settings[axis],3)-word($Settings[axis],2))
# Spider settings
set style arrow 1 dt 1 lw 1.0 lc -1 head # style for axes
set style arrow 2 dt 2 lw 0.5 lc -1 nohead # style for weblines
set style arrow 3 dt 1 lw 1 lc -1 nohead # style for axis tics
set samples AxesCount
set isosamples TicCount
set urange[1:AxesCount]
set vrange[1:TicCount]
do for [i=1:DataColCount] { # set linetypes/colors
set linetype i lc rgb word($Data[2],i+1)
}
set style fill transparent solid 0.2
set xrange[-Max-4*d:Max+4*d]
set yrange[-Max-4*d:Max+4*d]
plot \
'+' u (0):(0):(PosX($0,Max+d)):(PosY($0,Max+d)) w vec as 1 not, \
$Data u (PosX($0+1,Max+2*d)+word($Settings[$0+1],5)): \
(PosY($0+1,Max+2*d)+word($Settings[$0+1],6)):1 every ::HeaderLines w labels center enhanced not, \
'++' u (PosX($1,$2/TicCount)):(PosY($1,$2/TicCount)): \
(PosX($1+1,$2/TicCount)-PosX($1,$2/TicCount)): \
(PosY($1+1,$2/TicCount)-PosY($1,$2/TicCount)) w vec as 2 not, \
'++' u (PosX($1,$2/TicCount)-TicdX($1,$2/TicCount)): \
(PosY($1,$2/TicCount)-TicdY($1,$2/TicCount)): \
(2*TicdX($1,$2/TicCount)):(2*TicdY($1,$2/TicCount)) \
w vec as 3 not, \
for [i=1:DataColCount] $Data u (PosX($0+1,Scale($0+1,column(i+1)))): \
(PosY($0+1,Scale($0+1,column(i+1)))) every ::HeaderLines w filledcurves lt i title word($Data[1],i+1), \
'++' u (TicLabelPosX($1,$2)):(TicLabelPosY($1,$2)): \
(sprintf("%g",TicValue($1,$2))) w labels font ",8" not
### end of code
Result:
The answer by #george helped me figure out how to rearrange the dataset, in order to pick from it the corresponding attribute data.
Because I was also looking for different range scales for the different spider axes, in addition to #george's suggestion, I thought that an axis-specific normalisation to the common [0:1] range, would have the problem solved. The main modification is then related to the using field of the plot command.
The code is fairly lengthy, I'm sure it could be optimised. It could also be merged into a script or a simple C code, in order to let the user decide the number of axes (number of attributes), and the different ranges (min, max) for each specific axis.
The following example is for 5 attributes comparing 2 products. Here is shown the plot result image:
set nokey
set polar
set angles degrees
npoints = 5
a1 = 360/npoints*1
a2= 360/npoints*2
a3= 360/npoints*3
a4= 360/npoints*4
a5= 360/npoints*5
set grid polar 360.
set size square
set style data lines
unset border
set arrow nohead from 0,0 to first 1*cos(a1) , 1*sin(a1)
set arrow nohead from 0,0 to first 1*cos(a2) , 1*sin(a2)
set arrow nohead from 0,0 to first 1*cos(a3) , 1*sin(a3)
set arrow nohead from 0,0 to first 1*cos(a4) , 1*sin(a4)
set arrow nohead from 0,0 to first 1*cos(a5) , 1*sin(a5)
a1_max = 10
a2_max = 5
a3_max = 100
a4_max = 2020
a5_max = 1
a1_min = 0
a2_min = 0
a3_min = 50
a4_min = 1980
a5_min = 0
set label "(0:10)" at cos(a1),sin(a1) center offset char 1,1
set label "(0:5)" at cos(a2),sin(a2) center offset char -1,1
set label "(50:100)" at cos(a3),sin(a3) center offset char -1,-1
set label "(1980:2020)" at cos(a4),sin(a4) center offset char 0,-1
set label "(0:1)" at cos(a5),sin(a5) center offset char 3,0
set xrange [-1:1]
set yrange [-1:1]
unset xtics
unset ytics
set rrange [0:1]
set rtics (""0,""0.25,""0.5,""0.75,""1)
plot '-' using ($1==1?a1:($1==2?a2:($1==3?a3:($1==4?a4:($1==5?a5:$1))))):($1==1?(($2-a1_min)/(a1_max-a1_min)):($1==2?(($2-a2_min)/(a2_max-a2_min)):($1==3?(($2-a3_min)/(a3_max-a3_min)):($1==4?(($2-a4_min)/(a4_max-a4_min)):($1==5?(($2-a5_min)/(a5_max-a5_min)):$1))))) w l
1 8
2 3
3 67
4 2000
5 0.2
1 8
plot '-' using ($1==1?a1:($1==2?a2:($1==3?a3:($1==4?a4:($1==5?a5:$1))))):($1==1?(($2-a1_min)/(a1_max-a1_min)):($1==2?(($2-a2_min)/(a2_max-a2_min)):($1==3?(($2-a3_min)/(a3_max-a3_min)):($1==4?(($2-a4_min)/(a4_max-a4_min)):($1==5?(($2-a5_min)/(a5_max-a5_min)):$1))))) w l
1 6
2 1.5
3 85
4 2010
5 0.5
1 6
The following repo shows a spider chart with homogeneous scales. https://github.com/orey/gnuplot-radarchart
For your particular case, I would:
-Create functions that would normalize all data to fit in the diagram scale,
-Hide tge standard scale,
-Use arrows for the axis,
-Add points with labels for your particular scales and place them with your data functions.
I think inspiration can be found in the repo.

Resources