import wx import math import random class RadarGraph(wx.Window): """ A simple radar graph that plots a collection of values in the range of 0-100 onto a polar coordinate system designed to easily show outliers, etc. You might use this kind of graph to monitor some sort of resource allocation metrics, and a quick glance at the graph can tell you when conditions are good (within some accepted tolerance level) or approaching critical levels (total resource consumption). """ def __init__(self, parent, title, labels): wx.Window.__init__(self, parent) self.title = title self.labels = labels self.data = [0.0] * len(labels) self.titleFont = wx.Font(14, wx.SWISS, wx.NORMAL, wx.BOLD) self.labelFont = wx.Font(10, wx.SWISS, wx.NORMAL, wx.NORMAL) self.InitBuffer() self.Bind(wx.EVT_SIZE, self.OnSize) self.Bind(wx.EVT_PAINT, self.OnPaint) def OnSize(self, evt): # When the window size changes we need a new buffer. self.InitBuffer() def OnPaint(self, evt): # This automatically Blits self.buffer to a wx.PaintDC when # the dc is destroyed, and so nothing else needs done. dc = wx.BufferedPaintDC(self, self.buffer) def InitBuffer(self): # Create the buffer bitmap to be the same size as the window, # then draw our graph to it. Since we use wx.BufferedDC # whatever is drawn to the buffer is also drawn to the window. w, h = self.GetClientSize() self.buffer = wx.EmptyBitmap(w, h) dc = wx.BufferedDC(wx.ClientDC(self), self.buffer) self.DrawGraph(dc) def GetData(self): return self.data def SetData(self, newData): assert len(newData) == len(self.data) self.data = newData[:] # The data has changed, so update the buffer and the window dc = wx.BufferedDC(wx.ClientDC(self), self.buffer) self.DrawGraph(dc) def PolarToCartesian(self, radius, angle, cx, cy): x = radius * math.cos(math.radians(angle)) y = radius * math.sin(math.radians(angle)) return (cx+x, cy-y) def DrawGraph(self, dc): spacer = 10 scaledmax = 150.0 dc.SetBackground(wx.Brush(self.GetBackgroundColour())) dc.Clear() dw, dh = dc.GetSize() # Find out where to draw the title and do it dc.SetFont(self.titleFont) tw, th = dc.GetTextExtent(self.title) dc.DrawText(self.title, (dw-tw)/2, spacer) # find the center of the space below the title th = th + 2*spacer cx = dw/2 cy = (dh-th)/2 + th # calculate a scale factor to use for drawing the graph based # on the minimum available width or height mindim = min(cx, (dh-th)/2) scale = mindim/scaledmax # draw the graph axis and "bulls-eye" with rings at scaled 25, # 50, 75 and 100 positions dc.SetPen(wx.Pen("black", 1)) dc.SetBrush(wx.TRANSPARENT_BRUSH) dc.DrawCircle(cx,cy, 25*scale) dc.DrawCircle(cx,cy, 50*scale) dc.DrawCircle(cx,cy, 75*scale) dc.DrawCircle(cx,cy, 100*scale) dc.SetPen(wx.Pen("black", 2)) dc.DrawLine(cx-110*scale, cy, cx+110*scale, cy) dc.DrawLine(cx, cy-110*scale, cx, cy+110*scale) # Now find the coordinates for each data point, draw the # labels, and find the max data point dc.SetFont(self.labelFont) maxval = 0 angle = 0 polypoints = [] for i, label in enumerate(self.labels): val = self.data[i] point = self.PolarToCartesian(val*scale, angle, cx, cy) polypoints.append(point) x, y = self.PolarToCartesian(125*scale, angle, cx,cy) dc.DrawText(label, x, y) if val > maxval: maxval = val angle = angle + 360/len(self.labels) # Set the brush color based on the max value (green is good, # red is bad) c = "forest green" if maxval > 70: c = "yellow" if maxval > 95: c = "red" # Finally, draw the plot data as a filled polygon dc.SetBrush(wx.Brush(c)) dc.SetPen(wx.Pen("navy", 3)) dc.DrawPolygon(polypoints) class TestFrame(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, title="Double Buffered Drawing", size=(480,480)) self.plot = RadarGraph(self, "Sample 'Radar' Plot", ["A", "B", "C", "D", "E", "F", "G", "H"]) # Set some random initial data values data = [] for d in self.plot.GetData(): data.append(random.randint(0, 75)) self.plot.SetData(data) # Create a timer to update the data values self.Bind(wx.EVT_TIMER, self.OnTimeout) self.timer = wx.Timer(self) self.timer.Start(500) def OnTimeout(self, evt): # simulate the positive or negative growth of each data value data = [] for d in self.plot.GetData(): val = d + random.uniform(-5, 5) if val < 0: val = 0 if val > 110: val = 110 data.append(val) self.plot.SetData(data) app = wx.PySimpleApp() frm = TestFrame() frm.Show() app.MainLoop()