dotbox.py 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. from reportlab.lib.colors import blue, _PCMYK_black
  2. from reportlab.graphics.charts.textlabels import Label
  3. from reportlab.graphics.shapes import Circle, Drawing, Group, Line, Rect, String
  4. from reportlab.graphics.widgetbase import Widget
  5. from reportlab.lib.attrmap import *
  6. from reportlab.lib.validators import *
  7. from reportlab.lib.units import cm
  8. from reportlab.pdfbase.pdfmetrics import getFont
  9. from reportlab.graphics.charts.lineplots import _maxWidth
  10. class DotBox(Widget):
  11. """Returns a dotbox widget."""
  12. #Doesn't use TypedPropertyCollection for labels - this can be a later improvement
  13. _attrMap = AttrMap(
  14. xlabels = AttrMapValue(isNoneOrListOfNoneOrStrings,
  15. desc="List of text labels for boxes on left hand side"),
  16. ylabels = AttrMapValue(isNoneOrListOfNoneOrStrings,
  17. desc="Text label for second box on left hand side"),
  18. labelFontName = AttrMapValue(isString,
  19. desc="Name of font used for the labels"),
  20. labelFontSize = AttrMapValue(isNumber,
  21. desc="Size of font used for the labels"),
  22. labelOffset = AttrMapValue(isNumber,
  23. desc="Space between label text and grid edge"),
  24. strokeWidth = AttrMapValue(isNumber,
  25. desc='Width of the grid and dot outline'),
  26. gridDivWidth = AttrMapValue(isNumber,
  27. desc="Width of each 'box'"),
  28. gridColor = AttrMapValue(isColor,
  29. desc='Colour for the box and gridding'),
  30. dotDiameter = AttrMapValue(isNumber,
  31. desc="Diameter of the circle used for the 'dot'"),
  32. dotColor = AttrMapValue(isColor,
  33. desc='Colour of the circle on the box'),
  34. dotXPosition = AttrMapValue(isNumber,
  35. desc='X Position of the circle'),
  36. dotYPosition = AttrMapValue(isNumber,
  37. desc='X Position of the circle'),
  38. x = AttrMapValue(isNumber,
  39. desc='X Position of dotbox'),
  40. y = AttrMapValue(isNumber,
  41. desc='Y Position of dotbox'),
  42. )
  43. def __init__(self):
  44. self.xlabels=["Value", "Blend", "Growth"]
  45. self.ylabels=["Small", "Medium", "Large"]
  46. self.labelFontName = "Helvetica"
  47. self.labelFontSize = 6
  48. self.labelOffset = 5
  49. self.strokeWidth = 0.5
  50. self.gridDivWidth=0.5*cm
  51. self.gridColor=colors.Color(25/255.0,77/255.0,135/255.0)
  52. self.dotDiameter=0.4*cm
  53. self.dotColor=colors.Color(232/255.0,224/255.0,119/255.0)
  54. self.dotXPosition = 1
  55. self.dotYPosition = 1
  56. self.x = 30
  57. self.y = 5
  58. def _getDrawingDimensions(self):
  59. leftPadding=rightPadding=topPadding=bottomPadding=5
  60. #find width of grid
  61. tx=len(self.xlabels)*self.gridDivWidth
  62. #add padding (and offset)
  63. tx=tx+leftPadding+rightPadding+self.labelOffset
  64. #add in maximum width of text
  65. tx=tx+_maxWidth(self.xlabels, self.labelFontName, self.labelFontSize)
  66. #find height of grid
  67. ty=len(self.ylabels)*self.gridDivWidth
  68. #add padding (and offset)
  69. ty=ty+topPadding+bottomPadding+self.labelOffset
  70. #add in maximum width of text
  71. ty=ty+_maxWidth(self.ylabels, self.labelFontName, self.labelFontSize)
  72. #print (tx, ty)
  73. return (tx,ty)
  74. def demo(self,drawing=None):
  75. if not drawing:
  76. tx,ty=self._getDrawingDimensions()
  77. drawing = Drawing(tx,ty)
  78. drawing.add(self.draw())
  79. return drawing
  80. def draw(self):
  81. g = Group()
  82. #box
  83. g.add(Rect(self.x,self.y,len(self.xlabels)*self.gridDivWidth,len(self.ylabels)*self.gridDivWidth,
  84. strokeColor=self.gridColor,
  85. strokeWidth=self.strokeWidth,
  86. fillColor=None))
  87. #internal gridding
  88. for f in range (1,len(self.ylabels)):
  89. #horizontal
  90. g.add(Line(strokeColor=self.gridColor,
  91. strokeWidth=self.strokeWidth,
  92. x1 = self.x,
  93. y1 = self.y+f*self.gridDivWidth,
  94. x2 = self.x+len(self.xlabels)*self.gridDivWidth,
  95. y2 = self.y+f*self.gridDivWidth))
  96. for f in range (1,len(self.xlabels)):
  97. #vertical
  98. g.add(Line(strokeColor=self.gridColor,
  99. strokeWidth=self.strokeWidth,
  100. x1 = self.x+f*self.gridDivWidth,
  101. y1 = self.y,
  102. x2 = self.x+f*self.gridDivWidth,
  103. y2 = self.y+len(self.ylabels)*self.gridDivWidth))
  104. # draw the 'dot'
  105. g.add(Circle(strokeColor=self.gridColor,
  106. strokeWidth=self.strokeWidth,
  107. fillColor=self.dotColor,
  108. cx = self.x+(self.dotXPosition*self.gridDivWidth),
  109. cy = self.y+(self.dotYPosition*self.gridDivWidth),
  110. r = self.dotDiameter/2.0))
  111. #used for centering y-labels (below)
  112. ascent=getFont(self.labelFontName).face.ascent
  113. if ascent==0:
  114. ascent=0.718 # default (from helvetica)
  115. ascent=ascent*self.labelFontSize # normalize
  116. #do y-labels
  117. if self.ylabels != None:
  118. for f in range (len(self.ylabels)-1,-1,-1):
  119. if self.ylabels[f]!= None:
  120. g.add(String(strokeColor=self.gridColor,
  121. text = self.ylabels[f],
  122. fontName = self.labelFontName,
  123. fontSize = self.labelFontSize,
  124. fillColor=_PCMYK_black,
  125. x = self.x-self.labelOffset,
  126. y = self.y+(f*self.gridDivWidth+(self.gridDivWidth-ascent)/2.0),
  127. textAnchor = 'end'))
  128. #do x-labels
  129. if self.xlabels != None:
  130. for f in range (0,len(self.xlabels)):
  131. if self.xlabels[f]!= None:
  132. l=Label()
  133. l.x=self.x+(f*self.gridDivWidth)+(self.gridDivWidth+ascent)/2.0
  134. l.y=self.y+(len(self.ylabels)*self.gridDivWidth)+self.labelOffset
  135. l.angle=90
  136. l.textAnchor='start'
  137. l.fontName = self.labelFontName
  138. l.fontSize = self.labelFontSize
  139. l.fillColor = _PCMYK_black
  140. l.setText(self.xlabels[f])
  141. l.boxAnchor = 'sw'
  142. l.draw()
  143. g.add(l)
  144. return g
  145. if __name__ == "__main__":
  146. d = DotBox()
  147. d.demo().save(fnRoot="dotbox")