[reportlab-users] Stacked bar chart broken when negative values are involved

Brian jerald_13 at yahoo.com
Fri Sep 5 14:38:49 EDT 2008


There seems to be a problem with negative values being fed into the data. Results seem very erratic.
Here is what I need to do:
Plot 4 stacked bars, that will usually span from negative to positive,
that include a "data span" range (min value to max value of a data
set), and a bar on each side of the mean value that represents the
standard deviation.

So to recap:
Bar0: (invisible): 0 to Min
Bar1: Min to (Mean - STD)
Bar2: (Mean-STD) to Mean
Bar3: Mean to (Mean + STD)
Bar4: (Mean+STD) to Max

The problem is, any time a bar crosses the axis, strange things start
happening. It will do up to the axes in the expected color, and add
whatever's left to the next bar (in the color of the next bar).

I read somewhere that the issues can be avoided by plotting from top to
bottom, but this doesn't work either. Sometimes bars disappear,
sometimes they're plotted in the wrong color, sometimes they're cut off
and added to the next bar, and this is only the case when dealing with
negatives. Here is some sample code to reproduce the problem.

Here is the code (before i tried height tracking to work around boundaries, which broke because it started plotting things in the wrong color):



from reportlab.graphics.shapes import Drawing
from reportlab.graphics.charts.barcharts import VerticalBarChart
from reportlab.graphics.charts.legends import *
from reportlab.lib import colors
import math



class CompressedBarChart:
def __init__(self, title, stdcolor="green", spancolor="yellow"):
self.mindata = []
self.mincolor = None
self.maxdata = []
self.maxcolor = None
self.meandata = []
self.meancolor = None
self.stddata = []
self.stdcolor = None
self.minvalue = 0
self.maxvalue = -90000000
self.ticks = []
self.title=title
self.curbars = []

if hasattr(colors, stdcolor):
self.stdcolor = getattr(colors, stdcolor)
else:
self.stdcolor = colors.green

if hasattr(colors, spancolor):
self.spancolor = getattr(colors, spancolor)
else:
self.spancolor = colors.yellow

def addBar(self, name, minval, maxval, meanval=None, stdval=None):
self.ticks.append(name)
if not minval == None:
self.mindata.append(minval)
if minval < self.minvalue:
self.minvalue = math.ceil(minval/10 - 2)*10
if not maxval == None:
self.maxdata.append(maxval)
if maxval > self.maxvalue:
self.maxvalue = math.ceil(maxval/10 + 2)*10

if not meanval == None:
self.meandata.append(meanval)

if not stdval == None:
self.stddata.append(stdval)
self.curbars.append(0)


def createPlots(self):
if len(self.mindata) == 0 or len(self.maxdata) == 0:
return

if not len(self.mindata) == len(self.maxdata):
return

if not len(self.mindata) == len(self.meandata):
hasmean = False
hasstd = False
else:
hasmean = True
if not len(self.mindata) == len(self.stddata):
hasstd = False
else:
hasstd = True
data = []

bc = VerticalBarChart()
bc.categoryAxis.style='stacked'

if hasstd:
lspan = [x- y-z for x,y,z in zip(self.meandata, self.mindata, self.stddata)]
uspan = [x- (y+z) for x,y,z in zip(self.maxdata, self.meandata, self.stddata)]


data.append(tuple(self.mindata))
data.append(tuple(lspan))
data.append(tuple(self.stddata))
data.append(tuple(self.stddata))
data.append(tuple(uspan))

bc.bars[0].fillColor = None
bc.bars[0].strokeColor = None
bc.bars[1].fillColor = self.spancolor
bc.bars[2].fillColor = self.stdcolor
bc.bars[3].fillColor = self.stdcolor
bc.bars[4].fillColor = self.spancolor
print str(data)
bc.data = data
bc.x = 50
bc.y = 50
bc.height = 125
bc.width = 300
bc.strokeColor = colors.black

#set the range and tick marks on the y-axis
bc.valueAxis.valueMin = self.minvalue
bc.valueAxis.valueMax = self.maxvalue

#make the ticks on some multiple of 10
bc.valueAxis.valueStep = int((self.maxvalue-self.minvalue)/10)
bc.valueAxis.valueStep += (10-bc.valueAxis.valueStep % 10)

bc.categoryAxis.labels.boxAnchor = 'ne'
bc.categoryAxis.labels.dx = 8
bc.categoryAxis.labels.dy = -2
bc.categoryAxis.labels.angle = 30

bc.categoryAxis.categoryNames = self.ticks

bc.valueAxis.visibleGrid=0 #set 1 for horizontal tick lines
bc.groupSpacing=5 #change to change spacing between bars

drawing = Drawing(550,200)
drawing.add(bc)
legend = Legend()
legend.x = 300+70
legend.y = 125-5
legend.dx = 20
legend.dy = 5
legend.deltax = 0
legend.boxAnchor = 'nw'
legend.colorNamePairs = []
legend.colorNamePairs.append((self.spancolor, 'Span Min to Max'))
if hasstd:
legend.colorNamePairs.append((self.stdcolor, 'Mean +/- one STD'))
drawing.add(legend)
drawing.add(String(175,190,self.title), name='title')

from reportlab.graphics import renderPDF
renderPDF.drawToFile(drawing, 'funchart.pdf', 'Simple Bar Chart')


graphobj = CompressedBarChart(title="Sample Chart", stdcolor="purple", spancolor="yellow")
graphobj.addBar(name="BAD1", minval=-15, maxval=0, meanval=-7, stdval=3)# everything is shifted up because it made thee lower bounds of the span invisible
graphobj.addBar(name="BAD2", minval=-15, maxval=15, meanval=2, stdval=10)# The lower bound STD is cut off at the axis and the remainder is left as part of the span
graphobj.addBar(name="GOOD!", minval=15, maxval=50, meanval=35, stdval=5)
graphobj.createPlots()





More information about the reportlab-users mailing list