table.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. #!/usr/bin/env python
  2. #Copyright ReportLab Europe Ltd. 2000-2017
  3. #see license.txt for license details
  4. #history https://hg.reportlab.com/hg-public/reportlab/log/tip/src/reportlab/graphics/widgets/table.py
  5. __version__='3.3.0'
  6. from reportlab.graphics.widgetbase import Widget
  7. from reportlab.graphics.charts.textlabels import Label
  8. from reportlab.graphics import shapes
  9. from reportlab.lib import colors
  10. from reportlab.lib.validators import *
  11. from reportlab.lib.attrmap import *
  12. from reportlab.graphics.shapes import Drawing
  13. class TableWidget(Widget):
  14. """A two dimensions table of labels
  15. """
  16. _attrMap = AttrMap(
  17. x = AttrMapValue(isNumber, desc="x position of left edge of table"),
  18. y = AttrMapValue(isNumber, desc="y position of bottom edge of table"),
  19. width = AttrMapValue(isNumber, desc="table width"),
  20. height = AttrMapValue(isNumber, desc="table height"),
  21. borderStrokeColor = AttrMapValue(isColorOrNone, desc="table border color"),
  22. fillColor = AttrMapValue(isColorOrNone, desc="table fill color"),
  23. borderStrokeWidth = AttrMapValue(isNumber, desc="border line width"),
  24. horizontalDividerStrokeColor = AttrMapValue(isColorOrNone, desc="table inner horizontal lines color"),
  25. verticalDividerStrokeColor = AttrMapValue(isColorOrNone, desc="table inner vertical lines color"),
  26. horizontalDividerStrokeWidth = AttrMapValue(isNumber, desc="table inner horizontal lines width"),
  27. verticalDividerStrokeWidth = AttrMapValue(isNumber, desc="table inner vertical lines width"),
  28. dividerDashArray = AttrMapValue(isListOfNumbersOrNone, desc='Dash array for dividerLines.'),
  29. data = AttrMapValue(None, desc="a list of list of strings to be displayed in the cells"),
  30. boxAnchor = AttrMapValue(isBoxAnchor, desc="location of the table anchoring point"),
  31. fontName = AttrMapValue(isString, desc="text font in the table"),
  32. fontSize = AttrMapValue(isNumber, desc="font size of the table"),
  33. fontColor = AttrMapValue(isColorOrNone, desc="font color"),
  34. alignment = AttrMapValue(OneOf("left", "right"), desc="Alignment of text within cells"),
  35. textAnchor = AttrMapValue(OneOf('start','middle','end','numeric'), desc="Alignment of text within cells"),
  36. )
  37. def __init__(self, x=10, y=10, **kw):
  38. self.x = x
  39. self.y = y
  40. self.width = 200
  41. self.height = 100
  42. self.borderStrokeColor = colors.black
  43. self.fillColor = None
  44. self.borderStrokeWidth = 0.5
  45. self.horizontalDividerStrokeColor = colors.black
  46. self.verticalDividerStrokeColor = colors.black
  47. self.horizontalDividerStrokeWidth = 0.5
  48. self.verticalDividerStrokeWidth = 0.25
  49. self.dividerDashArray = None
  50. self.data = [['North','South','East','West'],[100,110,120,130],['A','B','C','D']] # list of rows each row is a list of columns
  51. self.boxAnchor = 'nw'
  52. #self.fontName = None
  53. self.fontSize = 8
  54. self.fontColor = colors.black
  55. self.alignment = 'right'
  56. self.textAnchor = 'start'
  57. for k, v in kw.items():
  58. if k in list(self.__class__._attrMap.keys()):
  59. setattr(self, k, v)
  60. else:
  61. raise ValueError('invalid argument supplied for class %s'%self.__class__)
  62. def demo(self):
  63. """ returns a sample of this widget with data
  64. """
  65. d = Drawing(400, 200)
  66. t = TableWidget()
  67. d.add(t, name='table')
  68. d.table.dividerDashArray = (1, 3, 2)
  69. d.table.verticalDividerStrokeColor = None
  70. d.table.borderStrokeWidth = 0
  71. d.table.borderStrokeColor = colors.red
  72. return d
  73. def draw(self):
  74. """ returns a group of shapes
  75. """
  76. g = shapes.Group()
  77. #overall border and fill
  78. if self.borderStrokeColor or self.fillColor: # adds border and filling color
  79. rect = shapes.Rect(self.x, self.y, self.width, self.height)
  80. rect.fillColor = self.fillColor
  81. rect.strokeColor = self.borderStrokeColor
  82. rect.strokeWidth = self.borderStrokeWidth
  83. g.add(rect)
  84. #special case - for an empty table we want to avoid divide-by-zero
  85. data = self.preProcessData(self.data)
  86. rows = len(self.data)
  87. cols = len(self.data[0])
  88. #print "(rows,cols)=(%s, %s)"%(rows,cols)
  89. row_step = self.height / float(rows)
  90. col_step = self.width / float(cols)
  91. #print "(row_step,col_step)=(%s, %s)"%(row_step,col_step)
  92. # draw the grid
  93. if self.horizontalDividerStrokeColor:
  94. for i in range(rows): # make horizontal lines
  95. x1 = self.x
  96. x2 = self.x + self.width
  97. y = self.y + row_step*i
  98. #print 'line (%s, %s), (%s, %s)'%(x1, y, x2, y)
  99. line = shapes.Line(x1, y, x2, y)
  100. line.strokeDashArray = self.dividerDashArray
  101. line.strokeWidth = self.horizontalDividerStrokeWidth
  102. line.strokeColor = self.horizontalDividerStrokeColor
  103. g.add(line)
  104. if self.verticalDividerStrokeColor:
  105. for i in range(cols): # make vertical lines
  106. x = self.x+col_step*i
  107. y1 = self.y
  108. y2 = self.y + self.height
  109. #print 'line (%s, %s), (%s, %s)'%(x, y1, x, y2)
  110. line = shapes.Line(x, y1, x, y2)
  111. line.strokeDashArray = self.dividerDashArray
  112. line.strokeWidth = self.verticalDividerStrokeWidth
  113. line.strokeColor = self.verticalDividerStrokeColor
  114. g.add(line)
  115. # since we plot data from down up, we reverse the list
  116. self.data.reverse()
  117. for (j, row) in enumerate(self.data):
  118. y = self.y + j*row_step + 0.5*row_step - 0.5 * self.fontSize
  119. for (i, datum) in enumerate(row):
  120. if datum:
  121. x = self.x + i*col_step + 0.5*col_step
  122. s = shapes.String(x, y, str(datum), textAnchor=self.textAnchor)
  123. s.fontName = self.fontName
  124. s.fontSize = self.fontSize
  125. s.fillColor = self.fontColor
  126. g.add(s)
  127. return g
  128. def preProcessData(self, data):
  129. """preprocess and return a new array with at least one row
  130. and column (use a None) if needed, and all rows the same
  131. length (adding Nones if needed)
  132. """
  133. if not data:
  134. return [[None]]
  135. #make all rows have similar number of cells, append None when needed
  136. max_row = max( [len(x) for x in data] )
  137. for rowNo, row in enumerate(data):
  138. if len(row) < max_row:
  139. row.extend([None]*(max_row-len(row)))
  140. return data
  141. #test
  142. if __name__ == '__main__':
  143. d = TableWidget().demo()
  144. import os
  145. d.save(formats=['pdf'],outDir=os.getcwd(),fnRoot=None)