def sample4d(): "Sample drawing, xvalue/yvalue axes, y connected to top of x." drawing = Drawing(400, 200) data = [(10, 20, 30, 42)] yAxis = YValueAxis() yAxis.setPosition(50, 50, 125) yAxis.configure(data) xAxis = XValueAxis() xAxis._length = 300 xAxis.joinAxis = yAxis xAxis.joinAxisMode = 'top' xAxis.configure(data) drawing.add(xAxis) drawing.add(yAxis) return drawing
def __init__(self): PlotArea.__init__(self) self.reversePlotOrder = 0 self.xValueAxis = XValueAxis() self.yValueAxis = YValueAxis() # this defines two series of 3 points. Just an example. self.data = [((1, 1), (2, 2), (2.5, 1), (3, 3), (4, 5)), ((1, 2), (2, 3), (2.5, 2), (3, 4), (4, 6))] self.lines = TypedPropertyCollection(LinePlotProperties) self.lines.strokeWidth = 1 self.lines[0].strokeColor = colors.red self.lines[1].strokeColor = colors.blue self.lineLabels = TypedPropertyCollection(Label) self.lineLabelFormat = None self.lineLabelArray = None # this says whether the origin is inside or outside # the bar - +10 means put the origin ten points # above the tip of the bar if value > 0, or ten # points inside if bar value < 0. This is different # to label dx/dy which are not dependent on the # sign of the data. self.lineLabelNudge = 10 # if you have multiple series, by default they butt # together. # New line chart attributes. self.joinedLines = 1 # Connect items with straight lines. # private attributes self._inFill = None
def __init__(self): LineChart.__init__(self) # Allow for a bounding rectangle. self.strokeColor = None self.fillColor = None # Named so we have less recoding for the horizontal one :-) self.categoryAxis = XCategoryAxis() self.valueAxis = YValueAxis() # This defines two series of 3 points. Just an example. self.data = [(100,110,120,130), (70, 80, 80, 90)] self.categoryNames = ('North','South','East','West') self.lines = TypedPropertyCollection(LineChartProperties) self.lines.strokeWidth = 1 self.lines[0].strokeColor = colors.red self.lines[1].strokeColor = colors.green self.lines[2].strokeColor = colors.blue # control spacing. if useAbsolute = 1 then # the next parameters are in points; otherwise # they are 'proportions' and are normalized to # fit the available space. self.useAbsolute = 0 #- not done yet self.groupSpacing = 1 #5 self.lineLabels = TypedPropertyCollection(Label) self.lineLabelFormat = None self.lineLabelArray = None # This says whether the origin is above or below # the data point. +10 means put the origin ten points # above the data point if value > 0, or ten # points below if data value < 0. This is different # to label dx/dy which are not dependent on the # sign of the data. self.lineLabelNudge = 10 # If you have multiple series, by default they butt # together. # New line chart attributes. self.joinedLines = 1 # Connect items with straight lines. self.inFill = 0 self.reversePlotOrder = 0
def makeTickLabels(self): g = YValueAxis.makeTickLabels(self) desc_text = make_desc_test(self, "Y") if desc_text is not None: g.add(desc_text) return g
def sample6b(): "Sample drawing, xcat/yvalue axes, x connected at bottom of y." drawing = Drawing(400, 200) data = [(10, 20, 30, 42)] yAxis = YValueAxis() yAxis.setPosition(50, 50, 125) yAxis.configure(data) xAxis = XCategoryAxis() xAxis._length = 300 xAxis.configure(data) xAxis.joinAxis = yAxis xAxis.joinAxisMode = 'bottom' xAxis.categoryNames = ['Beer', 'Wine', 'Meat', 'Cannelloni'] xAxis.labels.boxAnchor = 'n' drawing.add(xAxis) drawing.add(yAxis) return drawing
def __init__(self, data=None, legend_data=None): Drawing.__init__(self, width=530, height=160) self._add(self, MyChartFrame(x=0, y=0, width=530, height=160, title='Volume'), name='frame') # chart self._add(self, LinePlot(), name='chart') self.chart.y = 20 self.chart.x = 34 self.chart.width = 423 self.chart.height = 110 # line styles self.chart.lines.symbol = makeMarker('Circle', size=2) colors = map(toColor, get_n_random_colors(len(legend_data))) for (i, _) in enumerate(legend_data): self.chart.lines[i].strokeColor = colors[i] # x axis self.chart.xValueAxis = NormalDateXValueAxis() self.chart.xValueAxis.xLabelFormat = '{dd} {MMM}' self.chart.xValueAxis.loLLen = 8 self.chart.xValueAxis.hiLLen = 5 self.chart.xValueAxis.labels.fontName = 'Lato' self.chart.xValueAxis.labels.fontSize = 9 # y axis self.chart.yValueAxis = YValueAxis() self.chart.yValueAxis.visibleGrid = 1 self.chart.yValueAxis.visibleAxis = 0 self.chart.yValueAxis.strokeWidth = 0.25 self.chart.yValueAxis.labels.rightPadding = 5 self.chart.yValueAxis.labels.fontName = 'Lato' self.chart.yValueAxis.labels.fontSize = 9 self.chart.yValueAxis.rangeRound = 'both' self.chart.yValueAxis.tickLeft = 7.5 self.chart.yValueAxis.minimumTickSpacing = 0.5 self.chart.yValueAxis.maximumTicks = 8 self.chart.yValueAxis.avoidBoundFrac = 0.1 # legend self._add(self, LineLegend(), name='legend') self.legend.alignment = 'right' self.legend.y = 110 self.legend.x = 462 self.legend.dxTextSpace = 5 self.legend.columnMaximum = 4 self.legend.fontName = 'Lato' self.legend.fontSize = 9 self.legend.colorNamePairs = zip(colors, legend_data) # Data... self.chart.data = data
def sample1(): "Sample drawing containing two unconnected axes." from reportlab.graphics.shapes import _baseGFontNameB drawing = Drawing(400, 200) data = [(10, 20, 30, 42)] xAxis = XCategoryAxis() xAxis.setPosition(75, 75, 300) xAxis.configure(data) xAxis.categoryNames = ['Beer','Wine','Meat','Cannelloni'] xAxis.labels.boxAnchor = 'n' xAxis.labels[3].dy = -15 xAxis.labels[3].angle = 30 xAxis.labels[3].fontName = _baseGFontNameB yAxis = YValueAxis() yAxis.setPosition(50, 50, 125) yAxis.configure(data) drawing.add(xAxis) drawing.add(yAxis) return drawing
def sample5b(): "Sample drawing, xvalue/yvalue axes, y connected at value 35 of x." drawing = Drawing(400, 200) data = [(10, 20, 30, 42)] xAxis = XValueAxis() xAxis.setPosition(50, 50, 300) xAxis.configure(data) yAxis = YValueAxis() yAxis.setPosition(50, 50, 125) yAxis.joinAxis = xAxis yAxis.joinAxisMode = 'value' yAxis.joinAxisPos = 35 yAxis.configure(data) drawing.add(xAxis) drawing.add(yAxis) return drawing
def __init__(self): PlotArea.__init__(self) self.reversePlotOrder = 0 self.xValueAxis = XValueAxis() self.yValueAxis = YValueAxis() # this defines two series of 3 points. Just an example. self.data = [ ((1,1), (2,2), (2.5,1), (3,3), (4,5)), ((1,2), (2,3), (2.5,2), (3,4), (4,6)) ] self.lines = TypedPropertyCollection(LinePlotProperties) self.lines.strokeWidth = 1 self.lines[0].strokeColor = colors.red self.lines[1].strokeColor = colors.blue self.lineLabels = TypedPropertyCollection(Label) self.lineLabelFormat = None self.lineLabelArray = None # this says whether the origin is inside or outside # the bar - +10 means put the origin ten points # above the tip of the bar if value > 0, or ten # points inside if bar value < 0. This is different # to label dx/dy which are not dependent on the # sign of the data. self.lineLabelNudge = 10 # if you have multiple series, by default they butt # together. # New line chart attributes. self.joinedLines = 1 # Connect items with straight lines. #private attributes self._inFill = None self.annotations = [] self.behindAxes = 0 self.gridFirst = 0
def sample4c1(): "xvalue/yvalue axes, without drawing axis lines/ticks." drawing = Drawing(400, 200) data = [(10, 20, 30, 42)] yAxis = YValueAxis() yAxis.setPosition(50, 50, 125) yAxis.configure(data) yAxis.visibleAxis = 0 yAxis.visibleTicks = 0 xAxis = XValueAxis() xAxis._length = 300 xAxis.joinAxis = yAxis xAxis.joinAxisMode = 'bottom' xAxis.configure(data) xAxis.visibleAxis = 0 xAxis.visibleTicks = 0 drawing.add(xAxis) drawing.add(yAxis) return drawing
def __init__(self, data=None): Drawing.__init__(self, width=458, height=130) # chart self._add(self, LinePlot(), name='chart') self.chart.y = 20 self.chart.x = 60 self.chart.width = 320 self.chart.height = 100 # line styles self.chart.lines.symbol = makeMarker('Circle', size=2) colors = map(toColor, get_n_random_colors(len(data))) for (i, _) in enumerate(data): self.chart.lines[i].strokeColor = colors[i] # x axis self.chart.xValueAxis = NormalDateXValueAxis() self.chart.xValueAxis.xLabelFormat = '{dd} {MMM}' self.chart.xValueAxis.loLLen = 8 self.chart.xValueAxis.hiLLen = 5 self.chart.xValueAxis.labels.fontName = 'Lato' self.chart.xValueAxis.labels.fontSize = 9 # y axis self.chart.yValueAxis = YValueAxis() self.chart.yValueAxis.visibleGrid = 1 self.chart.yValueAxis.visibleAxis = 0 self.chart.yValueAxis.strokeWidth = 0.25 self.chart.yValueAxis.labels.rightPadding = 5 self.chart.yValueAxis.labels.fontName = 'Lato' self.chart.yValueAxis.labels.fontSize = 9 self.chart.yValueAxis.rangeRound = 'both' self.chart.yValueAxis.tickLeft = 7.5 self.chart.yValueAxis.minimumTickSpacing = 0.5 self.chart.yValueAxis.maximumTicks = 8 self.chart.yValueAxis.avoidBoundFrac = 0.1 # Data... self.chart.data = data
def sample1(): "Sample drawing containing two unconnected axes." from reportlab.graphics.shapes import _baseGFontNameB drawing = Drawing(400, 200) data = [(10, 20, 30, 42)] xAxis = XCategoryAxis() xAxis.setPosition(75, 75, 300) xAxis.configure(data) xAxis.categoryNames = ['Beer', 'Wine', 'Meat', 'Cannelloni'] xAxis.labels.boxAnchor = 'n' xAxis.labels[3].dy = -15 xAxis.labels[3].angle = 30 xAxis.labels[3].fontName = _baseGFontNameB yAxis = YValueAxis() yAxis.setPosition(50, 50, 125) yAxis.configure(data) drawing.add(xAxis) drawing.add(yAxis) return drawing
def axes_demo(): drawing = shapes.Drawing(width=500, height=300) data = [(5, 10, 15, 20), (10, 17, 25, 31)] x_axis = XCategoryAxis() x_axis.setPosition(100, 100, 350) x_axis.configure(data, barWidth=None) x_axis.categoryNames = ['Python', 'Ruby', 'C++', 'Haskell', 'Java'] x_axis.labels.boxAnchor = 'n' x_axis.labels[0].angle = 45 x_axis.labels[0].fontName = 'Times-Bold' x_axis.labels[1].fontName = 'Courier' x_axis.labels[1].fontSize = 16 drawing.add(x_axis) y_axis = YValueAxis() y_axis.setPosition(75, 75, 150) y_axis.configure(data) drawing.add(y_axis) renderPDF.drawToFile(drawing, 'axes_demo.pdf')
def generateGraphs(self, x, y): (x1, y1, Width, Height) = self._getGraphRegion(x, y) #Draw Axes yVAxis = YValueAxis() #If we got numeric X vals, we use a ValueAxis, otherwise CategoryAxis. if len(self.x_vals) > 0: if isinstance(self.x_vals[0], str): xVAxis = XCategoryAxis() else: xVAxis = XValueAxis() else: return -1 (y_min, y_max, y_step) = self.getValueAxisScale(yVAxis, [self.y_vals]) if y_min == -1 and y_min == -1 and y_step == -1: return -1 (self.valueMin, self.valueMax, self.valueStep) = (y_min, y_max, y_step) (SizeXaxis, SizeYaxis) = self.getSizes() #lot of ugliness in here to get the split chart working :| if self.betweenSplitsF == True: if self.numSplits == 0: self.tmpYVal = SizeYaxis else: SizeYaxis = self.tmpYVal if self.numSplits > 0: xVAxis.visibleLabels = False xVAxis.visibleTicks = False X_start_pos = x1 - x + SizeYaxis X_width = Width - SizeYaxis - 10 if self.betweenSplitsF == True: if self.numSplits > 0: Y_start_pos = y1 - y + SizeXaxis + (self.numSplits * self.height) - (self.numSplits * 15) else: Y_start_pos = y1 - y + SizeXaxis + (self.numSplits * self.height) Y_height = Height - SizeXaxis + 40 else: Y_start_pos = y1 - y + SizeXaxis + (self.numSplits * self.height) Y_height = Height - SizeXaxis xVAxis.setPosition(X_start_pos, Y_start_pos, X_width) if isinstance(xVAxis, XValueAxis): dataList = [] dataTuple = reduce(lambda a,b: a + (b,), self.x_vals, ()) dataList.append(dataTuple) xVAxis.configure(dataList) xVAxis.labelTextFormat = '%0.' + '%d' % self.xAxisDigits + 'f' else: dataList = [] zerodata = [0 for val in self.x_vals] dataTuple = reduce(lambda a,b: a + (b,), zerodata, ()) dataList.append(dataTuple) xVAxis.configure(dataList) xVAxis.categoryNames = self.x_vals #Ugly hack for setting the labels.dy. Empirical: If there are more than 5 #chars, dy = -35 fits fine (assuming the angle would be > 75,when we have #so long val) maxLen = 0 for val in self.x_vals: valLen = len(val) if valLen > maxLen: maxLen = valLen if maxLen > 5: xVAxis.labels.dy = -35 xVAxis.labels.fontName = 'Helvetica' xVAxis.labels.fontSize = 7 xVAxis.labels.angle = self.xValsDisplayAngle drawLegendF = False if len(self.legendList) > 0: drawLegendF = True drawLabelF = False lblCounts = len(self.x_vals) * len(self.y_vals) if lblCounts <= 30 and self.displayDataLbls == True: drawLabelF = True if len(self.x_vals) == 1: drawLabelF = True #hack to increase chart height to include legend and labels total_height = 0 legendHeight = 0 if drawLegendF == True: #Kludge: We enchroach the space needed for legends by bringing down the #y value by 28 (in drawOn()), we use that space here legendHeight = Y_height + 28 graph_height = Y_height yVAxis.setPosition(X_start_pos, Y_start_pos, Y_height) yVAxis.valueMin = y_min yVAxis.valueMax = y_max yVAxis.valueStep = y_step yVAxis.labels.fontName = 'Helvetica' yVAxis.labels.fontSize = 7 yVAxis.labelTextFormat = '%0.' + '%d' % self.yAxisDigits + 'f' yVAxis.configure(self.y_vals) #will later sync the yVAxis as the ValueAxis of the graph self.drawing.add(xVAxis) if self.numSplits == 0: tmp = self.height self.height = self.origHeight self._drawLabels(self.title, self.x_label, self.y_label) self.height = tmp for graphtype in self.graphList: #Draw only one set of Legends/Labels for any dataset, #even if there are multiple charts drawn for the same dataset if self.labelsDrawnF == True and self.numSplits == 0: drawLabelF = False if self.legendsDrawnF == True and self.numSplits == 0: drawLegendF = False if graphtype == 'Bar': GraphObj = VerticalBarChart() GraphObj.valueAxis = yVAxis self.drawGraph(GraphObj, X_start_pos, Y_start_pos, X_width, graph_height, drawLabelF, drawLegendF, legendHeight) if graphtype == 'Line': GraphObj = HorizontalLineChart() GraphObj.valueAxis = yVAxis self.drawGraph(GraphObj, X_start_pos, Y_start_pos, X_width, graph_height, drawLabelF, drawLegendF, legendHeight)
class HorizontalLineChart(LineChart): """Line chart with multiple lines. A line chart is assumed to have one category and one value axis. Despite its generic name this particular line chart class has a vertical value axis and a horizontal category one. It may evolve into individual horizontal and vertical variants (like with the existing bar charts). Available attributes are: x: x-position of lower-left chart origin y: y-position of lower-left chart origin width: chart width height: chart height useAbsolute: disables auto-scaling of chart elements (?) lineLabelNudge: distance of data labels to data points lineLabels: labels associated with data values lineLabelFormat: format string or callback function groupSpacing: space between categories joinedLines: enables drawing of lines strokeColor: color of chart lines (?) fillColor: color for chart background (?) lines: style list, used cyclically for data series valueAxis: value axis object categoryAxis: category axis object categoryNames: category names data: chart data, a list of data series of equal length """ _attrMap = AttrMap(BASE=LineChart, useAbsolute = AttrMapValue(isNumber, desc='Flag to use absolute spacing values.',advancedUsage=1), lineLabelNudge = AttrMapValue(isNumber, desc='Distance between a data point and its label.',advancedUsage=1), lineLabels = AttrMapValue(None, desc='Handle to the list of data point labels.'), lineLabelFormat = AttrMapValue(None, desc='Formatting string or function used for data point labels.'), lineLabelArray = AttrMapValue(None, desc='explicit array of line label values, must match size of data if present.'), groupSpacing = AttrMapValue(isNumber, desc='? - Likely to disappear.'), joinedLines = AttrMapValue(isNumber, desc='Display data points joined with lines if true.'), lines = AttrMapValue(None, desc='Handle of the lines.'), valueAxis = AttrMapValue(None, desc='Handle of the value axis.'), categoryAxis = AttrMapValue(None, desc='Handle of the category axis.'), categoryNames = AttrMapValue(isListOfStringsOrNone, desc='List of category names.'), data = AttrMapValue(None, desc='Data to be plotted, list of (lists of) numbers.'), inFill = AttrMapValue(isBoolean, desc='Whether infilling should be done.',advancedUsage=1), reversePlotOrder = AttrMapValue(isBoolean, desc='If true reverse plot order.',advancedUsage=1), annotations = AttrMapValue(None, desc='list of callables, will be called with self, xscale, yscale.',advancedUsage=1), ) def __init__(self): LineChart.__init__(self) # Allow for a bounding rectangle. self.strokeColor = None self.fillColor = None # Named so we have less recoding for the horizontal one :-) self.categoryAxis = XCategoryAxis() self.valueAxis = YValueAxis() # This defines two series of 3 points. Just an example. self.data = [(100,110,120,130), (70, 80, 80, 90)] self.categoryNames = ('North','South','East','West') self.lines = TypedPropertyCollection(LineChartProperties) self.lines.strokeWidth = 1 self.lines[0].strokeColor = colors.red self.lines[1].strokeColor = colors.green self.lines[2].strokeColor = colors.blue # control spacing. if useAbsolute = 1 then # the next parameters are in points; otherwise # they are 'proportions' and are normalized to # fit the available space. self.useAbsolute = 0 #- not done yet self.groupSpacing = 1 #5 self.lineLabels = TypedPropertyCollection(Label) self.lineLabelFormat = None self.lineLabelArray = None # This says whether the origin is above or below # the data point. +10 means put the origin ten points # above the data point if value > 0, or ten # points below if data value < 0. This is different # to label dx/dy which are not dependent on the # sign of the data. self.lineLabelNudge = 10 # If you have multiple series, by default they butt # together. # New line chart attributes. self.joinedLines = 1 # Connect items with straight lines. self.inFill = 0 self.reversePlotOrder = 0 def demo(self): """Shows basic use of a line chart.""" drawing = Drawing(200, 100) data = [ (13, 5, 20, 22, 37, 45, 19, 4), (14, 10, 21, 28, 38, 46, 25, 5) ] lc = HorizontalLineChart() lc.x = 20 lc.y = 10 lc.height = 85 lc.width = 170 lc.data = data lc.lines.symbol = makeMarker('Circle') drawing.add(lc) return drawing def calcPositions(self): """Works out where they go. Sets an attribute _positions which is a list of lists of (x, y) matching the data. """ self._seriesCount = len(self.data) self._rowLength = max(list(map(len,self.data))) if self.useAbsolute: # Dimensions are absolute. normFactor = 1.0 else: # Dimensions are normalized to fit. normWidth = self.groupSpacing availWidth = self.categoryAxis.scale(0)[1] normFactor = availWidth / normWidth self._positions = [] for rowNo in range(len(self.data)): lineRow = [] for colNo in range(len(self.data[rowNo])): datum = self.data[rowNo][colNo] if datum is not None: (groupX, groupWidth) = self.categoryAxis.scale(colNo) x = groupX + (0.5 * self.groupSpacing * normFactor) y = self.valueAxis.scale(0) height = self.valueAxis.scale(datum) - y lineRow.append((x, y+height)) self._positions.append(lineRow) def _innerDrawLabel(self, rowNo, colNo, x, y): "Draw a label for a given item in the list." labelFmt = self.lineLabelFormat labelValue = self.data[rowNo][colNo] if labelFmt is None: labelText = None elif type(labelFmt) is str: if labelFmt == 'values': try: labelText = self.lineLabelArray[rowNo][colNo] except: labelText = None else: labelText = labelFmt % labelValue elif hasattr(labelFmt,'__call__'): labelText = labelFmt(labelValue) else: raise ValueError("Unknown formatter type %s, expected string or function"%labelFmt) if labelText: label = self.lineLabels[(rowNo, colNo)] if not label.visible: return # Make sure labels are some distance off the data point. if y > 0: label.setOrigin(x, y + self.lineLabelNudge) else: label.setOrigin(x, y - self.lineLabelNudge) label.setText(labelText) else: label = None return label def drawLabel(self, G, rowNo, colNo, x, y): '''Draw a label for a given item in the list. G must have an add method''' G.add(self._innerDrawLabel(rowNo,colNo,x,y)) def makeLines(self): g = Group() labelFmt = self.lineLabelFormat P = list(range(len(self._positions))) if self.reversePlotOrder: P.reverse() inFill = self.inFill if inFill: inFillY = self.categoryAxis._y inFillX0 = self.valueAxis._x inFillX1 = inFillX0 + self.categoryAxis._length inFillG = getattr(self,'_inFillG',g) # Iterate over data rows. for rowNo in P: row = self._positions[rowNo] styleCount = len(self.lines) styleIdx = rowNo % styleCount rowStyle = self.lines[styleIdx] rowColor = rowStyle.strokeColor dash = getattr(rowStyle, 'strokeDashArray', None) if hasattr(self.lines[styleIdx], 'strokeWidth'): strokeWidth = self.lines[styleIdx].strokeWidth elif hasattr(self.lines, 'strokeWidth'): strokeWidth = self.lines.strokeWidth else: strokeWidth = None # Iterate over data columns. if self.joinedLines: points = [] for colNo in range(len(row)): points += row[colNo] if inFill: points = points + [inFillX1,inFillY,inFillX0,inFillY] inFillG.add(Polygon(points,fillColor=rowColor,strokeColor=rowColor,strokeWidth=0.1)) else: line = PolyLine(points,strokeColor=rowColor,strokeLineCap=0,strokeLineJoin=1) if strokeWidth: line.strokeWidth = strokeWidth if dash: line.strokeDashArray = dash g.add(line) if hasattr(self.lines[styleIdx], 'symbol'): uSymbol = self.lines[styleIdx].symbol elif hasattr(self.lines, 'symbol'): uSymbol = self.lines.symbol else: uSymbol = None if uSymbol: for colNo in range(len(row)): x1, y1 = row[colNo] symbol = uSymbol2Symbol(uSymbol,x1,y1,rowStyle.strokeColor) if symbol: g.add(symbol) # Draw item labels. for colNo in range(len(row)): x1, y1 = row[colNo] self.drawLabel(g, rowNo, colNo, x1, y1) return g def draw(self): "Draws itself." vA, cA = self.valueAxis, self.categoryAxis vA.setPosition(self.x, self.y, self.height) if vA: vA.joinAxis = cA if cA: cA.joinAxis = vA vA.configure(self.data) # If zero is in chart, put x axis there, otherwise # use bottom. xAxisCrossesAt = vA.scale(0) if ((xAxisCrossesAt > self.y + self.height) or (xAxisCrossesAt < self.y)): y = self.y else: y = xAxisCrossesAt cA.setPosition(self.x, y, self.width) cA.configure(self.data) self.calcPositions() g = Group() g.add(self.makeBackground()) if self.inFill: self._inFillG = Group() g.add(self._inFillG) g.add(cA) g.add(vA) cAdgl = getattr(cA,'drawGridLast',False) vAdgl = getattr(vA,'drawGridLast',False) if not cAdgl: cA.makeGrid(g,parent=self,dim=vA.getGridDims) if not vAdgl: vA.makeGrid(g,parent=self,dim=cA.getGridDims) g.add(self.makeLines()) if cAdgl: cA.makeGrid(g,parent=self,dim=vA.getGridDims) if vAdgl: vA.makeGrid(g,parent=self,dim=cA.getGridDims) for a in getattr(self,'annotations',()): g.add(a(self,cA.scale,vA.scale)) return g
def __init__(self): LinePlot.__init__(self) self.xValueAxis = NormalDateXValueAxis() self.yValueAxis = YValueAxis() self.data = _monthlyIndexData
def __init__(self, desc=None): YValueAxis.__init__(self) self.desc = None if isString(desc) is True: self.desc = desc
class LinePlot(PlotArea): """Line plot with multiple lines. Both x- and y-axis are value axis (so there are no seperate X and Y versions of this class). """ _attrMap = AttrMap( BASE=PlotArea, reversePlotOrder=AttrMapValue(isBoolean, desc="If true reverse plot order."), lineLabelNudge=AttrMapValue(isNumber, desc="Distance between a data point and its label."), lineLabels=AttrMapValue(None, desc="Handle to the list of data point labels."), lineLabelFormat=AttrMapValue(None, desc="Formatting string or function used for data point labels."), lineLabelArray=AttrMapValue( None, desc="explicit array of line label values, must match size of data if present." ), joinedLines=AttrMapValue(isNumber, desc="Display data points joined with lines if true."), strokeColor=AttrMapValue(isColorOrNone, desc="Color used for background border of plot area."), fillColor=AttrMapValue(isColorOrNone, desc="Color used for background interior of plot area."), lines=AttrMapValue(None, desc="Handle of the lines."), xValueAxis=AttrMapValue(None, desc="Handle of the x axis."), yValueAxis=AttrMapValue(None, desc="Handle of the y axis."), data=AttrMapValue(None, desc="Data to be plotted, list of (lists of) x/y tuples."), annotations=AttrMapValue(None, desc="list of callables, will be called with self, xscale, yscale."), ) def __init__(self): PlotArea.__init__(self) self.reversePlotOrder = 0 self.xValueAxis = XValueAxis() self.yValueAxis = YValueAxis() # this defines two series of 3 points. Just an example. self.data = [((1, 1), (2, 2), (2.5, 1), (3, 3), (4, 5)), ((1, 2), (2, 3), (2.5, 2), (3, 4), (4, 6))] self.lines = TypedPropertyCollection(LinePlotProperties) self.lines.strokeWidth = 1 self.lines[0].strokeColor = colors.red self.lines[1].strokeColor = colors.blue self.lineLabels = TypedPropertyCollection(Label) self.lineLabelFormat = None self.lineLabelArray = None # this says whether the origin is inside or outside # the bar - +10 means put the origin ten points # above the tip of the bar if value > 0, or ten # points inside if bar value < 0. This is different # to label dx/dy which are not dependent on the # sign of the data. self.lineLabelNudge = 10 # if you have multiple series, by default they butt # together. # New line chart attributes. self.joinedLines = 1 # Connect items with straight lines. # private attributes self._inFill = None def demo(self): """Shows basic use of a line chart.""" drawing = Drawing(400, 200) data = [((1, 1), (2, 2), (2.5, 1), (3, 3), (4, 5)), ((1, 2), (2, 3), (2.5, 2), (3.5, 5), (4, 6))] lp = LinePlot() lp.x = 50 lp.y = 50 lp.height = 125 lp.width = 300 lp.data = data lp.joinedLines = 1 lp.lineLabelFormat = "%2.0f" lp.strokeColor = colors.black lp.lines[0].strokeColor = colors.red lp.lines[0].symbol = makeMarker("FilledCircle") lp.lines[1].strokeColor = colors.blue lp.lines[1].symbol = makeMarker("FilledDiamond") lp.xValueAxis.valueMin = 0 lp.xValueAxis.valueMax = 5 lp.xValueAxis.valueStep = 1 lp.yValueAxis.valueMin = 0 lp.yValueAxis.valueMax = 7 lp.yValueAxis.valueStep = 1 drawing.add(lp) return drawing def calcPositions(self): """Works out where they go. Sets an attribute _positions which is a list of lists of (x, y) matching the data. """ self._seriesCount = len(self.data) self._rowLength = max(map(len, self.data)) self._positions = [] for rowNo in range(len(self.data)): line = [] for colNo in range(len(self.data[rowNo])): datum = self.data[rowNo][colNo] # x,y value if type(datum[0]) == type(""): x = self.xValueAxis.scale(mktime(mkTimeTuple(datum[0]))) else: x = self.xValueAxis.scale(datum[0]) y = self.yValueAxis.scale(datum[1]) line.append((x, y)) self._positions.append(line) def _innerDrawLabel(self, rowNo, colNo, x, y): "Draw a label for a given item in the list." labelFmt = self.lineLabelFormat labelValue = self.data[rowNo][colNo][1] ### if labelFmt is None: labelText = None elif type(labelFmt) is StringType: if labelFmt == "values": labelText = self.lineLabelArray[rowNo][colNo] else: labelText = labelFmt % labelValue elif type(labelFmt) is FunctionType: labelText = labelFmt(labelValue) elif isinstance(labelFmt, Formatter): labelText = labelFmt(labelValue) else: msg = "Unknown formatter type %s, expected string or function" raise Exception, msg % labelFmt if labelText: label = self.lineLabels[(rowNo, colNo)] # hack to make sure labels are outside the bar if y > 0: label.setOrigin(x, y + self.lineLabelNudge) else: label.setOrigin(x, y - self.lineLabelNudge) label.setText(labelText) else: label = None return label def drawLabel(self, G, rowNo, colNo, x, y): """Draw a label for a given item in the list. G must have an add method""" G.add(self._innerDrawLabel(rowNo, colNo, x, y)) def makeLines(self): g = Group() bubblePlot = getattr(self, "_bubblePlot", None) if bubblePlot: yA = self.yValueAxis xA = self.xValueAxis bubbleR = min(yA._bubbleRadius, xA._bubbleRadius) bubbleMax = xA._bubbleMax labelFmt = self.lineLabelFormat P = range(len(self._positions)) if self.reversePlotOrder: P.reverse() inFill = getattr(self, "_inFill", None) if inFill: inFillY = self.xValueAxis._y inFillX0 = self.yValueAxis._x inFillX1 = inFillX0 + self.xValueAxis._length inFillG = getattr(self, "_inFillG", g) # Iterate over data rows. styleCount = len(self.lines) for rowNo in P: row = self._positions[rowNo] rowStyle = self.lines[rowNo % styleCount] rowColor = rowStyle.strokeColor dash = getattr(rowStyle, "strokeDashArray", None) if hasattr(rowStyle, "strokeWidth"): width = rowStyle.strokeWidth elif hasattr(self.lines, "strokeWidth"): width = self.lines.strokeWidth else: width = None # Iterate over data columns. if self.joinedLines: points = [] for xy in row: points = points + [xy[0], xy[1]] if inFill: points = points + [inFillX1, inFillY, inFillX0, inFillY] filler = getattr(rowStyle, "filler", None) if filler: filler.fill(self, inFillG, rowNo, rowColor, points) else: inFillG.add(Polygon(points, fillColor=rowColor, strokeColor=rowColor, strokeWidth=0.1)) else: line = PolyLine(points, strokeColor=rowColor, strokeLineCap=0, strokeLineJoin=1) if width: line.strokeWidth = width if dash: line.strokeDashArray = dash g.add(line) if hasattr(rowStyle, "symbol"): uSymbol = rowStyle.symbol elif hasattr(self.lines, "symbol"): uSymbol = self.lines.symbol else: uSymbol = None if uSymbol: j = -1 if bubblePlot: drow = self.data[rowNo] for xy in row: j += 1 symbol = uSymbol2Symbol(uSymbol, xy[0], xy[1], rowColor) if symbol: if bubblePlot: symbol.size = bubbleR * (drow[j][2] / bubbleMax) ** 0.5 g.add(symbol) # Draw data labels. for colNo in range(len(row)): x1, y1 = row[colNo] self.drawLabel(g, rowNo, colNo, x1, y1) shader = getattr(rowStyle, "shader", None) if shader: shader.shade(self, g, rowNo, rowColor, row) return g def makeSwatchSample(self, rowNo, x, y, width, height): styleCount = len(self.lines) styleIdx = rowNo % styleCount rowColor = self.lines[styleIdx].strokeColor if self.joinedLines: dash = getattr(self.lines[styleIdx], "strokeDashArray", getattr(self.lines, "strokeDashArray", None)) strokeWidth = getattr( self.lines[styleIdx], "strokeWidth", getattr(self.lines[styleIdx], "strokeWidth", None) ) L = Line(x, y, x + width, y + height, strokeColor=rowColor, strokeLineCap=0) if strokeWidth: L.strokeWidth = strokeWidth if dash: L.strokeDashArray = dash else: L = None if hasattr(self.lines[styleIdx], "symbol"): S = self.lines[styleIdx].symbol elif hasattr(self.lines, "symbol"): S = self.lines.symbol else: S = None if S: S = uSymbol2Symbol(S, x + width / 2.0, y + height / 2.0, rowColor) if S and L: g = Group() g.add(S) g.add(L) return g return S or L def draw(self): yA = self.yValueAxis xA = self.xValueAxis if getattr(self, "_bubblePlot", None): yA._bubblePlot = xA._bubblePlot = 1 yA.setPosition(self.x, self.y, self.height) if yA: yA.joinAxis = xA if xA: xA.joinAxis = yA yA.configure(self.data) # if zero is in chart, put x axis there, otherwise use bottom. xAxisCrossesAt = yA.scale(0) if (xAxisCrossesAt > self.y + self.height) or (xAxisCrossesAt < self.y): y = self.y else: y = xAxisCrossesAt xA.setPosition(self.x, y, self.width) xA.configure(self.data) self.calcPositions() g = Group() g.add(self.makeBackground()) if self._inFill: self._inFillG = Group() g.add(self._inFillG) g.add(xA) g.add(yA) yA.gridStart = xA._x yA.gridEnd = xA._x + xA._length xA.gridStart = yA._y xA.gridEnd = yA._y + yA._length xA.makeGrid(g, parent=self) yA.makeGrid(g, parent=self) g.add(self.makeLines()) for a in getattr(self, "annotations", ()): g.add(a(self, xA.scale, yA.scale)) return g
def getPlot(): drawing = Drawing(400, 200) #temps = [((0.5,7), (1.5,1), (2.5,2), (3.5,1), (4.5,3), (5.5,5), (6.5, 10), (7.5,6))] temps = [getTemps()] bc = LinePlot() bc.x = 50 bc.y = 50 bc.height = 125 bc.width = 300 bc.data = temps #labels yilabel = Label() yilabel.setText("Temperatura (°C)") yilabel.angle = 90 yilabel.setOrigin(20,120) xlabel = Label() xlabel.setText("Días") xlabel.setOrigin(200,20) labelT = Label() labelT.setText("Temperatura") labelT.setOrigin(210,185) labelH = Label() labelH.setText("Humedad") labelH.setOrigin(285,185) bc.xValueAxis.valueMin = 0 bc.xValueAxis.valueMax = 20 bc.xValueAxis.valueSteps = [x for x in range(1,bc.xValueAxis.valueMax)] #bc.xValueAxis.labelTextFormat = '%2.1f' bc.yValueAxis.valueMin = 0 bc.yValueAxis.valueMax = 60 bc.yValueAxis.valueSteps = [0, 10, 20, 30, 40, 50, 60] drawing.add(bc) drawing.add(yilabel) drawing.add(xlabel) drawing.add(Line(170,185,185,185, strokeColor=colors.red)) drawing.add(Line(250,185,265,185, strokeColor=colors.blue)) drawing.add(labelT) drawing.add(labelH) #humedad=[[(0.5, 4), (1.5, 3), (2.5, 4), (3.5, 6), (4.5, 4), (5.5, 2), (6.5, 5), (7.5, 6)]] humedad = [getHumedad()] lp = LinePlot() lp.x = bc.x lp.y = bc.y lp.height = bc.height lp.width = bc.width lp.data = humedad ydlabel = Label() ydlabel.setText("Humedad (%)") ydlabel.angle = -90 ydlabel.setOrigin(lp.x+lp.width+30,lp.y+70) lp.joinedLines = 1 lp.lines[0].symbol = makeMarker('Circle') lp.lines[0].strokeColor=colors.blue lp.lineLabelFormat = '%2.0f' lp.xValueAxis.valueMin = 0 lp.xValueAxis.valueMax = bc.xValueAxis.valueMax lp.yValueAxis.valueMin = 0 lp.yValueAxis.valueMax = 100 lp.xValueAxis.visible=False lp.yValueAxis.visible=False #Hide 2nd plot its Yaxis drawing.add(lp) drawing.add(ydlabel) y2Axis = YValueAxis()#Replicate 2nd plot Yaxis in the right y2Axis.setProperties(lp.yValueAxis.getProperties()) y2Axis.setPosition(lp.x+lp.width,lp.y,lp.height) y2Axis.tickRight=5 y2Axis.tickLeft=0 y2Axis.labels.dx = 20 y2Axis.configure(humedad) y2Axis.visible=True drawing.add(y2Axis) return drawing
from reportlab.graphics.charts.axes import XCategoryAxis,YValueAxis drawing = Drawing(400, 200) data = [(10, 20, 30, 40), (15, 22, 37, 42)] xAxis = XCategoryAxis() xAxis.setPosition(75, 75, 300) xAxis.configure(data) xAxis.categoryNames = ['Beer', 'Wine', 'Meat', 'Cannelloni'] xAxis.labels.boxAnchor = 'n' xAxis.labels[3].dy = -15 xAxis.labels[3].angle = 30 xAxis.labels[3].fontName = 'Times-Bold' yAxis = YValueAxis() yAxis.setPosition(50, 50, 125) yAxis.configure(data) drawing.add(xAxis) drawing.add(yAxis) draw(drawing, 'Two isolated axes') disc("Here is the code that created them: ") eg(""" from reportlab.graphics import shapes from reportlab.graphics.charts.axes import XCategoryAxis,YValueAxis
def run(self): def weight_sort(a, b): return cmp(a.getWeight(), b.getWeight()) drawing = Drawing(600, 300) lc = LinePlot() # Determine axis dimensions and create data set maxval = 0 minval = 0 dimension_one_values = [] dimension_two_values = [] dimension_one_answeroptions_as_objects = [] dimension_two_answeroptions_as_objects = [] counter = 0 for question in self.getQuestions(): weights = [int(weight) for weight in question.getAnswerOptionsWeights()] answeroptions = list(question.getAnswerOptions()) # This is used by the legend. Sort on weight. if counter == 0: dimension_one_answeroptions_as_objects = question.getAnswerOptionsAsObjects() dimension_one_answeroptions_as_objects.sort(weight_sort) else: dimension_two_answeroptions_as_objects = question.getAnswerOptionsAsObjects() dimension_two_answeroptions_as_objects.sort(weight_sort) # Minmax lmin = min(weights) lmax = max(weights) if lmin < minval: minval = lmin if lmax > maxval: maxval = lmax # Data for user, answer in question.answers.items(): value = answer.get('value', None) weight = None if value is not None: # Lookup the integer weight of this answer if value in answeroptions: index = answeroptions.index(value) weight = weights[index] # Always add to the list. ReportLab deals with None. if counter == 0: dimension_one_values.append(weight) else: dimension_two_values.append(weight) counter += 1 # Set minmax absmax = max(abs(minval), abs(maxval)) * 1.1 lc.xValueAxis.valueMin = -absmax lc.xValueAxis.valueMax = absmax lc.yValueAxis.valueMin = -absmax lc.yValueAxis.valueMax = absmax # Zip to create data data = [zip(dimension_one_values, dimension_two_values)] if not len(data[0]): return lc.x = 0 lc.y = 0 # Misc setup lc.height = 300 lc.width = 300 lc.data = data lc.joinedLines = 0 lc.fillColor = None lc.lines[0].strokeColor = colors.red lc.lines[0].symbol = makeMarker('FilledCircle') # Add a grid grid = DoubleGrid() lc.background = grid setupGrid(lc) lc.background = None # Finetune the grid grid.grid0.strokeWidth = 0.2 grid.grid1.strokeWidth = 0.2 # Add to drawing else it overwrites the center Y axis drawing.add(grid) # Add a Y axis to pass through the origin yaxis = YValueAxis() yaxis.setPosition(lc.width/2, 0, lc.height) yaxis.configure([(0,-absmax),(0,absmax)]) yaxis.strokeColor = colors.blue drawing.add(yaxis) # Color X-Axis lc.xValueAxis.strokeColor = colors.green drawing.add(lc) # Legend for Dimension One drawing.add(String(lc.width+20, lc.height-12, 'Dimension One (X-Axis):', fontName='Helvetica', fontSize=12, fillColor=colors.green)) legend = Legend() legend.alignment = 'right' legend.x = lc.width + 20 legend.y = lc.height - 20 legend.fontName = 'Helvetica' legend.fontSize = 12 legend.columnMaximum = 7 items = [] for ob in dimension_one_answeroptions_as_objects: items.append( ( StringWidget(ob.getWeight()), ob() ) ) legend.colorNamePairs = items drawing.add(legend, 'legend1') # Legend for Dimension Two drawing.add(String(lc.width+20, lc.height/2-12, 'Dimension Two (Y-Axis):', fontName='Helvetica', fontSize=12, fillColor=colors.blue)) legend = Legend() legend.alignment = 'right' legend.x = lc.width + 20 legend.y = lc.height/2 - 20 legend.fontName = 'Helvetica' legend.fontSize = 12 legend.columnMaximum = 7 items = [] for ob in dimension_two_answeroptions_as_objects: items.append( ( StringWidget(ob.getWeight()), ob() ) ) legend.colorNamePairs = items drawing.add(legend, 'legend2') # Write out data = drawing.asString('png') request = self.REQUEST response = request.RESPONSE response.setHeader('Content-Type', 'image/png') response.setHeader('Content-Disposition','inline; filename=%s.png' % self.getId()) response.setHeader('Content-Length', len(data)) response.setHeader('Cache-Control', 's-maxage=0') return data
class LinePlot(AbstractLineChart): """Line plot with multiple lines. Both x- and y-axis are value axis (so there are no seperate X and Y versions of this class). """ _attrMap = AttrMap(BASE=PlotArea, reversePlotOrder = AttrMapValue(isBoolean, desc='If true reverse plot order.',advancedUsage=1), lineLabelNudge = AttrMapValue(isNumber, desc='Distance between a data point and its label.',advancedUsage=1), lineLabels = AttrMapValue(None, desc='Handle to the list of data point labels.'), lineLabelFormat = AttrMapValue(None, desc='Formatting string or function used for data point labels.'), lineLabelArray = AttrMapValue(None, desc='explicit array of line label values, must match size of data if present.'), joinedLines = AttrMapValue(isNumber, desc='Display data points joined with lines if true.'), strokeColor = AttrMapValue(isColorOrNone, desc='Color used for background border of plot area.'), fillColor = AttrMapValue(isColorOrNone, desc='Color used for background interior of plot area.'), lines = AttrMapValue(None, desc='Handle of the lines.'), xValueAxis = AttrMapValue(None, desc='Handle of the x axis.'), yValueAxis = AttrMapValue(None, desc='Handle of the y axis.'), data = AttrMapValue(None, desc='Data to be plotted, list of (lists of) x/y tuples.'), annotations = AttrMapValue(None, desc='list of callables, will be called with self, xscale, yscale.',advancedUsage=1), behindAxes = AttrMapValue(isBoolean, desc='If true use separate line group.',advancedUsage=1), gridFirst = AttrMapValue(isBoolean, desc='If true use draw grids before axes.',advancedUsage=1), ) def __init__(self): PlotArea.__init__(self) self.reversePlotOrder = 0 self.xValueAxis = XValueAxis() self.yValueAxis = YValueAxis() # this defines two series of 3 points. Just an example. self.data = [ ((1,1), (2,2), (2.5,1), (3,3), (4,5)), ((1,2), (2,3), (2.5,2), (3,4), (4,6)) ] self.lines = TypedPropertyCollection(LinePlotProperties) self.lines.strokeWidth = 1 self.lines[0].strokeColor = colors.red self.lines[1].strokeColor = colors.blue self.lineLabels = TypedPropertyCollection(Label) self.lineLabelFormat = None self.lineLabelArray = None # this says whether the origin is inside or outside # the bar - +10 means put the origin ten points # above the tip of the bar if value > 0, or ten # points inside if bar value < 0. This is different # to label dx/dy which are not dependent on the # sign of the data. self.lineLabelNudge = 10 # if you have multiple series, by default they butt # together. # New line chart attributes. self.joinedLines = 1 # Connect items with straight lines. #private attributes self._inFill = None self.annotations = [] self.behindAxes = 0 self.gridFirst = 0 def demo(self): """Shows basic use of a line chart.""" drawing = Drawing(400, 200) data = [ ((1,1), (2,2), (2.5,1), (3,3), (4,5)), ((1,2), (2,3), (2.5,2), (3.5,5), (4,6)) ] lp = LinePlot() lp.x = 50 lp.y = 50 lp.height = 125 lp.width = 300 lp.data = data lp.joinedLines = 1 lp.lineLabelFormat = '%2.0f' lp.strokeColor = colors.black lp.lines[0].strokeColor = colors.red lp.lines[0].symbol = makeMarker('FilledCircle') lp.lines[1].strokeColor = colors.blue lp.lines[1].symbol = makeMarker('FilledDiamond') lp.xValueAxis.valueMin = 0 lp.xValueAxis.valueMax = 5 lp.xValueAxis.valueStep = 1 lp.yValueAxis.valueMin = 0 lp.yValueAxis.valueMax = 7 lp.yValueAxis.valueStep = 1 drawing.add(lp) return drawing def calcPositions(self): """Works out where they go. Sets an attribute _positions which is a list of lists of (x, y) matching the data. """ self._seriesCount = len(self.data) self._rowLength = max(map(len,self.data)) self._positions = [] for rowNo in range(len(self.data)): line = [] for colNo in range(len(self.data[rowNo])): datum = self.data[rowNo][colNo] # x,y value if type(datum[0]) == type(''): x = self.xValueAxis.scale(mktime(mkTimeTuple(datum[0]))) else: x = self.xValueAxis.scale(datum[0]) y = self.yValueAxis.scale(datum[1]) line.append((x, y)) self._positions.append(line) def _innerDrawLabel(self, rowNo, colNo, x, y): "Draw a label for a given item in the list." labelFmt = self.lineLabelFormat labelValue = self.data[rowNo][colNo][1] ### if labelFmt is None: labelText = None elif type(labelFmt) is StringType: if labelFmt == 'values': labelText = self.lineLabelArray[rowNo][colNo] else: labelText = labelFmt % labelValue elif hasattr(labelFmt,'__call__'): labelText = labelFmt(labelValue) else: raise ValueError("Unknown formatter type %s, expected string or function"%labelFmt) if labelText: label = self.lineLabels[(rowNo, colNo)] if not label.visible: return #hack to make sure labels are outside the bar if y > 0: label.setOrigin(x, y + self.lineLabelNudge) else: label.setOrigin(x, y - self.lineLabelNudge) label.setText(labelText) else: label = None return label def drawLabel(self, G, rowNo, colNo, x, y): '''Draw a label for a given item in the list. G must have an add method''' G.add(self._innerDrawLabel(rowNo,colNo,x,y)) def makeLines(self): g = Group() bubblePlot = getattr(self,'_bubblePlot',None) if bubblePlot: yA = self.yValueAxis xA = self.xValueAxis bubbleR = min(yA._bubbleRadius,xA._bubbleRadius) bubbleMax = xA._bubbleMax labelFmt = self.lineLabelFormat P = range(len(self._positions)) if self.reversePlotOrder: P.reverse() inFill = getattr(self,'_inFill',None) styleCount = len(self.lines) if inFill or [rowNo for rowNo in P if getattr(self.lines[rowNo%styleCount],'inFill',False)]: inFillY = self.xValueAxis._y inFillX0 = self.yValueAxis._x inFillX1 = inFillX0 + self.xValueAxis._length inFillG = getattr(self,'_inFillG',g) lG = getattr(self,'_lineG',g) # Iterate over data rows. for rowNo in P: row = self._positions[rowNo] rowStyle = self.lines[rowNo % styleCount] rowColor = getattr(rowStyle,'strokeColor',None) dash = getattr(rowStyle, 'strokeDashArray', None) if hasattr(rowStyle, 'strokeWidth'): width = rowStyle.strokeWidth elif hasattr(self.lines, 'strokeWidth'): width = self.lines.strokeWidth else: width = None # Iterate over data columns. if self.joinedLines: points = [] for xy in row: points = points + [xy[0], xy[1]] if inFill or getattr(rowStyle,'inFill',False): fpoints = [inFillX0,inFillY] + points + [inFillX1,inFillY] filler = getattr(rowStyle, 'filler', None) if filler: filler.fill(self,inFillG,rowNo,rowColor,fpoints) else: inFillG.add(Polygon(fpoints,fillColor=rowColor,strokeColor=rowColor,strokeWidth=width or 0.1)) if inFill in (None,0,2): line = PolyLine(points,strokeColor=rowColor,strokeLineCap=0,strokeLineJoin=1) if width: line.strokeWidth = width if dash: line.strokeDashArray = dash lG.add(line) if hasattr(rowStyle, 'symbol'): uSymbol = rowStyle.symbol elif hasattr(self.lines, 'symbol'): uSymbol = self.lines.symbol else: uSymbol = None if uSymbol: j = -1 if bubblePlot: drow = self.data[rowNo] for xy in row: j += 1 symbol = uSymbol2Symbol(uSymbol,xy[0],xy[1],rowColor) if symbol: if bubblePlot: symbol.size = bubbleR*(drow[j][2]/bubbleMax)**0.5 g.add(symbol) # Draw data labels. for colNo in range(len(row)): x1, y1 = row[colNo] self.drawLabel(g, rowNo, colNo, x1, y1) shader = getattr(rowStyle, 'shader', None) if shader: shader.shade(self,g,rowNo,rowColor,row) return g def draw(self): yA = self.yValueAxis xA = self.xValueAxis if getattr(self,'_bubblePlot',None): yA._bubblePlot = xA._bubblePlot = 1 yA.setPosition(self.x, self.y, self.height) if yA: yA.joinAxis = xA if xA: xA.joinAxis = yA yA.configure(self.data) # if zero is in chart, put x axis there, otherwise use bottom. xAxisCrossesAt = yA.scale(0) if ((xAxisCrossesAt > self.y + self.height) or (xAxisCrossesAt < self.y)): y = self.y else: y = xAxisCrossesAt xA.setPosition(self.x, y, self.width) xA.configure(self.data) self.calcPositions() g = Group() g.add(self.makeBackground()) if self._inFill or self.behindAxes: xA._joinToAxis() if self._inFill: self._inFillG = Group() g.add(self._inFillG) if self.behindAxes: self._lineG = Group() g.add(self._lineG) if self.gridFirst: xA.makeGrid(g,parent=self,dim=yA.getGridDims) yA.makeGrid(g,parent=self,dim=xA.getGridDims) g.add(xA) g.add(yA) if not self.gridFirst: xAdgl = getattr(xA,'drawGridLast',False) yAdgl = getattr(yA,'drawGridLast',False) if not xAdgl: xA.makeGrid(g,parent=self,dim=yA.getGridDims) if not yAdgl: yA.makeGrid(g,parent=self,dim=xA.getGridDims) annotations = getattr(self,'annotations',[]) for a in annotations: if getattr(a,'beforeLines',None): g.add(a(self,xA.scale,yA.scale)) g.add(self.makeLines()) if not self.gridFirst: if xAdgl: xA.makeGrid(g,parent=self,dim=yA.getGridDims) if yAdgl: yA.makeGrid(g,parent=self,dim=xA.getGridDims) for a in annotations: if not getattr(a,'beforeLines',None): g.add(a(self,xA.scale,yA.scale)) return g def addCrossHair(self,name,xv,yv,strokeColor=colors.black,strokeWidth=1,beforeLines=True): from reportlab.graphics.shapes import Group, Line annotations = [a for a in getattr(self,'annotations',[]) if getattr(a,'name',None)!=name] def annotation(self,xScale,yScale): x = xScale(xv) y = yScale(yv) g = Group() xA = xScale.im_self #the x axis g.add(Line(xA._x,y,xA._x+xA._length,y,strokeColor=strokeColor,strokeWidth=strokeWidth)) yA = yScale.im_self #the y axis g.add(Line(x,yA._y,x,yA._y+yA._length,strokeColor=strokeColor,strokeWidth=strokeWidth)) return g annotation.beforeLines = beforeLines annotations.append(annotation) self.annotations = annotations
class LinePlot(AbstractLineChart): """Line plot with multiple lines. Both x- and y-axis are value axis (so there are no seperate X and Y versions of this class). """ _attrMap = AttrMap( BASE=PlotArea, reversePlotOrder=AttrMapValue(isBoolean, desc='If true reverse plot order.', advancedUsage=1), lineLabelNudge=AttrMapValue( isNumber, desc='Distance between a data point and its label.', advancedUsage=1), lineLabels=AttrMapValue( None, desc='Handle to the list of data point labels.'), lineLabelFormat=AttrMapValue( None, desc='Formatting string or function used for data point labels.'), lineLabelArray=AttrMapValue( None, desc= 'explicit array of line label values, must match size of data if present.' ), joinedLines=AttrMapValue( isNumber, desc='Display data points joined with lines if true.'), strokeColor=AttrMapValue( isColorOrNone, desc='Color used for background border of plot area.'), fillColor=AttrMapValue( isColorOrNone, desc='Color used for background interior of plot area.'), lines=AttrMapValue(None, desc='Handle of the lines.'), xValueAxis=AttrMapValue(None, desc='Handle of the x axis.'), yValueAxis=AttrMapValue(None, desc='Handle of the y axis.'), data=AttrMapValue( None, desc='Data to be plotted, list of (lists of) x/y tuples.'), annotations=AttrMapValue( None, desc='list of callables, will be called with self, xscale, yscale.', advancedUsage=1), behindAxes=AttrMapValue(isBoolean, desc='If true use separate line group.', advancedUsage=1), gridFirst=AttrMapValue(isBoolean, desc='If true use draw grids before axes.', advancedUsage=1), ) def __init__(self): PlotArea.__init__(self) self.reversePlotOrder = 0 self.xValueAxis = XValueAxis() self.yValueAxis = YValueAxis() # this defines two series of 3 points. Just an example. self.data = [((1, 1), (2, 2), (2.5, 1), (3, 3), (4, 5)), ((1, 2), (2, 3), (2.5, 2), (3, 4), (4, 6))] self.lines = TypedPropertyCollection(LinePlotProperties) self.lines.strokeWidth = 1 self.lines[0].strokeColor = colors.red self.lines[1].strokeColor = colors.blue self.lineLabels = TypedPropertyCollection(Label) self.lineLabelFormat = None self.lineLabelArray = None # this says whether the origin is inside or outside # the bar - +10 means put the origin ten points # above the tip of the bar if value > 0, or ten # points inside if bar value < 0. This is different # to label dx/dy which are not dependent on the # sign of the data. self.lineLabelNudge = 10 # if you have multiple series, by default they butt # together. # New line chart attributes. self.joinedLines = 1 # Connect items with straight lines. #private attributes self._inFill = None self.annotations = [] self.behindAxes = 0 self.gridFirst = 0 def demo(self): """Shows basic use of a line chart.""" drawing = Drawing(400, 200) data = [((1, 1), (2, 2), (2.5, 1), (3, 3), (4, 5)), ((1, 2), (2, 3), (2.5, 2), (3.5, 5), (4, 6))] lp = LinePlot() lp.x = 50 lp.y = 50 lp.height = 125 lp.width = 300 lp.data = data lp.joinedLines = 1 lp.lineLabelFormat = '%2.0f' lp.strokeColor = colors.black lp.lines[0].strokeColor = colors.red lp.lines[0].symbol = makeMarker('FilledCircle') lp.lines[1].strokeColor = colors.blue lp.lines[1].symbol = makeMarker('FilledDiamond') lp.xValueAxis.valueMin = 0 lp.xValueAxis.valueMax = 5 lp.xValueAxis.valueStep = 1 lp.yValueAxis.valueMin = 0 lp.yValueAxis.valueMax = 7 lp.yValueAxis.valueStep = 1 drawing.add(lp) return drawing def calcPositions(self): """Works out where they go. Sets an attribute _positions which is a list of lists of (x, y) matching the data. """ self._seriesCount = len(self.data) self._rowLength = max(map(len, self.data)) self._positions = [] for rowNo in range(len(self.data)): line = [] for colNo in range(len(self.data[rowNo])): datum = self.data[rowNo][colNo] # x,y value if type(datum[0]) == type(''): x = self.xValueAxis.scale(mktime(mkTimeTuple(datum[0]))) else: x = self.xValueAxis.scale(datum[0]) y = self.yValueAxis.scale(datum[1]) line.append((x, y)) self._positions.append(line) def _innerDrawLabel(self, rowNo, colNo, x, y): "Draw a label for a given item in the list." labelFmt = self.lineLabelFormat labelValue = self.data[rowNo][colNo][1] ### if labelFmt is None: labelText = None elif type(labelFmt) is StringType: if labelFmt == 'values': labelText = self.lineLabelArray[rowNo][colNo] else: labelText = labelFmt % labelValue elif hasattr(labelFmt, '__call__'): labelText = labelFmt(labelValue) else: raise ValueError( "Unknown formatter type %s, expected string or function" % labelFmt) if labelText: label = self.lineLabels[(rowNo, colNo)] if not label.visible: return #hack to make sure labels are outside the bar if y > 0: label.setOrigin(x, y + self.lineLabelNudge) else: label.setOrigin(x, y - self.lineLabelNudge) label.setText(labelText) else: label = None return label def drawLabel(self, G, rowNo, colNo, x, y): '''Draw a label for a given item in the list. G must have an add method''' G.add(self._innerDrawLabel(rowNo, colNo, x, y)) def makeLines(self): g = Group() bubblePlot = getattr(self, '_bubblePlot', None) if bubblePlot: yA = self.yValueAxis xA = self.xValueAxis bubbleR = min(yA._bubbleRadius, xA._bubbleRadius) bubbleMax = xA._bubbleMax labelFmt = self.lineLabelFormat P = range(len(self._positions)) if self.reversePlotOrder: P.reverse() inFill = getattr(self, '_inFill', None) styleCount = len(self.lines) if inFill or [ rowNo for rowNo in P if getattr(self.lines[rowNo % styleCount], 'inFill', False) ]: inFillY = self.xValueAxis._y inFillX0 = self.yValueAxis._x inFillX1 = inFillX0 + self.xValueAxis._length inFillG = getattr(self, '_inFillG', g) lG = getattr(self, '_lineG', g) # Iterate over data rows. for rowNo in P: row = self._positions[rowNo] rowStyle = self.lines[rowNo % styleCount] rowColor = getattr(rowStyle, 'strokeColor', None) dash = getattr(rowStyle, 'strokeDashArray', None) if hasattr(rowStyle, 'strokeWidth'): width = rowStyle.strokeWidth elif hasattr(self.lines, 'strokeWidth'): width = self.lines.strokeWidth else: width = None # Iterate over data columns. if self.joinedLines: points = [] for xy in row: points += [xy[0], xy[1]] if inFill or getattr(rowStyle, 'inFill', False): fpoints = [inFillX0, inFillY ] + points + [inFillX1, inFillY] filler = getattr(rowStyle, 'filler', None) if filler: filler.fill(self, inFillG, rowNo, rowColor, fpoints) else: inFillG.add( Polygon(fpoints, fillColor=rowColor, strokeColor=rowColor, strokeWidth=width or 0.1)) if inFill in (None, 0, 2): line = PolyLine(points, strokeColor=rowColor, strokeLineCap=0, strokeLineJoin=1) if width: line.strokeWidth = width if dash: line.strokeDashArray = dash lG.add(line) if hasattr(rowStyle, 'symbol'): uSymbol = rowStyle.symbol elif hasattr(self.lines, 'symbol'): uSymbol = self.lines.symbol else: uSymbol = None if uSymbol: j = -1 if bubblePlot: drow = self.data[rowNo] for xy in row: j += 1 symbol = uSymbol2Symbol(uSymbol, xy[0], xy[1], rowColor) if symbol: if bubblePlot: symbol.size = bubbleR * (drow[j][2] / bubbleMax)**0.5 g.add(symbol) # Draw data labels. for colNo in range(len(row)): x1, y1 = row[colNo] self.drawLabel(g, rowNo, colNo, x1, y1) shader = getattr(rowStyle, 'shader', None) if shader: shader.shade(self, g, rowNo, rowColor, row) return g def draw(self): yA = self.yValueAxis xA = self.xValueAxis if getattr(self, '_bubblePlot', None): yA._bubblePlot = xA._bubblePlot = 1 yA.setPosition(self.x, self.y, self.height) if yA: yA.joinAxis = xA if xA: xA.joinAxis = yA yA.configure(self.data) # if zero is in chart, put x axis there, otherwise use bottom. xAxisCrossesAt = yA.scale(0) if ((xAxisCrossesAt > self.y + self.height) or (xAxisCrossesAt < self.y)): y = self.y else: y = xAxisCrossesAt xA.setPosition(self.x, y, self.width) xA.configure(self.data) self.calcPositions() g = Group() g.add(self.makeBackground()) if self._inFill or self.behindAxes: xA._joinToAxis() if self._inFill: self._inFillG = Group() g.add(self._inFillG) if self.behindAxes: self._lineG = Group() g.add(self._lineG) if self.gridFirst: xA.makeGrid(g, parent=self, dim=yA.getGridDims) yA.makeGrid(g, parent=self, dim=xA.getGridDims) g.add(xA.draw()) g.add(yA.draw()) xAex = xA.visibleAxis and (xA._y, ) or () yAex = yA.visibleAxis and (yA._x, ) or () if not self.gridFirst: xAdgl = getattr(xA, 'drawGridLast', False) yAdgl = getattr(yA, 'drawGridLast', False) if not xAdgl: xA.makeGrid(g, parent=self, dim=yA.getGridDims, exclude=yAex) if not yAdgl: yA.makeGrid(g, parent=self, dim=xA.getGridDims, exclude=xAex) annotations = getattr(self, 'annotations', []) for a in annotations: if getattr(a, 'beforeLines', None): g.add(a(self, xA.scale, yA.scale)) g.add(self.makeLines()) if not self.gridFirst: if xAdgl: xA.makeGrid(g, parent=self, dim=yA.getGridDims, exclude=yAex) if yAdgl: yA.makeGrid(g, parent=self, dim=xA.getGridDims, exclude=xAex) for a in annotations: if not getattr(a, 'beforeLines', None): g.add(a(self, xA.scale, yA.scale)) return g def addCrossHair(self, name, xv, yv, strokeColor=colors.black, strokeWidth=1, beforeLines=True): from reportlab.graphics.shapes import Group, Line annotations = [ a for a in getattr(self, 'annotations', []) if getattr(a, 'name', None) != name ] def annotation(self, xScale, yScale): x = xScale(xv) y = yScale(yv) g = Group() xA = xScale.im_self #the x axis g.add( Line(xA._x, y, xA._x + xA._length, y, strokeColor=strokeColor, strokeWidth=strokeWidth)) yA = yScale.im_self #the y axis g.add( Line(x, yA._y, x, yA._y + yA._length, strokeColor=strokeColor, strokeWidth=strokeWidth)) return g annotation.beforeLines = beforeLines annotations.append(annotation) self.annotations = annotations
class HorizontalLineChart(LineChart): """Line chart with multiple lines. A line chart is assumed to have one category and one value axis. Despite its generic name this particular line chart class has a vertical value axis and a horizontal category one. It may evolve into individual horizontal and vertical variants (like with the existing bar charts). Available attributes are: x: x-position of lower-left chart origin y: y-position of lower-left chart origin width: chart width height: chart height useAbsolute: disables auto-scaling of chart elements (?) lineLabelNudge: distance of data labels to data points lineLabels: labels associated with data values lineLabelFormat: format string or callback function groupSpacing: space between categories joinedLines: enables drawing of lines strokeColor: color of chart lines (?) fillColor: color for chart background (?) lines: style list, used cyclically for data series valueAxis: value axis object categoryAxis: category axis object categoryNames: category names data: chart data, a list of data series of equal length """ _attrMap = AttrMap(BASE=LineChart, useAbsolute = AttrMapValue(isNumber, desc='Flag to use absolute spacing values.',advancedUsage=1), lineLabelNudge = AttrMapValue(isNumber, desc='Distance between a data point and its label.',advancedUsage=1), lineLabels = AttrMapValue(None, desc='Handle to the list of data point labels.'), lineLabelFormat = AttrMapValue(None, desc='Formatting string or function used for data point labels.'), lineLabelArray = AttrMapValue(None, desc='explicit array of line label values, must match size of data if present.'), groupSpacing = AttrMapValue(isNumber, desc='? - Likely to disappear.'), joinedLines = AttrMapValue(isNumber, desc='Display data points joined with lines if true.'), lines = AttrMapValue(None, desc='Handle of the lines.'), valueAxis = AttrMapValue(None, desc='Handle of the value axis.'), categoryAxis = AttrMapValue(None, desc='Handle of the category axis.'), categoryNames = AttrMapValue(isListOfStringsOrNone, desc='List of category names.'), data = AttrMapValue(None, desc='Data to be plotted, list of (lists of) numbers.'), inFill = AttrMapValue(isBoolean, desc='Whether infilling should be done.',advancedUsage=1), reversePlotOrder = AttrMapValue(isBoolean, desc='If true reverse plot order.',advancedUsage=1), annotations = AttrMapValue(None, desc='list of callables, will be called with self, xscale, yscale.',advancedUsage=1), ) def __init__(self): LineChart.__init__(self) # Allow for a bounding rectangle. self.strokeColor = None self.fillColor = None # Named so we have less recoding for the horizontal one :-) self.categoryAxis = XCategoryAxis() self.valueAxis = YValueAxis() # This defines two series of 3 points. Just an example. self.data = [(100,110,120,130), (70, 80, 80, 90)] self.categoryNames = ('North','South','East','West') self.lines = TypedPropertyCollection(LineChartProperties) self.lines.strokeWidth = 1 self.lines[0].strokeColor = colors.red self.lines[1].strokeColor = colors.green self.lines[2].strokeColor = colors.blue # control spacing. if useAbsolute = 1 then # the next parameters are in points; otherwise # they are 'proportions' and are normalized to # fit the available space. self.useAbsolute = 0 #- not done yet self.groupSpacing = 1 #5 self.lineLabels = TypedPropertyCollection(Label) self.lineLabelFormat = None self.lineLabelArray = None # This says whether the origin is above or below # the data point. +10 means put the origin ten points # above the data point if value > 0, or ten # points below if data value < 0. This is different # to label dx/dy which are not dependent on the # sign of the data. self.lineLabelNudge = 10 # If you have multiple series, by default they butt # together. # New line chart attributes. self.joinedLines = 1 # Connect items with straight lines. self.inFill = 0 self.reversePlotOrder = 0 def demo(self): """Shows basic use of a line chart.""" drawing = Drawing(200, 100) data = [ (13, 5, 20, 22, 37, 45, 19, 4), (14, 10, 21, 28, 38, 46, 25, 5) ] lc = HorizontalLineChart() lc.x = 20 lc.y = 10 lc.height = 85 lc.width = 170 lc.data = data lc.lines.symbol = makeMarker('Circle') drawing.add(lc) return drawing def calcPositions(self): """Works out where they go. Sets an attribute _positions which is a list of lists of (x, y) matching the data. """ self._seriesCount = len(self.data) self._rowLength = max(list(map(len,self.data))) if self.useAbsolute: # Dimensions are absolute. normFactor = 1.0 else: # Dimensions are normalized to fit. normWidth = self.groupSpacing availWidth = self.categoryAxis.scale(0)[1] normFactor = availWidth / normWidth self._normFactor = normFactor self._yzero = yzero = self.valueAxis.scale(0) self._hngs = hngs = 0.5 * self.groupSpacing * normFactor self._positions = [] for rowNo in range(len(self.data)): lineRow = [] for colNo in range(len(self.data[rowNo])): datum = self.data[rowNo][colNo] if datum is not None: (groupX, groupWidth) = self.categoryAxis.scale(colNo) x = groupX + hngs y = yzero height = self.valueAxis.scale(datum) - y lineRow.append((x, y+height)) self._positions.append(lineRow) def _innerDrawLabel(self, rowNo, colNo, x, y): "Draw a label for a given item in the list." labelFmt = self.lineLabelFormat labelValue = self.data[rowNo][colNo] if labelFmt is None: labelText = None elif type(labelFmt) is str: if labelFmt == 'values': try: labelText = self.lineLabelArray[rowNo][colNo] except: labelText = None else: labelText = labelFmt % labelValue elif hasattr(labelFmt,'__call__'): labelText = labelFmt(labelValue) else: raise ValueError("Unknown formatter type %s, expected string or function"%labelFmt) if labelText: label = self.lineLabels[(rowNo, colNo)] if not label.visible: return # Make sure labels are some distance off the data point. if y > 0: label.setOrigin(x, y + self.lineLabelNudge) else: label.setOrigin(x, y - self.lineLabelNudge) label.setText(labelText) else: label = None return label def drawLabel(self, G, rowNo, colNo, x, y): '''Draw a label for a given item in the list. G must have an add method''' G.add(self._innerDrawLabel(rowNo,colNo,x,y)) def makeLines(self): g = Group() labelFmt = self.lineLabelFormat P = list(range(len(self._positions))) if self.reversePlotOrder: P.reverse() inFill = self.inFill if inFill: inFillY = self.categoryAxis._y inFillX0 = self.valueAxis._x inFillX1 = inFillX0 + self.categoryAxis._length inFillG = getattr(self,'_inFillG',g) yzero = self._yzero # Iterate over data rows. for rowNo in P: row = self._positions[rowNo] styleCount = len(self.lines) styleIdx = rowNo % styleCount rowStyle = self.lines[styleIdx] rowColor = rowStyle.strokeColor dash = getattr(rowStyle, 'strokeDashArray', None) lineStyle = getattr(rowStyle,'lineStyle',None) if hasattr(rowStyle, 'strokeWidth'): strokeWidth = rowStyle.strokeWidth elif hasattr(self.lines, 'strokeWidth'): strokeWidth = self.lines.strokeWidth else: strokeWidth = None # Iterate over data columns. if lineStyle=='bar': barWidth = getattr(rowStyle,'barWidth',Percentage(50)) fillColor = getattr(rowStyle,'fillColor',rowColor) if isinstance(barWidth,Percentage): hbw = self._hngs*barWidth*0.01 else: hbw = barWidth*0.5 for colNo in range(len(row)): x,y = row[colNo] g.add(Rect(x-hbw,min(y,yzero),2*hbw,abs(y-yzero),strokeWidth=strokeWidth,strokeColor=rowColor,fillColor=fillColor)) elif self.joinedLines or lineStyle=='joinedLine': points = [] for colNo in range(len(row)): points += row[colNo] if inFill: points = points + [inFillX1,inFillY,inFillX0,inFillY] inFillG.add(Polygon(points,fillColor=rowColor,strokeColor=rowColor,strokeWidth=0.1)) else: line = PolyLine(points,strokeColor=rowColor,strokeLineCap=0,strokeLineJoin=1) if strokeWidth: line.strokeWidth = strokeWidth if dash: line.strokeDashArray = dash g.add(line) if hasattr(rowStyle, 'symbol'): uSymbol = rowStyle.symbol elif hasattr(self.lines, 'symbol'): uSymbol = self.lines.symbol else: uSymbol = None if uSymbol: for colNo in range(len(row)): x1, y1 = row[colNo] symbol = uSymbol2Symbol(uSymbol,x1,y1,rowStyle.strokeColor) if symbol: g.add(symbol) # Draw item labels. for colNo in range(len(row)): x1, y1 = row[colNo] self.drawLabel(g, rowNo, colNo, x1, y1) return g def draw(self): "Draws itself." vA, cA = self.valueAxis, self.categoryAxis vA.setPosition(self.x, self.y, self.height) if vA: vA.joinAxis = cA if cA: cA.joinAxis = vA vA.configure(self.data) # If zero is in chart, put x axis there, otherwise # use bottom. xAxisCrossesAt = vA.scale(0) if ((xAxisCrossesAt > self.y + self.height) or (xAxisCrossesAt < self.y)): y = self.y else: y = xAxisCrossesAt cA.setPosition(self.x, y, self.width) cA.configure(self.data) self.calcPositions() g = Group() g.add(self.makeBackground()) if self.inFill: self._inFillG = Group() g.add(self._inFillG) g.add(cA) g.add(vA) cAdgl = getattr(cA,'drawGridLast',False) vAdgl = getattr(vA,'drawGridLast',False) if not cAdgl: cA.makeGrid(g,parent=self,dim=vA.getGridDims) if not vAdgl: vA.makeGrid(g,parent=self,dim=cA.getGridDims) g.add(self.makeLines()) if cAdgl: cA.makeGrid(g,parent=self,dim=vA.getGridDims) if vAdgl: vA.makeGrid(g,parent=self,dim=cA.getGridDims) for a in getattr(self,'annotations',()): g.add(a(self,cA.scale,vA.scale)) return g
def generatePdfReport(request): # Create the HttpResponse object with the appropriate PDF headers. response = HttpResponse(content_type='application/pdf') response['Content-Disposition'] = 'attachment; filename="somefilename.pdf"' # Create the PDF object, using the response object as its "file." p = canvas.Canvas(response) # Draw things on the PDF. Here's where the PDF generation happens. # See the ReportLab documentation for the full list of functionality. Title="Test Report" PAGE_HEIGHT=defaultPageSize[1] PAGE_WIDTH=defaultPageSize[0] p.drawCentredString(PAGE_WIDTH/2.0, PAGE_HEIGHT-108, Title) p.drawString(100,900,"Test Configuration") configuration=request.session["test_configuration"] data=[['Operation','On/Off','Percentage','Keys','Size']] if 'readState' in configuration: readData=[['Read',configuration['readState'],configuration['readPercentage'],configuration['readKeys'],configuration['readSize']]] else: readData=[['Read','0','0','0','0']] data=data+readData if 'writeState' in configuration: writeData=[['Write',configuration['writeState'],configuration['writePercentage'],configuration['writeKeys'],configuration['writeSize']]] else: writeData=[['Write','0','0','0','0']] data=data+writeData if 'updateState' in configuration: updateData=[['Update',configuration['updateState'],configuration['updatePercentage'],configuration['updateKeys'],configuration['updateSize']]] else: updateData=[['Update','0','0','0','0']] data=data+updateData table = Table(data, colWidths=100, rowHeights=20) table.hAlign="CENTER" table.setStyle(TableStyle()) table.wrapOn(p, 300, 800) table.drawOn(p,80,550) p.drawString(80,700,"Test Configuration") p.drawString(80,500,"Test Time : ") p.drawString(200,500,str(t1.getTime()) + " miliseconds " ) p.drawString(80,400,"Test Chart") p.saveState() drawing = Drawing(10, 10) data = [(10, 20, 30, 40), (15, 22, 37, 42)] xAxis = XCategoryAxis() xAxis.setPosition(75, 75, 300) xAxis.configure(data) xAxis.categoryNames = ['Beer', 'Wine', 'Meat', 'Cannelloni'] xAxis.labels.boxAnchor = 'n' xAxis.labels[3].dy = -15 xAxis.labels[3].angle = 30 xAxis.labels[3].fontName = 'Times-Bold' yAxis = YValueAxis() yAxis.setPosition(50, 50, 125) yAxis.configure(data) drawing.add(xAxis) drawing.add(yAxis) # Close the PDF object cleanly, and we're done. p.showPage() p.save() return response