]> git.saurik.com Git - wxWidgets.git/blob - wxPython/samples/wxPIA_book/Chapter-12/radargraph.py
fixed wxVsnprintf() to write as much as it can if the output buffer is too short
[wxWidgets.git] / wxPython / samples / wxPIA_book / Chapter-12 / radargraph.py
1 import wx
2 import math
3 import random
4
5 class RadarGraph(wx.Window):
6 """
7 A simple radar graph that plots a collection of values in the
8 range of 0-100 onto a polar coordinate system designed to easily
9 show outliers, etc. You might use this kind of graph to monitor
10 some sort of resource allocation metrics, and a quick glance at
11 the graph can tell you when conditions are good (within some
12 accepted tolerance level) or approaching critical levels (total
13 resource consumption).
14 """
15 def __init__(self, parent, title, labels):
16 wx.Window.__init__(self, parent)
17 self.title = title
18 self.labels = labels
19 self.data = [0.0] * len(labels)
20 self.titleFont = wx.Font(14, wx.SWISS, wx.NORMAL, wx.BOLD)
21 self.labelFont = wx.Font(10, wx.SWISS, wx.NORMAL, wx.NORMAL)
22
23 self.InitBuffer()
24
25 self.Bind(wx.EVT_SIZE, self.OnSize)
26 self.Bind(wx.EVT_PAINT, self.OnPaint)
27
28
29 def OnSize(self, evt):
30 # When the window size changes we need a new buffer.
31 self.InitBuffer()
32
33
34 def OnPaint(self, evt):
35 # This automatically Blits self.buffer to a wx.PaintDC when
36 # the dc is destroyed, and so nothing else needs done.
37 dc = wx.BufferedPaintDC(self, self.buffer)
38
39
40 def InitBuffer(self):
41 # Create the buffer bitmap to be the same size as the window,
42 # then draw our graph to it. Since we use wx.BufferedDC
43 # whatever is drawn to the buffer is also drawn to the window.
44 w, h = self.GetClientSize()
45 self.buffer = wx.EmptyBitmap(w, h)
46 dc = wx.BufferedDC(wx.ClientDC(self), self.buffer)
47 self.DrawGraph(dc)
48
49
50 def GetData(self):
51 return self.data
52
53 def SetData(self, newData):
54 assert len(newData) == len(self.data)
55 self.data = newData[:]
56
57 # The data has changed, so update the buffer and the window
58 dc = wx.BufferedDC(wx.ClientDC(self), self.buffer)
59 self.DrawGraph(dc)
60
61
62 def PolarToCartesian(self, radius, angle, cx, cy):
63 x = radius * math.cos(math.radians(angle))
64 y = radius * math.sin(math.radians(angle))
65 return (cx+x, cy-y)
66
67
68 def DrawGraph(self, dc):
69 spacer = 10
70 scaledmax = 150.0
71
72 dc.SetBackground(wx.Brush(self.GetBackgroundColour()))
73 dc.Clear()
74 dw, dh = dc.GetSize()
75
76 # Find out where to draw the title and do it
77 dc.SetFont(self.titleFont)
78 tw, th = dc.GetTextExtent(self.title)
79 dc.DrawText(self.title, (dw-tw)/2, spacer)
80
81 # find the center of the space below the title
82 th = th + 2*spacer
83 cx = dw/2
84 cy = (dh-th)/2 + th
85
86 # calculate a scale factor to use for drawing the graph based
87 # on the minimum available width or height
88 mindim = min(cx, (dh-th)/2)
89 scale = mindim/scaledmax
90
91 # draw the graph axis and "bulls-eye" with rings at scaled 25,
92 # 50, 75 and 100 positions
93 dc.SetPen(wx.Pen("black", 1))
94 dc.SetBrush(wx.TRANSPARENT_BRUSH)
95 dc.DrawCircle(cx,cy, 25*scale)
96 dc.DrawCircle(cx,cy, 50*scale)
97 dc.DrawCircle(cx,cy, 75*scale)
98 dc.DrawCircle(cx,cy, 100*scale)
99
100 dc.SetPen(wx.Pen("black", 2))
101 dc.DrawLine(cx-110*scale, cy, cx+110*scale, cy)
102 dc.DrawLine(cx, cy-110*scale, cx, cy+110*scale)
103
104 # Now find the coordinates for each data point, draw the
105 # labels, and find the max data point
106 dc.SetFont(self.labelFont)
107 maxval = 0
108 angle = 0
109 polypoints = []
110 for i, label in enumerate(self.labels):
111 val = self.data[i]
112 point = self.PolarToCartesian(val*scale, angle, cx, cy)
113 polypoints.append(point)
114 x, y = self.PolarToCartesian(125*scale, angle, cx,cy)
115 dc.DrawText(label, x, y)
116 if val > maxval:
117 maxval = val
118 angle = angle + 360/len(self.labels)
119
120 # Set the brush color based on the max value (green is good,
121 # red is bad)
122 c = "forest green"
123 if maxval > 70:
124 c = "yellow"
125 if maxval > 95:
126 c = "red"
127
128 # Finally, draw the plot data as a filled polygon
129 dc.SetBrush(wx.Brush(c))
130 dc.SetPen(wx.Pen("navy", 3))
131 dc.DrawPolygon(polypoints)
132
133
134
135 class TestFrame(wx.Frame):
136 def __init__(self):
137 wx.Frame.__init__(self, None, title="Double Buffered Drawing",
138 size=(480,480))
139 self.plot = RadarGraph(self, "Sample 'Radar' Plot",
140 ["A", "B", "C", "D", "E", "F", "G", "H"])
141
142 # Set some random initial data values
143 data = []
144 for d in self.plot.GetData():
145 data.append(random.randint(0, 75))
146 self.plot.SetData(data)
147
148 # Create a timer to update the data values
149 self.Bind(wx.EVT_TIMER, self.OnTimeout)
150 self.timer = wx.Timer(self)
151 self.timer.Start(500)
152
153
154 def OnTimeout(self, evt):
155 # simulate the positive or negative growth of each data value
156 data = []
157 for d in self.plot.GetData():
158 val = d + random.uniform(-5, 5)
159 if val < 0:
160 val = 0
161 if val > 110:
162 val = 110
163 data.append(val)
164 self.plot.SetData(data)
165
166
167 app = wx.PySimpleApp()
168 frm = TestFrame()
169 frm.Show()
170 app.MainLoop()