widgetbase.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655
  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/widgetbase.py
  4. __version__='3.3.0'
  5. __doc__='''Base class for user-defined graphical widgets'''
  6. from reportlab.graphics import shapes
  7. from reportlab import rl_config
  8. from reportlab.lib import colors
  9. from reportlab.lib.validators import *
  10. from reportlab.lib.attrmap import *
  11. from weakref import ref as weakref_ref
  12. class PropHolder:
  13. '''Base for property holders'''
  14. _attrMap = None
  15. def verify(self):
  16. """If the _attrMap attribute is not None, this
  17. checks all expected attributes are present; no
  18. unwanted attributes are present; and (if a
  19. checking function is found) checks each
  20. attribute has a valid value. Either succeeds
  21. or raises an informative exception.
  22. """
  23. if self._attrMap is not None:
  24. for key in self.__dict__.keys():
  25. if key[0] != '_':
  26. msg = "Unexpected attribute %s found in %s" % (key, self)
  27. assert key in self._attrMap, msg
  28. for attr, metavalue in self._attrMap.items():
  29. msg = "Missing attribute %s from %s" % (attr, self)
  30. assert hasattr(self, attr), msg
  31. value = getattr(self, attr)
  32. args = (value, attr, self.__class__.__name__)
  33. assert metavalue.validate(value), "Invalid value %s for attribute %s in class %s" % args
  34. if rl_config.shapeChecking:
  35. """This adds the ability to check every attribute assignment
  36. as it is made. It slows down shapes but is a big help when
  37. developing. It does not get defined if rl_config.shapeChecking = 0.
  38. """
  39. def __setattr__(self, name, value):
  40. """By default we verify. This could be off
  41. in some parallel base classes."""
  42. validateSetattr(self,name,value)
  43. def getProperties(self,recur=1):
  44. """Returns a list of all properties which can be edited and
  45. which are not marked as private. This may include 'child
  46. widgets' or 'primitive shapes'. You are free to override
  47. this and provide alternative implementations; the default
  48. one simply returns everything without a leading underscore.
  49. """
  50. from reportlab.lib.validators import isValidChild
  51. # TODO when we need it, but not before -
  52. # expose sequence contents?
  53. props = {}
  54. for name in self.__dict__.keys():
  55. if name[0:1] != '_':
  56. component = getattr(self, name)
  57. if recur and isValidChild(component):
  58. # child object, get its properties too
  59. childProps = component.getProperties(recur=recur)
  60. for childKey, childValue in childProps.items():
  61. #key might be something indexed like '[2].fillColor'
  62. #or simple like 'fillColor'; in the former case we
  63. #don't need a '.' between me and my child.
  64. if childKey[0] == '[':
  65. props['%s%s' % (name, childKey)] = childValue
  66. else:
  67. props['%s.%s' % (name, childKey)] = childValue
  68. else:
  69. props[name] = component
  70. return props
  71. def setProperties(self, propDict):
  72. """Permits bulk setting of properties. These may include
  73. child objects e.g. "chart.legend.width = 200".
  74. All assignments will be validated by the object as if they
  75. were set individually in python code.
  76. All properties of a top-level object are guaranteed to be
  77. set before any of the children, which may be helpful to
  78. widget designers.
  79. """
  80. childPropDicts = {}
  81. for name, value in propDict.items():
  82. parts = name.split('.', 1)
  83. if len(parts) == 1:
  84. #simple attribute, set it now
  85. setattr(self, name, value)
  86. else:
  87. (childName, remains) = parts
  88. try:
  89. childPropDicts[childName][remains] = value
  90. except KeyError:
  91. childPropDicts[childName] = {remains: value}
  92. # now assign to children
  93. for childName, childPropDict in childPropDicts.items():
  94. child = getattr(self, childName)
  95. child.setProperties(childPropDict)
  96. def dumpProperties(self, prefix=""):
  97. """Convenience. Lists them on standard output. You
  98. may provide a prefix - mostly helps to generate code
  99. samples for documentation.
  100. """
  101. propList = list(self.getProperties().items())
  102. propList.sort()
  103. if prefix:
  104. prefix = prefix + '.'
  105. for (name, value) in propList:
  106. print('%s%s = %s' % (prefix, name, value))
  107. class Widget(PropHolder, shapes.UserNode):
  108. """Base for all user-defined widgets. Keep as simple as possible. Does
  109. not inherit from Shape so that we can rewrite shapes without breaking
  110. widgets and vice versa."""
  111. def _setKeywords(self,**kw):
  112. for k,v in kw.items():
  113. if k not in self.__dict__:
  114. setattr(self,k,v)
  115. def draw(self):
  116. msg = "draw() must be implemented for each Widget!"
  117. raise shapes.NotImplementedError(msg)
  118. def demo(self):
  119. msg = "demo() must be implemented for each Widget!"
  120. raise shapes.NotImplementedError(msg)
  121. def provideNode(self):
  122. return self.draw()
  123. def getBounds(self):
  124. "Return outer boundary as x1,y1,x2,y2. Can be overridden for efficiency"
  125. return self.draw().getBounds()
  126. class ScaleWidget(Widget):
  127. '''Contents with a scale and offset'''
  128. _attrMap = AttrMap(
  129. x = AttrMapValue(isNumber,desc="x offset"),
  130. y = AttrMapValue(isNumber,desc="y offset"),
  131. scale = AttrMapValue(isNumber,desc="scale"),
  132. contents = AttrMapValue(None,desc="Contained drawable elements"),
  133. )
  134. def __init__(self,x=0,y=0,scale=1.0,contents=None):
  135. self.x = x
  136. self.y = y
  137. if not contents: contents=[]
  138. elif not isinstance(contents,(tuple,list)):
  139. contents = (contents,)
  140. self.contents = list(contents)
  141. self.scale = scale
  142. def draw(self):
  143. return shapes.Group(transform=(self.scale,0,0,self.scale,self.x,self.y),*self.contents)
  144. _ItemWrapper={}
  145. class CloneMixin:
  146. def clone(self,**kwds):
  147. n = self.__class__()
  148. n.__dict__.clear()
  149. n.__dict__.update(self.__dict__)
  150. if kwds: n.__dict__.update(kwds)
  151. return n
  152. class TypedPropertyCollection(PropHolder):
  153. """A container with properties for objects of the same kind.
  154. This makes it easy to create lists of objects. You initialize
  155. it with a class of what it is to contain, and that is all you
  156. can add to it. You can assign properties to the collection
  157. as a whole, or to a numeric index within it; if so it creates
  158. a new child object to hold that data.
  159. So:
  160. wedges = TypedPropertyCollection(WedgeProperties)
  161. wedges.strokeWidth = 2 # applies to all
  162. wedges.strokeColor = colors.red # applies to all
  163. wedges[3].strokeColor = colors.blue # only to one
  164. The last line should be taken as a prescription of how to
  165. create wedge no. 3 if one is needed; no error is raised if
  166. there are only two data points.
  167. We try and make sensible use of tuple indices.
  168. line[(3,x)] is backed by line[(3,)] == line[3] & line
  169. """
  170. def __init__(self, exampleClass, **kwds):
  171. #give it same validation rules as what it holds
  172. self.__dict__['_value'] = exampleClass(**kwds)
  173. self.__dict__['_children'] = {}
  174. def wKlassFactory(self,Klass):
  175. class WKlass(Klass,CloneMixin):
  176. def __getattr__(self,name):
  177. try:
  178. return self.__class__.__bases__[0].__getattr__(self,name)
  179. except:
  180. parent = self.parent
  181. c = parent._children
  182. x = self.__propholder_index__
  183. while x:
  184. if x in c:
  185. return getattr(c[x],name)
  186. x = x[:-1]
  187. return getattr(parent,name)
  188. @property
  189. def parent(self):
  190. return self.__propholder_parent__()
  191. return WKlass
  192. def __getitem__(self, x):
  193. x = tuple(x) if isinstance(x,(tuple,list)) else (x,)
  194. try:
  195. return self._children[x]
  196. except KeyError:
  197. Klass = self._value.__class__
  198. if Klass in _ItemWrapper:
  199. WKlass = _ItemWrapper[Klass]
  200. else:
  201. _ItemWrapper[Klass] = WKlass = self.wKlassFactory(Klass)
  202. child = WKlass()
  203. for i in filter(lambda x,K=list(child.__dict__.keys()): x in K,list(child._attrMap.keys())):
  204. del child.__dict__[i]
  205. child.__dict__.update(dict(
  206. __propholder_parent__ = weakref_ref(self),
  207. __propholder_index__ = x[:-1])
  208. )
  209. self._children[x] = child
  210. return child
  211. def __contains__(self,key):
  212. return (tuple(key) if isinstance(key,(tuple,list)) else (key,)) in self._children
  213. def __setitem__(self, key, value):
  214. assert isinstance(value, self._value.__class__), (
  215. "This collection can only hold objects of type %s" % self._value.__class__.__name__)
  216. def __len__(self):
  217. return len(list(self._children.keys()))
  218. def getProperties(self,recur=1):
  219. # return any children which are defined and whatever
  220. # differs from the parent
  221. props = {}
  222. for key, value in self._value.getProperties(recur=recur).items():
  223. props['%s' % key] = value
  224. for idx in self._children.keys():
  225. childProps = self._children[idx].getProperties(recur=recur)
  226. for key, value in childProps.items():
  227. if not hasattr(self,key) or getattr(self, key)!=value:
  228. newKey = '[%s].%s' % (idx if len(idx)>1 else idx[0], key)
  229. props[newKey] = value
  230. return props
  231. def setVector(self,**kw):
  232. for name, value in kw.items():
  233. for i, v in enumerate(value):
  234. setattr(self[i],name,v)
  235. def __getattr__(self,name):
  236. return getattr(self._value,name)
  237. def __setattr__(self,name,value):
  238. return setattr(self._value,name,value)
  239. def checkAttr(self, key, a, default=None):
  240. return getattr(self[key], a, default) if key in self else default
  241. def tpcGetItem(obj,x):
  242. '''return obj if it's not a TypedPropertyCollection else obj[x]'''
  243. return obj[x] if isinstance(obj,TypedPropertyCollection) else obj
  244. def isWKlass(obj):
  245. if not hasattr(obj,'__propholder_parent__'): return
  246. ph = obj.__propholder_parent__
  247. if not isinstance(ph,weakref_ref): return
  248. return isinstance(ph(),TypedPropertyCollection)
  249. ## No longer needed!
  250. class StyleProperties(PropHolder):
  251. """A container class for attributes used in charts and legends.
  252. Attributes contained can be those for any graphical element
  253. (shape?) in the ReportLab graphics package. The idea for this
  254. container class is to be useful in combination with legends
  255. and/or the individual appearance of data series in charts.
  256. A legend could be as simple as a wrapper around a list of style
  257. properties, where the 'desc' attribute contains a descriptive
  258. string and the rest could be used by the legend e.g. to draw
  259. something like a color swatch. The graphical presentation of
  260. the legend would be its own business, though.
  261. A chart could be inspecting a legend or, more directly, a list
  262. of style properties to pick individual attributes that it knows
  263. about in order to render a particular row of the data. A bar
  264. chart e.g. could simply use 'strokeColor' and 'fillColor' for
  265. drawing the bars while a line chart could also use additional
  266. ones like strokeWidth.
  267. """
  268. _attrMap = AttrMap(
  269. strokeWidth = AttrMapValue(isNumber,desc='width of the stroke line'),
  270. strokeLineCap = AttrMapValue(isNumber,desc='Line cap 0=butt, 1=round & 2=square',advancedUsage=1),
  271. strokeLineJoin = AttrMapValue(isNumber,desc='Line join 0=miter, 1=round & 2=bevel',advancedUsage=1),
  272. strokeMiterLimit = AttrMapValue(None,desc='miter limit control miter line joins',advancedUsage=1),
  273. strokeDashArray = AttrMapValue(isListOfNumbersOrNone,desc='dashing patterns e.g. (1,3)'),
  274. strokeOpacity = AttrMapValue(isNumber,desc='level of transparency (alpha) accepts values between 0..1',advancedUsage=1),
  275. strokeColor = AttrMapValue(isColorOrNone,desc='the color of the stroke'),
  276. fillColor = AttrMapValue(isColorOrNone,desc='the filling color'),
  277. desc = AttrMapValue(isString),
  278. )
  279. def __init__(self, **kwargs):
  280. "Initialize with attributes if any."
  281. for k, v in kwargs.items():
  282. setattr(self, k, v)
  283. def __setattr__(self, name, value):
  284. "Verify attribute name and value, before setting it."
  285. validateSetattr(self,name,value)
  286. class TwoCircles(Widget):
  287. def __init__(self):
  288. self.leftCircle = shapes.Circle(100,100,20, fillColor=colors.red)
  289. self.rightCircle = shapes.Circle(300,100,20, fillColor=colors.red)
  290. def draw(self):
  291. return shapes.Group(self.leftCircle, self.rightCircle)
  292. class Face(Widget):
  293. """This draws a face with two eyes.
  294. It exposes a couple of properties
  295. to configure itself and hides all other details.
  296. """
  297. _attrMap = AttrMap(
  298. x = AttrMapValue(isNumber),
  299. y = AttrMapValue(isNumber),
  300. size = AttrMapValue(isNumber),
  301. skinColor = AttrMapValue(isColorOrNone),
  302. eyeColor = AttrMapValue(isColorOrNone),
  303. mood = AttrMapValue(OneOf('happy','sad','ok')),
  304. )
  305. def __init__(self):
  306. self.x = 10
  307. self.y = 10
  308. self.size = 80
  309. self.skinColor = None
  310. self.eyeColor = colors.blue
  311. self.mood = 'happy'
  312. def demo(self):
  313. pass
  314. def draw(self):
  315. s = self.size # abbreviate as we will use this a lot
  316. g = shapes.Group()
  317. g.transform = [1,0,0,1,self.x, self.y]
  318. # background
  319. g.add(shapes.Circle(s * 0.5, s * 0.5, s * 0.5, fillColor=self.skinColor))
  320. # left eye
  321. g.add(shapes.Circle(s * 0.35, s * 0.65, s * 0.1, fillColor=colors.white))
  322. g.add(shapes.Circle(s * 0.35, s * 0.65, s * 0.05, fillColor=self.eyeColor))
  323. # right eye
  324. g.add(shapes.Circle(s * 0.65, s * 0.65, s * 0.1, fillColor=colors.white))
  325. g.add(shapes.Circle(s * 0.65, s * 0.65, s * 0.05, fillColor=self.eyeColor))
  326. # nose
  327. g.add(shapes.Polygon(
  328. points=[s * 0.5, s * 0.6, s * 0.4, s * 0.3, s * 0.6, s * 0.3],
  329. fillColor=None))
  330. # mouth
  331. if self.mood == 'happy':
  332. offset = -0.05
  333. elif self.mood == 'sad':
  334. offset = +0.05
  335. else:
  336. offset = 0
  337. g.add(shapes.Polygon(
  338. points = [
  339. s * 0.3, s * 0.2, #left of mouth
  340. s * 0.7, s * 0.2, #right of mouth
  341. s * 0.6, s * (0.2 + offset), # the bit going up or down
  342. s * 0.4, s * (0.2 + offset) # the bit going up or down
  343. ],
  344. fillColor = colors.pink,
  345. strokeColor = colors.red,
  346. strokeWidth = s * 0.03
  347. ))
  348. return g
  349. class TwoFaces(Widget):
  350. def __init__(self):
  351. self.faceOne = Face()
  352. self.faceOne.mood = "happy"
  353. self.faceTwo = Face()
  354. self.faceTwo.x = 100
  355. self.faceTwo.mood = "sad"
  356. def draw(self):
  357. """Just return a group"""
  358. return shapes.Group(self.faceOne, self.faceTwo)
  359. def demo(self):
  360. """The default case already looks good enough,
  361. no implementation needed here"""
  362. pass
  363. class Sizer(Widget):
  364. "Container to show size of all enclosed objects"
  365. _attrMap = AttrMap(BASE=shapes.SolidShape,
  366. contents = AttrMapValue(isListOfShapes,desc="Contained drawable elements"),
  367. )
  368. def __init__(self, *elements):
  369. self.contents = []
  370. self.fillColor = colors.cyan
  371. self.strokeColor = colors.magenta
  372. for elem in elements:
  373. self.add(elem)
  374. def _addNamedNode(self,name,node):
  375. 'if name is not None add an attribute pointing to node and add to the attrMap'
  376. if name:
  377. if name not in list(self._attrMap.keys()):
  378. self._attrMap[name] = AttrMapValue(isValidChild)
  379. setattr(self, name, node)
  380. def add(self, node, name=None):
  381. """Appends non-None child node to the 'contents' attribute. In addition,
  382. if a name is provided, it is subsequently accessible by name
  383. """
  384. # propagates properties down
  385. if node is not None:
  386. assert isValidChild(node), "Can only add Shape or UserNode objects to a Group"
  387. self.contents.append(node)
  388. self._addNamedNode(name,node)
  389. def getBounds(self):
  390. # get bounds of each object
  391. if self.contents:
  392. b = []
  393. for elem in self.contents:
  394. b.append(elem.getBounds())
  395. return shapes.getRectsBounds(b)
  396. else:
  397. return (0,0,0,0)
  398. def draw(self):
  399. g = shapes.Group()
  400. (x1, y1, x2, y2) = self.getBounds()
  401. r = shapes.Rect(
  402. x = x1,
  403. y = y1,
  404. width = x2-x1,
  405. height = y2-y1,
  406. fillColor = self.fillColor,
  407. strokeColor = self.strokeColor
  408. )
  409. g.add(r)
  410. for elem in self.contents:
  411. g.add(elem)
  412. return g
  413. class CandleStickProperties(PropHolder):
  414. _attrMap = AttrMap(
  415. strokeWidth = AttrMapValue(isNumber, desc='Width of a line.'),
  416. strokeColor = AttrMapValue(isColorOrNone, desc='Color of a line or border.'),
  417. strokeDashArray = AttrMapValue(isListOfNumbersOrNone, desc='Dash array of a line.'),
  418. crossWidth = AttrMapValue(isNumberOrNone,desc="cross line width",advancedUsage=1),
  419. crossLo = AttrMapValue(isNumberOrNone,desc="cross line low value",advancedUsage=1),
  420. crossHi = AttrMapValue(isNumberOrNone,desc="cross line high value",advancedUsage=1),
  421. boxWidth = AttrMapValue(isNumberOrNone,desc="width of the box part",advancedUsage=1),
  422. boxFillColor = AttrMapValue(isColorOrNone, desc='fill color of box'),
  423. boxStrokeColor = AttrMapValue(NotSetOr(isColorOrNone), desc='stroke color of box'),
  424. boxStrokeDashArray = AttrMapValue(NotSetOr(isListOfNumbersOrNone), desc='Dash array of the box.'),
  425. boxStrokeWidth = AttrMapValue(NotSetOr(isNumber), desc='Width of the box lines.'),
  426. boxLo = AttrMapValue(isNumberOrNone,desc="low value of the box",advancedUsage=1),
  427. boxMid = AttrMapValue(isNumberOrNone,desc="middle box line value",advancedUsage=1),
  428. boxHi = AttrMapValue(isNumberOrNone,desc="high value of the box",advancedUsage=1),
  429. boxSides = AttrMapValue(isBoolean,desc="whether to show box sides",advancedUsage=1),
  430. position = AttrMapValue(isNumberOrNone,desc="position of the candle",advancedUsage=1),
  431. chart = AttrMapValue(None,desc="our chart",advancedUsage=1),
  432. candleKind = AttrMapValue(OneOf('vertical','horizontal'),desc="candle direction",advancedUsage=1),
  433. axes = AttrMapValue(SequenceOf(isString,emptyOK=0,lo=2,hi=2),desc="candle direction",advancedUsage=1),
  434. )
  435. def __init__(self,**kwds):
  436. self.strokeWidth = kwds.pop('strokeWidth',1)
  437. self.strokeColor = kwds.pop('strokeColor',colors.black)
  438. self.strokeDashArray = kwds.pop('strokeDashArray',None)
  439. self.crossWidth = kwds.pop('crossWidth',5)
  440. self.crossLo = kwds.pop('crossLo',None)
  441. self.crossHi = kwds.pop('crossHi',None)
  442. self.boxWidth = kwds.pop('boxWidth',None)
  443. self.boxFillColor = kwds.pop('boxFillColor',None)
  444. self.boxStrokeColor =kwds.pop('boxStrokeColor',NotSetOr._not_set)
  445. self.boxStrokeWidth =kwds.pop('boxStrokeWidth',NotSetOr._not_set)
  446. self.boxStrokeDashArray =kwds.pop('boxStrokeDashArray',NotSetOr._not_set)
  447. self.boxLo = kwds.pop('boxLo',None)
  448. self.boxMid = kwds.pop('boxMid',None)
  449. self.boxHi = kwds.pop('boxHi',None)
  450. self.boxSides = kwds.pop('boxSides',True)
  451. self.position = kwds.pop('position',None)
  452. self.candleKind = kwds.pop('candleKind','vertical')
  453. self.axes = kwds.pop('axes',['categoryAxis','valueAxis'])
  454. chart = kwds.pop('chart',None)
  455. self.chart = weakref_ref(chart) if chart else (lambda:None)
  456. def __call__(self,_x,_y,_size,_color):
  457. '''the symbol interface'''
  458. chart = self.chart()
  459. xA = getattr(chart,self.axes[0])
  460. _xScale = getattr(xA,'midScale',None)
  461. if not _xScale: _xScale = getattr(xA,'scale')
  462. xScale = lambda x: _xScale(x) if x is not None else None
  463. yA = getattr(chart,self.axes[1])
  464. _yScale = getattr(yA,'midScale',None)
  465. if not _yScale: _yScale = getattr(yA,'scale')
  466. yScale = lambda x: _yScale(x) if x is not None else None
  467. G = shapes.Group().add
  468. strokeWidth = self.strokeWidth
  469. strokeColor = self.strokeColor
  470. strokeDashArray = self.strokeDashArray
  471. crossWidth = self.crossWidth
  472. crossLo = yScale(self.crossLo)
  473. crossHi = yScale(self.crossHi)
  474. boxWidth = self.boxWidth
  475. boxFillColor = self.boxFillColor
  476. boxStrokeColor = NotSetOr.conditionalValue(self.boxStrokeColor,strokeColor)
  477. boxStrokeWidth = NotSetOr.conditionalValue(self.boxStrokeWidth,strokeWidth)
  478. boxStrokeDashArray = NotSetOr.conditionalValue(self.boxStrokeDashArray,strokeDashArray)
  479. boxLo = yScale(self.boxLo)
  480. boxMid = yScale(self.boxMid)
  481. boxHi = yScale(self.boxHi)
  482. position = xScale(self.position)
  483. candleKind = self.candleKind
  484. haveBox = None not in (boxWidth,boxLo,boxHi)
  485. haveLine = None not in (crossLo,crossHi)
  486. def aLine(x0,y0,x1,y1):
  487. if candleKind!='vertical':
  488. x0,y0 = y0,x0
  489. x1,y1 = y1,x1
  490. G(shapes.Line(x0,y0,x1,y1,strokeWidth=strokeWidth,strokeColor=strokeColor,strokeDashArray=strokeDashArray))
  491. if haveBox:
  492. boxLo, boxHi = min(boxLo,boxHi), max(boxLo,boxHi)
  493. if haveLine:
  494. crossLo, crossHi = min(crossLo,crossHi), max(crossLo,crossHi)
  495. if not haveBox or crossLo>=boxHi or crossHi<=boxLo:
  496. aLine(position,crossLo,position,crossHi)
  497. if crossWidth is not None:
  498. aLine(position-crossWidth*0.5,crossLo,position+crossWidth*0.5,crossLo)
  499. aLine(position-crossWidth*0.5,crossHi,position+crossWidth*0.5,crossHi)
  500. elif haveBox:
  501. if crossLo<boxLo:
  502. aLine(position,crossLo,position,boxLo)
  503. aLine(position-crossWidth*0.5,crossLo,position+crossWidth*0.5,crossLo)
  504. if crossHi>boxHi:
  505. aLine(position,boxHi,position,crossHi)
  506. aLine(position-crossWidth*0.5,crossHi,position+crossWidth*0.5,crossHi)
  507. if haveBox:
  508. x = position - boxWidth*0.5
  509. y = boxLo
  510. h = boxHi - boxLo
  511. w = boxWidth
  512. if candleKind!='vertical':
  513. x, y, w, h = y, x, h, w
  514. G(shapes.Rect(x,y,w,h,strokeColor=boxStrokeColor if self.boxSides else None,strokeWidth=boxStrokeWidth,strokeDashArray=boxStrokeDashArray,fillColor=boxFillColor))
  515. if not self.boxSides:
  516. aLine(position-0.5*boxWidth,boxHi,position+0.5*boxWidth,boxHi)
  517. aLine(position-0.5*boxWidth,boxLo,position+0.5*boxWidth,boxLo)
  518. if boxMid is not None:
  519. aLine(position-0.5*boxWidth,boxMid,position+0.5*boxWidth,boxMid)
  520. return G.__self__
  521. def CandleSticks(**kwds):
  522. return TypedPropertyCollection(CandleStickProperties,**kwds)
  523. def test():
  524. from reportlab.graphics.charts.piecharts import WedgeProperties
  525. wedges = TypedPropertyCollection(WedgeProperties)
  526. wedges.fillColor = colors.red
  527. wedges.setVector(fillColor=(colors.blue,colors.green,colors.white))
  528. print(len(_ItemWrapper))
  529. d = shapes.Drawing(400, 200)
  530. tc = TwoCircles()
  531. d.add(tc)
  532. from reportlab.graphics import renderPDF
  533. renderPDF.drawToFile(d, 'sample_widget.pdf', 'A Sample Widget')
  534. print('saved sample_widget.pdf')
  535. d = shapes.Drawing(400, 200)
  536. f = Face()
  537. f.skinColor = colors.yellow
  538. f.mood = "sad"
  539. d.add(f, name='theFace')
  540. print('drawing 1 properties:')
  541. d.dumpProperties()
  542. renderPDF.drawToFile(d, 'face.pdf', 'A Sample Widget')
  543. print('saved face.pdf')
  544. d2 = d.expandUserNodes()
  545. renderPDF.drawToFile(d2, 'face_copy.pdf', 'An expanded drawing')
  546. print('saved face_copy.pdf')
  547. print('drawing 2 properties:')
  548. d2.dumpProperties()
  549. if __name__=='__main__':
  550. test()