Prof. Dr.-Ing. Oliver Radfelder
Informatik / Wirtschaftsinformatik
Hochschule Bremerhaven
gnuplot
Mit gnuplot lassen sich Daten gut automatisiert visualisieren. Erstellen Sie zunächst eine Textdatei mit der Endung .gp, z.B.: quadrat.gp
set terminal pngcairo size 2048,1024 font 'Verdana,32'
set output 'quadrat.png'

plot 'quadrat.dat' using 1:2 \
  with linespoints \
  title 'quadratische Entwicklung'
  

Damit beschreiben Sie, dass eine png-Datei erzeugt wird (set terminal ...) und dass die Datei quadrat.png (set output ...) heißen soll. Mit dem plot-Befehl wird der endgültige Graph gezeichnet, indem aus der Datei quadrat.dat die Spalte 1 mit der Spalte 2 (using ...) korreliert wird. Nach dem Schlüsselwort with werden Formatangaben für den jeweiligen Graphen angegeben.

Erstellen Sie nun zusätzlich eine Datendatei mit dem Namen quadrat.dat, in der zeilenweise, jeweils durch Leerzeichen getrennte Zahlenpaare stehen, mit dem folgenden Code:

for i in $(seq 10); do
  echo "$i $((i*i))"
done > quadrat.dat

Rufen Sie das Programm gnuplot mit der Programmdatei als Argument auf:

gnuplot quadrat.gp

Dabei entsteht eine Datei mit dem Namen quadrat.png im aktuellen Verzeichnis, die sich wie üblich in das Webverzeichnis kopieren lässt.

cp quadrat.png /var/www/html/$USER

Die meisten Einstellungen, um eine ansprechendere Grafik zu erstellen, lassen sich durch set Anweisungen vornehmen:

set title 'Entwicklungen'
set xrange [0:10]
set yrange [0:100]

Es lassen sich auch mehrere Kurven in einer Grafik anzeigen. Erstellen Sie dazu eine Textdatei mit nun drei Spalten:

for i in $(seq 100); do
  echo "$i $i $((i*i))"
done > kurven.dat

Erstellen Sie wieder eine entsprechende kurven.gp Datei, in der die einzelnen Kurven hinter dem plot-Befehl durch Kommata getrennt aufgezählt werden:

set terminal pngcairo size 2048,1024 font 'Verdana,32'
set output 'kurven.png'

set title 'Entwicklungen'
set xrange [0:100]
set yrange [0:100]


plot 'kurven.dat' using 1:2 \
      with linespoints \
           title 'linear', \
     'kurven.dat' using 1:3 \
      with linespoints \
           title 'quadratisch'

gnuplot und bc

Wie Sie möglicherweise bereits festgestellt haben, lässt sich in der bash lediglich mit Ganzzahlen rechnen. Zudem ist in der bash ebenso wie in vielen Programmiersprachen der Standarddatentyp für Zahlen stark begrenzt. Mit solchen Datentypen lässt sich die Fakultät beispielsweise nur für sehr wenige Zahlen berechnen:

for i in $(seq 21); do
  x=$i
  for j in $(seq $((i-1)) -1 1); do
    x=$((x*(i-j)))
  done
  echo "$i  $x"
done

Wie man sieht, entsteht schon bei der Fakultät von 21 ein Überlauf - für Experimente in Statistik und Denkfallen wenig geeignet. Mit Java und dem Datentyp long (64bit) kommt man übrigens zum gleichen Ergebnis.

Mit dem Programm bc, dem basic calculator lassen sich hingegen beliebig große Zahlen berechnen. Dazu muss lediglich die vollständige Rechnung als Zeichenkette erzeugbar sein. Diese wird dann in das Programm bc hineingepipet:

x=365
for i in $(seq 364); do 
  x+="*$((365-i))"
done
echo "$x"| bc -l 

Die Fakultät von 365 ist immerhin eine 779-stellige Zahl...

Der Schalter -l lädt die erweiterte Mathebibliothek und sollte eigentlich immer mit angegeben werden.

Geburtstagsparadoxon

Das Geburtstagsparadoxon kommt in vielen einführenden Mathevorlesungen für Informatik vor. Das konkrete Paradoxon lesen Sie bitte im Web nach (bspw. bei Wikipedia). Dort finden Sie mit der Formel von Laplace beschrieben die Wahrscheinlichkeit , dass bei n Personen in einem Raum zwei Personen am gleichen Tag Geburtstag haben.

`1- (365 * 364 * ... * (365 - (n-1)))/(365^n)`

So entstehen sehr große Zahlen. Mit bc lassen sich nun auch für solche Fälle Rechnungen angeben:

n=23
a="1"
for i in $(seq 365 -1 $((365-(n-1))) ); do
  a+="*$i"
done
b="1"
for j in $(seq $n); do
  b+="*365"
done
echo "1-($a)/($b)" | bc -l

Nun lässt sich ganz einfach mit einer Schleife für die Werte von 1 bis 100 für n eine Textdatei mit Wahrscheinlichkeiten für das Zusammentreffen von zwei Geburtstagen an einem Tag erzeugen.

for n in $(seq 100); do
  a="1"
  for i in $(seq 365 -1 $((365-(n-1))) ); do
    a+="*$i"
  done
  b="1"
  for j in $(seq $n); do
    b+="*365"
  done
  p=$(echo "1-($a)/($b)" | bc -l)
  echo "$n  $p"
done > geburtstag.dat

Mit dem folgenden Code für gnuplot lässt sich nun ein visuell recht ansprechender Plot realisieren:

set terminal pngcairo size 1920,890 font 'Verdana,32'
set output 'geburtstag.png'

set title 'Geburtstagsparadoxon'
set yrange [0:1]
set xrange [0:100]
set tics scale 0

set style line 80 \
          linetype 1 \
          linecolor rgb 'black' \
          linewidth 4
set border 3 back linestyle 80 

set style line 81 \
          linetype 1 \
          linecolor rgb '#808080' \
          linewidth 2
set grid back linestyle 81

set key right bottom
set xlabel 'Personen im Raum'
set ylabel 'Wahrscheinlichkeit'

set style line 82 \
          linetype 1 \
          linecolor rgb '#A00000' \
          linewidth 3 \
          pointtype 7 \
          pointsize 2

plot 'geburtstag.dat' using 1:2 \
     with linespoints \
     linestyle 82 \
     title 'gleicher Geburtstag'

So lässt sich dann mit bash, gnuplot und bc die folgende Grafik erzeugen:

gnuplot geburtstag.gp
cp geburtstag.png /var/www/html/$USER/
paradoxon.png

Andere Beispiele
gnuplotsinus.png
set terminal pngcairo size 2048,1024 font 'Verdana,32'
set output 'sinus.png'
set grid ytics lt 1 lw 4 lc rgb "#888888"
unset xtics
unset border
set tics scale 0
set sample 3000

set style line 81 \
      linetype 1 \
      linecolor rgb '#00A000' \
      linewidth 7 
       
set style line 82 \
      linetype 1 \
      linecolor rgb '#0000A0' \
      linewidth 7 

plot 'sinus.dat' using 1:2 \
        with lines \
        linestyle 81 \
        title 'sinus' \
        smooth csplines, \
     'sinus.dat' using 1:3 \
        with lines \
        linestyle 82 \
        title 'cosinus' \
        smooth csplines
  

Oft benötigt man Zeitreihen - das heisst, es gibt eine Datei, in der die erste Spalte aus einer Datums- oder Zeitangabe besteht und eine weitere Spalte, in der konkrete Werte für den Tag oder Zeitpunkt stehen:

2019-10-27 37978
2019-10-28 25145
2019-10-29 43604
2019-10-30 43300
2019-10-31 48398
2019-11-01 47850
2019-11-02 51853
2019-11-03 51459
2019-11-04 43704
2019-11-05 23221

Damit Gunplot Zeitangaben erkennt, müssen noch Lese- und Ausgabeformat angegeben werden:

set terminal pngcairo  size 1280,600 font 'sans,12' 
set out '/var/www/html/oradfelder/mini.png'
set title "Zeitreihe"
set xdata time
set timefmt '%Y-%m-%d'
#set xrange [ '2019-07-01': ]
set format x '%d.%m.%Y' 
plot 'mini.dat' using 1:2  title 'Sicherung'  with  lines  \
     linewidth 4  linecolor rgb '#204080'
mini.png

Falls man beispielsweise das aktuelle Datum benötigt, lässt sich mit system auf eine Shell zugreifen:

set title "".system("date +%T")

Sonstiges:

# ein Rechteck vordefinieren, das beim Plot gezeichnet wird
set object 1 rect from graph 0, graph 0 to graph 0.5, graph 0.1 back
set object 1 rect fillcolor rgb "gray" fillstyle solid 1.0  noborder

# einen Pfeil zeichnen
set style arrow 1 head noborder size screen 0.025,20,45 lw 7
set arrow from graph 0,0 to 5,-0.0 as 1


So in etwa erzeuge ich regelmäßig Visualisierungen von Daten, die mit vmstat -SM -t 1 erzeugt werden:

set terminal pngcairo size 2048,1024 \
    background rgb '#eeeeee' font 'Verdana,32'
set output '/var/www/html/oradfelder/hopper-monitoring.png'

set title "hopper : ".system("date +'%F %T'")
set yrange [0:100]
set xdata time
set format x '%H' 
set timefmt '%H:%M:%S'
set xrange ['00:00:00':'23:59:59']

set style line 12 lc rgb '#808080' lt 1 lw 1
set grid back ls 12
set style line 11 lc rgb '#808080' lt 1
set border 3 back ls 11
set ytics nomirror
set xtics nomirror

plot 'today.dat' using 19:(100-$15) title 'hopper cpu'  with  lines  \
     linewidth 4  linecolor rgb '#996633'

Nicht selten kommt es vor, dass es bei der Analyse eines Systems um Erfolg oder Misserfolg geht. Im folgenden Beispiel gilt als Erfolg, wenn das Kopieren von 10.000 Dateien mit scp nicht abbricht, die Logdatei also aus *genau* 10.000 Zeilen besteht. Weniger Zeilen werden als Misserfolg gewertet. In der Darstellung sollte sehr schnell auf einen Blick deutlich werden, wie häufig ein Misserfolg auftritt.

set terminal svg enhanced font 'Helvetica,16' size 1200,280
set out '/var/www/html/oradfelder/hoppercheck.svg'

set title font ',20'
set lmargin 5

set title "scp von 10.000 Dateien von 3b.hsbhv-inf.de nach hopper ".system("date +'%F %H:%M'")
set xdata time
set format x '%d.%m. %H:%M' 
set timefmt '%Y-%m-%d-%H:%M:%S'

set xrange [ "2022-10-01-10:00:00" : "2022-10-04-24:00:00"]
set yrange [ 0:14000 ]

# xtics schwarz und um 30 Grad gedreht
set tics textcolor rgb "black"
set xtics rotate by 30 right

# setze xtics an vordefinierten Punkten
set xtics ("2022-10-01-12:00:00","2022-10-02-12:00:00","2022-10-03-12:00:00","2022-10-04-12:00:00")

# rechte Begrenzungslinie ohne Beschriftung
set arrow from "2022-10-04-24:00:00", 0 to "2022-10-04-24:00:00", 14000 lw 1 lc rgb 'dark-gray' nohead


unset ytics
set key bottom right

# Eine vertikale, gepunktete Hilfslinie zum aktuellen Zeitpunkt:
now=system("date +'%F-%T'")
set arrow from now, 0 to now, 14000 lw 1 lc rgb 'black' dt 3 nohead

# drei Hilfslinien und zwei Datensätze
plot 14000 lc rgb 'gray' notitle, \
     10000 lc rgb 'black' dt 3 notitle, \
      8000 lc rgb 'black' dt 3 notitle, \
     'success.dat'   using 1:2  title 'Erfolg'  with points pt 7 ps 0.5 lc rgb 'sea-green', \
     'nosuccess.dat' using 1:2  title 'kein Erfolg'  with points pt 7 ps 0.5 lc rgb 'dark-red'

Und als Wochenüberblick - die Datumsangaben, Rechtecke und die xtics sind natürlich mit einem Skript erzeugt. In einer Schleifen werden die vergangenen 7 Tage mit date berechnet und mit sed an die entsprechenden Stellen injiziert. Bei dem Wochenüberblick war es interessant, zu erfahren, ob es ein Muster von Ausfällen zwischen 12 und 15 Uhr gibt - daher die grauen Bereiche in der Zeit.

set terminal svg enhanced font 'Helvetica,12' size 1600,180
set out '/var/www/html/oradfelder/hoppercheck.svg'

set title font ',20'

set title "scp von 10.000 Dateien von 3b.hsbhv-inf.de nach hopper alle 10 Minuten - 17:16:25 (09-12 Uhr markiert)"
set xdata time
set format x '' 
set timefmt '%Y-%m-%d-%H:%M:%S'
today=system("date +'%F'")
now=system("date +'%F-%T'")

set xrange [ "2022-09-28-00:00:00" : "2022-10-04-24:00:00"]
set yrange [ 0:14000 ]

set xtics textcolor rgb "black"
set xtics ("2022-09-29-00:00:00","2022-09-30-00:00:00","2022-10-01-00:00:00","2022-10-02-00:00:00","2022-10-03-00:00:00","2022-10-04-00:00:00","2022-10-05-00:00:00")
set arrow  from "2022-10-04-24:00:00", 0 to "2022-10-04-24:00:00", 14000 lw 1 lc rgb '#808080' nohead

unset ytics
set xtics rotate by 30 right
set key bottom left
set arrow  from now, 0 to now, 14000 lw 1 lc rgb 'black' dt 3 nohead

set style rect  fc rgb 'gold' fs solid 0.1 noborder
set label at "2022-09-28-00:00:00",12000 "   Mittwoch 28.09.2022" front left
set label at "2022-09-29-00:00:00",12000 "   Donnerstag 29.09.2022" front left
set label at "2022-09-30-00:00:00",12000 "   Freitag 30.09.2022" front left
set obj rectangle from "2022-10-01-00:00:00",14000 to "2022-10-01-24:00:00", 0 
set label at "2022-10-01-00:00:00",12000 "   Samstag 01.10.2022" front left
set obj rectangle from "2022-10-02-00:00:00",14000 to "2022-10-02-24:00:00", 0 
set label at "2022-10-02-00:00:00",12000 "   Sonntag 02.10.2022" front left
set label at "2022-10-03-00:00:00",12000 "   Montag 03.10.2022" front left
set label at "2022-10-04-00:00:00",12000 "   Dienstag 04.10.2022" front left

set style rect fc lt -1 fs solid 0.03 noborder
set obj rectangle from "2022-09-28-09:00:00",14000 to "2022-09-28-12:00:00", 0
set obj rectangle from "2022-09-29-09:00:00",14000 to "2022-09-29-12:00:00", 0
set obj rectangle from "2022-09-30-09:00:00",14000 to "2022-09-30-12:00:00", 0
set obj rectangle from "2022-10-01-09:00:00",14000 to "2022-10-01-12:00:00", 0
set obj rectangle from "2022-10-02-09:00:00",14000 to "2022-10-02-12:00:00", 0
set obj rectangle from "2022-10-03-09:00:00",14000 to "2022-10-03-12:00:00", 0
set obj rectangle from "2022-10-04-09:00:00",14000 to "2022-10-04-12:00:00", 0
set obj rectangle from "2022-10-05-09:00:00",14000 to "2022-10-05-12:00:00", 0


plot 14000 lc rgb '#808080' notitle, \
     10000 lc rgb 'black' dt 3 notitle, \
      8000 lc rgb 'black'  dt 3 notitle, \
     'success.dat'   using 1:2  title 'Erfolg'  with points pt 7 ps 0.5 lc rgb 'sea-green', \
     'nosuccess.dat' using 1:2  title 'kein Erfolg'  with points pt 7 ps 0.5 lc rgb 'dark-red'

Das Bash-Skript, mit dem das Gnuplot-Skript auf Basis eines Templates erzeugt wird:

#!/usr/bin/env bash
from=$(date -d '6 day ago' +%F-00:00:00)
to=$(date +'%F-24:00:00')
XTICS=
RECTANGLES=
SUNDAY=

# 7 Tage rückwärts
for i in 6 5 4 3 2 1 0 -1; do
  day=$(date -d "$i day ago" +%F-00:00:00)
  XTICS+=",'$day'"
  daynine=$(date -d "$i day ago" +%F-09:00:00)
  daytwelve=$(date -d "$i day ago" +%F-12:00:00)
  dayzero=$(date -d "$i day ago" +%F-00:00:00)
  daytwentyfour=$(date -d "$i day ago" +%F-24:00:00)
  RECTANGLES+="set obj rectangle from '$daynine',14000 to '$daytwelve', 0\n"
  weekday=$(LC_ALL=de_DE.utf8 date -d "$i day ago" +%A)
  if [ "$weekday" = Sonntag ] || [ "$weekday" = Samstag ]; then
    SUNDAY+="set obj rectangle from '$dayzero',14000 to '$daytwentyfour', 0 \n"
  fi
  if [ "$i" != -1 ]; then
    SUNDAY+="set label at '$dayzero',12000 '   $(date -d "$i day ago" +"$weekday %d.%m.%Y")' front left\n"
  fi
done
# erstes Komma weg
XTICS=${XTICS:1}

# Platzhalter mit sed ersetzen
sed -e 's:___OUT___:/var/www/html/oradfelder/hoppercheck.svg:g' \
  -e "s/___VON___/$from/g" \
  -e "s/___BIS___/$to/g" \
  -e "s/___XTICS___/$XTICS/g" \
  -e "s/#___RECTANGLES___/$RECTANGLES/g" \
  -e "s/#___SUNDAY___/$SUNDAY/g" \
  template.gp > hopper.gp

sed -i -e "s/___TITLE___/scp von 10.000 Dateien von 3b.hsbhv-inf.de nach hopper alle 10 Minuten - $(date +%T) (09-12 Uhr markiert)/g" hopper.gp

# Daten separieren
cp ~l-oradfelder/hoppercheck.log .
echo 2022-01-01-09:00:00 10001 > success.dat
echo 2022-01-01-09:00:00 10000 > nosuccess.dat
grep '10001' hoppercheck.log |while read a b c d e f; do echo "$a-$b $e"; done >> success.dat
grep -v '10001' hoppercheck.log |sed 's/$/8000/g'|while read a b c d e f g; do echo "$a-$b $g"; done >> nosuccess.dat
gnuplot hopper.gp

Und das Template:

set terminal svg enhanced font 'Helvetica,12' size 1600,180
set out '___OUT___'

set title font ',20'

set title "___TITLE___"
set xdata time
set format x '' 
set timefmt '%Y-%m-%d-%H:%M:%S'
today=system("date +'%F'")
now=system("date +'%F-%T'")

set xrange [ "___VON___" : "___BIS___"]
set yrange [ 0:14000 ]

set xtics textcolor rgb "black"
set xtics (___XTICS___)
set arrow  from "___BIS___", 0 to "___BIS___", 14000 lw 1 lc rgb '#808080' nohead

unset ytics
set xtics rotate by 30 right
set key bottom left
set arrow  from now, 0 to now, 14000 lw 1 lc rgb 'black' dt 3 nohead

set style rect fc rgb 'gold' fs solid 0.1 noborder
#___SUNDAY___
set style rect fc lt -1 fs solid 0.03 noborder
#___RECTANGLES___

plot 14000 lc rgb '#808080' notitle, \
     10000 lc rgb 'black' dt 3 notitle, \
      8000 lc rgb 'black'  dt 3 notitle, \
     'success.dat'   using 1:2  title 'Erfolg'  with points pt 7 ps 0.5 lc rgb 'sea-green', \
     'nosuccess.dat' using 1:2  title 'kein Erfolg'  with points pt 7 ps 0.5 lc rgb 'dark-red'

Mit wachsenden Bedürfnissen an ästhetische Darstellungen werden natürlich auch die gnuplot-Dateien größer und unübersichtlicher. Sobald der eigene Stil gefunden ist, lassen sich einige veränderte Grundeinstellungen im Homeverzeichnis im .gnuplot-File ablegen:

set macros
# add default line colors
set style line 1 lc rgb '#dd181f' lt 1 lw 3 pt 7   # red
set style line 2 lc rgb '#006600' lt 1 lw 3 pt 7   # green
set style line 3 lc rgb '#0060ad' lt 1 lw 3 pt 7   # blue
set style line 4 lc rgb '#ffff00' lt 1 lw 3 pt 7   # yellow
set style line 5 lc rgb '#ff8c00' lt 1 lw 3 pt 7   # orange
set style line 6 lc rgb '#da70d6' lt 1 lw 3 pt 7   # purple
set style line 7 lc rgb '#800000' lt 1 lw 3 pt 7   # maroon
set style line 8 lc rgb '#191970' lt 1 lw 3 pt 7   # midnightblue
set style line 9 lc rgb '#808000' lt 1 lw 3 pt 7   # olive

set style line 11 lc rgb '#dd181f' lt 1 lw 1 pt 7 ps 0.2   # red
set style line 12 lc rgb '#006600' lt 1 lw 1 pt 7 ps 0.2   # green
set style line 13 lc rgb '#0060ad' lt 1 lw 1 pt 7 ps 0.2   # blue
set style line 14 lc rgb '#ffff00' lt 1 lw 1 pt 7 ps 0.2   # yellow
set style line 15 lc rgb '#ff8c00' lt 1 lw 1 pt 7 ps 0.2   # orange
set style line 16 lc rgb '#da70d6' lt 1 lw 1 pt 7 ps 0.2   # purple
set style line 17 lc rgb '#800000' lt 1 lw 1 pt 7 ps 0.2   # maroon
set style line 18 lc rgb '#191970' lt 1 lw 1 pt 7 ps 0.2   # midnightblue
set style line 19 lc rgb '#808000' lt 1 lw 1 pt 7 ps 0.2   # olive
# add macros to select a terminal
PNG = "set terminal pngcairo size 1024,600 background rgb '#eeeeee' enhanced font 'Verdana,14'"
PNGWIDE = "set terminal pngcairo size 2048,600 background rgb '#eeeeee' enhanced font 'Verdana,14'"

set style line 72 lc rgb '#808080' lt 1 lw 1
set grid back ls 72
set style line 71 lc rgb '#808080' lt 1
set border 3 back ls 71
set ytics nomirror
set xtics nomirror

Dann lassen sich mit deutlich weniger Zeilen schnell Visualisierungen erzeugen:

@PNG
set output "/var/www/html/docker-oradfelder-web/tomcat-minute.png"
set xdata time
set timefmt '%Y-%m-%dT%H:%M:%S'
set format x '%H:%M'
from="".system("date -d '3 minute ago' +%FT%H:%M:00")
to="".system("date +%FT%H:%M:00")
set title "".system("date +'%F %H:%M'")

set xrange [ from:to ]
set yrange [0:20000]
plot "tomcat-all-rps-clean.txt" using 1:2 title \
 'tomcat all rps' with linespoints ls 1

    

Weiterführendes

gnuplot Homepage

Examples

Attractive Plots

Youtube Tutorial