5 # see license for license details
8 it contains the base element for pdf
9 eImage is used to draw picture on the pdf document
10 eDataTable is used to draw table on the pdf document
11 eGraphicsTable is used to draw plot on the pdf document
12 eParagraph is used to draw text on the pdf document
14 from reportlab.platypus import Image, Table
15 from reportlab.graphics.shapes import Drawing
16 from reportlab.graphics.charts.lineplots import LinePlot
17 from reportlab.graphics.charts.linecharts import HorizontalLineChart
18 from reportlab.platypus.paragraph import Paragraph
19 from reportlab.graphics.widgets.markers import makeMarker
20 from reportlab.graphics.charts.legends import Legend
21 from reportlab.graphics.charts.textlabels import Label
22 from reportlab.graphics.charts.axes import XValueAxis
23 from reportlab.graphics.shapes import Group
24 from reportlab.graphics.charts.barcharts import VerticalBarChart
25 from vstf.controller.reporters.report.pdf.styles import *
29 """ an image(digital picture)which contains the function of auto zoom picture """
31 def __init__(self, filename, width=None, height=None, kind='direct', mask="auto", lazy=1, hAlign='CENTRE',
33 Image.__init__(self, filename, None, None, kind, mask, lazy)
35 print self.drawHeight, self.drawWidth
36 if self.drawWidth * height > self.drawHeight * width:
37 self.drawHeight = width * self.drawHeight / self.drawWidth
38 self.drawWidth = width
40 self.drawWidth = height * self.drawWidth / self.drawHeight
41 self.drawHeight = height
44 print self.drawHeight, self.drawWidth
48 """ an abstract table class, which is contains the base functions to create table """
50 def __init__(self, data, style=TableStyle(name="default")):
51 self._tablestyle = style
54 self._colWidths = None
55 self._data = self.analysisData(data)
59 def analysisData(self, data):
60 raise NotImplementedError("abstract eTable")
63 self._table = Table(self._data, style=self._style, splitByRow=1)
64 self._table.hAlign = self._tablestyle.table_hAlign
65 self._table.vAlign = self._tablestyle.table_vAlign
66 self._table.colWidths = self._tablestyle.table_colWidths
67 if self._spin or self._colWidths:
68 self._table.colWidths = self._colWidths
69 self._table.rowHeights = self._tablestyle.table_rowHeights
76 class eCommonTable(eTable):
77 def analysisData(self, data):
79 ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
80 ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
81 ('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
82 ('BOX', (0, 0), (-1, -1), 1.2, colors.black)
87 class eConfigTable(eTable):
88 def analysisData(self, data):
90 ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
91 ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
92 ('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
93 ('BOX', (0, 0), (-1, -1), 1, colors.black),
94 ('SPAN', (2, 0), (3, 0)),
95 ('SPAN', (2, 1), (3, 1)),
96 ('SPAN', (2, 8), (3, 8)),
97 ('SPAN', (2, 9), (3, 9)),
98 ('SPAN', (2, 10), (3, 10)),
99 ('SPAN', (0, 0), (0, 7)),
100 ('SPAN', (0, 8), (0, 10)),
101 ('SPAN', (0, 11), (0, 19)),
102 ('SPAN', (1, 2), (1, 6)),
103 ('SPAN', (1, 12), (1, 13)),
104 ('SPAN', (1, 14), (1, 16)),
105 ('SPAN', (1, 17), (1, 19)),
106 ('SPAN', (2, 3), (2, 6))
111 class eSummaryTable(eTable):
112 def analysisData(self, data):
114 ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
115 ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
116 ('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
117 ('BOX', (0, 0), (-1, -1), 1, colors.black),
118 ('SPAN', (0, 0), (0, 1)),
119 ('SPAN', (1, 0), (4, 0)),
120 ('SPAN', (5, 0), (-1, 0))
125 class eGitInfoTable(eTable):
126 def analysisData(self, data):
128 ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
129 ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
130 ('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
131 ('BOX', (0, 0), (-1, -1), 1, colors.black),
132 ('SPAN', (0, 0), (0, 2)),
133 ('SPAN', (0, 3), (0, 5)),
134 ('SPAN', (0, 6), (0, 8))
139 class eScenarioTable(eTable):
140 def analysisData(self, data):
142 ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
143 ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
144 ('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
145 ('BOX', (0, 0), (-1, -1), 1, colors.black),
146 ('ALIGN', (2, 1), (-1, -1), 'LEFT'),
147 ('SPAN', (0, 1), (0, 6)),
148 ('SPAN', (0, 7), (0, 12)),
149 ('SPAN', (0, 13), (0, 16)),
150 ('SPAN', (0, 17), (0, 20))
155 class eOptionsTable(eTable):
156 def analysisData(self, data):
158 ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
159 ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
160 ('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
161 ('BOX', (0, 0), (-1, -1), 1, colors.black),
162 ('SPAN', (2, 0), (4, 0)),
163 ('SPAN', (2, 1), (4, 1)),
164 ('SPAN', (0, 0), (0, -1)),
165 ('SPAN', (1, 2), (1, 16)),
166 ('SPAN', (1, 17), (1, 19)),
167 ('SPAN', (1, 20), (1, 22)),
168 ('SPAN', (1, 23), (1, 24)),
169 ('SPAN', (2, 2), (2, 4)),
170 ('SPAN', (2, 5), (2, 12)),
171 ('SPAN', (2, 13), (2, 16)),
172 ('SPAN', (2, 17), (2, 19)),
173 ('SPAN', (2, 20), (2, 22)),
174 ('SPAN', (2, 23), (2, 24))
179 class eProfileTable(eTable):
180 def analysisData(self, data):
182 ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
183 ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
184 ('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
185 ('BOX', (0, 0), (-1, -1), 1, colors.black),
186 ('SPAN', (0, 1), (0, -1)),
187 ('SPAN', (1, 0), (2, 0)),
192 class eDataTable(eTable):
193 def analysisData(self, data):
196 ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
197 ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
198 ('LEADING', (0, 0), (-1, -1), 18),
199 ('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
200 ('BOX', (0, 0), (-1, -1), 1, colors.black),
201 ('LINEBEFORE', (1, 0), (1, -1), 0.8, colors.black),
202 # ('LINEBEFORE', (3, 0), (3, -1), 1, colors.black),
203 # ('LINEBEFORE', (5, 0), (5, -1), 1, colors.black),
204 ('LINEBELOW', (0, 0), (-1, 0), 0.8, colors.black),
205 # ('SPAN', (0, 0), (0, 1)),
206 # ('SPAN', (1, 0), (2, 0)),
207 # ('SPAN', (3, 0), (4, 0))
209 if self._spin is True:
211 result = map(list, zip(*result))
213 for value in self._style:
215 value[1] = (value[1][1], value[1][0])
216 value[2] = (value[2][1], value[2][0])
217 if value[0] == 'LINEBELOW':
218 value[0] = 'LINEAFTER'
219 elif value[0] == 'LINEBEFORE':
220 value[0] = 'LINEABOVE'
227 class eGraphicsTable(eTable):
228 def analysisData(self, data):
230 ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
231 ('VALIGN', (0, 0), (-1, -1), 'MIDDLE')
236 class noScaleXValueAxis(XValueAxis):
238 XValueAxis.__init__(self)
240 def makeTickLabels(self):
242 if not self.visibleLabels: return g
244 f = self._labelTextFormat # perhaps someone already set it
246 f = self.labelTextFormat or (self._allIntTicks() and '%.0f' or str)
247 elif f is str and self._allIntTicks():
249 elif hasattr(f, 'calcPlaces'):
250 f.calcPlaces(self._tickValues)
251 post = self.labelTextPostFormat
252 scl = self.labelTextScale
253 pos = [self._x, self._y]
255 pos[1 - d] = self._labelAxisPos()
257 if self.skipEndL != 'none':
262 if self.skipEndL == 'start':
265 sk = [sk, sk + self._length]
266 if self.skipEndL == 'end':
271 nticks = len(self._tickValues)
273 for i, tick in enumerate(self._tickValues):
276 label = labels[label]
279 if f and label.visible:
283 if abs(skv - v) < 1e-6:
291 if isinstance(f, str):
294 # it's a list, use as many items as we get
299 elif hasattr(f, '__call__'):
300 if isinstance(f, TickLabeller):
305 raise ValueError('Invalid labelTextFormat %s' % f)
306 if post: txt = post % txt
308 label.setOrigin(*pos)
311 # special property to ensure a label doesn't project beyond the bounds of an x-axis
312 if self.keepTickLabelsInside:
313 if isinstance(self, XValueAxis): # not done yet for y axes
315 if not i: # first one
316 x0, y0, x1, y1 = label.getBounds()
318 label = label.clone(dx=label.dx + a_x - x0)
319 if i == nticks1: # final one
320 a_x1 = a_x + self._length
321 x0, y0, x1, y1 = label.getBounds()
323 label = label.clone(dx=label.dx - x1 + a_x1)
328 def ___calcScaleFactor(self):
329 """Calculate the axis' scale factor.
330 This should be called only *after* the axis' range is set.
333 self._scaleFactor = self._length / (len(self._tickValues) + 1)
334 return self._scaleFactor
336 def scale(self, value):
337 """Converts a numeric value to a plotarea position.
338 The chart first configures the axis, then asks it to
340 assert self._configured, "Axis cannot scale numbers before it is configured"
341 if value is None: value = 0
342 # this could be made more efficient by moving the definition of org and sf into the configuration
343 org = (self._x, self._y)[self._dataIndex]
344 sf = self._length / (len(self._tickValues) + 1)
345 if self.reverseDirection:
348 return org + sf * (value + 1)
351 class noScaleLinePlot(LinePlot):
353 LinePlot.__init__(self)
354 self.xValueAxis = noScaleXValueAxis()
356 def calcPositions(self):
357 """Works out where they go.
359 Sets an attribute _positions which is a list of
360 lists of (x, y) matching the data.
362 self._seriesCount = len(self.data)
363 self._rowLength = max(map(len, self.data))
366 for rowNo in range(len(self.data)):
368 len_row = len(self.data[rowNo])
369 for colNo in range(len_row):
370 datum = self.data[rowNo][colNo] # x, y value
371 x = self.x + self.width / (len_row + 1) * (colNo + 1)
372 self.xValueAxis.labels[colNo].x = self.x + self.width / (len_row + 1) * (colNo + 1)
373 y = self.yValueAxis.scale(datum[1])
374 # print self.width, " ", x
376 self._positions.append(line)
379 # def _innerDrawLabel(self, rowNo, colNo, x, y):
381 class eLinePlot(object):
382 def __init__(self, data, style):
383 self._lpstyle = style
384 self._linename = data[0]
385 self._data = self.analysisData(data[1:])
393 def analysisData(self, data):
396 data = map(list, zip(*data))
399 for i in range(rows):
400 for j in range(columns):
401 data[i][j] = float(data[i][j])
402 self._linename = self._linename[1:]
406 for i in range(columns):
408 del_line = [self._linename[0]]
409 for i in range(rows):
410 for j in range(columns):
411 data[i][j] = float(data[i][j])
412 if data[i] == delrows:
414 del_line.append(self._linename[i])
415 for i in range(delcnt):
417 for name in del_line:
418 self._linename.remove(name)
424 xvalueSteps = data[0]
425 xvalueMin = data[0][0]
426 xvalueMax = data[0][0]
427 yvalueMin = data[1][0]
428 yvalueMax = data[1][0]
431 for j in range(columns):
432 if xvalueMin > data[0][j]:
433 xvalueMin = data[0][j]
434 if xvalueMax < data[0][j]:
435 xvalueMax = data[0][j]
437 for i in range(rows - 1):
439 for j in range(columns):
440 lst.append((data[0][j], data[i + 1][j]))
441 if yvalueMin > data[i + 1][j]:
442 yvalueMin = data[i + 1][j]
443 if yvalueMax < data[i + 1][j]:
444 yvalueMax = data[i + 1][j]
445 yvalueSteps.append(int(data[i + 1][j] * 2.5) / 2.5)
446 result.append(tuple(lst))
447 xvalueMin = int(xvalueMin) / 100 * 100
448 xvalueMax = int(xvalueMax) / 100 * 100 + 200
449 yvalueMin = int(yvalueMin) * 1.0 - 1
452 yvalueMax = int(yvalueMax) + 2.0
453 yvalueSteps.append(yvalueMin)
454 yvalueSteps.append(yvalueMax)
455 yvalueSteps = {}.fromkeys(yvalueSteps).keys()
457 self._xvalue = (xvalueMin, xvalueMax, xvalueSteps)
458 self._yvalue = (yvalueMin, yvalueMax, yvalueSteps)
463 lpw = self._lpstyle.width
464 lph = self._lpstyle.height
465 draw = Drawing(lpw, lph)
466 line_cnts = len(self._linename)
467 # lp = noScaleLinePlot()
469 lg_line = (line_cnts + 3) / 4
470 lp.x = self._lpstyle.left
471 lp.y = self._lpstyle.bottom
473 lp.height = lph - self._lpstyle.bottom * (lg_line + 1.5)
474 lp.width = lpw - lp.x * 2
477 lp.strokeWidth = self._lpstyle.strokeWidth
478 line_cnts = len(self._data)
479 sytle_cnts = len(self._lpstyle.linestyle)
481 for i in range(line_cnts):
482 styleIndex = i % sytle_cnts
483 lp.lines[i].strokeColor = self._lpstyle.linestyle[styleIndex][0]
484 lp.lines[i].symbol = makeMarker(self._lpstyle.linestyle[styleIndex][1])
485 lp.lines[i].strokeWidth = self._lpstyle.linestyle[styleIndex][2]
486 color_paris.append((self._lpstyle.linestyle[styleIndex][0], self._linename[i]))
487 # lp.lineLabels[i].strokeColor = self._lpstyle.linestyle[styleIndex][0]
489 lp.lineLabelFormat = self._lpstyle.format[0]
491 lp.strokeColor = self._lpstyle.strokeColor
493 lp.xValueAxis.valueMin, lp.xValueAxis.valueMax, lp.xValueAxis.valueSteps = self._xvalue
494 # valueMin, valueMax, xvalueSteps = self._xvalue
495 # lp.xValueAxis.valueStep = (lp.xValueAxis.valueMax - lp.xValueAxis.valueMin)/len(xvalueSteps)
496 # lp.xValueAxis.valueSteps = map(lambda x: str(x), xvalueSteps)
498 lp.yValueAxis.valueMin, lp.yValueAxis.valueMax, lp.yValueAxis.valueSteps = self._yvalue
502 # lp.xValueAxis.forceZero = 0
503 # lp.xValueAxis.avoidBoundFrac = 1
504 # lp.xValueAxis.tickDown = 3
505 # lp.xValueAxis.visibleGrid = 1
506 # lp.xValueAxis.categoryNames = '64 256 512 1400 1500 4096'.split(' ')
508 lp.xValueAxis.labelTextFormat = self._lpstyle.format[1]
509 lp.yValueAxis.labelTextFormat = self._lpstyle.format[2]
511 delsize = int(lp.xValueAxis.valueMax / 2000)
512 lp.xValueAxis.labels.fontSize = self._lpstyle.labelsfont
513 lp.xValueAxis.labels.angle = 25
515 lp.yValueAxis.labels.fontSize = self._lpstyle.labelsfont
516 lp.lineLabels.fontSize = self._lpstyle.labelsfont - delsize
520 lg.colorNamePairs = color_paris
521 lg.fontName = 'Helvetica'
524 lg.x = self._lpstyle.left * 3
525 lg.y = self._lpstyle.bottom * (1 + lg_line) + lp.height
533 lg.alignment = 'right'
538 class eHorizontalLineChart(object):
539 def __init__(self, data, style):
540 self._lcstyle = style
543 self._linename = data[0]
544 self._data = self.analysisData(data[1:])
552 def analysisData(self, data):
554 data = map(list, zip(*data))
555 self._catNames = data[0]
556 self._linename = self._linename[1:]
560 yvalueMin = float(data[0][0])
561 yvalueMax = float(data[0][0])
565 for rowNo in range(rows):
566 for columnNo in range(columns):
567 data[rowNo][columnNo] = float(data[rowNo][columnNo])
568 if yvalueMin > data[rowNo][columnNo]:
569 yvalueMin = data[rowNo][columnNo]
570 if yvalueMax < data[rowNo][columnNo]:
571 yvalueMax = data[rowNo][columnNo]
572 yvalueSteps.append(int(data[rowNo][columnNo] * 1.0) / 1.0)
573 result.append(tuple(data[rowNo]))
575 yvalueMin = int(yvalueMin) * 1.0 - 1
578 yvalueMax = int(yvalueMax) + 2.0
579 yvalueSteps.append(yvalueMin)
580 yvalueSteps.append(yvalueMax)
581 yvalueSteps = {}.fromkeys(yvalueSteps).keys()
583 self._value = (yvalueMin, yvalueMax, yvalueSteps)
588 dw = self._lcstyle.width
589 dh = self._lcstyle.height
590 draw = Drawing(dw, dh)
592 lc = HorizontalLineChart()
593 line_cnts = len(self._linename)
595 lg_line = (line_cnts + 3) / 4
596 lc.height = dh - self._lcstyle.bottom * (lg_line + 1.5)
597 lc.width = dw - lc.x * 2
598 lc.x = self._lcstyle.left
599 lc.y = self._lcstyle.bottom
603 lc.strokeColor = self._lcstyle.strokeColor
604 lc.strokeWidth = self._lcstyle.strokeWidth
606 lc.groupSpacing = lc.width * 2.0 / len(self._catNames)
608 lc.lineLabelFormat = self._lcstyle.format[0]
610 lc.valueAxis.valueMin, lc.valueAxis.valueMax, lc.valueAxis.valueSteps = self._value
611 lc.valueAxis.labelTextFormat = self._lcstyle.format[1]
612 lc.valueAxis.labels.fontSize = self._lcstyle.labelsfont
614 lc.categoryAxis.categoryNames = self._catNames
615 lc.categoryAxis.labels.boxAnchor = 'ne'
616 lc.categoryAxis.labels.dx = lc.width / 2.0 / len(self._catNames)
617 lc.categoryAxis.labels.dy = -6
618 lc.categoryAxis.labels.angle = 10
619 lc.categoryAxis.labels.fontSize = self._lcstyle.labelsfont
620 # lc.categoryAxis.visibleGrid = 1
621 # lc.categoryAxis.tickUp = 100
622 # lc.categoryAxis.tickDown = 50
623 # lc.categoryAxis.gridEnd = dh
624 sytle_cnts = len(self._lcstyle.linestyle)
626 for i in range(line_cnts):
627 styleIndex = i % sytle_cnts
628 lc.lines[i].strokeColor = self._lcstyle.linestyle[styleIndex][0]
629 lc.lines[i].symbol = makeMarker(self._lcstyle.linestyle[styleIndex][1])
630 lc.lines[i].strokeWidth = self._lcstyle.linestyle[styleIndex][2]
631 color_paris.append((self._lcstyle.linestyle[styleIndex][0], self._linename[i]))
633 lc.lineLabels.fontSize = self._lcstyle.labelsfont - 2
638 lg.colorNamePairs = color_paris
639 lg.fontName = 'Helvetica'
642 # lg.y = self._lcstyle.bottom *(1.5 + lg_line)
644 lg.x = self._lcstyle.left * 3
645 lg.y = self._lcstyle.bottom * (1 + lg_line) + lc.height
653 lg.alignment = 'right'
658 class eBarChartColumn(object):
659 def __init__(self, data, style):
660 self._bcstyle = style
663 self._data = self.analysisData(data)
671 def analysisData(self, data):
672 self._ytitle = data[0]
678 bar = map(lambda x: float(x), bar)
679 result.append(tuple(bar))
683 dw = self._bcstyle.width
684 dh = self._bcstyle.height
685 draw = Drawing(dw, dh)
687 bc = VerticalBarChart()
688 bar_cnt = len(self._bar)
689 lg_line = (bar_cnt + 3) / 4
691 bc.width = dw - self._bcstyle.left - self._bcstyle.right
692 bc.height = dh - self._bcstyle.top - self._bcstyle.bottom
694 bc.height -= lg_line * 15
696 bc.x = self._bcstyle.left
697 bc.y = self._bcstyle.bottom
699 for i in range(bar_cnt):
700 bc.bars[i].fillColor = self._bcstyle.pillarstyle[self._bar[i]][0]
701 color_paris.append((self._bcstyle.pillarstyle[self._bar[i]][0], self._bar[i]))
703 bc.fillColor = self._bcstyle.background
704 bc.barLabels.fontName = 'Helvetica'
705 bc.barLabelFormat = self._bcstyle.pillarstyle[self._bar[0]][1]
706 bc.barLabels.fontSize = self._bcstyle.labelsfont
707 bc.barLabels.dy = self._bcstyle.labelsfont
708 bc.valueAxis.labels.fontName = 'Helvetica'
709 bc.valueAxis.labels.fontSize = self._bcstyle.labelsfont
710 bc.valueAxis.forceZero = 1
711 bc.valueAxis.valueMin = 0
714 bc.barSpacing = self._bcstyle.barSpacing
715 bc.groupSpacing = self._bcstyle.groupSpacing / bar_cnt
716 bc.valueAxis.avoidBoundFrac = 1
717 bc.valueAxis.gridEnd = dw - self._bcstyle.right
718 bc.valueAxis.tickLeft = self._bcstyle.tick
719 bc.valueAxis.visibleGrid = 1
720 bc.categoryAxis.categoryNames = self._name
721 bc.categoryAxis.tickDown = self._bcstyle.tick
722 bc.categoryAxis.labels.fontName = 'Helvetica'
723 bc.categoryAxis.labels.fontSize = self._bcstyle.labelsfont
724 bc.categoryAxis.labels.dy = -27
725 bc.categoryAxis.labels.angle = -90
728 lb.fontName = 'Helvetica'
733 lb.textAnchor = 'middle'
736 lb._text = self._ytitle
740 lg.colorNamePairs = color_paris
741 lg.fontName = 'Helvetica'
744 lg.x = self._bcstyle.left + bc.width / (bar_cnt + 1)
745 lg.y = dh - self._bcstyle.top - lg_line * 5
753 lg.alignment = 'right'
759 class eParagraph(object):
760 def __init__(self, data, style):
762 self._data = self.analysisData(data)
765 def analysisData(self, data):
768 if self._pstyle.name == 'ps_body':
769 # dstr = "<i>" + dstr + "</i><br/>"
770 dstr = dstr + "<br/>"
772 dstr = dstr + "<br/>"
777 self._para = Paragraph(self._data, self._pstyle)