Charts in 1 Line of Bash

There are some great charting packages out there, but for quick tests and playing around it’s nice to keep things simple. I wanted to do some basic Raspberry Pi charting and found that with the Gnuplot utility it took as little as 1 line of Bash to get the job done.

This blog shows some examples, probably the key points are:

  • Data can be piped to Gnuplot
  • Bash While-loops can be used to create real time/dynamic plots

Single Line Chart from a File

Gnuplot plot has a full scripting interface that allows users to create very complex presentations.

Gnuplot script can also be passed on the command line, with the options: -p (persist, spawn chart) and -e (execute command):

$ # Plot a file with 5 points, show file first
$ cat 5pnts.txt
2.7
3.5
4.1
3.9
5.6
$ # Plot the file as a line
$ gnuplot -p -e "plot '5pnts.txt' with lines title '5 Test Points' "

Multi-line Chart

The multi-line example is like the single line chart with the exception that the using statement is called for each plot line, 1:2 is the first line (x-axis column):(y-axis column) , and 1:3 is the second series line.

$ # Show the first 5 line of the data
$ head -n 5 data1.txt, Note: Gnuplot skips row starting with "#"
#t  Angle Error 
0.0	-14.7	3.6
1.0	8.6	    3.6
2.1	28.8	3.0
3.1	46.7	3.4
$
$ # plot, first column is x, next 2 columns are y series
$ gnuplot -p -e "plot 'data1.txt' \
  using 1:2 with lines title 'Angle','data1.txt' \
  using 1:3 with lines title 'Error'"

For-Loop Piped to a Line Chart

Rather than creating a text file, the output from a group of echo statements can be piped to Gnuplot. The ‘<cat’ definition is used for piped input.

$# Plot 10 random points
$ ( for i in `seq 1 10`; do echo "$RANDOM\n";  done )| \
  gnuplot -p -e "plot '<cat' with lines title '10 Random Pts'"

A more complex example would be to add a time on the X-axis and run a calculation at a sampled interval.

# Create a calc function
mycalc() {
  # Show the time and cpu idle time
  thetime=$(date +'%T')
  idle=$(top -n 1 | grep id | awk '{print $8}')
  echo "$thetime $idle\n"
}
# Run the calc function in the for-loop
# Define the y range, x scale as time
( for i in `seq 1 20`; do mycalc; sleep 1;  done )| \
  gnuplot -p -e "set yrange [50:100]; set xdata time; \
  set timefmt '%H:%M:%S';set format x '%H:%M:%S'; \
  plot '<cat' using 1:2 with lines title 'CPU Idle Time' "

Bar Charts from a File

For the bar chart example the plot options are defined in a variable. This example uses the iostat command to get the user and idle time usage. Awk is used to parse the 4th row and then get the individual values.

# Create a file with user and idle CPU usage
# Column: 1=bar position, 2=value, 3=label
iostat | awk '{if(NR==4) print "0 " $1 " user"}' > cpu0.dat
iostat | awk '{if(NR==4) print "1 " $3 " idle" }' >> cpu0.dat

# Define the options as a variable
options="set title 'CPU Diagnostics'; set boxwidth 0.5; \
  set style fill solid; "

# Plot the bars with labels, note: the label offset 
gnuplot -p -e "$options ;plot 'cpu0.dat' \
 using 1:2:xtic(3) with boxes notitle, \
 '' using 1:2:2 with labels font 'Helvetica,15' \
 offset 0.9,0.8  notitle" 

Bars with Dynamic Updates

A Bash while-loop can be used to dynamically get new data and pass it to Gnuplot.

Below is an example that refreshes a bar chart every 5 seconds. The refresh time is shown in the bar series title.

mycalc2() {
  # Show the time and user/cpu idle time
  # note: top gets instantaneous values, iostat uses averages
  top -n 1 | grep %Cpu | awk '{print "0 " $2 " user"}' > cpu0.dat
  top -n 1 | grep %Cpu | awk '{print "1 " $8 " idle"}' >> cpu0.dat
}
# define chart options
options="set title 'CPU Diagnostics'; set boxwidth 0.5; \
  set style fill solid; set yrange [0:100]; set xrange [-0.5:1.5]"

# create an infinite while loop to get data, and then plot
# note1: gnuplot needs a while loop to replot
# note2: use a big pause time in gnuplot or exiting will be tough 
( while :; do mycalc2; sleep 10;  done )| \
gnuplot -p -e "$options ; plot 'cpu0.dat' \
 using 1:2:xtic(3) with boxes \
 title \"$(date '+%T')\" ; while (1) { replot; pause 5 }" 

Summary

Using Gnuplot with the command line option allows for some quick and easy charting. For more detailed work try creating a Gnuplot script file.

Gnuplot Speedometer Gauge

Gnuplot is a great package that allows you to do charting from the command line.

Gnuplot has a wide range of plot types but unfortunately a speedometer gauge is not one of them.

This blog documents my notes in creating a dynamic gauge.

Some useful takeaways:

  • A gnuplot can be created with only graphic elements (no chart)
  • A named object can be re-positioned without redrawing the background
  • A gnuplot script can be called like a Bash or Python script with the first line being: #!/usr/bin/gnuplot

A Dynamic Gauge

The script gnuplot.gp is an example that refreshes a gauge chart every 5 seconds with the processor’s idle time. The idle time is obtained using the Linux top command (with some awk to get the 8th line item).

#!/usr/bin/gnuplot
#
# gnuplot.gp - speedometer dial with title as the description/value
#
set xrange [-1:1]
set yrange [0:1]
set angles degrees
set size ratio -1
# r1 = annulus outer radius, r2 = annulus inner radius
r1=1.0
r2=0.5
unset border; unset tics; unset key; unset raxis

set style fill solid noborder

# define a "needle" pointer as object 1
set object 1 circle at first 0,0 front size r1 arc [181:182]  fillcolor rgb 'black'

# define the gauge background
set object circle at first 0,0  size r1 arc [0:180]  fillcolor rgb 'green'
set object circle at first 0,0  size r1 arc [0:10]  fillcolor rgb 'red'
set object circle at first 0,0  size r1 arc [10:20]  fillcolor rgb 'yellow'
set object circle at first 0,0 front size r2 arc [0:180] fillcolor rgb 'black'

# plot the static background 
plot -10 notitle

# Define a partial heading  
variable = "Idle Time\n"
unit = "%"

# Refresh the plot with a new dial setting
while (1) {
  # Get the idle time using the top utility
  idle = system("top -n 1 | grep Cpu | awk '{print $8}'")
  # scale the value from 0-100 to 180-0 (Note: arc starts on the right)
  value = (100 - real(idle)) * 1.8
  # show the value in the title
  set title sprintf('%s %s %s', variable, idle, unit)  font "Ariel,28"
  # reposition the value in the gauge
  set object 1 circle at first 0,0 front size r1 arc [value:(value+2)]  fillcolor rgb 'black'
  replot
  pause 5 
}

The script can be run either from gnuplot or like a Bash script:

$ # run script from gnuplot
$ gnuplot -c gauge.gp
Use Control-C to exit ...
^C
$ # make script executable
$ chmod +x gauge.gp
$ # run script like a Bash script
$ ./gauge.gp
Use Control-C to exit ...
^C

Next Steps…

From here the next steps could be to add command line arguments to make the script more generic, such as:

  • pass the calculation to run (eg. pass the bash top command)
  • pass the scaling, title and units