Gnuplot

Gnuplot is a free plotting tool that can be run from the command-line or invoked by third-party applications. Despite its name, the program is not related to the GNU project. The plotting commands and the input data are read from files or stdin. Plots can either be displayed inside a window (fig. 1) or exported in various image formats. The development of Gnuplot started already in 1986.

Gnuplot
Fig. 1: Gnuplot called from Fortran

Gnuplot can be accessed from Fortran in various ways:

Command-Line Execution

The Fortran example below writes the output data to a text file data.txt, and then uses the intrinsic Fortran 2008 routine execute_command_line() to call Gnuplot. Additional Fortran modules are not required.

! example.f90
program main
    implicit none
    character(len=*), parameter :: OUT_FILE = 'data.txt'   ! Output file.
    character(len=*), parameter :: PLT_FILE = 'plot.plt'   ! Gnuplot file.
    integer,          parameter :: N        = 10           ! Number of values.

    integer            :: i, fu
    real, dimension(N) :: x, y

    x(1) = 0.0
    y(1) = 0.0

    do i = 2, N
        x(i) = 0.1 * i
        y(i) = x(i)**2
    end do

    open (action='write', file=OUT_FILE, newunit=fu, status='replace')

    do i = 1, N
        write (fu, *) x(i), y(i)
    end do

    close (fu)

    call execute_command_line('gnuplot -p ' // PLT_FILE)
end program main

The command-line argument -p lets the opened window persist. The Gnuplot commands to display the plot are stored in file plot.plt:

# plot.plt
set term x11 font "-*-helvetica-medium-r-*-*-14-*-*-*-*-*-*-*"
set title "Fortran Example"
set nokey
set grid
set xlabel "x"
set ylabel "y"
m="data.txt"
plot m using 1:2 with linespoints

The file will then be read by Gnuplot to perform the actual plotting:

$ gfortran11 -o example example.f90
$ ./example

Unix Pipe

Instead of transfering the output data to Gnuplot by file, we can feed plotting options and data values directly by connecting both applications with Unix pipes. The output of the Fortran program will become the input of Gnuplot.

The following example shows how to plot a shaded height map based on Perlin noise with Gnuplot (fig. 2). The Fortran program will simply print the height map to stdout. If we set the dgrid3d option followed by the correct dimensions of the matrix, Gnuplot will map a non-grid input to the internal grid format automatically. We could then output single coordinates (x, y, z) line by line instead of the matrix as a whole. Obviously, this is a lot slower.

Gnuplot Perlin noise
Fig. 2: Perlin noise generated in Fortran and visualised by Gnuplot

The Fortran module perlin.f90 provides the basic Perlin noise implementation that is called by the example. Gnuplot will additionally interpolate the surface.

! matrix.f90
program main
    use :: perlin
    implicit none
    integer, parameter :: WIDTH  = 128
    integer, parameter :: HEIGHT = 64
    integer            :: x, y
    real               :: r
    real               :: matrix(WIDTH, HEIGHT)

    ! Set Perlin noise seed.
    call random_seed()
    call random_number(r)
    call perlin_noise_seed(int(r * 10**6))

    ! Set Gnuplot options.
    print '(a)', 'set nokey'                           ! Disable legend.
    print '(a)', 'set cbtics scale 0'                  ! Disable colourbar tics.
    print '(a)', 'set title "Perlin Noise in Fortran"' ! Set title.
    print '(a)', 'set pm3d interpolate 2, 2'           ! Draw 3d data as colour map.
    print '(a)', 'splot "-" matrix with pm3d'          ! Plot matrix data in 3d.

    ! Calculate matrix values and print them to stdout.
    do y = 1, HEIGHT
        do x = 1, WIDTH
            matrix(x, y) = perlin_noise(real(x * .1), real(y * .1), 0.4, 8) * 100
            write (*, '(f8.5, " ")', advance='no') matrix(x, y)
        end do
        write (*, *)
    end do
end program main

The example can be compiled and executed with:

$ gfortran11 -c perlin.f90
$ gfortran11 -o matrix matrix.f90 perlin.o
$ ./matrix | gnuplot -p

We do not need to run the example each time to display the plot. Instead, redirect the output to a file first, then pipe the file contents to Gnuplot:

$ ./matrix > data.txt
$ cat data.txt | gnuplot -p

Unix Process

On Linux and FreeBSD, Gnuplot can be executed as a sub-process, and connected to Fortran through a pipe stream, using the POSIX function popen(3). The required interface bindings to the necessary C functions are included in the fortran-unix library.

Gnuplot sub-process
Fig. 3: Calling Gnuplot through a subprocess on Unix

In this approach, we do not need to pipe the output of the Fortran program to Gnuplot from the shell. Instead, we open a pipe for writing, fork, invoke the shell, and execute Gnuplot in persistent mode, directly from Fortran, just by calling popen(3), fputs(3), and pclose(3).

! process.f90
program main
    use :: unix
    implicit none
    character(len=*), parameter :: GNUPLOT_BIN = 'gnuplot' ! Name/path of Gnuplot binary.
    integer,          parameter :: N           = 800       ! Number of values.
    real,             parameter :: XSTEP       = 0.1       ! Step size.

    integer :: i
    real    :: x(N), y(N)

    ! Generate plotting data.
    do i = 1, N
        x(i) = -30 + ((i - 1) * XSTEP)
        y(i) = sin(x(i) * 20) * atan(x(i))
    end do

    ! Plot the data as line chart.
    call plot(x, y, 'Gnuplot from Fortran')
contains
    subroutine plot(x, y, title)
        !! Opens pipe to Gnuplot, writes Gnuplots settings, and plots
        !! X, Y data as line chart in an X11 window.
        real,             intent(inout) :: x(:)  !! X values.
        real,             intent(inout) :: y(:)  !! Y values.
        character(len=*), intent(in)    :: title !! Plot and window title.
        character(len=80)               :: buffer
        integer                         :: i, rc
        type(c_ptr)                     :: ptr

        ! Create a pipe for writing, fork, invoke the shell, and run Gnuplot.
        ptr = c_popen(GNUPLOT_BIN //  c_null_char, 'w' // c_null_char)

        if (.not. c_associated(ptr)) then
            print '("Error: failed to open pipe to Gnuplot")'
            return
        end if

        ! Open persistent X11 window, set window title.
        call puts(ptr, 'set terminal x11 persist title "' // trim(title) // '"')

        call puts(ptr, 'set title "' // trim(title) // '"') ! Plot title.
        call puts(ptr, 'set grid')                          ! Enable grid.
        call puts(ptr, 'set nokey')                         ! Disable legend.
        call puts(ptr, 'plot "-" using 1:2 with lines')     ! Create line chart.

        ! Output X, Y data.
        do i = 1, size(x)
            write (buffer, '(2(f12.8))') x(i), y(i)
            call puts(ptr, buffer)
        end do

        ! End plot data.
        call puts(ptr, 'e')

        ! Close the pipe.
        rc = c_pclose(ptr)
    end subroutine plot

    subroutine puts(ptr, str)
        !! Writes string to pipe, appends new line and null termination.
        type(c_ptr),      intent(in) :: ptr
        character(len=*), intent(in) :: str
        integer                      :: rc

        rc = c_fputs(trim(str) // c_new_line // c_null_char, ptr)
    end subroutine puts
end program main

The example program has to be linked against the fortran-unix interface library:

$ gfortran11 -o process process.f90 libfortran-unix.a
$ ./process

Gnuplot will run persistently as an independent process, and not terminate with the Fortran program is was called from (fig. 3).

DriverDescription
domtermDomTerm terminal emulator with embedded SVG
dumbASCII art for anything that prints text
sixelgdSixel using libgd and TrueType fonts
sixeltekSixel output using bitmap graphics
tek40xxTektronix 4010 and others; most TEK emulators
tek410xTektronix 4106, 4107, 4109 and 420X terminals
vttekVT-like tek40xx terminal emulator
Table 1: Selection of Gnuplot’s terminal-based drivers

Terminal Output

One of the lesser known features of Gnuplot is the option to display plots directly in the terminal emulator by using one of the pseudo-graphics drivers. We can output a list of all supported drivers in the Gnuplot interpreter:

$ gnuplot
gnuplot> set term

Available terminal types:
       cairolatex  LaTeX picture environment using graphicx package and Cairo backend
           canvas  HTML Canvas object
              cgm  Computer Graphics Metafile
          context  ConTeXt with MetaFun (for PDF documents)
          domterm  DomTerm terminal emulator with embedded SVG
           dpu414  Seiko DPU-414 thermal printer [small medium large]
             dumb  ascii art for anything that prints text
[…]

Or, run echo "set term" | gnuplot in the terminal. The backends sixelgd and sixeltek require a Sixel-compatible terminal, such as xterm(1) or mlterm(1) on Unix (table 1). Terminal emulators that do not support the format will ignore any Sixel data. XTerm has to be run in VT340 mode to enable Sixel graphics:

$ xterm -ti vt340

Add the following lines to your ~/.Xdefaults to select the mode by default:

xterm*decTerminalID: vt340
xterm*numColorRegisters: 256

Or, start XTerm in Tektronix mode by running xterm -t instead. The environment variable TERM should be set to xterm.

Gnuplot sixel
Fig. 4: Plotting directly to mlterm(1) through the Gnuplot Sixel backend

The example program sixel.f90 prints the results of the intrinsic error function

erf(x)

and all necessary Gnuplot options to stdout. We can change the backend in parameter TERM.

! sixel.f90
program main
    implicit none
    character(len=*), parameter :: TERM   = 'sixeltek'
    character(len=*), parameter :: TITLE  = 'Error Function'
    character(len=*), parameter :: XLABEL = 'x'
    character(len=*), parameter :: YLABEL = 'erf(x)'
    integer,          parameter :: N      = 25

    real    :: x(N), y(N)
    integer :: i

    x(1) = 0.0
    y(1) = 0.0

    do i = 2, N
        x(i) = 0.1 * i
        y(i) = erf(x(i))
    end do

    print '("set term ", a)', TERM                    ! Set backend.
    print '("set nokey")'                             ! Disable legend.
    print '("set grid")'                              ! Enable grid.
    print '("set title ''", a, "''")', TITLE          ! Set plot title.
    print '("set xlabel ''", a, "''")', XLABEL        ! Set x axis label.
    print '("set ylabel ''", a, "''")', YLABEL        ! Set y axis label.
    print '("plot ''-'' using 1:2 with linespoints")' ! Plot following data as line graph.

    do i = 1, N
        print '(f12.5, ",", f12.5)', x(i), y(i)
    end do
end program main

Single quotes ' in the format specifiers have to be escaped as ''. Simply pipe the output of the program to Gnuplot. The result is shown immediately (fig. 4):

$ gfortran -o sixel sixel.f90
$ ./sixel | gnuplot

Optionally, we may save the Sixel image to file:

$ ./sixel | gnuplot > plot.six
$ cat plot.six

The library libsixel provides several encoders and decoders for the Sixel format that can be invoked from the command-line.

Fortran Libraries

A selection of Gnuplot libraries is available for Fortran:

Further Reading

References