DrawCircle is slow for many points

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()

It is not really clear what you need. Namely, what is wrong with using
DrawPointList?Do you need to have circles rather than points?You might
try wx.lib.plot, it should be faster than maxplotlib, and support
plotting with marker. However I doubt it can be faster than
DrawCircles.
The other alternatives you propose (plot circles only if they do not
overlap) it seems to me pretty equal to reduce the number of points
you generate at the beginning. You might think of making this number
dependent to the size of the frame (i.e. work on a "density"
function). Otherwise you have a quite hard computational job for
calculating distance between points (is not impossible, but it's a
numpy topic, rather than wx).

···

On Nov 21, 10:22 pm, KHR <khras...@gmail.com> wrote:

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\(&#39;White&#39;\)
    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 &lt; 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 &lt; 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\(&#39;Blue&#39;, 1, wx\.SOLID\)\)
    dc\.SetBrush\(wx\.Brush\(&#39;Yellow&#39;\)\)

    if self\.fastdraw == False:
        randdata = \[\]
        if \(w &gt; 2\*self\.bordergap\) and \(h &gt; 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\(&#39;Please Wait \- creating new background&#39;,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\(&#39;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()

I would prefer not to use points.
The circles (or other markers) are more visually appealing.

Thanks for the hint to look into wx.lib.plot
I found the following excerpt from wx.lib.plot that is quite useful:

···

-----------------
Did a lot of work here to speed markers up. Only a factor of 4
improvement though. Lines are much faster than markers, especially
filled markers. Stay away from circles and triangles unless you
only have a few thousand points.

Times for 25,000 points
Line - 0.078 sec
Markers
Square - 0.22 sec
dot - 0.10
circle - 0.87
cross,plus - 0.28
triangle, triangle_down - 0.90
-----------------

wx.lib.plot uses DrawEllipseList to draw circles.
I found that DrawEllipseList is slower than DrawCircles, except when
the size is 3.
On my computer it takes 0.3 seconds to draw 150,000 circles of size 3
using DrawEllipseList.
Any other size (except 1) takes about 6.5 seconds.

As a comparison, it takes about 3.5 seconds to draw the 150,000
circles when I use DrawCircle.

On Nov 22, 2:00 am, tinauser <tinau...@libero.it> wrote:

On Nov 21, 10:22 pm, KHR <khras...@gmail.com> wrote:

> 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()

It is not really clear what you need. Namely, what is wrong with using
DrawPointList?Do you need to have circles rather than points?You might
try wx.lib.plot, it should be faster than maxplotlib, and support
plotting with marker. However I doubt it can be faster than
DrawCircles.
The other alternatives you propose (plot circles only if they do not
overlap) it seems to me pretty equal to reduce the number of points
you generate at the beginning. You might think of making this number
dependent to the size of the frame (i.e. work on a "density"
function). Otherwise you have a quite hard computational job for
calculating distance between points (is not impossible, but it's a
numpy topic, rather than wx).