Jan said:
Hello all,
I wanted to plot some statistics, so I wrote a simple wxPython class
to do it. Then I realized that I would like to draw bar graphs, so I
added that too.
Since I'm a complete Python newbie, I haven't done much of it the
"Python way", I suspect. So, I'm wondering if someone would like to show
me some of the tricks I should have used.
You can find the code at:
http://user.it.uu.se/~jada3673/drawtest.py
It's not finished[*], but it is usable. Oh, you can do whatever you
want with it. Since I had never touched wxPython before working with
this, I used some doodle application as a starting point.
Btw... Has anyone already written statistics classes for wxPython? If
so, where can I find them? If nothing similar exists, I'll probably add
support for loading data from a file, and a few other features. Feel
free to make requests or enhancements.
[*] There are some size/offset bugs which I am aware of, but they are
easily fixed. I just haven't got around to it yet.
If you ever want to add an extra dimension, then using OpenGL with
wxWindows is a breeze. See attached file for a 3d pie char..
Will McGugan
--
http://www.willmcgugan.com
"".join({'*':'@','^':'.'}.get(c,0) or chr(97+(ord(c)-84)%26) for c in
"jvyy*jvyyzpthtna^pbz")
# Pie Chart Window
# 2005 Will McGugan (
[email protected])
import wx
from wx import glcanvas
from OpenGL.GL import *
import math
import time
sin = math.sin
cos = math.cos
PI = math.pi
def DegToRad(deg):
return deg*PI/180.
class PieSegment(object):
def __init__(self, radius, angle1, angle2, colour, depth):
self.display_list = glGenLists(1)
glNewList(self.display_list, GL_COMPILE)
self.angle1 = angle1
self.angle2 = angle2
self.explode_angle = DegToRad( (angle2 + angle1) / 2.)
DrawPieSegment(radius, angle1, angle2, colour, depth)
glEndList()
def __del__(self):
glDeleteLists(self.display_list, 1)
def Draw(self, angle1, angle2, explode):
glPushMatrix()
glRotate(angle1, 1.0, 0.0, 0.0);
glRotate(angle2, 0.0, 0.0, 1.0);
glTranslatef( sin(self.explode_angle)*explode, cos(self.explode_angle)*explode, 0. )
glCallList(self.display_list)
glPopMatrix()
def DrawPieSegment(radius, angle1, angle2, colour, depth):
angle1 = DegToRad(angle1)
angle2 = DegToRad(angle2)
# Number of divisions in 360 degrees
RES = 100.
fan2D = []
step_degree = ((2*PI)/RES)
step_count = int( (angle2 - angle1) / step_degree ) + 1
step_degree = ( angle2 - angle1 ) / float(step_count)
# Precalculate the trig
sina1 = sin(angle1)
cosa1 = cos(angle1)
sina2 = sin(angle2)
cosa2 = cos(angle2)
# Calculate the points in an arc
for p in xrange(step_count+1):
a = angle1 + p * step_degree
x = sin(a)
y = cos(a)
fan2D.append( (x,y) )
z1 = +depth/2.
z2 = -depth/2.
glMaterial(GL_FRONT, GL_DIFFUSE, [colour[0], colour[1], colour[2], 1.0])
# Create a fan for the top and bottom of the pie segment
glNormal(0, 0, +1.)
glBegin(GL_TRIANGLE_FAN)
glVertex(0, 0, z1)
for x, y in fan2D:
glVertex(x*radius, y*radius, z1)
glEnd()
glNormal(0, 0, -1.)
glBegin(GL_TRIANGLE_FAN)
glVertex(0, 0, z2)
for x, y in fan2D:
glVertex(x*radius, y*radius, z2)
glEnd()
# A strip for the curved edge
glBegin(GL_TRIANGLE_STRIP)
for x, y in fan2D:
glNormal(x, y, 0.)
xr = x * radius
yr = y * radius
glVertex(xr, yr, z1)
glVertex(xr, yr, z2)
glEnd()
n1 = -cosa1, sina1, 0.
n2 = cosa2, -sina2, 0.
x1, y1 = sina1 * radius, cosa1 * radius
x2, y2 = sina2 * radius, cosa2 * radius
# Two quads for the radius edges
glNormal(n1)
glBegin(GL_QUADS)
glVertex(0, 0, z1)
glVertex(x1, y1, z1)
glVertex(x1, y1, z2)
glVertex(0, 0, z2)
glEnd()
glNormal(n2)
glBegin(GL_QUADS)
glVertex(0, 0, z1)
glVertex(x2, y2, z1)
glVertex(x2, y2, z2)
glVertex(0, 0, z2)
glEnd()
class PieChartWindow(glcanvas.GLCanvas):
def __init__(self, parent):
attriblist = None
glcanvas.GLCanvas.__init__(self, parent, -1, wx.DefaultPosition, wx.DefaultSize, 0, "GLCanvas", attriblist, wx.NullPalette)
self.init = False
self.segments = []
self.animating = False
self.angle = 0.
self.explode = 0.
self.downx = 0
self.rot = 0.
self.captured_mouse = False
self.timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.OnTimer)
self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftButtonDown)
self.Bind(wx.EVT_LEFT_UP, self.OnLeftButtonUp)
self.Bind(wx.EVT_MOTION, self.OnMouseMove)
self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
self.Bind(wx.EVT_SIZE, self.OnSize)
self.Bind(wx.EVT_PAINT, self.OnPaint)
def Animate(self):
self.animating = True
self.start_animate = time.clock()
self.timer.Start(1000/20)
def OnTimer(self, event):
if self.IsShown():
self.Refresh()
if not self.animating:
self.timer.Stop();
def SetSegments(self, segments, angle=0., radius=150., depth=50.):
self.segments = []
First = True
if len(segments):
angle = -(segments[0][0]/2. *360./100.)
for seg in segments:
seg_percent, colour = seg
if seg_percent == 0:
continue
if seg_percent < .2:
angle = angle + (seg_percent*360./100.)
continue
if First:
angle = -(seg_percent/2. * 360./100.)
First = False
next_angle = angle + (seg_percent*360./100.)
self.segments.append(PieSegment(radius, angle, next_angle, colour, depth))
angle=next_angle
def PerspectiveProjection(self):
w, h = 300., 300.
a = 30.
tana = math.tan(DegToRad(a/2.))
glMatrixMode(GL_PROJECTION);
v = (w/2) / tana
near = v / 10.
vw = tana * near
vh = tana * near
glFrustum(-vw, vw, -vh, vh, near, v*3.0);
glMatrixMode(GL_MODELVIEW);
glTranslatef(0.0, 0.0, -v*1.2);
glLight(GL_LIGHT0, GL_POSITION, [w/2, h/2, v*1.2, 0.0]);
def InitGL(self):
self.PerspectiveProjection()
glMaterial(GL_FRONT, GL_AMBIENT, [0.3, 0.3, 0.3, 1.0])
glMaterial(GL_FRONT, GL_DIFFUSE, [0.9, 0.9, 0.9, 1.0])
glMaterial(GL_FRONT, GL_SPECULAR, [1.0, 1.0, 1.0, 1.0])
glMaterial(GL_FRONT, GL_SHININESS, 70.0)
glLight(GL_LIGHT0, GL_AMBIENT, [0.25, 0.25, 0.25, 1.0])
glLight(GL_LIGHT0, GL_DIFFUSE, [1.0, 1.0, 1.0, 1.0])
glLight(GL_LIGHT0, GL_SPECULAR, [.1, .1, .1, 1.0])
glLightModel(GL_LIGHT_MODEL_AMBIENT, [0.2, 0.2, 0.2, 1.0])
glEnable(GL_LIGHTING)
glEnable(GL_LIGHT0)
glDepthFunc(GL_LESS)
glEnable(GL_DEPTH_TEST)
glClearColor(1,1,1,1)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glMatrixMode(GL_MODELVIEW);
def OnLeftButtonDown(self, event):
self.downx = event.GetPosition()[0]
self.captured_mouse = True
self.CaptureMouse()
def OnLeftButtonUp(self, event):
if self.captured_mouse and self.HasCapture():
self.ReleaseMouse()
def OnMouseMove(self, event):
if event.Dragging() and event.LeftIsDown():
mx, my = event.GetPosition()
rot_delta = self.downx - mx
self.rot+= rot_delta
self.downx = mx
self.Refresh()
def OnEraseBackground(self, event):
pass
def OnSize(self, event):
size = self.GetClientSize()
if self.GetContext():
self.SetCurrent()
glViewport(0, 0, size.width, size.height)
self.Refresh()
event.Skip()
def OnPaint(self, event):
dc = wx.PaintDC(self)
self.SetCurrent()
if not self.init:
self.InitGL()
self.init = True
self.OnDraw()
def OnDraw(self):
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
if self.animating:
t = time.clock() - self.start_animate
if t < 1.:
self.angle = 0.
self.explode = 0.
elif t < 2.:
ts = t - 1.
self.angle = -50. * ts
elif t < 3.:
ts = t - 2.
self.angle = -50.
self.explode = 15. * ts
else:
self.animating = False
self.angle = -50.
self.explode = 15.
for segment in self.segments:
segment.Draw(self.angle, self.rot, self.explode)
self.SwapBuffers()
if __name__ == "__main__":
class TestFrame(wx.Frame):
def __init__(self):
super(TestFrame, self).__init__(None, -1, "Pie Chart Test", style=wx.DEFAULT_FRAME_STYLE)
self.SetClientSize(wx.Size(300,300))
pie_chart = PieChartWindow(self)
# List of tuples ( <Percentage of Pie>, ( <Red>, <Green>, <Blue> ) )
seg = [ ( 20, (1., 0.1, 0.1) ),
( 25, (0.05, .9, .1) ),
( 10, (.2, 0.1, 1.) ),
( 30, (.8, .9, .1) ) ]
pie_chart.SetSegments( seg )
pie_chart.Animate()
app = wx.App()
frame = TestFrame()
frame.Show(True)
app.MainLoop()