Creating Pie Chart from Python

T

Thierry Lam

Let's say I have the following data:

500 objects:
-100 are red
-300 are blue
-the rest are green

Is there some python package which can represent the above information
in a pie chart?

Thanks
Thierry
 
W

Will McGugan

Thierry said:
Let's say I have the following data:

500 objects:
-100 are red
-300 are blue
-the rest are green

Is there some python package which can represent the above information
in a pie chart?

I wrote a wxPython control to render pretty 3D pie charts (see attached
piechartwindow.py). It can also be used to pre-generate images such as
this..

http://www.foodfileonline.com/static/piecharts/pie01009.jpg

Code is public domain.


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.)
if colour is not None:
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 DoPaint(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)

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

Fredrik Lundh

Thierry said:
Let's say I have the following data:

500 objects:
-100 are red
-300 are blue
-the rest are green

Is there some python package which can represent the above information
in a pie chart?

on a screen? in a web browser? on a printer?

</F>
 
F

Fredrik Lundh

Thierry said:
In a web browser, having a pie chart in some image format will be great.

here's a variation of jepler's tkinter example, using aggdraw to do the
drawing and PIL to generate the image. tweak as necessary.

# http://effbot.org/zone/draw-agg.htm
from aggdraw import *

# http://www.pythonware.com/products/pil/index.htm
import Image

def drawchart(size, data, range):

draw = Draw("RGB", size, "white")

bbox = 10, 10, size[0]-10, size[1]-10

for lo, hi, ink in data:
lo = 360.0 * lo / range
hi = 360.0 * hi / range
draw.pieslice(bbox, lo, hi, Brush(ink))

return Image.fromstring(draw.mode, draw.size, draw.tostring())


data = (
(0, 100, "red"),
(100, 400, "blue"),
(400, 500, "green")
)

im = drawchart((250, 250), data, 500)

im.save("chart.png")

</F>
 
M

Markus Weihs

Hi!

I tried your script and got the following error:

Traceback (most recent call last):
File "pie_chart.py", line 302, in OnPaint
self.OnDraw()
File "pie_chart.py", line 336, in OnDraw
segment.Draw(self.angle, self.rot, self.explode)
File "pie_chart.py", line 46, in Draw
glPopMatrix()
OpenGL.GL.GLerror: [Errno 1281] invalid value

What am I doing wrong?


Regards, Markus
 
W

Will McGugan

Markus said:
Hi!

I tried your script and got the following error:

Traceback (most recent call last):
File "pie_chart.py", line 302, in OnPaint
self.OnDraw()
File "pie_chart.py", line 336, in OnDraw
segment.Draw(self.angle, self.rot, self.explode)
File "pie_chart.py", line 46, in Draw
glPopMatrix()
OpenGL.GL.GLerror: [Errno 1281] invalid value

What am I doing wrong?

I'm not sure. Thats a strange place for it to fail. Do you have the
latest version of pyOpenGL / wxPython?

Its only been tested on Windows, but it just uses basic OpenGL so there
is not that much to go wrong..

Will McGugan
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top