source: sasview/guitools/plottables.py @ 3755d77

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 3755d77 was 08b9e586, checked in by Mathieu Doucet <doucetm@…>, 16 years ago

Updated plot legend behavior

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