Applied upstream as r3028 r3025 r3024
[PyX/mjg.git] / pyx / graph / style.py
blob6860b093c5094fd39905caf2a6db69fda55df4f4
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][0]].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][0]].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=[]):
1483 self.barattrs = barattrs
1485 def columnnames(self, privatedata, sharedata, graph, columnnames):
1486 if len(graph.axesnames) != 2:
1487 raise TypeError("bar style restricted on two-dimensional graphs")
1488 return []
1490 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1491 privatedata.barattrs = attr.selectattrs(self.defaultbarattrs + self.barattrs, selectindex, selecttotal)
1493 def initdrawpoints(self, privatedata, sharedata, graph):
1494 privatedata.rectcanvas = graph.insert(canvas.canvas())
1495 sharedata.stackedbardraw = 1
1496 privatedata.stackedbar = sharedata.stackedbar
1498 def drawpointfill(self, privatedata, p):
1499 if p:
1500 privatedata.rectcanvas.fill(p, privatedata.barattrs)
1502 def drawpoint(self, privatedata, sharedata, graph, point):
1503 xvmin = sharedata.vbarrange[0][0]
1504 xvmax = sharedata.vbarrange[0][1]
1505 yvmin = sharedata.vbarrange[1][0]
1506 yvmax = sharedata.vbarrange[1][1]
1507 try:
1508 if xvmin > xvmax:
1509 xvmin, xvmax = xvmax, xvmin
1510 except:
1511 pass
1512 try:
1513 if yvmin > yvmax:
1514 yvmin, yvmax = yvmax, yvmin
1515 except:
1516 pass
1517 if (xvmin is not None and xvmin < 1 and
1518 xvmax is not None and xvmax > 0 and
1519 yvmin is not None and yvmin < 1 and
1520 yvmax is not None and yvmax > 0):
1521 if xvmin < 0:
1522 xvmin = 0
1523 elif xvmax > 1:
1524 xvmax = 1
1525 if yvmin < 0:
1526 yvmin = 0
1527 elif yvmax > 1:
1528 yvmax = 1
1529 p = graph.vgeodesic(xvmin, yvmin, xvmax, yvmin)
1530 p.append(graph.vgeodesic_el(xvmax, yvmin, xvmax, yvmax))
1531 p.append(graph.vgeodesic_el(xvmax, yvmax, xvmin, yvmax))
1532 p.append(graph.vgeodesic_el(xvmin, yvmax, xvmin, yvmin))
1533 p.append(path.closepath())
1534 self.drawpointfill(privatedata, p)
1535 else:
1536 self.drawpointfill(privatedata, None)
1538 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
1539 selectindex = privatedata.stackedbar
1540 selecttotal = sharedata.stackedbar + 1
1541 graph.fill(path.rect_pt(x_pt + width_pt*selectindex/float(selecttotal), y_pt, width_pt/float(selecttotal), height_pt), privatedata.barattrs)
1544 class changebar(bar):
1546 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1547 if selecttotal != 1:
1548 raise RuntimeError("Changebar can't change its appearance. Thus you can't use it to plot several bars side by side on a subaxis.")
1550 def initdrawpoints(self, privatedata, sharedata, graph):
1551 bar.initdrawpoints(self, privatedata, sharedata, graph)
1552 privatedata.bars = []
1554 def drawpointfill(self, privatedata, p):
1555 privatedata.bars.append(p)
1557 def donedrawpoints(self, privatedata, sharedata, graph):
1558 selecttotal = len(privatedata.bars)
1559 for selectindex, p in enumerate(privatedata.bars):
1560 if p:
1561 barattrs = attr.selectattrs(self.defaultbarattrs + self.barattrs, selectindex, selecttotal)
1562 privatedata.rectcanvas.fill(p, barattrs)
1564 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
1565 raise RuntimeError("Style currently doesn't provide a graph key")
1568 class gridpos(_style):
1570 needsdata = ["vpos", "vposmissing", "vposavailable", "vposvalid"]
1571 providesdata = ["values1", "values2", "data12", "data21", "index1", "index2"]
1573 def __init__(self, index1=0, index2=1, epsilon=1e-10):
1574 self.index1 = index1
1575 self.index2 = index2
1576 self.epsilon = epsilon
1578 def initdrawpoints(self, privatedata, sharedata, graph):
1579 sharedata.index1 = self.index1
1580 sharedata.index2 = self.index2
1581 sharedata.values1 = {}
1582 sharedata.values2 = {}
1583 sharedata.data12 = {}
1584 sharedata.data21 = {}
1586 def drawpoint(self, privatedata, sharedata, graph, point):
1587 if sharedata.vposavailable:
1588 sharedata.value1 = sharedata.vpos[self.index1]
1589 sharedata.value2 = sharedata.vpos[self.index2]
1590 if not sharedata.values1.has_key(sharedata.value1):
1591 for hasvalue in sharedata.values1.keys():
1592 if hasvalue - self.epsilon <= sharedata.value1 <= hasvalue + self.epsilon:
1593 sharedata.value1 = hasvalue
1594 break
1595 else:
1596 sharedata.values1[sharedata.value1] = 1
1597 if not sharedata.values2.has_key(sharedata.value2):
1598 for hasvalue in sharedata.values2.keys():
1599 if hasvalue - self.epsilon <= sharedata.value2 <= hasvalue + self.epsilon:
1600 sharedata.value2 = hasvalue
1601 break
1602 else:
1603 sharedata.values2[sharedata.value2] = 1
1604 data = sharedata.vposavailable, sharedata.vposvalid, sharedata.vpos[:]
1605 sharedata.data12.setdefault(sharedata.value1, {})[sharedata.value2] = data
1606 sharedata.data21.setdefault(sharedata.value2, {})[sharedata.value1] = data
1608 registerdefaultprovider(gridpos(), gridpos.providesdata)
1611 class grid(_line, _style):
1613 needsdata = ["values1", "values2", "data12", "data21"]
1615 defaultgridattrs = [line.changelinestyle]
1617 def __init__(self, gridlines1=1, gridlines2=1, gridattrs=[]):
1618 self.gridlines1 = gridlines1
1619 self.gridlines2 = gridlines2
1620 self.gridattrs = gridattrs
1622 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1623 if self.gridattrs is not None:
1624 privatedata.gridattrs = attr.selectattrs(self.defaultgridattrs + self.gridattrs, selectindex, selecttotal)
1625 else:
1626 privatedata.gridattrs = None
1628 def donedrawpoints(self, privatedata, sharedata, graph):
1629 values1 = sharedata.values1.keys()
1630 values1.sort()
1631 values2 = sharedata.values2.keys()
1632 values2.sort()
1633 if self.gridlines1:
1634 for value2 in values2:
1635 data1 = sharedata.data21[value2]
1636 self.initpointstopath(privatedata)
1637 for value1 in values1:
1638 try:
1639 data = data1[value1]
1640 except KeyError:
1641 self.addinvalid(privatedata)
1642 else:
1643 self.addpoint(privatedata, graph.vpos_pt, *data)
1644 p = self.donepointstopath(privatedata)
1645 if len(p):
1646 graph.stroke(p, privatedata.gridattrs)
1647 if self.gridlines2:
1648 for value1 in values1:
1649 data2 = sharedata.data12[value1]
1650 self.initpointstopath(privatedata)
1651 for value2 in values2:
1652 try:
1653 data = data2[value2]
1654 except KeyError:
1655 self.addinvalid(privatedata)
1656 else:
1657 self.addpoint(privatedata, graph.vpos_pt, *data)
1658 p = self.donepointstopath(privatedata)
1659 if len(p):
1660 graph.stroke(p, privatedata.gridattrs)
1663 class surface(_style):
1665 needsdata = ["values1", "values2", "data12", "data21"]
1667 def __init__(self, colorname="color", gradient=color.gradient.Grey, mincolor=None, maxcolor=None,
1668 gridlines1=0.05, gridlines2=0.05, gridcolor=None,
1669 backcolor=color.gray.black):
1670 self.colorname = colorname
1671 self.gradient = gradient
1672 self.mincolor = mincolor
1673 self.maxcolor = maxcolor
1674 self.gridlines1 = gridlines1
1675 self.gridlines2 = gridlines2
1676 self.gridcolor = gridcolor
1677 self.backcolor = backcolor
1679 colorspacestring = gradient.getcolor(0).colorspacestring()
1680 if self.gridcolor is not None and self.gridcolor.colorspacestring() != colorspacestring:
1681 raise RuntimeError("colorspace mismatch (gradient/grid)")
1682 if self.backcolor is not None and self.backcolor.colorspacestring() != colorspacestring:
1683 raise RuntimeError("colorspace mismatch (gradient/back)")
1685 def midvalue(self, v1, v2, v3, v4):
1686 return [0.25*sum(values) for values in zip(v1, v2, v3, v4)]
1688 def midcolor(self, c1, c2, c3, c4):
1689 return 0.25*(c1+c2+c3+c4)
1691 def lighting(self, angle, zindex):
1692 if angle < 0 and self.backcolor is not None:
1693 return self.backcolor
1694 return self.gradient.getcolor(0.7-0.4*abs(angle)+0.1*zindex)
1696 def columnnames(self, privatedata, sharedata, graph, columnnames):
1697 privatedata.colorize = self.colorname in columnnames
1698 if privatedata.colorize:
1699 return [self.colorname]
1700 return []
1702 def initdrawpoints(self, privatedata, sharedata, graph):
1703 privatedata.colors = {}
1704 privatedata.mincolor = privatedata.maxcolor = None
1706 def drawpoint(self, privatedata, sharedata, graph, point):
1707 if privatedata.colorize:
1708 try:
1709 color = point[self.colorname] + 0
1710 except:
1711 pass
1712 else:
1713 privatedata.colors.setdefault(sharedata.value1, {})[sharedata.value2] = color
1714 if privatedata.mincolor is None or color < privatedata.mincolor:
1715 privatedata.mincolor = color
1716 if privatedata.mincolor is None or privatedata.maxcolor < color:
1717 privatedata.maxcolor = color
1719 def donedrawpoints(self, privatedata, sharedata, graph):
1720 v1 = [0]*len(graph.axesnames)
1721 v2 = [0]*len(graph.axesnames)
1722 v3 = [0]*len(graph.axesnames)
1723 v4 = [0]*len(graph.axesnames)
1724 v1[sharedata.index2] = 0.5
1725 v2[sharedata.index1] = 0.5
1726 v3[sharedata.index1] = 0.5
1727 v3[sharedata.index2] = 1
1728 v4[sharedata.index1] = 1
1729 v4[sharedata.index2] = 0.5
1730 sortElements = [-graph.vzindex(*v1),
1731 -graph.vzindex(*v2),
1732 -graph.vzindex(*v3),
1733 -graph.vzindex(*v4)]
1735 values1 = sharedata.values1.keys()
1736 values1.sort()
1737 v1 = [0]*len(graph.axesnames)
1738 v2 = [0]*len(graph.axesnames)
1739 v1[sharedata.index1] = -1
1740 v2[sharedata.index1] = 1
1741 sign = 1
1742 if graph.vzindex(*v1) < graph.vzindex(*v2):
1743 values1.reverse()
1744 sign *= -1
1745 sortElements = [sortElements[3], sortElements[1], sortElements[2], sortElements[0]]
1747 values2 = sharedata.values2.keys()
1748 values2.sort()
1749 v1 = [0]*len(graph.axesnames)
1750 v2 = [0]*len(graph.axesnames)
1751 v1[sharedata.index2] = -1
1752 v2[sharedata.index2] = 1
1753 if graph.vzindex(*v1) < graph.vzindex(*v2):
1754 values2.reverse()
1755 sign *= -1
1756 sortElements = [sortElements[0], sortElements[2], sortElements[1], sortElements[3]]
1758 sortElements = [(zindex, i) for i, zindex in enumerate(sortElements)]
1759 sortElements.sort()
1761 mincolor, maxcolor = privatedata.mincolor, privatedata.maxcolor
1762 if self.mincolor is not None:
1763 mincolor = self.mincolor
1764 if self.maxcolor is not None:
1765 maxcolor = self.maxcolor
1766 nodes = []
1767 elements = []
1768 for value1a, value1b in zip(values1[:-1], values1[1:]):
1769 for value2a, value2b in zip(values2[:-1], values2[1:]):
1770 try:
1771 available1, valid1, v1 = sharedata.data12[value1a][value2a]
1772 available2, valid2, v2 = sharedata.data12[value1a][value2b]
1773 available3, valid3, v3 = sharedata.data12[value1b][value2a]
1774 available4, valid4, v4 = sharedata.data12[value1b][value2b]
1775 except KeyError:
1776 continue
1777 if not available1 or not available2 or not available3 or not available4:
1778 continue
1779 if not valid1 or not valid2 or not valid3 or not valid4:
1780 warnings.warn("surface elements partially outside of the graph are (currently) skipped completely")
1781 continue
1782 def shrink(index, v1, v2, by):
1783 v1 = v1[:]
1784 v2 = v2[:]
1785 for i in builtinrange(3):
1786 if i != index:
1787 v1[i], v2[i] = v1[i] + by*(v2[i]-v1[i]), v2[i] + by*(v1[i]-v2[i])
1788 return v1, v2
1789 v1f, v2f, v3f, v4f = v1, v2, v3, v4
1790 if self.gridcolor is not None and self.gridlines1:
1791 v1, v2 = shrink(sharedata.index1, v1, v2, self.gridlines1)
1792 v3, v4 = shrink(sharedata.index1, v3, v4, self.gridlines1)
1793 if self.gridcolor is not None and self.gridlines2:
1794 v1, v3 = shrink(sharedata.index2, v1, v3, self.gridlines2)
1795 v2, v4 = shrink(sharedata.index2, v2, v4, self.gridlines2)
1796 v5 = self.midvalue(v1, v2, v3, v4)
1797 x1_pt, y1_pt = graph.vpos_pt(*v1)
1798 x2_pt, y2_pt = graph.vpos_pt(*v2)
1799 x3_pt, y3_pt = graph.vpos_pt(*v3)
1800 x4_pt, y4_pt = graph.vpos_pt(*v4)
1801 x5_pt, y5_pt = graph.vpos_pt(*v5)
1802 if privatedata.colorize:
1803 def colorfromgradient(c):
1804 return self.gradient.getcolor(min(1.0,max(0.0,(c - mincolor) /
1805 float(maxcolor - mincolor))))
1806 c1 = privatedata.colors[value1a][value2a]
1807 c2 = privatedata.colors[value1a][value2b]
1808 c3 = privatedata.colors[value1b][value2a]
1809 c4 = privatedata.colors[value1b][value2b]
1810 c5 = self.midcolor(c1, c2, c3, c4)
1811 c1a = c1b = colorfromgradient(c1)
1812 c2a = c2c = colorfromgradient(c2)
1813 c3b = c3d = colorfromgradient(c3)
1814 c4c = c4d = colorfromgradient(c4)
1815 c5a = c5b = c5c = c5d = colorfromgradient(c5)
1816 if self.backcolor is not None and sign*graph.vangle(*(v1+v2+v5)) < 0:
1817 c1a = c2a = c5a = self.backcolor
1818 if self.backcolor is not None and sign*graph.vangle(*(v3+v1+v5)) < 0:
1819 c3b = c1b = c5b = self.backcolor
1820 if self.backcolor is not None and sign*graph.vangle(*(v2+v4+v5)) < 0:
1821 c2c = c4c = c5c = self.backcolor
1822 if self.backcolor is not None and sign*graph.vangle(*(v4+v3+v5)) < 0:
1823 c4d = c3d = c5d = self.backcolor
1824 else:
1825 zindex = graph.vzindex(*v5)
1826 c1a = c2a = c5a = self.lighting(sign*graph.vangle(*(v1+v2+v5)), zindex)
1827 c3b = c1b = c5b = self.lighting(sign*graph.vangle(*(v3+v1+v5)), zindex)
1828 c2c = c4c = c5c = self.lighting(sign*graph.vangle(*(v2+v4+v5)), zindex)
1829 c4d = c3d = c5d = self.lighting(sign*graph.vangle(*(v4+v3+v5)), zindex)
1830 for zindex, i in sortElements:
1831 if i == 0:
1832 elements.append(mesh.element((mesh.node_pt((x1_pt, y1_pt), c1a),
1833 mesh.node_pt((x2_pt, y2_pt), c2a),
1834 mesh.node_pt((x5_pt, y5_pt), c5a))))
1835 if self.gridcolor is not None and self.gridlines2:
1836 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v1f), self.gridcolor),
1837 mesh.node_pt(graph.vpos_pt(*v2), self.gridcolor),
1838 mesh.node_pt(graph.vpos_pt(*v1), self.gridcolor))))
1839 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v1f), self.gridcolor),
1840 mesh.node_pt(graph.vpos_pt(*v2), self.gridcolor),
1841 mesh.node_pt(graph.vpos_pt(*v2f), self.gridcolor))))
1842 elif i == 1:
1843 elements.append(mesh.element((mesh.node_pt((x3_pt, y3_pt), c3b),
1844 mesh.node_pt((x1_pt, y1_pt), c1b),
1845 mesh.node_pt((x5_pt, y5_pt), c5b))))
1846 if self.gridcolor is not None and self.gridlines1:
1847 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v1f), self.gridcolor),
1848 mesh.node_pt(graph.vpos_pt(*v3), self.gridcolor),
1849 mesh.node_pt(graph.vpos_pt(*v1), self.gridcolor))))
1850 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v1f), self.gridcolor),
1851 mesh.node_pt(graph.vpos_pt(*v3), self.gridcolor),
1852 mesh.node_pt(graph.vpos_pt(*v3f), self.gridcolor))))
1853 elif i == 2:
1854 elements.append(mesh.element((mesh.node_pt((x2_pt, y2_pt), c2c),
1855 mesh.node_pt((x4_pt, y4_pt), c4c),
1856 mesh.node_pt((x5_pt, y5_pt), c5c))))
1857 if self.gridcolor is not None and self.gridlines1:
1858 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v2f), self.gridcolor),
1859 mesh.node_pt(graph.vpos_pt(*v4), self.gridcolor),
1860 mesh.node_pt(graph.vpos_pt(*v2), self.gridcolor))))
1861 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v2f), self.gridcolor),
1862 mesh.node_pt(graph.vpos_pt(*v4), self.gridcolor),
1863 mesh.node_pt(graph.vpos_pt(*v4f), self.gridcolor))))
1864 elif i == 3:
1865 elements.append(mesh.element((mesh.node_pt((x4_pt, y4_pt), c4d),
1866 mesh.node_pt((x3_pt, y3_pt), c3d),
1867 mesh.node_pt((x5_pt, y5_pt), c5d))))
1868 if self.gridcolor is not None and self.gridlines2:
1869 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v3f), self.gridcolor),
1870 mesh.node_pt(graph.vpos_pt(*v4), self.gridcolor),
1871 mesh.node_pt(graph.vpos_pt(*v3), self.gridcolor))))
1872 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v3f), self.gridcolor),
1873 mesh.node_pt(graph.vpos_pt(*v4), self.gridcolor),
1874 mesh.node_pt(graph.vpos_pt(*v4f), self.gridcolor))))
1875 m = mesh.mesh(elements, check=0)
1876 graph.insert(m)