1 ##############################################################################
2 # Copyright (c) 2015 Huawei Technologies Co.,Ltd and others.
4 # All rights reserved. This program and the accompanying materials
5 # are made available under the terms of the Apache License, Version 2.0
6 # which accompanies this distribution, and is available at
7 # http://www.apache.org/licenses/LICENSE-2.0
8 ##############################################################################
12 it contains the base element for pdf
13 eImage is used to draw picture on the pdf document
14 eDataTable is used to draw table on the pdf document
15 eGraphicsTable is used to draw plot on the pdf document
16 eParagraph is used to draw text on the pdf document
18 from reportlab.platypus import Image, Table
19 from reportlab.graphics.shapes import Drawing
20 from reportlab.graphics.charts.lineplots import LinePlot
21 from reportlab.graphics.charts.linecharts import HorizontalLineChart
22 from reportlab.platypus.paragraph import Paragraph
23 from reportlab.graphics.widgets.markers import makeMarker
24 from reportlab.graphics.charts.legends import Legend
25 from reportlab.graphics.charts.textlabels import Label
26 from reportlab.graphics.charts.axes import XValueAxis
27 from reportlab.graphics.shapes import Group
28 from reportlab.graphics.charts.barcharts import VerticalBarChart
29 from vstf.controller.reporters.report.pdf.styles import *
33 """ an image(digital picture)which contains the function of auto zoom picture """
35 def __init__(self, filename, width=None, height=None, kind='direct', mask="auto", lazy=1, hAlign='CENTRE',
37 Image.__init__(self, filename, None, None, kind, mask, lazy)
39 print self.drawHeight, self.drawWidth
40 if self.drawWidth * height > self.drawHeight * width:
41 self.drawHeight = width * self.drawHeight / self.drawWidth
42 self.drawWidth = width
44 self.drawWidth = height * self.drawWidth / self.drawHeight
45 self.drawHeight = height
48 print self.drawHeight, self.drawWidth
52 """ an abstract table class, which is contains the base functions to create table """
54 def __init__(self, data, style=TableStyle(name="default")):
55 self._tablestyle = style
58 self._colWidths = None
59 self._data = self.analysisData(data)
63 def analysisData(self, data):
64 raise NotImplementedError("abstract eTable")
67 self._table = Table(self._data, style=self._style, splitByRow=1)
68 self._table.hAlign = self._tablestyle.table_hAlign
69 self._table.vAlign = self._tablestyle.table_vAlign
70 self._table.colWidths = self._tablestyle.table_colWidths
71 if self._spin or self._colWidths:
72 self._table.colWidths = self._colWidths
73 self._table.rowHeights = self._tablestyle.table_rowHeights
80 class eCommonTable(eTable):
81 def analysisData(self, data):
83 ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
84 ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
85 ('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
86 ('BOX', (0, 0), (-1, -1), 1.2, colors.black)
91 class eConfigTable(eTable):
92 def analysisData(self, data):
94 ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
95 ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
96 ('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
97 ('BOX', (0, 0), (-1, -1), 1, colors.black),
98 ('SPAN', (2, 0), (3, 0)),
99 ('SPAN', (2, 1), (3, 1)),
100 ('SPAN', (2, 8), (3, 8)),
101 ('SPAN', (2, 9), (3, 9)),
102 ('SPAN', (2, 10), (3, 10)),
103 ('SPAN', (0, 0), (0, 7)),
104 ('SPAN', (0, 8), (0, 10)),
105 ('SPAN', (0, 11), (0, 19)),
106 ('SPAN', (1, 2), (1, 6)),
107 ('SPAN', (1, 12), (1, 13)),
108 ('SPAN', (1, 14), (1, 16)),
109 ('SPAN', (1, 17), (1, 19)),
110 ('SPAN', (2, 3), (2, 6))
115 class eSummaryTable(eTable):
116 def analysisData(self, data):
118 ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
119 ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
120 ('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
121 ('BOX', (0, 0), (-1, -1), 1, colors.black),
122 ('SPAN', (0, 0), (0, 1)),
123 ('SPAN', (1, 0), (4, 0)),
124 ('SPAN', (5, 0), (-1, 0))
129 class eGitInfoTable(eTable):
130 def analysisData(self, data):
132 ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
133 ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
134 ('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
135 ('BOX', (0, 0), (-1, -1), 1, colors.black),
136 ('SPAN', (0, 0), (0, 2)),
137 ('SPAN', (0, 3), (0, 5)),
138 ('SPAN', (0, 6), (0, 8))
143 class eScenarioTable(eTable):
144 def analysisData(self, data):
146 ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
147 ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
148 ('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
149 ('BOX', (0, 0), (-1, -1), 1, colors.black),
150 ('ALIGN', (2, 1), (-1, -1), 'LEFT'),
151 ('SPAN', (0, 1), (0, 6)),
152 ('SPAN', (0, 7), (0, 12)),
153 ('SPAN', (0, 13), (0, 16)),
154 ('SPAN', (0, 17), (0, 20))
159 class eOptionsTable(eTable):
160 def analysisData(self, data):
162 ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
163 ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
164 ('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
165 ('BOX', (0, 0), (-1, -1), 1, colors.black),
166 ('SPAN', (2, 0), (4, 0)),
167 ('SPAN', (2, 1), (4, 1)),
168 ('SPAN', (0, 0), (0, -1)),
169 ('SPAN', (1, 2), (1, 16)),
170 ('SPAN', (1, 17), (1, 19)),
171 ('SPAN', (1, 20), (1, 22)),
172 ('SPAN', (1, 23), (1, 24)),
173 ('SPAN', (2, 2), (2, 4)),
174 ('SPAN', (2, 5), (2, 12)),
175 ('SPAN', (2, 13), (2, 16)),
176 ('SPAN', (2, 17), (2, 19)),
177 ('SPAN', (2, 20), (2, 22)),
178 ('SPAN', (2, 23), (2, 24))
183 class eProfileTable(eTable):
184 def analysisData(self, data):
186 ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
187 ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
188 ('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
189 ('BOX', (0, 0), (-1, -1), 1, colors.black),
190 ('SPAN', (0, 1), (0, -1)),
191 ('SPAN', (1, 0), (2, 0)),
196 class eDataTable(eTable):
197 def analysisData(self, data):
200 ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
201 ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
202 ('LEADING', (0, 0), (-1, -1), 18),
203 ('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
204 ('BOX', (0, 0), (-1, -1), 1, colors.black),
205 ('LINEBEFORE', (1, 0), (1, -1), 0.8, colors.black),
206 # ('LINEBEFORE', (3, 0), (3, -1), 1, colors.black),
207 # ('LINEBEFORE', (5, 0), (5, -1), 1, colors.black),
208 ('LINEBELOW', (0, 0), (-1, 0), 0.8, colors.black),
209 # ('SPAN', (0, 0), (0, 1)),
210 # ('SPAN', (1, 0), (2, 0)),
211 # ('SPAN', (3, 0), (4, 0))
213 if self._spin is True:
215 result = map(list, zip(*result))
217 for value in self._style:
219 value[1] = (value[1][1], value[1][0])
220 value[2] = (value[2][1], value[2][0])
221 if value[0] == 'LINEBELOW':
222 value[0] = 'LINEAFTER'
223 elif value[0] == 'LINEBEFORE':
224 value[0] = 'LINEABOVE'
231 class eGraphicsTable(eTable):
232 def analysisData(self, data):
234 ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
235 ('VALIGN', (0, 0), (-1, -1), 'MIDDLE')
240 class noScaleXValueAxis(XValueAxis):
242 XValueAxis.__init__(self)
244 def makeTickLabels(self):
246 if not self.visibleLabels: return g
248 f = self._labelTextFormat # perhaps someone already set it
250 f = self.labelTextFormat or (self._allIntTicks() and '%.0f' or str)
251 elif f is str and self._allIntTicks():
253 elif hasattr(f, 'calcPlaces'):
254 f.calcPlaces(self._tickValues)
255 post = self.labelTextPostFormat
256 scl = self.labelTextScale
257 pos = [self._x, self._y]
259 pos[1 - d] = self._labelAxisPos()
261 if self.skipEndL != 'none':
266 if self.skipEndL == 'start':
269 sk = [sk, sk + self._length]
270 if self.skipEndL == 'end':
275 nticks = len(self._tickValues)
277 for i, tick in enumerate(self._tickValues):
280 label = labels[label]
283 if f and label.visible:
287 if abs(skv - v) < 1e-6:
295 if isinstance(f, str):
298 # it's a list, use as many items as we get
303 elif hasattr(f, '__call__'):
304 if isinstance(f, TickLabeller):
309 raise ValueError('Invalid labelTextFormat %s' % f)
310 if post: txt = post % txt
312 label.setOrigin(*pos)
315 # special property to ensure a label doesn't project beyond the bounds of an x-axis
316 if self.keepTickLabelsInside:
317 if isinstance(self, XValueAxis): # not done yet for y axes
319 if not i: # first one
320 x0, y0, x1, y1 = label.getBounds()
322 label = label.clone(dx=label.dx + a_x - x0)
323 if i == nticks1: # final one
324 a_x1 = a_x + self._length
325 x0, y0, x1, y1 = label.getBounds()
327 label = label.clone(dx=label.dx - x1 + a_x1)
332 def ___calcScaleFactor(self):
333 """Calculate the axis' scale factor.
334 This should be called only *after* the axis' range is set.
337 self._scaleFactor = self._length / (len(self._tickValues) + 1)
338 return self._scaleFactor
340 def scale(self, value):
341 """Converts a numeric value to a plotarea position.
342 The chart first configures the axis, then asks it to
344 assert self._configured, "Axis cannot scale numbers before it is configured"
345 if value is None: value = 0
346 # this could be made more efficient by moving the definition of org and sf into the configuration
347 org = (self._x, self._y)[self._dataIndex]
348 sf = self._length / (len(self._tickValues) + 1)
349 if self.reverseDirection:
352 return org + sf * (value + 1)
355 class noScaleLinePlot(LinePlot):
357 LinePlot.__init__(self)
358 self.xValueAxis = noScaleXValueAxis()
360 def calcPositions(self):
361 """Works out where they go.
363 Sets an attribute _positions which is a list of
364 lists of (x, y) matching the data.
366 self._seriesCount = len(self.data)
367 self._rowLength = max(map(len, self.data))
370 for rowNo in range(len(self.data)):
372 len_row = len(self.data[rowNo])
373 for colNo in range(len_row):
374 datum = self.data[rowNo][colNo] # x, y value
375 x = self.x + self.width / (len_row + 1) * (colNo + 1)
376 self.xValueAxis.labels[colNo].x = self.x + self.width / (len_row + 1) * (colNo + 1)
377 y = self.yValueAxis.scale(datum[1])
378 # print self.width, " ", x
380 self._positions.append(line)
383 # def _innerDrawLabel(self, rowNo, colNo, x, y):
385 class eLinePlot(object):
386 def __init__(self, data, style):
387 self._lpstyle = style
388 self._linename = data[0]
389 self._data = self.analysisData(data[1:])
397 def analysisData(self, data):
400 data = map(list, zip(*data))
403 for i in range(rows):
404 for j in range(columns):
405 data[i][j] = float(data[i][j])
406 self._linename = self._linename[1:]
410 for i in range(columns):
412 del_line = [self._linename[0]]
413 for i in range(rows):
414 for j in range(columns):
415 data[i][j] = float(data[i][j])
416 if data[i] == delrows:
418 del_line.append(self._linename[i])
419 for i in range(delcnt):
421 for name in del_line:
422 self._linename.remove(name)
428 xvalueSteps = data[0]
429 xvalueMin = data[0][0]
430 xvalueMax = data[0][0]
431 yvalueMin = data[1][0]
432 yvalueMax = data[1][0]
435 for j in range(columns):
436 if xvalueMin > data[0][j]:
437 xvalueMin = data[0][j]
438 if xvalueMax < data[0][j]:
439 xvalueMax = data[0][j]
441 for i in range(rows - 1):
443 for j in range(columns):
444 lst.append((data[0][j], data[i + 1][j]))
445 if yvalueMin > data[i + 1][j]:
446 yvalueMin = data[i + 1][j]
447 if yvalueMax < data[i + 1][j]:
448 yvalueMax = data[i + 1][j]
449 yvalueSteps.append(int(data[i + 1][j] * 2.5) / 2.5)
450 result.append(tuple(lst))
451 xvalueMin = int(xvalueMin) / 100 * 100
452 xvalueMax = int(xvalueMax) / 100 * 100 + 200
453 yvalueMin = int(yvalueMin) * 1.0 - 1
456 yvalueMax = int(yvalueMax) + 2.0
457 yvalueSteps.append(yvalueMin)
458 yvalueSteps.append(yvalueMax)
459 yvalueSteps = {}.fromkeys(yvalueSteps).keys()
461 self._xvalue = (xvalueMin, xvalueMax, xvalueSteps)
462 self._yvalue = (yvalueMin, yvalueMax, yvalueSteps)
467 lpw = self._lpstyle.width
468 lph = self._lpstyle.height
469 draw = Drawing(lpw, lph)
470 line_cnts = len(self._linename)
471 # lp = noScaleLinePlot()
473 lg_line = (line_cnts + 3) / 4
474 lp.x = self._lpstyle.left
475 lp.y = self._lpstyle.bottom
477 lp.height = lph - self._lpstyle.bottom * (lg_line + 1.5)
478 lp.width = lpw - lp.x * 2
481 lp.strokeWidth = self._lpstyle.strokeWidth
482 line_cnts = len(self._data)
483 sytle_cnts = len(self._lpstyle.linestyle)
485 for i in range(line_cnts):
486 styleIndex = i % sytle_cnts
487 lp.lines[i].strokeColor = self._lpstyle.linestyle[styleIndex][0]
488 lp.lines[i].symbol = makeMarker(self._lpstyle.linestyle[styleIndex][1])
489 lp.lines[i].strokeWidth = self._lpstyle.linestyle[styleIndex][2]
490 color_paris.append((self._lpstyle.linestyle[styleIndex][0], self._linename[i]))
491 # lp.lineLabels[i].strokeColor = self._lpstyle.linestyle[styleIndex][0]
493 lp.lineLabelFormat = self._lpstyle.format[0]
495 lp.strokeColor = self._lpstyle.strokeColor
497 lp.xValueAxis.valueMin, lp.xValueAxis.valueMax, lp.xValueAxis.valueSteps = self._xvalue
498 # valueMin, valueMax, xvalueSteps = self._xvalue
499 # lp.xValueAxis.valueStep = (lp.xValueAxis.valueMax - lp.xValueAxis.valueMin)/len(xvalueSteps)
500 # lp.xValueAxis.valueSteps = map(lambda x: str(x), xvalueSteps)
502 lp.yValueAxis.valueMin, lp.yValueAxis.valueMax, lp.yValueAxis.valueSteps = self._yvalue
506 # lp.xValueAxis.forceZero = 0
507 # lp.xValueAxis.avoidBoundFrac = 1
508 # lp.xValueAxis.tickDown = 3
509 # lp.xValueAxis.visibleGrid = 1
510 # lp.xValueAxis.categoryNames = '64 256 512 1400 1500 4096'.split(' ')
512 lp.xValueAxis.labelTextFormat = self._lpstyle.format[1]
513 lp.yValueAxis.labelTextFormat = self._lpstyle.format[2]
515 delsize = int(lp.xValueAxis.valueMax / 2000)
516 lp.xValueAxis.labels.fontSize = self._lpstyle.labelsfont
517 lp.xValueAxis.labels.angle = 25
519 lp.yValueAxis.labels.fontSize = self._lpstyle.labelsfont
520 lp.lineLabels.fontSize = self._lpstyle.labelsfont - delsize
524 lg.colorNamePairs = color_paris
525 lg.fontName = 'Helvetica'
528 lg.x = self._lpstyle.left * 3
529 lg.y = self._lpstyle.bottom * (1 + lg_line) + lp.height
537 lg.alignment = 'right'
542 class eHorizontalLineChart(object):
543 def __init__(self, data, style):
544 self._lcstyle = style
547 self._linename = data[0]
548 self._data = self.analysisData(data[1:])
556 def analysisData(self, data):
558 data = map(list, zip(*data))
559 self._catNames = data[0]
560 self._linename = self._linename[1:]
564 yvalueMin = float(data[0][0])
565 yvalueMax = float(data[0][0])
569 for rowNo in range(rows):
570 for columnNo in range(columns):
571 data[rowNo][columnNo] = float(data[rowNo][columnNo])
572 if yvalueMin > data[rowNo][columnNo]:
573 yvalueMin = data[rowNo][columnNo]
574 if yvalueMax < data[rowNo][columnNo]:
575 yvalueMax = data[rowNo][columnNo]
576 yvalueSteps.append(int(data[rowNo][columnNo] * 1.0) / 1.0)
577 result.append(tuple(data[rowNo]))
579 yvalueMin = int(yvalueMin) * 1.0 - 1
582 yvalueMax = int(yvalueMax) + 2.0
583 yvalueSteps.append(yvalueMin)
584 yvalueSteps.append(yvalueMax)
585 yvalueSteps = {}.fromkeys(yvalueSteps).keys()
587 self._value = (yvalueMin, yvalueMax, yvalueSteps)
592 dw = self._lcstyle.width
593 dh = self._lcstyle.height
594 draw = Drawing(dw, dh)
596 lc = HorizontalLineChart()
597 line_cnts = len(self._linename)
599 lg_line = (line_cnts + 3) / 4
600 lc.height = dh - self._lcstyle.bottom * (lg_line + 1.5)
601 lc.width = dw - lc.x * 2
602 lc.x = self._lcstyle.left
603 lc.y = self._lcstyle.bottom
607 lc.strokeColor = self._lcstyle.strokeColor
608 lc.strokeWidth = self._lcstyle.strokeWidth
610 lc.groupSpacing = lc.width * 2.0 / len(self._catNames)
612 lc.lineLabelFormat = self._lcstyle.format[0]
614 lc.valueAxis.valueMin, lc.valueAxis.valueMax, lc.valueAxis.valueSteps = self._value
615 lc.valueAxis.labelTextFormat = self._lcstyle.format[1]
616 lc.valueAxis.labels.fontSize = self._lcstyle.labelsfont
618 lc.categoryAxis.categoryNames = self._catNames
619 lc.categoryAxis.labels.boxAnchor = 'ne'
620 lc.categoryAxis.labels.dx = lc.width / 2.0 / len(self._catNames)
621 lc.categoryAxis.labels.dy = -6
622 lc.categoryAxis.labels.angle = 10
623 lc.categoryAxis.labels.fontSize = self._lcstyle.labelsfont
624 # lc.categoryAxis.visibleGrid = 1
625 # lc.categoryAxis.tickUp = 100
626 # lc.categoryAxis.tickDown = 50
627 # lc.categoryAxis.gridEnd = dh
628 sytle_cnts = len(self._lcstyle.linestyle)
630 for i in range(line_cnts):
631 styleIndex = i % sytle_cnts
632 lc.lines[i].strokeColor = self._lcstyle.linestyle[styleIndex][0]
633 lc.lines[i].symbol = makeMarker(self._lcstyle.linestyle[styleIndex][1])
634 lc.lines[i].strokeWidth = self._lcstyle.linestyle[styleIndex][2]
635 color_paris.append((self._lcstyle.linestyle[styleIndex][0], self._linename[i]))
637 lc.lineLabels.fontSize = self._lcstyle.labelsfont - 2
642 lg.colorNamePairs = color_paris
643 lg.fontName = 'Helvetica'
646 # lg.y = self._lcstyle.bottom *(1.5 + lg_line)
648 lg.x = self._lcstyle.left * 3
649 lg.y = self._lcstyle.bottom * (1 + lg_line) + lc.height
657 lg.alignment = 'right'
662 class eBarChartColumn(object):
663 def __init__(self, data, style):
664 self._bcstyle = style
667 self._data = self.analysisData(data)
675 def analysisData(self, data):
676 self._ytitle = data[0]
682 bar = map(lambda x: float(x), bar)
683 result.append(tuple(bar))
687 dw = self._bcstyle.width
688 dh = self._bcstyle.height
689 draw = Drawing(dw, dh)
691 bc = VerticalBarChart()
692 bar_cnt = len(self._bar)
693 lg_line = (bar_cnt + 3) / 4
695 bc.width = dw - self._bcstyle.left - self._bcstyle.right
696 bc.height = dh - self._bcstyle.top - self._bcstyle.bottom
698 bc.height -= lg_line * 15
700 bc.x = self._bcstyle.left
701 bc.y = self._bcstyle.bottom
703 for i in range(bar_cnt):
704 bc.bars[i].fillColor = self._bcstyle.pillarstyle[self._bar[i]][0]
705 color_paris.append((self._bcstyle.pillarstyle[self._bar[i]][0], self._bar[i]))
707 bc.fillColor = self._bcstyle.background
708 bc.barLabels.fontName = 'Helvetica'
709 bc.barLabelFormat = self._bcstyle.pillarstyle[self._bar[0]][1]
710 bc.barLabels.fontSize = self._bcstyle.labelsfont
711 bc.barLabels.dy = self._bcstyle.labelsfont
712 bc.valueAxis.labels.fontName = 'Helvetica'
713 bc.valueAxis.labels.fontSize = self._bcstyle.labelsfont
714 bc.valueAxis.forceZero = 1
715 bc.valueAxis.valueMin = 0
718 bc.barSpacing = self._bcstyle.barSpacing
719 bc.groupSpacing = self._bcstyle.groupSpacing / bar_cnt
720 bc.valueAxis.avoidBoundFrac = 1
721 bc.valueAxis.gridEnd = dw - self._bcstyle.right
722 bc.valueAxis.tickLeft = self._bcstyle.tick
723 bc.valueAxis.visibleGrid = 1
724 bc.categoryAxis.categoryNames = self._name
725 bc.categoryAxis.tickDown = self._bcstyle.tick
726 bc.categoryAxis.labels.fontName = 'Helvetica'
727 bc.categoryAxis.labels.fontSize = self._bcstyle.labelsfont
728 bc.categoryAxis.labels.dy = -27
729 bc.categoryAxis.labels.angle = -90
732 lb.fontName = 'Helvetica'
737 lb.textAnchor = 'middle'
740 lb._text = self._ytitle
744 lg.colorNamePairs = color_paris
745 lg.fontName = 'Helvetica'
748 lg.x = self._bcstyle.left + bc.width / (bar_cnt + 1)
749 lg.y = dh - self._bcstyle.top - lg_line * 5
757 lg.alignment = 'right'
763 class eParagraph(object):
764 def __init__(self, data, style):
766 self._data = self.analysisData(data)
769 def analysisData(self, data):
772 if self._pstyle.name == 'ps_body':
773 # dstr = "<i>" + dstr + "</i><br/>"
774 dstr = dstr + "<br/>"
776 dstr = dstr + "<br/>"
781 self._para = Paragraph(self._data, self._pstyle)