source: sasview/plottools/src/danse/common/plottools/plottables.py @ 26b0a7c

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 26b0a7c was 82a54b8, checked in by Mathieu Doucet <doucetm@…>, 13 years ago

adding plottools Part 2

  • Property mode set to 100644
File size: 42.0 KB
Line 
1"""
2Prototype plottable object support.
3
4The main point of this prototype is to provide a clean separation between
5the style (plotter details: color, grids, widgets, etc.) and substance
6(application details: which information to plot).  Programmers should not be
7dictating line colours and plotting symbols.
8
9Unlike the problem of style in CSS or Word, where most paragraphs look
10the same, each line on a graph has to be distinguishable from its neighbours.
11Our solution is to provide parametric styles, in which a number of
12different classes of object (e.g., reflectometry data, reflectometry
13theory) representing multiple graph primitives cycle through a colour
14palette provided by the underlying plotter.
15
16A full treatment would provide perceptual dimensions of prominence and
17distinctiveness rather than a simple colour number.
18
19"""
20
21# Design question: who owns the color?
22# Is it a property of the plottable?
23# Or of the plottable as it exists on the graph?
24# Or if the graph?
25# If a plottable can appear on multiple graphs, in some case the
26# color should be the same on each graph in which it appears, and
27# in other cases (where multiple plottables from different graphs
28# coexist), the color should be assigned by the graph.  In any case
29# once a plottable is placed on the graph its color should not
30# depend on the other plottables on the graph.  Furthermore, if
31# a plottable is added and removed from a graph and added again,
32# it may be nice, but not necessary, to have the color persist.
33#
34# The safest approach seems to be to give ownership of color
35# to the graph, which will allocate the colors along with the
36# plottable.  The plottable will need to return the number of
37# colors that are needed.
38#
39# The situation is less clear for symbols.  It is less clear
40# how much the application requires that symbols be unique across
41# all plots on the graph.
42
43# Support for ancient python versions
44import copy
45import numpy
46import math
47import sys
48
49
50if 'any' not in dir(__builtins__):
51    def any(L):
52        for cond in L:
53            if cond: return True
54        return False
55    def all(L):
56        for cond in L:
57            if not cond:
58                return False
59        return True
60
61# Graph structure for holding multiple plottables
62class Graph:
63    """
64    Generic plottables graph structure.
65   
66    Plot styles are based on color/symbol lists.  The user gets to select
67    the list of colors/symbols/sizes to choose from, not the application
68    developer.  The programmer only gets to add/remove lines from the
69    plot and move to the next symbol/color.
70
71    Another dimension is prominence, which refers to line sizes/point sizes.
72
73    Axis transformations allow the user to select the coordinate view
74    which provides clarity to the data.  There is no way we can provide
75    every possible transformation for every application generically, so
76    the plottable objects themselves will need to provide the transformations.
77    Here are some examples from reflectometry: ::
78   
79       independent: x -> f(x)
80          monitor scaling: y -> M*y
81          log:  y -> log(y if y > min else min)
82          cos:  y -> cos(y*pi/180)
83       dependent:   x -> f(x,y)
84          Q4:      y -> y*x^4
85          fresnel: y -> y*fresnel(x)
86       coordinated: x,y = f(x,y)
87          Q:    x -> 2*pi/L (cos(x*pi/180) - cos(y*pi/180))
88                y -> 2*pi/L (sin(x*pi/180) + sin(y*pi/180))
89       reducing: x,y = f(x1,x2,y1,y2)
90          spin asymmetry: x -> x1, y -> (y1 - y2)/(y1 + y2)
91          vector net: x -> x1, y -> y1*cos(y2*pi/180)
92         
93    Multiple transformations are possible, such as Q4 spin asymmetry
94
95    Axes have further complications in that the units of what are being
96    plotted should correspond to the units on the axes.  Plotting multiple
97    types on the same graph should be handled gracefully, e.g., by creating
98    a separate tab for each available axis type, breaking into subplots,
99    showing multiple axes on the same plot, or generating inset plots.
100    Ultimately the decision should be left to the user.
101
102    Graph properties such as grids/crosshairs should be under user control,
103    as should the sizes of items such as axis fonts, etc.  No direct
104    access will be provided to the application.
105
106    Axis limits are mostly under user control.  If the user has zoomed or
107    panned then those limits are preserved even if new data is plotted.
108    The exception is when, e.g., scanning through a set of related lines
109    in which the user may want to fix the limits so that user can compare
110    the values directly.  Another exception is when creating multiple
111    graphs sharing the same limits, though this case may be important
112    enough that it is handled by the graph widget itself.  Axis limits
113    will of course have to understand the effects of axis transformations.
114
115    High level plottable objects may be composed of low level primitives.
116    Operations such as legend/hide/show copy/paste, etc. need to operate
117    on these primitives as a group.  E.g., allowing the user to have a
118    working canvas where they can drag lines they want to save and annotate
119    them.
120
121    Graphs need to be printable.  A page layout program for entire plots
122    would be nice.
123   
124    """
125    def _xaxis_transformed(self, name, units):
126        """
127        Change the property of the x axis
128        according to an axis transformation
129        (as opposed to changing the basic properties)
130        """
131        if units != "": 
132             name = "%s (%s)" % (name, units)
133        self.prop["xlabel"] = name
134        self.prop["xunit"] = units
135       
136    def _yaxis_transformed(self, name, units):
137        """
138        Change the property of the y axis
139        according to an axis transformation
140        (as opposed to changing the basic properties)
141        """
142        if units != "": 
143             name = "%s (%s)" % (name, units)
144        self.prop["ylabel"] = name
145        self.prop["yunit"] = units
146       
147    def xaxis(self, name, units):
148        """
149        Properties of the x axis.
150        """
151        if units != "": 
152             name = "%s (%s)" % (name, units)
153        self.prop["xlabel"] = name
154        self.prop["xunit"] = units
155        self.prop["xlabel_base"] = name
156        self.prop["xunit_base"] = units
157
158    def yaxis(self, name, units):
159        """
160        Properties of the y axis.
161        """
162        if units != "": 
163             name = "%s (%s)" % (name, units)
164        self.prop["ylabel"] = name
165        self.prop["yunit"] = units
166        self.prop["ylabel_base"] = name
167        self.prop["yunit_base"] = units
168       
169    def title(self, name):
170        """
171        Graph title
172        """
173        self.prop["title"] = name
174       
175    def get(self, key):
176        """
177        Get the graph properties
178        """
179        if key == "color":
180            return self.color
181        elif key == "symbol":
182            return self.symbol
183        else:
184            return self.prop[key]
185
186    def set(self, **kw):
187        """
188        Set the graph properties
189        """
190        for key in kw:
191            if key == "color":
192                self.color = kw[key] % len(self.colorlist)
193            elif key == "symbol":
194                self.symbol = kw[key] % len(self.symbollist)
195            else:
196                self.prop[key] = kw[key]
197
198    def isPlotted(self, plottable):
199        """Return True is the plottable is already on the graph"""
200        if plottable in self.plottables:
201            return True
202        return False 
203
204    def add(self, plottable, color=None):
205        """Add a new plottable to the graph"""
206        # record the colour associated with the plottable
207        if not plottable in self.plottables:
208            if color is not None:
209                self.plottables[plottable] = color
210            else:
211                self.color += plottable.colors()
212                self.plottables[plottable] = self.color
213               
214    def changed(self):
215        """Detect if any graphed plottables have changed"""
216        return any([p.changed() for p in self.plottables])
217   
218    def get_range(self):
219        """
220        Return the range of all displayed plottables
221        """
222        min = None
223        max = None
224        for p in self.plottables:
225            if p.hidden == True:
226                continue
227            if not p.x == None:
228                for x_i in p.x:
229                    if min == None or x_i < min:
230                        min = x_i
231                    if max == None or x_i > max:
232                        max = x_i
233        return min, max
234   
235    def replace(self, plottable):
236        """Replace an existing plottable from the graph"""
237        selected_color = None
238        selected_plottable = None
239        for p in self.plottables.keys():
240            if plottable.id == p.id:
241                selected_plottable = p
242                selected_color = self.plottables[p]
243                break
244        if  selected_plottable is not None and selected_color is not None:
245            del self.plottables[selected_plottable]
246            self.plottables[plottable] = selected_color
247
248    def delete(self, plottable):
249        """Remove an existing plottable from the graph"""
250        if plottable in self.plottables:
251            del self.plottables[plottable]
252            self.color = len(self.plottables) 
253           
254    def reset_scale(self):
255        """
256        Resets the scale transformation data to the underlying data
257        """
258        for p in self.plottables:
259            p.reset_view()
260
261    def reset(self):
262        """Reset the graph."""
263        self.color = -1
264        self.symbol = 0
265        self.prop = {"xlabel":"", "xunit":None,
266                     "ylabel":"","yunit":None,
267                     "title":""}
268        self.plottables = {}
269   
270    def _make_labels(self):
271        """
272        """
273        # Find groups of related plottables
274        sets = {}
275        for p in self.plottables:
276            if p.__class__ in sets:
277                sets[p.__class__].append(p)
278            else:
279                sets[p.__class__] = [p]
280        # Ask each plottable class for a set of unique labels
281        labels = {}
282        for c in sets:
283            labels.update(c.labels(sets[c]))
284        return labels
285   
286    def get_plottable(self, name):
287        """
288        Return the plottable with the given
289        name if it exists. Otherwise return None
290        """
291        for item in self.plottables:
292            if item.name == name:
293                return item
294        return None
295   
296    def returnPlottable(self):
297        """
298        This method returns a dictionary of plottables contained in graph
299        It is just by Plotpanel to interact with the complete list of plottables
300        inside the graph.
301        """
302        return self.plottables
303   
304    def render(self,plot):
305        """Redraw the graph"""
306        plot.connect.clearall()
307        plot.clear()
308        plot.properties(self.prop)
309        labels = self._make_labels()
310        for p in self.plottables:
311            if p.custom_color is not None:
312                p.render(plot, color=p.custom_color, symbol=0, 
313                markersize=p.markersize, label=labels[p])
314            else:
315                p.render(plot, color=self.plottables[p], symbol=0, 
316                     markersize=p.markersize, label=labels[p])
317        plot.render()
318   
319    def __init__(self, **kw):
320        self.reset()
321        self.set(**kw)
322        # Name of selected plottable, if any
323        self.selected_plottable = None
324
325
326# Transform interface definition
327# No need to inherit from this class, just need to provide
328# the same methods.
329class Transform:
330    """
331    Define a transform plugin to the plottable architecture.
332   
333    Transforms operate on axes.  The plottable defines the
334    set of transforms available for it, and the axes on which
335    they operate.  These transforms can operate on the x axis
336    only, the y axis only or on the x and y axes together.
337   
338    This infrastructure is not able to support transformations
339    such as log and polar plots as these require full control
340    over the drawing of axes and grids.
341   
342    A transform has a number of attributes.
343   
344    name: user visible name for the transform.  This will
345        appear in the context menu for the axis and the transform
346        menu for the graph.
347       
348    type: operational axis.  This determines whether the
349        transform should appear on x,y or z axis context
350        menus, or if it should appear in the context menu for
351        the graph.
352       
353    inventory: (not implemented)
354        a dictionary of user settable parameter names and
355        their associated types.  These should appear as keyword
356        arguments to the transform call.  For example, Fresnel
357        reflectivity requires the substrate density:
358             { 'rho': type.Value(10e-6/units.angstrom**2) }
359             
360        Supply reasonable defaults in the callback so that
361        limited plotting clients work even though they cannot
362        set the inventory.
363       
364    """
365    def __call__(self, plottable, **kwargs):
366        """
367        Transform the data.  Whenever a plottable is added
368        to the axes, the infrastructure will apply all required
369        transforms.  When the user selects a different representation
370        for the axes (via menu, script, or context menu), all
371        plottables on the axes will be transformed.  The
372        plottable should store the underlying data but set
373        the standard x,dx,y,dy,z,dz attributes appropriately.
374       
375        If the call raises a NotImplemented error the dataline
376        will not be plotted.  The associated string will usually
377        be 'Not a valid transform', though other strings are possible.
378        The application may or may not display the message to the
379        user, along with an indication of which plottable was at fault.
380       
381        """
382        raise NotImplemented,"Not a valid transform"
383
384    # Related issues
385    # ==============
386    #
387    # log scale:
388    #    All axes have implicit log/linear scaling options.
389    #
390    # normalization:
391    #    Want to display raw counts vs detector efficiency correction
392    #    Want to normalize by time/monitor/proton current/intensity.
393    #    Want to display by eg. counts per 3 sec or counts per 10000 monitor.
394    #    Want to divide by footprint (ab initio, fitted or measured).
395    #    Want to scale by attenuator values.
396    #
397    # compare/contrast:
398    #    Want to average all visible lines with the same tag, and
399    #    display difference from one particular line.  Not a transform
400    #    issue?
401    #
402    # multiline graph:
403    #    How do we show/hide data parts.  E.g., data or theory, or
404    #    different polarization cross sections?  One way is with
405    #    tags: each plottable has a set of tags and the tags are
406    #    listed as check boxes above the plotting area.  Click a
407    #    tag and all plottables with that tag are hidden on the
408    #    plot and on the legend.
409    #
410    # nonconformant y-axes:
411    #    What do we do with temperature vs. Q and reflectivity vs. Q
412    #    on the same graph?
413    #
414    # 2D -> 1D:
415    #    Want various slices through the data.  Do transforms apply
416    #    to the sliced data as well?
417
418
419class Plottable(object):
420    """
421    """
422    # Short ascii name to refer to the plottable in a menu   
423    short_name = None
424    # Fancy name
425    name = None
426    # Data
427    x  = None
428    y  = None
429    dx = None
430    dy = None
431    # Parameter to allow a plot to be part of the list without being displayed
432    hidden = False
433    # Flag to set whether a plottable has an interactor or not
434    interactive = True
435    custom_color = None
436    markersize = 5 #default marker size is 'size 5'
437   
438    def __init__(self):
439        self.view = View()
440        self._xaxis = ""
441        self._xunit = ""
442        self._yaxis = ""
443        self._yunit = "" 
444       
445    def __setattr__(self, name, value):
446        """
447        Take care of changes in View when data is changed.
448        This method is provided for backward compatibility.
449        """
450        object.__setattr__(self, name, value)
451        if name in ['x', 'y', 'dx', 'dy']:
452            self.reset_view()
453            #print "self.%s has been called" % name
454
455    def set_data(self, x, y, dx=None, dy=None):
456        """
457        """
458        self.x = x
459        self.y = y
460        self.dy = dy
461        self.dx = dx
462        self.transformView()
463   
464    def xaxis(self, name, units):
465        """
466        Set the name and unit of x_axis
467       
468        :param name: the name of x-axis
469        :param units: the units of x_axis
470       
471        """
472        self._xaxis = name
473        self._xunit = units
474
475    def yaxis(self, name, units):
476        """
477        Set the name and unit of y_axis
478       
479        :param name: the name of y-axis
480        :param units: the units of y_axis
481       
482        """
483        self._yaxis = name
484        self._yunit = units
485       
486    def get_xaxis(self):
487        """Return the units and name of x-axis"""
488        return self._xaxis, self._xunit
489   
490    def get_yaxis(self):
491        """ Return the units and name of y- axis"""
492        return self._yaxis, self._yunit
493
494    @classmethod
495    def labels(cls, collection):
496        """
497        Construct a set of unique labels for a collection of plottables of
498        the same type.
499       
500        Returns a map from plottable to name.
501       
502        """
503        n = len(collection)
504        map = {}
505        if n > 0:
506            basename = str(cls).split('.')[-1]
507            if n == 1:
508                map[collection[0]] = basename
509            else:
510                for i in xrange(len(collection)):
511                    map[collection[i]] = "%s %d"%(basename, i)
512        return map
513    ##Use the following if @classmethod doesn't work
514    # labels = classmethod(labels)
515    def setLabel(self, labelx, labely):
516        """
517        It takes a label of the x and y transformation and set View parameters
518       
519        :param transx: The label of x transformation is sent by Properties Dialog
520        :param transy: The label of y transformation is sent Properties Dialog
521       
522        """
523        self.view.xLabel = labelx
524        self.view.yLabel = labely
525   
526    def set_View(self, x, y):
527        """Load View"""
528        self.x= x
529        self.y = y
530        self.reset_view()
531       
532    def reset_view(self):
533        """Reload view with new value to plot"""
534        self.view = View(self.x, self.y, self.dx, self.dy)
535        self.view.Xreel = self.view.x
536        self.view.Yreel = self.view.y
537        self.view.DXreel = self.view.dx
538        self.view.DYreel = self.view.dy
539       
540    def render(self, plot):
541        """
542        The base class makes sure the correct units are being used for
543        subsequent plottable. 
544       
545        For now it is assumed that the graphs are commensurate, and if you
546        put a Qx object on a Temperature graph then you had better hope
547        that it makes sense.
548       
549        """
550        plot.xaxis(self._xaxis, self._xunit)
551        plot.yaxis(self._yaxis, self._yunit)
552       
553    def is_empty(self):
554        """
555        Returns True if there is no data stored in the plottable
556        """
557        if not self.x == None and len(self.x) == 0 \
558            and not self.y == None and len(self.y) == 0:
559            return True
560        return False
561       
562       
563    def colors(self):
564        """Return the number of colors need to render the object"""
565        return 1
566   
567    def transformView(self):
568        """
569        It transforms x, y before displaying
570        """
571        self.view.transform(self.x, self.y, self.dx, self.dy)
572       
573    def returnValuesOfView(self):
574        """
575        Return View parameters and it is used by Fit Dialog
576        """
577        return self.view.returnXview()
578   
579    def check_data_PlottableX(self): 
580        """
581        Since no transformation is made for log10(x), check that
582        no negative values is plot in log scale
583        """
584        self.view.check_data_logX()
585       
586    def check_data_PlottableY(self): 
587        """
588        Since no transformation is made for log10(y), check that
589        no negative values is plot in log scale
590        """
591        self.view.check_data_logY() 
592       
593    def transformX(self, transx, transdx):
594        """
595        Receive pointers to function that transform x and dx
596        and set corresponding View pointers
597       
598        :param transx: pointer to function that transforms x
599        :param transdx: pointer to function that transforms dx
600       
601        """
602        self.view.setTransformX(transx, transdx)
603       
604    def transformY(self, transy, transdy):
605        """
606        Receive pointers to function that transform y and dy
607        and set corresponding View pointers
608       
609        :param transy: pointer to function that transforms y
610        :param transdy: pointer to function that transforms dy
611       
612        """
613        self.view.setTransformY(transy, transdy)
614       
615    def onReset(self):
616        """
617        Reset x, y, dx, dy view with its parameters
618        """
619        self.view.onResetView()
620       
621    def onFitRange(self, xmin=None, xmax=None):
622        """
623        It limits View data range to plot from min to max
624       
625        :param xmin: the minimum value of x to plot.
626        :param xmax: the maximum value of x to plot
627       
628        """
629        self.view.onFitRangeView(xmin, xmax)
630       
631class View:
632    """
633    Representation of the data that might include a transformation
634    """
635    x = None
636    y = None
637    dx = None
638    dy = None
639
640    def __init__(self, x=None, y=None, dx=None, dy=None):
641        """
642        """
643        self.x = x
644        self.y = y
645        self.dx = dx
646        self.dy = dy
647        # To change x range to the reel range
648        self.Xreel = self.x
649        self.Yreel = self.y
650        self.DXreel = self.dx
651        self.DYreel = self.dy
652        # Labels of x and y received from Properties Dialog
653        self.xLabel = ""
654        self.yLabel = ""
655        # Function to transform x, y, dx and dy
656        self.funcx = None
657        self.funcy = None
658        self.funcdx = None
659        self.funcdy = None
660
661    def transform(self, x=None, y=None, dx=None, dy=None):
662        """
663        Transforms the x,y,dx and dy vectors and stores
664         the output in View parameters
665
666        :param x: array of x values
667        :param y: array of y values
668        :param dx: array of  errors values on x
669        :param dy: array of error values on y
670       
671        """
672        # Sanity check
673        # Do the transofrmation only when x and y are empty
674        has_err_x = not (dx == None or len(dx) == 0)
675        has_err_y = not (dy == None or len(dy) == 0)
676       
677        if(x != None) and (y != None): 
678            if not dx == None and not len(dx) == 0 and not len(x)== len(dx):
679                msg = "Plottable.View: Given x and dx are not"
680                msg += " of the same length" 
681                raise ValueError, msg
682            # Check length of y array
683            if not len(y) == len(x):
684                msg = "Plottable.View: Given y "
685                msg += "and x are not of the same length"
686                raise ValueError, msg
687       
688            if not dy == None and not len(dy) == 0 and not len(y)==len(dy):
689                msg = "Plottable.View: Given y and dy are not of the same "
690                msg += "length: len(y)=%s, len(dy)=%s" %(len(y),len(dy))
691                raise ValueError, msg
692            self.x = []
693            self.y = []
694            if has_err_x:
695                self.dx = []
696            else:
697                self.dx = None
698            if has_err_y:
699                self.dy = []
700            else:
701                self.dy = None
702            tempx = []
703            tempy = []
704            if not has_err_x:
705                dx = numpy.zeros(len(x))
706            if not has_err_y:
707                dy = numpy.zeros(len(y))
708            for i in range(len(x)):
709                try:
710                    tempx =self.funcx(x[i],y[i])
711                    tempy =self.funcy(y[i],x[i])
712                    if has_err_x:
713                        tempdx = self.funcdx(x[i], y[i], dx[i], dy[i])
714                    if has_err_y:
715                        tempdy = self.funcdy(y[i], x[i], dy[i], dx[i])
716                    self.x.append(tempx)
717                    self.y.append(tempy)
718                    if has_err_x:
719                        self.dx.append(tempdx)
720                    if has_err_y:
721                        self.dy.append(tempdy)
722                except:
723                    tempx = x[i]
724                    tempy = y[i]
725                    tempdy = dy[i]
726            # Sanity check
727            if not len(self.x) == len(self.y):
728                msg = "Plottable.View: transformed x "
729                msg += "and y are not of the same length" 
730                raise ValueError, msg
731            if has_err_x and not (len(self.x) and len(self.dx)):
732                msg = "Plottable.View: transformed x and dx"
733                msg += " are not of the same length" 
734                raise ValueError, msg
735            if has_err_y and not (len(self.y) and len(self.dy)):
736                msg = "Plottable.View: transformed y"
737                msg += " and dy are not of the same length" 
738                raise ValueError, msg
739            # Check that negative values are not plot on x and y axis for
740            # log10 transformation
741            self.check_data_logX()
742            self.check_data_logY()
743            # Store x ,y dx,and dy in their full range for reset
744            self.Xreel = self.x
745            self.Yreel = self.y
746            self.DXreel = self.dx
747            self.DYreel = self.dy
748               
749    def onResetView(self):
750        """
751        Reset x,y,dx and y in their full range  and in the initial scale
752        in case their previous range has changed 
753       
754        """
755        self.x = self.Xreel
756        self.y = self.Yreel
757        self.dx = self.DXreel
758        self.dy = self.DYreel
759       
760    def setTransformX(self, funcx, funcdx): 
761        """
762        Receive pointers to function that transform x and dx
763        and set corresponding View pointers
764       
765        :param transx: pointer to function that transforms x
766        :param transdx: pointer to function that transforms dx
767       
768        """
769        self.funcx = funcx
770        self.funcdx = funcdx
771       
772    def setTransformY(self, funcy, funcdy):
773        """
774        Receive pointers to function that transform y and dy
775        and set corresponding View pointers
776       
777        :param transx: pointer to function that transforms y
778        :param transdx: pointer to function that transforms dy
779       
780        """   
781        self.funcy = funcy
782        self.funcdy = funcdy
783   
784    def returnXview(self):
785        """
786        Return View  x,y,dx,dy
787        """
788        return self.x, self.y, self.dx, self.dy
789   
790    def check_data_logX(self): 
791        """
792        Remove negative value in x vector to avoid plotting negative
793        value of Log10
794       
795        """
796        tempx = []
797        tempdx = []
798        tempy = []
799        tempdy = []
800        if self.dx == None:
801            self.dx = numpy.zeros(len(self.x))
802        if self.dy == None:
803            self.dy = numpy.zeros(len(self.y))
804        if self.xLabel == "log10(x)" :
805            for i in range(len(self.x)):
806                try:
807                    if (self.x[i] > 0):
808                        tempx.append(self.x[i])
809                        tempdx.append(self.dx[i])
810                        tempy.append(self.y[i])
811                        tempdy.append(self.dy[i])
812                except:
813                    print "check_data_logX: skipping point x %g" % self.x[i]
814                    print sys.exc_value 
815                    pass 
816            self.x = tempx
817            self.y = tempy
818            self.dx = tempdx
819            self.dy = tempdy
820       
821    def check_data_logY(self): 
822        """
823        Remove negative value in y vector
824        to avoid plotting negative value of Log10
825       
826        """
827        tempx = []
828        tempdx = []
829        tempy = []
830        tempdy = []
831        if self.dx == None:
832            self.dx = numpy.zeros(len(self.x))
833        if self.dy == None:
834            self.dy = numpy.zeros(len(self.y))
835        if (self.yLabel == "log10(y)" ):
836            for i in range(len(self.x)):
837                 try:
838                    if (self.y[i] > 0):
839                        tempx.append(self.x[i])
840                        tempdx.append(self.dx[i])
841                        tempy.append(self.y[i])
842                        tempdy.append(self.dy[i])
843                 except:
844                    print "check_data_logY: skipping point %g" %self.y[i]
845                    print sys.exc_value 
846                    pass
847            self.x = tempx
848            self.y = tempy
849            self.dx = tempdx
850            self.dy = tempdy
851           
852    def onFitRangeView(self, xmin=None, xmax=None):
853        """
854        It limits View data range to plot from min to max
855       
856        :param xmin: the minimum value of x to plot.
857        :param xmax: the maximum value of x to plot
858       
859        """
860        tempx = []
861        tempdx = []
862        tempy = []
863        tempdy = []
864        if self.dx == None:
865            self.dx = numpy.zeros(len(self.x))
866        if self.dy == None:
867            self.dy=numpy.zeros(len(self.y))
868        if (xmin != None) and (xmax != None):
869            for i in range(len(self.x)):
870                if (self.x[i] >= xmin) and (self.x[i] <= xmax):
871                    tempx.append(self.x[i])
872                    tempdx.append(self.dx[i])
873                    tempy.append(self.y[i])
874                    tempdy.append(self.dy[i])
875            self.x = tempx
876            self.y = tempy
877            self.dx = tempdx
878            self.dy = tempdy
879                       
880class Data2D(Plottable):
881    """
882    2D data class for image plotting
883    """
884    def __init__(self, image=None, qx_data=None, qy_data=None,
885                  err_image=None, xmin=None, xmax=None, ymin=None,
886                  ymax=None, zmin=None, zmax=None):
887        """
888        Draw image
889        """
890        Plottable.__init__(self)
891        self.name = "Data2D"
892        self.label = None
893        self.data = image
894        self.qx_data = qx_data
895        self.qy_data = qx_data
896        self.err_data = err_image
897        self.source = None
898        self.detector = []
899   
900        ## Units for Q-values
901        self.xy_unit = 'A^{-1}'
902        ## Units for I(Q) values
903        self.z_unit = 'cm^{-1}'
904        self._zaxis = ''
905        # x-axis unit and label
906        self._xaxis = '\\rm{Q_{x}}'
907        self._xunit = 'A^{-1}'
908        # y-axis unit and label
909        self._yaxis = '\\rm{Q_{y}}'
910        self._yunit = 'A^{-1}'
911       
912        ### might remove that later
913        ## Vector of Q-values at the center of each bin in x
914        self.x_bins = []
915        ## Vector of Q-values at the center of each bin in y
916        self.y_bins = []
917       
918        #x and y boundaries
919        self.xmin = xmin
920        self.xmax = xmax
921        self.ymin = ymin
922        self.ymax = ymax
923       
924        self.zmin = zmin
925        self.zmax = zmax
926        self.id = None
927       
928    def xaxis(self, label, unit):
929        """
930        set x-axis
931       
932        :param label: x-axis label
933        :param unit: x-axis unit
934       
935        """
936        self._xaxis = label
937        self._xunit = unit
938       
939    def yaxis(self, label, unit):
940        """
941        set y-axis
942       
943        :param label: y-axis label
944        :param unit: y-axis unit
945       
946        """
947        self._yaxis = label
948        self._yunit = unit
949       
950    def zaxis(self, label, unit):
951        """
952        set z-axis
953       
954        :param label: z-axis label
955        :param unit: z-axis unit
956       
957        """
958        self._zaxis = label
959        self._zunit = unit
960       
961    def setValues(self, datainfo=None):
962        """
963        Use datainfo object to initialize data2D
964       
965        :param datainfo: object
966       
967        """
968        self.image = copy.deepcopy(datainfo.data)
969        self.qx_data = copy.deepcopy(datainfo.qx_data)
970        self.qy_data = copy.deepcopy(datainfo.qy_data)
971        self.err_image = copy.deepcopy(datainfo.err_data)
972       
973        self.xy_unit = datainfo.Q_unit
974        self.z_unit = datainfo.I_unit
975        self._zaxis = datainfo._zaxis
976       
977        self.xaxis(datainfo._xunit, datainfo._xaxis)
978        self.yaxis(datainfo._yunit, datainfo._yaxis)
979        #x and y boundaries
980        self.xmin = datainfo.xmin
981        self.xmax = datainfo.xmax
982        self.ymin = datainfo.ymin
983        self.ymax = datainfo.ymax
984        ## Vector of Q-values at the center of each bin in x
985        self.x_bins = datainfo.x_bins
986        ## Vector of Q-values at the center of each bin in y
987        self.y_bins = datainfo.y_bins
988       
989    def set_zrange(self, zmin=None, zmax=None):
990        """
991        """
992        if zmin < zmax:
993            self.zmin = zmin
994            self.zmax = zmax
995        else:
996            raise "zmin is greater or equal to zmax "
997       
998    def render(self, plot, **kw):
999        """
1000        Renders the plottable on the graph
1001       
1002        """
1003        plot.image(self.data, self.qx_data, self.qy_data,
1004                   self.xmin, self.xmax, self.ymin,
1005                   self.ymax, self.zmin, self.zmax, **kw)   
1006       
1007    def changed(self):
1008        """
1009        """
1010        return False
1011   
1012    @classmethod
1013    def labels(cls, collection):
1014        """Build a label mostly unique within a collection"""
1015        map = {}
1016        for item in collection:
1017            #map[item] = label(item, collection)
1018            #map[item] = r"$\rm{%s}$" % item.name
1019            if item.label == "Data2D":
1020                item.label = item.name
1021            map[item] = item.label
1022        return map
1023
1024class Data1D(Plottable):
1025    """
1026    Data plottable: scatter plot of x,y with errors in x and y.
1027    """
1028   
1029    def __init__(self, x, y, dx=None, dy=None):
1030        """
1031        Draw points specified by x[i],y[i] in the current color/symbol.
1032        Uncertainty in x is given by dx[i], or by (xlo[i],xhi[i]) if the
1033        uncertainty is asymmetric.  Similarly for y uncertainty.
1034
1035        The title appears on the legend.
1036        The label, if it is different, appears on the status bar.
1037        """
1038        Plottable.__init__(self)
1039        self.name = "data"
1040        self.label = "data"
1041        self.x = x
1042        self.y = y
1043        self.dx = dx
1044        self.dy = dy
1045        self.source = None
1046        self.detector = None
1047        self.xaxis('', '')
1048        self.yaxis('', '')
1049        self.view = View(self.x, self.y, self.dx, self.dy)
1050        self.symbol = 0
1051        self.custom_color = None
1052        self.markersize = 5
1053        self.id = None
1054        self.hide_error = False
1055     
1056    def render(self, plot, **kw):
1057        """
1058        Renders the plottable on the graph
1059        """
1060        if self.interactive == True:
1061            kw['symbol'] = self.symbol
1062            kw['id'] =  self.id
1063            kw['hide_error'] = self.hide_error
1064            kw['markersize'] = self.markersize
1065            plot.interactive_points(self.view.x, self.view.y,
1066                                    dx=self.view.dx, dy=self.view.dy,
1067              name=self.name, **kw)           
1068        else:
1069            kw['id'] =  self.id
1070            kw['hide_error'] = self.hide_error
1071            kw['symbol'] = self.symbol
1072            kw['color'] = self.custom_color
1073            kw['markersize'] = self.markersize
1074            plot.points(self.view.x, self.view.y, dx=self.view.dx, 
1075                dy=self.view.dy, marker=self.symbollist[self.symbol], **kw)
1076       
1077    def changed(self):
1078        return False
1079
1080    @classmethod
1081    def labels(cls, collection):
1082        """Build a label mostly unique within a collection"""
1083        map = {}
1084        for item in collection:
1085            #map[item] = label(item, collection)
1086            #map[item] = r"$\rm{%s}$" % item.name
1087            if item.label == "data":
1088                item.label = item.name
1089            map[item] = item.label
1090        return map
1091   
1092class Theory1D(Plottable):
1093    """
1094    Theory plottable: line plot of x,y with confidence interval y.
1095   
1096    """
1097    def __init__(self, x, y, dy=None):
1098        """
1099        Draw lines specified in x[i],y[i] in the current color/symbol.
1100        Confidence intervals in x are given by dx[i] or by (xlo[i],xhi[i])
1101        if the limits are asymmetric.
1102       
1103        The title is the name that will show up on the legend.
1104        """
1105        Plottable.__init__(self)
1106        msg = "Theory1D is no longer supported, please use Data1D and change"
1107        msg += " symbol.\n"
1108        raise DeprecationWarning, msg
1109        self.name = "theory"
1110        self.label = "theory"
1111        self.x = x
1112        self.y = y
1113        self.dy = dy
1114        self.xaxis('', '')
1115        self.yaxis('', '')
1116        self.view = View(self.x, self.y, None, self.dy)
1117        self.symbol = 0
1118        self.id = None
1119       
1120    def render(self, plot, **kw):
1121        """
1122        """
1123        if self.interactive==True:
1124     
1125            kw['id'] =  self.id
1126            plot.interactive_curve(self.view.x, self.view.y,
1127                                   dy=self.view.dy,
1128                                   name=self.name, **kw)
1129        else:
1130            kw['id'] =  self.id
1131            plot.curve(self.view.x, self.view.y, dy=self.view.dy, **kw)
1132           
1133    def changed(self):
1134        return False
1135   
1136    @classmethod
1137    def labels(cls, collection):
1138        """Build a label mostly unique within a collection"""
1139        map = {}
1140        for item in collection:
1141            #map[item] = label(item, collection)
1142            #map[item] = r"$\rm{%s}$" % item.name
1143            if item.label == "theory":
1144                item.label = item.name
1145            map[item] = item.label
1146        return map
1147   
1148   
1149class Fit1D(Plottable):
1150    """
1151    Fit plottable: composed of a data line plus a theory line.  This
1152    is treated like a single object from the perspective of the graph,
1153    except that it will have two legend entries, one for the data and
1154    one for the theory.
1155
1156    The color of the data and theory will be shared.
1157   
1158    """
1159    def __init__(self, data=None, theory=None):
1160        """
1161        """
1162        Plottable.__init__(self)
1163        self.data = data
1164        self.theory = theory
1165
1166    def render(self, plot, **kw):
1167        """
1168        """
1169        self.data.render(plot, **kw)
1170        self.theory.render(plot, **kw)
1171
1172    def changed(self):
1173        """
1174        """
1175        return self.data.changed() or self.theory.changed()
1176
1177# ---------------------------------------------------------------
1178class Text(Plottable):
1179    """
1180    """
1181    def __init__(self, text=None, xpos=0.5, ypos=0.9, name='text'):
1182        """
1183        Draw the user-defined text in plotter
1184        We can specify the position of text
1185        """
1186        Plottable.__init__(self)
1187        self.name = name
1188        self.text = text
1189        self.xpos = xpos
1190        self.ypos = ypos
1191       
1192    def render(self,plot,**kw):
1193        """
1194        """
1195        from matplotlib import transforms
1196
1197        xcoords=transforms.blended_transform_factory(plot.subplot.transAxes,
1198                                                     plot.subplot.transAxes)
1199        plot.subplot.text(self.xpos,
1200                          self.ypos,
1201                          self.text,
1202                          label=self.name,
1203                          transform=xcoords,
1204                          )
1205           
1206    def setText(self, text):
1207        """Set the text string."""
1208        self.text =  text
1209
1210    def getText(self, text):
1211        """Get the text string."""
1212        return self.text
1213
1214    def set_x(self, x):
1215        """
1216        Set the x position of the text
1217        ACCEPTS: float
1218        """
1219        self.xpos = x
1220
1221    def set_y(self, y):
1222        """
1223        Set the y position of the text
1224        ACCEPTS: float
1225        """
1226        self.ypos = y
1227     
1228
1229# ---------------------------------------------------------------
1230class Chisq(Plottable):
1231    """
1232    Chisq plottable plots the chisq
1233    """
1234    def __init__(self, chisq=None):
1235        """
1236        Draw the chisq in plotter
1237        We can specify the position of chisq
1238        """
1239        Plottable.__init__(self)
1240        self.name = "chisq"
1241        #super( Chisq, self).__init__(None, None, None, None)
1242        self._chisq = chisq
1243        self.xpos = 0.5
1244        self.ypos = 0.9
1245       
1246    def render(self, plot, **kw):
1247        """
1248        """
1249        if  self._chisq == None:
1250            chisqTxt = r'$\chi^2=$'
1251        else:
1252            chisqTxt = r'$\chi^2=%g$' % (float(self._chisq))
1253
1254        from matplotlib import transforms
1255
1256        xcoords = transforms.blended_transform_factory(plot.subplot.transAxes,
1257                                                     plot.subplot.transAxes)
1258        plot.subplot.text(self.xpos,
1259                          self.ypos,
1260                          chisqTxt, label='chisq',
1261                          transform=xcoords,)
1262           
1263    def setChisq(self, chisq):
1264        """
1265        Set the chisq value.
1266        """
1267        self._chisq =  chisq
1268
1269
1270######################################################
1271
1272def sample_graph():
1273    import numpy as nx
1274   
1275    # Construct a simple graph
1276    if False:
1277        x = nx.array([1,2,3,4,5,6],'d')
1278        y = nx.array([4,5,6,5,4,5],'d')
1279        dy = nx.array([0.2, 0.3, 0.1, 0.2, 0.9, 0.3])
1280    else:
1281        x = nx.linspace(0,1.,10000)
1282        y = nx.sin(2 * nx.pi * x * 2.8)
1283        dy = nx.sqrt(100 * nx.abs(y)) / 100
1284    data = Data1D(x, y, dy=dy)
1285    data.xaxis('distance', 'm')
1286    data.yaxis('time', 's')
1287    graph = Graph()
1288    graph.title('Walking Results')
1289    graph.add(data)
1290    graph.add(Theory1D(x, y, dy=dy))
1291    return graph
1292
1293def demo_plotter(graph):
1294    import wx
1295    from pylab_plottables import Plotter
1296    #from mplplotter import Plotter
1297
1298    # Make a frame to show it
1299    app = wx.PySimpleApp()
1300    frame = wx.Frame(None, -1, 'Plottables')
1301    plotter = Plotter(frame)
1302    frame.Show()
1303
1304    # render the graph to the pylab plotter
1305    graph.render(plotter)
1306   
1307    class GraphUpdate:
1308        callnum=0
1309        def __init__(self, graph, plotter):
1310            self.graph, self.plotter = graph, plotter
1311        def __call__(self):
1312            if self.graph.changed(): 
1313                self.graph.render(self.plotter)
1314                return True
1315            return False
1316        def onIdle(self, event):
1317            #print "On Idle checker %d"%(self.callnum)
1318            self.callnum = self.callnum + 1
1319            if self.__call__(): 
1320                pass # event.RequestMore()
1321    update = GraphUpdate(graph, plotter)
1322    frame.Bind(wx.EVT_IDLE, update.onIdle)
1323    app.MainLoop()
1324
1325import sys; print sys.version
1326if __name__ == "__main__":
1327    demo_plotter(sample_graph())
1328   
Note: See TracBrowser for help on using the repository browser.