| 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 | |
|---|
| 22 | import string as _string |
|---|
| 23 | import time as _time |
|---|
| 24 | import wx |
|---|
| 25 | import sys |
|---|
| 26 | from omniORB import CORBA |
|---|
| 27 | import CosNaming |
|---|
| 28 | from ossie.cf import CF, CF__POA |
|---|
| 29 | from ossie.standardinterfaces import standardInterfaces |
|---|
| 30 | from ossie.standardinterfaces import standardInterfaces__POA |
|---|
| 31 | import time, threading |
|---|
| 32 | import struct |
|---|
| 33 | import math |
|---|
| 34 | import sys |
|---|
| 35 | |
|---|
| 36 | # Needs Numeric or numarray |
|---|
| 37 | try: |
|---|
| 38 | #import Numeric as _Numeric |
|---|
| 39 | import numpy as _Numeric |
|---|
| 40 | except: |
|---|
| 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 |
|---|
| 53 | try: |
|---|
| 54 | x = standardInterfaces.MetaData |
|---|
| 55 | # RadioMetaData interface is installed |
|---|
| 56 | HAVE_RADIO_METADATA = True |
|---|
| 57 | except AttributeError: |
|---|
| 58 | # RadioMetaData interface is not installed |
|---|
| 59 | HAVE_RADIO_METADATA = False |
|---|
| 60 | |
|---|
| 61 | # |
|---|
| 62 | # Plotting classes... |
|---|
| 63 | # |
|---|
| 64 | class 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 | |
|---|
| 126 | class 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 | |
|---|
| 166 | class 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 | |
|---|
| 275 | class 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 | |
|---|
| 366 | class 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 | |
|---|
| 1162 | class 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 | |
|---|
| 1390 | class 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 | |
|---|
| 1577 | class my_graph_structure_complexFloat(my_graph_structure, standardInterfaces__POA.complexFloat): |
|---|
| 1578 | '''Derived class for plotting complexFloat data''' |
|---|
| 1579 | |
|---|
| 1580 | class my_graph_structure_complexShort(my_graph_structure, standardInterfaces__POA.complexShort): |
|---|
| 1581 | '''Derived class for plotting complexShort data''' |
|---|
| 1582 | |
|---|
| 1583 | class my_graph_structure_realChar(my_graph_structure, standardInterfaces__POA.realChar): |
|---|
| 1584 | '''Derived class for plotting realChar data''' |
|---|
| 1585 | |
|---|
| 1586 | def create(parent,namespace, interface, ns_name, port_name): |
|---|
| 1587 | return TestFrame(parent, -1, "PlotCanvas", namespace, interface, ns_name, port_name) |
|---|
| 1588 | |
|---|
| 1589 | def __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 | |
|---|
| 1602 | if __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) |
|---|