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 """
45 Image.__init__(self, filename, None, None, kind, mask, lazy)
47 print self.drawHeight, self.drawWidth
48 if self.drawWidth * height > self.drawHeight * width:
49 self.drawHeight = width * self.drawHeight / self.drawWidth
50 self.drawWidth = width
52 self.drawWidth = height * self.drawWidth / self.drawHeight
53 self.drawHeight = height
56 print self.drawHeight, self.drawWidth
60 """ an abstract table class, which is contains the base functions to create table """
62 def __init__(self, data, style=TableStyle(name="default")):
63 self._tablestyle = style
66 self._colWidths = None
67 self._data = self.analysisData(data)
71 def analysisData(self, data):
72 raise NotImplementedError("abstract eTable")
75 self._table = Table(self._data, style=self._style, splitByRow=1)
76 self._table.hAlign = self._tablestyle.table_hAlign
77 self._table.vAlign = self._tablestyle.table_vAlign
78 self._table.colWidths = self._tablestyle.table_colWidths
79 if self._spin or self._colWidths:
80 self._table.colWidths = self._colWidths
81 self._table.rowHeights = self._tablestyle.table_rowHeights
88 class eCommonTable(eTable):
90 def analysisData(self, data):
92 ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
93 ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
94 ('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
95 ('BOX', (0, 0), (-1, -1), 1.2, colors.black)
100 class eConfigTable(eTable):
102 def analysisData(self, data):
104 ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
105 ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
106 ('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
107 ('BOX', (0, 0), (-1, -1), 1, colors.black),
108 ('SPAN', (2, 0), (3, 0)),
109 ('SPAN', (2, 1), (3, 1)),
110 ('SPAN', (2, 8), (3, 8)),
111 ('SPAN', (2, 9), (3, 9)),
112 ('SPAN', (2, 10), (3, 10)),
113 ('SPAN', (0, 0), (0, 7)),
114 ('SPAN', (0, 8), (0, 10)),
115 ('SPAN', (0, 11), (0, 19)),
116 ('SPAN', (1, 2), (1, 6)),
117 ('SPAN', (1, 12), (1, 13)),
118 ('SPAN', (1, 14), (1, 16)),
119 ('SPAN', (1, 17), (1, 19)),
120 ('SPAN', (2, 3), (2, 6))
125 class eSummaryTable(eTable):
127 def analysisData(self, data):
129 ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
130 ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
131 ('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
132 ('BOX', (0, 0), (-1, -1), 1, colors.black),
133 ('SPAN', (0, 0), (0, 1)),
134 ('SPAN', (1, 0), (4, 0)),
135 ('SPAN', (5, 0), (-1, 0))
140 class eGitInfoTable(eTable):
142 def analysisData(self, data):
144 ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
145 ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
146 ('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
147 ('BOX', (0, 0), (-1, -1), 1, colors.black),
148 ('SPAN', (0, 0), (0, 2)),
149 ('SPAN', (0, 3), (0, 5)),
150 ('SPAN', (0, 6), (0, 8))
155 class eScenarioTable(eTable):
157 def analysisData(self, data):
159 ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
160 ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
161 ('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
162 ('BOX', (0, 0), (-1, -1), 1, colors.black),
163 ('ALIGN', (2, 1), (-1, -1), 'LEFT'),
164 ('SPAN', (0, 1), (0, 6)),
165 ('SPAN', (0, 7), (0, 12)),
166 ('SPAN', (0, 13), (0, 16)),
167 ('SPAN', (0, 17), (0, 20))
172 class eOptionsTable(eTable):
174 def analysisData(self, data):
176 ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
177 ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
178 ('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
179 ('BOX', (0, 0), (-1, -1), 1, colors.black),
180 ('SPAN', (2, 0), (4, 0)),
181 ('SPAN', (2, 1), (4, 1)),
182 ('SPAN', (0, 0), (0, -1)),
183 ('SPAN', (1, 2), (1, 16)),
184 ('SPAN', (1, 17), (1, 19)),
185 ('SPAN', (1, 20), (1, 22)),
186 ('SPAN', (1, 23), (1, 24)),
187 ('SPAN', (2, 2), (2, 4)),
188 ('SPAN', (2, 5), (2, 12)),
189 ('SPAN', (2, 13), (2, 16)),
190 ('SPAN', (2, 17), (2, 19)),
191 ('SPAN', (2, 20), (2, 22)),
192 ('SPAN', (2, 23), (2, 24))
197 class eProfileTable(eTable):
199 def analysisData(self, data):
201 ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
202 ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
203 ('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
204 ('BOX', (0, 0), (-1, -1), 1, colors.black),
205 ('SPAN', (0, 1), (0, -1)),
206 ('SPAN', (1, 0), (2, 0)),
211 class eDataTable(eTable):
213 def analysisData(self, data):
216 ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
217 ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
218 ('LEADING', (0, 0), (-1, -1), 18),
219 ('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
220 ('BOX', (0, 0), (-1, -1), 1, colors.black),
221 ('LINEBEFORE', (1, 0), (1, -1), 0.8, colors.black),
222 # ('LINEBEFORE', (3, 0), (3, -1), 1, colors.black),
223 # ('LINEBEFORE', (5, 0), (5, -1), 1, colors.black),
224 ('LINEBELOW', (0, 0), (-1, 0), 0.8, colors.black),
225 # ('SPAN', (0, 0), (0, 1)),
226 # ('SPAN', (1, 0), (2, 0)),
227 # ('SPAN', (3, 0), (4, 0))
229 if self._spin is True:
231 result = map(list, zip(*result))
233 for value in self._style:
235 value[1] = (value[1][1], value[1][0])
236 value[2] = (value[2][1], value[2][0])
237 if value[0] == 'LINEBELOW':
238 value[0] = 'LINEAFTER'
239 elif value[0] == 'LINEBEFORE':
240 value[0] = 'LINEABOVE'
247 class eGraphicsTable(eTable):
249 def analysisData(self, data):
251 ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
252 ('VALIGN', (0, 0), (-1, -1), 'MIDDLE')
257 class noScaleXValueAxis(XValueAxis):
260 XValueAxis.__init__(self)
262 def makeTickLabels(self):
264 if not self.visibleLabels:
267 f = self._labelTextFormat # perhaps someone already set it
269 f = self.labelTextFormat or (self._allIntTicks() and '%.0f' or str)
270 elif f is str and self._allIntTicks():
272 elif hasattr(f, 'calcPlaces'):
273 f.calcPlaces(self._tickValues)
274 post = self.labelTextPostFormat
275 scl = self.labelTextScale
276 pos = [self._x, self._y]
278 pos[1 - d] = self._labelAxisPos()
280 if self.skipEndL != 'none':
285 if self.skipEndL == 'start':
288 sk = [sk, sk + self._length]
289 if self.skipEndL == 'end':
294 nticks = len(self._tickValues)
296 for i, tick in enumerate(self._tickValues):
299 label = labels[label]
302 if f and label.visible:
306 if abs(skv - v) < 1e-6:
314 if isinstance(f, str):
317 # it's a list, use as many items as we get
322 elif hasattr(f, '__call__'):
323 if isinstance(f, TickLabeller):
328 raise ValueError('Invalid labelTextFormat %s' % f)
332 label.setOrigin(*pos)
335 # special property to ensure a label doesn't project beyond
336 # the bounds of an x-axis
337 if self.keepTickLabelsInside:
339 self, XValueAxis): # not done yet for y axes
341 if not i: # first one
342 x0, y0, x1, y1 = label.getBounds()
344 label = label.clone(dx=label.dx + a_x - x0)
345 if i == nticks1: # final one
346 a_x1 = a_x + self._length
347 x0, y0, x1, y1 = label.getBounds()
350 dx=label.dx - x1 + a_x1)
355 def ___calcScaleFactor(self):
356 """Calculate the axis' scale factor.
357 This should be called only *after* the axis' range is set.
360 self._scaleFactor = self._length / (len(self._tickValues) + 1)
361 return self._scaleFactor
363 def scale(self, value):
364 """Converts a numeric value to a plotarea position.
365 The chart first configures the axis, then asks it to
367 assert self._configured, "Axis cannot scale numbers before it is configured"
370 # this could be made more efficient by moving the definition of org and
371 # sf into the configuration
372 org = (self._x, self._y)[self._dataIndex]
373 sf = self._length / (len(self._tickValues) + 1)
374 if self.reverseDirection:
377 return org + sf * (value + 1)
380 class noScaleLinePlot(LinePlot):
383 LinePlot.__init__(self)
384 self.xValueAxis = noScaleXValueAxis()
386 def calcPositions(self):
387 """Works out where they go.
389 Sets an attribute _positions which is a list of
390 lists of (x, y) matching the data.
392 self._seriesCount = len(self.data)
393 self._rowLength = max(map(len, self.data))
396 for rowNo in range(len(self.data)):
398 len_row = len(self.data[rowNo])
399 for colNo in range(len_row):
400 datum = self.data[rowNo][colNo] # x, y value
401 x = self.x + self.width / (len_row + 1) * (colNo + 1)
402 self.xValueAxis.labels[colNo].x = self.x + \
403 self.width / (len_row + 1) * (colNo + 1)
404 y = self.yValueAxis.scale(datum[1])
405 # print self.width, " ", x
407 self._positions.append(line)
410 # def _innerDrawLabel(self, rowNo, colNo, x, y):
412 class eLinePlot(object):
414 def __init__(self, data, style):
415 self._lpstyle = style
416 self._linename = data[0]
417 self._data = self.analysisData(data[1:])
425 def analysisData(self, data):
428 data = map(list, zip(*data))
431 for i in range(rows):
432 for j in range(columns):
433 data[i][j] = float(data[i][j])
434 self._linename = self._linename[1:]
438 for i in range(columns):
440 del_line = [self._linename[0]]
441 for i in range(rows):
442 for j in range(columns):
443 data[i][j] = float(data[i][j])
444 if data[i] == delrows:
446 del_line.append(self._linename[i])
447 for i in range(delcnt):
449 for name in del_line:
450 self._linename.remove(name)
456 xvalueSteps = data[0]
457 xvalueMin = data[0][0]
458 xvalueMax = data[0][0]
459 yvalueMin = data[1][0]
460 yvalueMax = data[1][0]
463 for j in range(columns):
464 if xvalueMin > data[0][j]:
465 xvalueMin = data[0][j]
466 if xvalueMax < data[0][j]:
467 xvalueMax = data[0][j]
469 for i in range(rows - 1):
471 for j in range(columns):
472 lst.append((data[0][j], data[i + 1][j]))
473 if yvalueMin > data[i + 1][j]:
474 yvalueMin = data[i + 1][j]
475 if yvalueMax < data[i + 1][j]:
476 yvalueMax = data[i + 1][j]
477 yvalueSteps.append(int(data[i + 1][j] * 2.5) / 2.5)
478 result.append(tuple(lst))
479 xvalueMin = int(xvalueMin) / 100 * 100
480 xvalueMax = int(xvalueMax) / 100 * 100 + 200
481 yvalueMin = int(yvalueMin) * 1.0 - 1
484 yvalueMax = int(yvalueMax) + 2.0
485 yvalueSteps.append(yvalueMin)
486 yvalueSteps.append(yvalueMax)
487 yvalueSteps = {}.fromkeys(yvalueSteps).keys()
489 self._xvalue = (xvalueMin, xvalueMax, xvalueSteps)
490 self._yvalue = (yvalueMin, yvalueMax, yvalueSteps)
495 lpw = self._lpstyle.width
496 lph = self._lpstyle.height
497 draw = Drawing(lpw, lph)
498 line_cnts = len(self._linename)
499 # lp = noScaleLinePlot()
501 lg_line = (line_cnts + 3) / 4
502 lp.x = self._lpstyle.left
503 lp.y = self._lpstyle.bottom
505 lp.height = lph - self._lpstyle.bottom * (lg_line + 1.5)
506 lp.width = lpw - lp.x * 2
509 lp.strokeWidth = self._lpstyle.strokeWidth
510 line_cnts = len(self._data)
511 sytle_cnts = len(self._lpstyle.linestyle)
513 for i in range(line_cnts):
514 styleIndex = i % sytle_cnts
515 lp.lines[i].strokeColor = self._lpstyle.linestyle[styleIndex][0]
516 lp.lines[i].symbol = makeMarker(
517 self._lpstyle.linestyle[styleIndex][1])
518 lp.lines[i].strokeWidth = self._lpstyle.linestyle[styleIndex][2]
520 (self._lpstyle.linestyle[styleIndex][0], self._linename[i]))
521 # lp.lineLabels[i].strokeColor = self._lpstyle.linestyle[styleIndex][0]
523 lp.lineLabelFormat = self._lpstyle.format[0]
525 lp.strokeColor = self._lpstyle.strokeColor
527 lp.xValueAxis.valueMin, lp.xValueAxis.valueMax, lp.xValueAxis.valueSteps = self._xvalue
528 # valueMin, valueMax, xvalueSteps = self._xvalue
529 # lp.xValueAxis.valueStep = (lp.xValueAxis.valueMax - lp.xValueAxis.valueMin)/len(xvalueSteps)
530 # lp.xValueAxis.valueSteps = map(lambda x: str(x), xvalueSteps)
532 lp.yValueAxis.valueMin, lp.yValueAxis.valueMax, lp.yValueAxis.valueSteps = self._yvalue
534 # lp.xValueAxis.forceZero = 0
535 # lp.xValueAxis.avoidBoundFrac = 1
536 # lp.xValueAxis.tickDown = 3
537 # lp.xValueAxis.visibleGrid = 1
538 # lp.xValueAxis.categoryNames = '64 256 512 1400 1500 4096'.split(' ')
540 lp.xValueAxis.labelTextFormat = self._lpstyle.format[1]
541 lp.yValueAxis.labelTextFormat = self._lpstyle.format[2]
543 delsize = int(lp.xValueAxis.valueMax / 2000)
544 lp.xValueAxis.labels.fontSize = self._lpstyle.labelsfont
545 lp.xValueAxis.labels.angle = 25
547 lp.yValueAxis.labels.fontSize = self._lpstyle.labelsfont
548 lp.lineLabels.fontSize = self._lpstyle.labelsfont - delsize
552 lg.colorNamePairs = color_paris
553 lg.fontName = 'Helvetica'
556 lg.x = self._lpstyle.left * 3
557 lg.y = self._lpstyle.bottom * (1 + lg_line) + lp.height
565 lg.alignment = 'right'
570 class eHorizontalLineChart(object):
572 def __init__(self, data, style):
573 self._lcstyle = style
576 self._linename = data[0]
577 self._data = self.analysisData(data[1:])
585 def analysisData(self, data):
587 data = map(list, zip(*data))
588 self._catNames = data[0]
589 self._linename = self._linename[1:]
593 yvalueMin = float(data[0][0])
594 yvalueMax = float(data[0][0])
598 for rowNo in range(rows):
599 for columnNo in range(columns):
600 data[rowNo][columnNo] = float(data[rowNo][columnNo])
601 if yvalueMin > data[rowNo][columnNo]:
602 yvalueMin = data[rowNo][columnNo]
603 if yvalueMax < data[rowNo][columnNo]:
604 yvalueMax = data[rowNo][columnNo]
605 yvalueSteps.append(int(data[rowNo][columnNo] * 1.0) / 1.0)
606 result.append(tuple(data[rowNo]))
608 yvalueMin = int(yvalueMin) * 1.0 - 1
611 yvalueMax = int(yvalueMax) + 2.0
612 yvalueSteps.append(yvalueMin)
613 yvalueSteps.append(yvalueMax)
614 yvalueSteps = {}.fromkeys(yvalueSteps).keys()
616 self._value = (yvalueMin, yvalueMax, yvalueSteps)
621 dw = self._lcstyle.width
622 dh = self._lcstyle.height
623 draw = Drawing(dw, dh)
625 lc = HorizontalLineChart()
626 line_cnts = len(self._linename)
628 lg_line = (line_cnts + 3) / 4
629 lc.height = dh - self._lcstyle.bottom * (lg_line + 1.5)
630 lc.width = dw - lc.x * 2
631 lc.x = self._lcstyle.left
632 lc.y = self._lcstyle.bottom
636 lc.strokeColor = self._lcstyle.strokeColor
637 lc.strokeWidth = self._lcstyle.strokeWidth
639 lc.groupSpacing = lc.width * 2.0 / len(self._catNames)
641 lc.lineLabelFormat = self._lcstyle.format[0]
643 lc.valueAxis.valueMin, lc.valueAxis.valueMax, lc.valueAxis.valueSteps = self._value
644 lc.valueAxis.labelTextFormat = self._lcstyle.format[1]
645 lc.valueAxis.labels.fontSize = self._lcstyle.labelsfont
647 lc.categoryAxis.categoryNames = self._catNames
648 lc.categoryAxis.labels.boxAnchor = 'ne'
649 lc.categoryAxis.labels.dx = lc.width / 2.0 / len(self._catNames)
650 lc.categoryAxis.labels.dy = -6
651 lc.categoryAxis.labels.angle = 10
652 lc.categoryAxis.labels.fontSize = self._lcstyle.labelsfont
653 # lc.categoryAxis.visibleGrid = 1
654 # lc.categoryAxis.tickUp = 100
655 # lc.categoryAxis.tickDown = 50
656 # lc.categoryAxis.gridEnd = dh
657 sytle_cnts = len(self._lcstyle.linestyle)
659 for i in range(line_cnts):
660 styleIndex = i % sytle_cnts
661 lc.lines[i].strokeColor = self._lcstyle.linestyle[styleIndex][0]
662 lc.lines[i].symbol = makeMarker(
663 self._lcstyle.linestyle[styleIndex][1])
664 lc.lines[i].strokeWidth = self._lcstyle.linestyle[styleIndex][2]
666 (self._lcstyle.linestyle[styleIndex][0], self._linename[i]))
668 lc.lineLabels.fontSize = self._lcstyle.labelsfont - 2
673 lg.colorNamePairs = color_paris
674 lg.fontName = 'Helvetica'
677 # lg.y = self._lcstyle.bottom *(1.5 + lg_line)
679 lg.x = self._lcstyle.left * 3
680 lg.y = self._lcstyle.bottom * (1 + lg_line) + lc.height
688 lg.alignment = 'right'
693 class eBarChartColumn(object):
695 def __init__(self, data, style):
696 self._bcstyle = style
699 self._data = self.analysisData(data)
707 def analysisData(self, data):
708 self._ytitle = data[0]
714 bar = map(lambda x: float(x), bar)
715 result.append(tuple(bar))
719 dw = self._bcstyle.width
720 dh = self._bcstyle.height
721 draw = Drawing(dw, dh)
723 bc = VerticalBarChart()
724 bar_cnt = len(self._bar)
725 lg_line = (bar_cnt + 3) / 4
727 bc.width = dw - self._bcstyle.left - self._bcstyle.right
728 bc.height = dh - self._bcstyle.top - self._bcstyle.bottom
730 bc.height -= lg_line * 15
732 bc.x = self._bcstyle.left
733 bc.y = self._bcstyle.bottom
735 for i in range(bar_cnt):
736 bc.bars[i].fillColor = self._bcstyle.pillarstyle[self._bar[i]][0]
738 (self._bcstyle.pillarstyle[
742 bc.fillColor = self._bcstyle.background
743 bc.barLabels.fontName = 'Helvetica'
744 bc.barLabelFormat = self._bcstyle.pillarstyle[self._bar[0]][1]
745 bc.barLabels.fontSize = self._bcstyle.labelsfont
746 bc.barLabels.dy = self._bcstyle.labelsfont
747 bc.valueAxis.labels.fontName = 'Helvetica'
748 bc.valueAxis.labels.fontSize = self._bcstyle.labelsfont
749 bc.valueAxis.forceZero = 1
750 bc.valueAxis.valueMin = 0
753 bc.barSpacing = self._bcstyle.barSpacing
754 bc.groupSpacing = self._bcstyle.groupSpacing / bar_cnt
755 bc.valueAxis.avoidBoundFrac = 1
756 bc.valueAxis.gridEnd = dw - self._bcstyle.right
757 bc.valueAxis.tickLeft = self._bcstyle.tick
758 bc.valueAxis.visibleGrid = 1
759 bc.categoryAxis.categoryNames = self._name
760 bc.categoryAxis.tickDown = self._bcstyle.tick
761 bc.categoryAxis.labels.fontName = 'Helvetica'
762 bc.categoryAxis.labels.fontSize = self._bcstyle.labelsfont
763 bc.categoryAxis.labels.dy = -27
764 bc.categoryAxis.labels.angle = -90
767 lb.fontName = 'Helvetica'
772 lb.textAnchor = 'middle'
775 lb._text = self._ytitle
779 lg.colorNamePairs = color_paris
780 lg.fontName = 'Helvetica'
783 lg.x = self._bcstyle.left + bc.width / (bar_cnt + 1)
784 lg.y = dh - self._bcstyle.top - lg_line * 5
792 lg.alignment = 'right'
798 class eParagraph(object):
800 def __init__(self, data, style):
802 self._data = self.analysisData(data)
805 def analysisData(self, data):
808 if self._pstyle.name == 'ps_body':
809 # dstr = "<i>" + dstr + "</i><br/>"
810 dstr = dstr + "<br/>"
812 dstr = dstr + "<br/>"
817 self._para = Paragraph(self._data, self._pstyle)