graph.style.arrow: decorator is an parameter of the constructor now reverting an...
[PyX/mjg.git] / pyx / graph / style.py
blob607bdc235b32374c6a3e1916e9dcb1cf2fea4eb2
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, decorator=deco.earrow):
873 self.linelength = linelength
874 self.arrowsize = arrowsize
875 self.lineattrs = lineattrs
876 self.arrowattrs = arrowattrs
877 self.arrowpos = arrowpos
878 self.epsilon = epsilon
879 self.decorator = decorator
881 def columnnames(self, privatedata, sharedata, graph, columnnames):
882 if len(graph.axesnames) != 2:
883 raise ValueError("arrow style restricted on two-dimensional graphs")
884 if "size" not in columnnames:
885 raise ValueError("size missing")
886 if "angle" not in columnnames:
887 raise ValueError("angle missing")
888 return ["size", "angle"] + _styleneedingpointpos.columnnames(self, privatedata, sharedata, graph, columnnames)
890 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
891 if self.lineattrs is not None:
892 privatedata.lineattrs = attr.selectattrs(self.defaultlineattrs + self.lineattrs, selectindex, selecttotal)
893 else:
894 privatedata.lineattrs = None
895 if self.arrowattrs is not None:
896 privatedata.arrowattrs = attr.selectattrs(self.defaultarrowattrs + self.arrowattrs, selectindex, selecttotal)
897 else:
898 privatedata.arrowattrs = None
900 def initdrawpoints(self, privatedata, sharedata, graph):
901 privatedata.arrowcanvas = canvas.canvas()
903 def drawpoint(self, privatedata, sharedata, graph, point):
904 if privatedata.lineattrs is not None and privatedata.arrowattrs is not None and sharedata.vposvalid:
905 linelength_pt = unit.topt(self.linelength)
906 x_pt, y_pt = graph.vpos_pt(*sharedata.vpos)
907 try:
908 angle = point["angle"] + 0.0
909 size = point["size"] + 0.0
910 except:
911 pass
912 else:
913 if point["size"] > self.epsilon:
914 dx = math.cos(angle*math.pi/180)
915 dy = math.sin(angle*math.pi/180)
916 x1 = x_pt-self.arrowpos*dx*linelength_pt*size
917 y1 = y_pt-self.arrowpos*dy*linelength_pt*size
918 x2 = x_pt+(1-self.arrowpos)*dx*linelength_pt*size
919 y2 = y_pt+(1-self.arrowpos)*dy*linelength_pt*size
920 if self.decorator:
921 privatedata.arrowcanvas.stroke(path.line_pt(x1, y1, x2, y2),
922 privatedata.lineattrs+[self.decorator(privatedata.arrowattrs, size=self.arrowsize*size)])
923 else:
924 privatedata.arrowcanvas.stroke(path.line_pt(x1, y1, x2, y2), privatedata.lineattrs)
926 def donedrawpoints(self, privatedata, sharedata, graph):
927 graph.insert(privatedata.arrowcanvas)
929 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
930 raise RuntimeError("Style currently doesn't provide a graph key")
933 class rect(_style):
935 needsdata = ["vrange", "vrangeminmissing", "vrangemaxmissing"]
937 def __init__(self, gradient=color.gradient.Grey):
938 self.gradient = gradient
940 def columnnames(self, privatedata, sharedata, graph, columnnames):
941 if len(graph.axesnames) != 2:
942 raise TypeError("arrow style restricted on two-dimensional graphs")
943 if "color" not in columnnames:
944 raise ValueError("color missing")
945 if len(sharedata.vrangeminmissing) + len(sharedata.vrangemaxmissing):
946 raise ValueError("incomplete range")
947 return ["color"]
949 def initdrawpoints(self, privatedata, sharedata, graph):
950 privatedata.rectcanvas = graph.insert(canvas.canvas())
952 def drawpoint(self, privatedata, sharedata, graph, point):
953 xvmin = sharedata.vrange[0][0]
954 xvmax = sharedata.vrange[0][1]
955 yvmin = sharedata.vrange[1][0]
956 yvmax = sharedata.vrange[1][1]
957 if (xvmin is not None and xvmin < 1 and
958 xvmax is not None and xvmax > 0 and
959 yvmin is not None and yvmin < 1 and
960 yvmax is not None and yvmax > 0):
961 if xvmin < 0:
962 xvmin = 0
963 elif xvmax > 1:
964 xvmax = 1
965 if yvmin < 0:
966 yvmin = 0
967 elif yvmax > 1:
968 yvmax = 1
969 p = graph.vgeodesic(xvmin, yvmin, xvmax, yvmin)
970 p.append(graph.vgeodesic_el(xvmax, yvmin, xvmax, yvmax))
971 p.append(graph.vgeodesic_el(xvmax, yvmax, xvmin, yvmax))
972 p.append(graph.vgeodesic_el(xvmin, yvmax, xvmin, yvmin))
973 p.append(path.closepath())
974 privatedata.rectcanvas.fill(p, [self.gradient.getcolor(point["color"])])
976 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
977 raise RuntimeError("Style currently doesn't provide a graph key")
980 class histogram(_style):
982 needsdata = ["vpos", "vposmissing", "vrange", "vrangeminmissing", "vrangemaxmissing"]
984 defaultlineattrs = [deco.stroked]
985 defaultfrompathattrs = []
987 def __init__(self, lineattrs=[], steps=0, fromvalue=0, frompathattrs=[], fillable=0, rectkey=0,
988 autohistogramaxisindex=0, autohistogrampointpos=0.5, epsilon=1e-10):
989 self.lineattrs = lineattrs
990 self.steps = steps
991 self.fromvalue = fromvalue
992 self.frompathattrs = frompathattrs
993 self.fillable = fillable # TODO: fillable paths might not properly be closed by straight lines on curved graph geometries
994 self.rectkey = rectkey
995 self.autohistogramaxisindex = autohistogramaxisindex
996 self.autohistogrampointpos = autohistogrampointpos
997 self.epsilon = epsilon
999 def columnnames(self, privatedata, sharedata, graph, columnnames):
1000 if len(graph.axesnames) != 2:
1001 raise TypeError("histogram style restricted on two-dimensional graphs")
1002 privatedata.rangeaxisindex = None
1003 for i in builtinrange(len(graph.axesnames)):
1004 if i in sharedata.vrangeminmissing or i in sharedata.vrangemaxmissing:
1005 if i in sharedata.vposmissing:
1006 raise ValueError("pos and range missing")
1007 else:
1008 if privatedata.rangeaxisindex is not None:
1009 raise ValueError("multiple ranges")
1010 privatedata.rangeaxisindex = i
1011 if privatedata.rangeaxisindex is None:
1012 privatedata.rangeaxisindex = self.autohistogramaxisindex
1013 privatedata.autohistogram = 1
1014 else:
1015 privatedata.autohistogram = 0
1016 return []
1018 def adjustaxis(self, privatedata, sharedata, graph, columnname, data):
1019 if privatedata.autohistogram and columnname == sharedata.poscolumnnames[privatedata.rangeaxisindex]:
1020 if len(data) == 1:
1021 raise ValueError("several data points needed for automatic histogram width calculation")
1022 if len(data) > 1:
1023 delta = data[1] - data[0]
1024 min = data[0] - self.autohistogrampointpos * delta
1025 max = data[-1] + (1-self.autohistogrampointpos) * delta
1026 graph.axes[columnname].adjustaxis([min, max])
1027 elif self.fromvalue is not None and columnname == sharedata.poscolumnnames[1-privatedata.rangeaxisindex]:
1028 graph.axes[columnname].adjustaxis([self.fromvalue])
1030 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1031 privatedata.insertfrompath = selectindex == 0
1032 if self.lineattrs is not None:
1033 privatedata.lineattrs = attr.selectattrs(self.defaultlineattrs + self.lineattrs, selectindex, selecttotal)
1034 else:
1035 privatedata.lineattrs = None
1037 def vmoveto(self, privatedata, sharedata, graph, vpos, vvalue):
1038 if -self.epsilon < vpos < 1+self.epsilon and -self.epsilon < vvalue < 1+self.epsilon:
1039 if privatedata.rangeaxisindex:
1040 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue, vpos)))
1041 else:
1042 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vpos, vvalue)))
1044 def vposline(self, privatedata, sharedata, graph, vpos, vvalue1, vvalue2):
1045 if -self.epsilon < vpos < 1+self.epsilon:
1046 vvalue1cut = 0
1047 if vvalue1 < 0:
1048 vvalue1 = 0
1049 vvalue1cut = -1
1050 elif vvalue1 > 1:
1051 vvalue1 = 1
1052 vvalue1cut = 1
1053 vvalue2cut = 0
1054 if vvalue2 < 0:
1055 vvalue2 = 0
1056 vvalue2cut = -1
1057 elif vvalue2 > 1:
1058 vvalue2 = 1
1059 vvalue2cut = 1
1060 if abs(vvalue1cut + vvalue2cut) <= 1:
1061 if vvalue1cut and not self.fillable:
1062 if privatedata.rangeaxisindex:
1063 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue1, vpos)))
1064 else:
1065 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vpos, vvalue1)))
1066 if privatedata.rangeaxisindex:
1067 privatedata.path.append(graph.vgeodesic_el(vvalue1, vpos, vvalue2, vpos))
1068 else:
1069 privatedata.path.append(graph.vgeodesic_el(vpos, vvalue1, vpos, vvalue2))
1071 def vvalueline(self, privatedata, sharedata, graph, vvalue, vpos1, vpos2):
1072 if self.fillable:
1073 if vvalue < -self.epsilon:
1074 vvalue = 0
1075 warnings.warn("cut at graph boundary adds artificial lines to fillable step histogram path")
1076 if vvalue > 1+self.epsilon:
1077 vvalue = 1
1078 warnings.warn("cut at graph boundary adds artificial lines to fillable step histogram path")
1079 if self.fillable or (-self.epsilon < vvalue < 1+self.epsilon):
1080 vpos1cut = 0
1081 if vpos1 < 0:
1082 vpos1 = 0
1083 vpos1cut = -1
1084 elif vpos1 > 1:
1085 vpos1 = 1
1086 vpos1cut = 1
1087 vpos2cut = 0
1088 if vpos2 < 0:
1089 vpos2 = 0
1090 vpos2cut = -1
1091 elif vpos2 > 1:
1092 vpos2 = 1
1093 vpos2cut = 1
1094 if abs(vpos1cut + vpos2cut) <= 1:
1095 if vpos1cut:
1096 if self.fillable:
1097 if privatedata.rangeaxisindex:
1098 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(privatedata.vfromvalue, vpos1)))
1099 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vpos1, vvalue, vpos1))
1100 else:
1101 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vpos1, privatedata.vfromvalue)))
1102 privatedata.path.append(graph.vgeodesic_el(vpos1, privatedata.vfromvalue, vpos1, vvalue))
1103 else:
1104 if privatedata.rangeaxisindex:
1105 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue, vpos1)))
1106 else:
1107 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vpos1, vvalue)))
1108 if privatedata.rangeaxisindex:
1109 privatedata.path.append(graph.vgeodesic_el(vvalue, vpos1, vvalue, vpos2))
1110 else:
1111 privatedata.path.append(graph.vgeodesic_el(vpos1, vvalue, vpos2, vvalue))
1112 if self.fillable and vpos2cut:
1113 warnings.warn("cut at graph boundary adds artificial lines to fillable step histogram path")
1114 if privatedata.rangeaxisindex:
1115 privatedata.path.append(graph.vgeodesic_el(vvalue, vpos2, privatedata.vfromvalue, vpos2))
1116 else:
1117 privatedata.path.append(graph.vgeodesic_el(vpos2, vvalue, vpos2, privatedata.vfromvalue))
1119 def drawvalue(self, privatedata, sharedata, graph, vmin, vmax, vvalue):
1120 currentvalid = vmin is not None and vmax is not None and vvalue is not None
1121 if self.fillable and not self.steps:
1122 if not currentvalid:
1123 return
1124 vmincut = 0
1125 if vmin < -self.epsilon:
1126 vmin = 0
1127 vmincut = -1
1128 elif vmin > 1+self.epsilon:
1129 vmin = 1
1130 vmincut = 1
1131 vmaxcut = 0
1132 if vmax < -self.epsilon:
1133 vmax = 0
1134 vmaxcut = -1
1135 if vmax > 1+self.epsilon:
1136 vmax = 1
1137 vmaxcut = 1
1138 vvaluecut = 0
1139 if vvalue < -self.epsilon:
1140 vvalue = 0
1141 vvaluecut = -1
1142 if vvalue > 1+self.epsilon:
1143 vvalue = 1
1144 vvaluecut = 1
1145 done = 0
1146 if abs(vmincut) + abs(vmaxcut) + abs(vvaluecut) + abs(privatedata.vfromvaluecut) > 1:
1147 if abs(vmincut + vmaxcut) > 1 or abs(vvaluecut+privatedata.vfromvaluecut) > 1:
1148 done = 1
1149 else:
1150 warnings.warn("multiple cuts at graph boundary add artificial lines to fillable rectangle histogram path")
1151 elif vmincut:
1152 done = 1
1153 if privatedata.rangeaxisindex:
1154 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(privatedata.vfromvalue, vmin)))
1155 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmin, privatedata.vfromvalue, vmax))
1156 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmax, vvalue, vmax))
1157 privatedata.path.append(graph.vgeodesic_el(vvalue, vmax, vvalue, vmin))
1158 else:
1159 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmin, privatedata.vfromvalue)))
1160 privatedata.path.append(graph.vgeodesic_el(vmin, privatedata.vfromvalue, vmax, privatedata.vfromvalue))
1161 privatedata.path.append(graph.vgeodesic_el(vmax, privatedata.vfromvalue, vmax, vvalue))
1162 privatedata.path.append(graph.vgeodesic_el(vmax, vvalue, vmin, vvalue))
1163 elif vmaxcut:
1164 done = 1
1165 if privatedata.rangeaxisindex:
1166 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue, vmax)))
1167 privatedata.path.append(graph.vgeodesic_el(vvalue, vmax, vvalue, vmin))
1168 privatedata.path.append(graph.vgeodesic_el(vvalue, vmin, privatedata.vfromvalue, vmin))
1169 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmin, privatedata.vfromvalue, vmax))
1170 else:
1171 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmax, vvalue)))
1172 privatedata.path.append(graph.vgeodesic_el(vmax, vvalue, vmin, vvalue))
1173 privatedata.path.append(graph.vgeodesic_el(vmin, vvalue, vmin, privatedata.vfromvalue))
1174 privatedata.path.append(graph.vgeodesic_el(vmin, privatedata.vfromvalue, vmax, privatedata.vfromvalue))
1175 elif privatedata.vfromvaluecut:
1176 done = 1
1177 if privatedata.rangeaxisindex:
1178 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(privatedata.vfromvalue, vmax)))
1179 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmax, vvalue, vmax))
1180 privatedata.path.append(graph.vgeodesic_el(vvalue, vmax, vvalue, vmin))
1181 privatedata.path.append(graph.vgeodesic_el(vvalue, vmin, privatedata.vfromvalue, vmin))
1182 else:
1183 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmax, privatedata.vfromvalue)))
1184 privatedata.path.append(graph.vgeodesic_el(vmax, privatedata.vfromvalue, vmax, vvalue))
1185 privatedata.path.append(graph.vgeodesic_el(vmax, vvalue, vmin, vvalue))
1186 privatedata.path.append(graph.vgeodesic_el(vmin, vvalue, vmin, privatedata.vfromvalue))
1187 elif vvaluecut:
1188 done = 1
1189 if privatedata.rangeaxisindex:
1190 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue, vmin)))
1191 privatedata.path.append(graph.vgeodesic_el(vvalue, vmin, privatedata.vfromvalue, vmin))
1192 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmin, privatedata.vfromvalue, vmax))
1193 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmax, vvalue, vmax))
1194 else:
1195 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmin, vvalue)))
1196 privatedata.path.append(graph.vgeodesic_el(vmin, vvalue, vmin, privatedata.vfromvalue))
1197 privatedata.path.append(graph.vgeodesic_el(vmin, privatedata.vfromvalue, vmax, privatedata.vfromvalue))
1198 privatedata.path.append(graph.vgeodesic_el(vmax, privatedata.vfromvalue, vmax, vvalue))
1199 if not done:
1200 if privatedata.rangeaxisindex:
1201 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(privatedata.vfromvalue, vmin)))
1202 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmin, privatedata.vfromvalue, vmax))
1203 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmax, vvalue, vmax))
1204 privatedata.path.append(graph.vgeodesic_el(vvalue, vmax, vvalue, vmin))
1205 privatedata.path.append(graph.vgeodesic_el(vvalue, vmin, privatedata.vfromvalue, vmin))
1206 privatedata.path.append(path.closepath())
1207 else:
1208 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmin, privatedata.vfromvalue)))
1209 privatedata.path.append(graph.vgeodesic_el(vmin, privatedata.vfromvalue, vmax, privatedata.vfromvalue))
1210 privatedata.path.append(graph.vgeodesic_el(vmax, privatedata.vfromvalue, vmax, vvalue))
1211 privatedata.path.append(graph.vgeodesic_el(vmax, vvalue, vmin, vvalue))
1212 privatedata.path.append(graph.vgeodesic_el(vmin, vvalue, vmin, privatedata.vfromvalue))
1213 privatedata.path.append(path.closepath())
1214 else:
1215 try:
1216 gap = abs(vmin - privatedata.lastvmax) > self.epsilon
1217 except (ArithmeticError, ValueError, TypeError):
1218 gap = 1
1219 if (privatedata.lastvvalue is not None and currentvalid and not gap and
1220 (self.steps or (privatedata.lastvvalue-privatedata.vfromvalue)*(vvalue-privatedata.vfromvalue) < 0)):
1221 self.vposline(privatedata, sharedata, graph,
1222 vmin, privatedata.lastvvalue, vvalue)
1223 else:
1224 if privatedata.lastvvalue is not None and currentvalid:
1225 currentbigger = abs(privatedata.lastvvalue-privatedata.vfromvalue) < abs(vvalue-privatedata.vfromvalue)
1226 if privatedata.lastvvalue is not None and (not currentvalid or not currentbigger or gap):
1227 self.vposline(privatedata, sharedata, graph,
1228 privatedata.lastvmax, privatedata.lastvvalue, privatedata.vfromvalue)
1229 if currentvalid:
1230 self.vmoveto(privatedata, sharedata, graph,
1231 vmin, vvalue)
1232 if currentvalid and (privatedata.lastvvalue is None or currentbigger or gap):
1233 self.vmoveto(privatedata, sharedata, graph,
1234 vmin, privatedata.vfromvalue)
1235 self.vposline(privatedata, sharedata, graph,
1236 vmin, privatedata.vfromvalue, vvalue)
1237 if currentvalid:
1238 self.vvalueline(privatedata, sharedata, graph,
1239 vvalue, vmin, vmax)
1240 privatedata.lastvvalue = vvalue
1241 privatedata.lastvmax = vmax
1242 else:
1243 privatedata.lastvvalue = privatedata.lastvmax = None
1245 def initdrawpoints(self, privatedata, sharedata, graph):
1246 privatedata.path = path.path()
1247 privatedata.lastvvalue = privatedata.lastvmax = None
1248 privatedata.vcurrentpoint = None
1249 privatedata.count = 0
1250 if self.fromvalue is not None:
1251 valueaxisname = sharedata.poscolumnnames[1-privatedata.rangeaxisindex]
1252 privatedata.vfromvalue = graph.axes[valueaxisname].convert(self.fromvalue)
1253 privatedata.vfromvaluecut = 0
1254 if privatedata.vfromvalue < 0:
1255 privatedata.vfromvalue = 0
1256 privatedata.vfromvaluecut = -1
1257 if privatedata.vfromvalue > 1:
1258 privatedata.vfromvalue = 1
1259 privatedata.vfromvaluecut = 1
1260 if self.frompathattrs is not None and privatedata.insertfrompath:
1261 graph.stroke(graph.axes[valueaxisname].vgridpath(privatedata.vfromvalue),
1262 self.defaultfrompathattrs + self.frompathattrs)
1263 else:
1264 privatedata.vfromvalue = 0
1266 def drawpoint(self, privatedata, sharedata, graph, point):
1267 if privatedata.autohistogram:
1268 # automatic range handling
1269 privatedata.count += 1
1270 if privatedata.count == 2:
1271 if privatedata.rangeaxisindex:
1272 privatedata.vrange = sharedata.vpos[1] - privatedata.lastvpos[1]
1273 self.drawvalue(privatedata, sharedata, graph,
1274 privatedata.lastvpos[1] - self.autohistogrampointpos*privatedata.vrange,
1275 privatedata.lastvpos[1] + (1-self.autohistogrampointpos)*privatedata.vrange,
1276 privatedata.lastvpos[0])
1277 else:
1278 privatedata.vrange = sharedata.vpos[0] - privatedata.lastvpos[0]
1279 self.drawvalue(privatedata, sharedata, graph,
1280 privatedata.lastvpos[0] - self.autohistogrampointpos*privatedata.vrange,
1281 privatedata.lastvpos[0] + (1-self.autohistogrampointpos)*privatedata.vrange,
1282 privatedata.lastvpos[1])
1283 elif privatedata.count > 2:
1284 if privatedata.rangeaxisindex:
1285 vrange = sharedata.vpos[1] - privatedata.lastvpos[1]
1286 else:
1287 vrange = sharedata.vpos[0] - privatedata.lastvpos[0]
1288 if abs(privatedata.vrange - vrange) > self.epsilon:
1289 raise ValueError("equal steps (in graph coordinates) needed for automatic width calculation")
1290 if privatedata.count > 1:
1291 if privatedata.rangeaxisindex:
1292 self.drawvalue(privatedata, sharedata, graph,
1293 sharedata.vpos[1] - self.autohistogrampointpos*privatedata.vrange,
1294 sharedata.vpos[1] + (1-self.autohistogrampointpos)*privatedata.vrange,
1295 sharedata.vpos[0])
1296 else:
1297 self.drawvalue(privatedata, sharedata, graph,
1298 sharedata.vpos[0] - self.autohistogrampointpos*privatedata.vrange,
1299 sharedata.vpos[0] + (1-self.autohistogrampointpos)*privatedata.vrange,
1300 sharedata.vpos[1])
1301 privatedata.lastvpos = sharedata.vpos[:]
1302 else:
1303 if privatedata.rangeaxisindex:
1304 self.drawvalue(privatedata, sharedata, graph,
1305 sharedata.vrange[1][0], sharedata.vrange[1][1], sharedata.vpos[0])
1306 else:
1307 self.drawvalue(privatedata, sharedata, graph,
1308 sharedata.vrange[0][0], sharedata.vrange[0][1], sharedata.vpos[1])
1310 def donedrawpoints(self, privatedata, sharedata, graph):
1311 self.drawvalue(privatedata, sharedata, graph, None, None, None)
1312 if privatedata.lineattrs is not None and len(privatedata.path):
1313 graph.draw(privatedata.path, privatedata.lineattrs)
1315 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
1316 if privatedata.lineattrs is not None:
1317 if self.rectkey:
1318 p = path.rect_pt(x_pt, y_pt, width_pt, height_pt)
1319 else:
1320 p = path.line_pt(x_pt, y_pt+0.5*height_pt, x_pt+width_pt, y_pt+0.5*height_pt)
1321 graph.draw(p, privatedata.lineattrs)
1324 class barpos(_style):
1326 providesdata = ["vpos", "vposmissing", "vposavailable", "vposvalid", "vbarrange", "barposcolumnnames", "barvalueindex", "lastbarvalue", "stackedbar", "stackedbardraw"]
1328 defaultfrompathattrs = []
1330 def __init__(self, fromvalue=None, frompathattrs=[], epsilon=1e-10):
1331 self.fromvalue = fromvalue
1332 self.frompathattrs = frompathattrs
1333 self.epsilon = epsilon
1335 def columnnames(self, privatedata, sharedata, graph, columnnames):
1336 sharedata.barposcolumnnames = []
1337 sharedata.barvalueindex = None
1338 for dimension, axisnames in enumerate(graph.axesnames):
1339 found = 0
1340 for axisname in axisnames:
1341 if axisname in columnnames:
1342 if sharedata.barvalueindex is not None:
1343 raise ValueError("multiple values")
1344 sharedata.barvalueindex = dimension
1345 sharedata.barposcolumnnames.append(axisname)
1346 found += 1
1347 if (axisname + "name") in columnnames:
1348 sharedata.barposcolumnnames.append(axisname + "name")
1349 found += 1
1350 if found > 1:
1351 raise ValueError("multiple names and value")
1352 if not found:
1353 raise ValueError("value/name missing")
1354 if sharedata.barvalueindex is None:
1355 raise ValueError("missing value")
1356 sharedata.vposmissing = []
1357 return sharedata.barposcolumnnames
1359 def addsubvalue(self, value, subvalue):
1360 try:
1361 value + ""
1362 except:
1363 try:
1364 return value[0], self.addsubvalue(value[1], subvalue)
1365 except:
1366 return value, subvalue
1367 else:
1368 return value, subvalue
1370 def adjustaxis(self, privatedata, sharedata, graph, columnname, data):
1371 try:
1372 i = sharedata.barposcolumnnames.index(columnname)
1373 except ValueError:
1374 pass
1375 else:
1376 if i == sharedata.barvalueindex:
1377 if self.fromvalue is not None:
1378 graph.axes[sharedata.barposcolumnnames[i]].adjustaxis([self.fromvalue])
1379 graph.axes[sharedata.barposcolumnnames[i]].adjustaxis(data)
1380 else:
1381 graph.axes[sharedata.barposcolumnnames[i][:-4]].adjustaxis([self.addsubvalue(x, 0) for x in data])
1382 graph.axes[sharedata.barposcolumnnames[i][:-4]].adjustaxis([self.addsubvalue(x, 1) for x in data])
1384 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1385 privatedata.insertfrompath = selectindex == 0
1387 def initdrawpoints(self, privatedata, sharedata, graph):
1388 sharedata.vpos = [None]*(len(sharedata.barposcolumnnames))
1389 sharedata.vbarrange = [[None for i in xrange(2)] for x in sharedata.barposcolumnnames]
1390 sharedata.stackedbar = sharedata.stackedbardraw = 0
1392 if self.fromvalue is not None:
1393 privatedata.vfromvalue = graph.axes[sharedata.barposcolumnnames[sharedata.barvalueindex]].convert(self.fromvalue)
1394 if privatedata.vfromvalue < 0:
1395 privatedata.vfromvalue = 0
1396 if privatedata.vfromvalue > 1:
1397 privatedata.vfromvalue = 1
1398 if self.frompathattrs is not None and privatedata.insertfrompath:
1399 graph.stroke(graph.axes[sharedata.barposcolumnnames[sharedata.barvalueindex]].vgridpath(privatedata.vfromvalue),
1400 self.defaultfrompathattrs + self.frompathattrs)
1401 else:
1402 privatedata.vfromvalue = 0
1404 def drawpoint(self, privatedata, sharedata, graph, point):
1405 sharedata.vposavailable = sharedata.vposvalid = 1
1406 for i, barname in enumerate(sharedata.barposcolumnnames):
1407 if i == sharedata.barvalueindex:
1408 sharedata.vbarrange[i][0] = privatedata.vfromvalue
1409 sharedata.lastbarvalue = point[barname]
1410 try:
1411 sharedata.vpos[i] = sharedata.vbarrange[i][1] = graph.axes[barname].convert(sharedata.lastbarvalue)
1412 except (ArithmeticError, ValueError, TypeError):
1413 sharedata.vpos[i] = sharedata.vbarrange[i][1] = None
1414 else:
1415 for j in xrange(2):
1416 try:
1417 sharedata.vbarrange[i][j] = graph.axes[barname[:-4]].convert(self.addsubvalue(point[barname], j))
1418 except (ArithmeticError, ValueError, TypeError):
1419 sharedata.vbarrange[i][j] = None
1420 try:
1421 sharedata.vpos[i] = 0.5*(sharedata.vbarrange[i][0]+sharedata.vbarrange[i][1])
1422 except (ArithmeticError, ValueError, TypeError):
1423 sharedata.vpos[i] = None
1424 if sharedata.vpos[i] is None:
1425 sharedata.vposavailable = sharedata.vposvalid = 0
1426 elif sharedata.vpos[i] < -self.epsilon or sharedata.vpos[i] > 1+self.epsilon:
1427 sharedata.vposvalid = 0
1429 registerdefaultprovider(barpos(), ["vbarrange", "barposcolumnnames", "barvalueindex", "lastbarvalue", "stackedbar", "stackedbardraw"])
1432 class stackedbarpos(_style):
1434 # provides no additional data, but needs some data (and modifies some of them)
1435 needsdata = ["vbarrange", "barposcolumnnames", "barvalueindex", "lastbarvalue", "stackedbar", "stackedbardraw"]
1437 def __init__(self, stackname, addontop=0, epsilon=1e-10):
1438 self.stackname = stackname
1439 self.epsilon = epsilon
1440 self.addontop = addontop
1442 def columnnames(self, privatedata, sharedata, graph, columnnames):
1443 if self.stackname not in columnnames:
1444 raise ValueError("column '%s' missing" % self.stackname)
1445 return [self.stackname]
1447 def adjustaxis(self, privatedata, sharedata, graph, columnname, data):
1448 if columnname == self.stackname:
1449 graph.axes[sharedata.barposcolumnnames[sharedata.barvalueindex]].adjustaxis(data)
1451 def initdrawpoints(self, privatedata, sharedata, graph):
1452 if sharedata.stackedbardraw: # do not count the start bar when not gets painted
1453 sharedata.stackedbar += 1
1455 def drawpoint(self, privatedata, sharedata, graph, point):
1456 sharedata.vbarrange[sharedata.barvalueindex][0] = sharedata.vbarrange[sharedata.barvalueindex][1]
1457 if self.addontop:
1458 try:
1459 sharedata.lastbarvalue += point[self.stackname]
1460 except (ArithmeticError, ValueError, TypeError):
1461 sharedata.lastbarvalue = None
1462 else:
1463 sharedata.lastbarvalue = point[self.stackname]
1464 try:
1465 sharedata.vpos[sharedata.barvalueindex] = sharedata.vbarrange[sharedata.barvalueindex][1] = graph.axes[sharedata.barposcolumnnames[sharedata.barvalueindex]].convert(sharedata.lastbarvalue)
1466 except (ArithmeticError, ValueError, TypeError):
1467 sharedata.vpos[sharedata.barvalueindex] = sharedata.vbarrange[sharedata.barvalueindex][1] = None
1468 sharedata.vposavailable = sharedata.vposvalid = 0
1469 else:
1470 if not sharedata.vposavailable or not sharedata.vposvalid:
1471 sharedata.vposavailable = sharedata.vposvalid = 1
1472 for v in sharedata.vpos:
1473 if v is None:
1474 sharedata.vposavailable = sharedata.vposvalid = 0
1475 break
1476 if v < -self.epsilon or v > 1+self.epsilon:
1477 sharedata.vposvalid = 0
1480 class bar(_style):
1482 needsdata = ["vbarrange"]
1484 defaultbarattrs = [color.gradient.Rainbow, deco.stroked([color.grey.black])]
1486 def __init__(self, barattrs=[], epsilon=1e-10, gradient=color.gradient.RedBlack):
1487 self.barattrs = barattrs
1488 self.epsilon = epsilon
1489 self.gradient = gradient
1491 def lighting(self, angle, zindex):
1492 return self.gradient.getcolor(0.7-0.4*abs(angle)+0.1*zindex)
1494 def columnnames(self, privatedata, sharedata, graph, columnnames):
1495 return []
1497 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1498 privatedata.barattrs = attr.selectattrs(self.defaultbarattrs + self.barattrs, selectindex, selecttotal)
1500 def initdrawpoints(self, privatedata, sharedata, graph):
1501 privatedata.barcanvas = graph.insert(canvas.canvas())
1502 sharedata.stackedbardraw = 1
1503 privatedata.stackedbar = sharedata.stackedbar
1504 privatedata.todraw = []
1506 def drawpointfill(self, privatedata, p):
1507 if p:
1508 privatedata.barcanvas.fill(p, privatedata.barattrs)
1510 def drawpoint(self, privatedata, sharedata, graph, point):
1511 vbarrange = []
1512 for vmin, vmax in sharedata.vbarrange:
1513 if vmin is None or vmax is None:
1514 self.drawpointfill(privatedata, None)
1515 return
1516 if vmin > vmax:
1517 vmin, vmax = vmax, vmin
1518 if vmin > 1 or vmax < 0:
1519 self.drawpointfill(privatedata, None)
1520 return
1521 if vmin < 0:
1522 vmin = 0
1523 if vmax > 1:
1524 vmax = 1
1525 vbarrange.append((vmin, vmax))
1526 if len(vbarrange) == 2:
1527 p = graph.vgeodesic(vbarrange[0][0], vbarrange[1][0], vbarrange[0][1], vbarrange[1][0])
1528 p.append(graph.vgeodesic_el(vbarrange[0][1], vbarrange[1][0], vbarrange[0][1], vbarrange[1][1]))
1529 p.append(graph.vgeodesic_el(vbarrange[0][1], vbarrange[1][1], vbarrange[0][0], vbarrange[1][1]))
1530 p.append(graph.vgeodesic_el(vbarrange[0][0], vbarrange[1][1], vbarrange[0][0], vbarrange[1][0]))
1531 p.append(path.closepath())
1532 self.drawpointfill(privatedata, p)
1533 elif len(vbarrange) == 3:
1534 planes = []
1535 if abs(vbarrange[0][0] - vbarrange[0][1]) > self.epsilon and abs(vbarrange[1][0] - vbarrange[1][1]):
1536 planes.append((vbarrange[0][0], vbarrange[1][0], vbarrange[2][0],
1537 vbarrange[0][1], vbarrange[1][0], vbarrange[2][0],
1538 vbarrange[0][1], vbarrange[1][1], vbarrange[2][0],
1539 vbarrange[0][0], vbarrange[1][1], vbarrange[2][0]))
1540 planes.append((vbarrange[0][0], vbarrange[1][0], vbarrange[2][1],
1541 vbarrange[0][0], vbarrange[1][1], vbarrange[2][1],
1542 vbarrange[0][1], vbarrange[1][1], vbarrange[2][1],
1543 vbarrange[0][1], vbarrange[1][0], vbarrange[2][1]))
1544 if abs(vbarrange[0][0] - vbarrange[0][1]) > self.epsilon and abs(vbarrange[2][0] - vbarrange[2][1]):
1545 planes.append((vbarrange[0][0], vbarrange[1][0], vbarrange[2][0],
1546 vbarrange[0][0], vbarrange[1][0], vbarrange[2][1],
1547 vbarrange[0][1], vbarrange[1][0], vbarrange[2][1],
1548 vbarrange[0][1], vbarrange[1][0], vbarrange[2][0]))
1549 planes.append((vbarrange[0][0], vbarrange[1][1], vbarrange[2][0],
1550 vbarrange[0][1], vbarrange[1][1], vbarrange[2][0],
1551 vbarrange[0][1], vbarrange[1][1], vbarrange[2][1],
1552 vbarrange[0][0], vbarrange[1][1], vbarrange[2][1]))
1553 if abs(vbarrange[1][0] - vbarrange[1][1]) > self.epsilon and abs(vbarrange[2][0] - vbarrange[2][1]):
1554 planes.append((vbarrange[0][0], vbarrange[1][0], vbarrange[2][0],
1555 vbarrange[0][0], vbarrange[1][1], vbarrange[2][0],
1556 vbarrange[0][0], vbarrange[1][1], vbarrange[2][1],
1557 vbarrange[0][0], vbarrange[1][0], vbarrange[2][1]))
1558 planes.append((vbarrange[0][1], vbarrange[1][0], vbarrange[2][0],
1559 vbarrange[0][1], vbarrange[1][0], vbarrange[2][1],
1560 vbarrange[0][1], vbarrange[1][1], vbarrange[2][1],
1561 vbarrange[0][1], vbarrange[1][1], vbarrange[2][0]))
1562 v = [0.5 * (vbarrange[0][0] + vbarrange[0][1]),
1563 0.5 * (vbarrange[1][0] + vbarrange[1][1]),
1564 0.5 * (vbarrange[2][0] + vbarrange[2][1])]
1565 v[sharedata.barvalueindex] = 0.5
1566 zindex = graph.vzindex(*v)
1567 for v11, v12, v13, v21, v22, v23, v31, v32, v33, v41, v42, v43 in planes:
1568 angle = graph.vangle(v11, v12, v13, v21, v22, v23, v41, v42, v43)
1569 if angle > 0:
1570 p = graph.vgeodesic(v11, v12, v13, v21, v22, v23)
1571 p.append(graph.vgeodesic_el(v21, v22, v23, v31, v32, v33))
1572 p.append(graph.vgeodesic_el(v31, v32, v33, v41, v42, v43))
1573 p.append(graph.vgeodesic_el(v41, v42, v43, v11, v12, v13))
1574 p.append(path.closepath())
1575 if self.gradient:
1576 privatedata.todraw.append((-zindex, p, privatedata.barattrs + [self.lighting(angle, zindex)]))
1577 else:
1578 privatedata.todraw.append((-zindex, p, privatedata.barattrs))
1579 else:
1580 raise TypeError("bar style restricted to two- and three dimensional graphs")
1582 def donedrawpoints(self, privatedata, sharedata, graph):
1583 privatedata.todraw.sort()
1584 for vzindex, p, a in privatedata.todraw:
1585 privatedata.barcanvas.fill(p, a)
1587 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
1588 selectindex = privatedata.stackedbar
1589 selecttotal = sharedata.stackedbar + 1
1590 graph.fill(path.rect_pt(x_pt + width_pt*selectindex/float(selecttotal), y_pt, width_pt/float(selecttotal), height_pt), privatedata.barattrs)
1593 class changebar(bar):
1595 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1596 if selecttotal != 1:
1597 raise RuntimeError("Changebar can't change its appearance. Thus you can't use it to plot several bars side by side on a subaxis.")
1599 def initdrawpoints(self, privatedata, sharedata, graph):
1600 if len(graph.axesnames) != 2:
1601 raise TypeError("changebar style restricted on two-dimensional graphs (at least for the moment)")
1602 bar.initdrawpoints(self, privatedata, sharedata, graph)
1603 privatedata.bars = []
1605 def drawpointfill(self, privatedata, p):
1606 privatedata.bars.append(p)
1608 def donedrawpoints(self, privatedata, sharedata, graph):
1609 selecttotal = len(privatedata.bars)
1610 for selectindex, p in enumerate(privatedata.bars):
1611 if p:
1612 barattrs = attr.selectattrs(self.defaultbarattrs + self.barattrs, selectindex, selecttotal)
1613 privatedata.barcanvas.fill(p, barattrs)
1615 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
1616 raise RuntimeError("Style currently doesn't provide a graph key")
1619 class gridpos(_style):
1621 needsdata = ["vpos", "vposmissing", "vposavailable", "vposvalid"]
1622 providesdata = ["values1", "values2", "data12", "data21", "index1", "index2"]
1624 def __init__(self, index1=0, index2=1, epsilon=1e-10):
1625 self.index1 = index1
1626 self.index2 = index2
1627 self.epsilon = epsilon
1629 def initdrawpoints(self, privatedata, sharedata, graph):
1630 sharedata.index1 = self.index1
1631 sharedata.index2 = self.index2
1632 sharedata.values1 = {}
1633 sharedata.values2 = {}
1634 sharedata.data12 = {}
1635 sharedata.data21 = {}
1637 def drawpoint(self, privatedata, sharedata, graph, point):
1638 if sharedata.vposavailable:
1639 sharedata.value1 = sharedata.vpos[self.index1]
1640 sharedata.value2 = sharedata.vpos[self.index2]
1641 if not sharedata.values1.has_key(sharedata.value1):
1642 for hasvalue in sharedata.values1.keys():
1643 if hasvalue - self.epsilon <= sharedata.value1 <= hasvalue + self.epsilon:
1644 sharedata.value1 = hasvalue
1645 break
1646 else:
1647 sharedata.values1[sharedata.value1] = 1
1648 if not sharedata.values2.has_key(sharedata.value2):
1649 for hasvalue in sharedata.values2.keys():
1650 if hasvalue - self.epsilon <= sharedata.value2 <= hasvalue + self.epsilon:
1651 sharedata.value2 = hasvalue
1652 break
1653 else:
1654 sharedata.values2[sharedata.value2] = 1
1655 data = sharedata.vposavailable, sharedata.vposvalid, sharedata.vpos[:]
1656 sharedata.data12.setdefault(sharedata.value1, {})[sharedata.value2] = data
1657 sharedata.data21.setdefault(sharedata.value2, {})[sharedata.value1] = data
1659 registerdefaultprovider(gridpos(), gridpos.providesdata)
1662 class grid(_line, _style):
1664 needsdata = ["values1", "values2", "data12", "data21"]
1666 defaultgridattrs = [line.changelinestyle]
1668 def __init__(self, gridlines1=1, gridlines2=1, gridattrs=[]):
1669 self.gridlines1 = gridlines1
1670 self.gridlines2 = gridlines2
1671 self.gridattrs = gridattrs
1673 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1674 if self.gridattrs is not None:
1675 privatedata.gridattrs = attr.selectattrs(self.defaultgridattrs + self.gridattrs, selectindex, selecttotal)
1676 else:
1677 privatedata.gridattrs = None
1679 def donedrawpoints(self, privatedata, sharedata, graph):
1680 values1 = sharedata.values1.keys()
1681 values1.sort()
1682 values2 = sharedata.values2.keys()
1683 values2.sort()
1684 if self.gridlines1:
1685 for value2 in values2:
1686 data1 = sharedata.data21[value2]
1687 self.initpointstopath(privatedata)
1688 for value1 in values1:
1689 try:
1690 data = data1[value1]
1691 except KeyError:
1692 self.addinvalid(privatedata)
1693 else:
1694 self.addpoint(privatedata, graph.vpos_pt, *data)
1695 p = self.donepointstopath(privatedata)
1696 if len(p):
1697 graph.stroke(p, privatedata.gridattrs)
1698 if self.gridlines2:
1699 for value1 in values1:
1700 data2 = sharedata.data12[value1]
1701 self.initpointstopath(privatedata)
1702 for value2 in values2:
1703 try:
1704 data = data2[value2]
1705 except KeyError:
1706 self.addinvalid(privatedata)
1707 else:
1708 self.addpoint(privatedata, graph.vpos_pt, *data)
1709 p = self.donepointstopath(privatedata)
1710 if len(p):
1711 graph.stroke(p, privatedata.gridattrs)
1714 class surface(_style):
1716 needsdata = ["values1", "values2", "data12", "data21"]
1718 def __init__(self, colorname="color", gradient=color.gradient.Grey, mincolor=None, maxcolor=None,
1719 gridlines1=0.05, gridlines2=0.05, gridcolor=None,
1720 backcolor=color.gray.black):
1721 self.colorname = colorname
1722 self.gradient = gradient
1723 self.mincolor = mincolor
1724 self.maxcolor = maxcolor
1725 self.gridlines1 = gridlines1
1726 self.gridlines2 = gridlines2
1727 self.gridcolor = gridcolor
1728 self.backcolor = backcolor
1730 colorspacestring = gradient.getcolor(0).colorspacestring()
1731 if self.gridcolor is not None and self.gridcolor.colorspacestring() != colorspacestring:
1732 raise RuntimeError("colorspace mismatch (gradient/grid)")
1733 if self.backcolor is not None and self.backcolor.colorspacestring() != colorspacestring:
1734 raise RuntimeError("colorspace mismatch (gradient/back)")
1736 def midvalue(self, v1, v2, v3, v4):
1737 return [0.25*sum(values) for values in zip(v1, v2, v3, v4)]
1739 def midcolor(self, c1, c2, c3, c4):
1740 return 0.25*(c1+c2+c3+c4)
1742 def lighting(self, angle, zindex):
1743 if angle < 0 and self.backcolor is not None:
1744 return self.backcolor
1745 return self.gradient.getcolor(0.7-0.4*abs(angle)+0.1*zindex)
1747 def columnnames(self, privatedata, sharedata, graph, columnnames):
1748 privatedata.colorize = self.colorname in columnnames
1749 if privatedata.colorize:
1750 return [self.colorname]
1751 return []
1753 def initdrawpoints(self, privatedata, sharedata, graph):
1754 privatedata.colors = {}
1755 privatedata.mincolor = privatedata.maxcolor = None
1757 def drawpoint(self, privatedata, sharedata, graph, point):
1758 if privatedata.colorize:
1759 try:
1760 color = point[self.colorname] + 0
1761 except:
1762 pass
1763 else:
1764 privatedata.colors.setdefault(sharedata.value1, {})[sharedata.value2] = color
1765 if privatedata.mincolor is None or color < privatedata.mincolor:
1766 privatedata.mincolor = color
1767 if privatedata.mincolor is None or privatedata.maxcolor < color:
1768 privatedata.maxcolor = color
1770 def donedrawpoints(self, privatedata, sharedata, graph):
1771 v1 = [0]*len(graph.axesnames)
1772 v2 = [0]*len(graph.axesnames)
1773 v3 = [0]*len(graph.axesnames)
1774 v4 = [0]*len(graph.axesnames)
1775 v1[sharedata.index2] = 0.5
1776 v2[sharedata.index1] = 0.5
1777 v3[sharedata.index1] = 0.5
1778 v3[sharedata.index2] = 1
1779 v4[sharedata.index1] = 1
1780 v4[sharedata.index2] = 0.5
1781 sortElements = [-graph.vzindex(*v1),
1782 -graph.vzindex(*v2),
1783 -graph.vzindex(*v3),
1784 -graph.vzindex(*v4)]
1786 values1 = sharedata.values1.keys()
1787 values1.sort()
1788 v1 = [0]*len(graph.axesnames)
1789 v2 = [0]*len(graph.axesnames)
1790 v1[sharedata.index1] = -1
1791 v2[sharedata.index1] = 1
1792 sign = 1
1793 if graph.vzindex(*v1) < graph.vzindex(*v2):
1794 values1.reverse()
1795 sign *= -1
1796 sortElements = [sortElements[3], sortElements[1], sortElements[2], sortElements[0]]
1798 values2 = sharedata.values2.keys()
1799 values2.sort()
1800 v1 = [0]*len(graph.axesnames)
1801 v2 = [0]*len(graph.axesnames)
1802 v1[sharedata.index2] = -1
1803 v2[sharedata.index2] = 1
1804 if graph.vzindex(*v1) < graph.vzindex(*v2):
1805 values2.reverse()
1806 sign *= -1
1807 sortElements = [sortElements[0], sortElements[2], sortElements[1], sortElements[3]]
1809 sortElements = [(zindex, i) for i, zindex in enumerate(sortElements)]
1810 sortElements.sort()
1812 mincolor, maxcolor = privatedata.mincolor, privatedata.maxcolor
1813 if self.mincolor is not None:
1814 mincolor = self.mincolor
1815 if self.maxcolor is not None:
1816 maxcolor = self.maxcolor
1817 nodes = []
1818 elements = []
1819 for value1a, value1b in zip(values1[:-1], values1[1:]):
1820 for value2a, value2b in zip(values2[:-1], values2[1:]):
1821 try:
1822 available1, valid1, v1 = sharedata.data12[value1a][value2a]
1823 available2, valid2, v2 = sharedata.data12[value1a][value2b]
1824 available3, valid3, v3 = sharedata.data12[value1b][value2a]
1825 available4, valid4, v4 = sharedata.data12[value1b][value2b]
1826 except KeyError:
1827 continue
1828 if not available1 or not available2 or not available3 or not available4:
1829 continue
1830 if not valid1 or not valid2 or not valid3 or not valid4:
1831 warnings.warn("surface elements partially outside of the graph are (currently) skipped completely")
1832 continue
1833 def shrink(index, v1, v2, by):
1834 v1 = v1[:]
1835 v2 = v2[:]
1836 for i in builtinrange(3):
1837 if i != index:
1838 v1[i], v2[i] = v1[i] + by*(v2[i]-v1[i]), v2[i] + by*(v1[i]-v2[i])
1839 return v1, v2
1840 v1f, v2f, v3f, v4f = v1, v2, v3, v4
1841 if self.gridcolor is not None and self.gridlines1:
1842 v1, v2 = shrink(sharedata.index1, v1, v2, self.gridlines1)
1843 v3, v4 = shrink(sharedata.index1, v3, v4, self.gridlines1)
1844 if self.gridcolor is not None and self.gridlines2:
1845 v1, v3 = shrink(sharedata.index2, v1, v3, self.gridlines2)
1846 v2, v4 = shrink(sharedata.index2, v2, v4, self.gridlines2)
1847 v5 = self.midvalue(v1, v2, v3, v4)
1848 x1_pt, y1_pt = graph.vpos_pt(*v1)
1849 x2_pt, y2_pt = graph.vpos_pt(*v2)
1850 x3_pt, y3_pt = graph.vpos_pt(*v3)
1851 x4_pt, y4_pt = graph.vpos_pt(*v4)
1852 x5_pt, y5_pt = graph.vpos_pt(*v5)
1853 if privatedata.colorize:
1854 def colorfromgradient(c):
1855 vc = (c - mincolor) / float(maxcolor - mincolor)
1856 if vc < 0:
1857 warnings.warn("gradiend color range is exceeded due to mincolor setting")
1858 vc = 0
1859 if vc > 1:
1860 warnings.warn("gradiend color range is exceeded due to maxcolor setting")
1861 vc = 1
1862 return self.gradient.getcolor(vc)
1863 c1 = privatedata.colors[value1a][value2a]
1864 c2 = privatedata.colors[value1a][value2b]
1865 c3 = privatedata.colors[value1b][value2a]
1866 c4 = privatedata.colors[value1b][value2b]
1867 c5 = self.midcolor(c1, c2, c3, c4)
1868 c1a = c1b = colorfromgradient(c1)
1869 c2a = c2c = colorfromgradient(c2)
1870 c3b = c3d = colorfromgradient(c3)
1871 c4c = c4d = colorfromgradient(c4)
1872 c5a = c5b = c5c = c5d = colorfromgradient(c5)
1873 if self.backcolor is not None and sign*graph.vangle(*(v1+v2+v5)) < 0:
1874 c1a = c2a = c5a = self.backcolor
1875 if self.backcolor is not None and sign*graph.vangle(*(v3+v1+v5)) < 0:
1876 c3b = c1b = c5b = self.backcolor
1877 if self.backcolor is not None and sign*graph.vangle(*(v2+v4+v5)) < 0:
1878 c2c = c4c = c5c = self.backcolor
1879 if self.backcolor is not None and sign*graph.vangle(*(v4+v3+v5)) < 0:
1880 c4d = c3d = c5d = self.backcolor
1881 else:
1882 zindex = graph.vzindex(*v5)
1883 c1a = c2a = c5a = self.lighting(sign*graph.vangle(*(v1+v2+v5)), zindex)
1884 c3b = c1b = c5b = self.lighting(sign*graph.vangle(*(v3+v1+v5)), zindex)
1885 c2c = c4c = c5c = self.lighting(sign*graph.vangle(*(v2+v4+v5)), zindex)
1886 c4d = c3d = c5d = self.lighting(sign*graph.vangle(*(v4+v3+v5)), zindex)
1887 for zindex, i in sortElements:
1888 if i == 0:
1889 elements.append(mesh.element((mesh.node_pt((x1_pt, y1_pt), c1a),
1890 mesh.node_pt((x2_pt, y2_pt), c2a),
1891 mesh.node_pt((x5_pt, y5_pt), c5a))))
1892 if self.gridcolor is not None and self.gridlines2:
1893 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v1f), self.gridcolor),
1894 mesh.node_pt(graph.vpos_pt(*v2), self.gridcolor),
1895 mesh.node_pt(graph.vpos_pt(*v1), self.gridcolor))))
1896 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v1f), self.gridcolor),
1897 mesh.node_pt(graph.vpos_pt(*v2), self.gridcolor),
1898 mesh.node_pt(graph.vpos_pt(*v2f), self.gridcolor))))
1899 elif i == 1:
1900 elements.append(mesh.element((mesh.node_pt((x3_pt, y3_pt), c3b),
1901 mesh.node_pt((x1_pt, y1_pt), c1b),
1902 mesh.node_pt((x5_pt, y5_pt), c5b))))
1903 if self.gridcolor is not None and self.gridlines1:
1904 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v1f), self.gridcolor),
1905 mesh.node_pt(graph.vpos_pt(*v3), self.gridcolor),
1906 mesh.node_pt(graph.vpos_pt(*v1), self.gridcolor))))
1907 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v1f), self.gridcolor),
1908 mesh.node_pt(graph.vpos_pt(*v3), self.gridcolor),
1909 mesh.node_pt(graph.vpos_pt(*v3f), self.gridcolor))))
1910 elif i == 2:
1911 elements.append(mesh.element((mesh.node_pt((x2_pt, y2_pt), c2c),
1912 mesh.node_pt((x4_pt, y4_pt), c4c),
1913 mesh.node_pt((x5_pt, y5_pt), c5c))))
1914 if self.gridcolor is not None and self.gridlines1:
1915 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v2f), self.gridcolor),
1916 mesh.node_pt(graph.vpos_pt(*v4), self.gridcolor),
1917 mesh.node_pt(graph.vpos_pt(*v2), self.gridcolor))))
1918 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v2f), self.gridcolor),
1919 mesh.node_pt(graph.vpos_pt(*v4), self.gridcolor),
1920 mesh.node_pt(graph.vpos_pt(*v4f), self.gridcolor))))
1921 elif i == 3:
1922 elements.append(mesh.element((mesh.node_pt((x4_pt, y4_pt), c4d),
1923 mesh.node_pt((x3_pt, y3_pt), c3d),
1924 mesh.node_pt((x5_pt, y5_pt), c5d))))
1925 if self.gridcolor is not None and self.gridlines2:
1926 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v3f), self.gridcolor),
1927 mesh.node_pt(graph.vpos_pt(*v4), self.gridcolor),
1928 mesh.node_pt(graph.vpos_pt(*v3), self.gridcolor))))
1929 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v3f), self.gridcolor),
1930 mesh.node_pt(graph.vpos_pt(*v4), self.gridcolor),
1931 mesh.node_pt(graph.vpos_pt(*v4f), self.gridcolor))))
1932 m = mesh.mesh(elements, check=0)
1933 graph.insert(m)