def sample5d(): "Sample drawing, xvalue/yvalue axes, y connected at left 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 = 'left' 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
def make_x_axis(self, x_label, x_min, x_max, x_step): self._add(self, Label(), name='XLabel', validate=None, desc="The label on the horizontal axis") self.XLabel.fontName = 'Helvetica' self.XLabel.fontSize = 10 self.XLabel.x = 22 self.XLabel.y = 45 self.XLabel.textAnchor = 'middle' self.XLabel.maxWidth = 500 self.XLabel.height = 20 self.XLabel._text = x_label self.chart.xValueAxis = XValueAxis() self.chart.xValueAxis.labels.boxAnchor = 'autox' self.chart.xValueAxis.valueMin = x_min self.chart.xValueAxis.valueMax = x_max self.chart.xValueAxis.valueStep = x_step self.chart.xValueAxis.labels.fontName = "Helvetica" self.chart.xValueAxis.labels.fontSize = 10 self.chart.xValueAxis.visibleTicks = 1 self.chart.xValueAxis.labels.rightPadding = 0 self.chart.xValueAxis.labels.dx = 1
def sample7b(): "Sample drawing, xvalue/ycat axes, y connected at left of x." drawing = Drawing(400, 200) data = [(10, 20, 30, 42)] xAxis = XValueAxis() xAxis._length = 300 xAxis.configure(data) yAxis = YCategoryAxis() yAxis.setPosition(50, 50, 125) yAxis.joinAxis = xAxis yAxis.joinAxisMode = 'left' yAxis.categoryNames = ['Beer', 'Wine', 'Meat', 'Cannelloni'] yAxis.labels.boxAnchor = 'e' yAxis.configure(data) drawing.add(xAxis) drawing.add(yAxis) return drawing
def makeTickLabels(self): g = XValueAxis.makeTickLabels(self) desc_text = make_desc_test(self, "X") if desc_text is not None: g.add(desc_text) return g
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 sample4b(): "Sample drawing, xvalue/yvalue axes, y connected at value 35 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 = 'value' xAxis.joinAxisPos = 35 xAxis.configure(data) drawing.add(xAxis) drawing.add(yAxis) return drawing
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
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
xAxis.valueSteps = [10, 15, 20, 30, 35, 40] xAxis.configure(data) xAxis.labels.boxAnchor = 'n' drawing.add(xAxis) """) from reportlab.graphics import shapes from reportlab.graphics.charts.axes import XValueAxis drawing = Drawing(400, 100) data = [(10, 20, 30, 40)] xAxis = XValueAxis() xAxis.setPosition(75, 50, 300) xAxis.valueSteps = [10, 15, 20, 30, 35, 40] xAxis.configure(data) xAxis.labels.boxAnchor = 'n' drawing.add(xAxis) draw(drawing, 'An axis with non-equidistant tick marks') disc(""" In addition to these properties, all axes classes have three properties describing how to join two of them to each other. Again, this is interesting only if you define your own charts or want to modify the appearance of an existing chart using
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
def drawLineChart(self, (names, start, end, data, title), reserved=None): w = PAGE_WIDTH - 2 * inch h = w * 0.6 drawing = Drawing(w, h) lp = LinePlot() lp.x = 0 lp.y = 0 lp.height = h - 30 lp.width = w lp.data = data lp.joinedLines = 1 lp.strokeColor = colors.black lp.xValueAxis = XValueAxis() lp.xValueAxis.valueMin = start lp.xValueAxis.valueMax = end lp.xValueAxis.valueSteps = [(start + i * (end - start) / 5) for i in range(6)] lp.xValueAxis.labelTextFormat = lambda seconds: time.strftime( "%m/%d %H:%M", time.localtime(seconds)) lp.xValueAxis.labels.angle = 35 lp.xValueAxis.labels.fontName = 'Helvetica' lp.xValueAxis.labels.fontSize = 7 lp.xValueAxis.labels.dy = -10 lp.xValueAxis.labels.boxAnchor = 'e' lp.yValueAxis.labelTextFormat = lambda value: '%d MB' % (int(value) / 1000) lp.yValueAxis.labels.fontName = 'Helvetica' lp.yValueAxis.labels.fontSize = 7
def __init__(self, desc=None): XValueAxis.__init__(self) self.desc = None if isString(desc) is True: self.desc = desc