remove shebang -- see comment 3 on https://bugzilla.redhat.com/bugzilla/show_bug...
[PyX/mjg.git] / pyx / graph / style.py
blobe3c66adb59718920327fd756148d03cff0c78c37
1 # -*- coding: ISO-8859-1 -*-
4 # Copyright (C) 2002-2004 Jörg Lehmann <joergl@users.sourceforge.net>
5 # Copyright (C) 2003-2004 Michael Schindler <m-schindler@users.sourceforge.net>
6 # Copyright (C) 2002-2005 André Wobst <wobsta@users.sourceforge.net>
8 # This file is part of PyX (http://pyx.sourceforge.net/).
10 # PyX is free software; you can redistribute it and/or modify
11 # it under the terms of the GNU General Public License as published by
12 # the Free Software Foundation; either version 2 of the License, or
13 # (at your option) any later version.
15 # PyX is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 # GNU General Public License for more details.
20 # You should have received a copy of the GNU General Public License
21 # along with PyX; if not, write to the Free Software
22 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
25 import math, warnings
26 from pyx import attr, deco, style, color, unit, canvas, path
27 from pyx import text as textmodule
29 builtinrange = range
31 try:
32 enumerate([])
33 except NameError:
34 # fallback implementation for Python 2.2. and below
35 def enumerate(list):
36 return zip(xrange(len(list)), list)
38 class _style:
39 """Interface class for graph styles
41 Each graph style must support the methods described in this
42 class. However, since a graph style might not need to perform
43 actions on all the various events, it does not need to overwrite
44 all methods of this base class (e.g. this class is not an abstract
45 class in any respect).
47 A style should never store private data by istance variables
48 (i.e. accessing self), but it should use the sharedata and privatedata
49 instances instead. A style instance can be used multiple times with
50 different sharedata and privatedata instances at the very same time.
51 The sharedata and privatedata instances act as data containers and
52 sharedata allows for sharing information across several styles.
54 Every style contains two class variables, which are not to be
55 modified:
56 - providesdata is a list of variable names a style offers via
57 the sharedata instance. This list is used to determine whether
58 all needs of subsequent styles are fullfilled. Otherwise
59 getdefaultprovider should return a proper style to be used.
60 - needsdata is a list of variable names the style needs to access in the
61 sharedata instance.
62 """
64 providesdata = [] # by default, we provide nothing
65 needsdata = [] # and do not depend on anything
67 def columnnames(self, privatedata, sharedata, graph, columnnames):
68 """Set column information
70 This method is used setup the column name information to be
71 accessible to the style later on. The style should analyse
72 the list of column names. The method should return a list of
73 column names which the style will make use of."""
74 return []
76 def adjustaxis(self, privatedata, sharedata, graph, columnname, data):
77 """Adjust axis range
79 This method is called in order to adjust the axis range to
80 the provided data. columnname is the column name (each style
81 is subsequently called for all column names)."""
82 pass
84 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
85 """Select stroke/fill attributes
87 This method is called to allow for the selection of
88 changable attributes of a style."""
89 pass
91 def initdrawpoints(self, privatedata, sharedata, graph):
92 """Initialize drawing of data
94 This method might be used to initialize the drawing of data."""
95 pass
97 def drawpoint(self, privatedata, sharedata, graph, point):
98 """Draw data
100 This method is called for each data point. The data is
101 available in the dictionary point. The dictionary
102 keys are the column names."""
103 pass
105 def donedrawpoints(self, privatedata, sharedata, graph):
106 """Finalize drawing of data
108 This method is called after the last data point was
109 drawn using the drawpoint method above."""
110 pass
112 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
113 """Draw graph key"""
116 # The following two methods are used to register and get a default provider
117 # for keys. A key is a variable name in sharedata. A provider is a style
118 # which creates variables in sharedata.
120 _defaultprovider = {}
122 def registerdefaultprovider(style, keys):
123 """sets a style as a default creator for sharedata variables 'keys'"""
124 assert not len(style.needsdata), "currently we state, that a style should not depend on other sharedata variables"
125 for key in keys:
126 assert key in style.providesdata, "key not provided by style"
127 # we might allow for overwriting the defaults, i.e. the following is not checked:
128 # assert key in _defaultprovider.keys(), "default provider already registered for key"
129 _defaultprovider[key] = style
131 def getdefaultprovider(key):
132 """returns a style, which acts as a default creator for the
133 sharedata variable 'key'"""
134 return _defaultprovider[key]
137 class pos(_style):
139 providesdata = ["vpos", "vposmissing", "vposavailable", "vposvalid", "poscolumnnames"]
141 def __init__(self, epsilon=1e-10):
142 self.epsilon = epsilon
144 def columnnames(self, privatedata, sharedata, graph, columnnames):
145 sharedata.poscolumnnames = []
146 sharedata.vposmissing = []
147 for count, axisnames in enumerate(graph.axesnames):
148 for axisname in axisnames:
149 for columnname in columnnames:
150 if axisname == columnname:
151 sharedata.poscolumnnames.append(columnname)
152 if len(sharedata.poscolumnnames) > count+1:
153 raise ValueError("multiple axes per graph dimension")
154 elif len(sharedata.poscolumnnames) < count+1:
155 sharedata.vposmissing.append(count)
156 sharedata.poscolumnnames.append(None)
157 return [columnname for columnname in sharedata.poscolumnnames if columnname is not None]
159 def adjustaxis(self, privatedata, sharedata, graph, columnname, data):
160 if columnname in sharedata.poscolumnnames:
161 graph.axes[columnname].adjustaxis(data)
163 def initdrawpoints(self, privatedata, sharedata, graph):
164 sharedata.vpos = [None]*(len(graph.axesnames))
165 privatedata.pointpostmplist = [[columnname, index, graph.axes[columnname]] # temporarily used by drawpoint only
166 for index, columnname in enumerate([columnname for columnname in sharedata.poscolumnnames if columnname is not None])]
167 for missing in sharedata.vposmissing:
168 for pointpostmp in privatedata.pointpostmplist:
169 if pointpostmp[1] >= missing:
170 pointpostmp[1] += 1
172 def drawpoint(self, privatedata, sharedata, graph, point):
173 sharedata.vposavailable = 1 # valid position (but might be outside of the graph)
174 sharedata.vposvalid = 1 # valid position inside the graph
175 for columnname, index, axis in privatedata.pointpostmplist:
176 try:
177 v = axis.convert(point[columnname])
178 except (ArithmeticError, ValueError, TypeError):
179 sharedata.vposavailable = sharedata.vposvalid = 0
180 sharedata.vpos[index] = None
181 else:
182 if v < -self.epsilon or v > 1+self.epsilon:
183 sharedata.vposvalid = 0
184 sharedata.vpos[index] = v
187 registerdefaultprovider(pos(), pos.providesdata)
190 class range(_style):
192 providesdata = ["vrange", "vrangemissing", "vrangeminmissing", "vrangemaxmissing"]
194 # internal bit masks
195 mask_value = 1
196 mask_min = 2
197 mask_max = 4
198 mask_dmin = 8
199 mask_dmax = 16
200 mask_d = 32
202 def __init__(self, usenames={}, epsilon=1e-10):
203 self.usenames = usenames
204 self.epsilon = epsilon
206 def _numberofbits(self, mask):
207 if not mask:
208 return 0
209 if mask & 1:
210 return self._numberofbits(mask >> 1) + 1
211 else:
212 return self._numberofbits(mask >> 1)
214 def columnnames(self, privatedata, sharedata, graph, columnnames):
215 usecolumns = []
216 privatedata.rangeposcolumns = []
217 sharedata.vrangemissing = []
218 sharedata.vrangeminmissing = []
219 sharedata.vrangemaxmissing = []
220 privatedata.rangeposdeltacolumns = {} # temporarily used by adjustaxis only
221 for count, axisnames in enumerate(graph.axesnames):
222 for axisname in axisnames:
223 try:
224 usename = self.usenames[axisname]
225 except KeyError:
226 usename = axisname
227 mask = 0
228 for columnname in columnnames:
229 addusecolumns = 1
230 if usename == columnname:
231 mask += self.mask_value
232 elif usename + "min" == columnname:
233 mask += self.mask_min
234 elif usename + "max" == columnname:
235 mask += self.mask_max
236 elif "d" + usename + "min" == columnname:
237 mask += self.mask_dmin
238 elif "d" + usename + "max" == columnname:
239 mask += self.mask_dmax
240 elif "d" + usename == columnname:
241 mask += self.mask_d
242 else:
243 addusecolumns = 0
244 if addusecolumns:
245 usecolumns.append(columnname)
246 if mask & (self.mask_min | self.mask_max | self.mask_dmin | self.mask_dmax | self.mask_d):
247 if (self._numberofbits(mask & (self.mask_min | self.mask_dmin | self.mask_d)) > 1 or
248 self._numberofbits(mask & (self.mask_max | self.mask_dmax | self.mask_d)) > 1):
249 raise ValueError("multiple range definition")
250 if mask & (self.mask_dmin | self.mask_dmax | self.mask_d):
251 if not (mask & self.mask_value):
252 raise ValueError("missing value for delta")
253 privatedata.rangeposdeltacolumns[axisname] = {}
254 privatedata.rangeposcolumns.append((axisname, usename, mask))
255 elif mask == self.mask_value:
256 usecolumns = usecolumns[:-1]
257 if len(privatedata.rangeposcolumns) + len(sharedata.vrangemissing) > count+1:
258 raise ValueError("multiple axes per graph dimension")
259 elif len(privatedata.rangeposcolumns) + len(sharedata.vrangemissing) < count+1:
260 sharedata.vrangemissing.append(count)
261 sharedata.vrangeminmissing.append(count)
262 sharedata.vrangemaxmissing.append(count)
263 else:
264 if not (privatedata.rangeposcolumns[-1][2] & (self.mask_min | self.mask_dmin | self.mask_d)):
265 sharedata.vrangeminmissing.append(count)
266 if not (privatedata.rangeposcolumns[-1][2] & (self.mask_max | self.mask_dmax | self.mask_d)):
267 sharedata.vrangemaxmissing.append(count)
268 return usecolumns
270 def adjustaxis(self, privatedata, sharedata, graph, columnname, data):
271 if columnname in [c + "min" for a, c, m in privatedata.rangeposcolumns if m & self.mask_min]:
272 graph.axes[columnname[:-3]].adjustaxis(data)
273 if columnname in [c + "max" for a, c, m in privatedata.rangeposcolumns if m & self.mask_max]:
274 graph.axes[columnname[:-3]].adjustaxis(data)
276 # delta handling: fill rangeposdeltacolumns
277 for axisname, usename, mask in privatedata.rangeposcolumns:
278 if columnname == usename and mask & (self.mask_dmin | self.mask_dmax | self.mask_d):
279 privatedata.rangeposdeltacolumns[axisname][self.mask_value] = data
280 if columnname == "d" + usename + "min" and mask & self.mask_dmin:
281 privatedata.rangeposdeltacolumns[axisname][self.mask_dmin] = data
282 if columnname == "d" + usename + "max" and mask & self.mask_dmax:
283 privatedata.rangeposdeltacolumns[axisname][self.mask_dmax] = data
284 if columnname == "d" + usename and mask & self.mask_d:
285 privatedata.rangeposdeltacolumns[axisname][self.mask_d] = data
287 # delta handling: process rangeposdeltacolumns
288 for a, d in privatedata.rangeposdeltacolumns.items():
289 if d.has_key(self.mask_value):
290 for k in d.keys():
291 if k != self.mask_value:
292 if k & (self.mask_dmin | self.mask_d):
293 mindata = []
294 try:
295 mindata.append(d[self.mask_value] - d[k])
296 except:
297 pass
298 graph.axes[a].adjustaxis(mindata)
299 if k & (self.mask_dmax | self.mask_d):
300 maxdata = []
301 try:
302 maxdata.append(d[self.mask_value] + d[k])
303 except:
304 pass
305 graph.axes[a].adjustaxis(maxdata)
306 del d[k]
308 def initdrawpoints(self, privatedata, sharedata, graph):
309 sharedata.vrange = [[None for x in xrange(2)] for y in privatedata.rangeposcolumns + sharedata.vrangemissing]
310 privatedata.rangepostmplist = [[usename, mask, index, graph.axes[axisname]] # temporarily used by drawpoint only
311 for index, (axisname, usename, mask) in enumerate(privatedata.rangeposcolumns)]
312 for missing in sharedata.vrangemissing:
313 for rangepostmp in privatedata.rangepostmplist:
314 if rangepostmp[2] >= missing:
315 rangepostmp[2] += 1
317 def drawpoint(self, privatedata, sharedata, graph, point):
318 for usename, mask, index, axis in privatedata.rangepostmplist:
319 try:
320 if mask & self.mask_min:
321 sharedata.vrange[index][0] = axis.convert(point[usename + "min"])
322 if mask & self.mask_dmin:
323 sharedata.vrange[index][0] = axis.convert(point[usename] - point["d" + usename + "min"])
324 if mask & self.mask_d:
325 sharedata.vrange[index][0] = axis.convert(point[usename] - point["d" + usename])
326 except (ArithmeticError, ValueError, TypeError):
327 sharedata.vrange[index][0] = None
328 try:
329 if mask & self.mask_max:
330 sharedata.vrange[index][1] = axis.convert(point[usename + "max"])
331 if mask & self.mask_dmax:
332 sharedata.vrange[index][1] = axis.convert(point[usename] + point["d" + usename + "max"])
333 if mask & self.mask_d:
334 sharedata.vrange[index][1] = axis.convert(point[usename] + point["d" + usename])
335 except (ArithmeticError, ValueError, TypeError):
336 sharedata.vrange[index][1] = None
338 # some range checks for data consistency
339 if (sharedata.vrange[index][0] is not None and sharedata.vrange[index][1] is not None and
340 sharedata.vrange[index][0] > sharedata.vrange[index][1] + self.epsilon):
341 raise ValueError("inverse range")
342 # disabled due to missing vpos access:
343 # if (sharedata.vrange[index][0] is not None and sharedata.vpos[index] is not None and
344 # sharedata.vrange[index][0] > sharedata.vpos[index] + self.epsilon):
345 # raise ValueError("negative minimum errorbar")
346 # if (sharedata.vrange[index][1] is not None and sharedata.vpos[index] is not None and
347 # sharedata.vrange[index][1] < sharedata.vpos[index] - self.epsilon):
348 # raise ValueError("negative maximum errorbar")
351 registerdefaultprovider(range(), range.providesdata)
354 def _crosssymbol(c, x_pt, y_pt, size_pt, attrs):
355 c.draw(path.path(path.moveto_pt(x_pt-0.5*size_pt, y_pt-0.5*size_pt),
356 path.lineto_pt(x_pt+0.5*size_pt, y_pt+0.5*size_pt),
357 path.moveto_pt(x_pt-0.5*size_pt, y_pt+0.5*size_pt),
358 path.lineto_pt(x_pt+0.5*size_pt, y_pt-0.5*size_pt)), attrs)
360 def _plussymbol(c, x_pt, y_pt, size_pt, attrs):
361 c.draw(path.path(path.moveto_pt(x_pt-0.707106781*size_pt, y_pt),
362 path.lineto_pt(x_pt+0.707106781*size_pt, y_pt),
363 path.moveto_pt(x_pt, y_pt-0.707106781*size_pt),
364 path.lineto_pt(x_pt, y_pt+0.707106781*size_pt)), attrs)
366 def _squaresymbol(c, x_pt, y_pt, size_pt, attrs):
367 c.draw(path.path(path.moveto_pt(x_pt-0.5*size_pt, y_pt-0.5*size_pt),
368 path.lineto_pt(x_pt+0.5*size_pt, y_pt-0.5*size_pt),
369 path.lineto_pt(x_pt+0.5*size_pt, y_pt+0.5*size_pt),
370 path.lineto_pt(x_pt-0.5*size_pt, y_pt+0.5*size_pt),
371 path.closepath()), attrs)
373 def _trianglesymbol(c, x_pt, y_pt, size_pt, attrs):
374 c.draw(path.path(path.moveto_pt(x_pt-0.759835685*size_pt, y_pt-0.438691337*size_pt),
375 path.lineto_pt(x_pt+0.759835685*size_pt, y_pt-0.438691337*size_pt),
376 path.lineto_pt(x_pt, y_pt+0.877382675*size_pt),
377 path.closepath()), attrs)
379 def _circlesymbol(c, x_pt, y_pt, size_pt, attrs):
380 c.draw(path.path(path.arc_pt(x_pt, y_pt, 0.564189583*size_pt, 0, 360),
381 path.closepath()), attrs)
383 def _diamondsymbol(c, x_pt, y_pt, size_pt, attrs):
384 c.draw(path.path(path.moveto_pt(x_pt-0.537284965*size_pt, y_pt),
385 path.lineto_pt(x_pt, y_pt-0.930604859*size_pt),
386 path.lineto_pt(x_pt+0.537284965*size_pt, y_pt),
387 path.lineto_pt(x_pt, y_pt+0.930604859*size_pt),
388 path.closepath()), attrs)
391 class _styleneedingpointpos(_style):
393 needsdata = ["vposmissing"]
395 def columnnames(self, privatedata, sharedata, graph, columnnames):
396 if len(sharedata.vposmissing):
397 raise ValueError("incomplete position information")
398 return []
401 class symbol(_styleneedingpointpos):
403 needsdata = ["vpos", "vposmissing", "vposvalid"]
405 # "inject" the predefinied symbols into the class:
407 # Note, that statements like cross = _crosssymbol are
408 # invalid, since the would lead to unbound methods, but
409 # a single entry changeable list does the trick.
411 # Once we require Python 2.2+ we should use staticmethods
412 # to implement the default symbols inplace.
414 cross = attr.changelist([_crosssymbol])
415 plus = attr.changelist([_plussymbol])
416 square = attr.changelist([_squaresymbol])
417 triangle = attr.changelist([_trianglesymbol])
418 circle = attr.changelist([_circlesymbol])
419 diamond = attr.changelist([_diamondsymbol])
421 changecross = attr.changelist([_crosssymbol, _plussymbol, _squaresymbol, _trianglesymbol, _circlesymbol, _diamondsymbol])
422 changeplus = attr.changelist([_plussymbol, _squaresymbol, _trianglesymbol, _circlesymbol, _diamondsymbol, _crosssymbol])
423 changesquare = attr.changelist([_squaresymbol, _trianglesymbol, _circlesymbol, _diamondsymbol, _crosssymbol, _plussymbol])
424 changetriangle = attr.changelist([_trianglesymbol, _circlesymbol, _diamondsymbol, _crosssymbol, _plussymbol, _squaresymbol])
425 changecircle = attr.changelist([_circlesymbol, _diamondsymbol, _crosssymbol, _plussymbol, _squaresymbol, _trianglesymbol])
426 changediamond = attr.changelist([_diamondsymbol, _crosssymbol, _plussymbol, _squaresymbol, _trianglesymbol, _circlesymbol])
427 changesquaretwice = attr.changelist([_squaresymbol, _squaresymbol, _trianglesymbol, _trianglesymbol, _circlesymbol, _circlesymbol, _diamondsymbol, _diamondsymbol])
428 changetriangletwice = attr.changelist([_trianglesymbol, _trianglesymbol, _circlesymbol, _circlesymbol, _diamondsymbol, _diamondsymbol, _squaresymbol, _squaresymbol])
429 changecircletwice = attr.changelist([_circlesymbol, _circlesymbol, _diamondsymbol, _diamondsymbol, _squaresymbol, _squaresymbol, _trianglesymbol, _trianglesymbol])
430 changediamondtwice = attr.changelist([_diamondsymbol, _diamondsymbol, _squaresymbol, _squaresymbol, _trianglesymbol, _trianglesymbol, _circlesymbol, _circlesymbol])
432 changestrokedfilled = attr.changelist([deco.stroked, deco.filled])
433 changefilledstroked = attr.changelist([deco.filled, deco.stroked])
435 defaultsymbolattrs = [deco.stroked]
437 def __init__(self, symbol=changecross, size=0.2*unit.v_cm, symbolattrs=[]):
438 self.symbol = symbol
439 self.size = size
440 self.symbolattrs = symbolattrs
442 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
443 privatedata.symbol = attr.selectattr(self.symbol, selectindex, selecttotal)
444 privatedata.size_pt = unit.topt(attr.selectattr(self.size, selectindex, selecttotal))
445 if self.symbolattrs is not None:
446 privatedata.symbolattrs = attr.selectattrs(self.defaultsymbolattrs + self.symbolattrs, selectindex, selecttotal)
447 else:
448 privatedata.symbolattrs = None
450 def initdrawpoints(self, privatedata, sharedata, graph):
451 privatedata.symbolcanvas = canvas.canvas()
453 def drawpoint(self, privatedata, sharedata, graph, point):
454 if sharedata.vposvalid and privatedata.symbolattrs is not None:
455 x_pt, y_pt = graph.vpos_pt(*sharedata.vpos)
456 privatedata.symbol(privatedata.symbolcanvas, x_pt, y_pt, privatedata.size_pt, privatedata.symbolattrs)
458 def donedrawpoints(self, privatedata, sharedata, graph):
459 graph.insert(privatedata.symbolcanvas)
461 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
462 if privatedata.symbolattrs is not None:
463 privatedata.symbol(graph, x_pt+0.5*width_pt, y_pt+0.5*height_pt, privatedata.size_pt, privatedata.symbolattrs)
466 class line(_styleneedingpointpos):
468 needsdata = ["vpos", "vposmissing", "vposavailable", "vposvalid"]
470 changelinestyle = attr.changelist([style.linestyle.solid,
471 style.linestyle.dashed,
472 style.linestyle.dotted,
473 style.linestyle.dashdotted])
475 defaultlineattrs = [changelinestyle]
477 def __init__(self, lineattrs=[]):
478 self.lineattrs = lineattrs
480 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
481 if self.lineattrs is not None:
482 privatedata.lineattrs = attr.selectattrs(self.defaultlineattrs + self.lineattrs, selectindex, selecttotal)
483 else:
484 privatedata.lineattrs = None
486 def initdrawpoints(self, privatedata, sharedata, graph):
487 privatedata.path = path.path()
488 privatedata.linebasepoints = []
489 privatedata.lastvpos = None
491 def addpointstopath(self, privatedata, sharedata):
492 # add baselinepoints to privatedata.path
493 if len(privatedata.linebasepoints) > 1:
494 privatedata.path.append(path.moveto_pt(*privatedata.linebasepoints[0]))
495 if len(privatedata.linebasepoints) > 2:
496 privatedata.path.append(path.multilineto_pt(privatedata.linebasepoints[1:]))
497 else:
498 privatedata.path.append(path.lineto_pt(*privatedata.linebasepoints[1]))
499 privatedata.linebasepoints = []
501 def drawpoint(self, privatedata, sharedata, graph, point):
502 # append linebasepoints
503 if sharedata.vposavailable:
504 if len(privatedata.linebasepoints):
505 # the last point was inside the graph
506 if sharedata.vposvalid: # shortcut for the common case
507 privatedata.linebasepoints.append(graph.vpos_pt(*sharedata.vpos))
508 else:
509 # cut end
510 cut = 1
511 for vstart, vend in zip(privatedata.lastvpos, sharedata.vpos):
512 newcut = None
513 if vend > 1:
514 # 1 = vstart + (vend - vstart) * cut
515 try:
516 newcut = (1 - vstart)/(vend - vstart)
517 except (ArithmeticError, TypeError):
518 break
519 if vend < 0:
520 # 0 = vstart + (vend - vstart) * cut
521 try:
522 newcut = - vstart/(vend - vstart)
523 except (ArithmeticError, TypeError):
524 break
525 if newcut is not None and newcut < cut:
526 cut = newcut
527 else:
528 cutvpos = []
529 for vstart, vend in zip(privatedata.lastvpos, sharedata.vpos):
530 cutvpos.append(vstart + (vend - vstart) * cut)
531 privatedata.linebasepoints.append(graph.vpos_pt(*cutvpos))
532 self.addpointstopath(privatedata, sharedata)
533 else:
534 # the last point was outside the graph
535 if privatedata.lastvpos is not None:
536 if sharedata.vposvalid:
537 # cut beginning
538 cut = 0
539 for vstart, vend in zip(privatedata.lastvpos, sharedata.vpos):
540 newcut = None
541 if vstart > 1:
542 # 1 = vstart + (vend - vstart) * cut
543 try:
544 newcut = (1 - vstart)/(vend - vstart)
545 except (ArithmeticError, TypeError):
546 break
547 if vstart < 0:
548 # 0 = vstart + (vend - vstart) * cut
549 try:
550 newcut = - vstart/(vend - vstart)
551 except (ArithmeticError, TypeError):
552 break
553 if newcut is not None and newcut > cut:
554 cut = newcut
555 else:
556 cutvpos = []
557 for vstart, vend in zip(privatedata.lastvpos, sharedata.vpos):
558 cutvpos.append(vstart + (vend - vstart) * cut)
559 privatedata.linebasepoints.append(graph.vpos_pt(*cutvpos))
560 privatedata.linebasepoints.append(graph.vpos_pt(*sharedata.vpos))
561 else:
562 # sometimes cut beginning and end
563 cutfrom = 0
564 cutto = 1
565 for vstart, vend in zip(privatedata.lastvpos, sharedata.vpos):
566 newcutfrom = None
567 if vstart > 1:
568 if vend > 1:
569 break
570 # 1 = vstart + (vend - vstart) * cutfrom
571 try:
572 newcutfrom = (1 - vstart)/(vend - vstart)
573 except (ArithmeticError, TypeError):
574 break
575 if vstart < 0:
576 if vend < 0:
577 break
578 # 0 = vstart + (vend - vstart) * cutfrom
579 try:
580 newcutfrom = - vstart/(vend - vstart)
581 except (ArithmeticError, TypeError):
582 break
583 if newcutfrom is not None and newcutfrom > cutfrom:
584 cutfrom = newcutfrom
585 newcutto = None
586 if vend > 1:
587 # 1 = vstart + (vend - vstart) * cutto
588 try:
589 newcutto = (1 - vstart)/(vend - vstart)
590 except (ArithmeticError, TypeError):
591 break
592 if vend < 0:
593 # 0 = vstart + (vend - vstart) * cutto
594 try:
595 newcutto = - vstart/(vend - vstart)
596 except (ArithmeticError, TypeError):
597 break
598 if newcutto is not None and newcutto < cutto:
599 cutto = newcutto
600 else:
601 if cutfrom < cutto:
602 cutfromvpos = []
603 cuttovpos = []
604 for vstart, vend in zip(privatedata.lastvpos, sharedata.vpos):
605 cutfromvpos.append(vstart + (vend - vstart) * cutfrom)
606 cuttovpos.append(vstart + (vend - vstart) * cutto)
607 privatedata.linebasepoints.append(graph.vpos_pt(*cutfromvpos))
608 privatedata.linebasepoints.append(graph.vpos_pt(*cuttovpos))
609 self.addpointstopath(privatedata, sharedata)
610 privatedata.lastvpos = sharedata.vpos[:]
611 else:
612 if len(privatedata.linebasepoints) > 1:
613 self.addpointstopath(privatedata, sharedata)
614 privatedata.lastvpos = None
616 def donedrawpoints(self, privatedata, sharedata, graph):
617 if len(privatedata.linebasepoints) > 1:
618 self.addpointstopath(privatedata, sharedata)
619 if privatedata.lineattrs is not None and len(privatedata.path):
620 graph.stroke(privatedata.path, privatedata.lineattrs)
622 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
623 if privatedata.lineattrs is not None:
624 graph.stroke(path.line_pt(x_pt, y_pt+0.5*height_pt, x_pt+width_pt, y_pt+0.5*height_pt), privatedata.lineattrs)
627 class errorbar(_style):
629 needsdata = ["vpos", "vposmissing", "vposavailable", "vposvalid", "vrange", "vrangeminmissing", "vrangemaxmissing"]
631 defaulterrorbarattrs = []
633 def __init__(self, size=0.1*unit.v_cm,
634 errorbarattrs=[],
635 epsilon=1e-10):
636 self.size = size
637 self.errorbarattrs = errorbarattrs
638 self.epsilon = epsilon
640 def columnnames(self, privatedata, sharedata, graph, columnnames):
641 for i in sharedata.vposmissing:
642 if i in sharedata.vrangeminmissing and i in sharedata.vrangemaxmissing:
643 raise ValueError("position and range for a graph dimension missing")
644 return []
646 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
647 privatedata.errorsize_pt = unit.topt(attr.selectattr(self.size, selectindex, selecttotal))
648 privatedata.errorbarattrs = attr.selectattrs(self.defaulterrorbarattrs + self.errorbarattrs, selectindex, selecttotal)
650 def initdrawpoints(self, privatedata, sharedata, graph):
651 if privatedata.errorbarattrs is not None:
652 privatedata.errorbarcanvas = canvas.canvas(privatedata.errorbarattrs)
653 privatedata.dimensionlist = list(xrange(len(sharedata.vpos)))
655 def drawpoint(self, privatedata, sharedata, graph, point):
656 if privatedata.errorbarattrs is not None:
657 for i in privatedata.dimensionlist:
658 for j in privatedata.dimensionlist:
659 if (i != j and
660 (sharedata.vpos[j] is None or
661 sharedata.vpos[j] < -self.epsilon or
662 sharedata.vpos[j] > 1+self.epsilon)):
663 break
664 else:
665 if ((sharedata.vrange[i][0] is None and sharedata.vpos[i] is None) or
666 (sharedata.vrange[i][1] is None and sharedata.vpos[i] is None) or
667 (sharedata.vrange[i][0] is None and sharedata.vrange[i][1] is None)):
668 continue
669 vminpos = sharedata.vpos[:]
670 if sharedata.vrange[i][0] is not None:
671 vminpos[i] = sharedata.vrange[i][0]
672 mincap = 1
673 else:
674 mincap = 0
675 if vminpos[i] > 1+self.epsilon:
676 continue
677 if vminpos[i] < -self.epsilon:
678 vminpos[i] = 0
679 mincap = 0
680 vmaxpos = sharedata.vpos[:]
681 if sharedata.vrange[i][1] is not None:
682 vmaxpos[i] = sharedata.vrange[i][1]
683 maxcap = 1
684 else:
685 maxcap = 0
686 if vmaxpos[i] < -self.epsilon:
687 continue
688 if vmaxpos[i] > 1+self.epsilon:
689 vmaxpos[i] = 1
690 maxcap = 0
691 privatedata.errorbarcanvas.stroke(graph.vgeodesic(*(vminpos + vmaxpos)))
692 for j in privatedata.dimensionlist:
693 if i != j:
694 if mincap:
695 privatedata.errorbarcanvas.stroke(graph.vcap_pt(j, privatedata.errorsize_pt, *vminpos))
696 if maxcap:
697 privatedata.errorbarcanvas.stroke(graph.vcap_pt(j, privatedata.errorsize_pt, *vmaxpos))
699 def donedrawpoints(self, privatedata, sharedata, graph):
700 if privatedata.errorbarattrs is not None:
701 graph.insert(privatedata.errorbarcanvas)
704 class text(_styleneedingpointpos):
706 needsdata = ["vpos", "vposmissing", "vposvalid"]
708 defaulttextattrs = [textmodule.halign.center, textmodule.vshift.mathaxis]
710 def __init__(self, textname="text", textdx=0*unit.v_cm, textdy=0.3*unit.v_cm, textattrs=[]):
711 self.textname = textname
712 self.textdx = textdx
713 self.textdy = textdy
714 self.textattrs = textattrs
716 def columnnames(self, privatedata, sharedata, graph, columnnames):
717 if self.textname not in columnnames:
718 raise ValueError("column '%s' missing" % self.textname)
719 return [self.textname] + _styleneedingpointpos.columnnames(self, privatedata, sharedata, graph, columnnames)
721 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
722 if self.textattrs is not None:
723 privatedata.textattrs = attr.selectattrs(self.defaulttextattrs + self.textattrs, selectindex, selecttotal)
724 else:
725 privatedata.textattrs = None
727 def initdrawpoints(self, privatedata, sharedata, grap):
728 privatedata.textdx_pt = unit.topt(self.textdx)
729 privatedata.textdy_pt = unit.topt(self.textdy)
731 def drawpoint(self, privatedata, sharedata, graph, point):
732 if privatedata.textattrs is not None and sharedata.vposvalid:
733 x_pt, y_pt = graph.vpos_pt(*sharedata.vpos)
734 try:
735 text = str(point[self.textname])
736 except:
737 pass
738 else:
739 graph.text_pt(x_pt + privatedata.textdx_pt, y_pt + privatedata.textdy_pt, text, privatedata.textattrs)
741 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
742 raise RuntimeError("Style currently doesn't provide a graph key")
745 class arrow(_styleneedingpointpos):
747 needsdata = ["vpos", "vposmissing", "vposvalid"]
749 defaultlineattrs = []
750 defaultarrowattrs = []
752 def __init__(self, linelength=0.25*unit.v_cm, arrowsize=0.15*unit.v_cm, lineattrs=[], arrowattrs=[], epsilon=1e-5):
753 self.linelength = linelength
754 self.arrowsize = arrowsize
755 self.lineattrs = lineattrs
756 self.arrowattrs = arrowattrs
757 self.epsilon = epsilon
759 def columnnames(self, privatedata, sharedata, graph, columnnames):
760 if len(graph.axesnames) != 2:
761 raise ValueError("arrow style restricted on two-dimensional graphs")
762 if "size" not in columnnames:
763 raise ValueError("size missing")
764 if "angle" not in columnnames:
765 raise ValueError("angle missing")
766 return ["size", "angle"] + _styleneedingpointpos.columnnames(self, privatedata, sharedata, graph, columnnames)
768 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
769 if self.lineattrs is not None:
770 privatedata.lineattrs = attr.selectattrs(self.defaultlineattrs + self.lineattrs, selectindex, selecttotal)
771 else:
772 privatedata.lineattrs = None
773 if self.arrowattrs is not None:
774 privatedata.arrowattrs = attr.selectattrs(self.defaultarrowattrs + self.arrowattrs, selectindex, selecttotal)
775 else:
776 privatedata.arrowattrs = None
778 def initdrawpoints(self, privatedata, sharedata, graph):
779 privatedata.arrowcanvas = canvas.canvas()
781 def drawpoint(self, privatedata, sharedata, graph, point):
782 if privatedata.lineattrs is not None and privatedata.arrowattrs is not None and sharedata.vposvalid:
783 linelength_pt = unit.topt(self.linelength)
784 x_pt, y_pt = graph.vpos_pt(*sharedata.vpos)
785 try:
786 angle = point["angle"] + 0.0
787 size = point["size"] + 0.0
788 except:
789 pass
790 else:
791 if point["size"] > self.epsilon:
792 dx = math.cos(angle*math.pi/180)
793 dy = math.sin(angle*math.pi/180)
794 x1 = x_pt-0.5*dx*linelength_pt*size
795 y1 = y_pt-0.5*dy*linelength_pt*size
796 x2 = x_pt+0.5*dx*linelength_pt*size
797 y2 = y_pt+0.5*dy*linelength_pt*size
798 privatedata.arrowcanvas.stroke(path.line_pt(x1, y1, x2, y2), privatedata.lineattrs +
799 [deco.earrow(privatedata.arrowattrs, size=self.arrowsize*size)])
801 def donedrawpoints(self, privatedata, sharedata, graph):
802 graph.insert(privatedata.arrowcanvas)
804 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
805 raise RuntimeError("Style currently doesn't provide a graph key")
808 class rect(_style):
810 needsdata = ["vrange", "vrangeminmissing", "vrangemaxmissing"]
812 def __init__(self, palette=color.palette.Grey):
813 self.palette = palette
815 def columnnames(self, privatedata, sharedata, graph, columnnames):
816 if len(graph.axesnames) != 2:
817 raise TypeError("arrow style restricted on two-dimensional graphs")
818 if "color" not in columnnames:
819 raise ValueError("color missing")
820 if len(sharedata.vrangeminmissing) + len(sharedata.vrangemaxmissing):
821 raise ValueError("incomplete range")
822 return ["color"]
824 def initdrawpoints(self, privatedata, sharedata, graph):
825 privatedata.rectcanvas = graph.insert(canvas.canvas())
827 def drawpoint(self, privatedata, sharedata, graph, point):
828 xvmin = sharedata.vrange[0][0]
829 xvmax = sharedata.vrange[0][1]
830 yvmin = sharedata.vrange[1][0]
831 yvmax = sharedata.vrange[1][1]
832 if (xvmin is not None and xvmin < 1 and
833 xvmax is not None and xvmax > 0 and
834 yvmin is not None and yvmin < 1 and
835 yvmax is not None and yvmax > 0):
836 if xvmin < 0:
837 xvmin = 0
838 elif xvmax > 1:
839 xvmax = 1
840 if yvmin < 0:
841 yvmin = 0
842 elif yvmax > 1:
843 yvmax = 1
844 p = graph.vgeodesic(xvmin, yvmin, xvmax, yvmin)
845 p.append(graph.vgeodesic_el(xvmax, yvmin, xvmax, yvmax))
846 p.append(graph.vgeodesic_el(xvmax, yvmax, xvmin, yvmax))
847 p.append(graph.vgeodesic_el(xvmin, yvmax, xvmin, yvmin))
848 p.append(path.closepath())
849 privatedata.rectcanvas.fill(p, [self.palette.getcolor(point["color"])])
851 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
852 raise RuntimeError("Style currently doesn't provide a graph key")
855 class histogram(_style):
857 needsdata = ["vpos", "vposmissing", "vrange", "vrangeminmissing", "vrangemaxmissing"]
859 defaultlineattrs = [deco.stroked]
860 defaultfrompathattrs = []
862 def __init__(self, lineattrs=[], steps=0, fromvalue=0, frompathattrs=[], fillable=0,
863 autohistogramaxisindex=0, autohistogrampointpos=0.5, epsilon=1e-10):
864 self.lineattrs = lineattrs
865 self.steps = steps
866 self.fromvalue = fromvalue
867 self.frompathattrs = frompathattrs
868 self.fillable = fillable # TODO: fillable paths might not properly be closed by straight lines on curved graph geometries
869 self.autohistogramaxisindex = autohistogramaxisindex
870 self.autohistogrampointpos = autohistogrampointpos
871 self.epsilon = epsilon
873 def columnnames(self, privatedata, sharedata, graph, columnnames):
874 if len(graph.axesnames) != 2:
875 raise TypeError("histogram style restricted on two-dimensional graphs")
876 privatedata.rangeaxisindex = None
877 for i in builtinrange(len(graph.axesnames)):
878 if i in sharedata.vrangeminmissing or i in sharedata.vrangemaxmissing:
879 if i in sharedata.vposmissing:
880 raise ValueError("pos and range missing")
881 else:
882 if privatedata.rangeaxisindex is not None:
883 raise ValueError("multiple ranges")
884 privatedata.rangeaxisindex = i
885 if privatedata.rangeaxisindex is None:
886 privatedata.rangeaxisindex = self.autohistogramaxisindex
887 privatedata.autohistogram = 1
888 else:
889 privatedata.autohistogram = 0
890 return []
892 def adjustaxis(self, privatedata, sharedata, graph, columnname, data):
893 if privatedata.autohistogram and columnname == sharedata.poscolumnnames[privatedata.rangeaxisindex]:
894 if len(data) == 1:
895 raise ValueError("several data points needed for automatic histogram width calculation")
896 if data:
897 delta = data[1] - data[0]
898 min = data[0] - self.autohistogrampointpos * delta
899 max = data[-1] + (1-self.autohistogrampointpos) * delta
900 graph.axes[columnname].adjustaxis([min, max])
902 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
903 privatedata.insertfrompath = selectindex == 0
904 if self.lineattrs is not None:
905 privatedata.lineattrs = attr.selectattrs(self.defaultlineattrs + self.lineattrs, selectindex, selecttotal)
906 else:
907 privatedata.lineattrs = None
909 def vmoveto(self, privatedata, sharedata, graph, vpos, vvalue):
910 if -self.epsilon < vpos < 1+self.epsilon and -self.epsilon < vvalue < 1+self.epsilon:
911 if privatedata.rangeaxisindex:
912 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue, vpos)))
913 else:
914 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vpos, vvalue)))
916 def vposline(self, privatedata, sharedata, graph, vpos, vvalue1, vvalue2):
917 if -self.epsilon < vpos < 1+self.epsilon:
918 vvalue1cut = 0
919 if vvalue1 < 0:
920 vvalue1 = 0
921 vvalue1cut = -1
922 elif vvalue1 > 1:
923 vvalue1 = 1
924 vvalue1cut = 1
925 vvalue2cut = 0
926 if vvalue2 < 0:
927 vvalue2 = 0
928 vvalue2cut = -1
929 elif vvalue2 > 1:
930 vvalue2 = 1
931 vvalue2cut = 1
932 if abs(vvalue1cut + vvalue2cut) <= 1:
933 if vvalue1cut and not self.fillable:
934 if privatedata.rangeaxisindex:
935 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue1, vpos)))
936 else:
937 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vpos, vvalue1)))
938 if privatedata.rangeaxisindex:
939 privatedata.path.append(graph.vgeodesic_el(vvalue1, vpos, vvalue2, vpos))
940 else:
941 privatedata.path.append(graph.vgeodesic_el(vpos, vvalue1, vpos, vvalue2))
943 def vvalueline(self, privatedata, sharedata, graph, vvalue, vpos1, vpos2):
944 if self.fillable:
945 if vvalue < -self.epsilon:
946 vvalue = 0
947 warnings.warn("cut at graph boundary adds artificial lines to fillable step histogram path")
948 if vvalue > 1+self.epsilon:
949 vvalue = 1
950 warnings.warn("cut at graph boundary adds artificial lines to fillable step histogram path")
951 if self.fillable or (-self.epsilon < vvalue < 1+self.epsilon):
952 vpos1cut = 0
953 if vpos1 < 0:
954 vpos1 = 0
955 vpos1cut = -1
956 elif vpos1 > 1:
957 vpos1 = 1
958 vpos1cut = 1
959 vpos2cut = 0
960 if vpos2 < 0:
961 vpos2 = 0
962 vpos2cut = -1
963 elif vpos2 > 1:
964 vpos2 = 1
965 vpos2cut = 1
966 if abs(vpos1cut + vpos2cut) <= 1:
967 if vpos1cut:
968 if self.fillable:
969 if privatedata.rangeaxisindex:
970 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(privatedata.vfromvalue, vpos1)))
971 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vpos1, vvalue, vpos1))
972 else:
973 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vpos1, privatedata.vfromvalue)))
974 privatedata.path.append(graph.vgeodesic_el(vpos1, privatedata.vfromvalue, vpos1, vvalue))
975 else:
976 if privatedata.rangeaxisindex:
977 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue, vpos1)))
978 else:
979 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vpos1, vvalue)))
980 if privatedata.rangeaxisindex:
981 privatedata.path.append(graph.vgeodesic_el(vvalue, vpos1, vvalue, vpos2))
982 else:
983 privatedata.path.append(graph.vgeodesic_el(vpos1, vvalue, vpos2, vvalue))
984 if self.fillable and vpos2cut:
985 warnings.warn("cut at graph boundary adds artificial lines to fillable step histogram path")
986 if privatedata.rangeaxisindex:
987 privatedata.path.append(graph.vgeodesic_el(vvalue, vpos2, privatedata.vfromvalue, vpos2))
988 else:
989 privatedata.path.append(graph.vgeodesic_el(vpos2, vvalue, vpos2, privatedata.vfromvalue))
991 def drawvalue(self, privatedata, sharedata, graph, vmin, vmax, vvalue):
992 currentvalid = vmin is not None and vmax is not None and vvalue is not None
993 if self.fillable and not self.steps:
994 if not currentvalid:
995 return
996 vmincut = 0
997 if vmin < -self.epsilon:
998 vmin = 0
999 vmincut = -1
1000 elif vmin > 1+self.epsilon:
1001 vmin = 1
1002 vmincut = 1
1003 vmaxcut = 0
1004 if vmax < -self.epsilon:
1005 vmax = 0
1006 vmaxcut = -1
1007 if vmax > 1+self.epsilon:
1008 vmax = 1
1009 vmaxcut = 1
1010 vvaluecut = 0
1011 if vvalue < -self.epsilon:
1012 vvalue = 0
1013 vvaluecut = -1
1014 if vvalue > 1+self.epsilon:
1015 vvalue = 1
1016 vvaluecut = 1
1017 done = 0
1018 if abs(vmincut) + abs(vmaxcut) + abs(vvaluecut) + abs(privatedata.vfromvaluecut) > 1:
1019 if abs(vmincut + vmaxcut) > 1 or abs(vvaluecut+privatedata.vfromvaluecut) > 1:
1020 done = 1
1021 else:
1022 warnings.warn("multiple cuts at graph boundary add artificial lines to fillable rectangle histogram path")
1023 elif vmincut:
1024 done = 1
1025 if privatedata.rangeaxisindex:
1026 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(privatedata.vfromvalue, vmin)))
1027 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmin, privatedata.vfromvalue, vmax))
1028 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmax, vvalue, vmax))
1029 privatedata.path.append(graph.vgeodesic_el(vvalue, vmax, vvalue, vmin))
1030 else:
1031 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmin, privatedata.vfromvalue)))
1032 privatedata.path.append(graph.vgeodesic_el(vmin, privatedata.vfromvalue, vmax, privatedata.vfromvalue))
1033 privatedata.path.append(graph.vgeodesic_el(vmax, privatedata.vfromvalue, vmax, vvalue))
1034 privatedata.path.append(graph.vgeodesic_el(vmax, vvalue, vmin, vvalue))
1035 elif vmaxcut:
1036 done = 1
1037 if privatedata.rangeaxisindex:
1038 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue, vmax)))
1039 privatedata.path.append(graph.vgeodesic_el(vvalue, vmax, vvalue, vmin))
1040 privatedata.path.append(graph.vgeodesic_el(vvalue, vmin, privatedata.vfromvalue, vmin))
1041 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmin, privatedata.vfromvalue, vmax))
1042 else:
1043 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmax, vvalue)))
1044 privatedata.path.append(graph.vgeodesic_el(vmax, vvalue, vmin, vvalue))
1045 privatedata.path.append(graph.vgeodesic_el(vmin, vvalue, vmin, privatedata.vfromvalue))
1046 privatedata.path.append(graph.vgeodesic_el(vmin, privatedata.vfromvalue, vmax, privatedata.vfromvalue))
1047 elif privatedata.vfromvaluecut:
1048 done = 1
1049 if privatedata.rangeaxisindex:
1050 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(privatedata.vfromvalue, vmax)))
1051 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmax, vvalue, vmax))
1052 privatedata.path.append(graph.vgeodesic_el(vvalue, vmax, vvalue, vmin))
1053 privatedata.path.append(graph.vgeodesic_el(vvalue, vmin, privatedata.vfromvalue, vmin))
1054 else:
1055 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmax, privatedata.vfromvalue)))
1056 privatedata.path.append(graph.vgeodesic_el(vmax, privatedata.vfromvalue, vmax, vvalue))
1057 privatedata.path.append(graph.vgeodesic_el(vmax, vvalue, vmin, vvalue))
1058 privatedata.path.append(graph.vgeodesic_el(vmin, vvalue, vmin, privatedata.vfromvalue))
1059 elif vvaluecut:
1060 done = 1
1061 if privatedata.rangeaxisindex:
1062 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue, vmin)))
1063 privatedata.path.append(graph.vgeodesic_el(vvalue, vmin, privatedata.vfromvalue, vmin))
1064 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmin, privatedata.vfromvalue, vmax))
1065 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmax, vvalue, vmax))
1066 else:
1067 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmin, vvalue)))
1068 privatedata.path.append(graph.vgeodesic_el(vmin, vvalue, vmin, privatedata.vfromvalue))
1069 privatedata.path.append(graph.vgeodesic_el(vmin, privatedata.vfromvalue, vmax, privatedata.vfromvalue))
1070 privatedata.path.append(graph.vgeodesic_el(vmax, privatedata.vfromvalue, vmax, vvalue))
1071 if not done:
1072 if privatedata.rangeaxisindex:
1073 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(privatedata.vfromvalue, vmin)))
1074 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmin, privatedata.vfromvalue, vmax))
1075 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmax, vvalue, vmax))
1076 privatedata.path.append(graph.vgeodesic_el(vvalue, vmax, vvalue, vmin))
1077 privatedata.path.append(graph.vgeodesic_el(vvalue, vmin, privatedata.vfromvalue, vmin))
1078 privatedata.path.append(path.closepath())
1079 else:
1080 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmin, privatedata.vfromvalue)))
1081 privatedata.path.append(graph.vgeodesic_el(vmin, privatedata.vfromvalue, vmax, privatedata.vfromvalue))
1082 privatedata.path.append(graph.vgeodesic_el(vmax, privatedata.vfromvalue, vmax, vvalue))
1083 privatedata.path.append(graph.vgeodesic_el(vmax, vvalue, vmin, vvalue))
1084 privatedata.path.append(graph.vgeodesic_el(vmin, vvalue, vmin, privatedata.vfromvalue))
1085 privatedata.path.append(path.closepath())
1086 else:
1087 try:
1088 gap = abs(vmin - privatedata.lastvmax) > self.epsilon
1089 except (ArithmeticError, ValueError, TypeError):
1090 gap = 1
1091 if (privatedata.lastvvalue is not None and currentvalid and not gap and
1092 (self.steps or (privatedata.lastvvalue-privatedata.vfromvalue)*(vvalue-privatedata.vfromvalue) < 0)):
1093 self.vposline(privatedata, sharedata, graph,
1094 vmin, privatedata.lastvvalue, vvalue)
1095 else:
1096 if privatedata.lastvvalue is not None and currentvalid:
1097 currentbigger = abs(privatedata.lastvvalue-privatedata.vfromvalue) < abs(vvalue-privatedata.vfromvalue)
1098 if privatedata.lastvvalue is not None and (not currentvalid or not currentbigger or gap):
1099 self.vposline(privatedata, sharedata, graph,
1100 privatedata.lastvmax, privatedata.lastvvalue, privatedata.vfromvalue)
1101 if currentvalid:
1102 self.vmoveto(privatedata, sharedata, graph,
1103 vmin, vvalue)
1104 if currentvalid and (privatedata.lastvvalue is None or currentbigger or gap):
1105 self.vmoveto(privatedata, sharedata, graph,
1106 vmin, privatedata.vfromvalue)
1107 self.vposline(privatedata, sharedata, graph,
1108 vmin, privatedata.vfromvalue, vvalue)
1109 if currentvalid:
1110 self.vvalueline(privatedata, sharedata, graph,
1111 vvalue, vmin, vmax)
1112 privatedata.lastvvalue = vvalue
1113 privatedata.lastvmax = vmax
1114 else:
1115 privatedata.lastvvalue = privatedata.lastvmax = None
1117 def initdrawpoints(self, privatedata, sharedata, graph):
1118 privatedata.path = path.path()
1119 privatedata.lastvvalue = privatedata.lastvmax = None
1120 privatedata.vcurrentpoint = None
1121 privatedata.count = 0
1122 if self.fromvalue is not None:
1123 valueaxisname = sharedata.poscolumnnames[1-privatedata.rangeaxisindex]
1124 privatedata.vfromvalue = graph.axes[valueaxisname].convert(self.fromvalue)
1125 privatedata.vfromvaluecut = 0
1126 if privatedata.vfromvalue < 0:
1127 privatedata.vfromvalue = 0
1128 privatedata.vfromvaluecut = -1
1129 if privatedata.vfromvalue > 1:
1130 privatedata.vfromvalue = 1
1131 privatedata.vfromvaluecut = 1
1132 if self.frompathattrs is not None and privatedata.insertfrompath:
1133 graph.stroke(graph.axes[valueaxisname].vgridpath(privatedata.vfromvalue),
1134 self.defaultfrompathattrs + self.frompathattrs)
1135 else:
1136 privatedata.vfromvalue = 0
1138 def drawpoint(self, privatedata, sharedata, graph, point):
1139 if privatedata.autohistogram:
1140 # automatic range handling
1141 privatedata.count += 1
1142 if privatedata.count == 2:
1143 if privatedata.rangeaxisindex:
1144 privatedata.vrange = sharedata.vpos[1] - privatedata.lastvpos[1]
1145 self.drawvalue(privatedata, sharedata, graph,
1146 privatedata.lastvpos[1] - self.autohistogrampointpos*privatedata.vrange,
1147 privatedata.lastvpos[1] + (1-self.autohistogrampointpos)*privatedata.vrange,
1148 privatedata.lastvpos[0])
1149 else:
1150 privatedata.vrange = sharedata.vpos[0] - privatedata.lastvpos[0]
1151 self.drawvalue(privatedata, sharedata, graph,
1152 privatedata.lastvpos[0] - self.autohistogrampointpos*privatedata.vrange,
1153 privatedata.lastvpos[0] + (1-self.autohistogrampointpos)*privatedata.vrange,
1154 privatedata.lastvpos[1])
1155 elif privatedata.count > 2:
1156 if privatedata.rangeaxisindex:
1157 vrange = sharedata.vpos[1] - privatedata.lastvpos[1]
1158 else:
1159 vrange = sharedata.vpos[0] - privatedata.lastvpos[0]
1160 if abs(privatedata.vrange - vrange) > self.epsilon:
1161 raise ValueError("equal steps (in graph coordinates) needed for automatic width calculation")
1162 if privatedata.count > 1:
1163 if privatedata.rangeaxisindex:
1164 self.drawvalue(privatedata, sharedata, graph,
1165 sharedata.vpos[1] - self.autohistogrampointpos*privatedata.vrange,
1166 sharedata.vpos[1] + (1-self.autohistogrampointpos)*privatedata.vrange,
1167 sharedata.vpos[0])
1168 else:
1169 self.drawvalue(privatedata, sharedata, graph,
1170 sharedata.vpos[0] - self.autohistogrampointpos*privatedata.vrange,
1171 sharedata.vpos[0] + (1-self.autohistogrampointpos)*privatedata.vrange,
1172 sharedata.vpos[1])
1173 privatedata.lastvpos = sharedata.vpos[:]
1174 else:
1175 if privatedata.rangeaxisindex:
1176 self.drawvalue(privatedata, sharedata, graph,
1177 sharedata.vrange[1][0], sharedata.vrange[1][1], sharedata.vpos[0])
1178 else:
1179 self.drawvalue(privatedata, sharedata, graph,
1180 sharedata.vrange[0][0], sharedata.vrange[0][1], sharedata.vpos[1])
1182 def donedrawpoints(self, privatedata, sharedata, graph):
1183 self.drawvalue(privatedata, sharedata, graph, None, None, None)
1184 if privatedata.lineattrs is not None and len(privatedata.path):
1185 graph.draw(privatedata.path, privatedata.lineattrs)
1187 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
1188 if privatedata.lineattrs is not None:
1189 graph.stroke(path.line_pt(x_pt, y_pt+0.5*height_pt, x_pt+width_pt, y_pt+0.5*height_pt), privatedata.lineattrs)
1192 class barpos(_style):
1194 providesdata = ["vpos", "vposmissing", "vposavailable", "vposvalid", "vbarrange", "barposcolumnnames", "barvalueindex", "lastbarvalue", "stackedbar", "stackedbardraw"]
1196 defaultfrompathattrs = []
1198 def __init__(self, fromvalue=None, frompathattrs=[], epsilon=1e-10):
1199 self.fromvalue = fromvalue
1200 self.frompathattrs = frompathattrs
1201 self.epsilon = epsilon
1203 def columnnames(self, privatedata, sharedata, graph, columnnames):
1204 sharedata.barposcolumnnames = []
1205 sharedata.barvalueindex = None
1206 for dimension, axisnames in enumerate(graph.axesnames):
1207 found = 0
1208 for axisname in axisnames:
1209 if axisname in columnnames:
1210 if sharedata.barvalueindex is not None:
1211 raise ValueError("multiple values")
1212 sharedata.barvalueindex = dimension
1213 sharedata.barposcolumnnames.append(axisname)
1214 found += 1
1215 if (axisname + "name") in columnnames:
1216 sharedata.barposcolumnnames.append(axisname + "name")
1217 found += 1
1218 if found > 1:
1219 raise ValueError("multiple names and value")
1220 if not found:
1221 raise ValueError("value/name missing")
1222 if sharedata.barvalueindex is None:
1223 raise ValueError("missing value")
1224 sharedata.vposmissing = []
1225 return sharedata.barposcolumnnames
1227 def addsubvalue(self, value, subvalue):
1228 try:
1229 value + ""
1230 except:
1231 try:
1232 return value[0], self.addsubvalue(value[1], subvalue)
1233 except:
1234 return value, subvalue
1235 else:
1236 return value, subvalue
1238 def adjustaxis(self, privatedata, sharedata, graph, columnname, data):
1239 try:
1240 i = sharedata.barposcolumnnames.index(columnname)
1241 except ValueError:
1242 pass
1243 else:
1244 if i == sharedata.barvalueindex:
1245 if self.fromvalue is not None:
1246 graph.axes[sharedata.barposcolumnnames[i]].adjustaxis([self.fromvalue])
1247 graph.axes[sharedata.barposcolumnnames[i]].adjustaxis(data)
1248 else:
1249 graph.axes[sharedata.barposcolumnnames[i][:-4]].adjustaxis([self.addsubvalue(x, 0) for x in data])
1250 graph.axes[sharedata.barposcolumnnames[i][:-4]].adjustaxis([self.addsubvalue(x, 1) for x in data])
1252 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1253 privatedata.insertfrompath = selectindex == 0
1255 def initdrawpoints(self, privatedata, sharedata, graph):
1256 sharedata.vpos = [None]*(len(sharedata.barposcolumnnames))
1257 sharedata.vbarrange = [[None for i in xrange(2)] for x in sharedata.barposcolumnnames]
1258 sharedata.stackedbar = sharedata.stackedbardraw = 0
1260 if self.fromvalue is not None:
1261 privatedata.vfromvalue = graph.axes[sharedata.barposcolumnnames[sharedata.barvalueindex][0]].convert(self.fromvalue)
1262 if privatedata.vfromvalue < 0:
1263 privatedata.vfromvalue = 0
1264 if privatedata.vfromvalue > 1:
1265 privatedata.vfromvalue = 1
1266 if self.frompathattrs is not None and privatedata.insertfrompath:
1267 graph.stroke(graph.axes[sharedata.barposcolumnnames[sharedata.barvalueindex][0]].vgridpath(privatedata.vfromvalue),
1268 self.defaultfrompathattrs + self.frompathattrs)
1269 else:
1270 privatedata.vfromvalue = 0
1272 def drawpoint(self, privatedata, sharedata, graph, point):
1273 sharedata.vposavailable = sharedata.vposvalid = 1
1274 for i, barname in enumerate(sharedata.barposcolumnnames):
1275 if i == sharedata.barvalueindex:
1276 sharedata.vbarrange[i][0] = privatedata.vfromvalue
1277 sharedata.lastbarvalue = point[barname]
1278 try:
1279 sharedata.vpos[i] = sharedata.vbarrange[i][1] = graph.axes[barname].convert(sharedata.lastbarvalue)
1280 except (ArithmeticError, ValueError, TypeError):
1281 sharedata.vpos[i] = sharedata.vbarrange[i][1] = None
1282 else:
1283 for j in xrange(2):
1284 try:
1285 sharedata.vbarrange[i][j] = graph.axes[barname[:-4]].convert(self.addsubvalue(point[barname], j))
1286 except (ArithmeticError, ValueError, TypeError):
1287 sharedata.vbarrange[i][j] = None
1288 try:
1289 sharedata.vpos[i] = 0.5*(sharedata.vbarrange[i][0]+sharedata.vbarrange[i][1])
1290 except (ArithmeticError, ValueError, TypeError):
1291 sharedata.vpos[i] = None
1292 if sharedata.vpos[i] is None:
1293 sharedata.vposavailable = sharedata.vposvalid = 0
1294 elif sharedata.vpos[i] < -self.epsilon or sharedata.vpos[i] > 1+self.epsilon:
1295 sharedata.vposvalid = 0
1297 registerdefaultprovider(barpos(), ["vbarrange", "barposcolumnnames", "barvalueindex", "lastbarvalue", "stackedbar", "stackedbardraw"])
1300 class stackedbarpos(_style):
1302 # provides no additional data, but needs some data (and modifies some of them)
1303 needsdata = ["vbarrange", "barposcolumnnames", "barvalueindex", "lastbarvalue", "stackedbar", "stackedbardraw"]
1305 def __init__(self, stackname, addontop=0, epsilon=1e-10):
1306 self.stackname = stackname
1307 self.epsilon = epsilon
1308 self.addontop = addontop
1310 def columnnames(self, privatedata, sharedata, graph, columnnames):
1311 if self.stackname not in columnnames:
1312 raise ValueError("column '%s' missing" % self.stackname)
1313 return [self.stackname]
1315 def adjustaxis(self, privatedata, sharedata, graph, columnname, data):
1316 if columnname == self.stackname:
1317 graph.axes[sharedata.barposcolumnnames[sharedata.barvalueindex]].adjustaxis(data)
1319 def initdrawpoints(self, privatedata, sharedata, graph):
1320 if sharedata.stackedbardraw: # do not count the start bar when not gets painted
1321 sharedata.stackedbar += 1
1323 def drawpoint(self, privatedata, sharedata, graph, point):
1324 sharedata.vbarrange[sharedata.barvalueindex][0] = sharedata.vbarrange[sharedata.barvalueindex][1]
1325 if self.addontop:
1326 try:
1327 sharedata.lastbarvalue += point[self.stackname]
1328 except (ArithmeticError, ValueError, TypeError):
1329 sharedata.lastbarvalue = None
1330 else:
1331 sharedata.lastbarvalue = point[self.stackname]
1332 try:
1333 sharedata.vpos[sharedata.barvalueindex] = sharedata.vbarrange[sharedata.barvalueindex][1] = graph.axes[sharedata.barposcolumnnames[sharedata.barvalueindex]].convert(sharedata.lastbarvalue)
1334 except (ArithmeticError, ValueError, TypeError):
1335 sharedata.vpos[sharedata.barvalueindex] = sharedata.vbarrange[sharedata.barvalueindex][1] = None
1336 sharedata.vposavailable = sharedata.vposvalid = 0
1337 else:
1338 if not sharedata.vposavailable or not sharedata.vposvalid:
1339 sharedata.vposavailable = sharedata.vposvalid = 1
1340 for v in sharedata.vpos:
1341 if v is None:
1342 sharedata.vposavailable = sharedata.vposvalid = 0
1343 break
1344 if v < -self.epsilon or v > 1+self.epsilon:
1345 sharedata.vposvalid = 0
1348 class bar(_style):
1350 needsdata = ["vbarrange"]
1352 defaultbarattrs = [color.palette.Rainbow, deco.stroked([color.grey.black])]
1354 def __init__(self, barattrs=[]):
1355 self.barattrs = barattrs
1357 def columnnames(self, privatedata, sharedata, graph, columnnames):
1358 if len(graph.axesnames) != 2:
1359 raise TypeError("bar style restricted on two-dimensional graphs")
1360 return []
1362 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1363 privatedata.barattrs = attr.selectattrs(self.defaultbarattrs + self.barattrs, selectindex, selecttotal)
1365 def initdrawpoints(self, privatedata, sharedata, graph):
1366 privatedata.rectcanvas = graph.insert(canvas.canvas())
1367 sharedata.stackedbardraw = 1
1368 privatedata.stackedbar = sharedata.stackedbar
1370 def drawpointfill(self, privatedata, p):
1371 if p:
1372 privatedata.rectcanvas.fill(p, privatedata.barattrs)
1374 def drawpoint(self, privatedata, sharedata, graph, point):
1375 xvmin = sharedata.vbarrange[0][0]
1376 xvmax = sharedata.vbarrange[0][1]
1377 yvmin = sharedata.vbarrange[1][0]
1378 yvmax = sharedata.vbarrange[1][1]
1379 try:
1380 if xvmin > xvmax:
1381 xvmin, xvmax = xvmax, xvmin
1382 except:
1383 pass
1384 try:
1385 if yvmin > yvmax:
1386 yvmin, yvmax = yvmax, yvmin
1387 except:
1388 pass
1389 if (xvmin is not None and xvmin < 1 and
1390 xvmax is not None and xvmax > 0 and
1391 yvmin is not None and yvmin < 1 and
1392 yvmax is not None and yvmax > 0):
1393 if xvmin < 0:
1394 xvmin = 0
1395 elif xvmax > 1:
1396 xvmax = 1
1397 if yvmin < 0:
1398 yvmin = 0
1399 elif yvmax > 1:
1400 yvmax = 1
1401 p = graph.vgeodesic(xvmin, yvmin, xvmax, yvmin)
1402 p.append(graph.vgeodesic_el(xvmax, yvmin, xvmax, yvmax))
1403 p.append(graph.vgeodesic_el(xvmax, yvmax, xvmin, yvmax))
1404 p.append(graph.vgeodesic_el(xvmin, yvmax, xvmin, yvmin))
1405 p.append(path.closepath())
1406 self.drawpointfill(privatedata, p)
1407 else:
1408 self.drawpointfill(privatedata, None)
1410 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
1411 selectindex = privatedata.stackedbar
1412 selecttotal = sharedata.stackedbar + 1
1413 graph.fill(path.rect_pt(x_pt + width_pt*selectindex/float(selecttotal), y_pt, width_pt/float(selecttotal), height_pt), privatedata.barattrs)
1416 class changebar(bar):
1418 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1419 if selecttotal != 1:
1420 raise RuntimeError("Changebar can't change its appearance. Thus you can't use it to plot several bars side by side on a subaxis.")
1422 def initdrawpoints(self, privatedata, sharedata, graph):
1423 bar.initdrawpoints(self, privatedata, sharedata, graph)
1424 privatedata.bars = []
1426 def drawpointfill(self, privatedata, p):
1427 privatedata.bars.append(p)
1429 def donedrawpoints(self, privatedata, sharedata, graph):
1430 selecttotal = len(privatedata.bars)
1431 for selectindex, p in enumerate(privatedata.bars):
1432 if p:
1433 barattrs = attr.selectattrs(self.defaultbarattrs + self.barattrs, selectindex, selecttotal)
1434 privatedata.rectcanvas.fill(p, barattrs)
1436 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
1437 raise RuntimeError("Style currently doesn't provide a graph key")