mpl_fig.py 35.9 KB
Newer Older
Yori 'AGy' Fournier's avatar
Yori 'AGy' Fournier committed
1
2
3
4
5
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# ================= FILE HEADER ========================================
#
Philipp Gast's avatar
Philipp Gast committed
6
#   myplotlib v3.0.1,
Yori 'AGy' Fournier's avatar
Yori 'AGy' Fournier committed
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#
#   @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.
30
#
Yori 'AGy' Fournier's avatar
Yori 'AGy' Fournier committed
31
32
33
34
35
36
37
#            - 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
38
39
40
#
#            - update:        update the parameters in the
#                             keywords dictionary.
Yori 'AGy' Fournier's avatar
Yori 'AGy' Fournier committed
41
42
43
#
#   @section History
#
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#   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.
Yori 'AGy' Fournier's avatar
Yori 'AGy' Fournier committed
62
#
63
64
65
#   v 2.2.3 - Allow the possibility of giving a unique rawdata format
#             all axes. /!\ self.rawdata is a generator. /!\
#
Yori 'AGy' Fournier's avatar
Yori 'AGy' Fournier committed
66
67
68
69
# ======================================================================
#
#
# IMPORT ---------------------------------------------------------------
70
from . import os
71
from . import D_FIGSIZE, D_INPUTARG, D_DEBUG, D_REFORMAT, D_FORMATTED
72
from . import D_OFORMAT, D_OPATH
73
from . import DBUG, SEVR, INFO, SPCE, WARN
74
75
from . import CONTEXT

76
# from . import MplData
77
from . import MplAxes
78
from . import HorizontalGrid
Yori 'AGy' Fournier's avatar
Yori 'AGy' Fournier committed
79

80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
if CONTEXT.lower() != 'server':
    from . import Figure

# Class Factory
class FigureClassFactory(type):


    # PREPARE METHOD --------------------------------------------------
    @classmethod
    def __prepare__(mcls, name, bases, **kwargs):
        return(type.__prepare__(mcls, name, bases))

    # NEW METHOD ------------------------------------------------------
    def __new__(mcls, name, bases, attrs, **kwargs):

95
96
        context = kwargs.get('context', 'local')  # python3
#        context = attrs.get('__context__', 'local')  # python2.7
97

98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
        # Contextualize the abstract functions
        for key in attrs.keys():
            if key.startswith('_') and key.endswith(context.lower()):
                
                length_context = len(context) + 1
                abstract_function_name = key[1:-length_context]
                
                if abstract_function_name in attrs.keys():
                    attrs[abstract_function_name] = attrs[key]
                    attrs[abstract_function_name].__name__ = abstract_function_name
                    print('{key} -> {abstract}'.format(key=key, abstract=abstract_function_name))
                    
                elif '_' + abstract_function_name in attrs.keys(): # maybe function is private
                    private_abstract_function_name = '_' + abstract_function_name
                    attrs[private_abstract_function_name] = attrs[key]
                    attrs[private_abstract_function_name].__name__ = private_abstract_function_name
                    print('{key} -> {abstract}'.format(key=key, abstract=private_abstract_function_name))
                    
                else:
                    # no such a function...
                    print('There is no {abstract}'.format(abstract=abstract_function_name))

        # Contextualize the bases for MplFig
121
        if context in ('client', 'local'):
122
123
            if bases == (): # in the case of MplFig
                bases = (Figure,)
124
        elif context == 'server':
125
            if bases == (): # in the case of MplFig
126
127
128
129
130
131
132
133
                bases = (object,)
        else:
            print("{sevr} The context given in the configuration file \
            is not correct: should be either local, client or server. \
            ".format(sevr=SEVR))
            raise ImportError

        # Actually create the class
134
        fig_class = type.__new__(mcls, name, bases, attrs)
135

136
        return(fig_class)
137
138
139
140


    # INITIALIZATION METHOD -------------------------------------------
    def __init__(cls, name, bases, dct, **kwargs):
141
        super(FigureClassFactory, cls).__init__(name, bases, dct)
142
    
Yori 'AGy' Fournier's avatar
Yori 'AGy' Fournier committed
143

144
# Class MyFig Overwriting Matplotlib.figure.Figure
145
146
# class MplFig(): # python2.7
class MplFig(metaclass=FigureClassFactory, context=CONTEXT.lower()):
147

148
149
    #__metaclass__ = FigureClassFactory # python2.7
    #__context__ = CONTEXT.lower()      # python2.7
150
    
Yori 'AGy' Fournier's avatar
Yori 'AGy' Fournier committed
151
152
153
    # Set the size of the Figure in inch
    # (private variable can not be updated)
    FIGSIZE = D_FIGSIZE
154

155
    # FOR SERVER CONTEXT
156
    G_SYNC_ID = 0
157
    
158
    # === SHARED METHODS =================================================
159

Yori 'AGy' Fournier's avatar
Yori 'AGy' Fournier committed
160
    # CONSTRUCTOR --------------------------------------------------------
161
    def __init__(self, rawdata, *args, **kwargs):
162

Yori 'AGy' Fournier's avatar
Yori 'AGy' Fournier committed
163
        # for the update function
164
        self._attributes_to_update_keys = [
165
166
            'fignum', 'reformat', 'debug', 'formatted', 'aliases']

Yori 'AGy' Fournier's avatar
Yori 'AGy' Fournier committed
167
        # initialise the attribute default values and remove them from kwargs
168
169
170
171
172
173
174
175
        # need to be hard coded for interactive mode
        self.fignum = kwargs.pop('fignum', -1)
        self.reformat = kwargs.pop('reformat', D_REFORMAT)
        self.debug = kwargs.pop('debug', D_DEBUG)
        self.formatted = kwargs.pop('formatted', D_FORMATTED)
        self.aliases = {}
        self.FIGSIZE = kwargs.pop('figsize', self.FIGSIZE)

Yori 'AGy' Fournier's avatar
Yori 'AGy' Fournier committed
176
        # parent constructor
177
        self._parent_constructor(figsize=self.FIGSIZE)
178

179
180
181
        # 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.
182
        self.bounded_to_win = False
183

184
        # add a default Grid to the figure
185
        self.set_grid(HorizontalGrid())
186

187
        # initialise
188
        self._initialize_extra(*args, **kwargs)
189
        self._initialize(*args, **kwargs)
190
        self.set_rawdata(rawdata)
191

192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
    # INITIALIZE -------------------------------------------------------
    def _initialize(self, *args, **kwargs):
        
        # add the axes
        self.addAxes()
        # This will throw a pep8 error but its needed to prevent
        # confusion with matplotlib -- need another name

        # declare the aliases
        self.declare_aliases()

        # update the attributes and keywords
        self.update(**kwargs)        

    # PRIVATE UPDATE ---------------------------------------------------
    def _update(self, **kwargs):

        # check attributes in keywords
        for keyword in kwargs.keys():

            # if it is the rawdata use the function
            if keyword == 'rawdata':
                self.set_rawdata(kwargs['rawdata'])

            # if it is an attribute
            elif keyword in self._attributes_to_update_keys:
                # treat aliases differently: do not replace value but update it
                # for replacing the user need to call MyFig.aliases =
                # replacement
                if keyword == 'aliases':
                    self.aliases.update(kwargs[keyword])
                else:
                    # 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 ['rawdata'] + self._attributes_to_update_keys:
                    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)
        
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
    # GET AXES BY NAME -------------------------------------------------
    def get_axes_by_name(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 {axes_name}, was not found\
        ".format(sevr=SEVR, axes_name=name))
        return None

    # GET AXES's NAME --------------------------------------------------
    def get_axes_name(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

    # GET AXES ---------------------------------------------------------
    def get_axes(self):
        return(self.axes)

    # DEBUG --------------------------------------------------------------
    def print_debug(self):
        class_name = str(self.__class__.__name__)
        print('\n' + DBUG + "  {0} PARAMETERS: ".format(class_name))
        print(SPCE + "          Raw data: " + str(self.rawdata))
        print(SPCE + "     ID the figure: " + str(self.fignum))
        print(SPCE + "Size of the figure: " + str(self.FIGSIZE) + ' [inch] \n')
            
    # SET LAYOUT -------------------------------------------------------
    def set_grid(self, grid):
        self.grid = grid
        self.grid.fig = self

    # LAYOUT -----------------------------------------------------------
    def layout(self):
        self.grid.layout()
298
299
300
301
302
303
304
305
306

    # DEF METHOD (DECORATOR) -------------------------------------------
    def def_method(self, f):
        name = f.__name__
        if hasattr(self, name):
            setattr(self, name, f.__get__(self))
        else:
            raise KeyError('Fig has no method called {name}'.format(name=name))
        return(f)
307
            
308
309
310
311
312
313
314
315
316
317
    # === INTERFACE METHODS ============================================
    #
    # The interfaces methods are the one that the user supposely
    # overwrites to produce its own figures.
    #
    # - addAxes is mendatory
    # - declare_aliases is optional
    #
    #
    # ADD AXES ---------------------------------------------------------
318
    def addAxes(self, *arg, **kwargs):
319
        raise NotImplementedError('The addAxes method needs to be implemented.')
320
    #    pass
321
    
322
    # DECLARE ALIASES --------------------------------------------------
323
    def declare_aliases(self):
324
325
    #    raise(NotImplementedError,
    #          'The declare_aliases method needs to be implemented.')
326
327
        pass
    
328
    # === ABSTRACT METHODS =============================================
329
330
331
332
333
334
335
336
337
    #
    # The abstract methods are the context dependant methods which are
    # dynamically implemented by the class factory.
    #
    # Some are mandatory (with NotImplementedError) others are optional
    # (passing methods).
    #
    #
    # PARENT CONSTRUCTOR -----------------------------------------------
338
    def _parent_constructor(self, **kwargs):
339
340
341
342
343
344
345
346
347
        """Should call the init method of the parent classes.

        Since the MplFig in the server context is not a matplotlib.Figures 
        but a simple python object, the parent constructor depends on the
        context and need to be implemented by the ClassFactory.

        called by __init__

        """
348
        raise NotImplementedError('The parent_constructor method needs to be implemented.')
349
350

    # INITIALIZE EXTRA -------------------------------------------------
351
    def _initialize_extra(self, *args, **kwargs):
352
353
354
355
356
357
358
359
        """Initialize context specific attributes.

        Some attributes are context dependant and need to be implemented
        differently bu the ClassFactory.

        called by __init__

        """
360
        raise NotImplementedError('The _initialize_extra method needs to be implemented.')
361

362
363
364
365
366
367
368
369
370
371
    # PUBLIC UPDATE ----------------------------------------------------
    def update(self, **kwargs):
        """The public update function. Updates the keywords of the Figure and contained Axes.

        In the client context the update function needs to 
        additionally send a signal to the server to keep synchronisation.

        Should call _update(self)

        """
372
        raise NotImplementedError('The update method needs to be implemented.')
373

374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
    # ADD AXES ---------------------------------------------------------
    def add_axes(self, ax, name):
        """Reimplementation of the add_axes method from matplotlib.

        We decided to add a name to axes in order to allow the user
        to access them more easily independently of the order they
        are added in the MplFig.axes list. Therefore this method
        is a wrapper around matplotlib.Figure.add_axes.

        In the server context since matplotlib is not accessable,
        this method needs to be dynamically implemented by the 
        ClassFactory.

        Supposly called by the addAxes method.

        Should call matplotlib.add_axes(self, ax) if inherit from Figure.

        """
392
        raise NotImplementedError('The add_axes method needs to be implemented.')
393
394

    # SET RAWDAT -------------------------------------------------------
395
    def set_rawdata(self, rawdata):
396
397
398
399
400
401
        """Set the rawdata attribute of the Figure.

        Since in the client context the Figure has no explicit rawdata
        this method need to be implemented by the ClassFactory.

        """
402
        raise NotImplementedError('The set_rawdata method needs to be implemented.')
403

404
    # FORMAT RAWDATA ---------------------------------------------------
405
    def format_rawdata(self):
406
407
408
409
410
411
412
413
414
        """Format the rawdata into the MplFig.data.

        In the client context the MplFig has no rawdata,
        this function request the server to process the rawdata
        on his side.

        called by MplFig.plot

        """
415
        raise NotImplementedError('The format_rawdata method needs to be implemented.')
416
417
    
    # ------ NOT FOR SERVER --------------------------------------------
418
419
420
421
422
423
    #
    # This function do not raise and error since they are used as empty
    # functions for the server context.
    #
    #
    # PLOT -------------------------------------------------------------
424
    def plot(self):
425
426
427
428
429
430
431
432
        """This methods does the plotting work of the figure.

        Since in the server context no plotting is available, this
        functione need to be implemented by the ClassFactory.

        Should call MplAxes.plotting in graphical contexts.

        """
433
434
        pass

435
    # RESET ------------------------------------------------------------
436
    def reset(self):
437
438
439
440
441
442
443
        """This method resets the MplFig to its default attributes.

        Since in the server context the server does not have any graphical
        attribute to reset this function need to be implemented by the 
        ClassFactory.

        """
444
445
        pass

446
    # PRINT TO FILE ----------------------------------------------------
447
    def print_to_file(self, filename, *args, **kwargs):
448
449
450
451
452
453
        """Print the rendered figure into a file.

        In the server context the MplFig can not be rendered, and therefore,
        can not print anything into a file.
        
        """
454
455
456
        pass

    # ------ EXCLUSIVE FOR CLIENT --------------------------------------
457
458
459
460
461
462
463
464
465
466
467
468
469
    #
    # These methods are exclusive for the client. They are the toolbox
    # to guaranty the synchronisation.
    #
    #
    # DE-SYNC FIGURE ---------------------------------------------------
    def de_sync_fig(self):
        """De-synchronise the client figure from the server side.

        Obviously this method make sense only in the client context,
        and need to be implemented by the ClassFactory.

        """
470
471
        pass

472
473
474
475
476
477
478
479
    # GET STATE --------------------------------------------------------
    def get_state(self):
        """Extract the state of the MplFig used to tests synchronisation.

        This method make sens only in the client context, and is therefore
        implemented in the ClassFactory.
        
        """
480
481
        pass

482
483
484
485
486
487
488
489
    # RE-SYNC FIGURE ---------------------------------------------------
    def re_sync_fig(self):
        """Re-Synchronise a client figure with the server side.

        This method make only sense in the client context, and 
        is therefore implemented by the ClassFactory.

        """
490
491
        pass

492
    # ------ EXCLUSIVE FOR SERVER --------------------------------------
493
494
495
496
497
498
    #
    # This method is part of the tool box on the server side for guaranty
    # synchronisation with the client side. 
    #
    #
    # GET FIG_ID
499
    def get_fig_id(self):
500
501
502
503
504
505
        """Return the sync_id of the figure.

        This is useful only in the server context and therefore need
        to be implemented by the ClassFactory.

        """
506
507
        pass

508
    # === LOCAL CONTEXT METHODS ========================================
509
510
511
512
513
514
    #
    # The Local Context Methods are the methods meant to overwrite the
    # corresponding abstract methods in the local context.
    #
    #
    # PARENT CONSTRUCTOR -----------------------------------------------
515
    def _parent_constructor_local(self, **kwargs):
516
517
518
        """Call the constructor of matplotlib.Figure with figsize argument.

        """
519
        Figure.__init__(self, figsize=self.FIGSIZE)
520

521
    # INITIALIZE EXTRA ATTRIBUTES --------------------------------------
522
    def _initialize_extra_local(self, *args, **kwargs):
523
524
525
        """This is an empty method for now, but may not remain like that

        """
526
        pass
527

528
    # PUBLIC UPDATE ----------------------------------------------------
529
    def _update_local(self, **kwargs):
530
531
532
533
534
        """Update the MplFig attributes.

        It is a simple wrapper around the private method.

        """
535
        return(self._update(**kwargs))
536

537
    # ADD AXES ---------------------------------------------------------
538
    def _add_axes_local(self, ax, name):
539
540
541
542
543
544
545
        """Add the MplAxes object in the list MplFig.axes and set a name to the axes.

        It calls the matplotlib.Figure.add_axes method. 

        It returns True when successful (if name colide return False)

        """
546
547
        # if isinstance(name, str) and issubclass(ax.__class__, MyAxes):
        # This lead to an error is ax is not in the same namespace
548
549
        ax.name = name
#        else:
550
551
#            print("{sevr} there is an error with the input type of \
#            add_axes()".format(sevr=SEVR))
552
#            return False
553

554
        # test if an axes with that name is already present
555
556
        for pax in self.get_axes():
            if pax.name == name:
557
558
                print("{sevr} an axes with that name is already present\
                ".format(sevr=SEVR))
559
                return False
560

561
        Figure.add_axes(self, ax)
562

563
        return True
564

565
    # SET RAWDATA ------------------------------------------------------
566
    def _set_rawdata_local(self, rawdata):
567
568
569
570
571
572
573
574
575
576
577
578
579
580
        """Set the rawdata of the MplFig, and format it.

        The rawdata can be of different type:

        - Either an Iterable of MplData object, where each goes ordered 
          to an MplAxes of the MplFig. It requires that the tuple (rawdata) 
          should be same length than the MplFig.axes list.

        - Either a single MplData object, which will be transformed into
          a generator of type repeat and will be shared with all MplAxes.

        After the rawdata is successfully set, it will be formatted.

        """
581
582
        #from collections import Iterable
        
583
584
        self.formatted = False
        status = False
585

586
        # DEFAULT: one item per axes in figure
587
        if isinstance(rawdata, tuple):
588

Yori 'AGy' Fournier's avatar
Yori 'AGy' Fournier committed
589
            if len(rawdata) == len(self.get_axes()):
590
                if(self.debug):
591
592
                    print("{dbug} set_rawdata: one item per axes\
                    ".format(dbug=DBUG))
Yori 'AGy' Fournier's avatar
Yori 'AGy' Fournier committed
593
                self.rawdata = rawdata
594
                status = self.format_rawdata()
595

Yori 'AGy' Fournier's avatar
Yori 'AGy' Fournier committed
596
597
            else:
                self.rawdata = None
598
599
600
601
602
603
                print("{sevr} rawdata should have the dimention of \
                the number of axes: nbr of axes={nbr_axes} ; \
                dim of rawdata={len_rawdata} \
                ".format(sevr=SEVR,
                         nbr_axes=len(self.get_axes()),
                         len_rawdata=len(rawdata)))
Yori 'AGy' Fournier's avatar
Yori 'AGy' Fournier committed
604
                return(False)
605
606

        else:
607
608
            print("{sevr} set_rawdata: I could not set the rawdata...\
            ".format(sevr=SEVR))
609
610
            self.rawdata = None
            status = False
611
            raise TypeError('rawdata must be an tuple of the length of the number of axes.')
612

613
        if not status:
614
615
            print("{sevr} set_rawdata: I could not set the rawdata...\
            ".format(sevr=SEVR))
616
            self.rawdata = None
617

618
        return(status)
619

620
    # FORMAT RAWDATA ---------------------------------------------------
621
    def _format_rawdata_local(self):
622
        """Call the format_rawdata method of each MplAxes of the figure.
623

624
625
626
627
628
629
        1. Test the rawdata (with MplAxes.test_rawdata)
        2. Format the rawdata (with MplAxes.format_rawdata)

        If debug mode is on, do not catch the error.

        """
Yori 'AGy' Fournier's avatar
Yori 'AGy' Fournier committed
630
        if self.rawdata is not None:
631

632
            for ax, rawdata in zip(self.get_axes(), self.rawdata):
633

634
                status = False
635

636
                if(self.debug):
637
                    print("{info} Axes: {axes_class_name} \
638
                    with index {index} formats rawdata\
639
640
                    ".format(info=INFO,
                             axes_class_name=ax.__class__.__name__,
641
                             index=self.get_axes().index(ax)))
642
                    status = ax.test_rawdata(rawdata)
643
                    if status:
644
                        status = ax.format_rawdata(rawdata)
645
                else:
646
                    status = ax.test_rawdata(rawdata)
647
648
                    if status:
                        try:
649
                            status = ax.format_rawdata(rawdata)
650
                        except Exception:
651
652
653
654
655
                            print("{sevr} The {axes_class_name} \
                            with index {index} could not format the rawdata.\
                            ".format(sevr=SEVR,
                                     axes_class_name=ax.__class__.__name__,
                                     index=self.get_axes().index(ax)))
656
                            return(False)
657

658
659
                if not status:
                    return(False)
660

661
662
            if self.formatted is False:
                self.formatted = True
663

664
            return(True)
665

666
667
        else:
            return(False)
668

669
    # PLOT -------------------------------------------------------------
670
    def _plot_local(self):
671
672
673
674
        """Call the MplAxes.plotting method for all axes.
        
        1. if need to format then format_rawdata
        2. try to plot for each axes.
675

676
        """
677
        status = True
678

679
        if(self.debug):
680
681
            print("{dbug} currently formatting the data...\
            ".format(dbug=DBUG))
682

683
        if((self.reformat) or (not self.formatted)):
684
685
686
687

            if(self.debug):
                print('reformat: {0}, formatted: {1}'.format(self.reformat, self.formatted))
            
688
            status = self.format_rawdata()
689
690
691
            if(not status):
                return(False)

692
        if(self.debug):
693
694
            print("{dbug} currently plotting the axes...\
            ".format(dbug=DBUG))
695

696
697
698
699
        # Layout the grid
        if self.grid is not None:
            self.grid.layout()

700
701
        # For all axes in the figure reformat if needed and plot
        for ax in self.get_axes():
702

703
704
            # clean the axes
            ax.cla()
705

706
            # plot
707
708
            plotting_status = ax.plotting()
            status = status and plotting_status
709

710
        return(status)
711

712
    # RESET ------------------------------------------------------------
713
    def _reset_local(self, *args, **kwargs):
714
        """Reset the default attributes."""
715
716
717
        self.clf()
        self._initialize(*args, **kwargs)
        self.plot()
718

719
    # PRINT 2 FILE -------------------------------------------------------
720
    def _print_to_file_local(self, filename, *args, **kwargs):
721
722
723
724
725
726
727
728
        """Print the rendered figure into a file.
        
        keywords:

            - oformat : [extention of the output] 
            - opath   : [path of the output]
            - as_seen : [without recalling the MplFig.plot method]
            - debug   : [debugging flag]
729

730
        """
731
        # get the keywords
732
        debug = kwargs.get('debug', D_DEBUG)
733
734
        oformat = kwargs.get('oformat', D_OFORMAT)
        opath = kwargs.get('opath', D_OPATH)
Yori 'AGy' Fournier's avatar
Yori 'AGy' Fournier committed
735
        as_seen = kwargs.get('as_seen', False)
736

737
        # set the dpi
738
739
        if(oformat == 'png'):
            dpi = 300.
740
            add_metadata = True  # NOT YET IMPLEENTED
741
742
        else:
            dpi = 100.
743
            add_metadata = False  # NOT YET IMPLEMENTED
744

745
746
        from matplotlib.backends.backend_agg import FigureCanvasAgg \
            as FigureCanvas
747
        # if the figure is boundedToWin
748
749
        # save to canvas in a local variable
        # and restore after saving to file.
750
751
        if self.bounded_to_win:
            win_canvas = self.canvas
752
        else:
753
            win_canvas = None
754

755
        canvas = FigureCanvas(self)  # the canvas of the figure changed
756

757
        # Plot the figure
Yori 'AGy' Fournier's avatar
Yori 'AGy' Fournier committed
758
        if not as_seen:
Yori 'AGy' Fournier's avatar
Yori 'AGy' Fournier committed
759
760
761
762
            status = self.plot()
            if not status:
                print(SEVR + 'Can not plot the figure. sorry --> EXIT')
                return(False)
763

764
765
        # draw the figure on the new canvas
        canvas.draw()  # not really needed (savefig does that)
766

767
768
769
770
771
772
773
        # 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]
774

775
776
777
        # check if the path is in the name
        if '/' in str(filename):
            if(debug):
778
779
780
781
                print("{warn} I detect that the path is in \
                the name of the file, will ignore D_OPATH.\
                ".format(warn=WARN))

782
783
784
            arg = filename
            filename = os.path.basename(arg)
            opath = os.path.dirname(arg)
785

786
787
788
        # take care of the . in the extention
        if oformat[0] == '.':
            oformat = oformat[1:]
789

790
791
792
793
794
        # clean the output path
        if str(opath)[-1] == '/':
            opath = str(opath)[:-1]
        else:
            opath = str(opath)
795

796
        # print into a file
797
        try:
798
799
800
            # ! this not the save from plt but from fig
            self.savefig(str(opath) + '/' + str(filename) +
                         '.' + str(oformat), dpi=dpi)
801
802
        except IOError:
            print(SEVR + 'the path to the file does not exists:')
803
804
            print(SPCE + str(opath) + '/' + str(filename) +
                  '.' + str(oformat) + ' --> EXIT')
805
            return(False)
806
        except Exception:
807
808
            print(SEVR + 'Can not save file. sorry --> EXIT')
            return(False)
809

810
        # restore the former canvas
811
812
        if win_canvas is not None:
            self.canvas = win_canvas
813

814
        return(True)
815
816
817
818
    

    # === CLIENT CONTEXT METHODS =======================================

819
    # PARENT CONSTRUCTOR -----------------------------------------------
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
    _parent_constructor_client = _parent_constructor_local

    def _initialize_extra_client(self, *args, **kwargs):
        self.sync_id = -1
        self.client = None

    def _update_client(self, **kwargs):
        
        # send the keywords to the server
        if self.client is not None:
            status = self.client.update_sync_figure(self.sync_id, 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)

    _add_axes_client = _add_axes_local

    def _set_rawdata_client(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
853
        self.formatted = False
854
855
856
857
858
859
860
861
862
863
864
865
866

    def _format_rawdata_client(self):
        """ 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.sync_id != -1):

            if (True):
                datas = self.client.sync_fig_format_rawdata(self.sync_id)

            for ax, data in zip(self.get_axes(), datas):
                ax.data = data

867
868
            self.formatted = True

869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
        else:
            print(SEVR + 'The figure is not properly synchronized.')
            return(False)

        return(True)

    _plot_client = _plot_local
    _reset_client = _reset_local
    _print_to_file_client = _print_to_file_local

    def _de_sync_fig_client(self):
        if self.client is not None:
            if self.client.delete_sync_figure(self.sync_id):
                print('The figure was successfully deleted on the server Side')
                self.sync_id = -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 _get_state_client(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():
                axes_for_alias, keyword = self.aliases[alias]
                if axes_for_alias == ax:
                    masks.update({keyword: alias})

            # collect all keywords of the axes and replace the keys with the
            # aliases
            ax_keywords = ax.keywords.copy()
            for mask in masks:
                if mask in ax_keywords:
                    ax_keywords[masks[mask]] = ax_keywords.pop(mask)

            # Test if kewords are already set by an other axes
            for key in ax_keywords.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(ax_keywords)

        return state        

    def _re_sync_fig_client(self):
        
        if self.client is not None:

            # collect the current state of the figure
            state = self.get_state()

            # call the client to create a new figure on the server side
            new_fig = self.client.new_sync_figure(
                self.__class__, self.remote_rawdata, **state)

            # since the state matches the current instance. only the new sync_id
            # is kept.
            self.sync_id = new_fig.sync_id
        else:
            print(
                'The client is not connected to a server yet. '
                'Please use the client to set up a sync conection.')

    # === SERVER CONTEXT METHODS =======================================

    def _parent_constructor_server(self, **kwargs):
        pass

    def _initialize_extra_server(self, *args, **kwargs):
        self.axes = []
        MplFig.G_SYNC_ID += 1
        self.sync_id = int(MplFig.G_SYNC_ID)
960
        self.server = kwargs['server']
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982

    _update_server = _update_local

    def _add_axes_server(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

        self.axes.append(ax)

        return True

983
984
    #_set_rawdata_server = _set_rawdata_local

Yori Fournier's avatar
Yori Fournier committed
985
    def _set_rawdata_server(self, rawdata):
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
        """Set the rawdata of the MplFig, and format it.

        The rawdata can be of different type:

        - Either an Iterable of MplData object, where each goes ordered 
          to an MplAxes of the MplFig. It requires that the tuple (rawdata) 
          should be same length than the MplFig.axes list.

        - Either a single MplData object, which will be transformed into
          a generator of type repeat and will be shared with all MplAxes.

        After the rawdata is successfully set, it will be formatted.

        """
        self.formatted = False
For faster browsing, not all history is shown. View entire blame