#!/usr/bin/python # -*- coding: utf8 -*- # author: wly # date: 2015-05-04 # see license for license details __version__ = ''' ''' __doc__ = """ it contains the base element for pdf eImage is used to draw picture on the pdf document eDataTable is used to draw table on the pdf document eGraphicsTable is used to draw plot on the pdf document eParagraph is used to draw text on the pdf document """ from reportlab.platypus import Image, Table from reportlab.graphics.shapes import Drawing from reportlab.graphics.charts.lineplots import LinePlot from reportlab.graphics.charts.linecharts import HorizontalLineChart from reportlab.platypus.paragraph import Paragraph from reportlab.graphics.widgets.markers import makeMarker from reportlab.graphics.charts.legends import Legend from reportlab.graphics.charts.textlabels import Label from reportlab.graphics.charts.axes import XValueAxis from reportlab.graphics.shapes import Group from reportlab.graphics.charts.barcharts import VerticalBarChart from vstf.controller.reporters.report.pdf.styles import * class eImage(Image): """ an image(digital picture)which contains the function of auto zoom picture """ def __init__(self, filename, width=None, height=None, kind='direct', mask="auto", lazy=1, hAlign='CENTRE', vAlign='BOTTOM'): Image.__init__(self, filename, None, None, kind, mask, lazy) print height, width print self.drawHeight, self.drawWidth if self.drawWidth * height > self.drawHeight * width: self.drawHeight = width * self.drawHeight / self.drawWidth self.drawWidth = width else: self.drawWidth = height * self.drawWidth / self.drawHeight self.drawHeight = height self.hAlign = hAlign self.vAlign = vAlign print self.drawHeight, self.drawWidth class eTable(object): """ an abstract table class, which is contains the base functions to create table """ def __init__(self, data, style=TableStyle(name="default")): self._tablestyle = style self._table = [] self._spin = False self._colWidths = None self._data = self.analysisData(data) if self._data: self.create() def analysisData(self, data): raise NotImplementedError("abstract eTable") def create(self): self._table = Table(self._data, style=self._style, splitByRow=1) self._table.hAlign = self._tablestyle.table_hAlign self._table.vAlign = self._tablestyle.table_vAlign self._table.colWidths = self._tablestyle.table_colWidths if self._spin or self._colWidths: self._table.colWidths = self._colWidths self._table.rowHeights = self._tablestyle.table_rowHeights @property def table(self): return self._table class eCommonTable(eTable): def analysisData(self, data): self._style = [ ('ALIGN', (0, 0), (-1, -1), 'CENTER'), ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), ('GRID', (0, 0), (-1, -1), 0.5, colors.grey), ('BOX', (0, 0), (-1, -1), 1.2, colors.black) ] return data class eConfigTable(eTable): def analysisData(self, data): self._style = [ ('ALIGN', (0, 0), (-1, -1), 'CENTER'), ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), ('GRID', (0, 0), (-1, -1), 0.5, colors.grey), ('BOX', (0, 0), (-1, -1), 1, colors.black), ('SPAN', (2, 0), (3, 0)), ('SPAN', (2, 1), (3, 1)), ('SPAN', (2, 8), (3, 8)), ('SPAN', (2, 9), (3, 9)), ('SPAN', (2, 10), (3, 10)), ('SPAN', (0, 0), (0, 7)), ('SPAN', (0, 8), (0, 10)), ('SPAN', (0, 11), (0, 19)), ('SPAN', (1, 2), (1, 6)), ('SPAN', (1, 12), (1, 13)), ('SPAN', (1, 14), (1, 16)), ('SPAN', (1, 17), (1, 19)), ('SPAN', (2, 3), (2, 6)) ] return data class eSummaryTable(eTable): def analysisData(self, data): self._style = [ ('ALIGN', (0, 0), (-1, -1), 'CENTER'), ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), ('GRID', (0, 0), (-1, -1), 0.5, colors.grey), ('BOX', (0, 0), (-1, -1), 1, colors.black), ('SPAN', (0, 0), (0, 1)), ('SPAN', (1, 0), (4, 0)), ('SPAN', (5, 0), (-1, 0)) ] return data class eGitInfoTable(eTable): def analysisData(self, data): self._style = [ ('ALIGN', (0, 0), (-1, -1), 'CENTER'), ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), ('GRID', (0, 0), (-1, -1), 0.5, colors.grey), ('BOX', (0, 0), (-1, -1), 1, colors.black), ('SPAN', (0, 0), (0, 2)), ('SPAN', (0, 3), (0, 5)), ('SPAN', (0, 6), (0, 8)) ] return data class eScenarioTable(eTable): def analysisData(self, data): self._style = [ ('ALIGN', (0, 0), (-1, -1), 'CENTER'), ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), ('GRID', (0, 0), (-1, -1), 0.5, colors.grey), ('BOX', (0, 0), (-1, -1), 1, colors.black), ('ALIGN', (2, 1), (-1, -1), 'LEFT'), ('SPAN', (0, 1), (0, 6)), ('SPAN', (0, 7), (0, 12)), ('SPAN', (0, 13), (0, 16)), ('SPAN', (0, 17), (0, 20)) ] return data class eOptionsTable(eTable): def analysisData(self, data): self._style = [ ('ALIGN', (0, 0), (-1, -1), 'CENTER'), ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), ('GRID', (0, 0), (-1, -1), 0.5, colors.grey), ('BOX', (0, 0), (-1, -1), 1, colors.black), ('SPAN', (2, 0), (4, 0)), ('SPAN', (2, 1), (4, 1)), ('SPAN', (0, 0), (0, -1)), ('SPAN', (1, 2), (1, 16)), ('SPAN', (1, 17), (1, 19)), ('SPAN', (1, 20), (1, 22)), ('SPAN', (1, 23), (1, 24)), ('SPAN', (2, 2), (2, 4)), ('SPAN', (2, 5), (2, 12)), ('SPAN', (2, 13), (2, 16)), ('SPAN', (2, 17), (2, 19)), ('SPAN', (2, 20), (2, 22)), ('SPAN', (2, 23), (2, 24)) ] return data class eProfileTable(eTable): def analysisData(self, data): self._style = [ ('ALIGN', (0, 0), (-1, -1), 'CENTER'), ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), ('GRID', (0, 0), (-1, -1), 0.5, colors.grey), ('BOX', (0, 0), (-1, -1), 1, colors.black), ('SPAN', (0, 1), (0, -1)), ('SPAN', (1, 0), (2, 0)), ] return data class eDataTable(eTable): def analysisData(self, data): result = data self._style = [ ('ALIGN', (0, 0), (-1, -1), 'CENTER'), ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), ('LEADING', (0, 0), (-1, -1), 18), ('GRID', (0, 0), (-1, -1), 0.5, colors.grey), ('BOX', (0, 0), (-1, -1), 1, colors.black), ('LINEBEFORE', (1, 0), (1, -1), 0.8, colors.black), # ('LINEBEFORE', (3, 0), (3, -1), 1, colors.black), # ('LINEBEFORE', (5, 0), (5, -1), 1, colors.black), ('LINEBELOW', (0, 0), (-1, 0), 0.8, colors.black), # ('SPAN', (0, 0), (0, 1)), # ('SPAN', (1, 0), (2, 0)), # ('SPAN', (3, 0), (4, 0)) ] if self._spin is True: print "start spin" result = map(list, zip(*result)) style = [] for value in self._style: value = list(value) value[1] = (value[1][1], value[1][0]) value[2] = (value[2][1], value[2][0]) if value[0] == 'LINEBELOW': value[0] = 'LINEAFTER' elif value[0] == 'LINEBEFORE': value[0] = 'LINEABOVE' value = tuple(value) style.append(value) self._style = style return result class eGraphicsTable(eTable): def analysisData(self, data): self._style = [ ('ALIGN', (0, 0), (-1, -1), 'CENTER'), ('VALIGN', (0, 0), (-1, -1), 'MIDDLE') ] return data class noScaleXValueAxis(XValueAxis): def __init__(self): XValueAxis.__init__(self) def makeTickLabels(self): g = Group() if not self.visibleLabels: return g f = self._labelTextFormat # perhaps someone already set it if f is None: f = self.labelTextFormat or (self._allIntTicks() and '%.0f' or str) elif f is str and self._allIntTicks(): f = '%.0f' elif hasattr(f, 'calcPlaces'): f.calcPlaces(self._tickValues) post = self.labelTextPostFormat scl = self.labelTextScale pos = [self._x, self._y] d = self._dataIndex pos[1 - d] = self._labelAxisPos() labels = self.labels if self.skipEndL != 'none': if self.isXAxis: sk = self._x else: sk = self._y if self.skipEndL == 'start': sk = [sk] else: sk = [sk, sk + self._length] if self.skipEndL == 'end': del sk[0] else: sk = [] nticks = len(self._tickValues) nticks1 = nticks - 1 for i, tick in enumerate(self._tickValues): label = i - nticks if label in labels: label = labels[label] else: label = labels[i] if f and label.visible: v = self.scale(i) if sk: for skv in sk: if abs(skv - v) < 1e-6: v = None break if v is not None: if scl is not None: t = tick * scl else: t = tick if isinstance(f, str): txt = f % t elif isSeq(f): # it's a list, use as many items as we get if i < len(f): txt = f[i] else: txt = '' elif hasattr(f, '__call__'): if isinstance(f, TickLabeller): txt = f(self, t) else: txt = f(t) else: raise ValueError('Invalid labelTextFormat %s' % f) if post: txt = post % txt pos[d] = v label.setOrigin(*pos) label.setText(txt) # special property to ensure a label doesn't project beyond the bounds of an x-axis if self.keepTickLabelsInside: if isinstance(self, XValueAxis): # not done yet for y axes a_x = self._x if not i: # first one x0, y0, x1, y1 = label.getBounds() if x0 < a_x: label = label.clone(dx=label.dx + a_x - x0) if i == nticks1: # final one a_x1 = a_x + self._length x0, y0, x1, y1 = label.getBounds() if x1 > a_x1: label = label.clone(dx=label.dx - x1 + a_x1) g.add(label) return g def ___calcScaleFactor(self): """Calculate the axis' scale factor. This should be called only *after* the axis' range is set. Returns a number. """ self._scaleFactor = self._length / (len(self._tickValues) + 1) return self._scaleFactor def scale(self, value): """Converts a numeric value to a plotarea position. The chart first configures the axis, then asks it to """ assert self._configured, "Axis cannot scale numbers before it is configured" if value is None: value = 0 # this could be made more efficient by moving the definition of org and sf into the configuration org = (self._x, self._y)[self._dataIndex] sf = self._length / (len(self._tickValues) + 1) if self.reverseDirection: sf = -sf org += self._length return org + sf * (value + 1) class noScaleLinePlot(LinePlot): def __init__(self): LinePlot.__init__(self) self.xValueAxis = noScaleXValueAxis() 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 = [] len_row = len(self.data[rowNo]) for colNo in range(len_row): datum = self.data[rowNo][colNo] # x, y value x = self.x + self.width / (len_row + 1) * (colNo + 1) self.xValueAxis.labels[colNo].x = self.x + self.width / (len_row + 1) * (colNo + 1) y = self.yValueAxis.scale(datum[1]) # print self.width, " ", x line.append((x, y)) self._positions.append(line) # def _innerDrawLabel(self, rowNo, colNo, x, y): # return None class eLinePlot(object): def __init__(self, data, style): self._lpstyle = style self._linename = data[0] self._data = self.analysisData(data[1:]) if self._data: self.create() @property def draw(self): return self._draw def analysisData(self, data): columns = len(data) # print data data = map(list, zip(*data)) rows = len(data) for i in range(rows): for j in range(columns): data[i][j] = float(data[i][j]) self._linename = self._linename[1:] """ delcnt = 0 delrows = [] for i in range(columns): delrows.append(0.0) del_line = [self._linename[0]] for i in range(rows): for j in range(columns): data[i][j] = float(data[i][j]) if data[i] == delrows: delcnt += 1 del_line.append(self._linename[i]) for i in range(delcnt): data.remove(delrows) for name in del_line: self._linename.remove(name) rows = len(data) """ # print rows # print data xvalueSteps = data[0] xvalueMin = data[0][0] xvalueMax = data[0][0] yvalueMin = data[1][0] yvalueMax = data[1][0] yvalueSteps = [] result = [] for j in range(columns): if xvalueMin > data[0][j]: xvalueMin = data[0][j] if xvalueMax < data[0][j]: xvalueMax = data[0][j] for i in range(rows - 1): lst = [] for j in range(columns): lst.append((data[0][j], data[i + 1][j])) if yvalueMin > data[i + 1][j]: yvalueMin = data[i + 1][j] if yvalueMax < data[i + 1][j]: yvalueMax = data[i + 1][j] yvalueSteps.append(int(data[i + 1][j] * 2.5) / 2.5) result.append(tuple(lst)) xvalueMin = int(xvalueMin) / 100 * 100 xvalueMax = int(xvalueMax) / 100 * 100 + 200 yvalueMin = int(yvalueMin) * 1.0 - 1 if yvalueMin < 0: yvalueMin = 0.0 yvalueMax = int(yvalueMax) + 2.0 yvalueSteps.append(yvalueMin) yvalueSteps.append(yvalueMax) yvalueSteps = {}.fromkeys(yvalueSteps).keys() self._xvalue = (xvalueMin, xvalueMax, xvalueSteps) self._yvalue = (yvalueMin, yvalueMax, yvalueSteps) print result return result def create(self): lpw = self._lpstyle.width lph = self._lpstyle.height draw = Drawing(lpw, lph) line_cnts = len(self._linename) # lp = noScaleLinePlot() lp = LinePlot() lg_line = (line_cnts + 3) / 4 lp.x = self._lpstyle.left lp.y = self._lpstyle.bottom lp.height = lph - self._lpstyle.bottom * (lg_line + 1.5) lp.width = lpw - lp.x * 2 lp.data = self._data lp.joinedLines = 1 lp.strokeWidth = self._lpstyle.strokeWidth line_cnts = len(self._data) sytle_cnts = len(self._lpstyle.linestyle) color_paris = [] for i in range(line_cnts): styleIndex = i % sytle_cnts lp.lines[i].strokeColor = self._lpstyle.linestyle[styleIndex][0] lp.lines[i].symbol = makeMarker(self._lpstyle.linestyle[styleIndex][1]) lp.lines[i].strokeWidth = self._lpstyle.linestyle[styleIndex][2] color_paris.append((self._lpstyle.linestyle[styleIndex][0], self._linename[i])) # lp.lineLabels[i].strokeColor = self._lpstyle.linestyle[styleIndex][0] lp.lineLabelFormat = self._lpstyle.format[0] lp.strokeColor = self._lpstyle.strokeColor lp.xValueAxis.valueMin, lp.xValueAxis.valueMax, lp.xValueAxis.valueSteps = self._xvalue # valueMin, valueMax, xvalueSteps = self._xvalue # lp.xValueAxis.valueStep = (lp.xValueAxis.valueMax - lp.xValueAxis.valueMin)/len(xvalueSteps) # lp.xValueAxis.valueSteps = map(lambda x: str(x), xvalueSteps) lp.yValueAxis.valueMin, lp.yValueAxis.valueMax, lp.yValueAxis.valueSteps = self._yvalue # lp.xValueAxis.forceZero = 0 # lp.xValueAxis.avoidBoundFrac = 1 # lp.xValueAxis.tickDown = 3 # lp.xValueAxis.visibleGrid = 1 # lp.xValueAxis.categoryNames = '64 256 512 1400 1500 4096'.split(' ') lp.xValueAxis.labelTextFormat = self._lpstyle.format[1] lp.yValueAxis.labelTextFormat = self._lpstyle.format[2] delsize = int(lp.xValueAxis.valueMax / 2000) lp.xValueAxis.labels.fontSize = self._lpstyle.labelsfont lp.xValueAxis.labels.angle = 25 lp.yValueAxis.labels.fontSize = self._lpstyle.labelsfont lp.lineLabels.fontSize = self._lpstyle.labelsfont - delsize draw.add(lp) lg = Legend() lg.colorNamePairs = color_paris lg.fontName = 'Helvetica' lg.fontSize = 7 lg.x = self._lpstyle.left * 3 lg.y = self._lpstyle.bottom * (1 + lg_line) + lp.height lg.dxTextSpace = 5 lg.dy = 5 lg.dx = 20 lg.deltax = 60 lg.deltay = 0 lg.columnMaximum = 1 lg.alignment = 'right' draw.add(lg) self._draw = draw class eHorizontalLineChart(object): def __init__(self, data, style): self._lcstyle = style if len(data) < 1: return self._linename = data[0] self._data = self.analysisData(data[1:]) if self._data: self.create() @property def draw(self): return self._draw def analysisData(self, data): columns = len(data) data = map(list, zip(*data)) self._catNames = data[0] self._linename = self._linename[1:] data = data[1:] rows = len(data) yvalueMin = float(data[0][0]) yvalueMax = float(data[0][0]) yvalueSteps = [] result = [] for rowNo in range(rows): for columnNo in range(columns): data[rowNo][columnNo] = float(data[rowNo][columnNo]) if yvalueMin > data[rowNo][columnNo]: yvalueMin = data[rowNo][columnNo] if yvalueMax < data[rowNo][columnNo]: yvalueMax = data[rowNo][columnNo] yvalueSteps.append(int(data[rowNo][columnNo] * 1.0) / 1.0) result.append(tuple(data[rowNo])) yvalueMin = int(yvalueMin) * 1.0 - 1 if yvalueMin < 0: yvalueMin = 0.0 yvalueMax = int(yvalueMax) + 2.0 yvalueSteps.append(yvalueMin) yvalueSteps.append(yvalueMax) yvalueSteps = {}.fromkeys(yvalueSteps).keys() self._value = (yvalueMin, yvalueMax, yvalueSteps) print result return result def create(self): dw = self._lcstyle.width dh = self._lcstyle.height draw = Drawing(dw, dh) lc = HorizontalLineChart() line_cnts = len(self._linename) lg_line = (line_cnts + 3) / 4 lc.height = dh - self._lcstyle.bottom * (lg_line + 1.5) lc.width = dw - lc.x * 2 lc.x = self._lcstyle.left lc.y = self._lcstyle.bottom lc.data = self._data lc.strokeColor = self._lcstyle.strokeColor lc.strokeWidth = self._lcstyle.strokeWidth lc.useAbsolute = 1 lc.groupSpacing = lc.width * 2.0 / len(self._catNames) lc.joinedLines = 1 lc.lineLabelFormat = self._lcstyle.format[0] lc.valueAxis.valueMin, lc.valueAxis.valueMax, lc.valueAxis.valueSteps = self._value lc.valueAxis.labelTextFormat = self._lcstyle.format[1] lc.valueAxis.labels.fontSize = self._lcstyle.labelsfont lc.categoryAxis.categoryNames = self._catNames lc.categoryAxis.labels.boxAnchor = 'ne' lc.categoryAxis.labels.dx = lc.width / 2.0 / len(self._catNames) lc.categoryAxis.labels.dy = -6 lc.categoryAxis.labels.angle = 10 lc.categoryAxis.labels.fontSize = self._lcstyle.labelsfont # lc.categoryAxis.visibleGrid = 1 # lc.categoryAxis.tickUp = 100 # lc.categoryAxis.tickDown = 50 # lc.categoryAxis.gridEnd = dh sytle_cnts = len(self._lcstyle.linestyle) color_paris = [] for i in range(line_cnts): styleIndex = i % sytle_cnts lc.lines[i].strokeColor = self._lcstyle.linestyle[styleIndex][0] lc.lines[i].symbol = makeMarker(self._lcstyle.linestyle[styleIndex][1]) lc.lines[i].strokeWidth = self._lcstyle.linestyle[styleIndex][2] color_paris.append((self._lcstyle.linestyle[styleIndex][0], self._linename[i])) lc.lineLabels.fontSize = self._lcstyle.labelsfont - 2 draw.add(lc) lg = Legend() lg.colorNamePairs = color_paris lg.fontName = 'Helvetica' lg.fontSize = 7 # lg.x = dw /2 # lg.y = self._lcstyle.bottom *(1.5 + lg_line) lg.x = self._lcstyle.left * 3 lg.y = self._lcstyle.bottom * (1 + lg_line) + lc.height lg.dxTextSpace = 5 lg.dy = 5 lg.dx = 20 lg.deltax = 60 lg.deltay = 0 lg.columnMaximum = 1 lg.alignment = 'right' draw.add(lg) self._draw = draw class eBarChartColumn(object): def __init__(self, data, style): self._bcstyle = style if len(data) < 4: return self._data = self.analysisData(data) if self._data: self.create() @property def draw(self): return self._draw def analysisData(self, data): self._ytitle = data[0] self._name = data[1] self._bar = data[2] bar_data = data[3] result = [] for bar in bar_data: bar = map(lambda x: float(x), bar) result.append(tuple(bar)) return result def create(self): dw = self._bcstyle.width dh = self._bcstyle.height draw = Drawing(dw, dh) bc = VerticalBarChart() bar_cnt = len(self._bar) lg_line = (bar_cnt + 3) / 4 bc.width = dw - self._bcstyle.left - self._bcstyle.right bc.height = dh - self._bcstyle.top - self._bcstyle.bottom if bar_cnt > 1: bc.height -= lg_line * 15 bc.x = self._bcstyle.left bc.y = self._bcstyle.bottom color_paris = [] for i in range(bar_cnt): bc.bars[i].fillColor = self._bcstyle.pillarstyle[self._bar[i]][0] color_paris.append((self._bcstyle.pillarstyle[self._bar[i]][0], self._bar[i])) bc.fillColor = self._bcstyle.background bc.barLabels.fontName = 'Helvetica' bc.barLabelFormat = self._bcstyle.pillarstyle[self._bar[0]][1] bc.barLabels.fontSize = self._bcstyle.labelsfont bc.barLabels.dy = self._bcstyle.labelsfont bc.valueAxis.labels.fontName = 'Helvetica' bc.valueAxis.labels.fontSize = self._bcstyle.labelsfont bc.valueAxis.forceZero = 1 bc.valueAxis.valueMin = 0 bc.data = self._data bc.barSpacing = self._bcstyle.barSpacing bc.groupSpacing = self._bcstyle.groupSpacing / bar_cnt bc.valueAxis.avoidBoundFrac = 1 bc.valueAxis.gridEnd = dw - self._bcstyle.right bc.valueAxis.tickLeft = self._bcstyle.tick bc.valueAxis.visibleGrid = 1 bc.categoryAxis.categoryNames = self._name bc.categoryAxis.tickDown = self._bcstyle.tick bc.categoryAxis.labels.fontName = 'Helvetica' bc.categoryAxis.labels.fontSize = self._bcstyle.labelsfont bc.categoryAxis.labels.dy = -27 bc.categoryAxis.labels.angle = -90 draw.add(bc) lb = Label() lb.fontName = 'Helvetica' lb.fontSize = 7 lb.x = 12 lb.y = 80 lb.angle = 90 lb.textAnchor = 'middle' lb.maxWidth = 100 lb.height = 20 lb._text = self._ytitle draw.add(lb) if bar_cnt > 1: lg = Legend() lg.colorNamePairs = color_paris lg.fontName = 'Helvetica' lg.fontSize = 7 lg.x = self._bcstyle.left + bc.width / (bar_cnt + 1) lg.y = dh - self._bcstyle.top - lg_line * 5 lg.dxTextSpace = 5 lg.dy = 5 lg.dx = 25 lg.deltax = 80 lg.deltay = 0 lg.columnMaximum = 1 lg.alignment = 'right' draw.add(lg) self._draw = draw class eParagraph(object): def __init__(self, data, style): self._pstyle = style self._data = self.analysisData(data) self.create() def analysisData(self, data): result = "" for dstr in data: if self._pstyle.name == 'ps_body': # dstr = "" + dstr + "
" dstr = dstr + "
" else: dstr = dstr + "
" result += dstr return result def create(self): self._para = Paragraph(self._data, self._pstyle) @property def para(self): return self._para