source: sasview/guitools/plottables.py @ 38fc601

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalccostrafo411magnetic_scattrelease-4.1.1release-4.1.2release-4.2.2release_4.0.1ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since 38fc601 was 052a66bc, checked in by Mathieu Doucet <doucetm@…>, 16 years ago

Bug fixing

  • Property mode set to 100644
File size: 32.3 KB
Line 
1"""Prototype plottable object support.
2
3The main point of this prototype is to provide a clean separation between
4the style (plotter details: color, grids, widgets, etc.) and substance
5(application details: which information to plot).  Programmers should not be
6dictating line colours and plotting symbols.
7
8Unlike the problem of style in CSS or Word, where most paragraphs look
9the same, each line on a graph has to be distinguishable from its neighbours.
10Our solution is to provide parametric styles, in which a number of
11different classes of object (e.g., reflectometry data, reflectometry
12theory) representing multiple graph primitives cycle through a colour
13palette provided by the underlying plotter.
14
15A full treatment would provide perceptual dimensions of prominence and
16distinctiveness rather than a simple colour number.
17"""
18
19# Design question: who owns the color?
20# Is it a property of the plottable?
21# Or of the plottable as it exists on the graph?
22# Or if the graph?
23# If a plottable can appear on multiple graphs, in some case the
24# color should be the same on each graph in which it appears, and
25# in other cases (where multiple plottables from different graphs
26# coexist), the color should be assigned by the graph.  In any case
27# once a plottable is placed on the graph its color should not
28# depend on the other plottables on the graph.  Furthermore, if
29# a plottable is added and removed from a graph and added again,
30# it may be nice, but not necessary, to have the color persist.
31#
32# The safest approach seems to be to give ownership of color
33# to the graph, which will allocate the colors along with the
34# plottable.  The plottable will need to return the number of
35# colors that are needed.
36#
37# The situation is less clear for symbols.  It is less clear
38# how much the application requires that symbols be unique across
39# all plots on the graph.
40
41# Support for ancient python versions
42import copy
43import numpy
44import math
45
46if 'any' not in dir(__builtins__):
47    def any(L):
48        for cond in L:
49            if cond: return True
50        return False
51    def all(L):
52        for cond in L:
53            if not cond: return False
54        return True
55
56# Graph structure for holding multiple plottables
57class Graph:
58    """
59    Generic plottables graph structure.
60   
61    Plot styles are based on color/symbol lists.  The user gets to select
62    the list of colors/symbols/sizes to choose from, not the application
63    developer.  The programmer only gets to add/remove lines from the
64    plot and move to the next symbol/color.
65
66    Another dimension is prominence, which refers to line sizes/point sizes.
67
68    Axis transformations allow the user to select the coordinate view
69    which provides clarity to the data.  There is no way we can provide
70    every possible transformation for every application generically, so
71    the plottable objects themselves will need to provide the transformations.
72    Here are some examples from reflectometry:
73       independent: x -> f(x)
74          monitor scaling: y -> M*y
75          log:  y -> log(y if y > min else min)
76          cos:  y -> cos(y*pi/180)
77       dependent:   x -> f(x,y)
78          Q4:      y -> y*x^4
79          fresnel: y -> y*fresnel(x)
80       coordinated: x,y = f(x,y)
81          Q:    x -> 2*pi/L (cos(x*pi/180) - cos(y*pi/180))
82                y -> 2*pi/L (sin(x*pi/180) + sin(y*pi/180))
83       reducing: x,y = f(x1,x2,y1,y2)
84          spin asymmetry: x -> x1, y -> (y1 - y2)/(y1 + y2)
85          vector net: x -> x1, y -> y1*cos(y2*pi/180)
86    Multiple transformations are possible, such as Q4 spin asymmetry
87
88    Axes have further complications in that the units of what are being
89    plotted should correspond to the units on the axes.  Plotting multiple
90    types on the same graph should be handled gracefully, e.g., by creating
91    a separate tab for each available axis type, breaking into subplots,
92    showing multiple axes on the same plot, or generating inset plots.
93    Ultimately the decision should be left to the user.
94
95    Graph properties such as grids/crosshairs should be under user control,
96    as should the sizes of items such as axis fonts, etc.  No direct
97    access will be provided to the application.
98
99    Axis limits are mostly under user control.  If the user has zoomed or
100    panned then those limits are preserved even if new data is plotted.
101    The exception is when, e.g., scanning through a set of related lines
102    in which the user may want to fix the limits so that user can compare
103    the values directly.  Another exception is when creating multiple
104    graphs sharing the same limits, though this case may be important
105    enough that it is handled by the graph widget itself.  Axis limits
106    will of course have to understand the effects of axis transformations.
107
108    High level plottable objects may be composed of low level primitives.
109    Operations such as legend/hide/show copy/paste, etc. need to operate
110    on these primitives as a group.  E.g., allowing the user to have a
111    working canvas where they can drag lines they want to save and annotate
112    them.
113
114    Graphs need to be printable.  A page layout program for entire plots
115    would be nice.
116    """
117    def xaxis(self,name,units):
118        """Properties of the x axis.
119        """
120        if self.prop["xunit"] and units != self.prop["xunit"]:
121            pass
122            #print "Plottable: how do we handle non-commensurate units"
123        self.prop["xlabel"] = "%s (%s)"%(name,units)
124        self.prop["xunit"] = units
125
126    def yaxis(self,name,units):
127        """Properties of the y axis.
128        """
129        if self.prop["yunit"] and units != self.prop["yunit"]:
130            pass
131            #print "Plottable: how do we handle non-commensurate units"
132        self.prop["ylabel"] = "%s (%s)"%(name,units)
133        self.prop["yunit"] = units
134       
135    def title(self,name):
136        """Graph title
137        """
138        self.prop["title"] = name
139       
140    def get(self,key):
141        """Get the graph properties"""
142        if key=="color":
143            return self.color
144        elif key == "symbol":
145            return self.symbol
146        else:
147            return self.prop[key]
148
149    def set(self,**kw):
150        """Set the graph properties"""
151        for key in kw:
152            if key == "color":
153                self.color = kw[key]%len(self.colorlist)
154            elif key == "symbol":
155                self.symbol = kw[key]%len(self.symbollist)
156            else:
157                self.prop[key] = kw[key]
158
159    def isPlotted(self, plottable):
160        """Return True is the plottable is already on the graph"""
161        if plottable in self.plottables:
162            return True
163        return False 
164       
165    def add(self,plottable):
166        """Add a new plottable to the graph"""
167        # record the colour associated with the plottable
168        if not plottable in self.plottables:         
169            self.plottables[plottable]=self.color
170            self.color += plottable.colors()
171       
172    def changed(self):
173        """Detect if any graphed plottables have changed"""
174        return any([p.changed() for p in self.plottables])
175   
176    def get_range(self):
177        """
178            Return the range of all displayed plottables
179        """
180        min = None
181        max = None
182       
183        for p in self.plottables:
184            if p.hidden==True:
185                continue
186           
187            if not p.x==None:
188                for x_i in p.x:
189                    if min==None or x_i<min:
190                        min = x_i
191                    if max==None or x_i>max:
192                        max = x_i
193                       
194        return min, max
195
196    def delete(self,plottable):
197        """Remove an existing plottable from the graph"""
198        if plottable in self.plottables:
199            del self.plottables[plottable]
200        if self.color > 0:
201            self.color =  self.color -1
202        else:
203            self.color =0 
204           
205    def reset_scale(self):
206        """
207            Resets the scale transformation data to the underlying data
208        """
209        for p in self.plottables:
210            p.reset_view()
211
212    def reset(self):
213        """Reset the graph."""
214        self.color = 0
215        self.symbol = 0
216        self.prop = {"xlabel":"", "xunit":None,
217                     "ylabel":"","yunit":None,
218                     "title":""}
219        self.plottables = {}
220       
221       
222    def _make_labels(self):
223        # Find groups of related plottables
224        sets = {}
225        for p in self.plottables:
226            if p.__class__ in sets:
227                sets[p.__class__].append(p)
228            else:
229                sets[p.__class__] = [p]
230               
231        # Ask each plottable class for a set of unique labels
232        labels = {}
233        for c in sets:
234            labels.update(c.labels(sets[c]))
235       
236        return labels
237   
238    def returnPlottable(self):
239        """
240            This method returns a dictionary of plottables contained in graph
241            It is just by Plotpanel to interact with the complete list of plottables
242            inside the graph.
243        """
244        return self.plottables
245
246    def render(self,plot):
247        """Redraw the graph"""
248        plot.clear()
249        plot.properties(self.prop)
250        labels = self._make_labels()
251        for p in self.plottables:
252            p.render(plot,color=self.plottables[p],symbol=0,label=labels[p])
253        plot.render()
254   
255   
256
257    def __init__(self,**kw):
258        self.reset()
259        self.set(**kw)
260
261
262# Transform interface definition
263# No need to inherit from this class, just need to provide
264# the same methods.
265class Transform:
266    """Define a transform plugin to the plottable architecture.
267   
268    Transforms operate on axes.  The plottable defines the
269    set of transforms available for it, and the axes on which
270    they operate.  These transforms can operate on the x axis
271    only, the y axis only or on the x and y axes together.
272   
273    This infrastructure is not able to support transformations
274    such as log and polar plots as these require full control
275    over the drawing of axes and grids.
276   
277    A transform has a number of attributes.
278   
279    name: user visible name for the transform.  This will
280        appear in the context menu for the axis and the transform
281        menu for the graph.
282    type: operational axis.  This determines whether the
283        transform should appear on x,y or z axis context
284        menus, or if it should appear in the context menu for
285        the graph.
286    inventory: (not implemented)
287        a dictionary of user settable parameter names and
288        their associated types.  These should appear as keyword
289        arguments to the transform call.  For example, Fresnel
290        reflectivity requires the substrate density:
291             { 'rho': type.Value(10e-6/units.angstrom**2) }
292        Supply reasonable defaults in the callback so that
293        limited plotting clients work even though they cannot
294        set the inventory.
295    """
296       
297    def __call__(self,plottable,**kwargs):
298        """Transform the data.  Whenever a plottable is added
299        to the axes, the infrastructure will apply all required
300        transforms.  When the user selects a different representation
301        for the axes (via menu, script, or context menu), all
302        plottables on the axes will be transformed.  The
303        plottable should store the underlying data but set
304        the standard x,dx,y,dy,z,dz attributes appropriately.
305       
306        If the call raises a NotImplemented error the dataline
307        will not be plotted.  The associated string will usually
308        be 'Not a valid transform', though other strings are possible.
309        The application may or may not display the message to the
310        user, along with an indication of which plottable was at fault.
311        """
312        raise NotImplemented,"Not a valid transform"
313
314    # Related issues
315    # ==============
316    #
317    # log scale:
318    #    All axes have implicit log/linear scaling options.
319    #
320    # normalization:
321    #    Want to display raw counts vs detector efficiency correction
322    #    Want to normalize by time/monitor/proton current/intensity.
323    #    Want to display by eg. counts per 3 sec or counts per 10000 monitor.
324    #    Want to divide by footprint (ab initio, fitted or measured).
325    #    Want to scale by attenuator values.
326    #
327    # compare/contrast:
328    #    Want to average all visible lines with the same tag, and
329    #    display difference from one particular line.  Not a transform
330    #    issue?
331    #
332    # multiline graph:
333    #    How do we show/hide data parts.  E.g., data or theory, or
334    #    different polarization cross sections?  One way is with
335    #    tags: each plottable has a set of tags and the tags are
336    #    listed as check boxes above the plotting area.  Click a
337    #    tag and all plottables with that tag are hidden on the
338    #    plot and on the legend.
339    #
340    # nonconformant y-axes:
341    #    What do we do with temperature vs. Q and reflectivity vs. Q
342    #    on the same graph?
343    #
344    # 2D -> 1D:
345    #    Want various slices through the data.  Do transforms apply
346    #    to the sliced data as well?
347
348
349class Plottable(object):
350   
351    # Short ascii name to refer to the plottable in a menu
352    short_name = None
353   
354    # Data
355    x  = None
356    y  = None
357    dx = None
358    dy = None
359   
360    # Parameter to allow a plot to be part of the list without being displayed
361    hidden = False
362
363    def __setattr__(self, name, value):
364        """
365            Take care of changes in View when data is changed.
366            This method is provided for backward compatibility.
367        """
368        object.__setattr__(self, name, value)
369       
370        if name in ['x', 'y', 'dx', 'dy']:
371            self.reset_view()
372            #print "self.%s has been called" % name
373
374    def set_data(self, x, y, dx=None, dy=None):
375        self.x = x
376        self.y = y
377        self.dy = dy
378        self.dx = dx
379        self.transformView()
380   
381    def xaxis(self, name, units):
382        """
383            Set the name and unit of x_axis
384            @param name: the name of x-axis
385            @param units : the units of x_axis
386        """
387        self._xaxis = name
388        self._xunit = units
389
390    def yaxis(self, name, units):
391        """
392            Set the name and unit of y_axis
393            @param name: the name of y-axis
394            @param units : the units of y_axis
395        """
396        self._yaxis = name
397        self._yunit = units
398       
399    def get_xaxis(self):
400        """ Return the units and name of x-axis"""
401        return self._xaxis, self._xunit
402   
403    def get_yaxis(self):
404        """ Return the units and name of y- axis"""
405        return self._yaxis, self._yunit
406
407    @classmethod
408    def labels(cls,collection):
409        """
410        Construct a set of unique labels for a collection of plottables of
411        the same type.
412       
413        Returns a map from plottable to name.
414        """
415        n = len(collection)
416        map = {}
417        if n > 0:
418            basename = str(cls).split('.')[-1]
419            if n == 1:
420                map[collection[0]] = basename
421            else:
422                for i in xrange(len(collection)):
423                    map[collection[i]] = "%s %d"%(basename,i)
424        return map
425    ##Use the following if @classmethod doesn't work
426    # labels = classmethod(labels)
427    def setLabel(self,labelx,labely):
428        """
429            It takes a label of the x and y transformation and set View parameters
430            @param transx: The label of x transformation is sent by Properties Dialog
431            @param transy: The label of y transformation is sent Properties Dialog
432        """
433        self.view.xLabel= labelx
434        self.view.yLabel = labely
435       
436    def __init__(self):
437        self.view = View()
438        self._xaxis = ""
439        self._xunit = ""
440        self._yaxis = ""
441        self._yunit = "" 
442       
443       
444    def set_View(self,x,y):
445        """ Load View  """
446        self.x= x
447        self.y = y
448        self.reset_view()
449       
450    def reset_view(self):
451        """ Reload view with new value to plot"""
452        self.view = self.View(self.x, self.y, self.dx, self.dy)
453        self.view.Xreel = self.view.x
454        self.view.Yreel = self.view.y
455        self.view.DXreel = self.view.dx
456        self.view.DYreel = self.view.dy
457    def render(self,plot):
458        """The base class makes sure the correct units are being used for
459        subsequent plottable. 
460       
461        For now it is assumed that the graphs are commensurate, and if you
462        put a Qx object on a Temperature graph then you had better hope
463        that it makes sense.
464        """
465       
466        plot.xaxis(self._xaxis, self._xunit)
467        plot.yaxis(self._yaxis, self._yunit)
468       
469    def is_empty(self):
470        """
471            Returns True if there is no data stored in the plottable
472        """
473        if not self.x==None and len(self.x)==0 \
474            and not self.y==None and len(self.y)==0:
475            return True
476        return False
477       
478       
479    def colors(self):
480        """Return the number of colors need to render the object"""
481        return 1
482   
483    def transformView(self):
484        """
485            It transforms x, y before displaying
486        """
487        self.view.transform( self.x, self.y, self.dx,self.dy)
488       
489    def returnValuesOfView(self):
490        """
491            Return View parameters and it is used by Fit Dialog
492        """
493        return self.view.returnXview()
494   
495    def check_data_PlottableX(self): 
496        """
497            Since no transformation is made for log10(x), check that
498            no negative values is plot in log scale
499        """
500        self.view.check_data_logX()
501       
502    def check_data_PlottableY(self): 
503        """
504            Since no transformation is made for log10(y), check that
505            no negative values is plot in log scale
506        """
507        self.view.check_data_logY() 
508       
509    def transformX(self,transx,transdx):
510        """
511            Receive pointers to function that transform x and dx
512            and set corresponding View pointers
513            @param transx: pointer to function that transforms x
514            @param transdx: pointer to function that transforms dx
515        """
516        self.view.setTransformX(transx,transdx)
517       
518    def transformY(self,transy,transdy):
519        """
520            Receive pointers to function that transform y and dy
521            and set corresponding View pointers
522            @param transy: pointer to function that transforms y
523            @param transdy: pointer to function that transforms dy
524        """
525        self.view.setTransformY(transy,transdy)
526       
527    def onReset(self):
528        """
529            Reset x, y, dx, dy view with its parameters
530        """
531        self.view.onResetView()
532       
533    def onFitRange(self,xmin=None,xmax=None):
534        """
535            It limits View data range to plot from min to max
536            @param xmin: the minimum value of x to plot.
537            @param xmax: the maximum value of x to plot
538        """
539        self.view.onFitRangeView(xmin,xmax)
540       
541    class View:
542        """
543            Representation of the data that might include a transformation
544        """
545        x = None
546        y = None
547        dx = None
548        dy = None
549
550       
551        def __init__(self, x=None, y=None, dx=None, dy=None):
552            self.x = x
553            self.y = y
554            self.dx = dx
555            self.dy = dy
556            # To change x range to the reel range
557            self.Xreel = self.x
558            self.Yreel = self.y
559            self.DXreel = self.dx
560            self.DYreel = self.dy
561            # Labels of x and y received from Properties Dialog
562            self.xLabel =""
563            self.yLabel =""
564            # Function to transform x, y, dx and dy
565            self.funcx= None
566            self.funcy= None
567            self.funcdx= None
568            self.funcdy= None
569
570        def transform(self, x=None,y=None,dx=None, dy=None):
571            """
572                Transforms the x,y,dx and dy vectors and stores the output in View parameters
573       
574                @param x: array of x values
575                @param y: array of y values
576                @param dx: array of  errors values on x
577                @param dy: array of error values on y
578            """
579           
580            # Sanity check
581            # Do the transofrmation only when x and y are empty
582            has_err_x = not (dx==None or len(dx)==0)
583            has_err_y = not (dy==None or len(dy)==0)
584           
585            if (x!=None) and (y!=None): 
586                if not dx==None and not len(dx)==0 and not len(x)==len(dx):
587                        raise ValueError, "Plottable.View: Given x and dx are not of the same length" 
588                # Check length of y array
589                if not len(y)==len(x):
590                    raise ValueError, "Plottable.View: Given y and x are not of the same length"
591           
592                if not dy==None and not len(dy)==0 and not len(y)==len(dy):
593                    message = "Plottable.View: Given y and dy are not of the same length: len(y)=%s, len(dy)=%s" %(len(y), len(dy))
594                    raise ValueError, message
595               
596               
597                self.x = []
598                self.y = []
599                if has_err_x:
600                    self.dx = []
601                else:
602                    self.dx = None
603                if has_err_y:
604                    self.dy = []
605                else:
606                    self.dy = None
607                tempx=[]
608                tempy=[]
609               
610                if not has_err_x:
611                    dx=numpy.zeros(len(x))
612                if not has_err_y:
613                    dy=numpy.zeros(len(y))
614             
615                for i in range(len(x)):
616                    try:
617                         tempx =self.funcx(x[i],y[i])
618                         tempy =self.funcy(y[i],x[i])
619                         if has_err_x:
620                             tempdx = self.funcdx(x[i], y[i], dx[i], dy[i])
621                         if has_err_y:
622                             tempdy = self.funcdy(y[i], x[i], dy[i], dx[i])
623                       
624                         self.x.append(tempx)
625                         self.y.append(tempy)
626                         if has_err_x:
627                             self.dx.append(tempdx)
628                         if has_err_y:
629                             self.dy.append(tempdy)
630                    except:
631                         tempx=x[i]
632                         tempy=y[i]
633                         print "View.transform: skipping point x=%g y=%g" % (x[i], y[i])
634                         
635                         print sys.exc_value 
636               
637                # Sanity check
638                if not len(self.x)==len(self.y):
639                    raise ValueError, "Plottable.View: transformed x and y are not of the same length" 
640                if has_err_x and not (len(self.x) and len(self.dx)):
641                    raise ValueError, "Plottable.View: transformed x and dx are not of the same length" 
642                if has_err_y and not (len(self.y) and len(self.dy)):
643                    raise ValueError, "Plottable.View: transformed y and dy are not of the same length" 
644               
645                # Check that negative values are not plot on x and y axis for log10 transformation
646                self.check_data_logX()
647                self.check_data_logY()
648                # Store x ,y dx,and dy in their full range for reset
649                self.Xreel = self.x
650                self.Yreel = self.y
651                self.DXreel = self.dx
652                self.DYreel = self.dy
653               
654               
655        def onResetView(self):
656            """
657                Reset x,y,dx and y in their full range  and in the initial scale
658                in case their previous range has changed 
659            """
660            self.x = self.Xreel
661            self.y = self.Yreel
662            self.dx = self.DXreel
663            self.dy = self.DYreel
664           
665        def setTransformX(self,funcx,funcdx): 
666            """
667                Receive pointers to function that transform x and dx
668                and set corresponding View pointers
669                @param transx: pointer to function that transforms x
670                @param transdx: pointer to function that transforms dx
671            """
672            self.funcx= funcx
673            self.funcdx= funcdx
674           
675        def setTransformY(self,funcy,funcdy):
676            """
677                Receive pointers to function that transform y and dy
678                and set corresponding View pointers
679                @param transx: pointer to function that transforms y
680                @param transdx: pointer to function that transforms dy
681            """   
682            self.funcy= funcy
683            self.funcdy= funcdy
684       
685        def returnXview(self):
686            """
687                Return View  x,y,dx,dy
688            """
689            return self.x,self.y,self.dx,self.dy
690       
691     
692        def check_data_logX(self): 
693            """
694                 Remove negative value in x vector
695                 to avoid plotting negative value of Log10
696            """
697            tempx=[]
698            tempdx=[]
699            tempy=[]
700            tempdy=[]
701            if self.dx==None:
702                self.dx=numpy.zeros(len(self.x))
703            if self.dy==None:
704                self.dy=numpy.zeros(len(self.y))
705            if self.xLabel=="log10(x)" :
706                for i in range(len(self.x)):
707                    try:
708                        if (self.x[i]> 0):
709                           
710                            tempx.append(self.x[i])
711                            tempdx.append(self.dx[i])
712                            tempy.append(self.y[i])
713                            tempdy.append(self.dy[i])
714                    except:
715                        print "check_data_logX: skipping point x %g" %self.x[i]
716                        print sys.exc_value 
717                        pass 
718           
719                self.x = tempx
720                self.y = tempy
721                self.dx = tempdx
722                self.dy = tempdy
723           
724        def check_data_logY(self): 
725            """
726                 Remove negative value in y vector
727                 to avoid plotting negative value of Log10
728            """
729            tempx=[]
730            tempdx=[]
731            tempy=[]
732            tempdy=[]
733            if self.dx==None:
734                self.dx=numpy.zeros(len(self.x))
735            if self.dy==None:
736                self.dy=numpy.zeros(len(self.y))
737            if (self.yLabel == "log10(y)" ):
738                for i in range(len(self.x)):
739                     try:
740                        if (self.y[i]> 0):
741                            tempx.append(self.x[i])
742                            tempdx.append(self.dx[i])
743                            tempy.append(self.y[i])
744                            tempdy.append(self.dy[i])
745                     except:
746                        print "check_data_logY: skipping point %g" %self.y[i]
747                        print sys.exc_value 
748                        pass
749             
750                self.x = tempx
751                self.y = tempy
752                self.dx = tempdx
753                self.dy = tempdy
754               
755        def onFitRangeView(self,xmin=None,xmax=None):
756            """
757                It limits View data range to plot from min to max
758                @param xmin: the minimum value of x to plot.
759                @param xmax: the maximum value of x to plot
760            """
761            tempx=[]
762            tempdx=[]
763            tempy=[]
764            tempdy=[]
765            if self.dx==None:
766                self.dx=numpy.zeros(len(self.x))
767            if self.dy==None:
768                self.dy=numpy.zeros(len(self.y))
769            if ( xmin != None ) and ( xmax != None ):
770                for i in range(len(self.x)):
771                    if ( self.x[i] >= xmin ) and ( self.x[i] <= xmax ):
772                        tempx.append(self.x[i])
773                        tempdx.append(self.dx[i])
774                        tempy.append(self.y[i])
775                        tempdy.append(self.dy[i])
776                self.x=tempx
777                self.y=tempy
778                self.dx=tempdx
779                self.dy=tempdy       
780
781class Data1D(Plottable):
782    """Data plottable: scatter plot of x,y with errors in x and y.
783    """
784   
785    def __init__(self,x,y,dx=None,dy=None):
786        """Draw points specified by x[i],y[i] in the current color/symbol.
787        Uncertainty in x is given by dx[i], or by (xlo[i],xhi[i]) if the
788        uncertainty is asymmetric.  Similarly for y uncertainty.
789
790        The title appears on the legend.
791        The label, if it is different, appears on the status bar.
792        """
793        self.name = "data"
794        self.x = x
795        self.y = y
796        self.dx = dx
797        self.dy = dy
798        self.xaxis( '', '')
799        self.yaxis( '', '')
800        self.view = self.View(self.x, self.y, self.dx, self.dy)
801       
802    def render(self,plot,**kw):
803        plot.points(self.view.x,self.view.y,dx=self.view.dx,dy=self.view.dy,**kw)
804     
805   
806    def changed(self):
807        return False
808
809    @classmethod
810    def labels(cls, collection):
811        """Build a label mostly unique within a collection"""
812        map = {}
813        for item in collection:
814            #map[item] = label(item, collection)
815            map[item] = r"$\rm{%s}$" % item.name
816        return map
817   
818class Theory1D(Plottable):
819    """Theory plottable: line plot of x,y with confidence interval y.
820    """
821    def __init__(self,x,y,dy=None):
822        """Draw lines specified in x[i],y[i] in the current color/symbol.
823        Confidence intervals in x are given by dx[i] or by (xlo[i],xhi[i])
824        if the limits are asymmetric.
825       
826        The title is the name that will show up on the legend.
827        """
828        self.name= "theory"
829        self.x = x
830        self.y = y
831        self.dy = dy
832        self.xaxis( '', '')
833        self.yaxis( '', '')
834        self.view = self.View(self.x, self.y, None, self.dy)
835       
836    def render(self,plot,**kw):
837        plot.curve(self.view.x,self.view.y,dy=self.view.dy,**kw)
838
839    def changed(self):
840        return False
841   
842    @classmethod
843    def labels(cls, collection):
844        """Build a label mostly unique within a collection"""
845        map = {}
846        for item in collection:
847            #map[item] = label(item, collection)
848            map[item] = r"$\rm{%s}$" % item.name
849        return map
850   
851   
852class Fit1D(Plottable):
853    """Fit plottable: composed of a data line plus a theory line.  This
854    is treated like a single object from the perspective of the graph,
855    except that it will have two legend entries, one for the data and
856    one for the theory.
857
858    The color of the data and theory will be shared."""
859
860    def __init__(self,data=None,theory=None):
861        self.data=data
862        self.theory=theory
863
864    def render(self,plot,**kw):
865        self.data.render(plot,**kw)
866        self.theory.render(plot,**kw)
867
868    def changed(self):
869        return self.data.changed() or self.theory.changed()
870
871######################################################
872
873def sample_graph():
874    import numpy as nx
875   
876    # Construct a simple graph
877    if False:
878        x = nx.array([1,2,3,4,5,6],'d')
879        y = nx.array([4,5,6,5,4,5],'d')
880        dy = nx.array([0.2, 0.3, 0.1, 0.2, 0.9, 0.3])
881    else:
882        x = nx.linspace(0,1.,10000)
883        y = nx.sin(2*nx.pi*x*2.8)
884        dy = nx.sqrt(100*nx.abs(y))/100
885    data = Data1D(x,y,dy=dy)
886    data.xaxis('distance', 'm')
887    data.yaxis('time', 's')
888    graph = Graph()
889    graph.title('Walking Results')
890    graph.add(data)
891    graph.add(Theory1D(x,y,dy=dy))
892
893    return graph
894
895def demo_plotter(graph):
896    import wx
897    #from pylab_plottables import Plotter
898    from mplplotter import Plotter
899
900    # Make a frame to show it
901    app = wx.PySimpleApp()
902    frame = wx.Frame(None,-1,'Plottables')
903    plotter = Plotter(frame)
904    frame.Show()
905
906    # render the graph to the pylab plotter
907    graph.render(plotter)
908   
909    class GraphUpdate:
910        callnum=0
911        def __init__(self,graph,plotter):
912            self.graph,self.plotter = graph,plotter
913        def __call__(self):
914            if self.graph.changed(): 
915                self.graph.render(self.plotter)
916                return True
917            return False
918        def onIdle(self,event):
919            #print "On Idle checker %d"%(self.callnum)
920            self.callnum = self.callnum+1
921            if self.__call__(): 
922                pass # event.RequestMore()
923    update = GraphUpdate(graph,plotter)
924    frame.Bind(wx.EVT_IDLE,update.onIdle)
925    app.MainLoop()
926
927import sys; print sys.version
928if __name__ == "__main__":
929    demo_plotter(sample_graph())
930   
Note: See TracBrowser for help on using the repository browser.