source: sasview/guitools/plottables.py @ a1718df

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 a1718df was b43a009, checked in by Gervaise Alina <gervyh@…>, 17 years ago

some modificatiosn made

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