Exemplo n.º 1
0
    def connect(self):
        logger.debug( "Opening new gnuplot session." )

        self.terminal = self.options.get('terminal', None)
        if self.terminal is None:
            self.terminal = DumbTerminal()
        
        # Note that due to 'close_fds=False', we will keep the connection
        # alive until we actively disconnect it.
        # TODO: how do we actually disconnect?
        cmd_list = ["gnuplot"]
        if self.options.get('persist',False):
            logger.debug("Enabling persistance!")
            cmd_list[0] += ' -persist'
                   
	p = Popen(cmd_list,shell=True,stdin=PIPE,stdout=PIPE,stderr=STDOUT,close_fds=False )
	(self.gpwrite,self.gpout) = (p.stdin, p.stdout)
        self.process = p
        self.history = list()

        logger.debug("gnuplot.py: creating tempfile: %s" % self.tmpdir)
        backend.Backend.connect(self)
Exemplo n.º 2
0
class Backend(backend.Backend):

    tmpdir = None  
    tmpfiles = []  # list of files that have been created by this class

    def init(self):
        self.encoding = self.options.get('encoding', 'iso_8859_15')
        self.window_title = "gnuplot-%s" % id(self)
        self.current_dir = None

        self.tmpdir = tempfile.mkdtemp(prefix="spl-gp-")
        self.tmpfiles = []
        # key: ds, value = (filename, change_counter_on_last_save)
        self.exports = {}

        # X-perimental
        self.layer_to_axes = {}
        self.axes_to_layer = {}
        self.layers_cache = [] # copy of self.plot.layers
        self.layer_signals = {}
        
        self.line_caches = {}
        self.omaps = {}

        # we keep an instance var 'cmd_list' which contains all commands
        # in the order that they should be executed
        self.cmd_list = []
        self.cmd_dict = {}

    
    def connect(self):
        logger.debug( "Opening new gnuplot session." )

        self.terminal = self.options.get('terminal', None)
        if self.terminal is None:
            self.terminal = DumbTerminal()
        
        # Note that due to 'close_fds=False', we will keep the connection
        # alive until we actively disconnect it.
        # TODO: how do we actually disconnect?
        cmd_list = ["gnuplot"]
        if self.options.get('persist',False):
            logger.debug("Enabling persistance!")
            cmd_list[0] += ' -persist'
                   
	p = Popen(cmd_list,shell=True,stdin=PIPE,stdout=PIPE,stderr=STDOUT,close_fds=False )
	(self.gpwrite,self.gpout) = (p.stdin, p.stdout)
        self.process = p
        self.history = list()

        logger.debug("gnuplot.py: creating tempfile: %s" % self.tmpdir)
        backend.Backend.connect(self)

    def disconnect(self):
        logger.debug("Closing gnuplot session.")
        try:
            # closing gnuplot will cause an IOError, since gpwrite won't
            # be able to write any more.
            self("exit")
        except IOError:
            pass

        try:
            shutil.rmtree(self.tmpdir)
        except OSError:
            logger.warn("Temporary directory %s not available!" % self.tmpdir)
        else:
            logger.debug("gnuplot.py: deleted tmpfile %s" % self.tmpdir)
            
        backend.Backend.disconnect(self)



    #----------------------------------------------------------------------
    # methods that a Backend might want to re-implement
    #
    
    def cd(self, dirname):
        """
        Change directory of Backend process to `dirname`, which
        may also be a file name or a relative path name.
        """
        dirname = os.path.abspath(os.path.dirname(dirname))
        self.current_dir = dirname
        self('cd "%s"' % self.current_dir)
        
    def pwd(self):
        """
        Return absolute path name of the working directory used by the
        Backend process.
        """
        return self.current_dir


        
    def __call__(self, cmd):
        """Send string to gnuplot"""

        encoded_cmd = cmd.encode( self.encoding )
        #encoded_cmd = cmd
        self.gpout.flush()
        Signals.emit(self, 'gnuplot-send-cmd', cmd=cmd)
        self.gpwrite.write(encoded_cmd + "\n")
        self.gpwrite.write("print '<--END-->'\n")
        self.gpwrite.flush()

        result = []
        while result[-1:] != ["<--END-->\n"]:
            if self.process.poll() is not None:                
                break
            result.append( self.gpout.readline() )

        if len(result) == 0:
            result = None
        elif result[-1:] == ["<--END-->\n"]:
            result = result[:-1]
            
        self.history.append( (encoded_cmd, result) )
        Signals.emit(self, 'gnuplot-finish-cmd', cmd=cmd, result=result)
        return result
            

    def getvar(self,var,convert_method=string.atof):
        """
	Get a gnuplot variable. 
	
	Returns a string containing the variable's value or None,
        if the variable is not defined.
	
	You may specify a method to convert the resultant parameter, e.g.
	>>> gp = Gnuplot()
	>>> gp("a = 10")
	>>> gp.getvar("a") # implies string.atof
	>>> gp.getvar("a", string.atof)
	>>> gp.getvar("a", string.atoi)
	>>> go.getvar("a", None) # no conversion -> returns String
        """
        self(" set print \"-\"\n")      # print output to stdout
        self(" if (defined(%s)) print %s ; else print \"None\" \n" % (var,var))
        result=self.gpout.readline()
        self(" set print\n")            # print output to default stderr
        if result[0:4]=="None":
            return None
	elif convert_method is not None:
	    return convert_method(result)
	else:
	    return(result)

    #----------------------------------------------------------------------
    # EXPORT FILE HANDLING
    
    def mark_for_export(self, source):
        """        
        Adds the given source to the list of datasets that need to be
        exported. Returns filename that the exported Dataset will
        have.
        """
        if not isinstance(source, Dataset):
            logger.error("Invalid source given. Not a Dataset.")
            return None
        
        # The format of self.exports is:
        #  key: source object (=> id)
        #  value: (filename, dataset_change_counter, dataset object)
        if self.exports.has_key(source) is False:
            logger.debug("Marking %s for export" % source)
            filename = source.key
            new_export = [filename, -1, source]
            self.exports[source] = new_export
            return new_export[0]
        else:
            return self.exports[source][0]


    def export_datasets(self):
        # Export Datasets to temporary directory, so that
        # gnuplot can access them.
        exporter = ExporterRegistry['ASCII']()
        
        destdir = self.tmpdir
        for (source, value) in self.exports.iteritems():
            (filename, change_counter, ds) = value
            if ds is None:
                logger.warn("One of the Datasets to export is None.")
                continue
            if ds.is_empty():
                logger.warn("One of the Datasets to export is empty")
                continue
            logging.debug("Change counter %d, old %d" % (ds.change_counter, change_counter))
            if ds.has_changes(change_counter):                              
                filename = os.path.join(destdir, filename)
                logger.debug('exporting "%s" to dir "%s"' % (ds, destdir))            
                exporter.write_to_file(filename, ds.data)
                self.exports[source][1] = ds.change_counter
            else:
                logger.info("Dataset has not changed and is not exported!")                           
        
    #----------------------------------------------------------------------
    def clear(self):
        self('reset')

    def build_layer(self, layer, group_info):
        """
        Returns a list of gnuplot commands that need to be executed
        to draw this layer.
        group_info is a dictionary containing information for grouped lines.        
        """

        rv = []

        # visible
        if uwrap.get(layer, 'visible') is False:
            return rv
        
        # title
        title = uwrap.get(layer, 'title')
        if title is not None: rv.append('set title "%s"' % title)
        else: rv.append("unset title")

        # grid
        grid = uwrap.get(layer, 'grid')
        if grid is True: rv.append('set grid')
        else: rv.append('unset grid')
        
        rv.extend(self.update_legend(layer))
        rv.extend(self.update_axes(layer))
        rv.extend(self.update_labels(layer))
        rv.extend(self.update_lines(layer))

        return rv


            
    def redraw(self, rebuild_cache=True):

        # All commands for gnuplot are appended to the cmd_list,
        # so that they can be executed at the very end.
        cmd_list = []
        cmd_list.append('cd "%s"' % self.tmpdir)
	cmd_list.append( "set encoding %s" % self.encoding )

        cmd_list += self.terminal.build(self)     

        # multiplot ?
        if len(self.plot.layers) > 1:
            cmd_list.append( "set multiplot" )
            for layer in self.plot.layers:
                group_info = {}
                x, y = uwrap.get(layer, 'x'), uwrap.get(layer, 'y')
                width, height = uwrap.get(layer, 'width'), uwrap.get(layer, 'height')
                cmd_list.append("set origin %.2f,%.2f" % (x,y))
                cmd_list.append("set size %.2f,%.2f" % (width, height))
                cmd_list += self.build_layer(layer, group_info)
            cmd_list.append( "unset multiplot" )
        else:
            # Single plot!
            # create plotting commands from the Layer information
            group_info = {}
            cmd_list += self.build_layer(self.plot.layers[0], group_info)

        self.export_datasets()
       
        # Now execute all collected commands.
        print "cmd list is: "
        for cmd in cmd_list:
            print "   ", cmd
        print
        
        Signals.emit(self, 'gnuplot-start-plotting')
        logger.info("Gnuplot command list:\n\n%s" % "\n".join(cmd_list))
        for cmd in cmd_list:
            self(cmd)

        Signals.emit(self,'gnuplot-after-plot', window_title=self.window_title)
        
    draw = redraw        
        

    #######################################################################
    # reimplementation

    """
    One idea:

    When you call an update function, then the cmd_list is regenerated.

    If you want to replot everything, then you can just execute all
    commands in the order of cmd_list.

    Alternatively, if update_layer gets an updateinfo and updates only
    parts, then it will move the commands needed to update these few
    things into a cmd_queue, which then must be executed by the calling
    function!
    
    """

    def update_axes(self, layer, updateinfo={}):
        # updateinfo is ignored
        cl = []
        # axes
        for key, axis in layer.axes.iteritems():            
            # axis format
            format = uwrap.get(axis, 'format')
            if format is not None: cl.append('set format %s "%s"' % (key, format))
            else: cl.append('set format %s' % key)

            # axis label
            label = uwrap.get(axis, 'label')
            if label is not None: cl.append('set %slabel "%s"' % (key, label))
            else: cl.append('unset %slabel' % key)

            # axis range
            start = uwrap.get(axis, 'start','*')
            end = uwrap.get(axis, 'end','*')
            cl.append('set %srange [%s:%s]' % (key,start,end))

            # axis scale
            scale = uwrap.get(axis, 'scale')
            if scale == 'linear': cl.append('unset log %s' % key)
            elif scale == 'log': cl.append('set log %s' % key)
            else:
                logger.error("Axis scale '%s' not supported by this backend." % scale)

        self.cmd_dict['axes'] = cl                
        return cl

    
    def update_lines(self, layer, updateinfo={}):
        # updateinfo is ignored

        cl = []
        # lines
        line_cache = []
        for line in layer.lines:
            try:
                if uwrap.get(line, 'visible') is False: continue

                ds = self.get_line_source(line)
                table = self.get_table(ds)
                cx, cy = self.get_column_indices(line)
                
                # mark source for export            
                filename = self.mark_for_export(ds)
                if filename is None:
                    continue
                source = '"%s"' % filename

                label = self.get_line_label(line, table=table, cy=cy)
                if label is not None: title = 'title "%s"' % label
                else: title = 'notitle'

                using = 'using %s:%s' % (cx+1,cy+1)

                # TODO: support 'style' and 'marker'
                # with-clause
                type = uwrap.get(line, 'style')
                type_mappings = {'solid': 'w l'}
                try:
                    with = type_mappings[type]
                except KeyError:
                    with = ''
                    logger.error('line type "%s" not supported by this backend.' % type )

                # line width
                width = uwrap.get(line, 'width')
                width = 'lw %s' % str(width)
            except backend.BackendError, msg:
                logger.error("Error while processing line: %s" % msg)
                continue