root/ossiedev/trunk/tools/alf_plugins/plot/plot.py @ 7943

Revision 7943, 62.5 KB (checked in by caguayog, 5 years ago)

Committing Carl's new and improved plot tool

Line 
1#!/usr/bin/env python
2
3## Copyright 2007 Virginia Polytechnic Institute and State University
4##
5## This file is part of the OSSIE ALF plot tool.
6##
7## OSSIE ALF plot is free software; you can redistribute it and/or modify
8## it under the terms of the GNU General Public License as published by
9## the Free Software Foundation; either version 2 of the License, or
10## (at your option) any later version.
11##
12## OSSIE ALF plot is distributed in the hope that it will be useful,
13## but WITHOUT ANY WARRANTY; without even the implied warranty of
14## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15## GNU General Public License for more details.
16##
17## You should have received a copy of the GNU General Public License
18## along with OSSIE ALF plot; if not, write to the Free Software
19## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20
21
22import  string as _string
23import  time as _time
24import  wx
25import sys
26from omniORB import CORBA
27import CosNaming
28from ossie.cf import CF, CF__POA
29from ossie.standardinterfaces import standardInterfaces
30from ossie.standardinterfaces import standardInterfaces__POA
31import time, threading
32import struct
33import math
34import sys
35
36# Needs Numeric or numarray
37try:
38    #import Numeric as _Numeric
39    import numpy as _Numeric
40except:
41    try:
42        import numarray as _Numeric  #if numarray is used it is renamed Numeric
43    except:
44        msg= """
45        This module requires the Numeric or numarray module,
46        which could not be imported.  It probably is not installed
47        (it's not part of the standard Python distribution). See the
48        Python site (http://www.python.org) for information on
49        downloading source or binaries."""
50        raise ImportError, "Numeric or numarray not found. \n" + msg
51
52# See if standardInterfaces.MetaData is available
53try:
54    x = standardInterfaces.MetaData
55    # RadioMetaData interface is installed
56    HAVE_RADIO_METADATA = True
57except AttributeError:
58    # RadioMetaData interface is not installed
59    HAVE_RADIO_METADATA = False
60
61#
62# Plotting classes...
63#
64class PolyPoints:
65    """Base Class for lines and markers
66        - All methods are private.
67    """
68
69    def __init__(self, points, attr):
70        self.points = _Numeric.array(points)
71        self.currentScale= (1,1)
72        self.currentShift= (0,0)
73        self.scaled = self.points
74        self.attributes = {}
75        self.attributes.update(self._attributes)
76        for name, value in attr.items():   
77            if name not in self._attributes.keys():
78                raise KeyError, "Style attribute incorrect. Should be one of %s" % self._attributes.keys()
79            self.attributes[name] = value
80       
81    def boundingBox(self):
82        if len(self.points) == 0:
83            # no curves to draw
84            # defaults to (-1,-1) and (1,1) but axis can be set in Draw
85            minXY= _Numeric.array([-1,-1])
86            maxXY= _Numeric.array([ 1, 1])
87        else:
88            minXY= _Numeric.minimum.reduce(self.points)
89            maxXY= _Numeric.maximum.reduce(self.points)
90        return minXY, maxXY
91
92    def scaleAndShift(self, scale=(1,1), shift=(0,0)):
93        if len(self.points) == 0:
94            # no curves to draw
95            return
96        if (scale is not self.currentScale) or (shift is not self.currentShift):
97            # update point scaling
98            self.scaled = scale*self.points+shift
99            self.currentScale= scale
100            self.currentShift= shift
101        # else unchanged use the current scaling
102       
103    def getLegend(self):
104        return self.attributes['legend']
105
106    def getClosestPoint(self, pntXY, pointScaled= True):
107        """Returns the index of closest point on the curve, pointXY, scaledXY, distance
108            x, y in user coords
109            if pointScaled == True based on screen coords
110            if pointScaled == False based on user coords
111        """
112        if pointScaled == True:
113            #Using screen coords
114            p = self.scaled
115            pxy = self.currentScale * _Numeric.array(pntXY)+ self.currentShift
116        else:
117            #Using user coords
118            p = self.points
119            pxy = _Numeric.array(pntXY)
120        #determine distance for each point
121        d= _Numeric.sqrt(_Numeric.add.reduce((p-pxy)**2,1)) #sqrt(dx^2+dy^2)
122        pntIndex = _Numeric.argmin(d)
123        dist = d[pntIndex]
124        return [pntIndex, self.points[pntIndex], self.scaled[pntIndex], dist]
125
126class PolyLine(PolyPoints):
127    """Class to define line type and style
128        - All methods except __init__ are private.
129    """
130   
131    _attributes = {'colour': 'black',
132                   'width': 1,
133                   'style': wx.SOLID,
134                   'legend': ''}
135
136    def __init__(self, points, **attr):
137        """Creates PolyLine object
138            points - sequence (array, tuple or list) of (x,y) points making up line
139            **attr - key word attributes
140                Defaults:
141                    'colour'= 'black',          - wx.Pen Colour any wx.NamedColour
142                    'width'= 1,                 - Pen width
143                    'style'= wx.SOLID,          - wx.Pen style
144                    'legend'= ''                - Line Legend to display
145        """
146        PolyPoints.__init__(self, points, attr)
147
148    def draw(self, dc, printerScale, coord= None):
149        colour = self.attributes['colour']
150        width = self.attributes['width'] * printerScale
151        style= self.attributes['style']
152        pen = wx.Pen(wx.NamedColour(colour), width, style)
153        pen.SetCap(wx.CAP_BUTT)
154        dc.SetPen(pen)
155        if coord == None:
156            dc.DrawLines(self.scaled)
157        else:
158            dc.DrawLines(coord) # draw legend line
159
160    def getSymExtent(self, printerScale):
161        """Width and Height of Marker"""
162        h= self.attributes['width'] * printerScale
163        w= 5 * h
164        return (w,h)
165
166class PolyMarker(PolyPoints):
167    """Class to define marker type and style
168        - All methods except __init__ are private.
169    """
170 
171    _attributes = {'colour': 'black',
172                   'width': 1,
173                   'size': 2,
174                   'fillcolour': None,
175                   'fillstyle': wx.SOLID,
176                   'marker': 'circle',
177                   'legend': ''}
178
179    def __init__(self, points, **attr):
180        """Creates PolyMarker object
181        points - sequence (array, tuple or list) of (x,y) points
182        **attr - key word attributes
183            Defaults:
184                'colour'= 'black',          - wx.Pen Colour any wx.NamedColour
185                'width'= 1,                 - Pen width
186                'size'= 2,                  - Marker size
187                'fillcolour'= same as colour,      - wx.Brush Colour any wx.NamedColour
188                'fillstyle'= wx.SOLID,      - wx.Brush fill style (use wx.TRANSPARENT for no fill)
189                'marker'= 'circle'          - Marker shape
190                'legend'= ''                - Marker Legend to display
191             
192            Marker Shapes:
193                - 'circle'
194                - 'dot'
195                - 'square'
196                - 'triangle'
197                - 'triangle_down'
198                - 'cross'
199                - 'plus'
200        """
201     
202        PolyPoints.__init__(self, points, attr)
203
204    def draw(self, dc, printerScale, coord= None):
205        colour = self.attributes['colour']
206        width = self.attributes['width'] * printerScale
207        size = self.attributes['size'] * printerScale
208        fillcolour = self.attributes['fillcolour']
209        fillstyle = self.attributes['fillstyle']
210        marker = self.attributes['marker']
211
212        dc.SetPen(wx.Pen(wx.NamedColour(colour), width))
213        if fillcolour:
214            dc.SetBrush(wx.Brush(wx.NamedColour(fillcolour),fillstyle))
215        else:
216            dc.SetBrush(wx.Brush(wx.NamedColour(colour), fillstyle))
217        if coord == None:
218            self._drawmarkers(dc, self.scaled, marker, size)
219        else:
220            self._drawmarkers(dc, coord, marker, size) # draw legend marker
221
222    def getSymExtent(self, printerScale):
223        """Width and Height of Marker"""
224        s= 5*self.attributes['size'] * printerScale
225        return (s,s)
226
227    def _drawmarkers(self, dc, coords, marker,size=1):
228        f = eval('self._' +marker)
229        f(dc, coords, size)
230
231    def _circle(self, dc, coords, size=1):
232        fact= 2.5*size
233        wh= 2.0*size
234        rect= _Numeric.zeros((len(coords),4))+[0.0,0.0,wh,wh]
235        rect[:,0:2]= coords-[fact,fact]
236        dc.DrawEllipseList(rect)
237
238    def _dot(self, dc, coords, size=1):
239        dc.DrawPointList(coords)
240
241    def _square(self, dc, coords, size=1):
242        fact= 2.5*size
243        wh= 5.0*size
244        rect= _Numeric.zeros((len(coords),4))+[0.0,0.0,wh,wh]
245        rect[:,0:2]= coords-[fact,fact]
246        dc.DrawRectangleList(rect.astype(_Numeric.Int32))
247
248    def _triangle(self, dc, coords, size=1):
249        shape= [(-2.5*size,1.44*size), (2.5*size,1.44*size), (0.0,-2.88*size)]
250        poly= _Numeric.repeat(coords,3)
251        poly.shape= (len(coords),3,2)
252        poly += shape
253        dc.DrawPolygonList(poly.astype(_Numeric.Int32))
254
255    def _triangle_down(self, dc, coords, size=1):
256        shape= [(-2.5*size,-1.44*size), (2.5*size,-1.44*size), (0.0,2.88*size)]
257        poly= _Numeric.repeat(coords,3)
258        poly.shape= (len(coords),3,2)
259        poly += shape
260        dc.DrawPolygonList(poly.astype(_Numeric.Int32))
261     
262    def _cross(self, dc, coords, size=1):
263        fact= 2.5*size
264        for f in [[-fact,-fact,fact,fact],[-fact,fact,fact,-fact]]:
265            lines= _Numeric.concatenate((coords,coords),axis=1)+f
266            dc.DrawLineList(lines.astype(_Numeric.Int32))
267
268    def _plus(self, dc, coords, size=1):
269        fact= 2.5*size
270        for f in [[-fact,0,fact,0],[0,-fact,0,fact]]:
271            lines= _Numeric.concatenate((coords,coords),axis=1)+f
272            #dc.DrawLineList(lines.astype(_Numeric.Int32))
273            dc.DrawLineList(lines)
274
275class PlotGraphics:
276    """Container to hold PolyXXX objects and graph labels
277        - All methods except __init__ are private.
278    """
279
280    def __init__(self, objects, title='', xLabel='', yLabel= ''):
281        """Creates PlotGraphics object
282        objects - list of PolyXXX objects to make graph
283        title - title shown at top of graph
284        xLabel - label shown on x-axis
285        yLabel - label shown on y-axis
286        """
287        if type(objects) not in [list,tuple]:
288            raise TypeError, "objects argument should be list or tuple"
289        self.objects = objects
290        self.title= title
291        self.xLabel= xLabel
292        self.yLabel= yLabel
293
294    def boundingBox(self):
295        p1, p2 = self.objects[0].boundingBox()
296        for o in self.objects[1:]:
297            p1o, p2o = o.boundingBox()
298            p1 = _Numeric.minimum(p1, p1o)
299            p2 = _Numeric.maximum(p2, p2o)
300        return p1, p2
301
302    def scaleAndShift(self, scale=(1,1), shift=(0,0)):
303        for o in self.objects:
304            o.scaleAndShift(scale, shift)
305
306    def setPrinterScale(self, scale):
307        """Thickens up lines and markers only for printing"""
308        self.printerScale= scale
309
310    def setXLabel(self, xLabel= ''):
311        """Set the X axis label on the graph"""
312        self.xLabel= xLabel
313
314    def setYLabel(self, yLabel= ''):
315        """Set the Y axis label on the graph"""
316        self.yLabel= yLabel
317       
318    def setTitle(self, title= ''):
319        """Set the title at the top of graph"""
320        self.title= title
321
322    def getXLabel(self):
323        """Get x axis label string"""
324        return self.xLabel
325
326    def getYLabel(self):
327        """Get y axis label string"""
328        return self.yLabel
329
330    def getTitle(self, title= ''):
331        """Get the title at the top of graph"""
332        return self.title
333
334    def draw(self, dc):
335        for o in self.objects:
336            #t=_time.clock()          # profile info
337            o.draw(dc, self.printerScale)
338            #dt= _time.clock()-t
339            #print o, "time=", dt
340
341    def getSymExtent(self, printerScale):
342        """Get max width and height of lines and markers symbols for legend"""
343        symExt = self.objects[0].getSymExtent(printerScale)
344        for o in self.objects[1:]:
345            oSymExt = o.getSymExtent(printerScale)
346            symExt = _Numeric.maximum(symExt, oSymExt)
347        return symExt
348   
349    def getLegendNames(self):
350        """Returns list of legend names"""
351        lst = [None]*len(self)
352        for i in range(len(self)):
353            lst[i]= self.objects[i].getLegend()
354        return lst
355           
356    def __len__(self):
357        return len(self.objects)
358
359    def __getitem__(self, item):
360        return self.objects[item]
361
362
363#-------------------------------------------------------------------------------
364# Main window that you will want to import into your application.
365
366class PlotCanvas(wx.Window):
367    """Subclass of a wx.Window to allow simple general plotting
368    of data with zoom, labels, and automatic axis scaling."""
369
370    def __init__(self, parent, id = -1, pos=wx.DefaultPosition,
371            size=wx.DefaultSize, style= wx.DEFAULT_FRAME_STYLE, name= ""):
372        """Constucts a window, which can be a child of a frame, dialog or
373        any other non-control window"""
374   
375        wx.Window.__init__(self, parent, id, pos, size, style, name)
376        self.border = (1,1)
377
378        self._DrawEnabled = True
379 
380        self.SetBackgroundColour("white")
381       
382        # Create some mouse events for zooming
383        self.Bind(wx.EVT_LEFT_DOWN, self.OnMouseLeftDown)
384        self.Bind(wx.EVT_LEFT_UP, self.OnMouseLeftUp)
385        self.Bind(wx.EVT_MOTION, self.OnMotion)
386        self.Bind(wx.EVT_LEFT_DCLICK, self.OnMouseDoubleClick)
387        self.Bind(wx.EVT_RIGHT_DOWN, self.OnMouseRightDown)
388
389        # set curser as cross-hairs
390        self.SetCursor(wx.CROSS_CURSOR)
391
392        # Things for printing
393        #self.print_data = wx.PrintData()
394        #self.print_data.SetPaperId(wx.PAPER_LETTER)
395        #self.print_data.SetOrientation(wx.LANDSCAPE)
396        #self.pageSetupData= wx.PageSetupDialogData()
397        #self.pageSetupData.SetMarginBottomRight((25,25))
398        #self.pageSetupData.SetMarginTopLeft((25,25))
399        #self.pageSetupData.SetPrintData(self.print_data)
400        self.printerScale = 1
401        self.parent= parent
402
403        # Zooming variables
404        self._zoomInFactor =  0.5
405        self._zoomOutFactor = 2
406        self._zoomCorner1= _Numeric.array([0.0, 0.0]) # left mouse down corner
407        self._zoomCorner2= _Numeric.array([0.0, 0.0])   # left mouse up corner
408        self._zoomEnabled= False
409        self._hasDragged= False
410       
411        # Drawing Variables
412        self.last_draw = None
413        self._pointScale= 1
414        self._pointShift= 0
415        self._xSpec= 'auto'
416        self._ySpec= 'auto'
417        self._gridEnabled= True
418        self._legendEnabled= False
419       
420        # Fonts
421        self._fontCache = {}
422        self._fontSizeAxis= 10
423        self._fontSizeTitle= 15
424        self._fontSizeLegend= 7
425
426        # pointLabels
427        self._pointLabelEnabled= False
428        self.last_PointLabel= None
429        self._pointLabelFunc= None
430        self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave)
431
432        self.Bind(wx.EVT_PAINT, self.OnPaint)
433        self.Bind(wx.EVT_SIZE, self.OnSize)
434        # OnSize called to make sure the buffer is initialized.
435        # This might result in OnSize getting called twice on some
436        # platforms at initialization, but little harm done.
437        if wx.Platform != "__WXMAC__":
438            self.OnSize(None) # sets the initial size based on client size
439
440    def SetFontSizeAxis(self, point= 10):
441        """Set the tick and axis label font size (default is 10 point)"""
442        self._fontSizeAxis= point
443       
444    def GetFontSizeAxis(self):
445        """Get current tick and axis label font size in points"""
446        return self._fontSizeAxis
447   
448    def SetFontSizeTitle(self, point= 15):
449        """Set Title font size (default is 15 point)"""
450        self._fontSizeTitle= point
451
452    def GetFontSizeTitle(self):
453        """Get current Title font size in points"""
454        return self._fontSizeTitle
455   
456    def SetFontSizeLegend(self, point= 7):
457        """Set Legend font size (default is 7 point)"""
458        self._fontSizeLegend= point
459       
460    def GetFontSizeLegend(self):
461        """Get current Legend font size in points"""
462        return self._fontSizeLegend
463
464    def SetEnableZoom(self, value):
465        """Set True to enable zooming."""
466        if value not in [True,False]:
467            raise TypeError, "Value should be True or False"
468        self._zoomEnabled= value
469
470    def GetEnableZoom(self):
471        """True if zooming enabled."""
472        return self._zoomEnabled
473
474    def SetEnableGrid(self, value):
475        """Set True to enable grid."""
476        if value not in [True,False,'Horizontal','Vertical']:
477            raise TypeError, "Value should be True, False, Horizontal or Vertical"
478        self._gridEnabled= value
479        self.Redraw()
480
481    def GetEnableGrid(self):
482        """True if grid enabled."""
483        return self._gridEnabled
484
485    def SetEnableLegend(self, value):
486        """Set True to enable legend."""
487        if value not in [True,False]:
488            raise TypeError, "Value should be True or False"
489        self._legendEnabled= value
490        self.Redraw()
491
492    def GetEnableLegend(self):
493        """True if Legend enabled."""
494        return self._legendEnabled
495
496    def SetEnablePointLabel(self, value):
497        """Set True to enable pointLabel."""
498        if value not in [True,False]:
499            raise TypeError, "Value should be True or False"
500        self._pointLabelEnabled= value
501        self.Redraw()  #will erase existing pointLabel if present
502        self.last_PointLabel = None
503
504    def GetEnablePointLabel(self):
505        """True if pointLabel enabled."""
506        return self._pointLabelEnabled
507
508    def SetPointLabelFunc(self, func):
509        """Sets the function with custom code for pointLabel drawing
510            ******** more info needed ***************
511        """
512        self._pointLabelFunc= func
513
514    def GetPointLabelFunc(self):
515        """Returns pointLabel Drawing Function"""
516        return self._pointLabelFunc
517
518    def Reset(self):
519        """Unzoom the plot."""
520        self.last_PointLabel = None        #reset pointLabel
521        if self.last_draw is not None:
522            self.Draw(self.last_draw[0])
523       
524    def ScrollRight(self, units):         
525        """Move view right number of axis units."""
526        self.last_PointLabel = None        #reset pointLabel
527        if self.last_draw is not None:
528            graphics, xAxis, yAxis= self.last_draw
529            xAxis= (xAxis[0]+units, xAxis[1]+units)
530            self.Draw(graphics,xAxis,yAxis)
531
532    def ScrollUp(self, units):
533        """Move view up number of axis units."""
534        self.last_PointLabel = None        #reset pointLabel
535        if self.last_draw is not None: 
536             graphics, xAxis, yAxis= self.last_draw
537             yAxis= (yAxis[0]+units, yAxis[1]+units)
538             self.Draw(graphics,xAxis,yAxis)
539
540       
541    def GetXY(self,event):
542        """Takes a mouse event and returns the XY user axis values."""
543        x,y= self.PositionScreenToUser(event.GetPosition())
544        return x,y
545
546    def PositionUserToScreen(self, pntXY):
547        """Converts User position to Screen Coordinates"""
548        userPos= _Numeric.array(pntXY)
549        x,y= userPos * self._pointScale + self._pointShift
550        return x,y
551       
552    def PositionScreenToUser(self, pntXY):
553        """Converts Screen position to User Coordinates"""
554        screenPos= _Numeric.array(pntXY)
555        x,y= (screenPos-self._pointShift)/self._pointScale
556        return x,y
557       
558    def SetXSpec(self, type= 'auto'):
559        """xSpec- defines x axis type. Can be 'none', 'min' or 'auto'
560        where:
561            'none' - shows no axis or tick mark values
562            'min' - shows min bounding box values
563            'auto' - rounds axis range to sensible values
564        """
565        self._xSpec= type
566       
567    def SetYSpec(self, type= 'auto'):
568        """ySpec- defines x axis type. Can be 'none', 'min' or 'auto'
569        where:
570            'none' - shows no axis or tick mark values
571            'min' - shows min bounding box values
572            'auto' - rounds axis range to sensible values
573        """
574        self._ySpec= type
575
576    def GetXSpec(self):
577        """Returns current XSpec for axis"""
578        return self._xSpec
579   
580    def GetYSpec(self):
581        """Returns current YSpec for axis"""
582        return self._ySpec
583   
584    def GetXMaxRange(self):
585        """Returns (minX, maxX) x-axis range for displayed graph"""
586        graphics= self.last_draw[0]
587        p1, p2 = graphics.boundingBox()     # min, max points of graphics
588        xAxis = self._axisInterval(self._xSpec, p1[0], p2[0]) # in user units
589        return xAxis
590
591    def GetYMaxRange(self):
592        """Returns (minY, maxY) y-axis range for displayed graph"""
593        graphics= self.last_draw[0]
594        p1, p2 = graphics.boundingBox()     # min, max points of graphics
595        yAxis = self._axisInterval(self._ySpec, p1[1], p2[1])
596        return yAxis
597
598    def GetXCurrentRange(self):
599        """Returns (minX, maxX) x-axis for currently displayed portion of graph"""
600        return self.last_draw[1]
601   
602    def GetYCurrentRange(self):
603        """Returns (minY, maxY) y-axis for currently displayed portion of graph"""
604        return self.last_draw[2]
605       
606    def Draw(self, graphics, xAxis = None, yAxis = None, dc = None):
607        """Draw objects in graphics with specified x and y axis.
608        graphics- instance of PlotGraphics with list of PolyXXX objects
609        xAxis - tuple with (min, max) axis range to view
610        yAxis - same as xAxis
611        dc - drawing context - doesn't have to be specified.   
612        If it's not, the offscreen buffer is used
613        """
614        if not self._DrawEnabled:
615            return
616        # check Axis is either tuple or none
617        if type(xAxis) not in [type(None),tuple]:
618            raise TypeError, "xAxis should be None or (minX,maxX)"
619        if type(yAxis) not in [type(None),tuple]:
620            raise TypeError, "yAxis should be None or (minY,maxY)"
621             
622        # check case for axis = (a,b) where a==b caused by improper zooms
623        if xAxis != None:
624            if xAxis[0] == xAxis[1]:
625                return
626        if yAxis != None:
627            if yAxis[0] == yAxis[1]:
628                return
629           
630        if dc == None:
631            # sets new dc and clears it
632            dc = wx.BufferedDC(wx.ClientDC(self), self._Buffer)
633            dc.Clear()
634           
635        dc.BeginDrawing()
636        # dc.Clear()
637       
638        # set font size for every thing but title and legend
639        dc.SetFont(self._getFont(self._fontSizeAxis))
640
641        # sizes axis to axis type, create lower left and upper right corners of plot
642        if xAxis == None or yAxis == None:
643            # One or both axis not specified in Draw
644            p1, p2 = graphics.boundingBox()     # min, max points of graphics
645            if xAxis == None:
646                xAxis = self._axisInterval(self._xSpec, p1[0], p2[0]) # in user units
647            if yAxis == None:
648                yAxis = self._axisInterval(self._ySpec, p1[1], p2[1])
649            # Adjust bounding box for axis spec
650            p1[0],p1[1] = xAxis[0], yAxis[0]     # lower left corner user scale (xmin,ymin)
651            p2[0],p2[1] = xAxis[1], yAxis[1]     # upper right corner user scale (xmax,ymax)
652        else:
653            # Both axis specified in Draw
654            p1= _Numeric.array([xAxis[0], yAxis[0]])    # lower left corner user scale (xmin,ymin)
655            p2= _Numeric.array([xAxis[1], yAxis[1]])     # upper right corner user scale (xmax,ymax)
656
657        self.last_draw = (graphics, xAxis, yAxis)       # saves most recient values
658
659        # Get ticks and textExtents for axis if required
660        if self._xSpec is not 'none':       
661            xticks = self._ticks(xAxis[0], xAxis[1])
662            xTextExtent = dc.GetTextExtent(xticks[-1][1])# w h of x axis text last number on axis
663        else:
664            xticks = None
665            xTextExtent= (0,0) # No text for ticks
666        if self._ySpec is not 'none':
667            yticks = self._ticks(yAxis[0], yAxis[1])
668            yTextExtentBottom= dc.GetTextExtent(yticks[0][1])
669            yTextExtentTop   = dc.GetTextExtent(yticks[-1][1])
670            yTextExtent= (max(yTextExtentBottom[0],yTextExtentTop[0]),
671                        max(yTextExtentBottom[1],yTextExtentTop[1]))
672        else:
673            yticks = None
674            yTextExtent= (0,0) # No text for ticks
675
676        # TextExtents for Title and Axis Labels
677        titleWH, xLabelWH, yLabelWH= self._titleLablesWH(dc, graphics)
678
679        # TextExtents for Legend
680        legendBoxWH, legendSymExt, legendTextExt = self._legendWH(dc, graphics)
681
682        # room around graph area
683        rhsW= max(xTextExtent[0], legendBoxWH[0]) # use larger of number width or legend width
684        lhsW= yTextExtent[0]+ yLabelWH[1]
685        bottomH= max(xTextExtent[1], yTextExtent[1]/2.)+ xLabelWH[1]
686        topH= yTextExtent[1]/2. + titleWH[1]
687        textSize_scale= _Numeric.array([rhsW+lhsW,bottomH+topH]) # make plot area smaller by text size
688        textSize_shift= _Numeric.array([lhsW, bottomH])          # shift plot area by this amount
689
690        # drawing title and labels text
691        dc.SetFont(self._getFont(self._fontSizeTitle))
692        titlePos= (self.plotbox_origin[0]+ lhsW + (self.plotbox_size[0]-lhsW-rhsW)/2.- titleWH[0]/2.,
693                 self.plotbox_origin[1]- self.plotbox_size[1])
694        dc.DrawText(graphics.getTitle(),titlePos[0],titlePos[1])
695        dc.SetFont(self._getFont(self._fontSizeAxis))
696        xLabelPos= (self.plotbox_origin[0]+ lhsW + (self.plotbox_size[0]-lhsW-rhsW)/2.- xLabelWH[0]/2.,
697                 self.plotbox_origin[1]- xLabelWH[1])
698        dc.DrawText(graphics.getXLabel(),xLabelPos[0],xLabelPos[1])
699        yLabelPos= (self.plotbox_origin[0],
700                 self.plotbox_origin[1]- bottomH- (self.plotbox_size[1]-bottomH-topH)/2.+ yLabelWH[0]/2.)
701        if graphics.getYLabel():  # bug fix for Linux
702            dc.DrawRotatedText(graphics.getYLabel(),yLabelPos[0],yLabelPos[1],90)
703
704        # drawing legend makers and text
705        if self._legendEnabled:
706            self._drawLegend(dc,graphics,rhsW,topH,legendBoxWH, legendSymExt, legendTextExt)
707
708        # allow for scaling and shifting plotted points
709        scale = (self.plotbox_size-textSize_scale) / (p2-p1)* _Numeric.array((1,-1))
710        shift = -p1*scale + self.plotbox_origin + textSize_shift * _Numeric.array((1,-1))
711        self._pointScale= scale  # make available for mouse events
712        self._pointShift= shift       
713        self._drawAxes(dc, p1, p2, scale, shift, xticks, yticks)
714       
715        graphics.scaleAndShift(scale, shift)
716        graphics.setPrinterScale(self.printerScale)  # thicken up lines and markers if printing
717       
718        # set clipping area so drawing does not occur outside axis box
719        ptx,pty,rectWidth,rectHeight= self._point2ClientCoord(p1, p2)
720        dc.SetClippingRegion(ptx,pty,rectWidth,rectHeight)
721        # Draw the lines and markers
722        #start = _time.clock()
723        graphics.draw(dc)
724        # print "entire graphics drawing took: %f second"%(_time.clock() - start)
725        # remove the clipping region
726        dc.DestroyClippingRegion()
727        dc.EndDrawing()
728
729    def Redraw(self, dc= None):
730        """Redraw the existing plot."""
731        if self.last_draw is not None:
732            graphics, xAxis, yAxis= self.last_draw
733            self.Draw(graphics,xAxis,yAxis,dc)
734
735    def Clear(self):
736        """Erase the window."""
737        self.last_PointLabel = None        #reset pointLabel
738        dc = wx.BufferedDC(wx.ClientDC(self), self._Buffer)
739        dc.Clear()
740        self.last_draw = None
741
742    def Zoom(self, Center, Ratio):
743        """ Zoom on the plot
744            Centers on the X,Y coords given in Center
745            Zooms by the Ratio = (Xratio, Yratio) given
746        """
747        self.last_PointLabel = None   #reset maker
748        x,y = Center
749        if self.last_draw != None:
750            (graphics, xAxis, yAxis) = self.last_draw
751            w = (xAxis[1] - xAxis[0]) * Ratio[0]
752            h = (yAxis[1] - yAxis[0]) * Ratio[1]
753            xAxis = ( x - w/2, x + w/2 )
754            yAxis = ( y - h/2, y + h/2 )
755            self.Draw(graphics, xAxis, yAxis)
756       
757    def GetClosestPoints(self, pntXY, pointScaled= True):
758        """Returns list with
759            [curveNumber, legend, index of closest point, pointXY, scaledXY, distance]
760            list for each curve.
761            Returns [] if no curves are being plotted.
762           
763            x, y in user coords
764            if pointScaled == True based on screen coords
765            if pointScaled == False based on user coords
766        """
767        if self.last_draw == None:
768            #no graph available
769            return []
770        graphics, xAxis, yAxis= self.last_draw
771        l = []
772        for curveNum,obj in enumerate(graphics):
773            #check there are points in the curve
774            if len(obj.points) == 0:
775                continue  #go to next obj
776            #[curveNumber, legend, index of closest point, pointXY, scaledXY, distance]
777            cn = [curveNum]+ [obj.getLegend()]+ obj.getClosestPoint( pntXY, pointScaled)
778            l.append(cn)
779        return l
780
781    def GetClosetPoint(self, pntXY, pointScaled= True):
782        """Returns list with
783            [curveNumber, legend, index of closest point, pointXY, scaledXY, distance]
784            list for only the closest curve.
785            Returns [] if no curves are being plotted.
786           
787            x, y in user coords
788            if pointScaled == True based on screen coords
789            if pointScaled == False based on user coords
790        """
791        #closest points on screen based on screen scaling (pointScaled= True)
792        #list [curveNumber, index, pointXY, scaledXY, distance] for each curve
793        closestPts= self.GetClosestPoints(pntXY, pointScaled)
794        if closestPts == []:
795            return []  #no graph present
796        #find one with least distance
797        dists = [c[-1] for c in closestPts]
798        mdist = min(dists)  #Min dist
799        i = dists.index(mdist)  #index for min dist
800        return closestPts[i]  #this is the closest point on closest curve
801
802    def UpdatePointLabel(self, mDataDict):
803        """Updates the pointLabel point on screen with data contained in
804            mDataDict.
805
806            mDataDict will be passed to your function set by
807            SetPointLabelFunc.  It can contain anything you
808            want to display on the screen at the scaledXY point
809            you specify.
810
811            This function can be called from parent window with onClick,
812            onMotion events etc.           
813        """
814        if self.last_PointLabel != None:
815            #compare pointXY
816            if mDataDict["pointXY"] != self.last_PointLabel["pointXY"]:
817                #closest changed
818                self._drawPointLabel(self.last_PointLabel) #erase old
819                self._drawPointLabel(mDataDict) #plot new
820        else:
821            #just plot new with no erase
822            self._drawPointLabel(mDataDict) #plot new
823        #save for next erase
824        self.last_PointLabel = mDataDict
825
826    # event handlers **********************************
827    def OnMotion(self, event):
828        if self._zoomEnabled and event.LeftIsDown():
829            if self._hasDragged:
830                self._drawRubberBand(self._zoomCorner1, self._zoomCorner2) # remove old
831            else:
832                self._hasDragged= True
833            self._zoomCorner2[0], self._zoomCorner2[1] = self.GetXY(event)
834            self._drawRubberBand(self._zoomCorner1, self._zoomCorner2) # add new
835
836    def OnMouseLeftDown(self,event):
837        self._zoomCorner1[0], self._zoomCorner1[1]= self.GetXY(event)
838        self._DrawEnabled = False
839
840    def OnMouseLeftUp(self, event):
841        if self._zoomEnabled:
842            if self._hasDragged == True:
843                self._drawRubberBand(self._zoomCorner1, self._zoomCorner2) # remove old
844                self._zoomCorner2[0], self._zoomCorner2[1]= self.GetXY(event)
845                self._hasDragged = False  # reset flag
846                minX, minY= _Numeric.minimum( self._zoomCorner1, self._zoomCorner2)
847                maxX, maxY= _Numeric.maximum( self._zoomCorner1, self._zoomCorner2)
848                self.last_PointLabel = None        #reset pointLabel
849                if self.last_draw != None:
850                    self._DrawEnabled = True
851                    if self.parent.my_local_plot != None:
852                        self.parent.my_local_plot.updateAxes((minX,maxX), (minY,maxY))
853                    self.Draw(self.last_draw[0], xAxis = (minX,maxX), yAxis = (minY,maxY), dc = None)
854            #else: # A box has not been drawn, zoom in on a point
855            ## this interfered with the double click, so I've disables it.
856            #    X,Y = self.GetXY(event)
857            #    self.Zoom( (X,Y), (self._zoomInFactor,self._zoomInFactor) )
858        self._DrawEnabled = True
859
860    def OnMouseDoubleClick(self,event):
861        if self._zoomEnabled:
862            self.Reset()
863       
864    def OnMouseRightDown(self,event):
865        if self._zoomEnabled:
866            X,Y = self.GetXY(event)
867            self.Zoom( (X,Y), (self._zoomOutFactor, self._zoomOutFactor) )
868
869    def OnPaint(self, event):
870        # All that is needed here is to draw the buffer to screen
871        if self.last_PointLabel != None:
872            self._drawPointLabel(self.last_PointLabel) #erase old
873            self.last_PointLabel = None
874        dc = wx.BufferedPaintDC(self, self._Buffer)
875
876    def OnSize(self,event):
877        # The Buffer init is done here, to make sure the buffer is always
878        # the same size as the Window
879        Size  = self.GetClientSize()
880        if Size.width <= 0 or Size.height <= 0:
881            return
882       
883        # Make new offscreen bitmap: this bitmap will always have the
884        # current drawing in it, so it can be used to save the image to
885        # a file, or whatever.
886        self._Buffer = wx.EmptyBitmap(Size[0],Size[1])
887        self._setSize()
888
889        self.last_PointLabel = None        #reset pointLabel
890
891        if self.last_draw is None:
892            self.Clear()
893        else:
894            graphics, xSpec, ySpec = self.last_draw
895            self.Draw(graphics,xSpec,ySpec)
896
897    def OnLeave(self, event):
898        """Used to erase pointLabel when mouse outside window"""
899        if self.last_PointLabel != None:
900            self._drawPointLabel(self.last_PointLabel) #erase old
901            self.last_PointLabel = None
902
903       
904    # Private Methods **************************************************
905    def _setSize(self, width=None, height=None):
906        """DC width and height."""
907        if width == None:
908            (self.width,self.height) = self.GetClientSize()
909        else:
910            self.width, self.height= width,height   
911        self.plotbox_size = 0.97*_Numeric.array([self.width, self.height])
912        xo = 0.5*(self.width-self.plotbox_size[0])
913        yo = self.height-0.5*(self.height-self.plotbox_size[1])
914        self.plotbox_origin = _Numeric.array([xo, yo])
915   
916    def _setPrinterScale(self, scale):
917        """Used to thicken lines and increase marker size for print out."""
918        # line thickness on printer is very thin at 600 dot/in. Markers small
919        self.printerScale= scale
920     
921    def _printDraw(self, printDC):
922        """Used for printing."""
923        if self.last_draw != None:
924            graphics, xSpec, ySpec= self.last_draw
925            self.Draw(graphics,xSpec,ySpec,printDC)
926
927    def _drawPointLabel(self, mDataDict):
928        """Draws and erases pointLabels"""
929        width = self._Buffer.GetWidth()
930        height = self._Buffer.GetHeight()
931        tmp_Buffer = wx.EmptyBitmap(width,height)
932        dcs = wx.MemoryDC()
933        dcs.SelectObject(tmp_Buffer)
934        dcs.Clear()
935        dcs.BeginDrawing()
936        self._pointLabelFunc(dcs,mDataDict)  #custom user pointLabel function
937        dcs.EndDrawing()
938
939        dc = wx.ClientDC( self )
940        #this will erase if called twice
941        dc.Blit(0, 0, width, height, dcs, 0, 0, wx.EQUIV)  #(NOT src) XOR dst
942       
943
944    def _drawLegend(self,dc,graphics,rhsW,topH,legendBoxWH, legendSymExt, legendTextExt):
945        """Draws legend symbols and text"""
946        # top right hand corner of graph box is ref corner
947        trhc= self.plotbox_origin+ (self.plotbox_size-[rhsW,topH])*[1,-1]
948        legendLHS= .091* legendBoxWH[0]  # border space between legend sym and graph box
949        lineHeight= max(legendSymExt[1], legendTextExt[1]) * 1.1 #1.1 used as space between lines
950        dc.SetFont(self._getFont(self._fontSizeLegend))
951        for i in range(len(graphics)):
952            o = graphics[i]
953            s= i*lineHeight
954            if isinstance(o,PolyMarker):
955                # draw marker with legend
956                pnt= (trhc[0]+legendLHS+legendSymExt[0]/2., trhc[1]+s+lineHeight/2.)
957                o.draw(dc, self.printerScale, coord= _Numeric.array([pnt]))
958            elif isinstance(o,PolyLine):
959                # draw line with legend
960                pnt1= (trhc[0]+legendLHS, trhc[1]+s+lineHeight/2.)
961                pnt2= (trhc[0]+legendLHS+legendSymExt[0], trhc[1]+s+lineHeight/2.)
962                o.draw(dc, self.printerScale, coord= _Numeric.array([pnt1,pnt2]))
963            else:
964                raise TypeError, "object is neither PolyMarker or PolyLine instance"
965            # draw legend txt
966            pnt= (trhc[0]+legendLHS+legendSymExt[0], trhc[1]+s+lineHeight/2.-legendTextExt[1]/2)
967            dc.DrawText(o.getLegend(),pnt[0],pnt[1])
968        dc.SetFont(self._getFont(self._fontSizeAxis)) # reset
969
970    def _titleLablesWH(self, dc, graphics):
971        """Draws Title and labels and returns width and height for each"""
972        # TextExtents for Title and Axis Labels
973        dc.SetFont(self._getFont(self._fontSizeTitle))
974        title= graphics.getTitle()
975        titleWH= dc.GetTextExtent(title)
976        dc.SetFont(self._getFont(self._fontSizeAxis))
977        xLabel, yLabel= graphics.getXLabel(),graphics.getYLabel()
978        xLabelWH= dc.GetTextExtent(xLabel)
979        yLabelWH= dc.GetTextExtent(yLabel)
980        return titleWH, xLabelWH, yLabelWH
981   
982    def _legendWH(self, dc, graphics):
983        """Returns the size in screen units for legend box"""
984        if self._legendEnabled != True:
985            legendBoxWH= symExt= txtExt= (0,0)
986        else:
987            # find max symbol size
988            symExt= graphics.getSymExtent(self.printerScale)
989            # find max legend text extent
990            dc.SetFont(self._getFont(self._fontSizeLegend))
991            txtList= graphics.getLegendNames()
992            txtExt= dc.GetTextExtent(txtList[0])
993            for txt in graphics.getLegendNames()[1:]:
994                txtExt= _Numeric.maximum(txtExt,dc.GetTextExtent(txt))
995            maxW= symExt[0]+txtExt[0]   
996            maxH= max(symExt[1],txtExt[1])
997            # padding .1 for lhs of legend box and space between lines
998            maxW= maxW* 1.1
999            maxH= maxH* 1.1 * len(txtList)
1000            dc.SetFont(self._getFont(self._fontSizeAxis))
1001            legendBoxWH= (maxW,maxH)
1002        return (legendBoxWH, symExt, txtExt)
1003
1004    def _drawRubberBand(self, corner1, corner2, flag=True):
1005        """Draws/erases rect box from corner1 to corner2"""
1006        ptx,pty,rectWidth,rectHeight= self._point2ClientCoord(corner1, corner2)
1007        # draw rectangle
1008        dc = wx.ClientDC( self )
1009        dc.BeginDrawing()                 
1010        dc.SetPen(wx.Pen(wx.BLACK))
1011        dc.SetBrush(wx.Brush( wx.WHITE, wx.TRANSPARENT ) )
1012        if flag:
1013            dc.SetLogicalFunction(wx.INVERT)
1014        else:
1015            dc.SetLogicalFunction(wx.COPY)
1016           
1017        dc.DrawRectangle( ptx,pty, rectWidth,rectHeight)
1018        dc.SetLogicalFunction(wx.COPY)
1019        dc.EndDrawing()
1020
1021    def _getFont(self,size):
1022        """Take font size, adjusts if printing and returns wx.Font"""
1023        s = size*self.printerScale
1024        of = self.GetFont()
1025        # Linux speed up to get font from cache rather than X font server
1026        key = (int(s), of.GetFamily (), of.GetStyle (), of.GetWeight ())
1027        font = self._fontCache.get (key, None)
1028        if font:
1029            return font                 # yeah! cache hit
1030        else:
1031            font =  wx.Font(int(s), of.GetFamily(), of.GetStyle(), of.GetWeight())
1032            self._fontCache[key] = font
1033            return font
1034
1035
1036    def _point2ClientCoord(self, corner1, corner2):
1037        """Converts user point coords to client screen int coords x,y,width,height"""
1038        c1= _Numeric.array(corner1)
1039        c2= _Numeric.array(corner2)
1040        # convert to screen coords
1041        pt1= c1*self._pointScale+self._pointShift
1042        pt2= c2*self._pointScale+self._pointShift
1043        # make height and width positive
1044        pul= _Numeric.minimum(pt1,pt2) # Upper left corner
1045        plr= _Numeric.maximum(pt1,pt2) # Lower right corner
1046        rectWidth, rectHeight= plr-pul
1047        ptx,pty= pul
1048        return ptx, pty, rectWidth, rectHeight
1049   
1050    def _axisInterval(self, spec, lower, upper):
1051        """Returns sensible axis range for given spec"""
1052        if spec == 'none' or spec == 'min':
1053            if lower == upper:
1054                return lower-0.5, upper+0.5
1055            else:
1056                return lower, upper
1057        elif spec == 'auto':
1058            range = upper-lower
1059            if range == 0.:
1060                return lower-0.5, upper+0.5
1061            log = _Numeric.log10(range)
1062            power = _Numeric.floor(log)
1063            fraction = log-power
1064            if fraction <= 0.05:
1065                power = power-1
1066            grid = 10.**power
1067            lower = lower - lower % grid
1068            mod = upper % grid
1069            if mod != 0:
1070                upper = upper - mod + grid
1071            return lower, upper
1072        elif type(spec) == type(()):
1073            lower, upper = spec
1074            if lower <= upper:
1075                return lower, upper
1076            else:
1077                return upper, lower
1078        else:
1079            raise ValueError, str(spec) + ': illegal axis specification'
1080
1081    def _drawAxes(self, dc, p1, p2, scale, shift, xticks, yticks):
1082       
1083        penWidth= self.printerScale        # increases thickness for printing only
1084        dc.SetPen(wx.Pen(wx.NamedColour('BLACK'), penWidth))
1085       
1086        # set length of tick marks--long ones make grid
1087        if self._gridEnabled:
1088            x,y,width,height= self._point2ClientCoord(p1,p2)
1089            if self._gridEnabled == 'Horizontal':
1090                yTickLength= width/2.0 +1
1091                xTickLength= 3 * self.printerScale
1092            elif self._gridEnabled == 'Vertical':
1093                yTickLength= 3 * self.printerScale
1094                xTickLength= height/2.0 +1
1095            else:
1096                yTickLength= width/2.0 +1
1097                xTickLength= height/2.0 +1
1098        else:
1099            yTickLength= 3 * self.printerScale  # lengthens lines for printing
1100            xTickLength= 3 * self.printerScale
1101       
1102        if self._xSpec is not 'none':
1103            lower, upper = p1[0],p2[0]
1104            text = 1
1105            for y, d in [(p1[1], -xTickLength), (p2[1], xTickLength)]:   # miny, maxy and tick lengths
1106                a1 = scale*_Numeric.array([lower, y])+shift
1107                a2 = scale*_Numeric.array([upper, y])+shift
1108                dc.DrawLine(a1[0],a1[1],a2[0],a2[1])  # draws upper and lower axis line
1109                for x, label in xticks:
1110                    pt = scale*_Numeric.array([x, y])+shift
1111                    dc.DrawLine(pt[0],pt[1],pt[0],pt[1] + d) # draws tick mark d units
1112                    if text:
1113                        dc.DrawText(label,pt[0],pt[1])
1114                text = 0  # axis values not drawn on top side
1115
1116        if self._ySpec is not 'none':
1117            lower, upper = p1[1],p2[1]
1118            text = 1
1119            h = dc.GetCharHeight()
1120            for x, d in [(p1[0], -yTickLength), (p2[0], yTickLength)]:
1121                a1 = scale*_Numeric.array([x, lower])+shift
1122                a2 = scale*_Numeric.array([x, upper])+shift
1123                dc.DrawLine(a1[0],a1[1],a2[0],a2[1])
1124                for y, label in yticks:
1125                    pt = scale*_Numeric.array([x, y])+shift
1126                    dc.DrawLine(pt[0],pt[1],pt[0]-d,pt[1])
1127                    if text:
1128                        dc.DrawText(label,pt[0]-dc.GetTextExtent(label)[0],
1129                                    pt[1]-0.5*h)
1130                text = 0    # axis values not drawn on right side
1131
1132    def _ticks(self, lower, upper):
1133        ideal = (upper-lower)/7.
1134        log = _Numeric.log10(ideal)
1135        power = _Numeric.floor(log)
1136        fraction = log-power
1137        factor = 1.
1138        error = fraction
1139        for f, lf in self._multiples:
1140            e = _Numeric.fabs(fraction-lf)
1141            if e < error:
1142                error = e
1143                factor = f
1144        grid = factor * 10.**power
1145        if power > 4 or power < -4:
1146            format = '%+7.1e'       
1147        elif power >= 0:
1148            digits = max(1, int(power))
1149            format = '%' + `digits`+'.0f'
1150        else:
1151            digits = -int(power)
1152            format = '%'+`digits+2`+'.'+`digits`+'f'
1153        ticks = []
1154        t = -grid*_Numeric.floor(-lower/grid)
1155        while t <= upper:
1156            ticks.append( (t, format % (t,)) )
1157            t = t + grid
1158        return ticks
1159
1160    _multiples = [(2., _Numeric.log10(2.)), (5., _Numeric.log10(5.))]
1161
1162class TestFrame(wx.Frame):
1163    def __init__(self, parent, id, title, namespace, interface, component_name, port_name):
1164        wx.Frame.__init__(self, parent, id, title,
1165                          wx.DefaultPosition, (600, 400))
1166
1167        self.parent = parent
1168        self.namespace = namespace
1169        self.interface = interface
1170        self.my_local_plot = None
1171        self.component_name = component_name
1172        self.port_name = port_name
1173        self.setup_graphics()
1174       
1175        # Now Create the menu bar and items
1176        self.mainmenu = wx.MenuBar()
1177
1178        menu = wx.Menu()
1179        menu.Append(205, 'E&xit', 'Enough of this already!')
1180        self.Bind(wx.EVT_MENU, self.OnFileExit, id=205)
1181        self.mainmenu.Append(menu, '&File')
1182       
1183        menu = wx.Menu()
1184        menu.Append(206, 'I/Q', 'Make a scatter plot of the I/Q data')
1185        self.Bind(wx.EVT_MENU,self.OnIQDraw, id=206)
1186        menu.Append(207, 'Spectrum', 'Plot and FFT of the signal')
1187        self.Bind(wx.EVT_MENU,self.OnSpectrumDraw, id=207)
1188        self.mainmenu.Append(menu, "&Plotting")
1189
1190        menu = wx.Menu()
1191        menu.Append(300, '&About', 'About this thing...')
1192        self.Bind(wx.EVT_MENU, self.OnHelpAbout, id=300)
1193        self.mainmenu.Append(menu, '&Help')
1194
1195        self.SetMenuBar(self.mainmenu)
1196
1197        # A status bar to tell people what's happening
1198        self.CreateStatusBar(1)
1199       
1200        self.client = PlotCanvas(self)
1201        #define the function for drawing pointLabels
1202        self.client.SetPointLabelFunc(self.DrawPointLabel)
1203        # Create mouse event for showing cursor coords in status bar
1204        self.client.Bind(wx.EVT_LEFT_DOWN, self.OnMouseLeftDown)
1205        # Show closest point when enabled
1206        self.client.Bind(wx.EVT_MOTION, self.OnMotion)
1207       
1208        # Initialize the plot to display the spectrum
1209        self.DrawMode = 2
1210        self.client.SetXSpec('min')
1211        self.client.SetYSpec('min')
1212        self.client.SetEnableZoom(True)
1213
1214        # Bind the close event so we can disconnect the ports
1215        self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
1216
1217        self.Show(True)
1218
1219    def DrawPointLabel(self, dc, mDataDict):
1220        """This is the fuction that defines how the pointLabels are plotted
1221            dc - DC that will be passed
1222            mDataDict - Dictionary of data that you want to use for the pointLabel
1223
1224            As an example I have decided I want a box at the curve point
1225            with some text information about the curve plotted below.
1226            Any wxDC method can be used.
1227        """
1228        # ----------
1229        dc.SetPen(wx.Pen(wx.BLACK))
1230        dc.SetBrush(wx.Brush( wx.BLACK, wx.SOLID ) )
1231       
1232        sx, sy = mDataDict["scaledXY"] #scaled x,y of closest point
1233        dc.DrawRectangle( sx-5,sy-5, 10, 10)  #10by10 square centered on point
1234        px,py = mDataDict["pointXY"]
1235        cNum = mDataDict["curveNum"]
1236        pntIn = mDataDict["pIndex"]
1237        legend = mDataDict["legend"]
1238        #make a string to display
1239        s = "Crv# %i, '%s', Pt. (%.2f,%.2f), PtInd %i" %(cNum, legend, px, py, pntIn)
1240        dc.DrawText(s, sx , sy+1)
1241        # -----------
1242
1243    def OnMouseLeftDown(self,event):
1244        s= "Left Mouse Down at Point: (%.4f, %.4f)" % self.client.GetXY(event)
1245        self.SetStatusText(s)
1246        event.Skip()            #allows plotCanvas OnMouseLeftDown to be called
1247
1248    def OnMotion(self, event):
1249        #show closest point (when enbled)
1250        if self.client.GetEnablePointLabel() == True:
1251            #make up dict with info for the pointLabel
1252            #I've decided to mark the closest point on the closest curve
1253            dlst= self.client.GetClosetPoint( self.client.GetXY(event), pointScaled= True)
1254            if dlst != []:    #returns [] if none
1255                curveNum, legend, pIndex, pointXY, scaledXY, distance = dlst
1256                #make up dictionary to pass to my user function (see DrawPointLabel)
1257                mDataDict= {"curveNum":curveNum, "legend":legend, "pIndex":pIndex,\
1258                            "pointXY":pointXY, "scaledXY":scaledXY}
1259                #pass dict to update the pointLabel
1260                self.client.UpdatePointLabel(mDataDict)
1261        event.Skip()           #go to next handler
1262
1263    def OnFileExit(self, event):
1264        self.Close()
1265
1266    def OnIQDraw(self, event):
1267        self.DrawMode = 1
1268        self.my_local_plot.first_draw = True
1269        self.my_local_plot.update_draw = False
1270   
1271    def OnSpectrumDraw(self, event):
1272        self.DrawMode = 2
1273        self.my_local_plot.first_draw = True
1274        self.client.SetXSpec('min')
1275        self.client.SetYSpec('min')
1276
1277    def OnPlotRedraw(self,event):
1278        self.client.Redraw()
1279
1280    def OnPlotClear(self,event):
1281        self.client.Clear()
1282       
1283    def OnPlotScale(self, event):
1284        if self.client.last_draw != None:
1285            graphics, xAxis, yAxis= self.client.last_draw
1286            self.client.Draw(graphics,(1,3.05),(0,1))
1287
1288    def OnEnableZoom(self, event):
1289        self.client.SetEnableZoom(event.IsChecked())
1290       
1291    def OnEnableGrid(self, event):
1292        self.client.SetEnableGrid(event.IsChecked())
1293       
1294    def OnEnableLegend(self, event):
1295        self.client.SetEnableLegend(event.IsChecked())
1296
1297    def OnEnablePointLabel(self, event):
1298        self.client.SetEnablePointLabel(event.IsChecked())
1299
1300    def OnScrUp(self, event):
1301        self.client.ScrollUp(1)
1302       
1303    def OnScrRt(self,event):
1304        self.client.ScrollRight(2)
1305
1306    def OnReset(self,event):
1307        self.client.Reset()
1308
1309    def OnHelpAbout(self, event):
1310        from wx.lib.dialogs import ScrolledMessageDialog
1311        about = ScrolledMessageDialog(self, "This is a simple plotting widget", "About...")
1312        about.ShowModal()
1313
1314    def resetDefaults(self):
1315        """Just to reset the fonts back to the PlotCanvas defaults"""
1316        self.client.SetFont(wx.Font(10,wx.SWISS,wx.NORMAL,wx.NORMAL))
1317        self.client.SetFontSizeAxis(10)
1318        self.client.SetFontSizeLegend(7)
1319        self.client.SetXSpec('auto')
1320        self.client.SetYSpec('auto')
1321   
1322    def setup_graphics(self):
1323        self.CORBA_being_used = False
1324
1325        if True:               
1326         self.CORBA_being_used = True
1327         self.orb = CORBA.ORB_init(sys.argv, CORBA.ORB_ID)
1328         obj = self.orb.resolve_initial_references("NameService")
1329         rootContext = obj._narrow(CosNaming.NamingContext)
1330         if rootContext is None:
1331             print "Failed to narrow the root naming context"
1332             sys.exit(1)
1333         name = [CosNaming.NameComponent(self.component_name[0],""),
1334             CosNaming.NameComponent(self.component_name[1],""),
1335             CosNaming.NameComponent(self.component_name[2],"")]
1336     
1337         try:
1338             ResourceRef = rootContext.resolve(name)
1339     
1340         except:
1341             print "Required resource not found"
1342             sys.exit(1)
1343     
1344         ResourceHandle = ResourceRef._narrow(CF.Resource)
1345         PortReference = ResourceHandle.getPort(self.port_name)
1346         if PortReference is None:
1347             print "Failed to get Port reference"
1348         self.PortHandle = PortReference._narrow(CF.Port)
1349         
1350         self.my_local_plot = None
1351         
1352         if self.interface == "complexFloat":
1353             self.my_local_plot = my_graph_structure_complexFloat(self.orb, self)
1354         elif self.interface == "complexShort":
1355             self.my_local_plot = my_graph_structure_complexShort(self.orb, self)
1356         elif self.interface == "realChar":
1357             self.my_local_plot = my_graph_structure_realChar(self.orb, self)
1358
1359         obj_poa = self.orb.resolve_initial_references("RootPOA")
1360         poaManager = obj_poa._get_the_POAManager()
1361         poaManager.activate()
1362         obj_poa.activate_object(self.my_local_plot)
1363         self.PortHandle.connectPort(self.my_local_plot._this(), "thisismyconnectionid_plot")
1364         #orb.run()
1365   
1366    def updateWaveformData(self, data):
1367        if data.has_key('center_freq'):
1368            self.my_local_plot.center_freq = data['center_freq']
1369        if data.has_key('sample_rate'):
1370            self.my_local_plot.sample_rate = data['sample_rate']
1371        if data.has_key('decimation'):
1372            self.my_local_plot.decimation = data['decimation']
1373
1374        self.my_local_plot.updateSpectrumPlotData()
1375
1376    def OnCloseWindow(self,event):
1377        if hasattr(self.parent, 'removeToolFrame'):
1378            self.parent.removeToolFrame(self)
1379        self = None
1380        event.Skip()
1381
1382    def __del__(self):
1383        if self.CORBA_being_used:
1384            self.PortHandle.disconnectPort("thisismyconnectionid_plot")
1385            while (_time.time() - self.my_local_plot.end_time) < 1.5:
1386                #print (time.time() - self.my_local_plot.end_time)
1387                pass
1388                #_time.sleep(1)
1389
1390class my_graph_structure:
1391    '''
1392    Base class for plotting
1393    '''
1394    def __init__(self, orb, gui):
1395        #print "Initializing consumer..."
1396        self.orb = orb
1397        self.gui = gui
1398        self.end_time = time.time()
1399        self.begin_time = time.time()
1400        self.first_draw = True
1401        self.center_freq = 0.0
1402        self.sample_rate = 0.0
1403        self.decimation = 20
1404        self.packet_counter = 0
1405        self.last_plot_time = 0
1406        self.plot_interval = 1 # interval at which plot is refreshed (seconds)
1407        self.bandwidth = 0.0
1408        self.yAxis = (3000,10000)
1409        self.xAxis = None
1410        self.minX=100000
1411        self.maxX=-100000
1412        self.minY=100000
1413        self.maxY=-100000
1414        self.minX_old=0
1415        self.maxX_old=0
1416        self.minY_old=0
1417        self.maxY_old=0
1418        self.dataHasChanged = False
1419        self.fftOrder = 512
1420        self.i_array = _Numeric.array(range(self.fftOrder),copy=False)
1421        self.q_array = _Numeric.array(range(self.fftOrder),copy=False)
1422        self.c_array = _Numeric.array(range(self.fftOrder),copy=False)
1423        self.initialArray = _Numeric.arange(-self.fftOrder/2, self.fftOrder/2, step=0.5)
1424        self.plottingPeriodogram = False #True #False
1425
1426
1427    def updateOnMetaData(self, metadata):
1428        self.center_freq = metadata.carrier_frequency
1429        self.sample_rate = metadata.sampling_frequency
1430        if self.gui.DrawMode == 2:
1431            self.updateSpectrumPlotData()
1432
1433    def pushPacketMetaData(self, I_data, Q_data, metadata):
1434        self.updateOnMetaData(metadata)
1435        self.pushPacket(I_data, Q_data)
1436
1437 #   def pushPacket(self, I_data, Q_data):
1438 #       if self.packet_counter == 0:
1439 #           self.plotData(I_data, Q_data)
1440 #       if self.packet_counter < (self.decimation -1):
1441 #           self.packet_counter = self.packet_counter + 1
1442 #       else:
1443 #           self.packet_counter = 0
1444
1445    def pushPacket(self, I_data, Q_data):
1446        pushPacket_start_time = _time.time()
1447        if (pushPacket_start_time - self.last_plot_time)  > self.plot_interval:
1448            self.plotData(I_data, Q_data)
1449            self.last_plot_time = pushPacket_start_time
1450            self.plot_interval = 3 * (time.time() - pushPacket_start_time)
1451
1452    def plotData(self, I_data, Q_data):
1453        locker = wx.MutexGuiLocker()
1454
1455        graph_legend = ''
1456        x_axis_label = ''
1457        y_axis_label = ''
1458        if self.gui.DrawMode == 1:
1459            self.data1 = _Numeric.arange(len(I_data)*2)
1460            self.data1.shape = (len(I_data), 2)
1461            self.data1[:,0] = I_data
1462            self.data1[:,1] = Q_data
1463            graph_legend = 'I/Q Data'
1464            x_axis_label = 'I'
1465            y_axis_label = 'Q'
1466            # set plot to scatter, not line
1467            markers1 = PolyMarker(self.data1, legend=graph_legend, colour='purple')
1468            if not self.first_draw:
1469                minX = _Numeric.minimum.reduce(self.data1[:,0])
1470                maxX = _Numeric.maximum.reduce(self.data1[:,0])
1471                minY = _Numeric.minimum.reduce(self.data1[:,1])
1472                maxY = _Numeric.maximum.reduce(self.data1[:,1])
1473                if (minX < self.minX) or (maxX > self.maxX) or (minY < self.minY) or (maxY > self.maxY):
1474                    self.first_draw = True
1475                    self.update_draw = True
1476        elif self.gui.DrawMode == 2:
1477
1478            # the input array should not be shorter than the FFT
1479            if len(I_data) < self.fftOrder:
1480                # zero-pad data to fit length
1481                for tmp_index in range(self.fftOrder-len(I_data)):
1482                    I_data.append(0)
1483                    Q_data.append(0)
1484            self.i_array[:] = I_data[0:self.fftOrder]
1485            self.q_array[:] = Q_data[0:self.fftOrder]
1486            self.c_array = self.i_array + self.q_array*1j
1487
1488            spectrum = _Numeric.fft.fft(self.c_array, n=self.fftOrder)
1489            spectrum = _Numeric.fft.helper.fftshift(spectrum)
1490            self.data1 = self.initialArray.copy()
1491            self.data1.shape = (len(spectrum), 2)
1492            if self.plottingPeriodogram:
1493                my_sp = 10 * _Numeric.log10(pow(abs(spectrum),2) + 0.00001)
1494            else:
1495                my_sp = 20 * _Numeric.log10(abs(spectrum) + 0.00001)
1496            #my_sp =  my_sp * 1000
1497            self.data1[:,1] = my_sp.astype('i4')
1498            if self.plottingPeriodogram:
1499                graph_legend = 'Periodogram'
1500                y_axis_label = '|FFT|^2 (dB)'
1501            else:
1502                graph_legend = 'Spectral Data'
1503                y_axis_label = 'Magnitude (dB)'
1504            x_axis_label = 'Normalized Frequency'
1505            self.yAxis = (-50,150)
1506            self.xAxis = None
1507            markers1 = PolyLine(self.data1, legend=graph_legend, colour='red')
1508   
1509        if self.first_draw:
1510            if self.gui.DrawMode == 1:
1511                minX = _Numeric.minimum.reduce(self.data1[:,0])
1512                maxX = _Numeric.maximum.reduce(self.data1[:,0])
1513                minY = _Numeric.minimum.reduce(self.data1[:,1])
1514                maxY = _Numeric.maximum.reduce(self.data1[:,1])
1515                multiply_factor = 1.5
1516                if self.update_draw:
1517                    if self.minX > minX:
1518                        if (self.minX_old > minX and self.minX > (minX * multiply_factor)):
1519                            if self.minX_old > minX:
1520                                self.minX = self.minX_old
1521                            else:
1522                                self.minX = minX
1523                        self.minX_old = minX
1524                    if self.maxX < maxX:
1525                        if (self.maxX_old < maxX and self.maxX < (maxX * multiply_factor)):
1526                            if self.maxX_old < maxX:
1527                                self.maxX = self.maxX_old
1528                            else:
1529                                self.maxX = maxX
1530                        self.maxX_old = maxX
1531                    if self.minY > minY:
1532                        if (self.minY_old > minY and self.minY > (minY * multiply_factor)):
1533                            if self.minY_old > minY:
1534                               self.minY = self.minY_old
1535                            else:
1536                               self.minY = minY
1537                        self.minY_old = minY
1538                    if self.maxY < maxY:
1539                        if (self.maxY_old < maxY and self.maxY < (maxY * multiply_factor)):
1540                            if self.maxY_old < maxY:
1541                                self.maxY = self.maxY_old
1542                            else:
1543                                self.maxY = maxY
1544                        self.maxY_old = maxY
1545                    self.update_draw = False
1546                else:
1547                    if self.minX > minX:
1548                        self.minX = minX
1549                    if self.maxX < maxX:
1550                        self.maxX = maxX
1551                    if self.minY > minY:
1552                        self.minY = minY
1553                    if self.maxY < maxY:
1554                        self.maxY = maxY
1555                self.updateAxes((self.minX,self.maxX), (self.minY,self.maxY))
1556            self.gui.client.Draw(PlotGraphics([markers1],self.gui.component_name[2], x_axis_label, y_axis_label),yAxis=self.yAxis, xAxis=self.xAxis)
1557            self.first_draw = False
1558        else:
1559            if self.gui.DrawMode == 1:
1560                self.gui.client.Draw(PlotGraphics([markers1],self.gui.component_name[2], x_axis_label, y_axis_label),yAxis=self.gui.client.last_draw[2],xAxis=self.xAxis)
1561            else:
1562                self.gui.client.Draw(PlotGraphics([markers1],self.gui.component_name[2], x_axis_label, y_axis_label),yAxis=self.gui.client.last_draw[2])
1563            pass
1564           
1565        self.end_time = _time.time()
1566
1567    def updateSpectrumPlotData(self):
1568        self.bandwidth = self.sample_rate
1569        self.xAxis = (self.center_freq - self.bandwidth/2.0, self.center_freq + self.bandwidth/2.0)
1570        # next line sometimes throws divide by zero exception
1571        #self.initialArray = (_Numeric.arange(self.xAxis[0],self.xAxis[1],step=self.bandwidth/(self.fftOrder*2.0)))/1000000
1572
1573    def updateAxes(self, x_axis, y_axis):
1574        self.xAxis = x_axis
1575        self.yAxis = y_axis
1576
1577class my_graph_structure_complexFloat(my_graph_structure, standardInterfaces__POA.complexFloat):
1578    '''Derived class for plotting complexFloat data'''
1579
1580class my_graph_structure_complexShort(my_graph_structure, standardInterfaces__POA.complexShort):
1581    '''Derived class for plotting complexShort data'''
1582
1583class my_graph_structure_realChar(my_graph_structure, standardInterfaces__POA.realChar):
1584    '''Derived class for plotting realChar data'''
1585
1586def create(parent,namespace, interface, ns_name, port_name):
1587    return TestFrame(parent, -1, "PlotCanvas", namespace, interface, ns_name, port_name)
1588
1589def __test(argv):
1590
1591    class MyApp(wx.App):
1592        def OnInit(self):
1593            wx.InitAllImageHandlers()
1594            frame = create(None, argv[1], argv[2], [argv[3], argv[4], argv[5]], argv[6])
1595            self.SetTopWindow(frame)
1596            return True
1597
1598
1599    app = MyApp(0)
1600    app.MainLoop()
1601
1602if __name__ == '__main__':
1603    if not (len(sys.argv)==7):
1604        print "usage: plot.py <interface_namespace> <type> <Domain_name> <Waveform_name> <Component_name> <port_name>"
1605        sys.exit(1)
1606    __test(sys.argv)
Note: See TracBrowser for help on using the browser.