source: sasview/guitools/plottables.py @ c3ee5b0

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 c3ee5b0 was 4972de2, checked in by Mathieu Doucet <doucetm@…>, 16 years ago

Updated for interactive graphs.

  • Property mode set to 100644
File size: 33.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 get_plottable(self, name):
239        """
240            Return the plottable with the given
241            name if it exists. Otherwise return None
242        """
243        for item in self.plottables:
244            if item.name==name:
245                return item
246        return None
247   
248    def returnPlottable(self):
249        """
250            This method returns a dictionary of plottables contained in graph
251            It is just by Plotpanel to interact with the complete list of plottables
252            inside the graph.
253        """
254        return self.plottables
255
256    def render(self,plot):
257        """Redraw the graph"""
258        plot.connect.clearall()
259       
260        plot.clear()
261        plot.properties(self.prop)
262        labels = self._make_labels()
263        for p in self.plottables:
264            p.render(plot,color=self.plottables[p],symbol=0,label=labels[p])
265        plot.render()
266   
267   
268
269    def __init__(self,**kw):
270        self.reset()
271        self.set(**kw)
272       
273        # Name of selected plottable, if any
274        self.selected_plottable = None
275
276
277# Transform interface definition
278# No need to inherit from this class, just need to provide
279# the same methods.
280class Transform:
281    """Define a transform plugin to the plottable architecture.
282   
283    Transforms operate on axes.  The plottable defines the
284    set of transforms available for it, and the axes on which
285    they operate.  These transforms can operate on the x axis
286    only, the y axis only or on the x and y axes together.
287   
288    This infrastructure is not able to support transformations
289    such as log and polar plots as these require full control
290    over the drawing of axes and grids.
291   
292    A transform has a number of attributes.
293   
294    name: user visible name for the transform.  This will
295        appear in the context menu for the axis and the transform
296        menu for the graph.
297    type: operational axis.  This determines whether the
298        transform should appear on x,y or z axis context
299        menus, or if it should appear in the context menu for
300        the graph.
301    inventory: (not implemented)
302        a dictionary of user settable parameter names and
303        their associated types.  These should appear as keyword
304        arguments to the transform call.  For example, Fresnel
305        reflectivity requires the substrate density:
306             { 'rho': type.Value(10e-6/units.angstrom**2) }
307        Supply reasonable defaults in the callback so that
308        limited plotting clients work even though they cannot
309        set the inventory.
310    """
311       
312    def __call__(self,plottable,**kwargs):
313        """Transform the data.  Whenever a plottable is added
314        to the axes, the infrastructure will apply all required
315        transforms.  When the user selects a different representation
316        for the axes (via menu, script, or context menu), all
317        plottables on the axes will be transformed.  The
318        plottable should store the underlying data but set
319        the standard x,dx,y,dy,z,dz attributes appropriately.
320       
321        If the call raises a NotImplemented error the dataline
322        will not be plotted.  The associated string will usually
323        be 'Not a valid transform', though other strings are possible.
324        The application may or may not display the message to the
325        user, along with an indication of which plottable was at fault.
326        """
327        raise NotImplemented,"Not a valid transform"
328
329    # Related issues
330    # ==============
331    #
332    # log scale:
333    #    All axes have implicit log/linear scaling options.
334    #
335    # normalization:
336    #    Want to display raw counts vs detector efficiency correction
337    #    Want to normalize by time/monitor/proton current/intensity.
338    #    Want to display by eg. counts per 3 sec or counts per 10000 monitor.
339    #    Want to divide by footprint (ab initio, fitted or measured).
340    #    Want to scale by attenuator values.
341    #
342    # compare/contrast:
343    #    Want to average all visible lines with the same tag, and
344    #    display difference from one particular line.  Not a transform
345    #    issue?
346    #
347    # multiline graph:
348    #    How do we show/hide data parts.  E.g., data or theory, or
349    #    different polarization cross sections?  One way is with
350    #    tags: each plottable has a set of tags and the tags are
351    #    listed as check boxes above the plotting area.  Click a
352    #    tag and all plottables with that tag are hidden on the
353    #    plot and on the legend.
354    #
355    # nonconformant y-axes:
356    #    What do we do with temperature vs. Q and reflectivity vs. Q
357    #    on the same graph?
358    #
359    # 2D -> 1D:
360    #    Want various slices through the data.  Do transforms apply
361    #    to the sliced data as well?
362
363
364class Plottable(object):
365   
366    # Short ascii name to refer to the plottable in a menu
367    short_name = None
368   
369    # Fancy name
370    name = None
371   
372    # Data
373    x  = None
374    y  = None
375    dx = None
376    dy = None
377   
378    # Parameter to allow a plot to be part of the list without being displayed
379    hidden = False
380   
381    # Flag to set whether a plottable has an interactor or not
382    interactive = True
383
384    def __setattr__(self, name, value):
385        """
386            Take care of changes in View when data is changed.
387            This method is provided for backward compatibility.
388        """
389        object.__setattr__(self, name, value)
390       
391        if name in ['x', 'y', 'dx', 'dy']:
392            self.reset_view()
393            #print "self.%s has been called" % name
394
395    def set_data(self, x, y, dx=None, dy=None):
396        self.x = x
397        self.y = y
398        self.dy = dy
399        self.dx = dx
400        self.transformView()
401   
402    def xaxis(self, name, units):
403        """
404            Set the name and unit of x_axis
405            @param name: the name of x-axis
406            @param units : the units of x_axis
407        """
408        self._xaxis = name
409        self._xunit = units
410
411    def yaxis(self, name, units):
412        """
413            Set the name and unit of y_axis
414            @param name: the name of y-axis
415            @param units : the units of y_axis
416        """
417        self._yaxis = name
418        self._yunit = units
419       
420    def get_xaxis(self):
421        """ Return the units and name of x-axis"""
422        return self._xaxis, self._xunit
423   
424    def get_yaxis(self):
425        """ Return the units and name of y- axis"""
426        return self._yaxis, self._yunit
427
428    @classmethod
429    def labels(cls,collection):
430        """
431        Construct a set of unique labels for a collection of plottables of
432        the same type.
433       
434        Returns a map from plottable to name.
435        """
436        n = len(collection)
437        map = {}
438        if n > 0:
439            basename = str(cls).split('.')[-1]
440            if n == 1:
441                map[collection[0]] = basename
442            else:
443                for i in xrange(len(collection)):
444                    map[collection[i]] = "%s %d"%(basename,i)
445        return map
446    ##Use the following if @classmethod doesn't work
447    # labels = classmethod(labels)
448    def setLabel(self,labelx,labely):
449        """
450            It takes a label of the x and y transformation and set View parameters
451            @param transx: The label of x transformation is sent by Properties Dialog
452            @param transy: The label of y transformation is sent Properties Dialog
453        """
454        self.view.xLabel= labelx
455        self.view.yLabel = labely
456       
457    def __init__(self):
458        self.view = View()
459        self._xaxis = ""
460        self._xunit = ""
461        self._yaxis = ""
462        self._yunit = "" 
463       
464       
465    def set_View(self,x,y):
466        """ Load View  """
467        self.x= x
468        self.y = y
469        self.reset_view()
470       
471    def reset_view(self):
472        """ Reload view with new value to plot"""
473        self.view = self.View(self.x, self.y, self.dx, self.dy)
474        self.view.Xreel = self.view.x
475        self.view.Yreel = self.view.y
476        self.view.DXreel = self.view.dx
477        self.view.DYreel = self.view.dy
478    def render(self,plot):
479        """The base class makes sure the correct units are being used for
480        subsequent plottable. 
481       
482        For now it is assumed that the graphs are commensurate, and if you
483        put a Qx object on a Temperature graph then you had better hope
484        that it makes sense.
485        """
486       
487        plot.xaxis(self._xaxis, self._xunit)
488        plot.yaxis(self._yaxis, self._yunit)
489       
490    def is_empty(self):
491        """
492            Returns True if there is no data stored in the plottable
493        """
494        if not self.x==None and len(self.x)==0 \
495            and not self.y==None and len(self.y)==0:
496            return True
497        return False
498       
499       
500    def colors(self):
501        """Return the number of colors need to render the object"""
502        return 1
503   
504    def transformView(self):
505        """
506            It transforms x, y before displaying
507        """
508        self.view.transform( self.x, self.y, self.dx,self.dy)
509       
510    def returnValuesOfView(self):
511        """
512            Return View parameters and it is used by Fit Dialog
513        """
514        return self.view.returnXview()
515   
516    def check_data_PlottableX(self): 
517        """
518            Since no transformation is made for log10(x), check that
519            no negative values is plot in log scale
520        """
521        self.view.check_data_logX()
522       
523    def check_data_PlottableY(self): 
524        """
525            Since no transformation is made for log10(y), check that
526            no negative values is plot in log scale
527        """
528        self.view.check_data_logY() 
529       
530    def transformX(self,transx,transdx):
531        """
532            Receive pointers to function that transform x and dx
533            and set corresponding View pointers
534            @param transx: pointer to function that transforms x
535            @param transdx: pointer to function that transforms dx
536        """
537        self.view.setTransformX(transx,transdx)
538       
539    def transformY(self,transy,transdy):
540        """
541            Receive pointers to function that transform y and dy
542            and set corresponding View pointers
543            @param transy: pointer to function that transforms y
544            @param transdy: pointer to function that transforms dy
545        """
546        self.view.setTransformY(transy,transdy)
547       
548    def onReset(self):
549        """
550            Reset x, y, dx, dy view with its parameters
551        """
552        self.view.onResetView()
553       
554    def onFitRange(self,xmin=None,xmax=None):
555        """
556            It limits View data range to plot from min to max
557            @param xmin: the minimum value of x to plot.
558            @param xmax: the maximum value of x to plot
559        """
560        self.view.onFitRangeView(xmin,xmax)
561       
562    class View:
563        """
564            Representation of the data that might include a transformation
565        """
566        x = None
567        y = None
568        dx = None
569        dy = None
570
571       
572        def __init__(self, x=None, y=None, dx=None, dy=None):
573            self.x = x
574            self.y = y
575            self.dx = dx
576            self.dy = dy
577            # To change x range to the reel range
578            self.Xreel = self.x
579            self.Yreel = self.y
580            self.DXreel = self.dx
581            self.DYreel = self.dy
582            # Labels of x and y received from Properties Dialog
583            self.xLabel =""
584            self.yLabel =""
585            # Function to transform x, y, dx and dy
586            self.funcx= None
587            self.funcy= None
588            self.funcdx= None
589            self.funcdy= None
590
591        def transform(self, x=None,y=None,dx=None, dy=None):
592            """
593                Transforms the x,y,dx and dy vectors and stores the output in View parameters
594       
595                @param x: array of x values
596                @param y: array of y values
597                @param dx: array of  errors values on x
598                @param dy: array of error values on y
599            """
600           
601            # Sanity check
602            # Do the transofrmation only when x and y are empty
603            has_err_x = not (dx==None or len(dx)==0)
604            has_err_y = not (dy==None or len(dy)==0)
605           
606            if (x!=None) and (y!=None): 
607                if not dx==None and not len(dx)==0 and not len(x)==len(dx):
608                        raise ValueError, "Plottable.View: Given x and dx are not of the same length" 
609                # Check length of y array
610                if not len(y)==len(x):
611                    raise ValueError, "Plottable.View: Given y and x are not of the same length"
612           
613                if not dy==None and not len(dy)==0 and not len(y)==len(dy):
614                    message = "Plottable.View: Given y and dy are not of the same length: len(y)=%s, len(dy)=%s" %(len(y), len(dy))
615                    raise ValueError, message
616               
617               
618                self.x = []
619                self.y = []
620                if has_err_x:
621                    self.dx = []
622                else:
623                    self.dx = None
624                if has_err_y:
625                    self.dy = []
626                else:
627                    self.dy = None
628                tempx=[]
629                tempy=[]
630               
631                if not has_err_x:
632                    dx=numpy.zeros(len(x))
633                if not has_err_y:
634                    dy=numpy.zeros(len(y))
635             
636                for i in range(len(x)):
637                    try:
638                         tempx =self.funcx(x[i],y[i])
639                         tempy =self.funcy(y[i],x[i])
640                         if has_err_x:
641                             tempdx = self.funcdx(x[i], y[i], dx[i], dy[i])
642                         if has_err_y:
643                             tempdy = self.funcdy(y[i], x[i], dy[i], dx[i])
644                       
645                         self.x.append(tempx)
646                         self.y.append(tempy)
647                         if has_err_x:
648                             self.dx.append(tempdx)
649                         if has_err_y:
650                             self.dy.append(tempdy)
651                    except:
652                         tempx=x[i]
653                         tempy=y[i]
654                         print "View.transform: skipping point x=%g y=%g" % (x[i], y[i])
655                         
656                         print sys.exc_value 
657               
658                # Sanity check
659                if not len(self.x)==len(self.y):
660                    raise ValueError, "Plottable.View: transformed x and y are not of the same length" 
661                if has_err_x and not (len(self.x) and len(self.dx)):
662                    raise ValueError, "Plottable.View: transformed x and dx are not of the same length" 
663                if has_err_y and not (len(self.y) and len(self.dy)):
664                    raise ValueError, "Plottable.View: transformed y and dy are not of the same length" 
665               
666                # Check that negative values are not plot on x and y axis for log10 transformation
667                self.check_data_logX()
668                self.check_data_logY()
669                # Store x ,y dx,and dy in their full range for reset
670                self.Xreel = self.x
671                self.Yreel = self.y
672                self.DXreel = self.dx
673                self.DYreel = self.dy
674               
675               
676        def onResetView(self):
677            """
678                Reset x,y,dx and y in their full range  and in the initial scale
679                in case their previous range has changed 
680            """
681            self.x = self.Xreel
682            self.y = self.Yreel
683            self.dx = self.DXreel
684            self.dy = self.DYreel
685           
686        def setTransformX(self,funcx,funcdx): 
687            """
688                Receive pointers to function that transform x and dx
689                and set corresponding View pointers
690                @param transx: pointer to function that transforms x
691                @param transdx: pointer to function that transforms dx
692            """
693            self.funcx= funcx
694            self.funcdx= funcdx
695           
696        def setTransformY(self,funcy,funcdy):
697            """
698                Receive pointers to function that transform y and dy
699                and set corresponding View pointers
700                @param transx: pointer to function that transforms y
701                @param transdx: pointer to function that transforms dy
702            """   
703            self.funcy= funcy
704            self.funcdy= funcdy
705       
706        def returnXview(self):
707            """
708                Return View  x,y,dx,dy
709            """
710            return self.x,self.y,self.dx,self.dy
711       
712     
713        def check_data_logX(self): 
714            """
715                 Remove negative value in x vector
716                 to avoid plotting negative value of Log10
717            """
718            tempx=[]
719            tempdx=[]
720            tempy=[]
721            tempdy=[]
722            if self.dx==None:
723                self.dx=numpy.zeros(len(self.x))
724            if self.dy==None:
725                self.dy=numpy.zeros(len(self.y))
726            if self.xLabel=="log10(x)" :
727                for i in range(len(self.x)):
728                    try:
729                        if (self.x[i]> 0):
730                           
731                            tempx.append(self.x[i])
732                            tempdx.append(self.dx[i])
733                            tempy.append(self.y[i])
734                            tempdy.append(self.dy[i])
735                    except:
736                        print "check_data_logX: skipping point x %g" %self.x[i]
737                        print sys.exc_value 
738                        pass 
739           
740                self.x = tempx
741                self.y = tempy
742                self.dx = tempdx
743                self.dy = tempdy
744           
745        def check_data_logY(self): 
746            """
747                 Remove negative value in y vector
748                 to avoid plotting negative value of Log10
749            """
750            tempx=[]
751            tempdx=[]
752            tempy=[]
753            tempdy=[]
754            if self.dx==None:
755                self.dx=numpy.zeros(len(self.x))
756            if self.dy==None:
757                self.dy=numpy.zeros(len(self.y))
758            if (self.yLabel == "log10(y)" ):
759                for i in range(len(self.x)):
760                     try:
761                        if (self.y[i]> 0):
762                            tempx.append(self.x[i])
763                            tempdx.append(self.dx[i])
764                            tempy.append(self.y[i])
765                            tempdy.append(self.dy[i])
766                     except:
767                        print "check_data_logY: skipping point %g" %self.y[i]
768                        print sys.exc_value 
769                        pass
770             
771                self.x = tempx
772                self.y = tempy
773                self.dx = tempdx
774                self.dy = tempdy
775               
776        def onFitRangeView(self,xmin=None,xmax=None):
777            """
778                It limits View data range to plot from min to max
779                @param xmin: the minimum value of x to plot.
780                @param xmax: the maximum value of x to plot
781            """
782            tempx=[]
783            tempdx=[]
784            tempy=[]
785            tempdy=[]
786            if self.dx==None:
787                self.dx=numpy.zeros(len(self.x))
788            if self.dy==None:
789                self.dy=numpy.zeros(len(self.y))
790            if ( xmin != None ) and ( xmax != None ):
791                for i in range(len(self.x)):
792                    if ( self.x[i] >= xmin ) and ( self.x[i] <= xmax ):
793                        tempx.append(self.x[i])
794                        tempdx.append(self.dx[i])
795                        tempy.append(self.y[i])
796                        tempdy.append(self.dy[i])
797                self.x=tempx
798                self.y=tempy
799                self.dx=tempdx
800                self.dy=tempdy       
801
802class Data1D(Plottable):
803    """Data plottable: scatter plot of x,y with errors in x and y.
804    """
805   
806    def __init__(self,x,y,dx=None,dy=None):
807        """Draw points specified by x[i],y[i] in the current color/symbol.
808        Uncertainty in x is given by dx[i], or by (xlo[i],xhi[i]) if the
809        uncertainty is asymmetric.  Similarly for y uncertainty.
810
811        The title appears on the legend.
812        The label, if it is different, appears on the status bar.
813        """
814        self.name = "data"
815        self.x = x
816        self.y = y
817        self.dx = dx
818        self.dy = dy
819        self.xaxis( '', '')
820        self.yaxis( '', '')
821        self.view = self.View(self.x, self.y, self.dx, self.dy)
822       
823    def render(self,plot,**kw):
824        """
825            Renders the plottable on the graph
826        """
827        if self.interactive==True:
828            plot.interactive_points(self.view.x,self.view.y,
829                                    dx=self.view.dx,dy=self.view.dy,
830                                    name=self.name, **kw)           
831        else:
832            plot.points(self.view.x,self.view.y,dx=self.view.dx,dy=self.view.dy,**kw)
833   
834    def changed(self):
835        return False
836
837    @classmethod
838    def labels(cls, collection):
839        """Build a label mostly unique within a collection"""
840        map = {}
841        for item in collection:
842            #map[item] = label(item, collection)
843            map[item] = r"$\rm{%s}$" % item.name
844        return map
845   
846class Theory1D(Plottable):
847    """Theory plottable: line plot of x,y with confidence interval y.
848    """
849    def __init__(self,x,y,dy=None):
850        """Draw lines specified in x[i],y[i] in the current color/symbol.
851        Confidence intervals in x are given by dx[i] or by (xlo[i],xhi[i])
852        if the limits are asymmetric.
853       
854        The title is the name that will show up on the legend.
855        """
856        self.name= "theory"
857        self.x = x
858        self.y = y
859        self.dy = dy
860        self.xaxis( '', '')
861        self.yaxis( '', '')
862        self.view = self.View(self.x, self.y, None, self.dy)
863       
864    def render(self,plot,**kw):
865        if self.interactive==True:
866            plot.interactive_curve(self.view.x,self.view.y,
867                                   dy=self.view.dy,
868                                   name=self.name,**kw)
869        else:
870            plot.curve(self.view.x,self.view.y,dy=self.view.dy,**kw)
871           
872
873    def changed(self):
874        return False
875   
876    @classmethod
877    def labels(cls, collection):
878        """Build a label mostly unique within a collection"""
879        map = {}
880        for item in collection:
881            #map[item] = label(item, collection)
882            map[item] = r"$\rm{%s}$" % item.name
883        return map
884   
885   
886class Fit1D(Plottable):
887    """Fit plottable: composed of a data line plus a theory line.  This
888    is treated like a single object from the perspective of the graph,
889    except that it will have two legend entries, one for the data and
890    one for the theory.
891
892    The color of the data and theory will be shared."""
893
894    def __init__(self,data=None,theory=None):
895        self.data=data
896        self.theory=theory
897
898    def render(self,plot,**kw):
899        self.data.render(plot,**kw)
900        self.theory.render(plot,**kw)
901
902    def changed(self):
903        return self.data.changed() or self.theory.changed()
904
905######################################################
906
907def sample_graph():
908    import numpy as nx
909   
910    # Construct a simple graph
911    if False:
912        x = nx.array([1,2,3,4,5,6],'d')
913        y = nx.array([4,5,6,5,4,5],'d')
914        dy = nx.array([0.2, 0.3, 0.1, 0.2, 0.9, 0.3])
915    else:
916        x = nx.linspace(0,1.,10000)
917        y = nx.sin(2*nx.pi*x*2.8)
918        dy = nx.sqrt(100*nx.abs(y))/100
919    data = Data1D(x,y,dy=dy)
920    data.xaxis('distance', 'm')
921    data.yaxis('time', 's')
922    graph = Graph()
923    graph.title('Walking Results')
924    graph.add(data)
925    graph.add(Theory1D(x,y,dy=dy))
926
927    return graph
928
929def demo_plotter(graph):
930    import wx
931    #from pylab_plottables import Plotter
932    from mplplotter import Plotter
933
934    # Make a frame to show it
935    app = wx.PySimpleApp()
936    frame = wx.Frame(None,-1,'Plottables')
937    plotter = Plotter(frame)
938    frame.Show()
939
940    # render the graph to the pylab plotter
941    graph.render(plotter)
942   
943    class GraphUpdate:
944        callnum=0
945        def __init__(self,graph,plotter):
946            self.graph,self.plotter = graph,plotter
947        def __call__(self):
948            if self.graph.changed(): 
949                self.graph.render(self.plotter)
950                return True
951            return False
952        def onIdle(self,event):
953            #print "On Idle checker %d"%(self.callnum)
954            self.callnum = self.callnum+1
955            if self.__call__(): 
956                pass # event.RequestMore()
957    update = GraphUpdate(graph,plotter)
958    frame.Bind(wx.EVT_IDLE,update.onIdle)
959    app.MainLoop()
960
961import sys; print sys.version
962if __name__ == "__main__":
963    demo_plotter(sample_graph())
964   
Note: See TracBrowser for help on using the repository browser.