I have created a scatter plot application. I create the scatter plot
in memory and then I copy it to the screen as the background. The
background is only redrawn when the frame is re-sized. Then I draw
smaller less computationally intensive stuff on top of the
background. This all works nice and flicker free. My problem is that
I use DrawCricle to create the background scatter plot. This is much
slower than say DrawPointList. I am running this on Windows XP using
wxPython 2.8.11.0
I guess I could utilize the fact that many of the points are
overlapping or on top of each other and only draw a new point if it is
a certain distance from other points. I would appreciate any ideas on
how to increase the speed (example code is listed below #1).
I tried matplotlib for a scatter plot, and it appears faster (example
code listed below #2).
# Code Example 1
import wx
import random
import time
class MyGraph(wx.Window):
def __init__(self,parent,ID):
wx.Window.__init__(self,parent)
self.SetBackgroundColour('White')
self.bitmap=wx.EmptyBitmap(1,1,-1)
self.number = 150000
self.bordergap = 50
self.drawtime = 0.0
self.x = 150
self.y = 150
self.distancelimit = 100
self.dragpoint = False
self.fastdraw = False
self.initbuffer = False
self.initbitmap = False
self.InitBuffer()
self.Bind(wx.EVT_PAINT, self.on_paint)
self.Bind(wx.EVT_SIZE, self.on_size)
self.Bind(wx.EVT_IDLE, self.on_idle)
self.Bind(wx.EVT_MOTION, self.OnMotion)
self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
self.Bind(wx.EVT_LEAVE_WINDOW,self.OnLeaveWindow)
def OnLeaveWindow(self,event):
if self.dragpoint:
self.initbuffer = True
self.dragpoint = False
def OnEraseBackground(self,event):
pass # pass on this event to reduce flickering
def InitBuffer(self):
size = self.GetClientSize()
self.buffer = wx.EmptyBitmap(size.width,size.height)
dc = wx.BufferedDC(wx.ClientDC(self), self.buffer)
dc.SetBackground(wx.Brush(self.GetBackgroundColour()))
dc.Clear()
self.DrawGraph(dc)
if self.initbitmap == False:
self.initbuffer = False
def OnLeftUp(self,event):
if self.dragpoint:
self.initbuffer = True
self.dragpoint = False
def OnLeftDown(self,event):
newpos = event.GetPositionTuple()
x = newpos[0]
y = newpos[1]
distance = (self.x-x)*(self.x-x) + (self.y-y)*(self.y-y)
if distance < self.distancelimit:
self.dragpoint = True
self.initbuffer = True
def OnMotion(self,event):
newpos = event.GetPositionTuple()
x = newpos[0]
y = newpos[1]
distance = (self.x-x)*(self.x-x) + (self.y-y)*(self.y-y)
if self.dragpoint:
self.x = newpos[0]
self.y = newpos[1]
self.initbuffer = True
if distance < self.distancelimit:
self.SetCursor(wx.StockCursor(wx.CURSOR_HAND))
else:
self.SetCursor(wx.StockCursor(wx.CURSOR_ARROW))
def DrawGraph(self,dc):
dc.DrawBitmap(self.bitmap,0,0) # draw prepared background
bitmap
if self.dragpoint:
dc.SetBrush(wx.Brush('Black'))
else:
dc.SetBrush(wx.Brush('Green'))
dc.SetPen(wx.Pen('Red', 12, wx.SOLID))
dc.DrawCircle(self.x,self.y,15)
self.Refresh()
def on_idle(self,event):
if self.initbuffer:
if self.initbitmap:
self.make_bitmap()
self.InitBuffer()
self.Refresh(False)
def on_paint(self,event):
dc = wx.BufferedPaintDC(self,self.buffer)
def on_size(self,event):
self.initbuffer = True
self.initbitmap = True
self.fastdraw = True
def make_bitmap(self):
# make background bitmap (slow) that only changes on a resize
w,h = self.GetClientSize()
self.bitmap=wx.EmptyBitmap(w,h,-1)
dc=wx.MemoryDC()
dc.SelectObject(self.bitmap)
dc.Clear()
dc.SetPen(wx.Pen('Blue', 1, wx.SOLID))
dc.SetBrush(wx.Brush('Yellow'))
if self.fastdraw == False:
randdata = []
if (w > 2*self.bordergap) and (h > 2*self.bordergap):
for i in range(self.number):
x = random.randint(self.bordergap,w-
self.bordergap)
y = x + random.randint(-
self.bordergap,self.bordergap)
if y < 0:
y = 0
if y > h-self.bordergap:
y = h-self.bordergap
randdata.append( (x,y) )
t1 = time.time()
[dc.DrawCircle(x,y,3) for (x,y) in randdata]
#dc.DrawPointList(randdata) # much faster
self.initbitmap = False
self.drawtime = time.time() - t1
else:
dc.DrawText('Please Wait - creating new background',20,20)
self.fastdraw = False
dc.SelectObject( wx.NullBitmap)
class MyFrame(wx.Frame):
def __init__(self,parent):
wx.Frame.__init__(self,parent,-1,'Drag Red Circle
Around',size=(400,500))
self.plot = MyGraph(self,-1)
self.plot.Bind(wx.EVT_MOTION,self.OnMotion)
self.statusbar = self.CreateStatusBar()
self.statusbar.SetFieldsCount(4)
self.statusbar.SetStatusWidths([-1,-1,-2,-1])
self.statusbar.SetStatusText('(?,?)',3)
self.Bind(wx.EVT_IDLE,self.OnIdle)
def OnIdle(self,event):
self.statusbar.SetStatusText(str(self.GetSize()),0)
self.statusbar.SetStatusText(str(self.plot.GetClientSize()),1)
self.statusbar.SetStatusText('Resize time: %6.3f
sec.'%self.plot.drawtime,2)
event.Skip()
def OnMotion(self,event):
newpos = event.GetPositionTuple()
self.statusbar.SetStatusText(str(newpos),3)
event.Skip()
app = wx.PySimpleApp()
frm = MyFrame(None)
frm.Show(True)
app.MainLoop()
# Code Example 2
import wx
import time
import numpy
from matplotlib.figure import Figure
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as
FigureCanvas
N = 150000
x = numpy.random.randn(N)
y = numpy.random.randn(N)
class MyFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, title='Scatter Plot
Example')
t = time.time()
figure = Figure()
axes = figure.add_subplot(1,1,1)
axes.plot(x, y, 'o', ms=2, color='blue')
canvas = FigureCanvas(self, wx.ID_ANY, figure)
print time.time()-t
app = wx.PySimpleApp()
frame = MyFrame()
frame.Show(True)
app.MainLoop()