Calling gnuplot from python

Question:

I’ve a python script that after some computing will generate two data files formatted as gnuplot input.

How do I ‘call’ gnuplot from python ?

I want to send the following python string as input to gnuplot:

"plot '%s' with lines, '%s' with points;" % (eout,nout)

where ‘eout‘ and ‘nout‘ are the two filenames.

PS:
I prefer not to use additional python modules (eg. gnuplot-py), only the standard API.

Thank You

Asked By: Andrei Ciobanu

||

Answers:

The subprocess module lets you call other programs:

import subprocess
plot = subprocess.Popen(['gnuplot'], stdin=subprocess.PIPE)
plot.communicate("plot '%s' with lines, '%s' with points;" % (eout,nout))
Answered By: sth

A simple approach might be to just write a third file containing your gnuplot commands and then tell Python to execute gnuplot with that file. Say you write

"plot '%s' with lines, '%s' with points;" % (eout,nout)

to a file called tmp.gp. Then you can use

from os import system, remove
system('gnuplot -persist tmp.gp')
remove('tmp.gp')
Answered By: Neal

Subprocess is explained very clearly on Doug Hellemann’s
Python Module of the Week

This works well:

import subprocess
proc = subprocess.Popen(['gnuplot','-p'], 
                        shell=True,
                        stdin=subprocess.PIPE,
                        )
proc.stdin.write('set xrange [0:10]; set yrange [-2:2]n')
proc.stdin.write('plot sin(x)n')
proc.stdin.write('quitn') #close the gnuplot window
proc.stdin.flush()

One could also use ‘communicate’ but the plot window closes immediately unless a gnuplot pause command is used

proc.communicate("""
set xrange [0:10]; set yrange [-2:2]
plot sin(x)
pause 4
""")
Answered By: tommcd

I was trying to do something similar, but additionally I wanted to feed data from within python and output the graph file as a variable (so neither the data nor the graph are actual files). This is what I came up with:

#! /usr/bin/env python

import subprocess
from sys import stdout, stderr
from os import linesep as nl

def gnuplot_ExecuteCommands(commands, data):
    args = ["gnuplot", "-e", (";".join([str(c) for c in commands]))]
    program = subprocess.Popen(
        args, 
        stdin=subprocess.PIPE, 
        stdout=subprocess.PIPE, 
        stderr=subprocess.PIPE, 
        )
    for line in data:
        program.stdin.write(str(line)+nl)
    return program

def gnuplot_GifTest():
    commands = [
        "set datafile separator ','",
        "set terminal gif",
        "set output",
        "plot '-' using 1:2 with linespoints, '' using 1:2 with linespoints",
        ]
    data = [
        "1,1",
        "2,2",
        "3,5",
        "4,2",
        "5,1",
        "e",
        "1,5",
        "2,4",
        "3,1",
        "4,4",
        "5,5",
        "e",
        ]
    return (commands, data)

if __name__=="__main__":
    (commands, data) = gnuplot_GifTest()
    plotProg = gnuplot_ExecuteCommands(commands, data)
    (out, err) = (plotProg.stdout, plotProg.stderr)
    stdout.write(out.read())

That script dumps the graph to stdout as the last step in main. The equivalent command line (where the graph is piped to ‘out.gif’) would be:

gnuplot -e "set datafile separator ','; set terminal gif; set output; plot '-' using 1:2 with linespoints, '' using 1:2 with linespoints" > out.gif
1,1
2,2
3,5
4,2
5,1
e
1,5
2,4
3,1
4,4
5,5
e
Answered By: Ben

Here’s a class that provides an interface to wgnuplot.exe:

from ctypes import *
import time
import sys
import os

#
# some win32 constants
#
WM_CHAR     = 0X0102
WM_CLOSE    = 16
SW_HIDE     = 0
STARTF_USESHOWWINDOW = 1

WORD    = c_ushort
DWORD   = c_ulong
LPBYTE  = POINTER(c_ubyte)
LPTSTR  = POINTER(c_char) 
HANDLE  = c_void_p

class STARTUPINFO(Structure):
    _fields_ = [("cb",DWORD),
        ("lpReserved",LPTSTR), 
        ("lpDesktop", LPTSTR),
        ("lpTitle", LPTSTR),
        ("dwX", DWORD),
        ("dwY", DWORD),
        ("dwXSize", DWORD),
        ("dwYSize", DWORD),
        ("dwXCountChars", DWORD),
        ("dwYCountChars", DWORD),
        ("dwFillAttribute", DWORD),
        ("dwFlags", DWORD),
        ("wShowWindow", WORD),
        ("cbReserved2", WORD),
        ("lpReserved2", LPBYTE),
        ("hStdInput", HANDLE),
        ("hStdOutput", HANDLE),
        ("hStdError", HANDLE),]

class PROCESS_INFORMATION(Structure):
    _fields_ = [("hProcess", HANDLE),
        ("hThread", HANDLE),
        ("dwProcessId", DWORD),
        ("dwThreadId", DWORD),]

#
# Gnuplot
#
class Gnuplot:
    #
    # __init__
    #
    def __init__(self, path_to_exe):
        # open gnuplot
        self.launch(path_to_exe)
        # wait till it's ready
        if(windll.user32.WaitForInputIdle(self.hProcess, 1000)):
            print "Error: Gnuplot timeout!"
            sys.exit(1)
        # get window handles
        self.hwndParent = windll.user32.FindWindowA(None, 'gnuplot')
        self.hwndText = windll.user32.FindWindowExA(self.hwndParent, None, 'wgnuplot_text', None)



    #
    # __del__
    #
    def __del__(self):
        windll.kernel32.CloseHandle(self.hProcess);
        windll.kernel32.CloseHandle(self.hThread);
        windll.user32.PostMessageA(self.hwndParent, WM_CLOSE, 0, 0)


    #
    # launch
    #
    def launch(self, path_to_exe):
        startupinfo = STARTUPINFO()
        process_information = PROCESS_INFORMATION()

        startupinfo.dwFlags = STARTF_USESHOWWINDOW
        startupinfo.wShowWindow = SW_HIDE

        if windll.kernel32.CreateProcessA(path_to_exe, None, None, None, False, 0, None, None, byref(startupinfo), byref(process_information)):
            self.hProcess = process_information.hProcess
            self.hThread = process_information.hThread
        else:
            print "Error: Create Process - Error code: ", windll.kernel32.GetLastError()
            sys.exit(1)



    #
    # execute
    #
    def execute(self, script, file_path):
        # make sure file doesn't exist
        try: os.unlink(file_path)
        except: pass

        # send script to gnuplot window
        for c in script: windll.user32.PostMessageA(self.hwndText, WM_CHAR, ord(c), 1L)

        # wait till gnuplot generates the chart
        while( not (os.path.exists(file_path) and (os.path.getsize(file_path) > 0))): time.sleep(0.01)
Answered By: Dumitru Pletosu

I am a bit late, but since it took me some time to make it work, maybe it’s worth putting a note. The programs are working with Python 3.3.2 on Windows.

Notice that bytes are used everywhere, not strings (e.g. b”plot x”, not simply “plot x”), but in case it’s a problem, simply do something like:

"plot x".encode("ascii")

First solution: use communicate to send everything, and close when it’s done. One must not forget pause, or the window is closed at once. However, it’s not a problem if gnuplot is used to store images in files.

from subprocess import *
path = "C:\app\gnuplot\bin\gnuplot"
p = Popen([path], stdin=PIPE, stdout=PIPE)
p.communicate(b"splot x*ynpause 4n")

Second solution: send commands one after another, using stdin.write(…). But, don’t forget flush! (this is what I didn’t get right at first) And use terminate to close the connection and gnuplot when the job is done.

from subprocess import *
path = "C:\app\gnuplot\bin\gnuplot"
p = Popen([path], stdin=PIPE, stdout=PIPE)

p.stdin.write(b"splot x*yn")
p.stdin.flush()
...
p.stdin.write(b"plot x,x*xn")
p.stdin.flush()
...
p.terminate()
Answered By: user1220978

I went with Ben’s suggestion as I was computing charts from a celery job and found that it would lockup when reading from stdout. I redesigned it like so using StringIO to create the file destined for stdin and subprocess.communicate to get the result immediately via stdout, no read required.


from subprocess import Popen, PIPE
from StringIO import StringIO                                            
from os import linesep as nl

def gnuplot(commands, data):                                                    
    """ drive gnuplot, expects lists, returns stdout as string """              

    dfile = StringIO()                                                          
    for line in data:                                                           
        dfile.write(str(line) + nl)                                             

    args = ["gnuplot", "-e", (";".join([str(c) for c in commands]))]            
    p = Popen(args, stdin=PIPE, stdout=PIPE, stderr=PIPE)                       

    dfile.seek(0)                                                               
    return p.communicate(dfile.read())[0]   

def gnuplot_GifTest():
    commands = [
        "set datafile separator ','",
        "set terminal gif",
        "set output",
        "plot '-' using 1:2 with linespoints, '' using 1:2 with linespoints",
        ]
    data = [
        "1,1",
        "2,2",
        "3,5",
        "4,2",
        "5,1",
        "e",
        "1,5",
        "2,4",
        "3,1",
        "4,4",
        "5,5",
        "e",
        ]
    return (commands, data)

if __name__=="__main__":
    (commands, data) = gnuplot_GifTest()
    print gnuplot(commands, data)
Answered By: ppetraki

Here is another example which extends some of the previous answers. This solution requires Gnuplot 5.1 because it uses datablocks. For more information on datablocks, execute help datablocks in gnuplot.
The problem with some of the previous approaches is that plot '-' instantly consumes the data that immediately follows the plot command. It is not possible to reuse the same data in a subsequent plot command. datablocks can be used to alleviate this issue. Using datablocks we can mimic multiple datafiles. For instance, you may want to plot a graph using data from two data files, e.g. plot "myData.dat" using 1:2 with linespoints, '' using 1:3 with linespoints, "myData2.dat" using 1:2 with linespoints. We could feed this data directly to gnuplot without the need to create actual data files.

import sys, subprocess
from os import linesep as nl
from subprocess import Popen, PIPE


def gnuplot(commands, data):                                                    
  """ drive gnuplot, expects lists, returns stdout as string """  
  script= nl.join(data)+nl.join(commands)+nl
  print script
  args = ["gnuplot", "-p"]
  p = Popen(args, shell=False, stdin=PIPE)                       
  return p.communicate(script)[0]  

def buildGraph():
  commands = [
      "set datafile separator ','",
      "plot '$data1' using 1:2 with linespoints, '' using 1:3 with linespoints, '$data2' using 1:2 with linespoints",
      ]
  data = [
      "$data1 << EOD",
      "1,30,12",
      "2,40,15",
      "3,35,20",
      "4,60,21",
      "5,50,30",
      "EOD",
      "$data2 << EOD",
      "1,20",
      "2,40",
      "3,40",
      "4,50",
      "5,60",
      "EOD",
      ]

  return (commands, data)  


def main(args):
  (commands, data) = buildGraph()
  print gnuplot(commands, data)


if __name__ == "__main__":
   main(sys.argv[1:])

This method is a bit more versatile than plot '-' as it makes it easier to reuse the same data multiple times, including on the same plot command: https://stackoverflow.com/a/33064402/895245
Notice that this approach requires that the data is fed to gnuplot before the plot commands!

Also, I did not use IOString as @ppetraki did, since apparently this is slower than a simple list joiner: https://waymoot.org/home/python_string/

Answered By: Joris Kinable
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.