lineplots.py 48 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213
  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/lineplots.py
  4. __version__='3.3.0'
  5. __doc__="""This module defines a very preliminary Line Plot example."""
  6. import string, time
  7. from reportlab.lib import colors
  8. from reportlab.lib.validators import *
  9. from reportlab.lib.attrmap import *
  10. from reportlab.lib.utils import flatten, isStr
  11. from reportlab.graphics.shapes import Drawing, Group, Rect, Line, PolyLine, Polygon, _SetKeyWordArgs
  12. from reportlab.graphics.widgetbase import Widget, TypedPropertyCollection, PropHolder, tpcGetItem
  13. from reportlab.graphics.charts.textlabels import Label
  14. from reportlab.graphics.charts.axes import XValueAxis, YValueAxis, AdjYValueAxis, NormalDateXValueAxis
  15. from reportlab.graphics.charts.utils import *
  16. from reportlab.graphics.widgets.markers import uSymbol2Symbol, isSymbol, makeMarker
  17. from reportlab.graphics.widgets.grids import Grid, DoubleGrid, ShadedRect, ShadedPolygon
  18. from reportlab.pdfbase.pdfmetrics import stringWidth, getFont
  19. from reportlab.graphics.charts.areas import PlotArea
  20. from .utils import FillPairedData
  21. # This might be moved again from here...
  22. class LinePlotProperties(PropHolder):
  23. _attrMap = AttrMap(
  24. strokeWidth = AttrMapValue(isNumber, desc='Width of a line.'),
  25. strokeColor = AttrMapValue(isColorOrNone, desc='Color of a line.'),
  26. strokeDashArray = AttrMapValue(isListOfNumbersOrNone, desc='Dash array of a line.'),
  27. fillColor = AttrMapValue(isColorOrNone, desc='Color of infill defaults to the strokeColor.'),
  28. symbol = AttrMapValue(None, desc='Widget placed at data points.',advancedUsage=1),
  29. shader = AttrMapValue(None, desc='Shader Class.',advancedUsage=1),
  30. filler = AttrMapValue(None, desc='Filler Class.',advancedUsage=1),
  31. name = AttrMapValue(isStringOrNone, desc='Name of the line.'),
  32. inFill = AttrMapValue(isBoolean, desc='If true flood fill to x axis',advancedUsage=1),
  33. )
  34. class InFillValue(int):
  35. def __new__(cls,v,yValue=None):
  36. self = int.__new__(cls,v)
  37. self.yValue = yValue
  38. return self
  39. class Shader(_SetKeyWordArgs):
  40. _attrMap = AttrMap(BASE=PlotArea,
  41. vertical = AttrMapValue(isBoolean, desc='If true shade to x axis'),
  42. colors = AttrMapValue(SequenceOf(isColorOrNone,lo=2,hi=2), desc='(AxisColor, LineColor)'),
  43. )
  44. def shade(self, lp, g, rowNo, rowColor, row):
  45. c = [None,None]
  46. c = getattr(self,'colors',c) or c
  47. if not c[0]: c[0] = getattr(lp,'fillColor',colors.white)
  48. if not c[1]: c[1] = rowColor
  49. class NoFiller:
  50. def fill(self, lp, g, rowNo, rowColor, points):
  51. pass
  52. class Filler:
  53. '''mixin providing simple polygon fill'''
  54. _attrMap = AttrMap(
  55. fillColor = AttrMapValue(isColorOrNone, desc='filler interior color'),
  56. strokeColor = AttrMapValue(isColorOrNone, desc='filler edge color'),
  57. strokeWidth = AttrMapValue(isNumberOrNone, desc='filler edge width'),
  58. )
  59. def __init__(self,**kw):
  60. self.__dict__ = kw
  61. def fill(self, lp, g, rowNo, rowColor, points):
  62. g.add(Polygon(points,
  63. fillColor=getattr(self,'fillColor',rowColor),
  64. strokeColor=getattr(self,'strokeColor',rowColor),
  65. strokeWidth=getattr(self,'strokeWidth',0.1)))
  66. class ShadedPolyFiller(Filler,ShadedPolygon):
  67. pass
  68. class PolyFiller(Filler,Polygon):
  69. pass
  70. from reportlab.graphics.charts.linecharts import AbstractLineChart
  71. class LinePlot(AbstractLineChart):
  72. """Line plot with multiple lines.
  73. Both x- and y-axis are value axis (so there are no seperate
  74. X and Y versions of this class).
  75. """
  76. _attrMap = AttrMap(BASE=PlotArea,
  77. reversePlotOrder = AttrMapValue(isBoolean, desc='If true reverse plot order.',advancedUsage=1),
  78. lineLabelNudge = AttrMapValue(isNumber, desc='Distance between a data point and its label.',advancedUsage=1),
  79. lineLabels = AttrMapValue(None, desc='Handle to the list of data point labels.'),
  80. lineLabelFormat = AttrMapValue(None, desc='Formatting string or function used for data point labels.'),
  81. lineLabelArray = AttrMapValue(None, desc='explicit array of line label values, must match size of data if present.'),
  82. joinedLines = AttrMapValue(isNumber, desc='Display data points joined with lines if true.'),
  83. strokeColor = AttrMapValue(isColorOrNone, desc='Color used for background border of plot area.'),
  84. fillColor = AttrMapValue(isColorOrNone, desc='Color used for background interior of plot area.'),
  85. lines = AttrMapValue(None, desc='Handle of the lines.'),
  86. xValueAxis = AttrMapValue(None, desc='Handle of the x axis.'),
  87. yValueAxis = AttrMapValue(None, desc='Handle of the y axis.'),
  88. data = AttrMapValue(None, desc='Data to be plotted, list of (lists of) x/y tuples.'),
  89. annotations = AttrMapValue(None, desc='list of callables, will be called with self, xscale, yscale.',advancedUsage=1),
  90. behindAxes = AttrMapValue(isBoolean, desc='If true use separate line group.',advancedUsage=1),
  91. gridFirst = AttrMapValue(isBoolean, desc='If true use draw grids before axes.',advancedUsage=1),
  92. )
  93. def __init__(self):
  94. PlotArea.__init__(self)
  95. self.reversePlotOrder = 0
  96. self.xValueAxis = XValueAxis()
  97. self.yValueAxis = YValueAxis()
  98. # this defines two series of 3 points. Just an example.
  99. self.data = [
  100. ((1,1), (2,2), (2.5,1), (3,3), (4,5)),
  101. ((1,2), (2,3), (2.5,2), (3,4), (4,6))
  102. ]
  103. self.lines = TypedPropertyCollection(LinePlotProperties)
  104. self.lines.strokeWidth = 1
  105. self.lines[0].strokeColor = colors.red
  106. self.lines[1].strokeColor = colors.blue
  107. self.lineLabels = TypedPropertyCollection(Label)
  108. self.lineLabelFormat = None
  109. self.lineLabelArray = None
  110. # this says whether the origin is inside or outside
  111. # the bar - +10 means put the origin ten points
  112. # above the tip of the bar if value > 0, or ten
  113. # points inside if bar value < 0. This is different
  114. # to label dx/dy which are not dependent on the
  115. # sign of the data.
  116. self.lineLabelNudge = 10
  117. # if you have multiple series, by default they butt
  118. # together.
  119. # New line chart attributes.
  120. self.joinedLines = 1 # Connect items with straight lines.
  121. #private attributes
  122. self._inFill = None
  123. self.annotations = []
  124. self.behindAxes = 0
  125. self.gridFirst = 0
  126. def demo(self):
  127. """Shows basic use of a line chart."""
  128. drawing = Drawing(400, 200)
  129. data = [
  130. ((1,1), (2,2), (2.5,1), (3,3), (4,5)),
  131. ((1,2), (2,3), (2.5,2), (3.5,5), (4,6))
  132. ]
  133. lp = LinePlot()
  134. lp.x = 50
  135. lp.y = 50
  136. lp.height = 125
  137. lp.width = 300
  138. lp.data = data
  139. lp.joinedLines = 1
  140. lp.lineLabelFormat = '%2.0f'
  141. lp.strokeColor = colors.black
  142. lp.lines[0].strokeColor = colors.red
  143. lp.lines[0].symbol = makeMarker('FilledCircle')
  144. lp.lines[1].strokeColor = colors.blue
  145. lp.lines[1].symbol = makeMarker('FilledDiamond')
  146. lp.xValueAxis.valueMin = 0
  147. lp.xValueAxis.valueMax = 5
  148. lp.xValueAxis.valueStep = 1
  149. lp.yValueAxis.valueMin = 0
  150. lp.yValueAxis.valueMax = 7
  151. lp.yValueAxis.valueStep = 1
  152. drawing.add(lp)
  153. return drawing
  154. def calcPositions(self):
  155. """Works out where they go.
  156. Sets an attribute _positions which is a list of
  157. lists of (x, y) matching the data.
  158. """
  159. self._seriesCount = len(self.data)
  160. self._rowLength = max(list(map(len,self.data)))
  161. pairs = set()
  162. P = [].append
  163. xscale = self.xValueAxis.scale
  164. yscale = self.yValueAxis.scale
  165. data = self.data
  166. n = len(data)
  167. for rowNo, row in enumerate(data):
  168. if isinstance(row, FillPairedData):
  169. other = row.other
  170. if 0<=other<n:
  171. if other==rowNo:
  172. raise ValueError('data row %r may not be paired with itself' % rowNo)
  173. pairs.add((rowNo,other))
  174. else:
  175. raise ValueError('data row %r is paired with invalid data row %r' % (rowNo, other))
  176. line = [].append
  177. for colNo, datum in enumerate(row):
  178. xv = datum[0]
  179. line(
  180. (
  181. xscale(mktime(mkTimeTuple(xv))) if isStr(xv) else xscale(xv),
  182. yscale(datum[1])
  183. )
  184. )
  185. P(line.__self__)
  186. P = P.__self__
  187. #if there are some paired lines we ensure only one is created
  188. for rowNo, other in pairs:
  189. P[rowNo] = FillPairedData(P[rowNo],other)
  190. self._pairInFills = len(pairs)
  191. self._positions = P
  192. def _innerDrawLabel(self, rowNo, colNo, x, y):
  193. "Draw a label for a given item in the list."
  194. labelFmt = self.lineLabelFormat
  195. labelValue = self.data[rowNo][colNo][1] ###
  196. if labelFmt is None:
  197. labelText = None
  198. elif isinstance(labelFmt,str):
  199. if labelFmt == 'values':
  200. labelText = self.lineLabelArray[rowNo][colNo]
  201. else:
  202. labelText = labelFmt % labelValue
  203. elif hasattr(labelFmt,'__call__'):
  204. if not hasattr(labelFmt,'__labelFmtEX__'):
  205. labelText = labelFmt(labelValue)
  206. else:
  207. labelText = labelFmt(self,rowNo,colNo,x,y)
  208. else:
  209. raise ValueError("Unknown formatter type %s, expected string or function"% labelFmt)
  210. if labelText:
  211. label = self.lineLabels[(rowNo, colNo)]
  212. if not label.visible: return
  213. #hack to make sure labels are outside the bar
  214. if y > 0:
  215. label.setOrigin(x, y + self.lineLabelNudge)
  216. else:
  217. label.setOrigin(x, y - self.lineLabelNudge)
  218. label.setText(labelText)
  219. else:
  220. label = None
  221. return label
  222. def drawLabel(self, G, rowNo, colNo, x, y):
  223. '''Draw a label for a given item in the list.
  224. G must have an add method'''
  225. G.add(self._innerDrawLabel(rowNo,colNo,x,y))
  226. def makeLines(self):
  227. g = Group()
  228. yA = self.yValueAxis
  229. xA = self.xValueAxis
  230. bubblePlot = getattr(self,'_bubblePlot',None)
  231. if bubblePlot:
  232. bubbleR = min(yA._bubbleRadius,xA._bubbleRadius)
  233. bubbleMax = xA._bubbleMax
  234. labelFmt = self.lineLabelFormat
  235. P = self._positions
  236. _inFill = getattr(self,'_inFill',None)
  237. lines = self.lines
  238. styleCount = len(lines)
  239. if (_inFill or self._pairInFills or
  240. [rowNo for rowNo in range(len(P))
  241. if getattr(lines[rowNo%styleCount],'inFill',False)]
  242. ):
  243. inFillY = getattr(_inFill,'yValue',None)
  244. if inFillY is None:
  245. inFillY = xA._y
  246. else:
  247. inFillY = yA.scale(inFillY)
  248. inFillX0 = yA._x
  249. inFillX1 = inFillX0 + xA._length
  250. inFillG = getattr(self,'_inFillG',g)
  251. lG = getattr(self,'_lineG',g)
  252. # Iterate over data rows.
  253. R = range(len(P))
  254. if self.reversePlotOrder: R = reversed(R)
  255. for rowNo in R:
  256. row = P[rowNo]
  257. styleRowNo = rowNo % styleCount
  258. rowStyle = lines[styleRowNo]
  259. strokeColor = getattr(rowStyle,'strokeColor',None)
  260. fillColor = getattr(rowStyle, 'fillColor', strokeColor)
  261. inFill = getattr(rowStyle,'inFill',_inFill)
  262. dash = getattr(rowStyle, 'strokeDashArray', None)
  263. if hasattr(rowStyle, 'strokeWidth'):
  264. width = rowStyle.strokeWidth
  265. elif hasattr(lines, 'strokeWidth'):
  266. width = lines.strokeWidth
  267. else:
  268. width = None
  269. # Iterate over data columns.
  270. if self.joinedLines:
  271. points = flatten(row)
  272. if inFill or isinstance(row,FillPairedData):
  273. filler = getattr(rowStyle, 'filler', None)
  274. if isinstance(row,FillPairedData):
  275. fpoints = points + flatten(reversed(P[row.other]))
  276. else:
  277. fpoints = [inFillX0,inFillY] + points + [inFillX1,inFillY]
  278. if filler:
  279. filler.fill(self,inFillG,rowNo,fillColor,fpoints)
  280. else:
  281. inFillG.add(Polygon(fpoints,fillColor=fillColor,strokeColor=strokeColor if strokeColor==fillColor else None,strokeWidth=width or 0.1))
  282. if not inFill or inFill==2 or strokeColor!=fillColor:
  283. line = PolyLine(points,strokeColor=strokeColor,strokeLineCap=0,strokeLineJoin=1)
  284. if width:
  285. line.strokeWidth = width
  286. if dash:
  287. line.strokeDashArray = dash
  288. lG.add(line)
  289. if hasattr(rowStyle, 'symbol'):
  290. uSymbol = rowStyle.symbol
  291. elif hasattr(lines, 'symbol'):
  292. uSymbol = lines.symbol
  293. else:
  294. uSymbol = None
  295. if uSymbol:
  296. if bubblePlot: drow = self.data[rowNo]
  297. for j,xy in enumerate(row):
  298. if (styleRowNo,j) in lines:
  299. juSymbol = getattr(lines[styleRowNo,j],'symbol',uSymbol)
  300. else:
  301. juSymbol = uSymbol
  302. if juSymbol is uSymbol:
  303. symbol = uSymbol
  304. symColor = strokeColor
  305. else:
  306. symbol = juSymbol
  307. symColor = getattr(symbol,'fillColor',strokeColor)
  308. symbol = uSymbol2Symbol(tpcGetItem(symbol,j),xy[0],xy[1],symColor)
  309. if symbol:
  310. if bubblePlot:
  311. symbol.size = bubbleR*(drow[j][2]/bubbleMax)**0.5
  312. g.add(symbol)
  313. else:
  314. if bubblePlot: drow = self.data[rowNo]
  315. for j,xy in enumerate(row):
  316. juSymbol = getattr(lines[styleRowNo,j],'symbol',None)
  317. if not juSymbol: continue
  318. symColor = getattr(juSymbol,'fillColor',getattr(juSymbol,'strokeColor',strokeColor))
  319. symbol = uSymbol2Symbol(juSymbol,xy[0],xy[1],symColor)
  320. if symbol:
  321. if bubblePlot:
  322. symbol.size = bubbleR*(drow[j][2]/bubbleMax)**0.5
  323. g.add(symbol)
  324. # Draw data labels.
  325. for colNo,datum in enumerate(row):
  326. x1, y1 = datum
  327. self.drawLabel(g, rowNo, colNo, x1, y1)
  328. shader = getattr(rowStyle, 'shader', None)
  329. if shader: shader.shade(self,g,rowNo,strokeColor,row)
  330. return g
  331. def draw(self):
  332. yA = self.yValueAxis
  333. xA = self.xValueAxis
  334. if getattr(self,'_bubblePlot',None):
  335. yA._bubblePlot = xA._bubblePlot = 1
  336. yA.setPosition(self.x, self.y, self.height)
  337. if yA: yA.joinAxis = xA
  338. if xA: xA.joinAxis = yA
  339. yA.configure(self.data)
  340. # if zero is in chart, put x axis there, otherwise use bottom.
  341. xAxisCrossesAt = yA.scale(0)
  342. if ((xAxisCrossesAt > self.y + self.height) or (xAxisCrossesAt < self.y)):
  343. y = self.y
  344. else:
  345. y = xAxisCrossesAt
  346. xA.setPosition(self.x, y, self.width)
  347. xA.configure(self.data)
  348. self.calcPositions()
  349. g = Group()
  350. g.add(self.makeBackground())
  351. if self._inFill or self.behindAxes:
  352. xA._joinToAxis()
  353. if self._inFill:
  354. self._inFillG = Group()
  355. g.add(self._inFillG)
  356. if self.behindAxes:
  357. self._lineG = Group()
  358. g.add(self._lineG)
  359. xA._joinToAxis()
  360. yA._joinToAxis()
  361. xAex = xA.visibleAxis and [xA._y] or []
  362. yAex = yA.visibleAxis and [yA._x] or []
  363. skipGrid = getattr(xA,'skipGrid','none')
  364. if skipGrid!=None:
  365. if skipGrid in ('both','top'):
  366. yAex.append(xA._x+xA._length)
  367. if skipGrid in ('both','bottom'):
  368. yAex.append(xA._x)
  369. skipGrid = getattr(yA,'skipGrid','none')
  370. if skipGrid!=None:
  371. if skipGrid in ('both','top'):
  372. xAex.append(yA._y+yA._length)
  373. if skipGrid in ('both','bottom'):
  374. xAex.append(yA._y)
  375. if self.gridFirst:
  376. xA.makeGrid(g,parent=self,dim=yA.getGridDims,exclude=yAex)
  377. yA.makeGrid(g,parent=self,dim=xA.getGridDims,exclude=xAex)
  378. g.add(xA.draw())
  379. g.add(yA.draw())
  380. if not self.gridFirst:
  381. xAdgl = getattr(xA,'drawGridLast',False)
  382. yAdgl = getattr(yA,'drawGridLast',False)
  383. if not xAdgl: xA.makeGrid(g,parent=self,dim=yA.getGridDims,exclude=yAex)
  384. if not yAdgl: yA.makeGrid(g,parent=self,dim=xA.getGridDims,exclude=xAex)
  385. annotations = getattr(self,'annotations',[])
  386. for a in annotations:
  387. if getattr(a,'beforeLines',None):
  388. g.add(a(self,xA.scale,yA.scale))
  389. g.add(self.makeLines())
  390. if not self.gridFirst:
  391. if xAdgl: xA.makeGrid(g,parent=self,dim=yA.getGridDims,exclude=yAex)
  392. if yAdgl: yA.makeGrid(g,parent=self,dim=xA.getGridDims,exclude=xAex)
  393. for a in annotations:
  394. if not getattr(a,'beforeLines',None):
  395. g.add(a(self,xA.scale,yA.scale))
  396. return g
  397. def addCrossHair(self,name,xv,yv,strokeColor=colors.black,strokeWidth=1,beforeLines=True):
  398. from reportlab.graphics.shapes import Group, Line
  399. annotations = [a for a in getattr(self,'annotations',[]) if getattr(a,'name',None)!=name]
  400. def annotation(self,xScale,yScale):
  401. x = xScale(xv)
  402. y = yScale(yv)
  403. g = Group()
  404. xA = xScale.__self__ #the x axis
  405. g.add(Line(xA._x,y,xA._x+xA._length,y,strokeColor=strokeColor,strokeWidth=strokeWidth))
  406. yA = yScale.__self__ #the y axis
  407. g.add(Line(x,yA._y,x,yA._y+yA._length,strokeColor=strokeColor,strokeWidth=strokeWidth))
  408. return g
  409. annotation.beforeLines = beforeLines
  410. annotations.append(annotation)
  411. self.annotations = annotations
  412. class LinePlot3D(LinePlot):
  413. _attrMap = AttrMap(BASE=LinePlot,
  414. theta_x = AttrMapValue(isNumber, desc='dx/dz'),
  415. theta_y = AttrMapValue(isNumber, desc='dy/dz'),
  416. zDepth = AttrMapValue(isNumber, desc='depth of an individual series'),
  417. zSpace = AttrMapValue(isNumber, desc='z gap around series'),
  418. )
  419. theta_x = .5
  420. theta_y = .5
  421. zDepth = 10
  422. zSpace = 3
  423. def calcPositions(self):
  424. LinePlot.calcPositions(self)
  425. nSeries = self._seriesCount
  426. zSpace = self.zSpace
  427. zDepth = self.zDepth
  428. if self.xValueAxis.style=='parallel_3d':
  429. _3d_depth = nSeries*zDepth+(nSeries+1)*zSpace
  430. else:
  431. _3d_depth = zDepth + 2*zSpace
  432. self._3d_dx = self.theta_x*_3d_depth
  433. self._3d_dy = self.theta_y*_3d_depth
  434. def _calc_z0(self,rowNo):
  435. zSpace = self.zSpace
  436. if self.xValueAxis.style=='parallel_3d':
  437. z0 = rowNo*(self.zDepth+zSpace)+zSpace
  438. else:
  439. z0 = zSpace
  440. return z0
  441. def _zadjust(self,x,y,z):
  442. return x+z*self.theta_x, y+z*self.theta_y
  443. def makeLines(self):
  444. bubblePlot = getattr(self,'_bubblePlot',None)
  445. assert not bubblePlot, "_bubblePlot not supported for 3d yet"
  446. #if bubblePlot:
  447. # yA = self.yValueAxis
  448. # xA = self.xValueAxis
  449. # bubbleR = min(yA._bubbleRadius,xA._bubbleRadius)
  450. # bubbleMax = xA._bubbleMax
  451. labelFmt = self.lineLabelFormat
  452. positions = self._positions
  453. P = list(range(len(positions)))
  454. if self.reversePlotOrder: P.reverse()
  455. inFill = getattr(self,'_inFill',None)
  456. assert not inFill, "inFill not supported for 3d yet"
  457. #if inFill:
  458. # inFillY = self.xValueAxis._y
  459. # inFillX0 = self.yValueAxis._x
  460. # inFillX1 = inFillX0 + self.xValueAxis._length
  461. # inFillG = getattr(self,'_inFillG',g)
  462. zDepth = self.zDepth
  463. _zadjust = self._zadjust
  464. theta_x = self.theta_x
  465. theta_y = self.theta_y
  466. from reportlab.graphics.charts.linecharts import _FakeGroup
  467. F = _FakeGroup()
  468. from reportlab.graphics.charts.utils3d import _make_3d_line_info, find_intersections
  469. if self.xValueAxis.style!='parallel_3d':
  470. tileWidth = getattr(self,'_3d_tilewidth',1)
  471. if getattr(self,'_find_intersections',None):
  472. from copy import copy
  473. fpositions = list(map(copy,positions))
  474. I = find_intersections(fpositions,small=tileWidth)
  475. ic = None
  476. for i,j,x,y in I:
  477. if ic!=i:
  478. ic = i
  479. jc = 0
  480. else:
  481. jc+=1
  482. fpositions[i].insert(j+jc,(x,y))
  483. tileWidth = None
  484. else:
  485. fpositions = positions
  486. else:
  487. tileWidth = None
  488. fpositions = positions
  489. # Iterate over data rows.
  490. styleCount = len(self.lines)
  491. for rowNo in P:
  492. row = positions[rowNo]
  493. n = len(row)
  494. rowStyle = self.lines[rowNo % styleCount]
  495. rowColor = rowStyle.strokeColor
  496. dash = getattr(rowStyle, 'strokeDashArray', None)
  497. z0 = self._calc_z0(rowNo)
  498. z1 = z0 + zDepth
  499. if hasattr(rowStyle, 'strokeWidth'):
  500. width = rowStyle.strokeWidth
  501. elif hasattr(self.lines, 'strokeWidth'):
  502. width = self.lines.strokeWidth
  503. else:
  504. width = None
  505. # Iterate over data columns.
  506. if self.joinedLines:
  507. if n:
  508. frow = fpositions[rowNo]
  509. x0, y0 = frow[0]
  510. for colNo in range(1,len(frow)):
  511. x1, y1 = frow[colNo]
  512. _make_3d_line_info( F, x0, x1, y0, y1, z0, z1,
  513. theta_x, theta_y,
  514. rowColor, fillColorShaded=None, tileWidth=tileWidth,
  515. strokeColor=None, strokeWidth=None, strokeDashArray=None,
  516. shading=0.1)
  517. x0, y0 = x1, y1
  518. if hasattr(rowStyle, 'symbol'):
  519. uSymbol = rowStyle.symbol
  520. elif hasattr(self.lines, 'symbol'):
  521. uSymbol = self.lines.symbol
  522. else:
  523. uSymbol = None
  524. if uSymbol:
  525. for xy in row:
  526. x1, y1 = row[colNo]
  527. x1, y1 = _zadjust(x1,y1,z0)
  528. symbol = uSymbol2Symbol(uSymbol,xy[0],xy[1],rowColor)
  529. if symbol: F.add((1,z0,z0,x1,y1,symbol))
  530. # Draw data labels.
  531. for colNo in range(n):
  532. x1, y1 = row[colNo]
  533. x1, y1 = _zadjust(x1,y1,z0)
  534. L = self._innerDrawLabel(rowNo, colNo, x1, y1)
  535. if L: F.add((2,z0,z0,x1,y1,L))
  536. F.sort()
  537. g = Group()
  538. for v in F.value(): g.add(v[-1])
  539. return g
  540. _monthlyIndexData = [[(19971202, 100.0),
  541. (19971231, 100.1704367),
  542. (19980131, 101.5639577),
  543. (19980228, 102.1879927),
  544. (19980331, 101.6337257),
  545. (19980430, 102.7640446),
  546. (19980531, 102.9198038),
  547. (19980630, 103.25938789999999),
  548. (19980731, 103.2516421),
  549. (19980831, 105.4744329),
  550. (19980930, 109.3242705),
  551. (19981031, 111.9859291),
  552. (19981130, 110.9184642),
  553. (19981231, 110.9184642),
  554. (19990131, 111.9882532),
  555. (19990228, 109.7912614),
  556. (19990331, 110.24189629999999),
  557. (19990430, 110.4279321),
  558. (19990531, 109.33955469999999),
  559. (19990630, 108.2341748),
  560. (19990731, 110.21294469999999),
  561. (19990831, 110.9683062),
  562. (19990930, 112.4425371),
  563. (19991031, 112.7314032),
  564. (19991130, 112.3509645),
  565. (19991231, 112.3660659),
  566. (20000131, 110.9255248),
  567. (20000229, 110.5266306),
  568. (20000331, 113.3116101),
  569. (20000430, 111.0449133),
  570. (20000531, 111.702717),
  571. (20000630, 113.5832178)],
  572. [(19971202, 100.0),
  573. (19971231, 100.0),
  574. (19980131, 100.8),
  575. (19980228, 102.0),
  576. (19980331, 101.9),
  577. (19980430, 103.0),
  578. (19980531, 103.0),
  579. (19980630, 103.1),
  580. (19980731, 103.1),
  581. (19980831, 102.8),
  582. (19980930, 105.6),
  583. (19981031, 108.3),
  584. (19981130, 108.1),
  585. (19981231, 111.9),
  586. (19990131, 113.1),
  587. (19990228, 110.2),
  588. (19990331, 111.8),
  589. (19990430, 112.3),
  590. (19990531, 110.1),
  591. (19990630, 109.3),
  592. (19990731, 111.2),
  593. (19990831, 111.7),
  594. (19990930, 112.6),
  595. (19991031, 113.2),
  596. (19991130, 113.9),
  597. (19991231, 115.4),
  598. (20000131, 112.7),
  599. (20000229, 113.9),
  600. (20000331, 115.8),
  601. (20000430, 112.2),
  602. (20000531, 112.6),
  603. (20000630, 114.6)]]
  604. class SimpleTimeSeriesPlot(LinePlot):
  605. """A customized version of LinePlot.
  606. It uses NormalDateXValueAxis() and AdjYValueAxis() for the X and Y axes.
  607. """
  608. def __init__(self):
  609. LinePlot.__init__(self)
  610. self.xValueAxis = NormalDateXValueAxis()
  611. self.yValueAxis = YValueAxis()
  612. self.data = _monthlyIndexData
  613. class GridLinePlot(SimpleTimeSeriesPlot):
  614. """A customized version of SimpleTimeSeriesSPlot.
  615. It uses NormalDateXValueAxis() and AdjYValueAxis() for the X and Y axes.
  616. The chart has a default grid background with thin horizontal lines
  617. aligned with the tickmarks (and labels). You can change the back-
  618. ground to be any Grid or ShadedRect, or scale the whole chart.
  619. If you do provide a background, you can specify the colours of the
  620. stripes with 'background.stripeColors'.
  621. """
  622. _attrMap = AttrMap(BASE=LinePlot,
  623. background = AttrMapValue(None, desc='Background for chart area (now Grid or ShadedRect).'),
  624. scaleFactor = AttrMapValue(isNumberOrNone, desc='Scalefactor to apply to whole drawing.'),
  625. )
  626. def __init__(self):
  627. from reportlab.lib import colors
  628. SimpleTimeSeriesPlot.__init__(self)
  629. self.scaleFactor = None
  630. self.background = Grid()
  631. self.background.orientation = 'horizontal'
  632. self.background.useRects = 0
  633. self.background.useLines = 1
  634. self.background.strokeWidth = 0.5
  635. self.background.strokeColor = colors.black
  636. def demo(self,drawing=None):
  637. from reportlab.lib import colors
  638. if not drawing:
  639. drawing = Drawing(400, 200)
  640. lp = GridLinePlot()
  641. lp.x = 50
  642. lp.y = 50
  643. lp.height = 125
  644. lp.width = 300
  645. lp.data = _monthlyIndexData
  646. lp.joinedLines = 1
  647. lp.strokeColor = colors.black
  648. c0 = colors.PCMYKColor(100,65,0,30, spotName='PANTONE 288 CV', density=100)
  649. lp.lines[0].strokeColor = c0
  650. lp.lines[0].strokeWidth = 2
  651. lp.lines[0].strokeDashArray = None
  652. c1 = colors.PCMYKColor(0,79,91,0, spotName='PANTONE Wm Red CV', density=100)
  653. lp.lines[1].strokeColor = c1
  654. lp.lines[1].strokeWidth = 1
  655. lp.lines[1].strokeDashArray = [3,1]
  656. lp.xValueAxis.labels.fontSize = 10
  657. lp.xValueAxis.labels.textAnchor = 'start'
  658. lp.xValueAxis.labels.boxAnchor = 'w'
  659. lp.xValueAxis.labels.angle = -45
  660. lp.xValueAxis.labels.dx = 0
  661. lp.xValueAxis.labels.dy = -8
  662. lp.xValueAxis.xLabelFormat = '{mm}/{yy}'
  663. lp.yValueAxis.labelTextFormat = '%5d%% '
  664. lp.yValueAxis.tickLeft = 5
  665. lp.yValueAxis.labels.fontSize = 10
  666. lp.background = Grid()
  667. lp.background.stripeColors = [colors.pink, colors.lightblue]
  668. lp.background.orientation = 'vertical'
  669. drawing.add(lp,'plot')
  670. return drawing
  671. def draw(self):
  672. xva, yva = self.xValueAxis, self.yValueAxis
  673. if xva: xva.joinAxis = yva
  674. if yva: yva.joinAxis = xva
  675. yva.setPosition(self.x, self.y, self.height)
  676. yva.configure(self.data)
  677. # if zero is in chart, put x axis there, otherwise
  678. # use bottom.
  679. xAxisCrossesAt = yva.scale(0)
  680. if ((xAxisCrossesAt > self.y + self.height) or (xAxisCrossesAt < self.y)):
  681. y = self.y
  682. else:
  683. y = xAxisCrossesAt
  684. xva.setPosition(self.x, y, self.width)
  685. xva.configure(self.data)
  686. back = self.background
  687. if isinstance(back, Grid):
  688. if back.orientation == 'vertical' and xva._tickValues:
  689. xpos = list(map(xva.scale, [xva._valueMin] + xva._tickValues))
  690. steps = []
  691. for i in range(len(xpos)-1):
  692. steps.append(xpos[i+1] - xpos[i])
  693. back.deltaSteps = steps
  694. elif back.orientation == 'horizontal' and yva._tickValues:
  695. ypos = list(map(yva.scale, [yva._valueMin] + yva._tickValues))
  696. steps = []
  697. for i in range(len(ypos)-1):
  698. steps.append(ypos[i+1] - ypos[i])
  699. back.deltaSteps = steps
  700. elif isinstance(back, DoubleGrid):
  701. # Ideally, these lines would not be needed...
  702. back.grid0.x = self.x
  703. back.grid0.y = self.y
  704. back.grid0.width = self.width
  705. back.grid0.height = self.height
  706. back.grid1.x = self.x
  707. back.grid1.y = self.y
  708. back.grid1.width = self.width
  709. back.grid1.height = self.height
  710. # some room left for optimization...
  711. if back.grid0.orientation == 'vertical' and xva._tickValues:
  712. xpos = list(map(xva.scale, [xva._valueMin] + xva._tickValues))
  713. steps = []
  714. for i in range(len(xpos)-1):
  715. steps.append(xpos[i+1] - xpos[i])
  716. back.grid0.deltaSteps = steps
  717. elif back.grid0.orientation == 'horizontal' and yva._tickValues:
  718. ypos = list(map(yva.scale, [yva._valueMin] + yva._tickValues))
  719. steps = []
  720. for i in range(len(ypos)-1):
  721. steps.append(ypos[i+1] - ypos[i])
  722. back.grid0.deltaSteps = steps
  723. if back.grid1.orientation == 'vertical' and xva._tickValues:
  724. xpos = list(map(xva.scale, [xva._valueMin] + xva._tickValues))
  725. steps = []
  726. for i in range(len(xpos)-1):
  727. steps.append(xpos[i+1] - xpos[i])
  728. back.grid1.deltaSteps = steps
  729. elif back.grid1.orientation == 'horizontal' and yva._tickValues:
  730. ypos = list(map(yva.scale, [yva._valueMin] + yva._tickValues))
  731. steps = []
  732. for i in range(len(ypos)-1):
  733. steps.append(ypos[i+1] - ypos[i])
  734. back.grid1.deltaSteps = steps
  735. self.calcPositions()
  736. width, height, scaleFactor = self.width, self.height, self.scaleFactor
  737. if scaleFactor and scaleFactor!=1:
  738. #g = Drawing(scaleFactor*width, scaleFactor*height)
  739. g.transform = (scaleFactor, 0, 0, scaleFactor,0,0)
  740. else:
  741. g = Group()
  742. g.add(self.makeBackground())
  743. g.add(self.xValueAxis)
  744. g.add(self.yValueAxis)
  745. g.add(self.makeLines())
  746. return g
  747. class AreaLinePlot(LinePlot):
  748. '''we're given data in the form [(X1,Y11,..Y1M)....(Xn,Yn1,...YnM)]'''#'
  749. def __init__(self):
  750. LinePlot.__init__(self)
  751. self._inFill = 1
  752. self.reversePlotOrder = 1
  753. self.data = [(1,20,100,30),(2,11,50,15),(3,15,70,40)]
  754. def draw(self):
  755. try:
  756. odata = self.data
  757. n = len(odata)
  758. m = len(odata[0])
  759. S = n*[0]
  760. self.data = []
  761. for i in range(1,m):
  762. D = []
  763. for j in range(n):
  764. S[j] = S[j] + odata[j][i]
  765. D.append((odata[j][0],S[j]))
  766. self.data.append(D)
  767. return LinePlot.draw(self)
  768. finally:
  769. self.data = odata
  770. class SplitLinePlot(AreaLinePlot):
  771. def __init__(self):
  772. AreaLinePlot.__init__(self)
  773. self.xValueAxis = NormalDateXValueAxis()
  774. self.yValueAxis = AdjYValueAxis()
  775. self.data=[(20030601,0.95,0.05,0.0),(20030701,0.95,0.05,0.0),(20030801,0.95,0.05,0.0),(20030901,0.95,0.05,0.0),(20031001,0.95,0.05,0.0),(20031101,0.95,0.05,0.0),(20031201,0.95,0.05,0.0),(20040101,0.95,0.05,0.0),(20040201,0.95,0.05,0.0),(20040301,0.95,0.05,0.0),(20040401,0.95,0.05,0.0),(20040501,0.95,0.05,0.0),(20040601,0.95,0.05,0.0),(20040701,0.95,0.05,0.0),(20040801,0.95,0.05,0.0),(20040901,0.95,0.05,0.0),(20041001,0.95,0.05,0.0),(20041101,0.95,0.05,0.0),(20041201,0.95,0.05,0.0),(20050101,0.95,0.05,0.0),(20050201,0.95,0.05,0.0),(20050301,0.95,0.05,0.0),(20050401,0.95,0.05,0.0),(20050501,0.95,0.05,0.0),(20050601,0.95,0.05,0.0),(20050701,0.95,0.05,0.0),(20050801,0.95,0.05,0.0),(20050901,0.95,0.05,0.0),(20051001,0.95,0.05,0.0),(20051101,0.95,0.05,0.0),(20051201,0.95,0.05,0.0),(20060101,0.95,0.05,0.0),(20060201,0.95,0.05,0.0),(20060301,0.95,0.05,0.0),(20060401,0.95,0.05,0.0),(20060501,0.95,0.05,0.0),(20060601,0.95,0.05,0.0),(20060701,0.95,0.05,0.0),(20060801,0.95,0.05,0.0),(20060901,0.95,0.05,0.0),(20061001,0.95,0.05,0.0),(20061101,0.95,0.05,0.0),(20061201,0.95,0.05,0.0),(20070101,0.95,0.05,0.0),(20070201,0.95,0.05,0.0),(20070301,0.95,0.05,0.0),(20070401,0.95,0.05,0.0),(20070501,0.95,0.05,0.0),(20070601,0.95,0.05,0.0),(20070701,0.95,0.05,0.0),(20070801,0.95,0.05,0.0),(20070901,0.95,0.05,0.0),(20071001,0.95,0.05,0.0),(20071101,0.95,0.05,0.0),(20071201,0.95,0.05,0.0),(20080101,0.95,0.05,0.0),(20080201,0.95,0.05,0.0),(20080301,0.95,0.05,0.0),(20080401,0.95,0.05,0.0),(20080501,0.95,0.05,0.0),(20080601,0.95,0.05,0.0),(20080701,0.95,0.05,0.0),(20080801,0.95,0.05,0.0),(20080901,0.95,0.05,0.0),(20081001,0.95,0.05,0.0),(20081101,0.95,0.05,0.0),(20081201,0.95,0.05,0.0),(20090101,0.95,0.05,0.0),(20090201,0.91,0.09,0.0),(20090301,0.91,0.09,0.0),(20090401,0.91,0.09,0.0),(20090501,0.91,0.09,0.0),(20090601,0.91,0.09,0.0),(20090701,0.91,0.09,0.0),(20090801,0.91,0.09,0.0),(20090901,0.91,0.09,0.0),(20091001,0.91,0.09,0.0),(20091101,0.91,0.09,0.0),(20091201,0.91,0.09,0.0),(20100101,0.91,0.09,0.0),(20100201,0.81,0.19,0.0),(20100301,0.81,0.19,0.0),(20100401,0.81,0.19,0.0),(20100501,0.81,0.19,0.0),(20100601,0.81,0.19,0.0),(20100701,0.81,0.19,0.0),(20100801,0.81,0.19,0.0),(20100901,0.81,0.19,0.0),(20101001,0.81,0.19,0.0),(20101101,0.81,0.19,0.0),(20101201,0.81,0.19,0.0),(20110101,0.81,0.19,0.0),(20110201,0.72,0.28,0.0),(20110301,0.72,0.28,0.0),(20110401,0.72,0.28,0.0),(20110501,0.72,0.28,0.0),(20110601,0.72,0.28,0.0),(20110701,0.72,0.28,0.0),(20110801,0.72,0.28,0.0),(20110901,0.72,0.28,0.0),(20111001,0.72,0.28,0.0),(20111101,0.72,0.28,0.0),(20111201,0.72,0.28,0.0),(20120101,0.72,0.28,0.0),(20120201,0.53,0.47,0.0),(20120301,0.53,0.47,0.0),(20120401,0.53,0.47,0.0),(20120501,0.53,0.47,0.0),(20120601,0.53,0.47,0.0),(20120701,0.53,0.47,0.0),(20120801,0.53,0.47,0.0),(20120901,0.53,0.47,0.0),(20121001,0.53,0.47,0.0),(20121101,0.53,0.47,0.0),(20121201,0.53,0.47,0.0),(20130101,0.53,0.47,0.0),(20130201,0.44,0.56,0.0),(20130301,0.44,0.56,0.0),(20130401,0.44,0.56,0.0),(20130501,0.44,0.56,0.0),(20130601,0.44,0.56,0.0),(20130701,0.44,0.56,0.0),(20130801,0.44,0.56,0.0),(20130901,0.44,0.56,0.0),(20131001,0.44,0.56,0.0),(20131101,0.44,0.56,0.0),(20131201,0.44,0.56,0.0),(20140101,0.44,0.56,0.0),(20140201,0.36,0.5,0.14),(20140301,0.36,0.5,0.14),(20140401,0.36,0.5,0.14),(20140501,0.36,0.5,0.14),(20140601,0.36,0.5,0.14),(20140701,0.36,0.5,0.14),(20140801,0.36,0.5,0.14),(20140901,0.36,0.5,0.14),(20141001,0.36,0.5,0.14),(20141101,0.36,0.5,0.14),(20141201,0.36,0.5,0.14),(20150101,0.36,0.5,0.14),(20150201,0.3,0.41,0.29),(20150301,0.3,0.41,0.29),(20150401,0.3,0.41,0.29),(20150501,0.3,0.41,0.29),(20150601,0.3,0.41,0.29),(20150701,0.3,0.41,0.29),(20150801,0.3,0.41,0.29),(20150901,0.3,0.41,0.29),(20151001,0.3,0.41,0.29),(20151101,0.3,0.41,0.29),(20151201,0.3,0.41,0.29),(20160101,0.3,0.41,0.29),(20160201,0.26,0.36,0.38),(20160301,0.26,0.36,0.38),(20160401,0.26,0.36,0.38),(20160501,0.26,0.36,0.38),(20160601,0.26,0.36,0.38),(20160701,0.26,0.36,0.38),(20160801,0.26,0.36,0.38),(20160901,0.26,0.36,0.38),(20161001,0.26,0.36,0.38),(20161101,0.26,0.36,0.38),(20161201,0.26,0.36,0.38),(20170101,0.26,0.36,0.38),(20170201,0.2,0.3,0.5),(20170301,0.2,0.3,0.5),(20170401,0.2,0.3,0.5),(20170501,0.2,0.3,0.5),(20170601,0.2,0.3,0.5),(20170701,0.2,0.3,0.5),(20170801,0.2,0.3,0.5),(20170901,0.2,0.3,0.5),(20171001,0.2,0.3,0.5),(20171101,0.2,0.3,0.5),(20171201,0.2,0.3,0.5),(20180101,0.2,0.3,0.5),(20180201,0.13,0.37,0.5),(20180301,0.13,0.37,0.5),(20180401,0.13,0.37,0.5),(20180501,0.13,0.37,0.5),(20180601,0.13,0.37,0.5),(20180701,0.13,0.37,0.5),(20180801,0.13,0.37,0.5),(20180901,0.13,0.37,0.5),(20181001,0.13,0.37,0.5),(20181101,0.13,0.37,0.5),(20181201,0.13,0.37,0.5),(20190101,0.13,0.37,0.5),(20190201,0.1,0.4,0.5),(20190301,0.1,0.4,0.5),(20190401,0.1,0.4,0.5),(20190501,0.1,0.4,0.5),(20190601,0.1,0.4,0.5),(20190701,0.1,0.4,0.5),(20190801,0.1,0.4,0.5),(20190901,0.1,0.4,0.5),(20191001,0.1,0.4,0.5),(20191101,0.1,0.4,0.5),(20191201,0.1,0.4,0.5),(20200101,0.1,0.4,0.5)]
  776. self.yValueAxis.requiredRange = None
  777. self.yValueAxis.leftAxisPercent = 0
  778. self.yValueAxis.leftAxisOrigShiftMin = 0
  779. self.yValueAxis.leftAxisOrigShiftIPC = 0
  780. self.lines[0].strokeColor = colors.toColor(0x0033cc)
  781. self.lines[1].strokeColor = colors.toColor(0x99c3ff)
  782. self.lines[2].strokeColor = colors.toColor(0xCC0033)
  783. def _maxWidth(T, fontName, fontSize):
  784. '''return max stringWidth for the list of strings T'''
  785. if not isinstance(T,(tuple,list)): T = (T,)
  786. T = [_f for _f in T if _f]
  787. return T and max(list(map(lambda t,sW=stringWidth,fN=fontName, fS=fontSize: sW(t,fN,fS),T))) or 0
  788. class ScatterPlot(LinePlot):
  789. """A scatter plot widget"""
  790. _attrMap = AttrMap(BASE=LinePlot,
  791. width = AttrMapValue(isNumber, desc="Width of the area inside the axes"),
  792. height = AttrMapValue(isNumber, desc="Height of the area inside the axes"),
  793. outerBorderOn = AttrMapValue(isBoolean, desc="Is there an outer border (continuation of axes)"),
  794. outerBorderColor = AttrMapValue(isColorOrNone, desc="Color of outer border (if any)"),
  795. labelOffset = AttrMapValue(isNumber, desc="Space between label and Axis (or other labels)",advancedUsage=1),
  796. axisTickLengths = AttrMapValue(isNumber, desc="Lenth of the ticks on both axes"),
  797. axisStrokeWidth = AttrMapValue(isNumber, desc="Stroke width for both axes"),
  798. xLabel = AttrMapValue(isString, desc="Label for the whole X-Axis"),
  799. yLabel = AttrMapValue(isString, desc="Label for the whole Y-Axis"),
  800. data = AttrMapValue(isAnything, desc='Data points - a list of x/y tuples.'),
  801. strokeColor = AttrMapValue(isColorOrNone, desc='Color used for border of plot area.'),
  802. fillColor = AttrMapValue(isColorOrNone, desc='Color used for background interior of plot area.'),
  803. leftPadding = AttrMapValue(isNumber, desc='Padding on left of drawing'),
  804. rightPadding = AttrMapValue(isNumber, desc='Padding on right of drawing'),
  805. topPadding = AttrMapValue(isNumber, desc='Padding at top of drawing'),
  806. bottomPadding = AttrMapValue(isNumber, desc='Padding at bottom of drawing'),
  807. )
  808. def __init__(self):
  809. LinePlot.__init__(self)
  810. self.width = 142
  811. self.height = 77
  812. self.outerBorderOn = 1
  813. self.outerBorderColor = colors.black
  814. self.background = None
  815. _labelOffset = 3
  816. _axisTickLengths = 2
  817. _axisStrokeWidth = 0.5
  818. self.yValueAxis.valueMin = None
  819. self.yValueAxis.valueMax = None
  820. self.yValueAxis.valueStep = None
  821. self.yValueAxis.labelTextFormat = '%s'
  822. self.xLabel="X Lable"
  823. self.xValueAxis.labels.fontSize = 6
  824. self.yLabel="Y Lable"
  825. self.yValueAxis.labels.fontSize = 6
  826. self.data =[((0.030, 62.73),
  827. (0.074, 54.363),
  828. (1.216, 17.964)),
  829. ((1.360, 11.621),
  830. (1.387, 50.011),
  831. (1.428, 68.953)),
  832. ((1.444, 86.888),
  833. (1.754, 35.58),
  834. (1.766, 36.05))]
  835. #values for lineplot
  836. self.joinedLines = 0
  837. self.leftPadding=5
  838. self.rightPadding=10
  839. self.topPadding=5
  840. self.bottomPadding=5
  841. self.x = self.leftPadding+_axisTickLengths+(_labelOffset*2)
  842. self.x=self.x+_maxWidth(str(self.yValueAxis.valueMax), self.yValueAxis.labels.fontName, self.yValueAxis.labels.fontSize)
  843. self.y = self.bottomPadding+_axisTickLengths+_labelOffset+self.xValueAxis.labels.fontSize
  844. self.xValueAxis.labels.dy = -_labelOffset
  845. self.xValueAxis.tickDown = _axisTickLengths
  846. self.xValueAxis.strokeWidth = _axisStrokeWidth
  847. self.xValueAxis.rangeRound='both'
  848. self.yValueAxis.labels.dx = -_labelOffset
  849. self.yValueAxis.tickLeft = _axisTickLengths
  850. self.yValueAxis.strokeWidth = _axisStrokeWidth
  851. self.yValueAxis.rangeRound='both'
  852. self.lineLabelFormat="%.2f"
  853. self.lineLabels.fontSize = 5
  854. self.lineLabels.boxAnchor = 'e'
  855. self.lineLabels.dx = -2
  856. self.lineLabelNudge = 0
  857. self.lines.symbol=makeMarker('FilledCircle',size=3)
  858. self.lines[1].symbol=makeMarker('FilledDiamond',size=3)
  859. self.lines[2].symbol=makeMarker('FilledSquare',size=3)
  860. self.lines[2].strokeColor = colors.green
  861. def _getDrawingDimensions(self):
  862. tx = self.leftPadding+self.yValueAxis.tickLeft+(self.yValueAxis.labels.dx*2)+self.xValueAxis.labels.fontSize
  863. tx=tx+(5*_maxWidth(str(self.yValueAxis.valueMax), self.yValueAxis.labels.fontName, self.yValueAxis.labels.fontSize))
  864. tx=tx+self.width+self.rightPadding
  865. t=('%.2f%%'%self.xValueAxis.valueMax)
  866. tx=tx+(_maxWidth(t, self.yValueAxis.labels.fontName, self.yValueAxis.labels.fontSize))
  867. ty = self.bottomPadding+self.xValueAxis.tickDown+(self.xValueAxis.labels.dy*2)+(self.xValueAxis.labels.fontSize*2)
  868. ty=ty+self.yValueAxis.labels.fontSize+self.height+self.topPadding
  869. #print (tx, ty)
  870. return (tx,ty)
  871. def demo(self,drawing=None):
  872. if not drawing:
  873. tx,ty=self._getDrawingDimensions()
  874. drawing = Drawing(tx,ty)
  875. drawing.add(self.draw())
  876. return drawing
  877. def draw(self):
  878. ascent=getFont(self.xValueAxis.labels.fontName).face.ascent
  879. if ascent==0:
  880. ascent=0.718 # default (from helvetica)
  881. ascent=ascent*self.xValueAxis.labels.fontSize # normalize
  882. #basic LinePlot - does the Axes, Ticks etc
  883. lp = LinePlot.draw(self)
  884. xLabel = self.xLabel
  885. if xLabel: #Overall label for the X-axis
  886. xl=Label()
  887. xl.x = (self.x+self.width)/2.0
  888. xl.y = 0
  889. xl.fontName = self.xValueAxis.labels.fontName
  890. xl.fontSize = self.xValueAxis.labels.fontSize
  891. xl.setText(xLabel)
  892. lp.add(xl)
  893. yLabel = self.yLabel
  894. if yLabel: #Overall label for the Y-axis
  895. yl=Label()
  896. yl.angle = 90
  897. yl.x = 0
  898. yl.y = (self.y+self.height/2.0)
  899. yl.fontName = self.yValueAxis.labels.fontName
  900. yl.fontSize = self.yValueAxis.labels.fontSize
  901. yl.setText(yLabel)
  902. lp.add(yl)
  903. # do a bounding box - in the same style as the axes
  904. if self.outerBorderOn:
  905. lp.add(Rect(self.x, self.y, self.width, self.height,
  906. strokeColor = self.outerBorderColor,
  907. strokeWidth = self.yValueAxis.strokeWidth,
  908. fillColor = None))
  909. lp.shift(self.leftPadding, self.bottomPadding)
  910. return lp
  911. def sample1a():
  912. "A line plot with non-equidistant points in x-axis."
  913. drawing = Drawing(400, 200)
  914. data = [
  915. ((1,1), (2,2), (2.5,1), (3,3), (4,5)),
  916. ((1,2), (2,3), (2.5,2), (3.5,5), (4,6))
  917. ]
  918. lp = LinePlot()
  919. lp.x = 50
  920. lp.y = 50
  921. lp.height = 125
  922. lp.width = 300
  923. lp.data = data
  924. lp.joinedLines = 1
  925. lp.strokeColor = colors.black
  926. lp.lines.symbol = makeMarker('UK_Flag')
  927. lp.lines[0].strokeWidth = 2
  928. lp.lines[1].strokeWidth = 4
  929. lp.xValueAxis.valueMin = 0
  930. lp.xValueAxis.valueMax = 5
  931. lp.xValueAxis.valueStep = 1
  932. lp.yValueAxis.valueMin = 0
  933. lp.yValueAxis.valueMax = 7
  934. lp.yValueAxis.valueStep = 1
  935. drawing.add(lp)
  936. return drawing
  937. def sample1b():
  938. "A line plot with non-equidistant points in x-axis."
  939. drawing = Drawing(400, 200)
  940. data = [
  941. ((1,1), (2,2), (2.5,1), (3,3), (4,5)),
  942. ((1,2), (2,3), (2.5,2), (3.5,5), (4,6))
  943. ]
  944. lp = LinePlot()
  945. lp.x = 50
  946. lp.y = 50
  947. lp.height = 125
  948. lp.width = 300
  949. lp.data = data
  950. lp.joinedLines = 1
  951. lp.lines.symbol = makeMarker('Circle')
  952. lp.lineLabelFormat = '%2.0f'
  953. lp.strokeColor = colors.black
  954. lp.xValueAxis.valueMin = 0
  955. lp.xValueAxis.valueMax = 5
  956. lp.xValueAxis.valueSteps = [1, 2, 2.5, 3, 4, 5]
  957. lp.xValueAxis.labelTextFormat = '%2.1f'
  958. lp.yValueAxis.valueMin = 0
  959. lp.yValueAxis.valueMax = 7
  960. lp.yValueAxis.valueStep = 1
  961. drawing.add(lp)
  962. return drawing
  963. def sample1c():
  964. "A line plot with non-equidistant points in x-axis."
  965. drawing = Drawing(400, 200)
  966. data = [
  967. ((1,1), (2,2), (2.5,1), (3,3), (4,5)),
  968. ((1,2), (2,3), (2.5,2), (3.5,5), (4,6))
  969. ]
  970. lp = LinePlot()
  971. lp.x = 50
  972. lp.y = 50
  973. lp.height = 125
  974. lp.width = 300
  975. lp.data = data
  976. lp.joinedLines = 1
  977. lp.lines[0].symbol = makeMarker('FilledCircle')
  978. lp.lines[1].symbol = makeMarker('Circle')
  979. lp.lineLabelFormat = '%2.0f'
  980. lp.strokeColor = colors.black
  981. lp.xValueAxis.valueMin = 0
  982. lp.xValueAxis.valueMax = 5
  983. lp.xValueAxis.valueSteps = [1, 2, 2.5, 3, 4, 5]
  984. lp.xValueAxis.labelTextFormat = '%2.1f'
  985. lp.yValueAxis.valueMin = 0
  986. lp.yValueAxis.valueMax = 7
  987. lp.yValueAxis.valueSteps = [1, 2, 3, 5, 6]
  988. drawing.add(lp)
  989. return drawing
  990. def preprocessData(series):
  991. "Convert date strings into seconds and multiply values by 100."
  992. return [(str2seconds(x[0]), x[1]*100) for x in series]
  993. def sample2():
  994. "A line plot with non-equidistant points in x-axis."
  995. drawing = Drawing(400, 200)
  996. data = [
  997. (('25/11/1991',1),
  998. ('30/11/1991',1.000933333),
  999. ('31/12/1991',1.0062),
  1000. ('31/01/1992',1.0112),
  1001. ('29/02/1992',1.0158),
  1002. ('31/03/1992',1.020733333),
  1003. ('30/04/1992',1.026133333),
  1004. ('31/05/1992',1.030266667),
  1005. ('30/06/1992',1.034466667),
  1006. ('31/07/1992',1.038733333),
  1007. ('31/08/1992',1.0422),
  1008. ('30/09/1992',1.045533333),
  1009. ('31/10/1992',1.049866667),
  1010. ('30/11/1992',1.054733333),
  1011. ('31/12/1992',1.061),
  1012. ),
  1013. ]
  1014. data[0] = preprocessData(data[0])
  1015. lp = LinePlot()
  1016. lp.x = 50
  1017. lp.y = 50
  1018. lp.height = 125
  1019. lp.width = 300
  1020. lp.data = data
  1021. lp.joinedLines = 1
  1022. lp.lines.symbol = makeMarker('FilledDiamond')
  1023. lp.strokeColor = colors.black
  1024. start = mktime(mkTimeTuple('25/11/1991'))
  1025. t0 = mktime(mkTimeTuple('30/11/1991'))
  1026. t1 = mktime(mkTimeTuple('31/12/1991'))
  1027. t2 = mktime(mkTimeTuple('31/03/1992'))
  1028. t3 = mktime(mkTimeTuple('30/06/1992'))
  1029. t4 = mktime(mkTimeTuple('30/09/1992'))
  1030. end = mktime(mkTimeTuple('31/12/1992'))
  1031. lp.xValueAxis.valueMin = start
  1032. lp.xValueAxis.valueMax = end
  1033. lp.xValueAxis.valueSteps = [start, t0, t1, t2, t3, t4, end]
  1034. lp.xValueAxis.labelTextFormat = seconds2str
  1035. lp.xValueAxis.labels[1].dy = -20
  1036. lp.xValueAxis.labels[2].dy = -35
  1037. lp.yValueAxis.labelTextFormat = '%4.2f'
  1038. lp.yValueAxis.valueMin = 100
  1039. lp.yValueAxis.valueMax = 110
  1040. lp.yValueAxis.valueStep = 2
  1041. drawing.add(lp)
  1042. return drawing
  1043. def sampleFillPairedData():
  1044. d = Drawing(400,200)
  1045. chart = SimpleTimeSeriesPlot()
  1046. d.add(chart)
  1047. chart.data = [FillPairedData(chart.data[0],1),chart.data[1]]
  1048. chart.lines[0].filler= Filler(fillColor=colors.toColor('#9f9f9f'),strokeWidth=0,strokeColor=None)
  1049. chart.lines[0].strokeColor = None
  1050. chart.lines[1].strokeColor = None
  1051. return d