1# -*- coding: utf-8 -*- 2# Copyright (C) 2012, Almar Klein 3# 4# Visvis is distributed under the terms of the (new) BSD License. 5# The full license can be found in 'license.txt'. 6 7""" Module line 8 9Defines the Line class, which represents a line connected (optionally) 10with markers. It is the object created with the plot() function. 11 12The lines are drawn simply with OpenGL lines. The markers are drawn 13with OpenGl points if possible, and using sprites otherwise. 14 15Since this is such a fundamental part of visvis, and it's uses by 16for example the Legend class, this module is part of the core. 17 18""" 19 20import OpenGL.GL as gl 21import numpy as np 22import math 23 24from visvis.utils.pypoints import Pointset, is_Pointset 25from visvis.core.misc import PropWithDraw, DrawAfter, basestring 26from visvis.core.misc import Range, getColor, getOpenGlCapable 27from visvis.core.base import Wobject 28 29 30# int('1010101010101010',2) int('1100110011001100',2) 31lineStyles = { ':':int('1010101010101010',2), '--':int('1111000011110000',2), 32 '-.':int('1110010011100100',2), '.-':int('1110010011100100',2), 33 '-':False, '+':False} 34 35 36class Sprite: 37 """ Sprite(data, width) 38 39 Represents an OpenGL sprite object. 40 41 """ 42 43 def __init__(self, data, width): 44 """ Supply the data, which must be uint8 alpha data, 45 preferably shaped with a power of two. """ 46 self._texId = 0 47 self._data = data 48 self._width = width 49 self._canUse = None # set to True/False if OpenGl version high enough 50 51 def Create(self): 52 """ Create an OpenGL texture from the data. """ 53 54 # detemine now if we can use point sprites 55 self._canUse = getOpenGlCapable('2.0', 56 'point sprites (for advanced markers)') 57 if not self._canUse: 58 return 59 60 # gl.glEnable(gl.GL_TEXTURE_2D) 61 62 # make texture 63 self._texId = gl.glGenTextures(1) 64 gl.glBindTexture(gl.GL_TEXTURE_2D, self._texId) 65 66 # set interpolation and extrapolation parameters 67 tmp = gl.GL_NEAREST # gl.GL_NEAREST | gl.GL_LINEAR 68 gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER, tmp) 69 gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_FILTER, tmp) 70 71 # upload data 72 shape = self._data.shape 73 gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_ALPHA, shape[0], shape[1], 74 0, gl.GL_ALPHA, gl.GL_UNSIGNED_BYTE, self._data) 75 76 77 @property 78 def usable(self): 79 return self._canUse 80 81 82 def Enable(self): 83 """ Enable the sprite, drawing points after calling this 84 draws this sprite at each point. """ 85 86 if not self._texId and self._canUse in [None, True]: 87 self.Create() 88 89 if not self._canUse: 90 gl.glEnable(gl.GL_POINT_SMOOTH) 91 gl.glPointSize(self._width) 92 return # proceed if None; canUse is assigned in Create() 93 94 # bind to texture 95 gl.glEnable(gl.GL_TEXTURE_2D) 96 gl.glBindTexture(gl.GL_TEXTURE_2D, self._texId) 97 98 # get allowed point size 99 sizeRange = gl.glGetFloatv(gl.GL_ALIASED_POINT_SIZE_RANGE) 100 gl.glPointParameterf( gl.GL_POINT_SIZE_MIN, sizeRange[0] ) 101 gl.glPointParameterf( gl.GL_POINT_SIZE_MAX, sizeRange[1] ) 102 103 # enable sprites and set params 104 gl.glEnable(gl.GL_POINT_SPRITE) 105 106 # tell opengl to iterate over the texture 107 gl.glTexEnvf( gl.GL_POINT_SPRITE, gl.GL_COORD_REPLACE, gl.GL_TRUE ) 108 109 110 def Disable(self): 111 """ Return to normal points. """ 112 if self._canUse: 113 gl.glDisable(gl.GL_TEXTURE_2D) 114 gl.glDisable(gl.GL_POINT_SPRITE) 115 else: 116 gl.glDisable(gl.GL_POINT_SMOOTH) 117 118 119 def Destroy(self): 120 """ Destroy the sprite, removing the texture from opengl. """ 121 if self._texId > 0: 122 try: 123 gl.glDeleteTextures([self._texId]) 124 self._texId = 0 125 except Exception: 126 pass 127 128 def __del__(self): 129 """ Delete when GC cleans up. """ 130 self.Destroy() 131 132 133class MarkerManager: 134 """ MarkerManager() 135 136 The markermanager manages sprites to draw the markers. It 137 creates the sprite textures on the fly when they are needed. 138 Already created sprites are reused. 139 140 Given the markerStyle, markerWidth and markerEdgeWidth a marker 141 can be requested. 142 143 """ 144 145 def __init__(self): 146 # a dict of 3 element tuples (size, sprite1, sprite2) 147 # where the first is for the face, the second for the edge. 148 self.sprites = {} 149 150 def GetSprites(self, ms, mw, mew): 151 """ GetSprites(mw, mw, mew) 152 153 Get the sprites for drawing the edge and the face, given 154 the ms, mw and mew. 155 156 This will create the appropriate sprites or reuse a previously 157 created set of sprites if available. 158 159 Returns a tuple (size, faceSprite, edgeSprite) 160 """ 161 # find the diameter which best fits, but which is a multiple of 4 162 # such that 32 bits are always filled. 163 mw, mew = int(mw), int(mew) 164 165 # create key of these settings 166 key = "%s_%i_%i" % (ms, mw, mew) 167 168 # if it does not exist, create it! 169 if key not in self.sprites: 170 self.sprites[key] = self._CreateSprites(ms, mw, mew) 171 else: 172 # check if it is a valid texture ... 173 id = self.sprites[key][1]._texId 174 if not gl.glIsTexture(id): 175 self.sprites[key] = self._CreateSprites(ms, mw, mew) 176 177 # return 178 return self.sprites[key] 179 180 181 def _CreateSprites(self, ms, mw, mew): 182 """ Create the sprites from scratch. """ 183 184 ## init 185 # We'll make a 2D array of size d, which fits the marker completely. 186 # Then we make a second array with the original eroded as many 187 # times as the edge is wide. 188 189 # find nearest multiple of four 190 d = 4 191 while d < mw+2*mew: 192 d += 4 193 # what is the offset for the face 194 #dd = ( d-(mw+2*mew) ) / 2 195 dd = (d-mw)//2 196 197 # calc center 198 c = mw/2.0 199 200 # create patch 201 data1 = np.zeros((d,d),dtype=np.uint8) 202 203 # create subarray for face 204 data2 = data1[dd:,dd:][:mw,:mw] 205 206 ## define marker functions 207 def square(xy): 208 x, y = xy 209 data2[y,x]=255 210 def diamond(xy): 211 x, y = xy 212 if y > x-mw/2 and y<x+mw/2 and y > (mw-x)-c and y<(mw-x)+c: 213 data2[y,x]=255 214 def plus(xy): 215 x, y = xy 216 if y > mw/3 and y < 2*mw/3: 217 data2[y,x]=255 218 if x > mw/3 and x < 2*mw/3: 219 data2[y,x]=255 220 def cross(xy): 221 x, y = xy 222 if y > x-mw/4 and y < x+mw/4: 223 data2[y,x]=255 224 if y > (mw-x)-mw/4 and y < (mw-x)+mw/4: 225 data2[y,x]=255 226 def flower(xy): 227 x, y = xy 228 a = math.atan2(y-c,x-c) 229 r = (x-c)**2 + (y-c)**2 230 relAng = 5 * a / (2*math.pi) # whole circle from 1 to 5 231 subAng = (relAng % 1) # get the non-integer bit 232 if subAng>0.5: subAng = 1-subAng 233 refRad1, refRad2 = c/4, c 234 a = math.sin(subAng*math.pi) 235 refRad = (1-a)*refRad1 + a*refRad2 236 if r < refRad**2: 237 data2[y,x]=255 238 def star5(xy): 239 x, y = xy 240 a = math.atan2(y-c,x-c) - 0.5*math.pi 241 r = (x-c)**2 + (y-c)**2 242 relAng = 5 * a / (2*math.pi) # whole circle from 1 to 5 243 subAng = (relAng % 1) # get the non-integer bit 244 if subAng>0.5: subAng = 1-subAng 245 refRad1, refRad2 = c/4, c 246 a = math.asin(subAng*2) / (math.pi/2) 247 refRad = (1-a)*refRad1 + a*refRad2 248 if r < refRad**2: 249 data2[y,x]=255 250 def star6(xy): 251 x, y = xy 252 a = math.atan2(y-c,x-c) 253 r = (x-c)**2 + (y-c)**2 254 relAng = 6 * a / (2*math.pi) # whole circle from 1 to 5 255 subAng = (relAng % 1) # get the non-integer bit 256 if subAng>0.5: subAng = 1-subAng 257 refRad1, refRad2 = c/3, c 258 a = math.asin(subAng*2) / (math.pi/2) 259 refRad = (1-a)*refRad1 + a*refRad2 260 if r < refRad**2: 261 data2[y,x]=255 262 def circle(xy): 263 x,y = xy 264 r = (x-c)**2 + (y-c)**2 265 if r < c**2: 266 data2[y,x] = 255 267 def triangleDown(xy): 268 x,y = xy 269 if x >= 0.5*y and x <= mw-0.5*(y+1): 270 data2[y,x] = 255 271 def triangleUp(xy): 272 x,y = xy 273 if x >= c-0.5*y and x <= c+0.5*y: 274 data2[y,x] = 255 275 def triangleLeft(xy): 276 x,y = xy 277 if y >= c-0.5*x and y <= c+0.5*x: 278 data2[y,x] = 255 279 def triangleRight(xy): 280 x,y = xy 281 print('oef', x, y) 282 if y >= 0.5*x and y <= mw-0.5*(x+1): 283 data2[y,x] = 255 284 285 # a dict ms to function 286 funcs = { 's':square, 'd':diamond, '+':plus, 'x':cross, 287 '*':star5, 'p':star5, 'h':star6, 'f':flower, 288 '.':circle, 'o':circle, 'v':triangleDown, 289 '^':triangleUp, '<':triangleLeft, '>':triangleRight} 290 291 # select function 292 try: 293 func = funcs[ms] 294 except KeyError: 295 func = circle 296 297 ## Create face 298 I,J = np.where(data2==0) 299 for xy in zip(I,J): 300 func(xy) 301 302 ## dilate x times to create edge 303 # we add a border to the array to make the dilation possible 304 data3 = np.zeros((d+4,d+4), dtype=np.uint8) 305 data3[2:-2,2:-2] = 1 306 # retrieve indices. 307 I,J = np.where(data3==1) 308 # copy face 309 data3[2:-2,2:-2] = data1 310 tmp = data3.copy() 311 # apply 312 def dilatePixel(xy): 313 x,y = xy 314 if tmp[y-1:y+2,x-1:x+2].max(): 315 data3[y,x] = 255 316 for i in range(int(mew)): 317 for xy in zip(I,J): 318 dilatePixel(xy) 319 tmp = data3.copy() 320 # remove border 321 data3 = data3[2:-2,2:-2] 322 323 ## create sprites and return 324 325 sprite1 = Sprite(data1, mw) 326 sprite2 = Sprite(data3-data1, mw+2*mew) 327 328 return d, sprite1, sprite2 329 330 331class Line(Wobject): 332 """ Line(parent, points) 333 334 The line class represents a set of points (locations) in world coordinates. 335 This class can draw lines between the points, markers at the point 336 coordinates, or both. 337 338 Line objects can be created with the function vv.plot(). 339 340 Performance tips 341 ---------------- 342 The s, o (and .) styles can be drawn using standard 343 OpenGL points if alpha is 1 or if no markeredge is drawn. 344 345 Otherwise point sprites are used, which can be slower 346 on some (older) cards (like ATI, Nvidia performs quite ok with with 347 sprites). 348 349 Some video cards simply do not support sprites (seen on ATI). 350 351 """ 352 353 def __init__(self, parent, points): 354 Wobject.__init__(self, parent) 355 356 # Store points 357 self.SetPoints(points) 358 359 # init line properties 360 self._lw, self._ls, self._lc = 1, '-', 'b' 361 # init marker properties 362 self._mw, self._ms, self._mc = 7, '', 'b' 363 # init marker edge properties 364 self._mew, self._mec = 1, 'k' 365 366 # alpha values 367 self._alpha1 = 1 368 369 370 def _AsFloat(self, value, descr): 371 """ Make sure a value is a float. """ 372 try: 373 value = float(value) 374 if value<0: 375 raise ValueError() 376 except ValueError: 377 tmp = "the value must be a number equal or larger than zero!" 378 raise Exception("Error in %s: %s" % (descr, tmp) ) 379 return value 380 381 382 def _GetLimits(self): 383 """ Get the limits in world coordinates between which the object exists. 384 """ 385 386 # Obtain untransformed coords (if not an empty set) 387 if not self._points: 388 return None 389 p = self._points.data 390 valid = np.isfinite(p[:,0]) * np.isfinite(p[:,1]) * np.isfinite(p[:,2]) 391 validpoints = p[valid, :] 392 x1, y1, z1 = validpoints.min(axis=0) 393 x2, y2, z2 = validpoints.max(axis=0) 394 395 # There we are 396 return Wobject._GetLimits(self, x1, x2, y1, y2, z1, z2) 397 398 399 ## Create properties 400 401 402 @PropWithDraw 403 def lw(): 404 """ Get/Set the lineWidth: the width of the line in pixels. 405 If zero, the line is not drawn. 406 """ 407 def fget(self): 408 return self._lw 409 def fset(self, value): 410 self._lw = self._AsFloat(value, 'lineWidth') 411 return locals() 412 413 @PropWithDraw 414 def ls(): 415 """ Get/Set the lineStyle: the style of the line. 416 * Solid line: '-' 417 * Dotted line: ':' 418 * Dashed line: '--' 419 * Dash-dot line: '-.' or '.-' 420 * A line that is drawn between each pair of points: '+' 421 * No line: '' or None. 422 """ 423 def fget(self): 424 return self._ls 425 def fset(self, value): 426 if not value: 427 value = None 428 elif not isinstance(value, basestring): 429 raise Exception("Error in lineStyle: style must be a string!") 430 elif value not in ['-', '--', ':', '-.', '.-', '+']: 431 raise Exception("Error in lineStyle: unknown line style!") 432 self._ls = value 433 return locals() 434 435 @PropWithDraw 436 def lc(): 437 """ Get/Set the lineColor: the color of the line, as a 3-element 438 tuple or as a single character string (shown in uppercase): 439 Red, Green, Blue, Yellow, Cyan, Magenta, blacK, White. 440 """ 441 def fget(self): 442 return self._lc 443 def fset(self, value): 444 value = getColor(value, 'lineColor') 445 self._lc = value 446 return locals() 447 448 449 @PropWithDraw 450 def mw(): 451 """ Get/Set the markerWidth: the width (bounding box) of the marker 452 in (screen) pixels. If zero, no marker is drawn. 453 """ 454 def fget(self): 455 return self._mw 456 def fset(self, value): 457 self._mw = self._AsFloat(value, 'markerWidth') 458 return locals() 459 460 @PropWithDraw 461 def ms(): 462 """ Get/Set the markerStyle: the style of the marker. 463 * Plus: '+' 464 * Cross: 'x' 465 * Square: 's' 466 * Diamond: 'd' 467 * Triangle (pointing up, down, left, right): '^', 'v', '<', '>' 468 * Pentagram star: 'p' or '*' 469 * Hexgram: 'h' 470 * Point/cirle: 'o' or '.' 471 * No marker: '' or None 472 """ 473 def fget(self): 474 return self._ms 475 def fset(self, value): 476 if not value: 477 value = None 478 elif not isinstance(value, basestring): 479 raise Exception("markerstyle (ms) should be a string!") 480 elif value not in 'sd+x*phfv^><.o': 481 raise Exception("Error in markerStyle: unknown line style!") 482 self._ms = value 483 return locals() 484 485 @PropWithDraw 486 def mc(): 487 """ Get/Set the markerColor: The color of the face of the marker 488 If None, '', or False, the marker face is not drawn (but the edge is). 489 """ 490 def fget(self): 491 return self._mc 492 def fset(self, value): 493 self._mc = getColor(value, 'markerColor') 494 return locals() 495 496 @PropWithDraw 497 def mew(): 498 """ Get/Set the markerEdgeWidth: the width of the edge of the marker. 499 If zero, no edge is drawn. 500 """ 501 def fget(self): 502 return self._mew 503 def fset(self, value): 504 self._mew = self._AsFloat(value, 'markerEdgeWidth') 505 return locals() 506 507 @PropWithDraw 508 def mec(): 509 """ Get/Set the markerEdgeColor: the color of the edge of the marker. 510 """ 511 def fget(self): 512 return self._mec 513 def fset(self, value): 514 self._mec = getColor(value, 'markerEdgeColor') 515 return locals() 516 517# # create aliases 518# lineWidth = lw 519# lineStyle = ls 520# lineColor = lc 521# markerWidth = mw 522# markerStyle = ms 523# markerColor = mc 524# markerEdgeWidth = mew 525# markerEdgeColor = mec 526 527 @PropWithDraw 528 def alpha(): 529 """ Get/Set the alpha (transparancy) of the line and markers. 530 When this is < 1, the line cannot be anti-aliased, and it 531 is drawn on top of any other wobjects. 532 """ 533 def fget(self): 534 return self._alpha1 535 def fset(self, value): 536 self._alpha1 = self._AsFloat(value, 'alpha') 537 return locals() 538 539 ## Set methods 540 541 @DrawAfter 542 def SetXdata(self, data): 543 """ SetXdata(data) 544 545 Set the x coordinates of the points of the line. 546 547 """ 548 self._points[:,0] = handleInvalidValues(data) 549 550 @DrawAfter 551 def SetYdata(self, data): 552 """ SetYdata(data) 553 554 Set the y coordinates of the points of the line. 555 556 """ 557 self._points[:,1] = handleInvalidValues(data) 558 559 @DrawAfter 560 def SetZdata(self, data): 561 """ SetZdata(data) 562 563 Set the z coordinates of the points of the line. 564 565 """ 566 self._points[:,2] = handleInvalidValues(data) 567 568 @DrawAfter 569 def SetPoints(self, points): 570 """ SetPoints(points) 571 572 Set x,y (and optionally z) data. The given argument can be anything 573 that can be converted to a pointset. (From version 1.7 this method 574 also works with 2D pointsets.) 575 576 The data is copied, so changes to original data will not affect 577 the visualized points. If you do want this, use the points property. 578 579 """ 580 581 # Try make it a (copied) pointset (handle masked array) 582 if is_Pointset(points): 583 points = Pointset(handleInvalidValues(points.data)) 584 else: 585 points = Pointset(handleInvalidValues(points)) 586 587 # Add z dimension to points if not available 588 if points.ndim == 3: 589 pass 590 elif points.ndim == 2: 591 zz = 0.1*np.ones((len(points._data),1), dtype='float32') 592 points._data = np.concatenate((points._data, zz),1) 593 elif points.ndim == 1: 594 N = len(points._data) 595 xx = np.arange(N, dtype='float32').reshape(N, 1) 596 zz = 0.1*np.ones((N,1), dtype='float32') 597 points._data = np.concatenate((xx, points._data, zz), 1) 598 599 # Store 600 self._points = points 601 602 @property 603 def points(self): 604 """ Get a reference to the internal Pointset used to draw the line 605 object. Note that this pointset is always 3D. One can modify 606 this pointset in place, but note that a call to Draw() may be 607 required to update the screen. (New in version 1.7.) 608 """ 609 return self._points 610 611 612 ## Draw methods 613 614 def OnDrawFast(self): 615 self.OnDraw(True) 616 617 def OnDraw(self, fast=False): 618 619 # add z dimension to points if not available 620 pp = self._points 621 if pp.ndim == 2: 622 # a bit dirty this 623 tmp = pp._data, 0.1*np.ones((len(pp._data),1),dtype='float32') 624 pp._data = np.concatenate(tmp,1) 625 626 # can I draw this data? 627 if pp.ndim != 3: 628 raise Exception("Can only draw 3D data!") 629 630 # no need to draw if no points 631 if len(self._points) == 0: 632 return 633 634 # enable anti aliasing and blending 635 gl.glEnable(gl.GL_LINE_SMOOTH) 636 gl.glEnable(gl.GL_BLEND) 637 638 # lines 639 if self.lw and self.ls: 640 self._DrawLines() 641 642 # points 643 if self.mw and self.ms: 644 self._DrawPoints() 645 646 # clean up 647 #gl.glDisable(gl.GL_BLEND) 648 649 650 def _DrawLines(self): 651 652 # set stipple style 653 if not self.ls in lineStyles: 654 stipple = False 655 else: 656 stipple = lineStyles[self.ls] 657 # 658 if stipple and self.lw: 659 gl.glEnable(gl.GL_LINE_STIPPLE) 660 gl.glLineStipple(int(round(self.lw)), stipple) 661 else: 662 gl.glDisable(gl.GL_LINE_STIPPLE) 663 664 # init vertex array 665 gl.glEnableClientState(gl.GL_VERTEX_ARRAY) 666 gl.glVertexPointerf(self._points.data) 667 668 # linepieces drawn on top of other should draw just fine. See issue #95 669 gl.glDepthFunc(gl.GL_LEQUAL) 670 671 # init blending. Only use constant blendfactor when alpha<1 672 gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA) 673 if self._alpha1<1: 674 #if len(self._points) < 1000: 675 if getOpenGlCapable('1.4','transparant points and lines'): 676 gl.glBlendFunc(gl.GL_CONSTANT_ALPHA, gl.GL_ONE_MINUS_CONSTANT_ALPHA) 677 gl.glBlendColor(0.0,0.0,0.0, self._alpha1) 678 gl.glDisable(gl.GL_DEPTH_TEST) 679 680 # get color 681 clr = getColor( self.lc ) 682 683 if clr and self._alpha1>0: 684 685 # set width and color 686 gl.glLineWidth(self.lw) 687 gl.glColor3f(clr[0], clr[1], clr[2]) 688 689 # draw 690 method = gl.GL_LINE_STRIP 691 if self.ls == '+': 692 method = gl.GL_LINES 693 gl.glDrawArrays(method, 0, len(self._points)) 694 # flush! 695 gl.glFlush() 696 697 # clean up 698 gl.glDisable(gl.GL_LINE_STIPPLE) 699 gl.glLineStipple(int(round(self.lw)), int('1111111111111111',2)) 700 gl.glEnable(gl.GL_DEPTH_TEST) 701 gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA) 702 gl.glDisableClientState(gl.GL_VERTEX_ARRAY) 703 gl.glDepthFunc(gl.GL_LESS) 704 705 706 def _DrawPoints(self): 707 708 # get colors (use color from edge or face if not present) 709 clr1 = getColor(self.mc) 710 clr2 = getColor(self.mec) 711 712 # draw face or edge? 713 drawFace = bool(self.mc) # if not ms or mw we would not get here 714 drawEdge = self.mec and self.mew 715 if not drawFace and not drawEdge: 716 return 717 718 # get figure 719 f = self.GetFigure() 720 if not f: 721 return 722 723 # init blending. Only use constant blendfactor when alpha<1 724 gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA) 725 if self._alpha1<1: 726 if getOpenGlCapable('1.4','transparant points and lines'): 727 gl.glBlendFunc(gl.GL_CONSTANT_ALPHA, 728 gl.GL_ONE_MINUS_CONSTANT_ALPHA) 729 gl.glBlendColor(0.0,0.0,0.0, self._alpha1) 730 gl.glDisable(gl.GL_DEPTH_TEST) 731 732 # init vertex array 733 gl.glEnableClientState(gl.GL_VERTEX_ARRAY) 734 gl.glVertexPointerf(self._points.data) 735 736 # points drawn on top of points should draw (because we draw 737 # the face and edge seperately) 738 gl.glDepthFunc(gl.GL_LEQUAL) 739 740 # Enable alpha test, such that fragments with 0 alpha 741 # will not update the z-buffer. 742 gl.glEnable(gl.GL_ALPHA_TEST) 743 gl.glAlphaFunc(gl.GL_GREATER, 0.01) 744 745 if self.ms in ['o','.','s'] and not drawEdge: 746 # Use standard OpenGL points, faster and anti-aliased 747 # Pure filled points or squares always work. 748 749 # choose style 750 if self.ms == 's': 751 gl.glDisable(gl.GL_POINT_SMOOTH) 752 else: 753 gl.glEnable(gl.GL_POINT_SMOOTH) 754 755 # draw faces only 756 if drawFace: 757 gl.glColor3f(clr1[0],clr1[1],clr1[2]) 758 gl.glPointSize(self.mw) 759 gl.glDrawArrays(gl.GL_POINTS, 0, len(self._points)) 760 761 elif self.ms in ['o','.','s'] and drawFace and self.alpha==1: 762 # Use standard OpenGL points, faster and anti-aliased 763 # If alpha=1 and we have a filled marker, we can draw in two steps. 764 765 # choose style 766 if self.ms == 's': 767 gl.glDisable(gl.GL_POINT_SMOOTH) 768 else: 769 gl.glEnable(gl.GL_POINT_SMOOTH) 770 771 # draw edges 772 if drawEdge: 773 gl.glColor3f(clr2[0],clr2[1],clr2[2]) 774 gl.glPointSize(self.mw+self.mew*2) 775 gl.glDrawArrays(gl.GL_POINTS, 0, len(self._points)) 776 # draw faces 777 if drawFace: 778 gl.glColor3f(clr1[0],clr1[1],clr1[2]) 779 gl.glPointSize(self.mw) 780 gl.glDrawArrays(gl.GL_POINTS, 0, len(self._points)) 781 782 #elif self.alpha>0: 783 else: 784 # Use sprites 785 786 # get sprites 787 tmp = f._markerManager.GetSprites(self.ms, self.mw, self.mew) 788 pSize, sprite1, sprite2 = tmp 789 gl.glPointSize(pSize) 790 791 # draw points for the edges 792 if drawEdge: 793 sprite2.Enable() 794 gl.glColor3f(clr2[0],clr2[1],clr2[2]) 795 gl.glDrawArrays(gl.GL_POINTS, 0, len(self._points)) 796 # draw points for the faces 797 if drawFace: 798 sprite1.Enable() 799 gl.glColor3f(clr1[0],clr1[1],clr1[2]) 800 gl.glDrawArrays(gl.GL_POINTS, 0, len(self._points)) 801 802 # disable sprites 803 sprite1.Disable() # Could as well have used sprite2 804 805 # clean up 806 gl.glDisable(gl.GL_ALPHA_TEST) 807 gl.glEnable(gl.GL_DEPTH_TEST) 808 gl.glDisableClientState(gl.GL_VERTEX_ARRAY) 809 gl.glDepthFunc(gl.GL_LESS) 810 gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA) 811 812 813 def OnDrawShape(self, clr): 814 # Draw the shape of the line so we can detect mouse actions 815 816 # disable anti aliasing and blending 817 gl.glDisable(gl.GL_LINE_SMOOTH) 818 gl.glDisable(gl.GL_BLEND) 819 820 # no stippling, square points 821 gl.glDisable(gl.GL_LINE_STIPPLE) 822 gl.glDisable(gl.GL_POINT_SMOOTH) 823 824 # init vertex array 825 gl.glEnableClientState(gl.GL_VERTEX_ARRAY) 826 gl.glVertexPointerf(self._points.data) 827 828 # detect which parts to draw 829 drawLine, drawMarker = False, False 830 if self.lw and self.ls and getColor(self.lc): 831 drawLine = True 832 if self.mw and self.ms: 833 drawMarker = True 834 835 if drawLine: 836 # set width and color 837 gl.glLineWidth(self.lw) 838 gl.glColor3f(clr[0], clr[1], clr[2]) 839 # draw 840 gl.glDrawArrays(gl.GL_LINE_STRIP, 0, len(self._points)) 841 gl.glFlush() 842 843 if drawMarker: 844 w = self.mw 845 if self.mec: 846 w += self.mew 847 # set width and color 848 gl.glColor3f(clr[0],clr[1],clr[2]) 849 gl.glPointSize(w) 850 # draw 851 gl.glDrawArrays(gl.GL_POINTS, 0, len(self._points)) 852 gl.glFlush() 853 854 # clean up 855 gl.glDisableClientState(gl.GL_VERTEX_ARRAY) 856 857 858 def OnDestroy(self): 859 # clean up some memory 860 self._points.clear() 861 862 863# This is a new type of wobject called PolarLine which encapsulates 864# polar plot data. 865class PolarLine(Line): 866 """ PolarLine(parent, angle(radians), mag) 867 868 The Polarline class represents a set of points (locations) 869 in world coordinates. This class can draw lines between the points, 870 markers at the point coordinates, or both. 871 872 There are several linestyles that can be used: 873 * - a solid line 874 * : a dotted line 875 * -- a dashed line 876 * -. a dashdot line 877 * .- dito 878 * + draws a line between each pair of points (handy for visualizing 879 for example vectore fields) 880 If None, '' or False is given no line is drawn. 881 882 There are several marker styles that can be used: 883 * `+` a plus 884 * `x` a cross 885 * `s` a square 886 * `d` a diamond 887 * `^v<>` an up-, down-, left- or rightpointing triangle 888 * `*` or `p` a (pentagram star) 889 * `h` a hexagram 890 * `o` or `.` a point/circle 891 If None, '', or False is given, no marker is drawn. 892 893 Performance tip 894 --------------- 895 The s, o (and .) styles can be drawn using standard 896 OpenGL points if alpha is 1 or if no markeredge is drawn. 897 Otherwise point sprites are used, which can be slower 898 on some cards (like ATI, Nvidia performs quite ok with with 899 sprites) 900 901 """ 902 def __init__(self, parent, angs, mags): 903 self._angs = angs 904 self._mags = mags 905 x = mags * np.cos(angs) 906 y = mags * np.sin(angs) 907 z = np.zeros((np.size(x), 1)) 908 tmp = x, y, z 909 pp = Pointset(np.concatenate(tmp, 1)) 910 Line.__init__(self, parent, pp) 911 912 def TransformPolar(self, radialRange, angRefPos, sense): 913 offsetMags = self._mags - radialRange.min 914 rangeMags = radialRange.range 915 offsetMags[offsetMags > rangeMags] = rangeMags 916 tmpang = angRefPos + sense * self._angs 917 x = offsetMags * np.cos(tmpang) 918 y = offsetMags * np.sin(tmpang) 919 z = np.zeros((np.size(x), 1)) + 0.2 920 x[offsetMags < 0] = 0 921 y[offsetMags < 0] = 0 922 tmp = x, y, z 923 self._points = Pointset(np.concatenate(tmp, 1)) 924 925 def _GetPolarLimits(self): 926 if not self._points: 927 return None 928 else: 929 return Range(self._angs.min(), self._angs.max()), \ 930 Range(self._mags.min(), self._mags.max()) 931 932 933 934def handleInvalidValues(values): 935 """ handleInvalidValues(values) 936 937 Modifies any invalid values (NaN, Inf, -Inf) to Inf, 938 and turn masked values of masked arrays to Inf. 939 Returns a copy if correction if needed. 940 """ 941 if isinstance(values, np.ma.MaskedArray): 942 values = values.filled(np.inf) 943 _inplace = True # values is already a copy, so we can modify it 944 else: 945 _inplace = False 946 if not isinstance(values, np.ndarray): 947 values = np.array(values) 948 949 invalid = ~np.isfinite(values) 950 # Determine if we should make a copy 951 if invalid.sum() and not _inplace: 952 values = values.copy() 953 # Convert values and return 954 try: 955 values[invalid] = np.inf 956 except OverflowError: 957 pass # Cannot do this with integer types 958 return values 959