#!/usr/bin/python # -*- coding: utf-8 -*- # # ================= FILE HEADER ======================================== # # myplotlib v3.0.1, # # @file myFig.py # @author Yori 'AGy' Fournier # @licence CC-BY-SA # # MyFig class: Overlay of matplotlib Figure class # It is done such that the user can concentrate on # the important aspect of the figure and not the # technical part. # # @Class MyFig # # @Constructor # # fignum: number of the Figure. # reformat: flag for reComputing plotted data # from rawData. # debug: flag for debugging, more verbatile. # (kw)args: user defined arguments and keywords. # # @section Functions # # - printDebug: print some debug information. # # - plot: overlay of Figure.plot() function, # should be overwrited by the user. # Consists of # - creating axes, # - formating the data, # - adding the axis to the figure # - plotting the axis # # - update: update the parameters in the # keywords dictionary. # # @section History # # v 0.1.1 - MyFig class for the myplotlib module. # # v 1.0.0 - Changed rawdata with inputarg to allows any type # of rawdata, can be an array. # # replace the attribute format (reserved word) with # formatted. # # v 1.0.1 - Suppress the explicit definitions of the keywords. # by the introduction of the keywords dictionary. # All parameters in this dictionary are automatically # updated. # # suppress the default value of fignum and hardcode it # to be -1. # # add the attribute boundedToWin required for the # interactive mode of myplotlib. # # v 2.2.3 - Allow the possibility of giving a unique rawdata format # all axes. /!\ self.rawdata is a generator. /!\ # # ====================================================================== # # # IMPORT --------------------------------------------------------------- from . import os from . import D_FIGSIZE, D_INPUTARG, D_DEBUG, D_REFORMAT, D_FORMATTED from . import D_OFORMAT, D_OPATH from . import DBUG, SEVR, INFO, SPCE, WARN from . import MyData from . import MyAxes from . import Figure # Class MyFig Overwriting Matplotlib.figure.Figure class MyFig_client(Figure): # Set the size of the Figure in inch # (private variable can not be updated) FIGSIZE = D_FIGSIZE # CONSTRUCTOR -------------------------------------------------------- def __init__(self, rawdata, *args, **kwargs): # for the update function self._attributesToUpdateKeys = ['fignum', 'reformat', 'debug', 'formatted', 'aliases'] # initialise the attribute default values and remove them from kwargs self.fignum = kwargs.pop('fignum', -1) # need to be hard coded for interactive mode self.reformat = kwargs.pop('reformat', D_REFORMAT) self.debug = kwargs.pop('debug', D_DEBUG) self.formatted = kwargs.pop('formatted', D_FORMATTED) self.aliases = {} self.syncID = -1 self.client = None self.FIGSIZE = kwargs.pop('figsize', self.FIGSIZE) # parent constructor Figure.__init__(self, figsize=self.FIGSIZE) # This is required for the interactive mode # it ensures that a figure can not be bounded # to several windows and prevent the loss of canvas. self.boundedToWin = False # initialise self._initialize(*args, **kwargs) self.set_rawdata(rawdata) def deSyncFig(self): if self.client is not None : if self.client.deleteSyncFigure(self.syncID) : print('The figure was successfully deleted on the server Side') self.syncID = -1 else : print('The figure could not be deleted on the server Side') else: print('The client is not connected to a server yet. Please use the client to set up a sync conection.') def getState(self) : """ this function collects the current state of in the figure by creating a dict that holds all keyword,value pairs (taking the aliases into account) Warning: myplotlib allows in a transparent manner to modify the axes directly. Those changes can not be covert. """ state = dict() # test for double keywords and print a waring for ax in self.axes: # find aliases for this specific axes masks = dict() for alias in self.aliases.keys() : axes4alias,keyword = self.aliases[alias] if axes4alias == ax : masks.update({keyword : alias}) # collect all keywords of the axes and replace the keys with the aliases axKeywords = ax.keywords.copy() for mask in masks : if mask in axKeywords : axKeywords[masks[mask]] = axKeywords.pop(mask) # Test if kewords are already set by an other axes for key in axKeywords.keys() : if key in state.keys(): print('Warning: The keyword \"',key,'\" appears in multiple axes. The Defaults will be overwritten. Prevent this by using aliases') # update the global keyword index state.update(axKeywords) return state def reSyncFig(self): if self.client is not None : # collect the current state of the figure state = self.getState() # call the client to create a new figure on the server side newFig = self.client.newSyncFigure(self.__class__, self.remote_rawdata, **state) # since the state matches the current instance. only the new syncID is kept. self.syncID = newFig.syncID else: print('The client is not connected to a server yet. Please use the client to set up a sync conection.') # INITIALIZE ------------------------------------------------------- def _initialize(self, *args, **kwargs): # add the axes self.addAxes() # declare the aliases self.declareAliases() # update the attributes and keywords self._update(**kwargs) def _update(self, **kwargs): # check attributes in keywords for keyword in kwargs.keys(): # if it is an attribute if keyword in self._attributesToUpdateKeys: # update value setattr(self, keyword, kwargs[keyword]) # For each axes update the keywords for ax in self.get_axes(): forax = {} for keyword in kwargs.keys(): # ignore figure attributes if keyword in self._attributesToUpdateKeys : pass # Check if a key of kwargs has an alias for this axes elif keyword in self.aliases.keys(): alax, alkey = self.aliases[keyword] # If an alias is found then update axkwargs if ax == alax: forax.update(**{alkey: kwargs[keyword]}) # use keyword as it is for the axes else : forax.update(**{keyword : kwargs[keyword]}) # Then eventually all collected Keywords are updated in one go if (forax) : if self.debug: print (DBUG+' fig.update ', ax, 'keywords: ', forax) ax.update(**forax) return(True) # UPDATE ---------------------------------------------------------- def update(self, **kwargs): # send the keywords to the server if self.client is not None : status = self.client.updateSyncFigure(self.syncID,kwargs) # if the server reported no errors apply the keywords on client side also: if status : self._update(**kwargs) else : print('The server reported of an error in setting the keywords') return(False) else : print(WARN+'The figure is not yet connected to a client') self._update(**kwargs) return(False) # DECLARE ALIASES ------------------------------------------------- def declareAliases(self): pass # ADD AXES --------------------------------------------------------- def addAxes(self, *arg, **kwargs): pass # This overwrites MPL add_axes. It gives a name to the axis added so that it is easier to refer to def add_axes(self, ax, name) : # if isinstance(name, str) and issubclass(ax.__class__, MyAxes): # This lead to an error is ax is not in the same namespace ax.name = name # else: # print(SEVR + " there is an error with the input type of add_axes()") # return False # test if an axes with that name is already present for pax in self.get_axes() : if pax.name == name : print(SEVR + " an axes with that name is already present") return False Figure.add_axes(self, ax) return True # GET AXES BY NAME ------------------------------------------------- def getAxesByName(self, name): for ax in self.get_axes() : if ax.name == name : return ax #if the name is correct we should never get here. print(SEVR + "The axes name ", name, " was not found") return None #~ def getAxesName(self,unknownax) : #~ """ compares all axes against the function parameter and returns the name of that axes if found in figure""" #~ #~ for ax in self.get_axes() : #~ if unknownax == ax : #~ return ax.name # SET RAW DATA ----------------------------------------------------- def set_rawdata(self, rawdata): ''' This function sets loacally the names of the remote data in a similar way as a local figure would do. Warning : No consitancy checking is done. The server side will report the errors. ''' self.remote_rawdata = rawdata # FORMAT RAW DATA -------------------------------------------------- def formatRawData(self): # HEre comes a function that send a signal to the server # and tells him to execute the format rawdata of the # synchronized figure (server-side) if (self.client is not None) and (self.syncID != -1): # try: if (True): datas = self.client.syncFigFormatRawData(self.syncID) # except: # print(SEVR+'The server-side figure could not format the data...') # return(False) for ax, data in zip(self.get_axes(), datas): # each data is a string that can be unpacked by axes itself # ax.data = ax.unpackFormattedData(data) ax.data = data else: print(SEVR+'The figure is not properly synchronized.') return(False) return(True) # PLOT ------------------------------------------------------------- def plot(self): status = True if(self.debug): print(DBUG + "currently formatting the data...") if((self.reformat) or (not self.formatted)): status = self.formatRawData() if(not status): return(False) if(self.debug): print(DBUG + "currently plotting the axes...") # For all axes in the figure reformat if needed and plot for ax in self.get_axes(): # clean the axes ax.cla() # plot plottingStatus = ax.plotting() status = status and plottingStatus return(status) # RESET ------------------------------------------------------------ def reset(self, *args, **kwargs): self.clf() self._initialize(*args, **kwargs) self.plot() # DEBUG -------------------------------------------------------------- def printDebug(self): className = str(self.__class__.__name__) print('\n' + DBUG + " {0} PARAMETERS: ".format(className)) print(SPCE + " ID the figure: " + str(self.fignum)) print(SPCE + "Size of the figure: " + str(self.FIGSIZE) + ' [inch] \n') # PRINT 2 FILE ------------------------------------------------------- def print2file(self, filename, *args, **kwargs): # get the keywords debug = kwargs.get('debug', D_DEBUG) oformat = kwargs.get('oformat', D_OFORMAT) opath = kwargs.get('opath', D_OPATH) as_seen = kwargs.get('as_seen', False) # set the dpi if(oformat == 'png'): dpi = 300. addMetaData = True # NOT YET IMPLEENTED else: dpi = 100. addMetaData = False # NOT YET IMPLEMENTED from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas # if the figure is boundedToWin # save to canvas in a local variable and restore after saving to file. if self.boundedToWin: winCanvas = self.canvas else: winCanvas = None canvas = FigureCanvas(self) # the canvas of the figure changed # Plot the figure if not as_seen: status = self.plot() if not status: print(SEVR + 'Can not plot the figure. sorry --> EXIT') return(False) # draw the figure on the new canvas canvas.draw() # not really needed (savefig does that) # check if the extention is in the name if '.png' in str(filename): oformat = 'png' filename = filename[:-4] elif '.eps' in str(filename): oformat = 'eps' filename = filename[:-4] # check if the path is in the name if '/' in str(filename): if(debug): print(WARN + "I detect that the path is in the name of the file, will ignore D_OPATH.") arg = filename filename = os.path.basename(arg) opath = os.path.dirname(arg) # take care of the . in the extention if oformat[0] == '.': oformat = oformat[1:] # clean the output path if str(opath)[-1] == '/': opath = str(opath)[:-1] else: opath = str(opath) # print into a file try: self.savefig(str(opath) + '/' + str(filename) + '.' + str(oformat), dpi=dpi) # ! this not the save from plt but from fig except IOError: print(SEVR + 'the path to the file does not exists:') print(SPCE + str(opath) + '/' + str(filename) + '.' + str(oformat) + ' --> EXIT') return(False) except: print(SEVR + 'Can not save file. sorry --> EXIT') return(False) # restore the former canvas if winCanvas is not None: self.canvas = winCanvas return(True)