linecharts.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762
  1. #Copyright ReportLab Europe Ltd. 2000-2017
  2. #see license.txt for license details
  3. #history https://hg.reportlab.com/hg-public/reportlab/log/tip/src/reportlab/graphics/charts/linecharts.py
  4. __version__='3.3.0'
  5. __doc__="""This modules defines a very preliminary Line Chart example."""
  6. from reportlab.lib import colors
  7. from reportlab.lib.validators import isNumber, isNumberOrNone, isColor, isColorOrNone, isListOfStrings, \
  8. isListOfStringsOrNone, SequenceOf, isBoolean, NoneOr, \
  9. isListOfNumbersOrNone, isStringOrNone, OneOf, Percentage
  10. from reportlab.lib.attrmap import *
  11. from reportlab.lib.utils import flatten
  12. from reportlab.graphics.widgetbase import Widget, TypedPropertyCollection, PropHolder, tpcGetItem
  13. from reportlab.graphics.shapes import Line, Rect, Group, Drawing, Polygon, PolyLine
  14. from reportlab.graphics.widgets.signsandsymbols import NoEntry
  15. from reportlab.graphics.charts.axes import XCategoryAxis, YValueAxis
  16. from reportlab.graphics.charts.textlabels import Label
  17. from reportlab.graphics.widgets.markers import uSymbol2Symbol, isSymbol, makeMarker
  18. from reportlab.graphics.charts.areas import PlotArea
  19. from reportlab.graphics.charts.legends import _objStr
  20. from .utils import FillPairedData
  21. class LineChartProperties(PropHolder):
  22. _attrMap = AttrMap(
  23. strokeWidth = AttrMapValue(isNumber, desc='Width of a line.'),
  24. strokeColor = AttrMapValue(isColorOrNone, desc='Color of a line or border.'),
  25. fillColor = AttrMapValue(isColorOrNone, desc='fill color of a bar.'),
  26. strokeDashArray = AttrMapValue(isListOfNumbersOrNone, desc='Dash array of a line.'),
  27. symbol = AttrMapValue(NoneOr(isSymbol), desc='Widget placed at data points.',advancedUsage=1),
  28. shader = AttrMapValue(None, desc='Shader Class.',advancedUsage=1),
  29. filler = AttrMapValue(None, desc='Filler Class.',advancedUsage=1),
  30. name = AttrMapValue(isStringOrNone, desc='Name of the line.'),
  31. lineStyle = AttrMapValue(NoneOr(OneOf('line','joinedLine','bar')), desc="What kind of plot this line is",advancedUsage=1),
  32. barWidth = AttrMapValue(isNumberOrNone,desc="Percentage of available width to be used for a bar",advancedUsage=1),
  33. inFill = AttrMapValue(isBoolean, desc='If true flood fill to x axis',advancedUsage=1),
  34. )
  35. class AbstractLineChart(PlotArea):
  36. def makeSwatchSample(self,rowNo, x, y, width, height):
  37. baseStyle = self.lines
  38. styleIdx = rowNo % len(baseStyle)
  39. style = baseStyle[styleIdx]
  40. color = style.strokeColor
  41. yh2 = y+height/2.
  42. lineStyle = getattr(style,'lineStyle',None)
  43. if lineStyle=='bar':
  44. dash = getattr(style, 'strokeDashArray', getattr(baseStyle,'strokeDashArray',None))
  45. strokeWidth= getattr(style, 'strokeWidth', getattr(style, 'strokeWidth',None))
  46. L = Rect(x,y,width,height,strokeWidth=strokeWidth,strokeColor=color,strokeLineCap=0,strokeDashArray=dash,fillColor=getattr(style,'fillColor',color))
  47. elif self.joinedLines or lineStyle=='joinedLine':
  48. dash = getattr(style, 'strokeDashArray', getattr(baseStyle,'strokeDashArray',None))
  49. strokeWidth= getattr(style, 'strokeWidth', getattr(style, 'strokeWidth',None))
  50. L = Line(x,yh2,x+width,yh2,strokeColor=color,strokeLineCap=0)
  51. if strokeWidth: L.strokeWidth = strokeWidth
  52. if dash: L.strokeDashArray = dash
  53. else:
  54. L = None
  55. if hasattr(style, 'symbol'):
  56. S = style.symbol
  57. elif hasattr(baseStyle, 'symbol'):
  58. S = baseStyle.symbol
  59. else:
  60. S = None
  61. if S: S = uSymbol2Symbol(S,x+width/2.,yh2,color)
  62. if S and L:
  63. g = Group()
  64. g.add(L)
  65. g.add(S)
  66. return g
  67. return S or L
  68. def getSeriesName(self,i,default=None):
  69. '''return series name i or default'''
  70. return _objStr(getattr(self.lines[i],'name',default))
  71. class LineChart(AbstractLineChart):
  72. pass
  73. # This is conceptually similar to the VerticalBarChart.
  74. # Still it is better named HorizontalLineChart... :-/
  75. class HorizontalLineChart(LineChart):
  76. """Line chart with multiple lines.
  77. A line chart is assumed to have one category and one value axis.
  78. Despite its generic name this particular line chart class has
  79. a vertical value axis and a horizontal category one. It may
  80. evolve into individual horizontal and vertical variants (like
  81. with the existing bar charts).
  82. Available attributes are:
  83. x: x-position of lower-left chart origin
  84. y: y-position of lower-left chart origin
  85. width: chart width
  86. height: chart height
  87. useAbsolute: disables auto-scaling of chart elements (?)
  88. lineLabelNudge: distance of data labels to data points
  89. lineLabels: labels associated with data values
  90. lineLabelFormat: format string or callback function
  91. groupSpacing: space between categories
  92. joinedLines: enables drawing of lines
  93. strokeColor: color of chart lines (?)
  94. fillColor: color for chart background (?)
  95. lines: style list, used cyclically for data series
  96. valueAxis: value axis object
  97. categoryAxis: category axis object
  98. categoryNames: category names
  99. data: chart data, a list of data series of equal length
  100. """
  101. _attrMap = AttrMap(BASE=LineChart,
  102. useAbsolute = AttrMapValue(isNumber, desc='Flag to use absolute spacing values.',advancedUsage=1),
  103. lineLabelNudge = AttrMapValue(isNumber, desc='Distance between a data point and its label.',advancedUsage=1),
  104. lineLabels = AttrMapValue(None, desc='Handle to the list of data point labels.'),
  105. lineLabelFormat = AttrMapValue(None, desc='Formatting string or function used for data point labels.'),
  106. lineLabelArray = AttrMapValue(None, desc='explicit array of line label values, must match size of data if present.'),
  107. groupSpacing = AttrMapValue(isNumber, desc='? - Likely to disappear.'),
  108. joinedLines = AttrMapValue(isNumber, desc='Display data points joined with lines if true.'),
  109. lines = AttrMapValue(None, desc='Handle of the lines.'),
  110. valueAxis = AttrMapValue(None, desc='Handle of the value axis.'),
  111. categoryAxis = AttrMapValue(None, desc='Handle of the category axis.'),
  112. categoryNames = AttrMapValue(isListOfStringsOrNone, desc='List of category names.'),
  113. data = AttrMapValue(None, desc='Data to be plotted, list of (lists of) numbers.'),
  114. inFill = AttrMapValue(isBoolean, desc='Whether infilling should be done.',advancedUsage=1),
  115. reversePlotOrder = AttrMapValue(isBoolean, desc='If true reverse plot order.',advancedUsage=1),
  116. annotations = AttrMapValue(None, desc='list of callables, will be called with self, xscale, yscale.',advancedUsage=1),
  117. )
  118. def __init__(self):
  119. LineChart.__init__(self)
  120. # Allow for a bounding rectangle.
  121. self.strokeColor = None
  122. self.fillColor = None
  123. # Named so we have less recoding for the horizontal one :-)
  124. self.categoryAxis = XCategoryAxis()
  125. self.valueAxis = YValueAxis()
  126. # This defines two series of 3 points. Just an example.
  127. self.data = [(100,110,120,130),
  128. (70, 80, 80, 90)]
  129. self.categoryNames = ('North','South','East','West')
  130. self.lines = TypedPropertyCollection(LineChartProperties)
  131. self.lines.strokeWidth = 1
  132. self.lines[0].strokeColor = colors.red
  133. self.lines[1].strokeColor = colors.green
  134. self.lines[2].strokeColor = colors.blue
  135. # control spacing. if useAbsolute = 1 then
  136. # the next parameters are in points; otherwise
  137. # they are 'proportions' and are normalized to
  138. # fit the available space.
  139. self.useAbsolute = 0 #- not done yet
  140. self.groupSpacing = 1 #5
  141. self.lineLabels = TypedPropertyCollection(Label)
  142. self.lineLabelFormat = None
  143. self.lineLabelArray = None
  144. # This says whether the origin is above or below
  145. # the data point. +10 means put the origin ten points
  146. # above the data point if value > 0, or ten
  147. # points below if data value < 0. This is different
  148. # to label dx/dy which are not dependent on the
  149. # sign of the data.
  150. self.lineLabelNudge = 10
  151. # If you have multiple series, by default they butt
  152. # together.
  153. # New line chart attributes.
  154. self.joinedLines = 1 # Connect items with straight lines.
  155. self.inFill = 0
  156. self.reversePlotOrder = 0
  157. def demo(self):
  158. """Shows basic use of a line chart."""
  159. drawing = Drawing(200, 100)
  160. data = [
  161. (13, 5, 20, 22, 37, 45, 19, 4),
  162. (14, 10, 21, 28, 38, 46, 25, 5)
  163. ]
  164. lc = HorizontalLineChart()
  165. lc.x = 20
  166. lc.y = 10
  167. lc.height = 85
  168. lc.width = 170
  169. lc.data = data
  170. lc.lines.symbol = makeMarker('Circle')
  171. drawing.add(lc)
  172. return drawing
  173. def calcPositions(self):
  174. """Works out where they go.
  175. Sets an attribute _positions which is a list of
  176. lists of (x, y) matching the data.
  177. """
  178. self._seriesCount = len(self.data)
  179. self._rowLength = max(list(map(len,self.data)))
  180. if self.useAbsolute:
  181. # Dimensions are absolute.
  182. normFactor = 1.0
  183. else:
  184. # Dimensions are normalized to fit.
  185. normWidth = self.groupSpacing
  186. availWidth = self.categoryAxis.scale(0)[1]
  187. normFactor = availWidth / normWidth
  188. self._normFactor = normFactor
  189. self._yzero = yzero = self.valueAxis.scale(0)
  190. self._hngs = hngs = 0.5 * self.groupSpacing * normFactor
  191. pairs = set()
  192. P = [].append
  193. cscale = self.categoryAxis.scale
  194. vscale = self.valueAxis.scale
  195. data = self.data
  196. n = len(data)
  197. for rowNo,row in enumerate(data):
  198. if isinstance(row, FillPairedData):
  199. other = row.other
  200. if 0<=other<n:
  201. if other==rowNo:
  202. raise ValueError('data row %r may not be paired with itself' % rowNo)
  203. t = (rowNo,other)
  204. pairs.add((min(t),max(t)))
  205. else:
  206. raise ValueError('data row %r is paired with invalid data row %r' % (rowNo, other))
  207. line = [].append
  208. for colNo,datum in enumerate(row):
  209. if datum is not None:
  210. groupX, groupWidth = cscale(colNo)
  211. x = groupX + hngs
  212. y = yzero
  213. height = vscale(datum) - y
  214. line((x, y+height))
  215. P(line.__self__)
  216. P = P.__self__
  217. #if there are some paired lines we ensure only one is created
  218. for rowNo, other in pairs:
  219. P[rowNo] = FillPairedData(P[rowNo],other)
  220. self._pairInFills = len(pairs)
  221. self._positions = P
  222. def _innerDrawLabel(self, rowNo, colNo, x, y):
  223. "Draw a label for a given item in the list."
  224. labelFmt = self.lineLabelFormat
  225. labelValue = self.data[rowNo][colNo]
  226. if labelFmt is None:
  227. labelText = None
  228. elif type(labelFmt) is str:
  229. if labelFmt == 'values':
  230. try:
  231. labelText = self.lineLabelArray[rowNo][colNo]
  232. except:
  233. labelText = None
  234. else:
  235. labelText = labelFmt % labelValue
  236. elif hasattr(labelFmt,'__call__'):
  237. labelText = labelFmt(labelValue)
  238. else:
  239. raise ValueError("Unknown formatter type %s, expected string or function"%labelFmt)
  240. if labelText:
  241. label = self.lineLabels[(rowNo, colNo)]
  242. if not label.visible: return
  243. # Make sure labels are some distance off the data point.
  244. if y > 0:
  245. label.setOrigin(x, y + self.lineLabelNudge)
  246. else:
  247. label.setOrigin(x, y - self.lineLabelNudge)
  248. label.setText(labelText)
  249. else:
  250. label = None
  251. return label
  252. def drawLabel(self, G, rowNo, colNo, x, y):
  253. '''Draw a label for a given item in the list.
  254. G must have an add method'''
  255. G.add(self._innerDrawLabel(rowNo,colNo,x,y))
  256. def makeLines(self):
  257. g = Group()
  258. labelFmt = self.lineLabelFormat
  259. P = self._positions
  260. if self.reversePlotOrder: P.reverse()
  261. lines = self.lines
  262. styleCount = len(lines)
  263. _inFill = self.inFill
  264. if (_inFill or self._pairInFills or
  265. [rowNo for rowNo in range(len(P))
  266. if getattr(lines[rowNo%styleCount],'inFill',False)]
  267. ):
  268. inFillY = self.categoryAxis._y
  269. inFillX0 = self.valueAxis._x
  270. inFillX1 = inFillX0 + self.categoryAxis._length
  271. inFillG = getattr(self,'_inFillG',g)
  272. yzero = self._yzero
  273. # Iterate over data rows.
  274. for rowNo, row in enumerate(reversed(P) if self.reversePlotOrder else P):
  275. styleIdx = rowNo % styleCount
  276. rowStyle = lines[styleIdx]
  277. strokeColor = rowStyle.strokeColor
  278. fillColor = getattr(rowStyle,'fillColor',strokeColor)
  279. inFill = getattr(rowStyle,'inFill',_inFill)
  280. dash = getattr(rowStyle, 'strokeDashArray', None)
  281. lineStyle = getattr(rowStyle,'lineStyle',None)
  282. if hasattr(rowStyle, 'strokeWidth'):
  283. strokeWidth = rowStyle.strokeWidth
  284. elif hasattr(lines, 'strokeWidth'):
  285. strokeWidth = lines.strokeWidth
  286. else:
  287. strokeWidth = None
  288. # Iterate over data columns.
  289. if lineStyle=='bar':
  290. barWidth = getattr(rowStyle,'barWidth',Percentage(50))
  291. if isinstance(barWidth,Percentage):
  292. hbw = self._hngs*barWidth*0.01
  293. else:
  294. hbw = barWidth*0.5
  295. for x, y in row:
  296. g.add(Rect(x-hbw,min(y,yzero),2*hbw,abs(y-yzero),strokeWidth=strokeWidth,strokeColor=strokeColor,fillColor=fillColor))
  297. elif self.joinedLines or lineStyle=='joinedLine':
  298. points = flatten(row)
  299. if inFill or isinstance(row,FillPairedData):
  300. filler = getattr(rowStyle, 'filler', None)
  301. if isinstance(row,FillPairedData):
  302. fpoints = points + flatten(reversed(P[row.other]))
  303. else:
  304. fpoints = [inFillX0,inFillY] + points + [inFillX1,inFillY]
  305. if filler:
  306. filler.fill(self,inFillG,rowNo,fillColor,fpoints)
  307. else:
  308. inFillG.add(Polygon(fpoints,fillColor=fillColor,strokeColor=strokeColor if strokeColor==fillColor else None,strokeWidth=strokeWidth or 0.1))
  309. if not inFill or inFill==2 or strokeColor!=fillColor:
  310. line = PolyLine(points,strokeColor=strokeColor,strokeLineCap=0,strokeLineJoin=1)
  311. if strokeWidth:
  312. line.strokeWidth = strokeWidth
  313. if dash:
  314. line.strokeDashArray = dash
  315. g.add(line)
  316. if hasattr(rowStyle, 'symbol'):
  317. uSymbol = rowStyle.symbol
  318. elif hasattr(lines, 'symbol'):
  319. uSymbol = lines.symbol
  320. else:
  321. uSymbol = None
  322. if uSymbol:
  323. for colNo,(x,y) in enumerate(row):
  324. symbol = uSymbol2Symbol(tpcGetItem(uSymbol,colNo),x,y,rowStyle.strokeColor)
  325. if symbol: g.add(symbol)
  326. # Draw item labels.
  327. for colNo, (x, y) in enumerate(row):
  328. self.drawLabel(g, rowNo, colNo, x, y)
  329. return g
  330. def draw(self):
  331. "Draws itself."
  332. vA, cA = self.valueAxis, self.categoryAxis
  333. vA.setPosition(self.x, self.y, self.height)
  334. if vA: vA.joinAxis = cA
  335. if cA: cA.joinAxis = vA
  336. vA.configure(self.data)
  337. # If zero is in chart, put x axis there, otherwise
  338. # use bottom.
  339. xAxisCrossesAt = vA.scale(0)
  340. if ((xAxisCrossesAt > self.y + self.height) or (xAxisCrossesAt < self.y)):
  341. y = self.y
  342. else:
  343. y = xAxisCrossesAt
  344. cA.setPosition(self.x, y, self.width)
  345. cA.configure(self.data)
  346. self.calcPositions()
  347. g = Group()
  348. g.add(self.makeBackground())
  349. if self.inFill:
  350. self._inFillG = Group()
  351. g.add(self._inFillG)
  352. g.add(cA)
  353. g.add(vA)
  354. cAdgl = getattr(cA,'drawGridLast',False)
  355. vAdgl = getattr(vA,'drawGridLast',False)
  356. if not cAdgl: cA.makeGrid(g,parent=self,dim=vA.getGridDims)
  357. if not vAdgl: vA.makeGrid(g,parent=self,dim=cA.getGridDims)
  358. g.add(self.makeLines())
  359. if cAdgl: cA.makeGrid(g,parent=self,dim=vA.getGridDims)
  360. if vAdgl: vA.makeGrid(g,parent=self,dim=cA.getGridDims)
  361. for a in getattr(self,'annotations',()): g.add(a(self,cA.scale,vA.scale))
  362. return g
  363. def _fakeItemKey(a):
  364. '''t, z0, z1, x, y = a[:5]'''
  365. return (-a[1],a[3],a[0],-a[4])
  366. class _FakeGroup:
  367. def __init__(self):
  368. self._data = []
  369. def add(self,what):
  370. if what: self._data.append(what)
  371. def value(self):
  372. return self._data
  373. def sort(self):
  374. self._data.sort(key=_fakeItemKey)
  375. #for t in self._data: print t
  376. class HorizontalLineChart3D(HorizontalLineChart):
  377. _attrMap = AttrMap(BASE=HorizontalLineChart,
  378. theta_x = AttrMapValue(isNumber, desc='dx/dz'),
  379. theta_y = AttrMapValue(isNumber, desc='dy/dz'),
  380. zDepth = AttrMapValue(isNumber, desc='depth of an individual series'),
  381. zSpace = AttrMapValue(isNumber, desc='z gap around series'),
  382. )
  383. theta_x = .5
  384. theta_y = .5
  385. zDepth = 10
  386. zSpace = 3
  387. def calcPositions(self):
  388. HorizontalLineChart.calcPositions(self)
  389. nSeries = self._seriesCount
  390. zSpace = self.zSpace
  391. zDepth = self.zDepth
  392. if self.categoryAxis.style=='parallel_3d':
  393. _3d_depth = nSeries*zDepth+(nSeries+1)*zSpace
  394. else:
  395. _3d_depth = zDepth + 2*zSpace
  396. self._3d_dx = self.theta_x*_3d_depth
  397. self._3d_dy = self.theta_y*_3d_depth
  398. def _calc_z0(self,rowNo):
  399. zSpace = self.zSpace
  400. if self.categoryAxis.style=='parallel_3d':
  401. z0 = rowNo*(self.zDepth+zSpace)+zSpace
  402. else:
  403. z0 = zSpace
  404. return z0
  405. def _zadjust(self,x,y,z):
  406. return x+z*self.theta_x, y+z*self.theta_y
  407. def makeLines(self):
  408. labelFmt = self.lineLabelFormat
  409. P = list(range(len(self._positions)))
  410. if self.reversePlotOrder: P.reverse()
  411. inFill = self.inFill
  412. assert not inFill, "inFill not supported for 3d yet"
  413. #if inFill:
  414. #inFillY = self.categoryAxis._y
  415. #inFillX0 = self.valueAxis._x
  416. #inFillX1 = inFillX0 + self.categoryAxis._length
  417. #inFillG = getattr(self,'_inFillG',g)
  418. zDepth = self.zDepth
  419. _zadjust = self._zadjust
  420. theta_x = self.theta_x
  421. theta_y = self.theta_y
  422. F = _FakeGroup()
  423. from reportlab.graphics.charts.utils3d import _make_3d_line_info
  424. tileWidth = getattr(self,'_3d_tilewidth',None)
  425. if not tileWidth and self.categoryAxis.style!='parallel_3d': tileWidth = 1
  426. # Iterate over data rows.
  427. for rowNo in P:
  428. row = self._positions[rowNo]
  429. n = len(row)
  430. styleCount = len(self.lines)
  431. styleIdx = rowNo % styleCount
  432. rowStyle = self.lines[styleIdx]
  433. rowColor = rowStyle.strokeColor
  434. dash = getattr(rowStyle, 'strokeDashArray', None)
  435. z0 = self._calc_z0(rowNo)
  436. z1 = z0 + zDepth
  437. if hasattr(self.lines[styleIdx], 'strokeWidth'):
  438. strokeWidth = self.lines[styleIdx].strokeWidth
  439. elif hasattr(self.lines, 'strokeWidth'):
  440. strokeWidth = self.lines.strokeWidth
  441. else:
  442. strokeWidth = None
  443. # Iterate over data columns.
  444. if self.joinedLines:
  445. if n:
  446. x0, y0 = row[0]
  447. for colNo in range(1,n):
  448. x1, y1 = row[colNo]
  449. _make_3d_line_info( F, x0, x1, y0, y1, z0, z1,
  450. theta_x, theta_y,
  451. rowColor, fillColorShaded=None, tileWidth=tileWidth,
  452. strokeColor=None, strokeWidth=None, strokeDashArray=None,
  453. shading=0.1)
  454. x0, y0 = x1, y1
  455. if hasattr(self.lines[styleIdx], 'symbol'):
  456. uSymbol = self.lines[styleIdx].symbol
  457. elif hasattr(self.lines, 'symbol'):
  458. uSymbol = self.lines.symbol
  459. else:
  460. uSymbol = None
  461. if uSymbol:
  462. for colNo in range(n):
  463. x1, y1 = row[colNo]
  464. x1, y1 = _zadjust(x1,y1,z0)
  465. symbol = uSymbol2Symbol(uSymbol,x1,y1,rowColor)
  466. if symbol: F.add((2,z0,z0,x1,y1,symbol))
  467. # Draw item labels.
  468. for colNo in range(n):
  469. x1, y1 = row[colNo]
  470. x1, y1 = _zadjust(x1,y1,z0)
  471. L = self._innerDrawLabel(rowNo, colNo, x1, y1)
  472. if L: F.add((2,z0,z0,x1,y1,L))
  473. F.sort()
  474. g = Group()
  475. for v in F.value(): g.add(v[-1])
  476. return g
  477. class VerticalLineChart(LineChart):
  478. pass
  479. def sample1():
  480. drawing = Drawing(400, 200)
  481. data = [
  482. (13, 5, 20, 22, 37, 45, 19, 4),
  483. (5, 20, 46, 38, 23, 21, 6, 14)
  484. ]
  485. lc = HorizontalLineChart()
  486. lc.x = 50
  487. lc.y = 50
  488. lc.height = 125
  489. lc.width = 300
  490. lc.data = data
  491. lc.joinedLines = 1
  492. lc.lines.symbol = makeMarker('FilledDiamond')
  493. lc.lineLabelFormat = '%2.0f'
  494. catNames = 'Jan Feb Mar Apr May Jun Jul Aug'.split(' ')
  495. lc.categoryAxis.categoryNames = catNames
  496. lc.categoryAxis.labels.boxAnchor = 'n'
  497. lc.valueAxis.valueMin = 0
  498. lc.valueAxis.valueMax = 60
  499. lc.valueAxis.valueStep = 15
  500. drawing.add(lc)
  501. return drawing
  502. class SampleHorizontalLineChart(HorizontalLineChart):
  503. "Sample class overwriting one method to draw additional horizontal lines."
  504. def demo(self):
  505. """Shows basic use of a line chart."""
  506. drawing = Drawing(200, 100)
  507. data = [
  508. (13, 5, 20, 22, 37, 45, 19, 4),
  509. (14, 10, 21, 28, 38, 46, 25, 5)
  510. ]
  511. lc = SampleHorizontalLineChart()
  512. lc.x = 20
  513. lc.y = 10
  514. lc.height = 85
  515. lc.width = 170
  516. lc.data = data
  517. lc.strokeColor = colors.white
  518. lc.fillColor = colors.HexColor(0xCCCCCC)
  519. drawing.add(lc)
  520. return drawing
  521. def makeBackground(self):
  522. g = Group()
  523. g.add(HorizontalLineChart.makeBackground(self))
  524. valAxis = self.valueAxis
  525. valTickPositions = valAxis._tickValues
  526. for y in valTickPositions:
  527. y = valAxis.scale(y)
  528. g.add(Line(self.x, y, self.x+self.width, y,
  529. strokeColor = self.strokeColor))
  530. return g
  531. def sample1a():
  532. drawing = Drawing(400, 200)
  533. data = [
  534. (13, 5, 20, 22, 37, 45, 19, 4),
  535. (5, 20, 46, 38, 23, 21, 6, 14)
  536. ]
  537. lc = SampleHorizontalLineChart()
  538. lc.x = 50
  539. lc.y = 50
  540. lc.height = 125
  541. lc.width = 300
  542. lc.data = data
  543. lc.joinedLines = 1
  544. lc.strokeColor = colors.white
  545. lc.fillColor = colors.HexColor(0xCCCCCC)
  546. lc.lines.symbol = makeMarker('FilledDiamond')
  547. lc.lineLabelFormat = '%2.0f'
  548. catNames = 'Jan Feb Mar Apr May Jun Jul Aug'.split(' ')
  549. lc.categoryAxis.categoryNames = catNames
  550. lc.categoryAxis.labels.boxAnchor = 'n'
  551. lc.valueAxis.valueMin = 0
  552. lc.valueAxis.valueMax = 60
  553. lc.valueAxis.valueStep = 15
  554. drawing.add(lc)
  555. return drawing
  556. def sample2():
  557. drawing = Drawing(400, 200)
  558. data = [
  559. (13, 5, 20, 22, 37, 45, 19, 4),
  560. (5, 20, 46, 38, 23, 21, 6, 14)
  561. ]
  562. lc = HorizontalLineChart()
  563. lc.x = 50
  564. lc.y = 50
  565. lc.height = 125
  566. lc.width = 300
  567. lc.data = data
  568. lc.joinedLines = 1
  569. lc.lines.symbol = makeMarker('Smiley')
  570. lc.lineLabelFormat = '%2.0f'
  571. lc.strokeColor = colors.black
  572. lc.fillColor = colors.lightblue
  573. catNames = 'Jan Feb Mar Apr May Jun Jul Aug'.split(' ')
  574. lc.categoryAxis.categoryNames = catNames
  575. lc.categoryAxis.labels.boxAnchor = 'n'
  576. lc.valueAxis.valueMin = 0
  577. lc.valueAxis.valueMax = 60
  578. lc.valueAxis.valueStep = 15
  579. drawing.add(lc)
  580. return drawing
  581. def sample3():
  582. drawing = Drawing(400, 200)
  583. data = [
  584. (13, 5, 20, 22, 37, 45, 19, 4),
  585. (5, 20, 46, 38, 23, 21, 6, 14)
  586. ]
  587. lc = HorizontalLineChart()
  588. lc.x = 50
  589. lc.y = 50
  590. lc.height = 125
  591. lc.width = 300
  592. lc.data = data
  593. lc.joinedLines = 1
  594. lc.lineLabelFormat = '%2.0f'
  595. lc.strokeColor = colors.black
  596. lc.lines[0].symbol = makeMarker('Smiley')
  597. lc.lines[1].symbol = NoEntry
  598. lc.lines[0].strokeWidth = 2
  599. lc.lines[1].strokeWidth = 4
  600. catNames = 'Jan Feb Mar Apr May Jun Jul Aug'.split(' ')
  601. lc.categoryAxis.categoryNames = catNames
  602. lc.categoryAxis.labels.boxAnchor = 'n'
  603. lc.valueAxis.valueMin = 0
  604. lc.valueAxis.valueMax = 60
  605. lc.valueAxis.valueStep = 15
  606. drawing.add(lc)
  607. return drawing
  608. def sampleCandleStick():
  609. from reportlab.graphics.widgetbase import CandleSticks
  610. d = Drawing(400, 200)
  611. chart = HorizontalLineChart()
  612. d.add(chart)
  613. chart.y = 20
  614. boxMid = (100, 110, 120, 130)
  615. hi = [m+10 for m in boxMid]
  616. lo = [m-10 for m in boxMid]
  617. boxHi = [m+6 for m in boxMid]
  618. boxLo = [m-4 for m in boxMid]
  619. boxFillColor = colors.pink
  620. boxWidth = 20
  621. crossWidth = 10
  622. candleStrokeWidth = 0.5
  623. candleStrokeColor = colors.black
  624. chart.valueAxis.avoidBoundSpace = 5
  625. chart.valueAxis.valueMin = min(min(boxMid),min(hi),min(lo),min(boxLo),min(boxHi))
  626. chart.valueAxis.valueMax = max(max(boxMid),max(hi),max(lo),max(boxLo),max(boxHi))
  627. lines = chart.lines
  628. lines[0].strokeColor = None
  629. I = range(len(boxMid))
  630. chart.data = [boxMid]
  631. lines[0].symbol = candles = CandleSticks(chart=chart, boxFillColor=boxFillColor, boxWidth=boxWidth, crossWidth=crossWidth, strokeWidth=candleStrokeWidth, strokeColor=candleStrokeColor)
  632. for i in I: candles[i].setProperties(dict(position=i,boxMid=boxMid[i],crossLo=lo[i],crossHi=hi[i],boxLo=boxLo[i],boxHi=boxHi[i]))
  633. return d