there is no name at the value axes, but taking the first char only possibly strips...
[PyX.git] / pyx / graph / style.py
blob55a2f0e98941e2d2c9922fd853c6e6a89c38ba6c
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-2006 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 from __future__ import nested_scopes
27 import math, warnings
28 from pyx import attr, deco, style, color, unit, canvas, path, mesh
29 from pyx import text as textmodule
31 builtinrange = range
33 try:
34 sum([])
35 except NameError:
36 # fallback implementation for Python 2.2 and below
37 def sum(list):
38 return reduce(lambda x, y: x+y, list, 0)
40 try:
41 enumerate([])
42 except NameError:
43 # fallback implementation for Python 2.2. and below
44 def enumerate(list):
45 return zip(xrange(len(list)), list)
47 class _style:
48 """Interface class for graph styles
50 Each graph style must support the methods described in this
51 class. However, since a graph style might not need to perform
52 actions on all the various events, it does not need to overwrite
53 all methods of this base class (e.g. this class is not an abstract
54 class in any respect).
56 A style should never store private data by istance variables
57 (i.e. accessing self), but it should use the sharedata and privatedata
58 instances instead. A style instance can be used multiple times with
59 different sharedata and privatedata instances at the very same time.
60 The sharedata and privatedata instances act as data containers and
61 sharedata allows for sharing information across several styles.
63 Every style contains two class variables, which are not to be
64 modified:
65 - providesdata is a list of variable names a style offers via
66 the sharedata instance. This list is used to determine whether
67 all needs of subsequent styles are fullfilled. Otherwise
68 getdefaultprovider should return a proper style to be used.
69 - needsdata is a list of variable names the style needs to access in the
70 sharedata instance.
71 """
73 providesdata = [] # by default, we provide nothing
74 needsdata = [] # and do not depend on anything
76 def columnnames(self, privatedata, sharedata, graph, columnnames):
77 """Set column information
79 This method is used setup the column name information to be
80 accessible to the style later on. The style should analyse
81 the list of column names. The method should return a list of
82 column names which the style will make use of."""
83 return []
85 def adjustaxis(self, privatedata, sharedata, graph, columnname, data):
86 """Adjust axis range
88 This method is called in order to adjust the axis range to
89 the provided data. columnname is the column name (each style
90 is subsequently called for all column names)."""
91 pass
93 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
94 """Select stroke/fill attributes
96 This method is called to allow for the selection of
97 changable attributes of a style."""
98 pass
100 def initdrawpoints(self, privatedata, sharedata, graph):
101 """Initialize drawing of data
103 This method might be used to initialize the drawing of data."""
104 pass
106 def drawpoint(self, privatedata, sharedata, graph, point):
107 """Draw data
109 This method is called for each data point. The data is
110 available in the dictionary point. The dictionary
111 keys are the column names."""
112 pass
114 def donedrawpoints(self, privatedata, sharedata, graph):
115 """Finalize drawing of data
117 This method is called after the last data point was
118 drawn using the drawpoint method above."""
119 pass
121 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
122 """Draw graph key"""
125 # The following two methods are used to register and get a default provider
126 # for keys. A key is a variable name in sharedata. A provider is a style
127 # which creates variables in sharedata.
129 _defaultprovider = {}
131 def registerdefaultprovider(style, keys):
132 """sets a style as a default creator for sharedata variables 'keys'"""
133 for key in keys:
134 assert key in style.providesdata, "key not provided by style"
135 # we might allow for overwriting the defaults, i.e. the following is not checked:
136 # assert key in _defaultprovider.keys(), "default provider already registered for key"
137 _defaultprovider[key] = style
139 def getdefaultprovider(key):
140 """returns a style, which acts as a default creator for the
141 sharedata variable 'key'"""
142 return _defaultprovider[key]
145 class pos(_style):
147 providesdata = ["vpos", "vposmissing", "vposavailable", "vposvalid", "poscolumnnames"]
149 def __init__(self, epsilon=1e-10):
150 self.epsilon = epsilon
152 def columnnames(self, privatedata, sharedata, graph, columnnames):
153 sharedata.poscolumnnames = []
154 sharedata.vposmissing = []
155 for count, axisnames in enumerate(graph.axesnames):
156 for axisname in axisnames:
157 for columnname in columnnames:
158 if axisname == columnname:
159 sharedata.poscolumnnames.append(columnname)
160 if len(sharedata.poscolumnnames) > count+1:
161 raise ValueError("multiple axes per graph dimension")
162 elif len(sharedata.poscolumnnames) < count+1:
163 sharedata.vposmissing.append(count)
164 sharedata.poscolumnnames.append(None)
165 return [columnname for columnname in sharedata.poscolumnnames if columnname is not None]
167 def adjustaxis(self, privatedata, sharedata, graph, columnname, data):
168 if columnname in sharedata.poscolumnnames:
169 graph.axes[columnname].adjustaxis(data)
171 def initdrawpoints(self, privatedata, sharedata, graph):
172 sharedata.vpos = [None]*(len(graph.axesnames))
173 privatedata.pointpostmplist = [[columnname, index, graph.axes[columnname]] # temporarily used by drawpoint only
174 for index, columnname in enumerate([columnname for columnname in sharedata.poscolumnnames if columnname is not None])]
175 for missing in sharedata.vposmissing:
176 for pointpostmp in privatedata.pointpostmplist:
177 if pointpostmp[1] >= missing:
178 pointpostmp[1] += 1
180 def drawpoint(self, privatedata, sharedata, graph, point):
181 sharedata.vposavailable = 1 # valid position (but might be outside of the graph)
182 sharedata.vposvalid = 1 # valid position inside the graph
183 for columnname, index, axis in privatedata.pointpostmplist:
184 try:
185 v = axis.convert(point[columnname])
186 except (ArithmeticError, ValueError, TypeError):
187 sharedata.vposavailable = sharedata.vposvalid = 0
188 sharedata.vpos[index] = None
189 else:
190 if v < -self.epsilon or v > 1+self.epsilon:
191 sharedata.vposvalid = 0
192 sharedata.vpos[index] = v
195 registerdefaultprovider(pos(), pos.providesdata)
198 class range(_style):
200 providesdata = ["vrange", "vrangemissing", "vrangeminmissing", "vrangemaxmissing"]
202 # internal bit masks
203 mask_value = 1
204 mask_min = 2
205 mask_max = 4
206 mask_dmin = 8
207 mask_dmax = 16
208 mask_d = 32
210 def __init__(self, usenames={}, epsilon=1e-10):
211 self.usenames = usenames
212 self.epsilon = epsilon
214 def _numberofbits(self, mask):
215 if not mask:
216 return 0
217 if mask & 1:
218 return self._numberofbits(mask >> 1) + 1
219 else:
220 return self._numberofbits(mask >> 1)
222 def columnnames(self, privatedata, sharedata, graph, columnnames):
223 usecolumns = []
224 privatedata.rangeposcolumns = []
225 sharedata.vrangemissing = []
226 sharedata.vrangeminmissing = []
227 sharedata.vrangemaxmissing = []
228 privatedata.rangeposdeltacolumns = {} # temporarily used by adjustaxis only
229 for count, axisnames in enumerate(graph.axesnames):
230 for axisname in axisnames:
231 try:
232 usename = self.usenames[axisname]
233 except KeyError:
234 usename = axisname
235 mask = 0
236 for columnname in columnnames:
237 addusecolumns = 1
238 if usename == columnname:
239 mask += self.mask_value
240 elif usename + "min" == columnname:
241 mask += self.mask_min
242 elif usename + "max" == columnname:
243 mask += self.mask_max
244 elif "d" + usename + "min" == columnname:
245 mask += self.mask_dmin
246 elif "d" + usename + "max" == columnname:
247 mask += self.mask_dmax
248 elif "d" + usename == columnname:
249 mask += self.mask_d
250 else:
251 addusecolumns = 0
252 if addusecolumns:
253 usecolumns.append(columnname)
254 if mask & (self.mask_min | self.mask_max | self.mask_dmin | self.mask_dmax | self.mask_d):
255 if (self._numberofbits(mask & (self.mask_min | self.mask_dmin | self.mask_d)) > 1 or
256 self._numberofbits(mask & (self.mask_max | self.mask_dmax | self.mask_d)) > 1):
257 raise ValueError("multiple range definition")
258 if mask & (self.mask_dmin | self.mask_dmax | self.mask_d):
259 if not (mask & self.mask_value):
260 raise ValueError("missing value for delta")
261 privatedata.rangeposdeltacolumns[axisname] = {}
262 privatedata.rangeposcolumns.append((axisname, usename, mask))
263 elif mask == self.mask_value:
264 usecolumns = usecolumns[:-1]
265 if len(privatedata.rangeposcolumns) + len(sharedata.vrangemissing) > count+1:
266 raise ValueError("multiple axes per graph dimension")
267 elif len(privatedata.rangeposcolumns) + len(sharedata.vrangemissing) < count+1:
268 sharedata.vrangemissing.append(count)
269 sharedata.vrangeminmissing.append(count)
270 sharedata.vrangemaxmissing.append(count)
271 else:
272 if not (privatedata.rangeposcolumns[-1][2] & (self.mask_min | self.mask_dmin | self.mask_d)):
273 sharedata.vrangeminmissing.append(count)
274 if not (privatedata.rangeposcolumns[-1][2] & (self.mask_max | self.mask_dmax | self.mask_d)):
275 sharedata.vrangemaxmissing.append(count)
276 return usecolumns
278 def adjustaxis(self, privatedata, sharedata, graph, columnname, data):
279 if columnname in [c + "min" for a, c, m in privatedata.rangeposcolumns if m & self.mask_min]:
280 graph.axes[columnname[:-3]].adjustaxis(data)
281 if columnname in [c + "max" for a, c, m in privatedata.rangeposcolumns if m & self.mask_max]:
282 graph.axes[columnname[:-3]].adjustaxis(data)
284 # delta handling: fill rangeposdeltacolumns
285 for axisname, usename, mask in privatedata.rangeposcolumns:
286 if columnname == usename and mask & (self.mask_dmin | self.mask_dmax | self.mask_d):
287 privatedata.rangeposdeltacolumns[axisname][self.mask_value] = data
288 if columnname == "d" + usename + "min" and mask & self.mask_dmin:
289 privatedata.rangeposdeltacolumns[axisname][self.mask_dmin] = data
290 if columnname == "d" + usename + "max" and mask & self.mask_dmax:
291 privatedata.rangeposdeltacolumns[axisname][self.mask_dmax] = data
292 if columnname == "d" + usename and mask & self.mask_d:
293 privatedata.rangeposdeltacolumns[axisname][self.mask_d] = data
295 # delta handling: process rangeposdeltacolumns
296 for a, d in privatedata.rangeposdeltacolumns.items():
297 if d.has_key(self.mask_value):
298 for k in d.keys():
299 if k != self.mask_value:
300 if k & (self.mask_dmin | self.mask_d):
301 mindata = []
302 for value, delta in zip(d[self.mask_value], d[k]):
303 try:
304 mindata.append(value-delta)
305 except:
306 pass
307 graph.axes[a].adjustaxis(mindata)
308 if k & (self.mask_dmax | self.mask_d):
309 maxdata = []
310 for value, delta in zip(d[self.mask_value], d[k]):
311 try:
312 maxdata.append(value+delta)
313 except:
314 pass
315 graph.axes[a].adjustaxis(maxdata)
316 del d[k]
318 def initdrawpoints(self, privatedata, sharedata, graph):
319 sharedata.vrange = [[None for x in xrange(2)] for y in privatedata.rangeposcolumns + sharedata.vrangemissing]
320 privatedata.rangepostmplist = [[usename, mask, index, graph.axes[axisname]] # temporarily used by drawpoint only
321 for index, (axisname, usename, mask) in enumerate(privatedata.rangeposcolumns)]
322 for missing in sharedata.vrangemissing:
323 for rangepostmp in privatedata.rangepostmplist:
324 if rangepostmp[2] >= missing:
325 rangepostmp[2] += 1
327 def drawpoint(self, privatedata, sharedata, graph, point):
328 for usename, mask, index, axis in privatedata.rangepostmplist:
329 try:
330 if mask & self.mask_min:
331 sharedata.vrange[index][0] = axis.convert(point[usename + "min"])
332 if mask & self.mask_dmin:
333 sharedata.vrange[index][0] = axis.convert(point[usename] - point["d" + usename + "min"])
334 if mask & self.mask_d:
335 sharedata.vrange[index][0] = axis.convert(point[usename] - point["d" + usename])
336 except (ArithmeticError, ValueError, TypeError):
337 sharedata.vrange[index][0] = None
338 try:
339 if mask & self.mask_max:
340 sharedata.vrange[index][1] = axis.convert(point[usename + "max"])
341 if mask & self.mask_dmax:
342 sharedata.vrange[index][1] = axis.convert(point[usename] + point["d" + usename + "max"])
343 if mask & self.mask_d:
344 sharedata.vrange[index][1] = axis.convert(point[usename] + point["d" + usename])
345 except (ArithmeticError, ValueError, TypeError):
346 sharedata.vrange[index][1] = None
348 # some range checks for data consistency
349 if (sharedata.vrange[index][0] is not None and sharedata.vrange[index][1] is not None and
350 sharedata.vrange[index][0] > sharedata.vrange[index][1] + self.epsilon):
351 raise ValueError("inverse range")
352 # disabled due to missing vpos access:
353 # if (sharedata.vrange[index][0] is not None and sharedata.vpos[index] is not None and
354 # sharedata.vrange[index][0] > sharedata.vpos[index] + self.epsilon):
355 # raise ValueError("negative minimum errorbar")
356 # if (sharedata.vrange[index][1] is not None and sharedata.vpos[index] is not None and
357 # sharedata.vrange[index][1] < sharedata.vpos[index] - self.epsilon):
358 # raise ValueError("negative maximum errorbar")
361 registerdefaultprovider(range(), range.providesdata)
364 def _crosssymbol(c, x_pt, y_pt, size_pt, attrs):
365 c.draw(path.path(path.moveto_pt(x_pt-0.5*size_pt, y_pt-0.5*size_pt),
366 path.lineto_pt(x_pt+0.5*size_pt, y_pt+0.5*size_pt),
367 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)), attrs)
370 def _plussymbol(c, x_pt, y_pt, size_pt, attrs):
371 c.draw(path.path(path.moveto_pt(x_pt-0.707106781*size_pt, y_pt),
372 path.lineto_pt(x_pt+0.707106781*size_pt, y_pt),
373 path.moveto_pt(x_pt, y_pt-0.707106781*size_pt),
374 path.lineto_pt(x_pt, y_pt+0.707106781*size_pt)), attrs)
376 def _squaresymbol(c, x_pt, y_pt, size_pt, attrs):
377 c.draw(path.path(path.moveto_pt(x_pt-0.5*size_pt, y_pt-0.5*size_pt),
378 path.lineto_pt(x_pt+0.5*size_pt, y_pt-0.5*size_pt),
379 path.lineto_pt(x_pt+0.5*size_pt, y_pt+0.5*size_pt),
380 path.lineto_pt(x_pt-0.5*size_pt, y_pt+0.5*size_pt),
381 path.closepath()), attrs)
383 def _trianglesymbol(c, x_pt, y_pt, size_pt, attrs):
384 c.draw(path.path(path.moveto_pt(x_pt-0.759835685*size_pt, y_pt-0.438691337*size_pt),
385 path.lineto_pt(x_pt+0.759835685*size_pt, y_pt-0.438691337*size_pt),
386 path.lineto_pt(x_pt, y_pt+0.877382675*size_pt),
387 path.closepath()), attrs)
389 def _circlesymbol(c, x_pt, y_pt, size_pt, attrs):
390 c.draw(path.path(path.arc_pt(x_pt, y_pt, 0.564189583*size_pt, 0, 360),
391 path.closepath()), attrs)
393 def _diamondsymbol(c, x_pt, y_pt, size_pt, attrs):
394 c.draw(path.path(path.moveto_pt(x_pt-0.537284965*size_pt, y_pt),
395 path.lineto_pt(x_pt, y_pt-0.930604859*size_pt),
396 path.lineto_pt(x_pt+0.537284965*size_pt, y_pt),
397 path.lineto_pt(x_pt, y_pt+0.930604859*size_pt),
398 path.closepath()), attrs)
401 class _styleneedingpointpos(_style):
403 needsdata = ["vposmissing"]
405 def columnnames(self, privatedata, sharedata, graph, columnnames):
406 if len(sharedata.vposmissing):
407 raise ValueError("incomplete position information")
408 return []
411 class symbol(_styleneedingpointpos):
413 needsdata = ["vpos", "vposmissing", "vposvalid"]
415 # "inject" the predefinied symbols into the class:
417 # Note, that statements like cross = _crosssymbol are
418 # invalid, since the would lead to unbound methods, but
419 # a single entry changeable list does the trick.
421 # Once we require Python 2.2+ we should use staticmethods
422 # to implement the default symbols inplace.
424 cross = attr.changelist([_crosssymbol])
425 plus = attr.changelist([_plussymbol])
426 square = attr.changelist([_squaresymbol])
427 triangle = attr.changelist([_trianglesymbol])
428 circle = attr.changelist([_circlesymbol])
429 diamond = attr.changelist([_diamondsymbol])
431 changecross = attr.changelist([_crosssymbol, _plussymbol, _squaresymbol, _trianglesymbol, _circlesymbol, _diamondsymbol])
432 changeplus = attr.changelist([_plussymbol, _squaresymbol, _trianglesymbol, _circlesymbol, _diamondsymbol, _crosssymbol])
433 changesquare = attr.changelist([_squaresymbol, _trianglesymbol, _circlesymbol, _diamondsymbol, _crosssymbol, _plussymbol])
434 changetriangle = attr.changelist([_trianglesymbol, _circlesymbol, _diamondsymbol, _crosssymbol, _plussymbol, _squaresymbol])
435 changecircle = attr.changelist([_circlesymbol, _diamondsymbol, _crosssymbol, _plussymbol, _squaresymbol, _trianglesymbol])
436 changediamond = attr.changelist([_diamondsymbol, _crosssymbol, _plussymbol, _squaresymbol, _trianglesymbol, _circlesymbol])
437 changesquaretwice = attr.changelist([_squaresymbol, _squaresymbol, _trianglesymbol, _trianglesymbol, _circlesymbol, _circlesymbol, _diamondsymbol, _diamondsymbol])
438 changetriangletwice = attr.changelist([_trianglesymbol, _trianglesymbol, _circlesymbol, _circlesymbol, _diamondsymbol, _diamondsymbol, _squaresymbol, _squaresymbol])
439 changecircletwice = attr.changelist([_circlesymbol, _circlesymbol, _diamondsymbol, _diamondsymbol, _squaresymbol, _squaresymbol, _trianglesymbol, _trianglesymbol])
440 changediamondtwice = attr.changelist([_diamondsymbol, _diamondsymbol, _squaresymbol, _squaresymbol, _trianglesymbol, _trianglesymbol, _circlesymbol, _circlesymbol])
442 changestrokedfilled = attr.changelist([deco.stroked, deco.filled])
443 changefilledstroked = attr.changelist([deco.filled, deco.stroked])
445 defaultsymbolattrs = [deco.stroked]
447 def __init__(self, symbol=changecross, size=0.2*unit.v_cm, symbolattrs=[]):
448 self.symbol = symbol
449 self.size = size
450 self.symbolattrs = symbolattrs
452 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
453 privatedata.symbol = attr.selectattr(self.symbol, selectindex, selecttotal)
454 privatedata.size_pt = unit.topt(attr.selectattr(self.size, selectindex, selecttotal))
455 if self.symbolattrs is not None:
456 privatedata.symbolattrs = attr.selectattrs(self.defaultsymbolattrs + self.symbolattrs, selectindex, selecttotal)
457 else:
458 privatedata.symbolattrs = None
460 def initdrawpoints(self, privatedata, sharedata, graph):
461 privatedata.symbolcanvas = canvas.canvas()
463 def drawpoint(self, privatedata, sharedata, graph, point):
464 if sharedata.vposvalid and privatedata.symbolattrs is not None:
465 x_pt, y_pt = graph.vpos_pt(*sharedata.vpos)
466 privatedata.symbol(privatedata.symbolcanvas, x_pt, y_pt, privatedata.size_pt, privatedata.symbolattrs)
468 def donedrawpoints(self, privatedata, sharedata, graph):
469 graph.insert(privatedata.symbolcanvas)
471 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
472 if privatedata.symbolattrs is not None:
473 privatedata.symbol(graph, x_pt+0.5*width_pt, y_pt+0.5*height_pt, privatedata.size_pt, privatedata.symbolattrs)
476 class _line(_styleneedingpointpos):
478 # this style is not a complete style, but it provides the basic functionality to
479 # create a line, which is cut at the graph boundaries (or at otherwise invalid points)
481 def initpointstopath(self, privatedata):
482 privatedata.path = path.path()
483 privatedata.linebasepoints = []
484 privatedata.lastvpos = None
486 def addpointstopath(self, privatedata):
487 # add baselinepoints to privatedata.path
488 if len(privatedata.linebasepoints) > 1:
489 privatedata.path.append(path.moveto_pt(*privatedata.linebasepoints[0]))
490 if len(privatedata.linebasepoints) > 2:
491 privatedata.path.append(path.multilineto_pt(privatedata.linebasepoints[1:]))
492 else:
493 privatedata.path.append(path.lineto_pt(*privatedata.linebasepoints[1]))
494 privatedata.linebasepoints = []
496 def addpoint(self, privatedata, graphvpos_pt, vposavailable, vposvalid, vpos):
497 # append linebasepoints
498 if vposavailable:
499 if len(privatedata.linebasepoints):
500 # the last point was inside the graph
501 if vposvalid: # shortcut for the common case
502 privatedata.linebasepoints.append(graphvpos_pt(*vpos))
503 else:
504 # cut end
505 cut = 1
506 for vstart, vend in zip(privatedata.lastvpos, vpos):
507 newcut = None
508 if vend > 1:
509 # 1 = vstart + (vend - vstart) * cut
510 try:
511 newcut = (1 - vstart)/(vend - vstart)
512 except (ArithmeticError, TypeError):
513 break
514 if vend < 0:
515 # 0 = vstart + (vend - vstart) * cut
516 try:
517 newcut = - vstart/(vend - vstart)
518 except (ArithmeticError, TypeError):
519 break
520 if newcut is not None and newcut < cut:
521 cut = newcut
522 else:
523 cutvpos = []
524 for vstart, vend in zip(privatedata.lastvpos, vpos):
525 cutvpos.append(vstart + (vend - vstart) * cut)
526 privatedata.linebasepoints.append(graphvpos_pt(*cutvpos))
527 self.addpointstopath(privatedata)
528 else:
529 # the last point was outside the graph
530 if privatedata.lastvpos is not None:
531 if vposvalid:
532 # cut beginning
533 cut = 0
534 for vstart, vend in zip(privatedata.lastvpos, vpos):
535 newcut = None
536 if vstart > 1:
537 # 1 = vstart + (vend - vstart) * cut
538 try:
539 newcut = (1 - vstart)/(vend - vstart)
540 except (ArithmeticError, TypeError):
541 break
542 if vstart < 0:
543 # 0 = vstart + (vend - vstart) * cut
544 try:
545 newcut = - vstart/(vend - vstart)
546 except (ArithmeticError, TypeError):
547 break
548 if newcut is not None and newcut > cut:
549 cut = newcut
550 else:
551 cutvpos = []
552 for vstart, vend in zip(privatedata.lastvpos, vpos):
553 cutvpos.append(vstart + (vend - vstart) * cut)
554 privatedata.linebasepoints.append(graphvpos_pt(*cutvpos))
555 privatedata.linebasepoints.append(graphvpos_pt(*vpos))
556 else:
557 # sometimes cut beginning and end
558 cutfrom = 0
559 cutto = 1
560 for vstart, vend in zip(privatedata.lastvpos, vpos):
561 newcutfrom = None
562 if vstart > 1:
563 if vend > 1:
564 break
565 # 1 = vstart + (vend - vstart) * cutfrom
566 try:
567 newcutfrom = (1 - vstart)/(vend - vstart)
568 except (ArithmeticError, TypeError):
569 break
570 if vstart < 0:
571 if vend < 0:
572 break
573 # 0 = vstart + (vend - vstart) * cutfrom
574 try:
575 newcutfrom = - vstart/(vend - vstart)
576 except (ArithmeticError, TypeError):
577 break
578 if newcutfrom is not None and newcutfrom > cutfrom:
579 cutfrom = newcutfrom
580 newcutto = None
581 if vend > 1:
582 # 1 = vstart + (vend - vstart) * cutto
583 try:
584 newcutto = (1 - vstart)/(vend - vstart)
585 except (ArithmeticError, TypeError):
586 break
587 if vend < 0:
588 # 0 = vstart + (vend - vstart) * cutto
589 try:
590 newcutto = - vstart/(vend - vstart)
591 except (ArithmeticError, TypeError):
592 break
593 if newcutto is not None and newcutto < cutto:
594 cutto = newcutto
595 else:
596 if cutfrom < cutto:
597 cutfromvpos = []
598 cuttovpos = []
599 for vstart, vend in zip(privatedata.lastvpos, vpos):
600 cutfromvpos.append(vstart + (vend - vstart) * cutfrom)
601 cuttovpos.append(vstart + (vend - vstart) * cutto)
602 privatedata.linebasepoints.append(graphvpos_pt(*cutfromvpos))
603 privatedata.linebasepoints.append(graphvpos_pt(*cuttovpos))
604 self.addpointstopath(privatedata)
605 privatedata.lastvpos = vpos[:]
606 else:
607 if len(privatedata.linebasepoints) > 1:
608 self.addpointstopath(privatedata)
609 privatedata.lastvpos = None
611 def addinvalid(self, privatedata):
612 if len(privatedata.linebasepoints) > 1:
613 self.addpointstopath(privatedata)
614 privatedata.lastvpos = None
616 def donepointstopath(self, privatedata):
617 if len(privatedata.linebasepoints) > 1:
618 self.addpointstopath(privatedata)
619 return privatedata.path
622 class line(_line):
624 needsdata = ["vpos", "vposmissing", "vposavailable", "vposvalid"]
626 changelinestyle = attr.changelist([style.linestyle.solid,
627 style.linestyle.dashed,
628 style.linestyle.dotted,
629 style.linestyle.dashdotted])
631 defaultlineattrs = [changelinestyle]
633 def __init__(self, lineattrs=[]):
634 self.lineattrs = lineattrs
636 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
637 if self.lineattrs is not None:
638 privatedata.lineattrs = attr.selectattrs(self.defaultlineattrs + self.lineattrs, selectindex, selecttotal)
639 else:
640 privatedata.lineattrs = None
642 def initdrawpoints(self, privatedata, sharedata, graph):
643 self.initpointstopath(privatedata)
645 def drawpoint(self, privatedata, sharedata, graph, point):
646 self.addpoint(privatedata, graph.vpos_pt, sharedata.vposavailable, sharedata.vposvalid, sharedata.vpos)
648 def donedrawpoints(self, privatedata, sharedata, graph):
649 path = self.donepointstopath(privatedata)
650 if privatedata.lineattrs is not None and len(path):
651 graph.stroke(path, privatedata.lineattrs)
653 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
654 if privatedata.lineattrs is not None:
655 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)
658 class impulses(_styleneedingpointpos):
660 needsdata = ["vpos", "vposmissing", "vposavailable", "vposvalid", "poscolumnnames"]
662 defaultlineattrs = [line.changelinestyle]
663 defaultfrompathattrs = []
665 def __init__(self, lineattrs=[], fromvalue=0, frompathattrs=[], valueaxisindex=1):
666 self.lineattrs = lineattrs
667 self.fromvalue = fromvalue
668 self.frompathattrs = frompathattrs
669 self.valueaxisindex = valueaxisindex
671 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
672 privatedata.insertfrompath = selectindex == 0
673 if self.lineattrs is not None:
674 privatedata.lineattrs = attr.selectattrs(self.defaultlineattrs + self.lineattrs, selectindex, selecttotal)
675 else:
676 privatedata.lineattrs = None
678 def adjustaxis(self, privatedata, sharedata, graph, columnname, data):
679 if self.fromvalue is not None:
680 try:
681 i = sharedata.poscolumnnames.index(columnname)
682 except ValueError:
683 pass
684 else:
685 if i == self.valueaxisindex:
686 graph.axes[sharedata.poscolumnnames[i]].adjustaxis([self.fromvalue])
688 def initdrawpoints(self, privatedata, sharedata, graph):
689 privatedata.impulsescanvas = canvas.canvas()
690 if self.fromvalue is not None:
691 valueaxisname = sharedata.poscolumnnames[self.valueaxisindex]
692 privatedata.vfromvalue = graph.axes[valueaxisname].convert(self.fromvalue)
693 privatedata.vfromvaluecut = 0
694 if privatedata.vfromvalue < 0:
695 privatedata.vfromvalue = 0
696 if privatedata.vfromvalue > 1:
697 privatedata.vfromvalue = 1
698 if self.frompathattrs is not None and privatedata.insertfrompath:
699 graph.stroke(graph.axes[valueaxisname].vgridpath(privatedata.vfromvalue),
700 self.defaultfrompathattrs + self.frompathattrs)
701 else:
702 privatedata.vfromvalue = 0
704 def drawpoint(self, privatedata, sharedata, graph, point):
705 if sharedata.vposvalid and privatedata.lineattrs is not None:
706 vpos = sharedata.vpos[:]
707 vpos[self.valueaxisindex] = privatedata.vfromvalue
708 privatedata.impulsescanvas.stroke(graph.vgeodesic(*(vpos + sharedata.vpos)), privatedata.lineattrs)
710 def donedrawpoints(self, privatedata, sharedata, graph):
711 graph.insert(privatedata.impulsescanvas)
713 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
714 if privatedata.lineattrs is not None:
715 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)
718 class errorbar(_style):
720 needsdata = ["vpos", "vposmissing", "vposavailable", "vposvalid", "vrange", "vrangeminmissing", "vrangemaxmissing"]
722 defaulterrorbarattrs = []
724 def __init__(self, size=0.1*unit.v_cm,
725 errorbarattrs=[],
726 epsilon=1e-10):
727 self.size = size
728 self.errorbarattrs = errorbarattrs
729 self.epsilon = epsilon
731 def columnnames(self, privatedata, sharedata, graph, columnnames):
732 for i in sharedata.vposmissing:
733 if i in sharedata.vrangeminmissing and i in sharedata.vrangemaxmissing:
734 raise ValueError("position and range for a graph dimension missing")
735 return []
737 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
738 privatedata.errorsize_pt = unit.topt(attr.selectattr(self.size, selectindex, selecttotal))
739 privatedata.errorbarattrs = attr.selectattrs(self.defaulterrorbarattrs + self.errorbarattrs, selectindex, selecttotal)
741 def initdrawpoints(self, privatedata, sharedata, graph):
742 if privatedata.errorbarattrs is not None:
743 privatedata.errorbarcanvas = canvas.canvas(privatedata.errorbarattrs)
744 privatedata.dimensionlist = list(xrange(len(sharedata.vpos)))
746 def drawpoint(self, privatedata, sharedata, graph, point):
747 if privatedata.errorbarattrs is not None:
748 for i in privatedata.dimensionlist:
749 for j in privatedata.dimensionlist:
750 if (i != j and
751 (sharedata.vpos[j] is None or
752 sharedata.vpos[j] < -self.epsilon or
753 sharedata.vpos[j] > 1+self.epsilon)):
754 break
755 else:
756 if ((sharedata.vrange[i][0] is None and sharedata.vpos[i] is None) or
757 (sharedata.vrange[i][1] is None and sharedata.vpos[i] is None) or
758 (sharedata.vrange[i][0] is None and sharedata.vrange[i][1] is None)):
759 continue
760 vminpos = sharedata.vpos[:]
761 if sharedata.vrange[i][0] is not None:
762 vminpos[i] = sharedata.vrange[i][0]
763 mincap = 1
764 else:
765 mincap = 0
766 if vminpos[i] > 1+self.epsilon:
767 continue
768 if vminpos[i] < -self.epsilon:
769 vminpos[i] = 0
770 mincap = 0
771 vmaxpos = sharedata.vpos[:]
772 if sharedata.vrange[i][1] is not None:
773 vmaxpos[i] = sharedata.vrange[i][1]
774 maxcap = 1
775 else:
776 maxcap = 0
777 if vmaxpos[i] < -self.epsilon:
778 continue
779 if vmaxpos[i] > 1+self.epsilon:
780 vmaxpos[i] = 1
781 maxcap = 0
782 privatedata.errorbarcanvas.stroke(graph.vgeodesic(*(vminpos + vmaxpos)))
783 for j in privatedata.dimensionlist:
784 if i != j:
785 if mincap:
786 privatedata.errorbarcanvas.stroke(graph.vcap_pt(j, privatedata.errorsize_pt, *vminpos))
787 if maxcap:
788 privatedata.errorbarcanvas.stroke(graph.vcap_pt(j, privatedata.errorsize_pt, *vmaxpos))
790 def donedrawpoints(self, privatedata, sharedata, graph):
791 if privatedata.errorbarattrs is not None:
792 graph.insert(privatedata.errorbarcanvas)
795 class text(_styleneedingpointpos):
797 needsdata = ["vpos", "vposmissing", "vposvalid"]
799 defaulttextattrs = [textmodule.halign.center, textmodule.vshift.mathaxis]
801 def __init__(self, textname="text", dxname=None, dyname=None,
802 dxunit=0.3*unit.v_cm, dyunit=0.3*unit.v_cm,
803 textdx=0*unit.v_cm, textdy=0.3*unit.v_cm, textattrs=[]):
804 self.textname = textname
805 self.dxname = dxname
806 self.dyname = dyname
807 self.dxunit = dxunit
808 self.dyunit = dyunit
809 self.textdx = textdx
810 self.textdy = textdy
811 self.textattrs = textattrs
813 def columnnames(self, privatedata, sharedata, graph, columnnames):
814 if self.textname not in columnnames:
815 raise ValueError("column '%s' missing" % self.textname)
816 names = [self.textname]
817 if self.dxname is not None:
818 if self.dxname not in columnnames:
819 raise ValueError("column '%s' missing" % self.dxname)
820 names.append(self.dxname)
821 if self.dyname is not None:
822 if self.dyname not in columnnames:
823 raise ValueError("column '%s' missing" % self.dyname)
824 names.append(self.dyname)
825 return names + _styleneedingpointpos.columnnames(self, privatedata, sharedata, graph, columnnames)
827 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
828 if self.textattrs is not None:
829 privatedata.textattrs = attr.selectattrs(self.defaulttextattrs + self.textattrs, selectindex, selecttotal)
830 else:
831 privatedata.textattrs = None
833 def initdrawpoints(self, privatedata, sharedata, grap):
834 if self.dxname is None:
835 privatedata.textdx_pt = unit.topt(self.textdx)
836 else:
837 privatedata.dxunit_pt = unit.topt(self.dxunit)
838 if self.dyname is None:
839 privatedata.textdy_pt = unit.topt(self.textdy)
840 else:
841 privatedata.dyunit_pt = unit.topt(self.dyunit)
843 def drawpoint(self, privatedata, sharedata, graph, point):
844 if privatedata.textattrs is not None and sharedata.vposvalid:
845 x_pt, y_pt = graph.vpos_pt(*sharedata.vpos)
846 try:
847 text = str(point[self.textname])
848 except:
849 pass
850 else:
851 if self.dxname is None:
852 dx_pt = privatedata.textdx_pt
853 else:
854 dx_pt = float(point[self.dxname]) * privatedata.dxunit_pt
855 if self.dyname is None:
856 dy_pt = privatedata.textdy_pt
857 else:
858 dy_pt = float(point[self.dyname]) * privatedata.dyunit_pt
859 graph.text_pt(x_pt + dx_pt, y_pt + dy_pt, text, privatedata.textattrs)
861 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
862 raise RuntimeError("Style currently doesn't provide a graph key")
865 class arrow(_styleneedingpointpos):
867 needsdata = ["vpos", "vposmissing", "vposvalid"]
869 defaultlineattrs = []
870 defaultarrowattrs = []
872 def __init__(self, linelength=0.25*unit.v_cm, arrowsize=0.15*unit.v_cm, lineattrs=[], arrowattrs=[], arrowpos=0.5, epsilon=1e-5):
873 self.linelength = linelength
874 self.arrowsize = arrowsize
875 self.lineattrs = lineattrs
876 self.arrowattrs = arrowattrs
877 self.arrowpos = arrowpos
878 self.epsilon = epsilon
880 def columnnames(self, privatedata, sharedata, graph, columnnames):
881 if len(graph.axesnames) != 2:
882 raise ValueError("arrow style restricted on two-dimensional graphs")
883 if "size" not in columnnames:
884 raise ValueError("size missing")
885 if "angle" not in columnnames:
886 raise ValueError("angle missing")
887 return ["size", "angle"] + _styleneedingpointpos.columnnames(self, privatedata, sharedata, graph, columnnames)
889 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
890 if self.lineattrs is not None:
891 privatedata.lineattrs = attr.selectattrs(self.defaultlineattrs + self.lineattrs, selectindex, selecttotal)
892 else:
893 privatedata.lineattrs = None
894 if self.arrowattrs is not None:
895 privatedata.arrowattrs = attr.selectattrs(self.defaultarrowattrs + self.arrowattrs, selectindex, selecttotal)
896 else:
897 privatedata.arrowattrs = None
899 def initdrawpoints(self, privatedata, sharedata, graph):
900 privatedata.arrowcanvas = canvas.canvas()
902 def drawpoint(self, privatedata, sharedata, graph, point):
903 if privatedata.lineattrs is not None and privatedata.arrowattrs is not None and sharedata.vposvalid:
904 linelength_pt = unit.topt(self.linelength)
905 x_pt, y_pt = graph.vpos_pt(*sharedata.vpos)
906 try:
907 angle = point["angle"] + 0.0
908 size = point["size"] + 0.0
909 except:
910 pass
911 else:
912 if point["size"] > self.epsilon:
913 dx = math.cos(angle*math.pi/180)
914 dy = math.sin(angle*math.pi/180)
915 x1 = x_pt-self.arrowpos*dx*linelength_pt*size
916 y1 = y_pt-self.arrowpos*dy*linelength_pt*size
917 x2 = x_pt+(1-self.arrowpos)*dx*linelength_pt*size
918 y2 = y_pt+(1-self.arrowpos)*dy*linelength_pt*size
919 privatedata.arrowcanvas.stroke(path.line_pt(x1, y1, x2, y2), privatedata.lineattrs +
920 [deco.earrow(privatedata.arrowattrs, size=self.arrowsize*size)])
922 def donedrawpoints(self, privatedata, sharedata, graph):
923 graph.insert(privatedata.arrowcanvas)
925 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
926 raise RuntimeError("Style currently doesn't provide a graph key")
929 class rect(_style):
931 needsdata = ["vrange", "vrangeminmissing", "vrangemaxmissing"]
933 def __init__(self, gradient=color.gradient.Grey):
934 self.gradient = gradient
936 def columnnames(self, privatedata, sharedata, graph, columnnames):
937 if len(graph.axesnames) != 2:
938 raise TypeError("arrow style restricted on two-dimensional graphs")
939 if "color" not in columnnames:
940 raise ValueError("color missing")
941 if len(sharedata.vrangeminmissing) + len(sharedata.vrangemaxmissing):
942 raise ValueError("incomplete range")
943 return ["color"]
945 def initdrawpoints(self, privatedata, sharedata, graph):
946 privatedata.rectcanvas = graph.insert(canvas.canvas())
948 def drawpoint(self, privatedata, sharedata, graph, point):
949 xvmin = sharedata.vrange[0][0]
950 xvmax = sharedata.vrange[0][1]
951 yvmin = sharedata.vrange[1][0]
952 yvmax = sharedata.vrange[1][1]
953 if (xvmin is not None and xvmin < 1 and
954 xvmax is not None and xvmax > 0 and
955 yvmin is not None and yvmin < 1 and
956 yvmax is not None and yvmax > 0):
957 if xvmin < 0:
958 xvmin = 0
959 elif xvmax > 1:
960 xvmax = 1
961 if yvmin < 0:
962 yvmin = 0
963 elif yvmax > 1:
964 yvmax = 1
965 p = graph.vgeodesic(xvmin, yvmin, xvmax, yvmin)
966 p.append(graph.vgeodesic_el(xvmax, yvmin, xvmax, yvmax))
967 p.append(graph.vgeodesic_el(xvmax, yvmax, xvmin, yvmax))
968 p.append(graph.vgeodesic_el(xvmin, yvmax, xvmin, yvmin))
969 p.append(path.closepath())
970 privatedata.rectcanvas.fill(p, [self.gradient.getcolor(point["color"])])
972 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
973 raise RuntimeError("Style currently doesn't provide a graph key")
976 class histogram(_style):
978 needsdata = ["vpos", "vposmissing", "vrange", "vrangeminmissing", "vrangemaxmissing"]
980 defaultlineattrs = [deco.stroked]
981 defaultfrompathattrs = []
983 def __init__(self, lineattrs=[], steps=0, fromvalue=0, frompathattrs=[], fillable=0, rectkey=0,
984 autohistogramaxisindex=0, autohistogrampointpos=0.5, epsilon=1e-10):
985 self.lineattrs = lineattrs
986 self.steps = steps
987 self.fromvalue = fromvalue
988 self.frompathattrs = frompathattrs
989 self.fillable = fillable # TODO: fillable paths might not properly be closed by straight lines on curved graph geometries
990 self.rectkey = rectkey
991 self.autohistogramaxisindex = autohistogramaxisindex
992 self.autohistogrampointpos = autohistogrampointpos
993 self.epsilon = epsilon
995 def columnnames(self, privatedata, sharedata, graph, columnnames):
996 if len(graph.axesnames) != 2:
997 raise TypeError("histogram style restricted on two-dimensional graphs")
998 privatedata.rangeaxisindex = None
999 for i in builtinrange(len(graph.axesnames)):
1000 if i in sharedata.vrangeminmissing or i in sharedata.vrangemaxmissing:
1001 if i in sharedata.vposmissing:
1002 raise ValueError("pos and range missing")
1003 else:
1004 if privatedata.rangeaxisindex is not None:
1005 raise ValueError("multiple ranges")
1006 privatedata.rangeaxisindex = i
1007 if privatedata.rangeaxisindex is None:
1008 privatedata.rangeaxisindex = self.autohistogramaxisindex
1009 privatedata.autohistogram = 1
1010 else:
1011 privatedata.autohistogram = 0
1012 return []
1014 def adjustaxis(self, privatedata, sharedata, graph, columnname, data):
1015 if privatedata.autohistogram and columnname == sharedata.poscolumnnames[privatedata.rangeaxisindex]:
1016 if len(data) == 1:
1017 raise ValueError("several data points needed for automatic histogram width calculation")
1018 if len(data) > 1:
1019 delta = data[1] - data[0]
1020 min = data[0] - self.autohistogrampointpos * delta
1021 max = data[-1] + (1-self.autohistogrampointpos) * delta
1022 graph.axes[columnname].adjustaxis([min, max])
1023 elif self.fromvalue is not None and columnname == sharedata.poscolumnnames[1-privatedata.rangeaxisindex]:
1024 graph.axes[columnname].adjustaxis([self.fromvalue])
1026 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1027 privatedata.insertfrompath = selectindex == 0
1028 if self.lineattrs is not None:
1029 privatedata.lineattrs = attr.selectattrs(self.defaultlineattrs + self.lineattrs, selectindex, selecttotal)
1030 else:
1031 privatedata.lineattrs = None
1033 def vmoveto(self, privatedata, sharedata, graph, vpos, vvalue):
1034 if -self.epsilon < vpos < 1+self.epsilon and -self.epsilon < vvalue < 1+self.epsilon:
1035 if privatedata.rangeaxisindex:
1036 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue, vpos)))
1037 else:
1038 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vpos, vvalue)))
1040 def vposline(self, privatedata, sharedata, graph, vpos, vvalue1, vvalue2):
1041 if -self.epsilon < vpos < 1+self.epsilon:
1042 vvalue1cut = 0
1043 if vvalue1 < 0:
1044 vvalue1 = 0
1045 vvalue1cut = -1
1046 elif vvalue1 > 1:
1047 vvalue1 = 1
1048 vvalue1cut = 1
1049 vvalue2cut = 0
1050 if vvalue2 < 0:
1051 vvalue2 = 0
1052 vvalue2cut = -1
1053 elif vvalue2 > 1:
1054 vvalue2 = 1
1055 vvalue2cut = 1
1056 if abs(vvalue1cut + vvalue2cut) <= 1:
1057 if vvalue1cut and not self.fillable:
1058 if privatedata.rangeaxisindex:
1059 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue1, vpos)))
1060 else:
1061 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vpos, vvalue1)))
1062 if privatedata.rangeaxisindex:
1063 privatedata.path.append(graph.vgeodesic_el(vvalue1, vpos, vvalue2, vpos))
1064 else:
1065 privatedata.path.append(graph.vgeodesic_el(vpos, vvalue1, vpos, vvalue2))
1067 def vvalueline(self, privatedata, sharedata, graph, vvalue, vpos1, vpos2):
1068 if self.fillable:
1069 if vvalue < -self.epsilon:
1070 vvalue = 0
1071 warnings.warn("cut at graph boundary adds artificial lines to fillable step histogram path")
1072 if vvalue > 1+self.epsilon:
1073 vvalue = 1
1074 warnings.warn("cut at graph boundary adds artificial lines to fillable step histogram path")
1075 if self.fillable or (-self.epsilon < vvalue < 1+self.epsilon):
1076 vpos1cut = 0
1077 if vpos1 < 0:
1078 vpos1 = 0
1079 vpos1cut = -1
1080 elif vpos1 > 1:
1081 vpos1 = 1
1082 vpos1cut = 1
1083 vpos2cut = 0
1084 if vpos2 < 0:
1085 vpos2 = 0
1086 vpos2cut = -1
1087 elif vpos2 > 1:
1088 vpos2 = 1
1089 vpos2cut = 1
1090 if abs(vpos1cut + vpos2cut) <= 1:
1091 if vpos1cut:
1092 if self.fillable:
1093 if privatedata.rangeaxisindex:
1094 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(privatedata.vfromvalue, vpos1)))
1095 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vpos1, vvalue, vpos1))
1096 else:
1097 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vpos1, privatedata.vfromvalue)))
1098 privatedata.path.append(graph.vgeodesic_el(vpos1, privatedata.vfromvalue, vpos1, vvalue))
1099 else:
1100 if privatedata.rangeaxisindex:
1101 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue, vpos1)))
1102 else:
1103 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vpos1, vvalue)))
1104 if privatedata.rangeaxisindex:
1105 privatedata.path.append(graph.vgeodesic_el(vvalue, vpos1, vvalue, vpos2))
1106 else:
1107 privatedata.path.append(graph.vgeodesic_el(vpos1, vvalue, vpos2, vvalue))
1108 if self.fillable and vpos2cut:
1109 warnings.warn("cut at graph boundary adds artificial lines to fillable step histogram path")
1110 if privatedata.rangeaxisindex:
1111 privatedata.path.append(graph.vgeodesic_el(vvalue, vpos2, privatedata.vfromvalue, vpos2))
1112 else:
1113 privatedata.path.append(graph.vgeodesic_el(vpos2, vvalue, vpos2, privatedata.vfromvalue))
1115 def drawvalue(self, privatedata, sharedata, graph, vmin, vmax, vvalue):
1116 currentvalid = vmin is not None and vmax is not None and vvalue is not None
1117 if self.fillable and not self.steps:
1118 if not currentvalid:
1119 return
1120 vmincut = 0
1121 if vmin < -self.epsilon:
1122 vmin = 0
1123 vmincut = -1
1124 elif vmin > 1+self.epsilon:
1125 vmin = 1
1126 vmincut = 1
1127 vmaxcut = 0
1128 if vmax < -self.epsilon:
1129 vmax = 0
1130 vmaxcut = -1
1131 if vmax > 1+self.epsilon:
1132 vmax = 1
1133 vmaxcut = 1
1134 vvaluecut = 0
1135 if vvalue < -self.epsilon:
1136 vvalue = 0
1137 vvaluecut = -1
1138 if vvalue > 1+self.epsilon:
1139 vvalue = 1
1140 vvaluecut = 1
1141 done = 0
1142 if abs(vmincut) + abs(vmaxcut) + abs(vvaluecut) + abs(privatedata.vfromvaluecut) > 1:
1143 if abs(vmincut + vmaxcut) > 1 or abs(vvaluecut+privatedata.vfromvaluecut) > 1:
1144 done = 1
1145 else:
1146 warnings.warn("multiple cuts at graph boundary add artificial lines to fillable rectangle histogram path")
1147 elif vmincut:
1148 done = 1
1149 if privatedata.rangeaxisindex:
1150 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(privatedata.vfromvalue, vmin)))
1151 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmin, privatedata.vfromvalue, vmax))
1152 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmax, vvalue, vmax))
1153 privatedata.path.append(graph.vgeodesic_el(vvalue, vmax, vvalue, vmin))
1154 else:
1155 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmin, privatedata.vfromvalue)))
1156 privatedata.path.append(graph.vgeodesic_el(vmin, privatedata.vfromvalue, vmax, privatedata.vfromvalue))
1157 privatedata.path.append(graph.vgeodesic_el(vmax, privatedata.vfromvalue, vmax, vvalue))
1158 privatedata.path.append(graph.vgeodesic_el(vmax, vvalue, vmin, vvalue))
1159 elif vmaxcut:
1160 done = 1
1161 if privatedata.rangeaxisindex:
1162 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue, vmax)))
1163 privatedata.path.append(graph.vgeodesic_el(vvalue, vmax, vvalue, vmin))
1164 privatedata.path.append(graph.vgeodesic_el(vvalue, vmin, privatedata.vfromvalue, vmin))
1165 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmin, privatedata.vfromvalue, vmax))
1166 else:
1167 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmax, vvalue)))
1168 privatedata.path.append(graph.vgeodesic_el(vmax, vvalue, vmin, vvalue))
1169 privatedata.path.append(graph.vgeodesic_el(vmin, vvalue, vmin, privatedata.vfromvalue))
1170 privatedata.path.append(graph.vgeodesic_el(vmin, privatedata.vfromvalue, vmax, privatedata.vfromvalue))
1171 elif privatedata.vfromvaluecut:
1172 done = 1
1173 if privatedata.rangeaxisindex:
1174 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(privatedata.vfromvalue, vmax)))
1175 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmax, vvalue, vmax))
1176 privatedata.path.append(graph.vgeodesic_el(vvalue, vmax, vvalue, vmin))
1177 privatedata.path.append(graph.vgeodesic_el(vvalue, vmin, privatedata.vfromvalue, vmin))
1178 else:
1179 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmax, privatedata.vfromvalue)))
1180 privatedata.path.append(graph.vgeodesic_el(vmax, privatedata.vfromvalue, vmax, vvalue))
1181 privatedata.path.append(graph.vgeodesic_el(vmax, vvalue, vmin, vvalue))
1182 privatedata.path.append(graph.vgeodesic_el(vmin, vvalue, vmin, privatedata.vfromvalue))
1183 elif vvaluecut:
1184 done = 1
1185 if privatedata.rangeaxisindex:
1186 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue, vmin)))
1187 privatedata.path.append(graph.vgeodesic_el(vvalue, vmin, privatedata.vfromvalue, vmin))
1188 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmin, privatedata.vfromvalue, vmax))
1189 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmax, vvalue, vmax))
1190 else:
1191 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmin, vvalue)))
1192 privatedata.path.append(graph.vgeodesic_el(vmin, vvalue, vmin, privatedata.vfromvalue))
1193 privatedata.path.append(graph.vgeodesic_el(vmin, privatedata.vfromvalue, vmax, privatedata.vfromvalue))
1194 privatedata.path.append(graph.vgeodesic_el(vmax, privatedata.vfromvalue, vmax, vvalue))
1195 if not done:
1196 if privatedata.rangeaxisindex:
1197 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(privatedata.vfromvalue, vmin)))
1198 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmin, privatedata.vfromvalue, vmax))
1199 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmax, vvalue, vmax))
1200 privatedata.path.append(graph.vgeodesic_el(vvalue, vmax, vvalue, vmin))
1201 privatedata.path.append(graph.vgeodesic_el(vvalue, vmin, privatedata.vfromvalue, vmin))
1202 privatedata.path.append(path.closepath())
1203 else:
1204 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmin, privatedata.vfromvalue)))
1205 privatedata.path.append(graph.vgeodesic_el(vmin, privatedata.vfromvalue, vmax, privatedata.vfromvalue))
1206 privatedata.path.append(graph.vgeodesic_el(vmax, privatedata.vfromvalue, vmax, vvalue))
1207 privatedata.path.append(graph.vgeodesic_el(vmax, vvalue, vmin, vvalue))
1208 privatedata.path.append(graph.vgeodesic_el(vmin, vvalue, vmin, privatedata.vfromvalue))
1209 privatedata.path.append(path.closepath())
1210 else:
1211 try:
1212 gap = abs(vmin - privatedata.lastvmax) > self.epsilon
1213 except (ArithmeticError, ValueError, TypeError):
1214 gap = 1
1215 if (privatedata.lastvvalue is not None and currentvalid and not gap and
1216 (self.steps or (privatedata.lastvvalue-privatedata.vfromvalue)*(vvalue-privatedata.vfromvalue) < 0)):
1217 self.vposline(privatedata, sharedata, graph,
1218 vmin, privatedata.lastvvalue, vvalue)
1219 else:
1220 if privatedata.lastvvalue is not None and currentvalid:
1221 currentbigger = abs(privatedata.lastvvalue-privatedata.vfromvalue) < abs(vvalue-privatedata.vfromvalue)
1222 if privatedata.lastvvalue is not None and (not currentvalid or not currentbigger or gap):
1223 self.vposline(privatedata, sharedata, graph,
1224 privatedata.lastvmax, privatedata.lastvvalue, privatedata.vfromvalue)
1225 if currentvalid:
1226 self.vmoveto(privatedata, sharedata, graph,
1227 vmin, vvalue)
1228 if currentvalid and (privatedata.lastvvalue is None or currentbigger or gap):
1229 self.vmoveto(privatedata, sharedata, graph,
1230 vmin, privatedata.vfromvalue)
1231 self.vposline(privatedata, sharedata, graph,
1232 vmin, privatedata.vfromvalue, vvalue)
1233 if currentvalid:
1234 self.vvalueline(privatedata, sharedata, graph,
1235 vvalue, vmin, vmax)
1236 privatedata.lastvvalue = vvalue
1237 privatedata.lastvmax = vmax
1238 else:
1239 privatedata.lastvvalue = privatedata.lastvmax = None
1241 def initdrawpoints(self, privatedata, sharedata, graph):
1242 privatedata.path = path.path()
1243 privatedata.lastvvalue = privatedata.lastvmax = None
1244 privatedata.vcurrentpoint = None
1245 privatedata.count = 0
1246 if self.fromvalue is not None:
1247 valueaxisname = sharedata.poscolumnnames[1-privatedata.rangeaxisindex]
1248 privatedata.vfromvalue = graph.axes[valueaxisname].convert(self.fromvalue)
1249 privatedata.vfromvaluecut = 0
1250 if privatedata.vfromvalue < 0:
1251 privatedata.vfromvalue = 0
1252 privatedata.vfromvaluecut = -1
1253 if privatedata.vfromvalue > 1:
1254 privatedata.vfromvalue = 1
1255 privatedata.vfromvaluecut = 1
1256 if self.frompathattrs is not None and privatedata.insertfrompath:
1257 graph.stroke(graph.axes[valueaxisname].vgridpath(privatedata.vfromvalue),
1258 self.defaultfrompathattrs + self.frompathattrs)
1259 else:
1260 privatedata.vfromvalue = 0
1262 def drawpoint(self, privatedata, sharedata, graph, point):
1263 if privatedata.autohistogram:
1264 # automatic range handling
1265 privatedata.count += 1
1266 if privatedata.count == 2:
1267 if privatedata.rangeaxisindex:
1268 privatedata.vrange = sharedata.vpos[1] - privatedata.lastvpos[1]
1269 self.drawvalue(privatedata, sharedata, graph,
1270 privatedata.lastvpos[1] - self.autohistogrampointpos*privatedata.vrange,
1271 privatedata.lastvpos[1] + (1-self.autohistogrampointpos)*privatedata.vrange,
1272 privatedata.lastvpos[0])
1273 else:
1274 privatedata.vrange = sharedata.vpos[0] - privatedata.lastvpos[0]
1275 self.drawvalue(privatedata, sharedata, graph,
1276 privatedata.lastvpos[0] - self.autohistogrampointpos*privatedata.vrange,
1277 privatedata.lastvpos[0] + (1-self.autohistogrampointpos)*privatedata.vrange,
1278 privatedata.lastvpos[1])
1279 elif privatedata.count > 2:
1280 if privatedata.rangeaxisindex:
1281 vrange = sharedata.vpos[1] - privatedata.lastvpos[1]
1282 else:
1283 vrange = sharedata.vpos[0] - privatedata.lastvpos[0]
1284 if abs(privatedata.vrange - vrange) > self.epsilon:
1285 raise ValueError("equal steps (in graph coordinates) needed for automatic width calculation")
1286 if privatedata.count > 1:
1287 if privatedata.rangeaxisindex:
1288 self.drawvalue(privatedata, sharedata, graph,
1289 sharedata.vpos[1] - self.autohistogrampointpos*privatedata.vrange,
1290 sharedata.vpos[1] + (1-self.autohistogrampointpos)*privatedata.vrange,
1291 sharedata.vpos[0])
1292 else:
1293 self.drawvalue(privatedata, sharedata, graph,
1294 sharedata.vpos[0] - self.autohistogrampointpos*privatedata.vrange,
1295 sharedata.vpos[0] + (1-self.autohistogrampointpos)*privatedata.vrange,
1296 sharedata.vpos[1])
1297 privatedata.lastvpos = sharedata.vpos[:]
1298 else:
1299 if privatedata.rangeaxisindex:
1300 self.drawvalue(privatedata, sharedata, graph,
1301 sharedata.vrange[1][0], sharedata.vrange[1][1], sharedata.vpos[0])
1302 else:
1303 self.drawvalue(privatedata, sharedata, graph,
1304 sharedata.vrange[0][0], sharedata.vrange[0][1], sharedata.vpos[1])
1306 def donedrawpoints(self, privatedata, sharedata, graph):
1307 self.drawvalue(privatedata, sharedata, graph, None, None, None)
1308 if privatedata.lineattrs is not None and len(privatedata.path):
1309 graph.draw(privatedata.path, privatedata.lineattrs)
1311 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
1312 if privatedata.lineattrs is not None:
1313 if self.rectkey:
1314 p = path.rect_pt(x_pt, y_pt, width_pt, height_pt)
1315 else:
1316 p = path.line_pt(x_pt, y_pt+0.5*height_pt, x_pt+width_pt, y_pt+0.5*height_pt)
1317 graph.draw(p, privatedata.lineattrs)
1320 class barpos(_style):
1322 providesdata = ["vpos", "vposmissing", "vposavailable", "vposvalid", "vbarrange", "barposcolumnnames", "barvalueindex", "lastbarvalue", "stackedbar", "stackedbardraw"]
1324 defaultfrompathattrs = []
1326 def __init__(self, fromvalue=None, frompathattrs=[], epsilon=1e-10):
1327 self.fromvalue = fromvalue
1328 self.frompathattrs = frompathattrs
1329 self.epsilon = epsilon
1331 def columnnames(self, privatedata, sharedata, graph, columnnames):
1332 sharedata.barposcolumnnames = []
1333 sharedata.barvalueindex = None
1334 for dimension, axisnames in enumerate(graph.axesnames):
1335 found = 0
1336 for axisname in axisnames:
1337 if axisname in columnnames:
1338 if sharedata.barvalueindex is not None:
1339 raise ValueError("multiple values")
1340 sharedata.barvalueindex = dimension
1341 sharedata.barposcolumnnames.append(axisname)
1342 found += 1
1343 if (axisname + "name") in columnnames:
1344 sharedata.barposcolumnnames.append(axisname + "name")
1345 found += 1
1346 if found > 1:
1347 raise ValueError("multiple names and value")
1348 if not found:
1349 raise ValueError("value/name missing")
1350 if sharedata.barvalueindex is None:
1351 raise ValueError("missing value")
1352 sharedata.vposmissing = []
1353 return sharedata.barposcolumnnames
1355 def addsubvalue(self, value, subvalue):
1356 try:
1357 value + ""
1358 except:
1359 try:
1360 return value[0], self.addsubvalue(value[1], subvalue)
1361 except:
1362 return value, subvalue
1363 else:
1364 return value, subvalue
1366 def adjustaxis(self, privatedata, sharedata, graph, columnname, data):
1367 try:
1368 i = sharedata.barposcolumnnames.index(columnname)
1369 except ValueError:
1370 pass
1371 else:
1372 if i == sharedata.barvalueindex:
1373 if self.fromvalue is not None:
1374 graph.axes[sharedata.barposcolumnnames[i]].adjustaxis([self.fromvalue])
1375 graph.axes[sharedata.barposcolumnnames[i]].adjustaxis(data)
1376 else:
1377 graph.axes[sharedata.barposcolumnnames[i][:-4]].adjustaxis([self.addsubvalue(x, 0) for x in data])
1378 graph.axes[sharedata.barposcolumnnames[i][:-4]].adjustaxis([self.addsubvalue(x, 1) for x in data])
1380 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1381 privatedata.insertfrompath = selectindex == 0
1383 def initdrawpoints(self, privatedata, sharedata, graph):
1384 sharedata.vpos = [None]*(len(sharedata.barposcolumnnames))
1385 sharedata.vbarrange = [[None for i in xrange(2)] for x in sharedata.barposcolumnnames]
1386 sharedata.stackedbar = sharedata.stackedbardraw = 0
1388 if self.fromvalue is not None:
1389 privatedata.vfromvalue = graph.axes[sharedata.barposcolumnnames[sharedata.barvalueindex]].convert(self.fromvalue)
1390 if privatedata.vfromvalue < 0:
1391 privatedata.vfromvalue = 0
1392 if privatedata.vfromvalue > 1:
1393 privatedata.vfromvalue = 1
1394 if self.frompathattrs is not None and privatedata.insertfrompath:
1395 graph.stroke(graph.axes[sharedata.barposcolumnnames[sharedata.barvalueindex]].vgridpath(privatedata.vfromvalue),
1396 self.defaultfrompathattrs + self.frompathattrs)
1397 else:
1398 privatedata.vfromvalue = 0
1400 def drawpoint(self, privatedata, sharedata, graph, point):
1401 sharedata.vposavailable = sharedata.vposvalid = 1
1402 for i, barname in enumerate(sharedata.barposcolumnnames):
1403 if i == sharedata.barvalueindex:
1404 sharedata.vbarrange[i][0] = privatedata.vfromvalue
1405 sharedata.lastbarvalue = point[barname]
1406 try:
1407 sharedata.vpos[i] = sharedata.vbarrange[i][1] = graph.axes[barname].convert(sharedata.lastbarvalue)
1408 except (ArithmeticError, ValueError, TypeError):
1409 sharedata.vpos[i] = sharedata.vbarrange[i][1] = None
1410 else:
1411 for j in xrange(2):
1412 try:
1413 sharedata.vbarrange[i][j] = graph.axes[barname[:-4]].convert(self.addsubvalue(point[barname], j))
1414 except (ArithmeticError, ValueError, TypeError):
1415 sharedata.vbarrange[i][j] = None
1416 try:
1417 sharedata.vpos[i] = 0.5*(sharedata.vbarrange[i][0]+sharedata.vbarrange[i][1])
1418 except (ArithmeticError, ValueError, TypeError):
1419 sharedata.vpos[i] = None
1420 if sharedata.vpos[i] is None:
1421 sharedata.vposavailable = sharedata.vposvalid = 0
1422 elif sharedata.vpos[i] < -self.epsilon or sharedata.vpos[i] > 1+self.epsilon:
1423 sharedata.vposvalid = 0
1425 registerdefaultprovider(barpos(), ["vbarrange", "barposcolumnnames", "barvalueindex", "lastbarvalue", "stackedbar", "stackedbardraw"])
1428 class stackedbarpos(_style):
1430 # provides no additional data, but needs some data (and modifies some of them)
1431 needsdata = ["vbarrange", "barposcolumnnames", "barvalueindex", "lastbarvalue", "stackedbar", "stackedbardraw"]
1433 def __init__(self, stackname, addontop=0, epsilon=1e-10):
1434 self.stackname = stackname
1435 self.epsilon = epsilon
1436 self.addontop = addontop
1438 def columnnames(self, privatedata, sharedata, graph, columnnames):
1439 if self.stackname not in columnnames:
1440 raise ValueError("column '%s' missing" % self.stackname)
1441 return [self.stackname]
1443 def adjustaxis(self, privatedata, sharedata, graph, columnname, data):
1444 if columnname == self.stackname:
1445 graph.axes[sharedata.barposcolumnnames[sharedata.barvalueindex]].adjustaxis(data)
1447 def initdrawpoints(self, privatedata, sharedata, graph):
1448 if sharedata.stackedbardraw: # do not count the start bar when not gets painted
1449 sharedata.stackedbar += 1
1451 def drawpoint(self, privatedata, sharedata, graph, point):
1452 sharedata.vbarrange[sharedata.barvalueindex][0] = sharedata.vbarrange[sharedata.barvalueindex][1]
1453 if self.addontop:
1454 try:
1455 sharedata.lastbarvalue += point[self.stackname]
1456 except (ArithmeticError, ValueError, TypeError):
1457 sharedata.lastbarvalue = None
1458 else:
1459 sharedata.lastbarvalue = point[self.stackname]
1460 try:
1461 sharedata.vpos[sharedata.barvalueindex] = sharedata.vbarrange[sharedata.barvalueindex][1] = graph.axes[sharedata.barposcolumnnames[sharedata.barvalueindex]].convert(sharedata.lastbarvalue)
1462 except (ArithmeticError, ValueError, TypeError):
1463 sharedata.vpos[sharedata.barvalueindex] = sharedata.vbarrange[sharedata.barvalueindex][1] = None
1464 sharedata.vposavailable = sharedata.vposvalid = 0
1465 else:
1466 if not sharedata.vposavailable or not sharedata.vposvalid:
1467 sharedata.vposavailable = sharedata.vposvalid = 1
1468 for v in sharedata.vpos:
1469 if v is None:
1470 sharedata.vposavailable = sharedata.vposvalid = 0
1471 break
1472 if v < -self.epsilon or v > 1+self.epsilon:
1473 sharedata.vposvalid = 0
1476 class bar(_style):
1478 needsdata = ["vbarrange"]
1480 defaultbarattrs = [color.gradient.Rainbow, deco.stroked([color.grey.black])]
1482 def __init__(self, barattrs=[], epsilon=1e-10, gradient=color.gradient.RedBlack):
1483 self.barattrs = barattrs
1484 self.epsilon = epsilon
1485 self.gradient = gradient
1487 def lighting(self, angle, zindex):
1488 return self.gradient.getcolor(0.7-0.4*abs(angle)+0.1*zindex)
1490 def columnnames(self, privatedata, sharedata, graph, columnnames):
1491 return []
1493 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1494 privatedata.barattrs = attr.selectattrs(self.defaultbarattrs + self.barattrs, selectindex, selecttotal)
1496 def initdrawpoints(self, privatedata, sharedata, graph):
1497 privatedata.barcanvas = graph.insert(canvas.canvas())
1498 sharedata.stackedbardraw = 1
1499 privatedata.stackedbar = sharedata.stackedbar
1500 privatedata.todraw = []
1502 def drawpointfill(self, privatedata, p):
1503 if p:
1504 privatedata.barcanvas.fill(p, privatedata.barattrs)
1506 def drawpoint(self, privatedata, sharedata, graph, point):
1507 vbarrange = []
1508 for vmin, vmax in sharedata.vbarrange:
1509 if vmin is None or vmax is None:
1510 self.drawpointfill(privatedata, None)
1511 return
1512 if vmin > vmax:
1513 vmin, vmax = vmax, vmin
1514 if vmin > 1 or vmax < 0:
1515 self.drawpointfill(privatedata, None)
1516 return
1517 if vmin < 0:
1518 vmin = 0
1519 if vmax > 1:
1520 vmax = 1
1521 vbarrange.append((vmin, vmax))
1522 if len(vbarrange) == 2:
1523 p = graph.vgeodesic(vbarrange[0][0], vbarrange[1][0], vbarrange[0][1], vbarrange[1][0])
1524 p.append(graph.vgeodesic_el(vbarrange[0][1], vbarrange[1][0], vbarrange[0][1], vbarrange[1][1]))
1525 p.append(graph.vgeodesic_el(vbarrange[0][1], vbarrange[1][1], vbarrange[0][0], vbarrange[1][1]))
1526 p.append(graph.vgeodesic_el(vbarrange[0][0], vbarrange[1][1], vbarrange[0][0], vbarrange[1][0]))
1527 p.append(path.closepath())
1528 self.drawpointfill(privatedata, p)
1529 elif len(vbarrange) == 3:
1530 planes = []
1531 if abs(vbarrange[0][0] - vbarrange[0][1]) > self.epsilon and abs(vbarrange[1][0] - vbarrange[1][1]):
1532 planes.append((vbarrange[0][0], vbarrange[1][0], vbarrange[2][0],
1533 vbarrange[0][1], vbarrange[1][0], vbarrange[2][0],
1534 vbarrange[0][1], vbarrange[1][1], vbarrange[2][0],
1535 vbarrange[0][0], vbarrange[1][1], vbarrange[2][0]))
1536 planes.append((vbarrange[0][0], vbarrange[1][0], vbarrange[2][1],
1537 vbarrange[0][0], vbarrange[1][1], vbarrange[2][1],
1538 vbarrange[0][1], vbarrange[1][1], vbarrange[2][1],
1539 vbarrange[0][1], vbarrange[1][0], vbarrange[2][1]))
1540 if abs(vbarrange[0][0] - vbarrange[0][1]) > self.epsilon and abs(vbarrange[2][0] - vbarrange[2][1]):
1541 planes.append((vbarrange[0][0], vbarrange[1][0], vbarrange[2][0],
1542 vbarrange[0][0], vbarrange[1][0], vbarrange[2][1],
1543 vbarrange[0][1], vbarrange[1][0], vbarrange[2][1],
1544 vbarrange[0][1], vbarrange[1][0], vbarrange[2][0]))
1545 planes.append((vbarrange[0][0], vbarrange[1][1], vbarrange[2][0],
1546 vbarrange[0][1], vbarrange[1][1], vbarrange[2][0],
1547 vbarrange[0][1], vbarrange[1][1], vbarrange[2][1],
1548 vbarrange[0][0], vbarrange[1][1], vbarrange[2][1]))
1549 if abs(vbarrange[1][0] - vbarrange[1][1]) > self.epsilon and abs(vbarrange[2][0] - vbarrange[2][1]):
1550 planes.append((vbarrange[0][0], vbarrange[1][0], vbarrange[2][0],
1551 vbarrange[0][0], vbarrange[1][1], vbarrange[2][0],
1552 vbarrange[0][0], vbarrange[1][1], vbarrange[2][1],
1553 vbarrange[0][0], vbarrange[1][0], vbarrange[2][1]))
1554 planes.append((vbarrange[0][1], vbarrange[1][0], vbarrange[2][0],
1555 vbarrange[0][1], vbarrange[1][0], vbarrange[2][1],
1556 vbarrange[0][1], vbarrange[1][1], vbarrange[2][1],
1557 vbarrange[0][1], vbarrange[1][1], vbarrange[2][0]))
1558 v = [0.5 * (vbarrange[0][0] + vbarrange[0][1]),
1559 0.5 * (vbarrange[1][0] + vbarrange[1][1]),
1560 0.5 * (vbarrange[2][0] + vbarrange[2][1])]
1561 v[sharedata.barvalueindex] = 0.5
1562 zindex = graph.vzindex(*v)
1563 for v11, v12, v13, v21, v22, v23, v31, v32, v33, v41, v42, v43 in planes:
1564 angle = graph.vangle(v11, v12, v13, v21, v22, v23, v41, v42, v43)
1565 if angle > 0:
1566 p = graph.vgeodesic(v11, v12, v13, v21, v22, v23)
1567 p.append(graph.vgeodesic_el(v21, v22, v23, v31, v32, v33))
1568 p.append(graph.vgeodesic_el(v31, v32, v33, v41, v42, v43))
1569 p.append(graph.vgeodesic_el(v41, v42, v43, v11, v12, v13))
1570 p.append(path.closepath())
1571 if self.gradient:
1572 privatedata.todraw.append((-zindex, p, privatedata.barattrs + [self.lighting(angle, zindex)]))
1573 else:
1574 privatedata.todraw.append((-zindex, p, privatedata.barattrs))
1575 else:
1576 raise TypeError("bar style restricted to two- and three dimensional graphs")
1578 def donedrawpoints(self, privatedata, sharedata, graph):
1579 privatedata.todraw.sort()
1580 for vzindex, p, a in privatedata.todraw:
1581 privatedata.barcanvas.fill(p, a)
1583 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
1584 selectindex = privatedata.stackedbar
1585 selecttotal = sharedata.stackedbar + 1
1586 graph.fill(path.rect_pt(x_pt + width_pt*selectindex/float(selecttotal), y_pt, width_pt/float(selecttotal), height_pt), privatedata.barattrs)
1589 class changebar(bar):
1591 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1592 if selecttotal != 1:
1593 raise RuntimeError("Changebar can't change its appearance. Thus you can't use it to plot several bars side by side on a subaxis.")
1595 def initdrawpoints(self, privatedata, sharedata, graph):
1596 if len(graph.axesnames) != 2:
1597 raise TypeError("changebar style restricted on two-dimensional graphs (at least for the moment)")
1598 bar.initdrawpoints(self, privatedata, sharedata, graph)
1599 privatedata.bars = []
1601 def drawpointfill(self, privatedata, p):
1602 privatedata.bars.append(p)
1604 def donedrawpoints(self, privatedata, sharedata, graph):
1605 selecttotal = len(privatedata.bars)
1606 for selectindex, p in enumerate(privatedata.bars):
1607 if p:
1608 barattrs = attr.selectattrs(self.defaultbarattrs + self.barattrs, selectindex, selecttotal)
1609 privatedata.barcanvas.fill(p, barattrs)
1611 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
1612 raise RuntimeError("Style currently doesn't provide a graph key")
1615 class gridpos(_style):
1617 needsdata = ["vpos", "vposmissing", "vposavailable", "vposvalid"]
1618 providesdata = ["values1", "values2", "data12", "data21", "index1", "index2"]
1620 def __init__(self, index1=0, index2=1, epsilon=1e-10):
1621 self.index1 = index1
1622 self.index2 = index2
1623 self.epsilon = epsilon
1625 def initdrawpoints(self, privatedata, sharedata, graph):
1626 sharedata.index1 = self.index1
1627 sharedata.index2 = self.index2
1628 sharedata.values1 = {}
1629 sharedata.values2 = {}
1630 sharedata.data12 = {}
1631 sharedata.data21 = {}
1633 def drawpoint(self, privatedata, sharedata, graph, point):
1634 if sharedata.vposavailable:
1635 sharedata.value1 = sharedata.vpos[self.index1]
1636 sharedata.value2 = sharedata.vpos[self.index2]
1637 if not sharedata.values1.has_key(sharedata.value1):
1638 for hasvalue in sharedata.values1.keys():
1639 if hasvalue - self.epsilon <= sharedata.value1 <= hasvalue + self.epsilon:
1640 sharedata.value1 = hasvalue
1641 break
1642 else:
1643 sharedata.values1[sharedata.value1] = 1
1644 if not sharedata.values2.has_key(sharedata.value2):
1645 for hasvalue in sharedata.values2.keys():
1646 if hasvalue - self.epsilon <= sharedata.value2 <= hasvalue + self.epsilon:
1647 sharedata.value2 = hasvalue
1648 break
1649 else:
1650 sharedata.values2[sharedata.value2] = 1
1651 data = sharedata.vposavailable, sharedata.vposvalid, sharedata.vpos[:]
1652 sharedata.data12.setdefault(sharedata.value1, {})[sharedata.value2] = data
1653 sharedata.data21.setdefault(sharedata.value2, {})[sharedata.value1] = data
1655 registerdefaultprovider(gridpos(), gridpos.providesdata)
1658 class grid(_line, _style):
1660 needsdata = ["values1", "values2", "data12", "data21"]
1662 defaultgridattrs = [line.changelinestyle]
1664 def __init__(self, gridlines1=1, gridlines2=1, gridattrs=[]):
1665 self.gridlines1 = gridlines1
1666 self.gridlines2 = gridlines2
1667 self.gridattrs = gridattrs
1669 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1670 if self.gridattrs is not None:
1671 privatedata.gridattrs = attr.selectattrs(self.defaultgridattrs + self.gridattrs, selectindex, selecttotal)
1672 else:
1673 privatedata.gridattrs = None
1675 def donedrawpoints(self, privatedata, sharedata, graph):
1676 values1 = sharedata.values1.keys()
1677 values1.sort()
1678 values2 = sharedata.values2.keys()
1679 values2.sort()
1680 if self.gridlines1:
1681 for value2 in values2:
1682 data1 = sharedata.data21[value2]
1683 self.initpointstopath(privatedata)
1684 for value1 in values1:
1685 try:
1686 data = data1[value1]
1687 except KeyError:
1688 self.addinvalid(privatedata)
1689 else:
1690 self.addpoint(privatedata, graph.vpos_pt, *data)
1691 p = self.donepointstopath(privatedata)
1692 if len(p):
1693 graph.stroke(p, privatedata.gridattrs)
1694 if self.gridlines2:
1695 for value1 in values1:
1696 data2 = sharedata.data12[value1]
1697 self.initpointstopath(privatedata)
1698 for value2 in values2:
1699 try:
1700 data = data2[value2]
1701 except KeyError:
1702 self.addinvalid(privatedata)
1703 else:
1704 self.addpoint(privatedata, graph.vpos_pt, *data)
1705 p = self.donepointstopath(privatedata)
1706 if len(p):
1707 graph.stroke(p, privatedata.gridattrs)
1710 class surface(_style):
1712 needsdata = ["values1", "values2", "data12", "data21"]
1714 def __init__(self, colorname="color", gradient=color.gradient.Grey, mincolor=None, maxcolor=None,
1715 gridlines1=0.05, gridlines2=0.05, gridcolor=None,
1716 backcolor=color.gray.black):
1717 self.colorname = colorname
1718 self.gradient = gradient
1719 self.mincolor = mincolor
1720 self.maxcolor = maxcolor
1721 self.gridlines1 = gridlines1
1722 self.gridlines2 = gridlines2
1723 self.gridcolor = gridcolor
1724 self.backcolor = backcolor
1726 colorspacestring = gradient.getcolor(0).colorspacestring()
1727 if self.gridcolor is not None and self.gridcolor.colorspacestring() != colorspacestring:
1728 raise RuntimeError("colorspace mismatch (gradient/grid)")
1729 if self.backcolor is not None and self.backcolor.colorspacestring() != colorspacestring:
1730 raise RuntimeError("colorspace mismatch (gradient/back)")
1732 def midvalue(self, v1, v2, v3, v4):
1733 return [0.25*sum(values) for values in zip(v1, v2, v3, v4)]
1735 def midcolor(self, c1, c2, c3, c4):
1736 return 0.25*(c1+c2+c3+c4)
1738 def lighting(self, angle, zindex):
1739 if angle < 0 and self.backcolor is not None:
1740 return self.backcolor
1741 return self.gradient.getcolor(0.7-0.4*abs(angle)+0.1*zindex)
1743 def columnnames(self, privatedata, sharedata, graph, columnnames):
1744 privatedata.colorize = self.colorname in columnnames
1745 if privatedata.colorize:
1746 return [self.colorname]
1747 return []
1749 def initdrawpoints(self, privatedata, sharedata, graph):
1750 privatedata.colors = {}
1751 privatedata.mincolor = privatedata.maxcolor = None
1753 def drawpoint(self, privatedata, sharedata, graph, point):
1754 if privatedata.colorize:
1755 try:
1756 color = point[self.colorname] + 0
1757 except:
1758 pass
1759 else:
1760 privatedata.colors.setdefault(sharedata.value1, {})[sharedata.value2] = color
1761 if privatedata.mincolor is None or color < privatedata.mincolor:
1762 privatedata.mincolor = color
1763 if privatedata.mincolor is None or privatedata.maxcolor < color:
1764 privatedata.maxcolor = color
1766 def donedrawpoints(self, privatedata, sharedata, graph):
1767 v1 = [0]*len(graph.axesnames)
1768 v2 = [0]*len(graph.axesnames)
1769 v3 = [0]*len(graph.axesnames)
1770 v4 = [0]*len(graph.axesnames)
1771 v1[sharedata.index2] = 0.5
1772 v2[sharedata.index1] = 0.5
1773 v3[sharedata.index1] = 0.5
1774 v3[sharedata.index2] = 1
1775 v4[sharedata.index1] = 1
1776 v4[sharedata.index2] = 0.5
1777 sortElements = [-graph.vzindex(*v1),
1778 -graph.vzindex(*v2),
1779 -graph.vzindex(*v3),
1780 -graph.vzindex(*v4)]
1782 values1 = sharedata.values1.keys()
1783 values1.sort()
1784 v1 = [0]*len(graph.axesnames)
1785 v2 = [0]*len(graph.axesnames)
1786 v1[sharedata.index1] = -1
1787 v2[sharedata.index1] = 1
1788 sign = 1
1789 if graph.vzindex(*v1) < graph.vzindex(*v2):
1790 values1.reverse()
1791 sign *= -1
1792 sortElements = [sortElements[3], sortElements[1], sortElements[2], sortElements[0]]
1794 values2 = sharedata.values2.keys()
1795 values2.sort()
1796 v1 = [0]*len(graph.axesnames)
1797 v2 = [0]*len(graph.axesnames)
1798 v1[sharedata.index2] = -1
1799 v2[sharedata.index2] = 1
1800 if graph.vzindex(*v1) < graph.vzindex(*v2):
1801 values2.reverse()
1802 sign *= -1
1803 sortElements = [sortElements[0], sortElements[2], sortElements[1], sortElements[3]]
1805 sortElements = [(zindex, i) for i, zindex in enumerate(sortElements)]
1806 sortElements.sort()
1808 mincolor, maxcolor = privatedata.mincolor, privatedata.maxcolor
1809 if self.mincolor is not None:
1810 mincolor = self.mincolor
1811 if self.maxcolor is not None:
1812 maxcolor = self.maxcolor
1813 nodes = []
1814 elements = []
1815 for value1a, value1b in zip(values1[:-1], values1[1:]):
1816 for value2a, value2b in zip(values2[:-1], values2[1:]):
1817 try:
1818 available1, valid1, v1 = sharedata.data12[value1a][value2a]
1819 available2, valid2, v2 = sharedata.data12[value1a][value2b]
1820 available3, valid3, v3 = sharedata.data12[value1b][value2a]
1821 available4, valid4, v4 = sharedata.data12[value1b][value2b]
1822 except KeyError:
1823 continue
1824 if not available1 or not available2 or not available3 or not available4:
1825 continue
1826 if not valid1 or not valid2 or not valid3 or not valid4:
1827 warnings.warn("surface elements partially outside of the graph are (currently) skipped completely")
1828 continue
1829 def shrink(index, v1, v2, by):
1830 v1 = v1[:]
1831 v2 = v2[:]
1832 for i in builtinrange(3):
1833 if i != index:
1834 v1[i], v2[i] = v1[i] + by*(v2[i]-v1[i]), v2[i] + by*(v1[i]-v2[i])
1835 return v1, v2
1836 v1f, v2f, v3f, v4f = v1, v2, v3, v4
1837 if self.gridcolor is not None and self.gridlines1:
1838 v1, v2 = shrink(sharedata.index1, v1, v2, self.gridlines1)
1839 v3, v4 = shrink(sharedata.index1, v3, v4, self.gridlines1)
1840 if self.gridcolor is not None and self.gridlines2:
1841 v1, v3 = shrink(sharedata.index2, v1, v3, self.gridlines2)
1842 v2, v4 = shrink(sharedata.index2, v2, v4, self.gridlines2)
1843 v5 = self.midvalue(v1, v2, v3, v4)
1844 x1_pt, y1_pt = graph.vpos_pt(*v1)
1845 x2_pt, y2_pt = graph.vpos_pt(*v2)
1846 x3_pt, y3_pt = graph.vpos_pt(*v3)
1847 x4_pt, y4_pt = graph.vpos_pt(*v4)
1848 x5_pt, y5_pt = graph.vpos_pt(*v5)
1849 if privatedata.colorize:
1850 def colorfromgradient(c):
1851 vc = (c - mincolor) / float(maxcolor - mincolor)
1852 if vc < 0:
1853 warnings.warn("gradiend color range is exceeded due to mincolor setting")
1854 vc = 0
1855 if vc > 1:
1856 warnings.warn("gradiend color range is exceeded due to maxcolor setting")
1857 vc = 1
1858 return self.gradient.getcolor(vc)
1859 c1 = privatedata.colors[value1a][value2a]
1860 c2 = privatedata.colors[value1a][value2b]
1861 c3 = privatedata.colors[value1b][value2a]
1862 c4 = privatedata.colors[value1b][value2b]
1863 c5 = self.midcolor(c1, c2, c3, c4)
1864 c1a = c1b = colorfromgradient(c1)
1865 c2a = c2c = colorfromgradient(c2)
1866 c3b = c3d = colorfromgradient(c3)
1867 c4c = c4d = colorfromgradient(c4)
1868 c5a = c5b = c5c = c5d = colorfromgradient(c5)
1869 if self.backcolor is not None and sign*graph.vangle(*(v1+v2+v5)) < 0:
1870 c1a = c2a = c5a = self.backcolor
1871 if self.backcolor is not None and sign*graph.vangle(*(v3+v1+v5)) < 0:
1872 c3b = c1b = c5b = self.backcolor
1873 if self.backcolor is not None and sign*graph.vangle(*(v2+v4+v5)) < 0:
1874 c2c = c4c = c5c = self.backcolor
1875 if self.backcolor is not None and sign*graph.vangle(*(v4+v3+v5)) < 0:
1876 c4d = c3d = c5d = self.backcolor
1877 else:
1878 zindex = graph.vzindex(*v5)
1879 c1a = c2a = c5a = self.lighting(sign*graph.vangle(*(v1+v2+v5)), zindex)
1880 c3b = c1b = c5b = self.lighting(sign*graph.vangle(*(v3+v1+v5)), zindex)
1881 c2c = c4c = c5c = self.lighting(sign*graph.vangle(*(v2+v4+v5)), zindex)
1882 c4d = c3d = c5d = self.lighting(sign*graph.vangle(*(v4+v3+v5)), zindex)
1883 for zindex, i in sortElements:
1884 if i == 0:
1885 elements.append(mesh.element((mesh.node_pt((x1_pt, y1_pt), c1a),
1886 mesh.node_pt((x2_pt, y2_pt), c2a),
1887 mesh.node_pt((x5_pt, y5_pt), c5a))))
1888 if self.gridcolor is not None and self.gridlines2:
1889 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v1f), self.gridcolor),
1890 mesh.node_pt(graph.vpos_pt(*v2), self.gridcolor),
1891 mesh.node_pt(graph.vpos_pt(*v1), self.gridcolor))))
1892 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v1f), self.gridcolor),
1893 mesh.node_pt(graph.vpos_pt(*v2), self.gridcolor),
1894 mesh.node_pt(graph.vpos_pt(*v2f), self.gridcolor))))
1895 elif i == 1:
1896 elements.append(mesh.element((mesh.node_pt((x3_pt, y3_pt), c3b),
1897 mesh.node_pt((x1_pt, y1_pt), c1b),
1898 mesh.node_pt((x5_pt, y5_pt), c5b))))
1899 if self.gridcolor is not None and self.gridlines1:
1900 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v1f), self.gridcolor),
1901 mesh.node_pt(graph.vpos_pt(*v3), self.gridcolor),
1902 mesh.node_pt(graph.vpos_pt(*v1), self.gridcolor))))
1903 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v1f), self.gridcolor),
1904 mesh.node_pt(graph.vpos_pt(*v3), self.gridcolor),
1905 mesh.node_pt(graph.vpos_pt(*v3f), self.gridcolor))))
1906 elif i == 2:
1907 elements.append(mesh.element((mesh.node_pt((x2_pt, y2_pt), c2c),
1908 mesh.node_pt((x4_pt, y4_pt), c4c),
1909 mesh.node_pt((x5_pt, y5_pt), c5c))))
1910 if self.gridcolor is not None and self.gridlines1:
1911 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v2f), self.gridcolor),
1912 mesh.node_pt(graph.vpos_pt(*v4), self.gridcolor),
1913 mesh.node_pt(graph.vpos_pt(*v2), self.gridcolor))))
1914 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v2f), self.gridcolor),
1915 mesh.node_pt(graph.vpos_pt(*v4), self.gridcolor),
1916 mesh.node_pt(graph.vpos_pt(*v4f), self.gridcolor))))
1917 elif i == 3:
1918 elements.append(mesh.element((mesh.node_pt((x4_pt, y4_pt), c4d),
1919 mesh.node_pt((x3_pt, y3_pt), c3d),
1920 mesh.node_pt((x5_pt, y5_pt), c5d))))
1921 if self.gridcolor is not None and self.gridlines2:
1922 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v3f), self.gridcolor),
1923 mesh.node_pt(graph.vpos_pt(*v4), self.gridcolor),
1924 mesh.node_pt(graph.vpos_pt(*v3), self.gridcolor))))
1925 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v3f), self.gridcolor),
1926 mesh.node_pt(graph.vpos_pt(*v4), self.gridcolor),
1927 mesh.node_pt(graph.vpos_pt(*v4f), self.gridcolor))))
1928 m = mesh.mesh(elements, check=0)
1929 graph.insert(m)