[reportlab-users] Wrapped Labels Pie Chart
Marc Stober
reportlab-users@reportlab.com
Wed, 23 Apr 2003 13:24:07 -0400
This message is in MIME format. Since your mail reader does not understand
this format, some or all of this message may not be legible.
------_=_NextPart_000_01C309BD.251FB610
Content-Type: multipart/alternative;
boundary="----_=_NextPart_001_01C309BD.251FB610"
------_=_NextPart_001_01C309BD.251FB610
Content-Type: text/plain
Daniel and Troy,
Here is a pie chart with wrapped labels (using some internals of the
Paragraph class as a wrapping algorithm). Hope you find it useful. Feel free
to contact me with any questions you have or bugs you find.
Thanks,
Marc Stober
mstober@dalbar.com
> -----Original Message-----
> From: Daniel McQuillen [mailto:daniel@bluepattern.com]
> Sent: Tuesday, April 22, 2003 3:27 PM
> To: Troy Sorzano; Marc Stober
> Subject: Re: [reportlab-users] RE: reportlab-users digest,
> Vol 1 #293 -
> 2 msgs
>
>
> Troy Sorzano wrote:
>
> >>>1. I have made a *pie* chart class, based on the standard
> ReportLab pie chart class but with some added wrapping of the
> labels. If you are interested I will send you a copy.<<
> >>>
> >>>
> >
> >
> >Hi Daniel,
> >
> >Could you send me your pie chart class. I am looking for
> the ability to wrap labels.
> >
> >Thanks,
> >
> >Troy Sorzano
> >Info Packaging
> >
------_=_NextPart_001_01C309BD.251FB610
Content-Type: text/html
Content-Transfer-Encoding: quoted-printable
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
<HTML>
<HEAD>
<META HTTP-EQUIV=3D"Content-Type" CONTENT=3D"text/html; =
charset=3DUS-ASCII">
<META NAME=3D"Generator" CONTENT=3D"MS Exchange Server version =
5.5.2653.12">
<TITLE>Wrapped Labels Pie Chart</TITLE>
</HEAD>
<BODY>
<BR>
<P><FONT SIZE=3D2>Daniel and Troy, </FONT>
</P>
<P><FONT SIZE=3D2>Here is a pie chart with wrapped labels (using some =
internals of the Paragraph class as a wrapping algorithm). Hope you =
find it useful. Feel free to contact me with any questions you have or =
bugs you find.</FONT></P>
<P><FONT SIZE=3D2>Thanks,</FONT>
<BR><FONT SIZE=3D2>Marc Stober</FONT>
<BR><FONT SIZE=3D2>mstober@dalbar.com</FONT>
<BR><FONT SIZE=3D2> </FONT>
</P>
<P><FONT SIZE=3D2>> -----Original Message-----</FONT>
<BR><FONT SIZE=3D2>> From: Daniel McQuillen [<A =
HREF=3D"mailto:daniel@bluepattern.com">mailto:daniel@bluepattern.com</A>=
]</FONT>
<BR><FONT SIZE=3D2>> Sent: Tuesday, April 22, 2003 3:27 PM</FONT>
<BR><FONT SIZE=3D2>> To: Troy Sorzano; Marc Stober</FONT>
<BR><FONT SIZE=3D2>> Subject: Re: [reportlab-users] RE: =
reportlab-users digest, </FONT>
<BR><FONT SIZE=3D2>> Vol 1 #293 -</FONT>
<BR><FONT SIZE=3D2>> 2 msgs</FONT>
<BR><FONT SIZE=3D2>> </FONT>
<BR><FONT SIZE=3D2>> </FONT>
<BR><FONT SIZE=3D2>> Troy Sorzano wrote:</FONT>
<BR><FONT SIZE=3D2>> </FONT>
<BR><FONT SIZE=3D2>> >>>1. I have made a *pie* chart class, =
based on the standard </FONT>
<BR><FONT SIZE=3D2>> ReportLab pie chart class but with some added =
wrapping of the </FONT>
<BR><FONT SIZE=3D2>> labels. If you are interested I will send you a =
copy.<<</FONT>
<BR><FONT SIZE=3D2>> >>> =
</FONT>
<BR><FONT SIZE=3D2>> >>></FONT>
<BR><FONT SIZE=3D2>> ></FONT>
<BR><FONT SIZE=3D2>> ></FONT>
<BR><FONT SIZE=3D2>> >Hi Daniel,</FONT>
<BR><FONT SIZE=3D2>> ></FONT>
<BR><FONT SIZE=3D2>> >Could you send me your pie chart =
class. I am looking for </FONT>
<BR><FONT SIZE=3D2>> the ability to wrap labels.</FONT>
<BR><FONT SIZE=3D2>> ></FONT>
<BR><FONT SIZE=3D2>> >Thanks,</FONT>
<BR><FONT SIZE=3D2>> ></FONT>
<BR><FONT SIZE=3D2>> >Troy Sorzano</FONT>
<BR><FONT SIZE=3D2>> >Info Packaging</FONT>
<BR><FONT SIZE=3D2>> ></FONT>
</P>
<P><FONT FACE=3D"Arial" SIZE=3D2 COLOR=3D"#000000"></FONT>
</BODY>
</HTML>
------_=_NextPart_001_01C309BD.251FB610--
------_=_NextPart_000_01C309BD.251FB610
Content-Type: application/octet-stream;
name="wrappedpie.py"
Content-Transfer-Encoding: quoted-printable
Content-Disposition: attachment;
filename="wrappedpie.py"
from math import sin, cos, pi
from reportlab.graphics.charts.piecharts import Pie
from reportlab.graphics.shapes import Ellipse, Group, Line, Rect, =
String, Wedge
from reportlab.lib import colors
from reportlab.lib.attrmap import AttrMap, AttrMapValue
from reportlab.lib.styles import ParagraphStyle
from reportlab.lib.validators import isNumber, isBoolean
from reportlab.platypus import Paragraph
class WrappedPie(Pie):
"""
A pie chart class, derived from the standard ReportLab Pie chart =
class,
with wrapping for text labels. Text labels can be wrapped around =
the chart itself
and within a vertical column of specified width.
Marc Louis Stober, mstober@dalbar.com, rev. April 23, 2003.
Contains two attributes in addition to those in the standard Pie =
class:
.columnWidth: width of column in which to center pie and wrap the =
text.
.showGuides: for debugging, to show how text boxes are sized and =
positioned.
Also:
.slices.labelRadius: works differently; the label will be to the =
outside of the
radius, rather than centered.
How the wrapping works:
For each slice, the algorithm attempts to fit the label within a =
vertical
column defined by the columnWidth attribute, and to the left or =
right of
the pie slice.
How close the label is to the pie slice depends on the value of
labelRadius, with a value of 1 theoretically putting the edge of
the label on the edge of the slice.
The ReportLab Paragraph class is used for its wrapping alogrithm.
Finally, the label may be adjusted downward to prevent overlapping
with other labels.
"""
_attrMap =3D AttrMap(Pie._attrMap,
columnWidth =3D AttrMapValue(isNumber, desc=3D'Width of =
column in which to contain chart and labels.'),
showGuides =3D AttrMapValue(isBoolean, desc=3D'Show guide =
lines for debugging text wrapping.') =20
)
def __init__(self):
Pie.__init__(self)
self.columnWidth =3D 0
self.showGuides =3D False
def makeWedges(self):
# normalize slice data
normData =3D self.normalizeData()
n =3D len(normData)
# set default column width =3D 2 * width (if not specified)
if self.columnWidth =3D=3D 0: self.columnWidth =3D self.width * =
2
# labels
if self.labels is None:
labels =3D [''] * n
else:
labels =3D self.labels
#there's no point in raising errors for less than enough =
errors if
#we silently create all for the extreme case of no labels.
i =3D n-len(labels)
if i>0:
labels =3D labels + ['']*i
xradius =3D self.width/2.0
yradius =3D self.height/2.0
centerx =3D self.x + xradius
centery =3D self.y + yradius
if self.direction =3D=3D "anticlockwise":
whichWay =3D 1
else:
whichWay =3D -1
g =3D Group()
labels2 =3D []
i =3D 0
styleCount =3D len(self.slices)
startAngle =3D self.startAngle #% 360
for angle in normData:
endAngle =3D (startAngle + (angle * whichWay))
if abs(startAngle-endAngle)>=3D1e-5:
if startAngle < endAngle:
a1 =3D startAngle
a2 =3D endAngle
else:
a1 =3D endAngle
a2 =3D startAngle
#if we didn't use %stylecount here we'd end up with the =
later wedges
#all having the default style
wedgeStyle =3D self.slices[i%styleCount]
# is it a popout?
cx, cy =3D centerx, centery
if wedgeStyle.popout <> 0:
# pop out the wedge
averageAngle =3D (a1+a2)/2.0
aveAngleRadians =3D averageAngle * pi/180.0
popdistance =3D wedgeStyle.popout
cx =3D centerx + popdistance * cos(aveAngleRadians)
cy =3D centery + popdistance * sin(aveAngleRadians)
if n > 1:
theWedge =3D Wedge(cx, cy, xradius, a1, a2, =
yradius=3Dyradius)
elif n=3D=3D1:
theWedge =3D Ellipse(cx, cy, xradius, yradius)
theWedge.fillColor =3D wedgeStyle.fillColor
theWedge.strokeColor =3D wedgeStyle.strokeColor
theWedge.strokeWidth =3D wedgeStyle.strokeWidth
theWedge.strokeDashArray =3D wedgeStyle.strokeDashArray
g.add(theWedge)
# now draw a label
if labels[i] <> "":
averageAngle =3D (a1+a2)/2.0
aveAngleRadians =3D averageAngle*pi/180.0
labelRadius =3D wedgeStyle.labelRadius
labelX =3D centerx + (0.5 * self.width * =
cos(aveAngleRadians) * labelRadius)
labelY =3D centery + (0.5 * self.height * =
sin(aveAngleRadians) * labelRadius)
pos =3D int(len(labels[i])/2)
while str(labels[i])[pos] !=3D ' ' and pos < =
len(labels[i]):
pos +=3D 1
# use a paragraph to wrap the text. since we can't =
actually draw a paragraph
# ... in a RL drawing, we'll just take the split =
text out below
ps =3D ParagraphStyle('PieLabel')
ps.fontName =3D wedgeStyle.fontName
ps.fontSize =3D wedgeStyle.fontSize
ps.fontColor =3D wedgeStyle.fontColor
ps.leading =3D wedgeStyle.fontSize * 1.2
p =3D Paragraph(labels[i], ps)
relativeBoxSize =3D self.columnWidth / self.width
if labelX > centerx:
w =3D (centerx * relativeBoxSize) - labelX
if self.showGuides:
labels2.append(Line(centerx * =
relativeBoxSize, labelY, labelX, labelY, strokeColor =3D =
colors.lightcyan))
else:
w =3D labelX
if self.showGuides:
labels2.append(Line(0, labelY, labelX, =
labelY, strokeColor =3D colors.lightcyan))
w, h =3D p.wrap(w, 9999)
# figure out minimum width needed
minW =3D 0
for linelist in p.blPara.lines:
minW =3D max(minW, w - linelist[0])
w =3D minW
yOffsetRatio =3D (abs(averageAngle + 90) % 90) / 90
yOffsetRatio =3D abs((averageAngle % 180 - 2 * ( =
averageAngle % 90 )) / 90)
if labelY > centery:
labelY1 =3D labelY - averageAngle % 90 / 90 * h =
/ 2.
else:
yOffsetRatio *=3D -1 # offset labels down on =
the bottom
labelY1 =3D labelY - (1.0 - averageAngle % 90 / =
90) * h / 2.
labelY1 =3D labelY - h / 2
labelY1 +=3D yOffsetRatio * h / 2
if labelX > centerx:
side =3D 'R'
labelX1 =3D labelX
if self.showGuides:
labels2.append(Rect(labelX, labelY1, w, h, =
fillColor =3D None, strokeColor =3D colors.lightcyan))
else:
side =3D 'L'
labelX1 =3D labelX - w
if self.showGuides:
labels2.append(Rect(labelX - w, labelY1, w, =
h, fillColor =3D None, strokeColor =3D colors.lightcyan))
# reset current Y position when we move to the =
other size
if getattr(self, '_lastSide', None) !=3D side:
self._currentY =3D labelY1 + h
self._lastSide =3D side
=20
# starting position below any previous labels
if side =3D=3D 'R':
self._currentY =3D min(labelY1 + h, =
self._currentY)
else:
self._currentY =3D max(labelY1 + h, =
self._currentY)
for linelist in p.blPara.lines:
self._currentY -=3D ps.leading
linelist =3D linelist[1]
line =3D ''
for word in linelist:
line +=3D '%s ' % word
line =3D line.rstrip()
theLabel =3D String(labelX1 + w / 2, =
self._currentY, line)
theLabel.textAnchor =3D "middle"
theLabel.fontSize =3D wedgeStyle.fontSize
theLabel.fontName =3D wedgeStyle.fontName
theLabel.fillColor =3D wedgeStyle.fontColor
labels2.append(theLabel)
=20
startAngle =3D endAngle
i =3D i + 1
# add the labels at the end so they print over wedges [MLS =
9/30/02]
for label in labels2:
g.add(label)
return g
if __name__ =3D=3D '__main__':
from reportlab.graphics import renderPDF
from reportlab.graphics.shapes import Drawing
import sys
=20
# create a drawing on which to draw the chart
d =3D Drawing(500, 500)
# create a pie chart
p =3D WrappedPie()
# add chart to drawing
d.add(p)
# set data and properties of chart
p.x =3D 100
p.y =3D 100
p.width =3D 300
p.height =3D 300
p.slices.labelRadius =3D 1.1
p.showGuides =3D True # for debugging
p.columnWidth =3D 600=20
p.data =3D [22,
19.4,
15.714,
15.463,
15,
15,
14.6,
14,
13.417,
13.35]
p.labels =3D ('Alex Rodriguez, Shortstop - Texas $22.000 million',=20
'Carlos Delgado, First Base - Toronto $19.400 million',
'Kevin Brown, Pitcher - Los Angeles $15.714 million',
'Manny Ramirez, Left Field - Boston $15.463 million',
'Sammy Sosa, Right Field - Chicago-NL $15.000 million',
'Barry Bonds, Right Field - San Francisco $15.000 million',
'Derek Jeter, Shortstop - NY Yankees $14.600 million',
'Pedro Martinez, Pitcher - Boston $14.000 million',
'Shawn Green, Right Field - Los Angeles $13.417 million',
'Randy Johnson, Pitcher - Arizona $13.350 million')
# add some labels
d.add(String(250, 450, 'Top 10 MLB Salaries',
textAnchor =3D 'middle', fontSize =3D 18))
d.add(String(30, 30, 'Source: Infoplease.com',
fontSize =3D 8))
# save as (name of script).pdf
filename =3D '%spdf' % sys.argv[0][:-2]
renderPDF.drawToFile(d, filename)
print "Pie Chart Demo saved to '%s'." % filename
=20
------_=_NextPart_000_01C309BD.251FB610--