# A python-gnuplot interface from __future__ import print_function import subprocess from numpy import savetxt import tempfile import os __author__ = 'Mathias Zechmeister' __version__ = 'v17' __date__ = '2021-03-30' __all__ = ['gplot', 'Gplot', 'ogplot', 'Iplot'] class Gplot(object): """ An interface between Python and gnuplot. Creation of an instance opens a pipe to gnuplot and returns an object for communication. Plot commands are send to gnuplot via the call method; arrays as arguments are handled. Gnuplot options are set by calling them as method attributes. Each method returns the object again. This allows to chain set and plot method. Parameters ---------- tmp : str, optional Method for passing data. * '$' - use inline datablock (default) (not faster than temporary data) * None - create a non-persistent temporary file * '' - create a local persistent file * '-' - use gnuplot special filename (no interactive zoom available, replot does not work) * 'filename' - create manually a temporary file stdout : boolean, optional If true, plot commands are send to stdout instead to gnuplot pipe. stderr : int, optional Gnuplot prints errors and user prints to stderr (term output is sent to stdout). The default stderr=None retains this behaviour. stderr=-1 (subprocess.PIPE) tries to capture stderr so that the output can be redirected to the Jupyter cells instead of parent console. This feature can be fragile and is experimental. mode : str, optional Primary command for the call method. The default is 'plot'. After creation it can be changed, e.g. gplot.mode = gplot.splot. args : array or str for function, file, or other plot commands like style flush : str, optional set to '' to suppress flush until next the ogplot (for large data sets) Methods ------- __call__ load replot plot print put set splot test unset var NOTES ----- The attribute print does not work in python 2. Examples -------- A simple plot and add a data set >>> gplot('sin(x) w lp lt 2') >>> gplot+(np.arange(10)**2., 'w lp lt 3') >>> gplot+('"filename" w lp lt 3') Pass multiple curves in one call >>> gplot('x/5, 1, x**2/50 w l lt 3,', np.sqrt(np.arange(10)),' us 0:1 ps 2 pt 7 ,sin(x)') >>> gplot.mxtics().mytics(2).repl >>> gplot([1,2,3,4]) >>> gplot([1,2,3,4], [2,3,1,1.5]) >>> gplot([1,2,3,4], [[2,2,1,1.5], [3,1,4,5.5]]) >>> gplot([[2,2,1,1.5]]) >>> gplot([1],[2],[3],[4]) >>> gplot(1,2,3,4) Pass options as function arguments or in the method name separated with underscore >>> gplot.key_bottom_rev("left")('sin(x)') """ version = subprocess.check_output(['gnuplot', '-V']) version = float(version.split()[1]) def __init__(self, cmdargs='', tmp='$', mode='plot', stdout=False, stderr=None): self.stdout = stdout self.tmp = tmp self.mode = getattr(self, mode) # set the default mode for __call__ (plot, splot) self.gnuplot = subprocess.Popen('gnuplot '+cmdargs, shell=True, stdin=subprocess.PIPE, stderr=stderr, universal_newlines=True, bufsize=0) # This line is needed for python3! Unbuffered and to pass str instead of bytes self.pid = self.gnuplot.pid self.og = 0 # overplot number self.buf = '' self.tmp2 = [] self.flush = None self.put = self._put if stderr: import fcntl fcntl.fcntl(self.gnuplot.stderr, fcntl.F_SETFL, os.O_NONBLOCK) self.put = self.PUT def _plot(self, *args, **kwargs): # collect all arguments tmp = kwargs.pop('tmp', self.tmp) flush = kwargs.pop('flush', '\n') if self.version in [4.6] and flush=="\n": flush = "\n\n" # append a newline to workaround a gnuplot pipe bug # with mouse zooming (see http://sourceforge.net/p/gnuplot/bugs/1203/) self.flush = flush pl = '' buf = '' data = () if tmp in ('$',): pl, self.buf = self.buf, pl for arg in args + (flush,): if isinstance(arg, (str, u''.__class__)): # append argument, but flush the data before if data: # transpose data when writing data = zip(*data) self.og += 1 tmpname = tmp if tmp in ('-',): # use gnuplot's special filename '-' self.buf += "\n".join(" ".join(map(str,tup)) for tup in data)+"\ne\n" elif tmp in ('$',): # gnuplot's inline datablock tmpname = "$data%s" % self.og # prepend the datablock buf += tmpname+" <>> iplot = Iplot(opt='size 300,200', stderr=-1) >>> iplot('x') NOTES ----- To get nice working gnuplot output, some issues needed to be solved. * canvas: To enable correct mousing, div#site position (which defaults to static) is set to sticky. Then it becomes an offsetParent and its scrollTop value resulting from overflow:auto can be processed in gnuplot_mouse.js, which was modified. * svg: Inline javascript is not loaded when using uri. * png: ok. Changing size was not tested yet. ''' self.suffix = kwargs.pop('suffix', 'png') self.opt = kwargs.pop('opt', '') self.uri = kwargs.pop('uri', True) self.jsdir = kwargs.pop('jsdir', 'jsdir "%s"'% self._jsdir) self.cleanup = kwargs.pop('cleanup', True) self.canvasnum = 0 return super(Iplot, self).__init__(*args, **kwargs) def _plot(self, *args, **kwargs): ''' filename : Output to a user specified file. ''' if kwargs.get('flush') == '': super(Iplot, self)._plot(*args, **kwargs) return self.canvasnum += 1 uri = self.uri filename = kwargs.pop('file', None) suffix = kwargs.pop('suffix', self.suffix) if filename: suffix = os.path.splitext(filename)[1][1:] # extension without separator uri = False canvasname = "fishplot_%s" % self.canvasnum term = {'pdf': 'pdfcairo', 'svg': 'svg mouse %s' % self.jsdir, 'svg5': 'svg mouse standalone name "bla"', 'html': 'canvas name "%s" mousing %s' % (canvasname, self.jsdir) }.get(suffix, 'pngcairo') + ' ' + self.opt # png + no_uri, Image still converts into uri -> but works # png + uri, Image still converts into uri -> works # svg + local_svg, mousing works # svg + no_uri, as inline # svg + uri, as inline # html + no_uri (if cleanup=False, the browser can read the file) # html + uri -> works imgfile = filename or tempfile.NamedTemporaryFile(suffix='.'+suffix).name if suffix=='svg' and not uri: # use a local file imgfile = 'simple_%s.svg' % self.canvasnum if suffix=='html': imgfile = canvasname + '.js' if uri: # cleanup if already exists os.system("rm -f "+imgfile) os.mkfifo(imgfile) self.term(term).out('"%s"' % imgfile) if uri: # the fifo needs something to read; but display will finally open and read imgfile fifo = open(imgfile, 'r') super(Iplot, self)._plot(*args, **kwargs) self.out() if uri: 1 #imgdata = fifo.read() #imgfile = 'data:image/png;base64,'+ #imgdata = open(imgfile, "rb").read() # png needs rb, but imgfile can be also passed directly #imgdata.replace('\n ''' + gnuplot_svg_js + ''' // manually gnuplot_svg.Init (onload does not fire?) gnuplot_svg.SVGDoc = document.getElementsByTagName("svg")[0]; console.log(gnuplot_svg.SVGRoot, gnuplot_svg.SVGDoc); ''' + imgdata + '') if suffix=='html': if uri: imgdata = '' % open(imgfile).read() else: imgdata = '''''' % imgfile # style the buttons and work around some mousing issues imgdata = ''' %s
# unzoom rezoom zoom text ?  NaN   NaN   

Your browser does not support the HTML 5 canvas element

''' % ((self._jsdir,)*4 + (imgdata,) + (self._jsdir,)*5 + (canvasname,)*12) #print(imgdata) img = showfunc(imgdata, **showargs) if self.cleanup and not filename and not (suffix=='svg' and not uri): os.system("rm -f "+imgfile) # print(counter, end='\r') # print(counter, imgfile, os.path.exists(imgfile), os.system("lsof "+imgfile)) else: display(HTML('%s' % (imgfile, imgfile))) return img # a default instance gplot = Gplot() ogplot = gplot.oplot