source: sasview/guitools/plottables.py @ 6d19988

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 6d19988 was a17ffdf, checked in by Mathieu Doucet <doucetm@…>, 16 years ago

Fixed color management

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