further 3d work
[PyX/mjg.git] / pyx / graph / style.py
blobe12e1372b7c2ad13d8911e583cf74ea7b5ac61de
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 import math, warnings
26 from pyx import attr, deco, style, color, unit, canvas, path, mesh
27 from pyx import text as textmodule
29 builtinrange = range
31 try:
32 enumerate([])
33 except NameError:
34 # fallback implementation for Python 2.2. and below
35 def enumerate(list):
36 return zip(xrange(len(list)), list)
38 class _style:
39 """Interface class for graph styles
41 Each graph style must support the methods described in this
42 class. However, since a graph style might not need to perform
43 actions on all the various events, it does not need to overwrite
44 all methods of this base class (e.g. this class is not an abstract
45 class in any respect).
47 A style should never store private data by istance variables
48 (i.e. accessing self), but it should use the sharedata and privatedata
49 instances instead. A style instance can be used multiple times with
50 different sharedata and privatedata instances at the very same time.
51 The sharedata and privatedata instances act as data containers and
52 sharedata allows for sharing information across several styles.
54 Every style contains two class variables, which are not to be
55 modified:
56 - providesdata is a list of variable names a style offers via
57 the sharedata instance. This list is used to determine whether
58 all needs of subsequent styles are fullfilled. Otherwise
59 getdefaultprovider should return a proper style to be used.
60 - needsdata is a list of variable names the style needs to access in the
61 sharedata instance.
62 """
64 providesdata = [] # by default, we provide nothing
65 needsdata = [] # and do not depend on anything
67 def columnnames(self, privatedata, sharedata, graph, columnnames):
68 """Set column information
70 This method is used setup the column name information to be
71 accessible to the style later on. The style should analyse
72 the list of column names. The method should return a list of
73 column names which the style will make use of."""
74 return []
76 def adjustaxis(self, privatedata, sharedata, graph, columnname, data):
77 """Adjust axis range
79 This method is called in order to adjust the axis range to
80 the provided data. columnname is the column name (each style
81 is subsequently called for all column names)."""
82 pass
84 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
85 """Select stroke/fill attributes
87 This method is called to allow for the selection of
88 changable attributes of a style."""
89 pass
91 def initdrawpoints(self, privatedata, sharedata, graph):
92 """Initialize drawing of data
94 This method might be used to initialize the drawing of data."""
95 pass
97 def drawpoint(self, privatedata, sharedata, graph, point):
98 """Draw data
100 This method is called for each data point. The data is
101 available in the dictionary point. The dictionary
102 keys are the column names."""
103 pass
105 def donedrawpoints(self, privatedata, sharedata, graph):
106 """Finalize drawing of data
108 This method is called after the last data point was
109 drawn using the drawpoint method above."""
110 pass
112 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
113 """Draw graph key"""
116 # The following two methods are used to register and get a default provider
117 # for keys. A key is a variable name in sharedata. A provider is a style
118 # which creates variables in sharedata.
120 _defaultprovider = {}
122 def registerdefaultprovider(style, keys):
123 """sets a style as a default creator for sharedata variables 'keys'"""
124 assert not len(style.needsdata), "currently we state, that a style should not depend on other sharedata variables"
125 for key in keys:
126 assert key in style.providesdata, "key not provided by style"
127 # we might allow for overwriting the defaults, i.e. the following is not checked:
128 # assert key in _defaultprovider.keys(), "default provider already registered for key"
129 _defaultprovider[key] = style
131 def getdefaultprovider(key):
132 """returns a style, which acts as a default creator for the
133 sharedata variable 'key'"""
134 return _defaultprovider[key]
137 class pos(_style):
139 providesdata = ["vpos", "vposmissing", "vposavailable", "vposvalid", "poscolumnnames"]
141 def __init__(self, epsilon=1e-10):
142 self.epsilon = epsilon
144 def columnnames(self, privatedata, sharedata, graph, columnnames):
145 sharedata.poscolumnnames = []
146 sharedata.vposmissing = []
147 for count, axisnames in enumerate(graph.axesnames):
148 for axisname in axisnames:
149 for columnname in columnnames:
150 if axisname == columnname:
151 sharedata.poscolumnnames.append(columnname)
152 if len(sharedata.poscolumnnames) > count+1:
153 raise ValueError("multiple axes per graph dimension")
154 elif len(sharedata.poscolumnnames) < count+1:
155 sharedata.vposmissing.append(count)
156 sharedata.poscolumnnames.append(None)
157 return [columnname for columnname in sharedata.poscolumnnames if columnname is not None]
159 def adjustaxis(self, privatedata, sharedata, graph, columnname, data):
160 if columnname in sharedata.poscolumnnames:
161 graph.axes[columnname].adjustaxis(data)
163 def initdrawpoints(self, privatedata, sharedata, graph):
164 sharedata.vpos = [None]*(len(graph.axesnames))
165 privatedata.pointpostmplist = [[columnname, index, graph.axes[columnname]] # temporarily used by drawpoint only
166 for index, columnname in enumerate([columnname for columnname in sharedata.poscolumnnames if columnname is not None])]
167 for missing in sharedata.vposmissing:
168 for pointpostmp in privatedata.pointpostmplist:
169 if pointpostmp[1] >= missing:
170 pointpostmp[1] += 1
172 def drawpoint(self, privatedata, sharedata, graph, point):
173 sharedata.vposavailable = 1 # valid position (but might be outside of the graph)
174 sharedata.vposvalid = 1 # valid position inside the graph
175 for columnname, index, axis in privatedata.pointpostmplist:
176 try:
177 v = axis.convert(point[columnname])
178 except (ArithmeticError, ValueError, TypeError):
179 sharedata.vposavailable = sharedata.vposvalid = 0
180 sharedata.vpos[index] = None
181 else:
182 if v < -self.epsilon or v > 1+self.epsilon:
183 sharedata.vposvalid = 0
184 sharedata.vpos[index] = v
187 registerdefaultprovider(pos(), pos.providesdata)
190 class range(_style):
192 providesdata = ["vrange", "vrangemissing", "vrangeminmissing", "vrangemaxmissing"]
194 # internal bit masks
195 mask_value = 1
196 mask_min = 2
197 mask_max = 4
198 mask_dmin = 8
199 mask_dmax = 16
200 mask_d = 32
202 def __init__(self, usenames={}, epsilon=1e-10):
203 self.usenames = usenames
204 self.epsilon = epsilon
206 def _numberofbits(self, mask):
207 if not mask:
208 return 0
209 if mask & 1:
210 return self._numberofbits(mask >> 1) + 1
211 else:
212 return self._numberofbits(mask >> 1)
214 def columnnames(self, privatedata, sharedata, graph, columnnames):
215 usecolumns = []
216 privatedata.rangeposcolumns = []
217 sharedata.vrangemissing = []
218 sharedata.vrangeminmissing = []
219 sharedata.vrangemaxmissing = []
220 privatedata.rangeposdeltacolumns = {} # temporarily used by adjustaxis only
221 for count, axisnames in enumerate(graph.axesnames):
222 for axisname in axisnames:
223 try:
224 usename = self.usenames[axisname]
225 except KeyError:
226 usename = axisname
227 mask = 0
228 for columnname in columnnames:
229 addusecolumns = 1
230 if usename == columnname:
231 mask += self.mask_value
232 elif usename + "min" == columnname:
233 mask += self.mask_min
234 elif usename + "max" == columnname:
235 mask += self.mask_max
236 elif "d" + usename + "min" == columnname:
237 mask += self.mask_dmin
238 elif "d" + usename + "max" == columnname:
239 mask += self.mask_dmax
240 elif "d" + usename == columnname:
241 mask += self.mask_d
242 else:
243 addusecolumns = 0
244 if addusecolumns:
245 usecolumns.append(columnname)
246 if mask & (self.mask_min | self.mask_max | self.mask_dmin | self.mask_dmax | self.mask_d):
247 if (self._numberofbits(mask & (self.mask_min | self.mask_dmin | self.mask_d)) > 1 or
248 self._numberofbits(mask & (self.mask_max | self.mask_dmax | self.mask_d)) > 1):
249 raise ValueError("multiple range definition")
250 if mask & (self.mask_dmin | self.mask_dmax | self.mask_d):
251 if not (mask & self.mask_value):
252 raise ValueError("missing value for delta")
253 privatedata.rangeposdeltacolumns[axisname] = {}
254 privatedata.rangeposcolumns.append((axisname, usename, mask))
255 elif mask == self.mask_value:
256 usecolumns = usecolumns[:-1]
257 if len(privatedata.rangeposcolumns) + len(sharedata.vrangemissing) > count+1:
258 raise ValueError("multiple axes per graph dimension")
259 elif len(privatedata.rangeposcolumns) + len(sharedata.vrangemissing) < count+1:
260 sharedata.vrangemissing.append(count)
261 sharedata.vrangeminmissing.append(count)
262 sharedata.vrangemaxmissing.append(count)
263 else:
264 if not (privatedata.rangeposcolumns[-1][2] & (self.mask_min | self.mask_dmin | self.mask_d)):
265 sharedata.vrangeminmissing.append(count)
266 if not (privatedata.rangeposcolumns[-1][2] & (self.mask_max | self.mask_dmax | self.mask_d)):
267 sharedata.vrangemaxmissing.append(count)
268 return usecolumns
270 def adjustaxis(self, privatedata, sharedata, graph, columnname, data):
271 if columnname in [c + "min" for a, c, m in privatedata.rangeposcolumns if m & self.mask_min]:
272 graph.axes[columnname[:-3]].adjustaxis(data)
273 if columnname in [c + "max" for a, c, m in privatedata.rangeposcolumns if m & self.mask_max]:
274 graph.axes[columnname[:-3]].adjustaxis(data)
276 # delta handling: fill rangeposdeltacolumns
277 for axisname, usename, mask in privatedata.rangeposcolumns:
278 if columnname == usename and mask & (self.mask_dmin | self.mask_dmax | self.mask_d):
279 privatedata.rangeposdeltacolumns[axisname][self.mask_value] = data
280 if columnname == "d" + usename + "min" and mask & self.mask_dmin:
281 privatedata.rangeposdeltacolumns[axisname][self.mask_dmin] = data
282 if columnname == "d" + usename + "max" and mask & self.mask_dmax:
283 privatedata.rangeposdeltacolumns[axisname][self.mask_dmax] = data
284 if columnname == "d" + usename and mask & self.mask_d:
285 privatedata.rangeposdeltacolumns[axisname][self.mask_d] = data
287 # delta handling: process rangeposdeltacolumns
288 for a, d in privatedata.rangeposdeltacolumns.items():
289 if d.has_key(self.mask_value):
290 for k in d.keys():
291 if k != self.mask_value:
292 if k & (self.mask_dmin | self.mask_d):
293 mindata = []
294 for value, delta in zip(d[self.mask_value], d[k]):
295 try:
296 mindata.append(value-delta)
297 except:
298 pass
299 graph.axes[a].adjustaxis(mindata)
300 if k & (self.mask_dmax | self.mask_d):
301 maxdata = []
302 for value, delta in zip(d[self.mask_value], d[k]):
303 try:
304 maxdata.append(value+delta)
305 except:
306 pass
307 graph.axes[a].adjustaxis(maxdata)
308 del d[k]
310 def initdrawpoints(self, privatedata, sharedata, graph):
311 sharedata.vrange = [[None for x in xrange(2)] for y in privatedata.rangeposcolumns + sharedata.vrangemissing]
312 privatedata.rangepostmplist = [[usename, mask, index, graph.axes[axisname]] # temporarily used by drawpoint only
313 for index, (axisname, usename, mask) in enumerate(privatedata.rangeposcolumns)]
314 for missing in sharedata.vrangemissing:
315 for rangepostmp in privatedata.rangepostmplist:
316 if rangepostmp[2] >= missing:
317 rangepostmp[2] += 1
319 def drawpoint(self, privatedata, sharedata, graph, point):
320 for usename, mask, index, axis in privatedata.rangepostmplist:
321 try:
322 if mask & self.mask_min:
323 sharedata.vrange[index][0] = axis.convert(point[usename + "min"])
324 if mask & self.mask_dmin:
325 sharedata.vrange[index][0] = axis.convert(point[usename] - point["d" + usename + "min"])
326 if mask & self.mask_d:
327 sharedata.vrange[index][0] = axis.convert(point[usename] - point["d" + usename])
328 except (ArithmeticError, ValueError, TypeError):
329 sharedata.vrange[index][0] = None
330 try:
331 if mask & self.mask_max:
332 sharedata.vrange[index][1] = axis.convert(point[usename + "max"])
333 if mask & self.mask_dmax:
334 sharedata.vrange[index][1] = axis.convert(point[usename] + point["d" + usename + "max"])
335 if mask & self.mask_d:
336 sharedata.vrange[index][1] = axis.convert(point[usename] + point["d" + usename])
337 except (ArithmeticError, ValueError, TypeError):
338 sharedata.vrange[index][1] = None
340 # some range checks for data consistency
341 if (sharedata.vrange[index][0] is not None and sharedata.vrange[index][1] is not None and
342 sharedata.vrange[index][0] > sharedata.vrange[index][1] + self.epsilon):
343 raise ValueError("inverse range")
344 # disabled due to missing vpos access:
345 # if (sharedata.vrange[index][0] is not None and sharedata.vpos[index] is not None and
346 # sharedata.vrange[index][0] > sharedata.vpos[index] + self.epsilon):
347 # raise ValueError("negative minimum errorbar")
348 # if (sharedata.vrange[index][1] is not None and sharedata.vpos[index] is not None and
349 # sharedata.vrange[index][1] < sharedata.vpos[index] - self.epsilon):
350 # raise ValueError("negative maximum errorbar")
353 registerdefaultprovider(range(), range.providesdata)
356 def _crosssymbol(c, x_pt, y_pt, size_pt, attrs):
357 c.draw(path.path(path.moveto_pt(x_pt-0.5*size_pt, y_pt-0.5*size_pt),
358 path.lineto_pt(x_pt+0.5*size_pt, y_pt+0.5*size_pt),
359 path.moveto_pt(x_pt-0.5*size_pt, y_pt+0.5*size_pt),
360 path.lineto_pt(x_pt+0.5*size_pt, y_pt-0.5*size_pt)), attrs)
362 def _plussymbol(c, x_pt, y_pt, size_pt, attrs):
363 c.draw(path.path(path.moveto_pt(x_pt-0.707106781*size_pt, y_pt),
364 path.lineto_pt(x_pt+0.707106781*size_pt, y_pt),
365 path.moveto_pt(x_pt, y_pt-0.707106781*size_pt),
366 path.lineto_pt(x_pt, y_pt+0.707106781*size_pt)), attrs)
368 def _squaresymbol(c, x_pt, y_pt, size_pt, attrs):
369 c.draw(path.path(path.moveto_pt(x_pt-0.5*size_pt, y_pt-0.5*size_pt),
370 path.lineto_pt(x_pt+0.5*size_pt, y_pt-0.5*size_pt),
371 path.lineto_pt(x_pt+0.5*size_pt, y_pt+0.5*size_pt),
372 path.lineto_pt(x_pt-0.5*size_pt, y_pt+0.5*size_pt),
373 path.closepath()), attrs)
375 def _trianglesymbol(c, x_pt, y_pt, size_pt, attrs):
376 c.draw(path.path(path.moveto_pt(x_pt-0.759835685*size_pt, y_pt-0.438691337*size_pt),
377 path.lineto_pt(x_pt+0.759835685*size_pt, y_pt-0.438691337*size_pt),
378 path.lineto_pt(x_pt, y_pt+0.877382675*size_pt),
379 path.closepath()), attrs)
381 def _circlesymbol(c, x_pt, y_pt, size_pt, attrs):
382 c.draw(path.path(path.arc_pt(x_pt, y_pt, 0.564189583*size_pt, 0, 360),
383 path.closepath()), attrs)
385 def _diamondsymbol(c, x_pt, y_pt, size_pt, attrs):
386 c.draw(path.path(path.moveto_pt(x_pt-0.537284965*size_pt, y_pt),
387 path.lineto_pt(x_pt, y_pt-0.930604859*size_pt),
388 path.lineto_pt(x_pt+0.537284965*size_pt, y_pt),
389 path.lineto_pt(x_pt, y_pt+0.930604859*size_pt),
390 path.closepath()), attrs)
393 class _styleneedingpointpos(_style):
395 needsdata = ["vposmissing"]
397 def columnnames(self, privatedata, sharedata, graph, columnnames):
398 if len(sharedata.vposmissing):
399 raise ValueError("incomplete position information")
400 return []
403 class symbol(_styleneedingpointpos):
405 needsdata = ["vpos", "vposmissing", "vposvalid"]
407 # "inject" the predefinied symbols into the class:
409 # Note, that statements like cross = _crosssymbol are
410 # invalid, since the would lead to unbound methods, but
411 # a single entry changeable list does the trick.
413 # Once we require Python 2.2+ we should use staticmethods
414 # to implement the default symbols inplace.
416 cross = attr.changelist([_crosssymbol])
417 plus = attr.changelist([_plussymbol])
418 square = attr.changelist([_squaresymbol])
419 triangle = attr.changelist([_trianglesymbol])
420 circle = attr.changelist([_circlesymbol])
421 diamond = attr.changelist([_diamondsymbol])
423 changecross = attr.changelist([_crosssymbol, _plussymbol, _squaresymbol, _trianglesymbol, _circlesymbol, _diamondsymbol])
424 changeplus = attr.changelist([_plussymbol, _squaresymbol, _trianglesymbol, _circlesymbol, _diamondsymbol, _crosssymbol])
425 changesquare = attr.changelist([_squaresymbol, _trianglesymbol, _circlesymbol, _diamondsymbol, _crosssymbol, _plussymbol])
426 changetriangle = attr.changelist([_trianglesymbol, _circlesymbol, _diamondsymbol, _crosssymbol, _plussymbol, _squaresymbol])
427 changecircle = attr.changelist([_circlesymbol, _diamondsymbol, _crosssymbol, _plussymbol, _squaresymbol, _trianglesymbol])
428 changediamond = attr.changelist([_diamondsymbol, _crosssymbol, _plussymbol, _squaresymbol, _trianglesymbol, _circlesymbol])
429 changesquaretwice = attr.changelist([_squaresymbol, _squaresymbol, _trianglesymbol, _trianglesymbol, _circlesymbol, _circlesymbol, _diamondsymbol, _diamondsymbol])
430 changetriangletwice = attr.changelist([_trianglesymbol, _trianglesymbol, _circlesymbol, _circlesymbol, _diamondsymbol, _diamondsymbol, _squaresymbol, _squaresymbol])
431 changecircletwice = attr.changelist([_circlesymbol, _circlesymbol, _diamondsymbol, _diamondsymbol, _squaresymbol, _squaresymbol, _trianglesymbol, _trianglesymbol])
432 changediamondtwice = attr.changelist([_diamondsymbol, _diamondsymbol, _squaresymbol, _squaresymbol, _trianglesymbol, _trianglesymbol, _circlesymbol, _circlesymbol])
434 changestrokedfilled = attr.changelist([deco.stroked, deco.filled])
435 changefilledstroked = attr.changelist([deco.filled, deco.stroked])
437 defaultsymbolattrs = [deco.stroked]
439 def __init__(self, symbol=changecross, size=0.2*unit.v_cm, symbolattrs=[]):
440 self.symbol = symbol
441 self.size = size
442 self.symbolattrs = symbolattrs
444 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
445 privatedata.symbol = attr.selectattr(self.symbol, selectindex, selecttotal)
446 privatedata.size_pt = unit.topt(attr.selectattr(self.size, selectindex, selecttotal))
447 if self.symbolattrs is not None:
448 privatedata.symbolattrs = attr.selectattrs(self.defaultsymbolattrs + self.symbolattrs, selectindex, selecttotal)
449 else:
450 privatedata.symbolattrs = None
452 def initdrawpoints(self, privatedata, sharedata, graph):
453 privatedata.symbolcanvas = canvas.canvas()
455 def drawpoint(self, privatedata, sharedata, graph, point):
456 if sharedata.vposvalid and privatedata.symbolattrs is not None:
457 x_pt, y_pt = graph.vpos_pt(*sharedata.vpos)
458 privatedata.symbol(privatedata.symbolcanvas, x_pt, y_pt, privatedata.size_pt, privatedata.symbolattrs)
460 def donedrawpoints(self, privatedata, sharedata, graph):
461 graph.insert(privatedata.symbolcanvas)
463 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
464 if privatedata.symbolattrs is not None:
465 privatedata.symbol(graph, x_pt+0.5*width_pt, y_pt+0.5*height_pt, privatedata.size_pt, privatedata.symbolattrs)
468 class _line(_styleneedingpointpos):
470 # this style is not a complete style, but it provides the basic functionality to
471 # create a line, which is cut at the graph boundaries (or at otherwise invalid points)
473 needsdata = ["vposmissing"]
475 def initpointstopath(self, privatedata):
476 privatedata.path = path.path()
477 privatedata.linebasepoints = []
478 privatedata.lastvpos = None
480 def addpointstopath(self, privatedata):
481 # add baselinepoints to privatedata.path
482 if len(privatedata.linebasepoints) > 1:
483 privatedata.path.append(path.moveto_pt(*privatedata.linebasepoints[0]))
484 if len(privatedata.linebasepoints) > 2:
485 privatedata.path.append(path.multilineto_pt(privatedata.linebasepoints[1:]))
486 else:
487 privatedata.path.append(path.lineto_pt(*privatedata.linebasepoints[1]))
488 privatedata.linebasepoints = []
490 def addpoint(self, privatedata, graphvpos_pt, vposavailable, vposvalid, vpos):
491 # append linebasepoints
492 if vposavailable:
493 if len(privatedata.linebasepoints):
494 # the last point was inside the graph
495 if vposvalid: # shortcut for the common case
496 privatedata.linebasepoints.append(graphvpos_pt(*vpos))
497 else:
498 # cut end
499 cut = 1
500 for vstart, vend in zip(privatedata.lastvpos, vpos):
501 newcut = None
502 if vend > 1:
503 # 1 = vstart + (vend - vstart) * cut
504 try:
505 newcut = (1 - vstart)/(vend - vstart)
506 except (ArithmeticError, TypeError):
507 break
508 if vend < 0:
509 # 0 = vstart + (vend - vstart) * cut
510 try:
511 newcut = - vstart/(vend - vstart)
512 except (ArithmeticError, TypeError):
513 break
514 if newcut is not None and newcut < cut:
515 cut = newcut
516 else:
517 cutvpos = []
518 for vstart, vend in zip(privatedata.lastvpos, vpos):
519 cutvpos.append(vstart + (vend - vstart) * cut)
520 privatedata.linebasepoints.append(graphvpos_pt(*cutvpos))
521 self.addpointstopath(privatedata)
522 else:
523 # the last point was outside the graph
524 if privatedata.lastvpos is not None:
525 if vposvalid:
526 # cut beginning
527 cut = 0
528 for vstart, vend in zip(privatedata.lastvpos, vpos):
529 newcut = None
530 if vstart > 1:
531 # 1 = vstart + (vend - vstart) * cut
532 try:
533 newcut = (1 - vstart)/(vend - vstart)
534 except (ArithmeticError, TypeError):
535 break
536 if vstart < 0:
537 # 0 = vstart + (vend - vstart) * cut
538 try:
539 newcut = - vstart/(vend - vstart)
540 except (ArithmeticError, TypeError):
541 break
542 if newcut is not None and newcut > cut:
543 cut = newcut
544 else:
545 cutvpos = []
546 for vstart, vend in zip(privatedata.lastvpos, vpos):
547 cutvpos.append(vstart + (vend - vstart) * cut)
548 privatedata.linebasepoints.append(graphvpos_pt(*cutvpos))
549 privatedata.linebasepoints.append(graphvpos_pt(*vpos))
550 else:
551 # sometimes cut beginning and end
552 cutfrom = 0
553 cutto = 1
554 for vstart, vend in zip(privatedata.lastvpos, vpos):
555 newcutfrom = None
556 if vstart > 1:
557 if vend > 1:
558 break
559 # 1 = vstart + (vend - vstart) * cutfrom
560 try:
561 newcutfrom = (1 - vstart)/(vend - vstart)
562 except (ArithmeticError, TypeError):
563 break
564 if vstart < 0:
565 if vend < 0:
566 break
567 # 0 = vstart + (vend - vstart) * cutfrom
568 try:
569 newcutfrom = - vstart/(vend - vstart)
570 except (ArithmeticError, TypeError):
571 break
572 if newcutfrom is not None and newcutfrom > cutfrom:
573 cutfrom = newcutfrom
574 newcutto = None
575 if vend > 1:
576 # 1 = vstart + (vend - vstart) * cutto
577 try:
578 newcutto = (1 - vstart)/(vend - vstart)
579 except (ArithmeticError, TypeError):
580 break
581 if vend < 0:
582 # 0 = vstart + (vend - vstart) * cutto
583 try:
584 newcutto = - vstart/(vend - vstart)
585 except (ArithmeticError, TypeError):
586 break
587 if newcutto is not None and newcutto < cutto:
588 cutto = newcutto
589 else:
590 if cutfrom < cutto:
591 cutfromvpos = []
592 cuttovpos = []
593 for vstart, vend in zip(privatedata.lastvpos, vpos):
594 cutfromvpos.append(vstart + (vend - vstart) * cutfrom)
595 cuttovpos.append(vstart + (vend - vstart) * cutto)
596 privatedata.linebasepoints.append(graphvpos_pt(*cutfromvpos))
597 privatedata.linebasepoints.append(graphvpos_pt(*cuttovpos))
598 self.addpointstopath(privatedata)
599 privatedata.lastvpos = vpos[:]
600 else:
601 if len(privatedata.linebasepoints) > 1:
602 self.addpointstopath(privatedata)
603 privatedata.lastvpos = None
605 def addinvalid(self, privatedata):
606 if len(privatedata.linebasepoints) > 1:
607 self.addpointstopath(privatedata)
608 privatedata.lastvpos = None
610 def donepointstopath(self, privatedata):
611 if len(privatedata.linebasepoints) > 1:
612 self.addpointstopath(privatedata)
613 return privatedata.path
616 class line(_line):
618 needsdata = ["vpos", "vposmissing", "vposavailable", "vposvalid"]
620 changelinestyle = attr.changelist([style.linestyle.solid,
621 style.linestyle.dashed,
622 style.linestyle.dotted,
623 style.linestyle.dashdotted])
625 defaultlineattrs = [changelinestyle]
627 def __init__(self, lineattrs=[]):
628 self.lineattrs = lineattrs
630 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
631 if self.lineattrs is not None:
632 privatedata.lineattrs = attr.selectattrs(self.defaultlineattrs + self.lineattrs, selectindex, selecttotal)
633 else:
634 privatedata.lineattrs = None
636 def initdrawpoints(self, privatedata, sharedata, graph):
637 self.initpointstopath(privatedata)
639 def drawpoint(self, privatedata, sharedata, graph, point):
640 self.addpoint(privatedata, graph.vpos_pt, sharedata.vposavailable, sharedata.vposvalid, sharedata.vpos)
642 def donedrawpoints(self, privatedata, sharedata, graph):
643 path = self.donepointstopath(privatedata)
644 if privatedata.lineattrs is not None and len(path):
645 graph.stroke(path, privatedata.lineattrs)
647 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
648 if privatedata.lineattrs is not None:
649 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)
652 class impulses(_styleneedingpointpos):
654 needsdata = ["vpos", "vposmissing", "vposavailable", "vposvalid", "poscolumnnames"]
656 changelinestyle = attr.changelist([style.linestyle.solid,
657 style.linestyle.dashed,
658 style.linestyle.dotted,
659 style.linestyle.dashdotted])
661 defaultlineattrs = [changelinestyle]
662 defaultfrompathattrs = []
664 def __init__(self, lineattrs=[], fromvalue=0, frompathattrs=[], valueaxisindex=1):
665 self.lineattrs = lineattrs
666 self.fromvalue = fromvalue
667 self.frompathattrs = frompathattrs
668 self.valueaxisindex = valueaxisindex
670 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
671 privatedata.insertfrompath = selectindex == 0
672 if self.lineattrs is not None:
673 privatedata.lineattrs = attr.selectattrs(self.defaultlineattrs + self.lineattrs, selectindex, selecttotal)
674 else:
675 privatedata.lineattrs = None
677 def adjustaxis(self, privatedata, sharedata, graph, columnname, data):
678 if self.fromvalue is not None:
679 try:
680 i = sharedata.poscolumnnames.index(columnname)
681 except ValueError:
682 pass
683 else:
684 if i == self.valueaxisindex:
685 graph.axes[sharedata.poscolumnnames[i]].adjustaxis([self.fromvalue])
687 def initdrawpoints(self, privatedata, sharedata, graph):
688 privatedata.impulsescanvas = canvas.canvas()
689 if self.fromvalue is not None:
690 valueaxisname = sharedata.poscolumnnames[self.valueaxisindex]
691 privatedata.vfromvalue = graph.axes[valueaxisname].convert(self.fromvalue)
692 privatedata.vfromvaluecut = 0
693 if privatedata.vfromvalue < 0:
694 privatedata.vfromvalue = 0
695 if privatedata.vfromvalue > 1:
696 privatedata.vfromvalue = 1
697 if self.frompathattrs is not None and privatedata.insertfrompath:
698 graph.stroke(graph.axes[valueaxisname].vgridpath(privatedata.vfromvalue),
699 self.defaultfrompathattrs + self.frompathattrs)
700 else:
701 privatedata.vfromvalue = 0
703 def drawpoint(self, privatedata, sharedata, graph, point):
704 if sharedata.vposvalid and privatedata.lineattrs is not None:
705 vpos = sharedata.vpos[:]
706 vpos[self.valueaxisindex] = privatedata.vfromvalue
707 privatedata.impulsescanvas.stroke(graph.vgeodesic(*(vpos + sharedata.vpos)), privatedata.lineattrs)
709 def donedrawpoints(self, privatedata, sharedata, graph):
710 graph.insert(privatedata.impulsescanvas)
712 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
713 if privatedata.lineattrs is not None:
714 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)
717 class errorbar(_style):
719 needsdata = ["vpos", "vposmissing", "vposavailable", "vposvalid", "vrange", "vrangeminmissing", "vrangemaxmissing"]
721 defaulterrorbarattrs = []
723 def __init__(self, size=0.1*unit.v_cm,
724 errorbarattrs=[],
725 epsilon=1e-10):
726 self.size = size
727 self.errorbarattrs = errorbarattrs
728 self.epsilon = epsilon
730 def columnnames(self, privatedata, sharedata, graph, columnnames):
731 for i in sharedata.vposmissing:
732 if i in sharedata.vrangeminmissing and i in sharedata.vrangemaxmissing:
733 raise ValueError("position and range for a graph dimension missing")
734 return []
736 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
737 privatedata.errorsize_pt = unit.topt(attr.selectattr(self.size, selectindex, selecttotal))
738 privatedata.errorbarattrs = attr.selectattrs(self.defaulterrorbarattrs + self.errorbarattrs, selectindex, selecttotal)
740 def initdrawpoints(self, privatedata, sharedata, graph):
741 if privatedata.errorbarattrs is not None:
742 privatedata.errorbarcanvas = canvas.canvas(privatedata.errorbarattrs)
743 privatedata.dimensionlist = list(xrange(len(sharedata.vpos)))
745 def drawpoint(self, privatedata, sharedata, graph, point):
746 if privatedata.errorbarattrs is not None:
747 for i in privatedata.dimensionlist:
748 for j in privatedata.dimensionlist:
749 if (i != j and
750 (sharedata.vpos[j] is None or
751 sharedata.vpos[j] < -self.epsilon or
752 sharedata.vpos[j] > 1+self.epsilon)):
753 break
754 else:
755 if ((sharedata.vrange[i][0] is None and sharedata.vpos[i] is None) or
756 (sharedata.vrange[i][1] is None and sharedata.vpos[i] is None) or
757 (sharedata.vrange[i][0] is None and sharedata.vrange[i][1] is None)):
758 continue
759 vminpos = sharedata.vpos[:]
760 if sharedata.vrange[i][0] is not None:
761 vminpos[i] = sharedata.vrange[i][0]
762 mincap = 1
763 else:
764 mincap = 0
765 if vminpos[i] > 1+self.epsilon:
766 continue
767 if vminpos[i] < -self.epsilon:
768 vminpos[i] = 0
769 mincap = 0
770 vmaxpos = sharedata.vpos[:]
771 if sharedata.vrange[i][1] is not None:
772 vmaxpos[i] = sharedata.vrange[i][1]
773 maxcap = 1
774 else:
775 maxcap = 0
776 if vmaxpos[i] < -self.epsilon:
777 continue
778 if vmaxpos[i] > 1+self.epsilon:
779 vmaxpos[i] = 1
780 maxcap = 0
781 privatedata.errorbarcanvas.stroke(graph.vgeodesic(*(vminpos + vmaxpos)))
782 for j in privatedata.dimensionlist:
783 if i != j:
784 if mincap:
785 privatedata.errorbarcanvas.stroke(graph.vcap_pt(j, privatedata.errorsize_pt, *vminpos))
786 if maxcap:
787 privatedata.errorbarcanvas.stroke(graph.vcap_pt(j, privatedata.errorsize_pt, *vmaxpos))
789 def donedrawpoints(self, privatedata, sharedata, graph):
790 if privatedata.errorbarattrs is not None:
791 graph.insert(privatedata.errorbarcanvas)
794 class text(_styleneedingpointpos):
796 needsdata = ["vpos", "vposmissing", "vposvalid"]
798 defaulttextattrs = [textmodule.halign.center, textmodule.vshift.mathaxis]
800 def __init__(self, textname="text", dxname=None, dyname=None,
801 dxunit=0.3*unit.v_cm, dyunit=0.3*unit.v_cm,
802 textdx=0*unit.v_cm, textdy=0.3*unit.v_cm, textattrs=[]):
803 self.textname = textname
804 self.dxname = dxname
805 self.dyname = dyname
806 self.dxunit = dxunit
807 self.dyunit = dyunit
808 self.textdx = textdx
809 self.textdy = textdy
810 self.textattrs = textattrs
812 def columnnames(self, privatedata, sharedata, graph, columnnames):
813 if self.textname not in columnnames:
814 raise ValueError("column '%s' missing" % self.textname)
815 names = [self.textname]
816 if self.dxname is not None:
817 if self.dxname not in columnnames:
818 raise ValueError("column '%s' missing" % self.dxname)
819 names.append(self.dxname)
820 if self.dyname is not None:
821 if self.dyname not in columnnames:
822 raise ValueError("column '%s' missing" % self.dyname)
823 names.append(self.dyname)
824 return names + _styleneedingpointpos.columnnames(self, privatedata, sharedata, graph, columnnames)
826 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
827 if self.textattrs is not None:
828 privatedata.textattrs = attr.selectattrs(self.defaulttextattrs + self.textattrs, selectindex, selecttotal)
829 else:
830 privatedata.textattrs = None
832 def initdrawpoints(self, privatedata, sharedata, grap):
833 if self.dxname is None:
834 privatedata.textdx_pt = unit.topt(self.textdx)
835 else:
836 privatedata.dxunit_pt = unit.topt(self.dxunit)
837 if self.dyname is None:
838 privatedata.textdy_pt = unit.topt(self.textdy)
839 else:
840 privatedata.dyunit_pt = unit.topt(self.dyunit)
842 def drawpoint(self, privatedata, sharedata, graph, point):
843 if privatedata.textattrs is not None and sharedata.vposvalid:
844 x_pt, y_pt = graph.vpos_pt(*sharedata.vpos)
845 try:
846 text = str(point[self.textname])
847 except:
848 pass
849 else:
850 if self.dxname is None:
851 dx_pt = privatedata.textdx_pt
852 else:
853 dx_pt = float(point[self.dxname]) * privatedata.dxunit_pt
854 if self.dyname is None:
855 dy_pt = privatedata.textdy_pt
856 else:
857 dy_pt = float(point[self.dyname]) * privatedata.dyunit_pt
858 graph.text_pt(x_pt + dx_pt, y_pt + dy_pt, text, privatedata.textattrs)
860 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
861 raise RuntimeError("Style currently doesn't provide a graph key")
864 class arrow(_styleneedingpointpos):
866 needsdata = ["vpos", "vposmissing", "vposvalid"]
868 defaultlineattrs = []
869 defaultarrowattrs = []
871 def __init__(self, linelength=0.25*unit.v_cm, arrowsize=0.15*unit.v_cm, lineattrs=[], arrowattrs=[], arrowpos=0.5, epsilon=1e-5):
872 self.linelength = linelength
873 self.arrowsize = arrowsize
874 self.lineattrs = lineattrs
875 self.arrowattrs = arrowattrs
876 self.arrowpos = arrowpos
877 self.epsilon = epsilon
879 def columnnames(self, privatedata, sharedata, graph, columnnames):
880 if len(graph.axesnames) != 2:
881 raise ValueError("arrow style restricted on two-dimensional graphs")
882 if "size" not in columnnames:
883 raise ValueError("size missing")
884 if "angle" not in columnnames:
885 raise ValueError("angle missing")
886 return ["size", "angle"] + _styleneedingpointpos.columnnames(self, privatedata, sharedata, graph, columnnames)
888 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
889 if self.lineattrs is not None:
890 privatedata.lineattrs = attr.selectattrs(self.defaultlineattrs + self.lineattrs, selectindex, selecttotal)
891 else:
892 privatedata.lineattrs = None
893 if self.arrowattrs is not None:
894 privatedata.arrowattrs = attr.selectattrs(self.defaultarrowattrs + self.arrowattrs, selectindex, selecttotal)
895 else:
896 privatedata.arrowattrs = None
898 def initdrawpoints(self, privatedata, sharedata, graph):
899 privatedata.arrowcanvas = canvas.canvas()
901 def drawpoint(self, privatedata, sharedata, graph, point):
902 if privatedata.lineattrs is not None and privatedata.arrowattrs is not None and sharedata.vposvalid:
903 linelength_pt = unit.topt(self.linelength)
904 x_pt, y_pt = graph.vpos_pt(*sharedata.vpos)
905 try:
906 angle = point["angle"] + 0.0
907 size = point["size"] + 0.0
908 except:
909 pass
910 else:
911 if point["size"] > self.epsilon:
912 dx = math.cos(angle*math.pi/180)
913 dy = math.sin(angle*math.pi/180)
914 x1 = x_pt-self.arrowpos*dx*linelength_pt*size
915 y1 = y_pt-self.arrowpos*dy*linelength_pt*size
916 x2 = x_pt+(1-self.arrowpos)*dx*linelength_pt*size
917 y2 = y_pt+(1-self.arrowpos)*dy*linelength_pt*size
918 privatedata.arrowcanvas.stroke(path.line_pt(x1, y1, x2, y2), privatedata.lineattrs +
919 [deco.earrow(privatedata.arrowattrs, size=self.arrowsize*size)])
921 def donedrawpoints(self, privatedata, sharedata, graph):
922 graph.insert(privatedata.arrowcanvas)
924 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
925 raise RuntimeError("Style currently doesn't provide a graph key")
928 class rect(_style):
930 needsdata = ["vrange", "vrangeminmissing", "vrangemaxmissing"]
932 def __init__(self, gradient=color.gradient.Grey):
933 self.gradient = gradient
935 def columnnames(self, privatedata, sharedata, graph, columnnames):
936 if len(graph.axesnames) != 2:
937 raise TypeError("arrow style restricted on two-dimensional graphs")
938 if "color" not in columnnames:
939 raise ValueError("color missing")
940 if len(sharedata.vrangeminmissing) + len(sharedata.vrangemaxmissing):
941 raise ValueError("incomplete range")
942 return ["color"]
944 def initdrawpoints(self, privatedata, sharedata, graph):
945 privatedata.rectcanvas = graph.insert(canvas.canvas())
947 def drawpoint(self, privatedata, sharedata, graph, point):
948 xvmin = sharedata.vrange[0][0]
949 xvmax = sharedata.vrange[0][1]
950 yvmin = sharedata.vrange[1][0]
951 yvmax = sharedata.vrange[1][1]
952 if (xvmin is not None and xvmin < 1 and
953 xvmax is not None and xvmax > 0 and
954 yvmin is not None and yvmin < 1 and
955 yvmax is not None and yvmax > 0):
956 if xvmin < 0:
957 xvmin = 0
958 elif xvmax > 1:
959 xvmax = 1
960 if yvmin < 0:
961 yvmin = 0
962 elif yvmax > 1:
963 yvmax = 1
964 p = graph.vgeodesic(xvmin, yvmin, xvmax, yvmin)
965 p.append(graph.vgeodesic_el(xvmax, yvmin, xvmax, yvmax))
966 p.append(graph.vgeodesic_el(xvmax, yvmax, xvmin, yvmax))
967 p.append(graph.vgeodesic_el(xvmin, yvmax, xvmin, yvmin))
968 p.append(path.closepath())
969 privatedata.rectcanvas.fill(p, [self.gradient.getcolor(point["color"])])
971 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
972 raise RuntimeError("Style currently doesn't provide a graph key")
975 class histogram(_style):
977 needsdata = ["vpos", "vposmissing", "vrange", "vrangeminmissing", "vrangemaxmissing"]
979 defaultlineattrs = [deco.stroked]
980 defaultfrompathattrs = []
982 def __init__(self, lineattrs=[], steps=0, fromvalue=0, frompathattrs=[], fillable=0, rectkey=0,
983 autohistogramaxisindex=0, autohistogrampointpos=0.5, epsilon=1e-10):
984 self.lineattrs = lineattrs
985 self.steps = steps
986 self.fromvalue = fromvalue
987 self.frompathattrs = frompathattrs
988 self.fillable = fillable # TODO: fillable paths might not properly be closed by straight lines on curved graph geometries
989 self.rectkey = rectkey
990 self.autohistogramaxisindex = autohistogramaxisindex
991 self.autohistogrampointpos = autohistogrampointpos
992 self.epsilon = epsilon
994 def columnnames(self, privatedata, sharedata, graph, columnnames):
995 if len(graph.axesnames) != 2:
996 raise TypeError("histogram style restricted on two-dimensional graphs")
997 privatedata.rangeaxisindex = None
998 for i in builtinrange(len(graph.axesnames)):
999 if i in sharedata.vrangeminmissing or i in sharedata.vrangemaxmissing:
1000 if i in sharedata.vposmissing:
1001 raise ValueError("pos and range missing")
1002 else:
1003 if privatedata.rangeaxisindex is not None:
1004 raise ValueError("multiple ranges")
1005 privatedata.rangeaxisindex = i
1006 if privatedata.rangeaxisindex is None:
1007 privatedata.rangeaxisindex = self.autohistogramaxisindex
1008 privatedata.autohistogram = 1
1009 else:
1010 privatedata.autohistogram = 0
1011 return []
1013 def adjustaxis(self, privatedata, sharedata, graph, columnname, data):
1014 if privatedata.autohistogram and columnname == sharedata.poscolumnnames[privatedata.rangeaxisindex]:
1015 if len(data) == 1:
1016 raise ValueError("several data points needed for automatic histogram width calculation")
1017 if data:
1018 delta = data[1] - data[0]
1019 min = data[0] - self.autohistogrampointpos * delta
1020 max = data[-1] + (1-self.autohistogrampointpos) * delta
1021 graph.axes[columnname].adjustaxis([min, max])
1022 elif self.fromvalue is not None and columnname == sharedata.poscolumnnames[1-privatedata.rangeaxisindex]:
1023 graph.axes[columnname].adjustaxis([self.fromvalue])
1025 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1026 privatedata.insertfrompath = selectindex == 0
1027 if self.lineattrs is not None:
1028 privatedata.lineattrs = attr.selectattrs(self.defaultlineattrs + self.lineattrs, selectindex, selecttotal)
1029 else:
1030 privatedata.lineattrs = None
1032 def vmoveto(self, privatedata, sharedata, graph, vpos, vvalue):
1033 if -self.epsilon < vpos < 1+self.epsilon and -self.epsilon < vvalue < 1+self.epsilon:
1034 if privatedata.rangeaxisindex:
1035 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue, vpos)))
1036 else:
1037 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vpos, vvalue)))
1039 def vposline(self, privatedata, sharedata, graph, vpos, vvalue1, vvalue2):
1040 if -self.epsilon < vpos < 1+self.epsilon:
1041 vvalue1cut = 0
1042 if vvalue1 < 0:
1043 vvalue1 = 0
1044 vvalue1cut = -1
1045 elif vvalue1 > 1:
1046 vvalue1 = 1
1047 vvalue1cut = 1
1048 vvalue2cut = 0
1049 if vvalue2 < 0:
1050 vvalue2 = 0
1051 vvalue2cut = -1
1052 elif vvalue2 > 1:
1053 vvalue2 = 1
1054 vvalue2cut = 1
1055 if abs(vvalue1cut + vvalue2cut) <= 1:
1056 if vvalue1cut and not self.fillable:
1057 if privatedata.rangeaxisindex:
1058 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue1, vpos)))
1059 else:
1060 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vpos, vvalue1)))
1061 if privatedata.rangeaxisindex:
1062 privatedata.path.append(graph.vgeodesic_el(vvalue1, vpos, vvalue2, vpos))
1063 else:
1064 privatedata.path.append(graph.vgeodesic_el(vpos, vvalue1, vpos, vvalue2))
1066 def vvalueline(self, privatedata, sharedata, graph, vvalue, vpos1, vpos2):
1067 if self.fillable:
1068 if vvalue < -self.epsilon:
1069 vvalue = 0
1070 warnings.warn("cut at graph boundary adds artificial lines to fillable step histogram path")
1071 if vvalue > 1+self.epsilon:
1072 vvalue = 1
1073 warnings.warn("cut at graph boundary adds artificial lines to fillable step histogram path")
1074 if self.fillable or (-self.epsilon < vvalue < 1+self.epsilon):
1075 vpos1cut = 0
1076 if vpos1 < 0:
1077 vpos1 = 0
1078 vpos1cut = -1
1079 elif vpos1 > 1:
1080 vpos1 = 1
1081 vpos1cut = 1
1082 vpos2cut = 0
1083 if vpos2 < 0:
1084 vpos2 = 0
1085 vpos2cut = -1
1086 elif vpos2 > 1:
1087 vpos2 = 1
1088 vpos2cut = 1
1089 if abs(vpos1cut + vpos2cut) <= 1:
1090 if vpos1cut:
1091 if self.fillable:
1092 if privatedata.rangeaxisindex:
1093 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(privatedata.vfromvalue, vpos1)))
1094 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vpos1, vvalue, vpos1))
1095 else:
1096 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vpos1, privatedata.vfromvalue)))
1097 privatedata.path.append(graph.vgeodesic_el(vpos1, privatedata.vfromvalue, vpos1, vvalue))
1098 else:
1099 if privatedata.rangeaxisindex:
1100 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue, vpos1)))
1101 else:
1102 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vpos1, vvalue)))
1103 if privatedata.rangeaxisindex:
1104 privatedata.path.append(graph.vgeodesic_el(vvalue, vpos1, vvalue, vpos2))
1105 else:
1106 privatedata.path.append(graph.vgeodesic_el(vpos1, vvalue, vpos2, vvalue))
1107 if self.fillable and vpos2cut:
1108 warnings.warn("cut at graph boundary adds artificial lines to fillable step histogram path")
1109 if privatedata.rangeaxisindex:
1110 privatedata.path.append(graph.vgeodesic_el(vvalue, vpos2, privatedata.vfromvalue, vpos2))
1111 else:
1112 privatedata.path.append(graph.vgeodesic_el(vpos2, vvalue, vpos2, privatedata.vfromvalue))
1114 def drawvalue(self, privatedata, sharedata, graph, vmin, vmax, vvalue):
1115 currentvalid = vmin is not None and vmax is not None and vvalue is not None
1116 if self.fillable and not self.steps:
1117 if not currentvalid:
1118 return
1119 vmincut = 0
1120 if vmin < -self.epsilon:
1121 vmin = 0
1122 vmincut = -1
1123 elif vmin > 1+self.epsilon:
1124 vmin = 1
1125 vmincut = 1
1126 vmaxcut = 0
1127 if vmax < -self.epsilon:
1128 vmax = 0
1129 vmaxcut = -1
1130 if vmax > 1+self.epsilon:
1131 vmax = 1
1132 vmaxcut = 1
1133 vvaluecut = 0
1134 if vvalue < -self.epsilon:
1135 vvalue = 0
1136 vvaluecut = -1
1137 if vvalue > 1+self.epsilon:
1138 vvalue = 1
1139 vvaluecut = 1
1140 done = 0
1141 if abs(vmincut) + abs(vmaxcut) + abs(vvaluecut) + abs(privatedata.vfromvaluecut) > 1:
1142 if abs(vmincut + vmaxcut) > 1 or abs(vvaluecut+privatedata.vfromvaluecut) > 1:
1143 done = 1
1144 else:
1145 warnings.warn("multiple cuts at graph boundary add artificial lines to fillable rectangle histogram path")
1146 elif vmincut:
1147 done = 1
1148 if privatedata.rangeaxisindex:
1149 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(privatedata.vfromvalue, vmin)))
1150 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmin, privatedata.vfromvalue, vmax))
1151 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmax, vvalue, vmax))
1152 privatedata.path.append(graph.vgeodesic_el(vvalue, vmax, vvalue, vmin))
1153 else:
1154 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmin, privatedata.vfromvalue)))
1155 privatedata.path.append(graph.vgeodesic_el(vmin, privatedata.vfromvalue, vmax, privatedata.vfromvalue))
1156 privatedata.path.append(graph.vgeodesic_el(vmax, privatedata.vfromvalue, vmax, vvalue))
1157 privatedata.path.append(graph.vgeodesic_el(vmax, vvalue, vmin, vvalue))
1158 elif vmaxcut:
1159 done = 1
1160 if privatedata.rangeaxisindex:
1161 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue, vmax)))
1162 privatedata.path.append(graph.vgeodesic_el(vvalue, vmax, vvalue, vmin))
1163 privatedata.path.append(graph.vgeodesic_el(vvalue, vmin, privatedata.vfromvalue, vmin))
1164 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmin, privatedata.vfromvalue, vmax))
1165 else:
1166 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmax, vvalue)))
1167 privatedata.path.append(graph.vgeodesic_el(vmax, vvalue, vmin, vvalue))
1168 privatedata.path.append(graph.vgeodesic_el(vmin, vvalue, vmin, privatedata.vfromvalue))
1169 privatedata.path.append(graph.vgeodesic_el(vmin, privatedata.vfromvalue, vmax, privatedata.vfromvalue))
1170 elif privatedata.vfromvaluecut:
1171 done = 1
1172 if privatedata.rangeaxisindex:
1173 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(privatedata.vfromvalue, vmax)))
1174 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmax, vvalue, vmax))
1175 privatedata.path.append(graph.vgeodesic_el(vvalue, vmax, vvalue, vmin))
1176 privatedata.path.append(graph.vgeodesic_el(vvalue, vmin, privatedata.vfromvalue, vmin))
1177 else:
1178 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmax, privatedata.vfromvalue)))
1179 privatedata.path.append(graph.vgeodesic_el(vmax, privatedata.vfromvalue, vmax, vvalue))
1180 privatedata.path.append(graph.vgeodesic_el(vmax, vvalue, vmin, vvalue))
1181 privatedata.path.append(graph.vgeodesic_el(vmin, vvalue, vmin, privatedata.vfromvalue))
1182 elif vvaluecut:
1183 done = 1
1184 if privatedata.rangeaxisindex:
1185 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue, vmin)))
1186 privatedata.path.append(graph.vgeodesic_el(vvalue, vmin, privatedata.vfromvalue, vmin))
1187 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmin, privatedata.vfromvalue, vmax))
1188 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmax, vvalue, vmax))
1189 else:
1190 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmin, vvalue)))
1191 privatedata.path.append(graph.vgeodesic_el(vmin, vvalue, vmin, privatedata.vfromvalue))
1192 privatedata.path.append(graph.vgeodesic_el(vmin, privatedata.vfromvalue, vmax, privatedata.vfromvalue))
1193 privatedata.path.append(graph.vgeodesic_el(vmax, privatedata.vfromvalue, vmax, vvalue))
1194 if not done:
1195 if privatedata.rangeaxisindex:
1196 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(privatedata.vfromvalue, vmin)))
1197 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmin, privatedata.vfromvalue, vmax))
1198 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmax, vvalue, vmax))
1199 privatedata.path.append(graph.vgeodesic_el(vvalue, vmax, vvalue, vmin))
1200 privatedata.path.append(graph.vgeodesic_el(vvalue, vmin, privatedata.vfromvalue, vmin))
1201 privatedata.path.append(path.closepath())
1202 else:
1203 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmin, privatedata.vfromvalue)))
1204 privatedata.path.append(graph.vgeodesic_el(vmin, privatedata.vfromvalue, vmax, privatedata.vfromvalue))
1205 privatedata.path.append(graph.vgeodesic_el(vmax, privatedata.vfromvalue, vmax, vvalue))
1206 privatedata.path.append(graph.vgeodesic_el(vmax, vvalue, vmin, vvalue))
1207 privatedata.path.append(graph.vgeodesic_el(vmin, vvalue, vmin, privatedata.vfromvalue))
1208 privatedata.path.append(path.closepath())
1209 else:
1210 try:
1211 gap = abs(vmin - privatedata.lastvmax) > self.epsilon
1212 except (ArithmeticError, ValueError, TypeError):
1213 gap = 1
1214 if (privatedata.lastvvalue is not None and currentvalid and not gap and
1215 (self.steps or (privatedata.lastvvalue-privatedata.vfromvalue)*(vvalue-privatedata.vfromvalue) < 0)):
1216 self.vposline(privatedata, sharedata, graph,
1217 vmin, privatedata.lastvvalue, vvalue)
1218 else:
1219 if privatedata.lastvvalue is not None and currentvalid:
1220 currentbigger = abs(privatedata.lastvvalue-privatedata.vfromvalue) < abs(vvalue-privatedata.vfromvalue)
1221 if privatedata.lastvvalue is not None and (not currentvalid or not currentbigger or gap):
1222 self.vposline(privatedata, sharedata, graph,
1223 privatedata.lastvmax, privatedata.lastvvalue, privatedata.vfromvalue)
1224 if currentvalid:
1225 self.vmoveto(privatedata, sharedata, graph,
1226 vmin, vvalue)
1227 if currentvalid and (privatedata.lastvvalue is None or currentbigger or gap):
1228 self.vmoveto(privatedata, sharedata, graph,
1229 vmin, privatedata.vfromvalue)
1230 self.vposline(privatedata, sharedata, graph,
1231 vmin, privatedata.vfromvalue, vvalue)
1232 if currentvalid:
1233 self.vvalueline(privatedata, sharedata, graph,
1234 vvalue, vmin, vmax)
1235 privatedata.lastvvalue = vvalue
1236 privatedata.lastvmax = vmax
1237 else:
1238 privatedata.lastvvalue = privatedata.lastvmax = None
1240 def initdrawpoints(self, privatedata, sharedata, graph):
1241 privatedata.path = path.path()
1242 privatedata.lastvvalue = privatedata.lastvmax = None
1243 privatedata.vcurrentpoint = None
1244 privatedata.count = 0
1245 if self.fromvalue is not None:
1246 valueaxisname = sharedata.poscolumnnames[1-privatedata.rangeaxisindex]
1247 privatedata.vfromvalue = graph.axes[valueaxisname].convert(self.fromvalue)
1248 privatedata.vfromvaluecut = 0
1249 if privatedata.vfromvalue < 0:
1250 privatedata.vfromvalue = 0
1251 privatedata.vfromvaluecut = -1
1252 if privatedata.vfromvalue > 1:
1253 privatedata.vfromvalue = 1
1254 privatedata.vfromvaluecut = 1
1255 if self.frompathattrs is not None and privatedata.insertfrompath:
1256 graph.stroke(graph.axes[valueaxisname].vgridpath(privatedata.vfromvalue),
1257 self.defaultfrompathattrs + self.frompathattrs)
1258 else:
1259 privatedata.vfromvalue = 0
1261 def drawpoint(self, privatedata, sharedata, graph, point):
1262 if privatedata.autohistogram:
1263 # automatic range handling
1264 privatedata.count += 1
1265 if privatedata.count == 2:
1266 if privatedata.rangeaxisindex:
1267 privatedata.vrange = sharedata.vpos[1] - privatedata.lastvpos[1]
1268 self.drawvalue(privatedata, sharedata, graph,
1269 privatedata.lastvpos[1] - self.autohistogrampointpos*privatedata.vrange,
1270 privatedata.lastvpos[1] + (1-self.autohistogrampointpos)*privatedata.vrange,
1271 privatedata.lastvpos[0])
1272 else:
1273 privatedata.vrange = sharedata.vpos[0] - privatedata.lastvpos[0]
1274 self.drawvalue(privatedata, sharedata, graph,
1275 privatedata.lastvpos[0] - self.autohistogrampointpos*privatedata.vrange,
1276 privatedata.lastvpos[0] + (1-self.autohistogrampointpos)*privatedata.vrange,
1277 privatedata.lastvpos[1])
1278 elif privatedata.count > 2:
1279 if privatedata.rangeaxisindex:
1280 vrange = sharedata.vpos[1] - privatedata.lastvpos[1]
1281 else:
1282 vrange = sharedata.vpos[0] - privatedata.lastvpos[0]
1283 if abs(privatedata.vrange - vrange) > self.epsilon:
1284 raise ValueError("equal steps (in graph coordinates) needed for automatic width calculation")
1285 if privatedata.count > 1:
1286 if privatedata.rangeaxisindex:
1287 self.drawvalue(privatedata, sharedata, graph,
1288 sharedata.vpos[1] - self.autohistogrampointpos*privatedata.vrange,
1289 sharedata.vpos[1] + (1-self.autohistogrampointpos)*privatedata.vrange,
1290 sharedata.vpos[0])
1291 else:
1292 self.drawvalue(privatedata, sharedata, graph,
1293 sharedata.vpos[0] - self.autohistogrampointpos*privatedata.vrange,
1294 sharedata.vpos[0] + (1-self.autohistogrampointpos)*privatedata.vrange,
1295 sharedata.vpos[1])
1296 privatedata.lastvpos = sharedata.vpos[:]
1297 else:
1298 if privatedata.rangeaxisindex:
1299 self.drawvalue(privatedata, sharedata, graph,
1300 sharedata.vrange[1][0], sharedata.vrange[1][1], sharedata.vpos[0])
1301 else:
1302 self.drawvalue(privatedata, sharedata, graph,
1303 sharedata.vrange[0][0], sharedata.vrange[0][1], sharedata.vpos[1])
1305 def donedrawpoints(self, privatedata, sharedata, graph):
1306 self.drawvalue(privatedata, sharedata, graph, None, None, None)
1307 if privatedata.lineattrs is not None and len(privatedata.path):
1308 graph.draw(privatedata.path, privatedata.lineattrs)
1310 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
1311 if privatedata.lineattrs is not None:
1312 if self.rectkey:
1313 p = path.rect_pt(x_pt, y_pt, width_pt, height_pt)
1314 else:
1315 p = path.line_pt(x_pt, y_pt+0.5*height_pt, x_pt+width_pt, y_pt+0.5*height_pt)
1316 graph.draw(p, privatedata.lineattrs)
1319 class barpos(_style):
1321 providesdata = ["vpos", "vposmissing", "vposavailable", "vposvalid", "vbarrange", "barposcolumnnames", "barvalueindex", "lastbarvalue", "stackedbar", "stackedbardraw"]
1323 defaultfrompathattrs = []
1325 def __init__(self, fromvalue=None, frompathattrs=[], epsilon=1e-10):
1326 self.fromvalue = fromvalue
1327 self.frompathattrs = frompathattrs
1328 self.epsilon = epsilon
1330 def columnnames(self, privatedata, sharedata, graph, columnnames):
1331 sharedata.barposcolumnnames = []
1332 sharedata.barvalueindex = None
1333 for dimension, axisnames in enumerate(graph.axesnames):
1334 found = 0
1335 for axisname in axisnames:
1336 if axisname in columnnames:
1337 if sharedata.barvalueindex is not None:
1338 raise ValueError("multiple values")
1339 sharedata.barvalueindex = dimension
1340 sharedata.barposcolumnnames.append(axisname)
1341 found += 1
1342 if (axisname + "name") in columnnames:
1343 sharedata.barposcolumnnames.append(axisname + "name")
1344 found += 1
1345 if found > 1:
1346 raise ValueError("multiple names and value")
1347 if not found:
1348 raise ValueError("value/name missing")
1349 if sharedata.barvalueindex is None:
1350 raise ValueError("missing value")
1351 sharedata.vposmissing = []
1352 return sharedata.barposcolumnnames
1354 def addsubvalue(self, value, subvalue):
1355 try:
1356 value + ""
1357 except:
1358 try:
1359 return value[0], self.addsubvalue(value[1], subvalue)
1360 except:
1361 return value, subvalue
1362 else:
1363 return value, subvalue
1365 def adjustaxis(self, privatedata, sharedata, graph, columnname, data):
1366 try:
1367 i = sharedata.barposcolumnnames.index(columnname)
1368 except ValueError:
1369 pass
1370 else:
1371 if i == sharedata.barvalueindex:
1372 if self.fromvalue is not None:
1373 graph.axes[sharedata.barposcolumnnames[i]].adjustaxis([self.fromvalue])
1374 graph.axes[sharedata.barposcolumnnames[i]].adjustaxis(data)
1375 else:
1376 graph.axes[sharedata.barposcolumnnames[i][:-4]].adjustaxis([self.addsubvalue(x, 0) for x in data])
1377 graph.axes[sharedata.barposcolumnnames[i][:-4]].adjustaxis([self.addsubvalue(x, 1) for x in data])
1379 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1380 privatedata.insertfrompath = selectindex == 0
1382 def initdrawpoints(self, privatedata, sharedata, graph):
1383 sharedata.vpos = [None]*(len(sharedata.barposcolumnnames))
1384 sharedata.vbarrange = [[None for i in xrange(2)] for x in sharedata.barposcolumnnames]
1385 sharedata.stackedbar = sharedata.stackedbardraw = 0
1387 if self.fromvalue is not None:
1388 privatedata.vfromvalue = graph.axes[sharedata.barposcolumnnames[sharedata.barvalueindex][0]].convert(self.fromvalue)
1389 if privatedata.vfromvalue < 0:
1390 privatedata.vfromvalue = 0
1391 if privatedata.vfromvalue > 1:
1392 privatedata.vfromvalue = 1
1393 if self.frompathattrs is not None and privatedata.insertfrompath:
1394 graph.stroke(graph.axes[sharedata.barposcolumnnames[sharedata.barvalueindex][0]].vgridpath(privatedata.vfromvalue),
1395 self.defaultfrompathattrs + self.frompathattrs)
1396 else:
1397 privatedata.vfromvalue = 0
1399 def drawpoint(self, privatedata, sharedata, graph, point):
1400 sharedata.vposavailable = sharedata.vposvalid = 1
1401 for i, barname in enumerate(sharedata.barposcolumnnames):
1402 if i == sharedata.barvalueindex:
1403 sharedata.vbarrange[i][0] = privatedata.vfromvalue
1404 sharedata.lastbarvalue = point[barname]
1405 try:
1406 sharedata.vpos[i] = sharedata.vbarrange[i][1] = graph.axes[barname].convert(sharedata.lastbarvalue)
1407 except (ArithmeticError, ValueError, TypeError):
1408 sharedata.vpos[i] = sharedata.vbarrange[i][1] = None
1409 else:
1410 for j in xrange(2):
1411 try:
1412 sharedata.vbarrange[i][j] = graph.axes[barname[:-4]].convert(self.addsubvalue(point[barname], j))
1413 except (ArithmeticError, ValueError, TypeError):
1414 sharedata.vbarrange[i][j] = None
1415 try:
1416 sharedata.vpos[i] = 0.5*(sharedata.vbarrange[i][0]+sharedata.vbarrange[i][1])
1417 except (ArithmeticError, ValueError, TypeError):
1418 sharedata.vpos[i] = None
1419 if sharedata.vpos[i] is None:
1420 sharedata.vposavailable = sharedata.vposvalid = 0
1421 elif sharedata.vpos[i] < -self.epsilon or sharedata.vpos[i] > 1+self.epsilon:
1422 sharedata.vposvalid = 0
1424 registerdefaultprovider(barpos(), ["vbarrange", "barposcolumnnames", "barvalueindex", "lastbarvalue", "stackedbar", "stackedbardraw"])
1427 class stackedbarpos(_style):
1429 # provides no additional data, but needs some data (and modifies some of them)
1430 needsdata = ["vbarrange", "barposcolumnnames", "barvalueindex", "lastbarvalue", "stackedbar", "stackedbardraw"]
1432 def __init__(self, stackname, addontop=0, epsilon=1e-10):
1433 self.stackname = stackname
1434 self.epsilon = epsilon
1435 self.addontop = addontop
1437 def columnnames(self, privatedata, sharedata, graph, columnnames):
1438 if self.stackname not in columnnames:
1439 raise ValueError("column '%s' missing" % self.stackname)
1440 return [self.stackname]
1442 def adjustaxis(self, privatedata, sharedata, graph, columnname, data):
1443 if columnname == self.stackname:
1444 graph.axes[sharedata.barposcolumnnames[sharedata.barvalueindex]].adjustaxis(data)
1446 def initdrawpoints(self, privatedata, sharedata, graph):
1447 if sharedata.stackedbardraw: # do not count the start bar when not gets painted
1448 sharedata.stackedbar += 1
1450 def drawpoint(self, privatedata, sharedata, graph, point):
1451 sharedata.vbarrange[sharedata.barvalueindex][0] = sharedata.vbarrange[sharedata.barvalueindex][1]
1452 if self.addontop:
1453 try:
1454 sharedata.lastbarvalue += point[self.stackname]
1455 except (ArithmeticError, ValueError, TypeError):
1456 sharedata.lastbarvalue = None
1457 else:
1458 sharedata.lastbarvalue = point[self.stackname]
1459 try:
1460 sharedata.vpos[sharedata.barvalueindex] = sharedata.vbarrange[sharedata.barvalueindex][1] = graph.axes[sharedata.barposcolumnnames[sharedata.barvalueindex]].convert(sharedata.lastbarvalue)
1461 except (ArithmeticError, ValueError, TypeError):
1462 sharedata.vpos[sharedata.barvalueindex] = sharedata.vbarrange[sharedata.barvalueindex][1] = None
1463 sharedata.vposavailable = sharedata.vposvalid = 0
1464 else:
1465 if not sharedata.vposavailable or not sharedata.vposvalid:
1466 sharedata.vposavailable = sharedata.vposvalid = 1
1467 for v in sharedata.vpos:
1468 if v is None:
1469 sharedata.vposavailable = sharedata.vposvalid = 0
1470 break
1471 if v < -self.epsilon or v > 1+self.epsilon:
1472 sharedata.vposvalid = 0
1475 class bar(_style):
1477 needsdata = ["vbarrange"]
1479 defaultbarattrs = [color.gradient.Rainbow, deco.stroked([color.grey.black])]
1481 def __init__(self, barattrs=[]):
1482 self.barattrs = barattrs
1484 def columnnames(self, privatedata, sharedata, graph, columnnames):
1485 if len(graph.axesnames) != 2:
1486 raise TypeError("bar style restricted on two-dimensional graphs")
1487 return []
1489 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1490 privatedata.barattrs = attr.selectattrs(self.defaultbarattrs + self.barattrs, selectindex, selecttotal)
1492 def initdrawpoints(self, privatedata, sharedata, graph):
1493 privatedata.rectcanvas = graph.insert(canvas.canvas())
1494 sharedata.stackedbardraw = 1
1495 privatedata.stackedbar = sharedata.stackedbar
1497 def drawpointfill(self, privatedata, p):
1498 if p:
1499 privatedata.rectcanvas.fill(p, privatedata.barattrs)
1501 def drawpoint(self, privatedata, sharedata, graph, point):
1502 xvmin = sharedata.vbarrange[0][0]
1503 xvmax = sharedata.vbarrange[0][1]
1504 yvmin = sharedata.vbarrange[1][0]
1505 yvmax = sharedata.vbarrange[1][1]
1506 try:
1507 if xvmin > xvmax:
1508 xvmin, xvmax = xvmax, xvmin
1509 except:
1510 pass
1511 try:
1512 if yvmin > yvmax:
1513 yvmin, yvmax = yvmax, yvmin
1514 except:
1515 pass
1516 if (xvmin is not None and xvmin < 1 and
1517 xvmax is not None and xvmax > 0 and
1518 yvmin is not None and yvmin < 1 and
1519 yvmax is not None and yvmax > 0):
1520 if xvmin < 0:
1521 xvmin = 0
1522 elif xvmax > 1:
1523 xvmax = 1
1524 if yvmin < 0:
1525 yvmin = 0
1526 elif yvmax > 1:
1527 yvmax = 1
1528 p = graph.vgeodesic(xvmin, yvmin, xvmax, yvmin)
1529 p.append(graph.vgeodesic_el(xvmax, yvmin, xvmax, yvmax))
1530 p.append(graph.vgeodesic_el(xvmax, yvmax, xvmin, yvmax))
1531 p.append(graph.vgeodesic_el(xvmin, yvmax, xvmin, yvmin))
1532 p.append(path.closepath())
1533 self.drawpointfill(privatedata, p)
1534 else:
1535 self.drawpointfill(privatedata, None)
1537 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
1538 selectindex = privatedata.stackedbar
1539 selecttotal = sharedata.stackedbar + 1
1540 graph.fill(path.rect_pt(x_pt + width_pt*selectindex/float(selecttotal), y_pt, width_pt/float(selecttotal), height_pt), privatedata.barattrs)
1543 class changebar(bar):
1545 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1546 if selecttotal != 1:
1547 raise RuntimeError("Changebar can't change its appearance. Thus you can't use it to plot several bars side by side on a subaxis.")
1549 def initdrawpoints(self, privatedata, sharedata, graph):
1550 bar.initdrawpoints(self, privatedata, sharedata, graph)
1551 privatedata.bars = []
1553 def drawpointfill(self, privatedata, p):
1554 privatedata.bars.append(p)
1556 def donedrawpoints(self, privatedata, sharedata, graph):
1557 selecttotal = len(privatedata.bars)
1558 for selectindex, p in enumerate(privatedata.bars):
1559 if p:
1560 barattrs = attr.selectattrs(self.defaultbarattrs + self.barattrs, selectindex, selecttotal)
1561 privatedata.rectcanvas.fill(p, barattrs)
1563 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
1564 raise RuntimeError("Style currently doesn't provide a graph key")
1567 class _surface:
1569 needsdata = ["vpos", "vposmissing", "vposavailable", "vposvalid"]
1571 def __init__(self, index1=0, index2=1, strokelines1=1, strokelines2=1, gridattrs=[],
1572 epsilon=1e-10):
1573 self.index1 = index1
1574 self.index2 = index2
1575 self.epsilon = epsilon
1577 def initdrawpoints(self, privatedata, sharedata, graph):
1578 privatedata.values1 = {}
1579 privatedata.values2 = {}
1580 privatedata.data12 = {}
1581 privatedata.data21 = {}
1583 def drawpoint(self, privatedata, sharedata, graph, point):
1584 if sharedata.vposavailable:
1585 privatedata.value1 = sharedata.vpos[self.index1]
1586 privatedata.value2 = sharedata.vpos[self.index2]
1587 if not privatedata.values1.has_key(privatedata.value1):
1588 for hasvalue in privatedata.values1.keys():
1589 if hasvalue - self.epsilon <= privatedata.value1 <= hasvalue + self.epsilon:
1590 privatedata.value1 = hasvalue
1591 break
1592 else:
1593 privatedata.values1[privatedata.value1] = 1
1594 if not privatedata.values2.has_key(privatedata.value2):
1595 for hasvalue in privatedata.values2.keys():
1596 if hasvalue - self.epsilon <= privatedata.value2 <= hasvalue + self.epsilon:
1597 privatedata.value2 = hasvalue
1598 break
1599 else:
1600 privatedata.values2[privatedata.value2] = 1
1601 data = sharedata.vposavailable, sharedata.vposvalid, sharedata.vpos[:]
1602 privatedata.data12.setdefault(privatedata.value1, {})[privatedata.value2] = data
1603 privatedata.data21.setdefault(privatedata.value2, {})[privatedata.value1] = data
1605 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
1606 raise NotImplementedError
1609 class grid(_surface, _line):
1611 defaultgridattrs = []
1613 def __init__(self, strokelines1=1, strokelines2=1, gridattrs=[], **kwargs):
1614 _surface.__init__(self, **kwargs)
1615 self.strokelines1 = strokelines1
1616 self.strokelines2 = strokelines2
1617 self.gridattrs = gridattrs
1619 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1620 if self.gridattrs is not None:
1621 privatedata.gridattrs = attr.selectattrs(self.defaultgridattrs + self.gridattrs, selectindex, selecttotal)
1622 else:
1623 privatedata.gridattrs = None
1625 def donedrawpoints(self, privatedata, sharedata, graph):
1626 values1 = privatedata.values1.keys()
1627 values1.sort()
1628 values2 = privatedata.values2.keys()
1629 values2.sort()
1630 if self.strokelines1:
1631 for value2 in values2:
1632 data1 = privatedata.data21[value2]
1633 self.initpointstopath(privatedata)
1634 for value1 in values1:
1635 try:
1636 data = data1[value1]
1637 except KeyError:
1638 self.addinvalid(privatedata)
1639 else:
1640 self.addpoint(privatedata, graph.vpos_pt, *data)
1641 p = self.donepointstopath(privatedata)
1642 if len(p):
1643 graph.stroke(p, privatedata.gridattrs)
1644 if self.strokelines2:
1645 for value1 in values1:
1646 data2 = privatedata.data12[value1]
1647 self.initpointstopath(privatedata)
1648 for value2 in values2:
1649 try:
1650 data = data2[value2]
1651 except KeyError:
1652 self.addinvalid(privatedata)
1653 else:
1654 self.addpoint(privatedata, graph.vpos_pt, *data)
1655 p = self.donepointstopath(privatedata)
1656 if len(p):
1657 graph.stroke(p, privatedata.gridattrs)
1660 class surface(_surface, _style):
1662 needsdata = ["vpos", "vposmissing", "vposavailable", "vposvalid"]
1664 def __init__(self, colorname="color", gradient=color.gradient.Rainbow,
1665 mincolor=None, maxcolor=None, **kwargs):
1666 _surface.__init__(self, **kwargs)
1667 self.colorname = colorname
1668 self.gradient = gradient
1669 self.mincolor = mincolor
1670 self.maxcolor = maxcolor
1672 def columnnames(self, privatedata, sharedata, graph, columnnames):
1673 privatedata.colorize = self.colorname in columnnames
1674 return privatedata.colorize and [self.colorname] or [] + _surface.columnnames(self, privatedata, sharedata, graph, columnnames)
1676 def initdrawpoints(self, privatedata, sharedata, graph):
1677 _surface.initdrawpoints(self, privatedata, sharedata, graph)
1678 privatedata.colors = {}
1679 privatedata.mincolor = privatedata.maxcolor = None
1681 def drawpoint(self, privatedata, sharedata, graph, point):
1682 _surface.drawpoint(self, privatedata, sharedata, graph, point)
1683 if sharedata.vposavailable:
1684 try:
1685 color = point[self.colorname] + 0
1686 except:
1687 pass
1688 else:
1689 privatedata.colors.setdefault(privatedata.value1, {})[privatedata.value2] = color
1690 if privatedata.mincolor is None or color < privatedata.mincolor:
1691 privatedata.mincolor = color
1692 if privatedata.mincolor is None or privatedata.maxcolor < color:
1693 privatedata.maxcolor = color
1695 def donedrawpoints(self, privatedata, sharedata, graph):
1696 v1 = [0]*len(graph.axesnames)
1697 v2 = [0]*len(graph.axesnames)
1698 v3 = [0]*len(graph.axesnames)
1699 v4 = [0]*len(graph.axesnames)
1700 v1[self.index2] = 0.5
1701 v2[self.index1] = 0.5
1702 v3[self.index1] = 0.5
1703 v3[self.index2] = 1
1704 v4[self.index1] = 1
1705 v4[self.index2] = 0.5
1706 sortElements = [-graph.vzindex(*v1),
1707 -graph.vzindex(*v2),
1708 -graph.vzindex(*v3),
1709 -graph.vzindex(*v4)]
1711 values1 = privatedata.values1.keys()
1712 values1.sort()
1713 v1 = [0]*len(graph.axesnames)
1714 v2 = [0]*len(graph.axesnames)
1715 v1[self.index1] = -1
1716 v2[self.index1] = 1
1717 sign = 1
1718 if graph.vzindex(*v1) < graph.vzindex(*v2):
1719 values1.reverse()
1720 sign *= -1
1721 sortElements = [sortElements[3], sortElements[1], sortElements[2], sortElements[0]]
1723 values2 = privatedata.values2.keys()
1724 values2.sort()
1725 v1 = [0]*len(graph.axesnames)
1726 v2 = [0]*len(graph.axesnames)
1727 v1[self.index2] = -1
1728 v2[self.index2] = 1
1729 if graph.vzindex(*v1) < graph.vzindex(*v2):
1730 values2.reverse()
1731 sign *= -1
1732 sortElements = [sortElements[0], sortElements[2], sortElements[1], sortElements[3]]
1734 sortElements = [(zindex, i) for i, zindex in enumerate(sortElements)]
1735 sortElements.sort()
1736 sortElements = dict([(i, pos) for i, (zindex, pos) in enumerate(sortElements)])
1738 if self.mincolor is not None:
1739 mincolor = self.mincolor
1740 if self.maxcolor is not None:
1741 maxcolor = self.maxcolor
1742 nodes = []
1743 elements = []
1744 for value1a, value1b in zip(values1[:-1], values1[1:]):
1745 for value2a, value2b in zip(values2[:-1], values2[1:]):
1746 try:
1747 available1, valid1, v1 = privatedata.data12[value1a][value2a]
1748 available2, valid2, v2 = privatedata.data12[value1a][value2b]
1749 available3, valid3, v3 = privatedata.data12[value1b][value2a]
1750 available4, valid4, v4 = privatedata.data12[value1b][value2b]
1751 except KeyError:
1752 continue
1753 if not available1 or not available2 or not available3 or not available4:
1754 continue
1755 if not valid1 or not valid2 or not valid3 or not valid4:
1756 warnings.warn("surface elements partially outside of the graph are (currently) skipped completely")
1757 v5 = [0.25*sum(values) for values in zip(v1, v2, v3, v4)]
1758 x1_pt, y1_pt = graph.vpos_pt(*v1)
1759 x2_pt, y2_pt = graph.vpos_pt(*v2)
1760 x3_pt, y3_pt = graph.vpos_pt(*v3)
1761 x4_pt, y4_pt = graph.vpos_pt(*v4)
1762 x5_pt, y5_pt = graph.vpos_pt(*v5)
1763 c1 = privatedata.colors[value1a][value2a]
1764 c2 = privatedata.colors[value1a][value2b]
1765 c3 = privatedata.colors[value1b][value2a]
1766 c4 = privatedata.colors[value1b][value2b]
1767 c5 = 0.25*(c1+c2+c3+c4)
1768 def getcolor(c):
1769 return self.gradient.getcolor((c - privatedata.mincolor) / float(privatedata.maxcolor - privatedata.mincolor)).rgb()
1770 color1 = getcolor(c1)
1771 color2 = getcolor(c2)
1772 color3 = getcolor(c3)
1773 color4 = getcolor(c4)
1774 color5 = getcolor(c5)
1775 if sign*graph.vangle(*(v1+v2+v5)) >= 0:
1776 e1 = mesh.element((mesh.node_pt((x1_pt, y1_pt), color1),
1777 mesh.node_pt((x2_pt, y2_pt), color2),
1778 mesh.node_pt((x5_pt, y5_pt), color5)))
1779 else:
1780 e1 = mesh.element((mesh.node_pt((x1_pt, y1_pt), color.rgb.black),
1781 mesh.node_pt((x2_pt, y2_pt), color.rgb.black),
1782 mesh.node_pt((x5_pt, y5_pt), color.rgb.black)))
1783 if sign*graph.vangle(*(v3+v1+v5)) >= 0:
1784 e2 = mesh.element((mesh.node_pt((x1_pt, y1_pt), color1),
1785 mesh.node_pt((x3_pt, y3_pt), color3),
1786 mesh.node_pt((x5_pt, y5_pt), color5)))
1787 else:
1788 e2 = mesh.element((mesh.node_pt((x1_pt, y1_pt), color.rgb.black),
1789 mesh.node_pt((x3_pt, y3_pt), color.rgb.black),
1790 mesh.node_pt((x5_pt, y5_pt), color.rgb.black)))
1791 if sign*graph.vangle(*(v2+v4+v5)) >= 0:
1792 e3 = mesh.element((mesh.node_pt((x2_pt, y2_pt), color2),
1793 mesh.node_pt((x4_pt, y4_pt), color4),
1794 mesh.node_pt((x5_pt, y5_pt), color5)))
1795 else:
1796 e3 = mesh.element((mesh.node_pt((x2_pt, y2_pt), color.rgb.black),
1797 mesh.node_pt((x4_pt, y4_pt), color.rgb.black),
1798 mesh.node_pt((x5_pt, y5_pt), color.rgb.black)))
1799 if sign*graph.vangle(*(v4+v3+v5)) >= 0:
1800 e4 = mesh.element((mesh.node_pt((x3_pt, y3_pt), color3),
1801 mesh.node_pt((x4_pt, y4_pt), color4),
1802 mesh.node_pt((x5_pt, y5_pt), color5)))
1803 else:
1804 e4 = mesh.element((mesh.node_pt((x3_pt, y3_pt), color.rgb.black),
1805 mesh.node_pt((x4_pt, y4_pt), color.rgb.black),
1806 mesh.node_pt((x5_pt, y5_pt), color.rgb.black)))
1807 elements.extend([[e1, e2, e3, e4][sortElements[i]] for i in builtinrange(4)])
1808 m = mesh.mesh(elements)
1809 graph.insert(m)