separate gridpos and use needsdata and providesdata functionality; multi-level needsd...
[PyX/mjg.git] / pyx / graph / style.py
bloba49cf307a17d176169f6a8ab41178bdb02d8b9f9
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 for key in keys:
125 assert key in style.providesdata, "key not provided by style"
126 # we might allow for overwriting the defaults, i.e. the following is not checked:
127 # assert key in _defaultprovider.keys(), "default provider already registered for key"
128 _defaultprovider[key] = style
130 def getdefaultprovider(key):
131 """returns a style, which acts as a default creator for the
132 sharedata variable 'key'"""
133 return _defaultprovider[key]
136 class pos(_style):
138 providesdata = ["vpos", "vposmissing", "vposavailable", "vposvalid", "poscolumnnames"]
140 def __init__(self, epsilon=1e-10):
141 self.epsilon = epsilon
143 def columnnames(self, privatedata, sharedata, graph, columnnames):
144 sharedata.poscolumnnames = []
145 sharedata.vposmissing = []
146 for count, axisnames in enumerate(graph.axesnames):
147 for axisname in axisnames:
148 for columnname in columnnames:
149 if axisname == columnname:
150 sharedata.poscolumnnames.append(columnname)
151 if len(sharedata.poscolumnnames) > count+1:
152 raise ValueError("multiple axes per graph dimension")
153 elif len(sharedata.poscolumnnames) < count+1:
154 sharedata.vposmissing.append(count)
155 sharedata.poscolumnnames.append(None)
156 return [columnname for columnname in sharedata.poscolumnnames if columnname is not None]
158 def adjustaxis(self, privatedata, sharedata, graph, columnname, data):
159 if columnname in sharedata.poscolumnnames:
160 graph.axes[columnname].adjustaxis(data)
162 def initdrawpoints(self, privatedata, sharedata, graph):
163 sharedata.vpos = [None]*(len(graph.axesnames))
164 privatedata.pointpostmplist = [[columnname, index, graph.axes[columnname]] # temporarily used by drawpoint only
165 for index, columnname in enumerate([columnname for columnname in sharedata.poscolumnnames if columnname is not None])]
166 for missing in sharedata.vposmissing:
167 for pointpostmp in privatedata.pointpostmplist:
168 if pointpostmp[1] >= missing:
169 pointpostmp[1] += 1
171 def drawpoint(self, privatedata, sharedata, graph, point):
172 sharedata.vposavailable = 1 # valid position (but might be outside of the graph)
173 sharedata.vposvalid = 1 # valid position inside the graph
174 for columnname, index, axis in privatedata.pointpostmplist:
175 try:
176 v = axis.convert(point[columnname])
177 except (ArithmeticError, ValueError, TypeError):
178 sharedata.vposavailable = sharedata.vposvalid = 0
179 sharedata.vpos[index] = None
180 else:
181 if v < -self.epsilon or v > 1+self.epsilon:
182 sharedata.vposvalid = 0
183 sharedata.vpos[index] = v
186 registerdefaultprovider(pos(), pos.providesdata)
189 class range(_style):
191 providesdata = ["vrange", "vrangemissing", "vrangeminmissing", "vrangemaxmissing"]
193 # internal bit masks
194 mask_value = 1
195 mask_min = 2
196 mask_max = 4
197 mask_dmin = 8
198 mask_dmax = 16
199 mask_d = 32
201 def __init__(self, usenames={}, epsilon=1e-10):
202 self.usenames = usenames
203 self.epsilon = epsilon
205 def _numberofbits(self, mask):
206 if not mask:
207 return 0
208 if mask & 1:
209 return self._numberofbits(mask >> 1) + 1
210 else:
211 return self._numberofbits(mask >> 1)
213 def columnnames(self, privatedata, sharedata, graph, columnnames):
214 usecolumns = []
215 privatedata.rangeposcolumns = []
216 sharedata.vrangemissing = []
217 sharedata.vrangeminmissing = []
218 sharedata.vrangemaxmissing = []
219 privatedata.rangeposdeltacolumns = {} # temporarily used by adjustaxis only
220 for count, axisnames in enumerate(graph.axesnames):
221 for axisname in axisnames:
222 try:
223 usename = self.usenames[axisname]
224 except KeyError:
225 usename = axisname
226 mask = 0
227 for columnname in columnnames:
228 addusecolumns = 1
229 if usename == columnname:
230 mask += self.mask_value
231 elif usename + "min" == columnname:
232 mask += self.mask_min
233 elif usename + "max" == columnname:
234 mask += self.mask_max
235 elif "d" + usename + "min" == columnname:
236 mask += self.mask_dmin
237 elif "d" + usename + "max" == columnname:
238 mask += self.mask_dmax
239 elif "d" + usename == columnname:
240 mask += self.mask_d
241 else:
242 addusecolumns = 0
243 if addusecolumns:
244 usecolumns.append(columnname)
245 if mask & (self.mask_min | self.mask_max | self.mask_dmin | self.mask_dmax | self.mask_d):
246 if (self._numberofbits(mask & (self.mask_min | self.mask_dmin | self.mask_d)) > 1 or
247 self._numberofbits(mask & (self.mask_max | self.mask_dmax | self.mask_d)) > 1):
248 raise ValueError("multiple range definition")
249 if mask & (self.mask_dmin | self.mask_dmax | self.mask_d):
250 if not (mask & self.mask_value):
251 raise ValueError("missing value for delta")
252 privatedata.rangeposdeltacolumns[axisname] = {}
253 privatedata.rangeposcolumns.append((axisname, usename, mask))
254 elif mask == self.mask_value:
255 usecolumns = usecolumns[:-1]
256 if len(privatedata.rangeposcolumns) + len(sharedata.vrangemissing) > count+1:
257 raise ValueError("multiple axes per graph dimension")
258 elif len(privatedata.rangeposcolumns) + len(sharedata.vrangemissing) < count+1:
259 sharedata.vrangemissing.append(count)
260 sharedata.vrangeminmissing.append(count)
261 sharedata.vrangemaxmissing.append(count)
262 else:
263 if not (privatedata.rangeposcolumns[-1][2] & (self.mask_min | self.mask_dmin | self.mask_d)):
264 sharedata.vrangeminmissing.append(count)
265 if not (privatedata.rangeposcolumns[-1][2] & (self.mask_max | self.mask_dmax | self.mask_d)):
266 sharedata.vrangemaxmissing.append(count)
267 return usecolumns
269 def adjustaxis(self, privatedata, sharedata, graph, columnname, data):
270 if columnname in [c + "min" for a, c, m in privatedata.rangeposcolumns if m & self.mask_min]:
271 graph.axes[columnname[:-3]].adjustaxis(data)
272 if columnname in [c + "max" for a, c, m in privatedata.rangeposcolumns if m & self.mask_max]:
273 graph.axes[columnname[:-3]].adjustaxis(data)
275 # delta handling: fill rangeposdeltacolumns
276 for axisname, usename, mask in privatedata.rangeposcolumns:
277 if columnname == usename and mask & (self.mask_dmin | self.mask_dmax | self.mask_d):
278 privatedata.rangeposdeltacolumns[axisname][self.mask_value] = data
279 if columnname == "d" + usename + "min" and mask & self.mask_dmin:
280 privatedata.rangeposdeltacolumns[axisname][self.mask_dmin] = data
281 if columnname == "d" + usename + "max" and mask & self.mask_dmax:
282 privatedata.rangeposdeltacolumns[axisname][self.mask_dmax] = data
283 if columnname == "d" + usename and mask & self.mask_d:
284 privatedata.rangeposdeltacolumns[axisname][self.mask_d] = data
286 # delta handling: process rangeposdeltacolumns
287 for a, d in privatedata.rangeposdeltacolumns.items():
288 if d.has_key(self.mask_value):
289 for k in d.keys():
290 if k != self.mask_value:
291 if k & (self.mask_dmin | self.mask_d):
292 mindata = []
293 for value, delta in zip(d[self.mask_value], d[k]):
294 try:
295 mindata.append(value-delta)
296 except:
297 pass
298 graph.axes[a].adjustaxis(mindata)
299 if k & (self.mask_dmax | self.mask_d):
300 maxdata = []
301 for value, delta in zip(d[self.mask_value], d[k]):
302 try:
303 maxdata.append(value+delta)
304 except:
305 pass
306 graph.axes[a].adjustaxis(maxdata)
307 del d[k]
309 def initdrawpoints(self, privatedata, sharedata, graph):
310 sharedata.vrange = [[None for x in xrange(2)] for y in privatedata.rangeposcolumns + sharedata.vrangemissing]
311 privatedata.rangepostmplist = [[usename, mask, index, graph.axes[axisname]] # temporarily used by drawpoint only
312 for index, (axisname, usename, mask) in enumerate(privatedata.rangeposcolumns)]
313 for missing in sharedata.vrangemissing:
314 for rangepostmp in privatedata.rangepostmplist:
315 if rangepostmp[2] >= missing:
316 rangepostmp[2] += 1
318 def drawpoint(self, privatedata, sharedata, graph, point):
319 for usename, mask, index, axis in privatedata.rangepostmplist:
320 try:
321 if mask & self.mask_min:
322 sharedata.vrange[index][0] = axis.convert(point[usename + "min"])
323 if mask & self.mask_dmin:
324 sharedata.vrange[index][0] = axis.convert(point[usename] - point["d" + usename + "min"])
325 if mask & self.mask_d:
326 sharedata.vrange[index][0] = axis.convert(point[usename] - point["d" + usename])
327 except (ArithmeticError, ValueError, TypeError):
328 sharedata.vrange[index][0] = None
329 try:
330 if mask & self.mask_max:
331 sharedata.vrange[index][1] = axis.convert(point[usename + "max"])
332 if mask & self.mask_dmax:
333 sharedata.vrange[index][1] = axis.convert(point[usename] + point["d" + usename + "max"])
334 if mask & self.mask_d:
335 sharedata.vrange[index][1] = axis.convert(point[usename] + point["d" + usename])
336 except (ArithmeticError, ValueError, TypeError):
337 sharedata.vrange[index][1] = None
339 # some range checks for data consistency
340 if (sharedata.vrange[index][0] is not None and sharedata.vrange[index][1] is not None and
341 sharedata.vrange[index][0] > sharedata.vrange[index][1] + self.epsilon):
342 raise ValueError("inverse range")
343 # disabled due to missing vpos access:
344 # if (sharedata.vrange[index][0] is not None and sharedata.vpos[index] is not None and
345 # sharedata.vrange[index][0] > sharedata.vpos[index] + self.epsilon):
346 # raise ValueError("negative minimum errorbar")
347 # if (sharedata.vrange[index][1] is not None and sharedata.vpos[index] is not None and
348 # sharedata.vrange[index][1] < sharedata.vpos[index] - self.epsilon):
349 # raise ValueError("negative maximum errorbar")
352 registerdefaultprovider(range(), range.providesdata)
355 def _crosssymbol(c, x_pt, y_pt, size_pt, attrs):
356 c.draw(path.path(path.moveto_pt(x_pt-0.5*size_pt, y_pt-0.5*size_pt),
357 path.lineto_pt(x_pt+0.5*size_pt, y_pt+0.5*size_pt),
358 path.moveto_pt(x_pt-0.5*size_pt, y_pt+0.5*size_pt),
359 path.lineto_pt(x_pt+0.5*size_pt, y_pt-0.5*size_pt)), attrs)
361 def _plussymbol(c, x_pt, y_pt, size_pt, attrs):
362 c.draw(path.path(path.moveto_pt(x_pt-0.707106781*size_pt, y_pt),
363 path.lineto_pt(x_pt+0.707106781*size_pt, y_pt),
364 path.moveto_pt(x_pt, y_pt-0.707106781*size_pt),
365 path.lineto_pt(x_pt, y_pt+0.707106781*size_pt)), attrs)
367 def _squaresymbol(c, x_pt, y_pt, size_pt, attrs):
368 c.draw(path.path(path.moveto_pt(x_pt-0.5*size_pt, y_pt-0.5*size_pt),
369 path.lineto_pt(x_pt+0.5*size_pt, y_pt-0.5*size_pt),
370 path.lineto_pt(x_pt+0.5*size_pt, y_pt+0.5*size_pt),
371 path.lineto_pt(x_pt-0.5*size_pt, y_pt+0.5*size_pt),
372 path.closepath()), attrs)
374 def _trianglesymbol(c, x_pt, y_pt, size_pt, attrs):
375 c.draw(path.path(path.moveto_pt(x_pt-0.759835685*size_pt, y_pt-0.438691337*size_pt),
376 path.lineto_pt(x_pt+0.759835685*size_pt, y_pt-0.438691337*size_pt),
377 path.lineto_pt(x_pt, y_pt+0.877382675*size_pt),
378 path.closepath()), attrs)
380 def _circlesymbol(c, x_pt, y_pt, size_pt, attrs):
381 c.draw(path.path(path.arc_pt(x_pt, y_pt, 0.564189583*size_pt, 0, 360),
382 path.closepath()), attrs)
384 def _diamondsymbol(c, x_pt, y_pt, size_pt, attrs):
385 c.draw(path.path(path.moveto_pt(x_pt-0.537284965*size_pt, y_pt),
386 path.lineto_pt(x_pt, y_pt-0.930604859*size_pt),
387 path.lineto_pt(x_pt+0.537284965*size_pt, y_pt),
388 path.lineto_pt(x_pt, y_pt+0.930604859*size_pt),
389 path.closepath()), attrs)
392 class _styleneedingpointpos(_style):
394 needsdata = ["vposmissing"]
396 def columnnames(self, privatedata, sharedata, graph, columnnames):
397 if len(sharedata.vposmissing):
398 raise ValueError("incomplete position information")
399 return []
402 class symbol(_styleneedingpointpos):
404 needsdata = ["vpos", "vposmissing", "vposvalid"]
406 # "inject" the predefinied symbols into the class:
408 # Note, that statements like cross = _crosssymbol are
409 # invalid, since the would lead to unbound methods, but
410 # a single entry changeable list does the trick.
412 # Once we require Python 2.2+ we should use staticmethods
413 # to implement the default symbols inplace.
415 cross = attr.changelist([_crosssymbol])
416 plus = attr.changelist([_plussymbol])
417 square = attr.changelist([_squaresymbol])
418 triangle = attr.changelist([_trianglesymbol])
419 circle = attr.changelist([_circlesymbol])
420 diamond = attr.changelist([_diamondsymbol])
422 changecross = attr.changelist([_crosssymbol, _plussymbol, _squaresymbol, _trianglesymbol, _circlesymbol, _diamondsymbol])
423 changeplus = attr.changelist([_plussymbol, _squaresymbol, _trianglesymbol, _circlesymbol, _diamondsymbol, _crosssymbol])
424 changesquare = attr.changelist([_squaresymbol, _trianglesymbol, _circlesymbol, _diamondsymbol, _crosssymbol, _plussymbol])
425 changetriangle = attr.changelist([_trianglesymbol, _circlesymbol, _diamondsymbol, _crosssymbol, _plussymbol, _squaresymbol])
426 changecircle = attr.changelist([_circlesymbol, _diamondsymbol, _crosssymbol, _plussymbol, _squaresymbol, _trianglesymbol])
427 changediamond = attr.changelist([_diamondsymbol, _crosssymbol, _plussymbol, _squaresymbol, _trianglesymbol, _circlesymbol])
428 changesquaretwice = attr.changelist([_squaresymbol, _squaresymbol, _trianglesymbol, _trianglesymbol, _circlesymbol, _circlesymbol, _diamondsymbol, _diamondsymbol])
429 changetriangletwice = attr.changelist([_trianglesymbol, _trianglesymbol, _circlesymbol, _circlesymbol, _diamondsymbol, _diamondsymbol, _squaresymbol, _squaresymbol])
430 changecircletwice = attr.changelist([_circlesymbol, _circlesymbol, _diamondsymbol, _diamondsymbol, _squaresymbol, _squaresymbol, _trianglesymbol, _trianglesymbol])
431 changediamondtwice = attr.changelist([_diamondsymbol, _diamondsymbol, _squaresymbol, _squaresymbol, _trianglesymbol, _trianglesymbol, _circlesymbol, _circlesymbol])
433 changestrokedfilled = attr.changelist([deco.stroked, deco.filled])
434 changefilledstroked = attr.changelist([deco.filled, deco.stroked])
436 defaultsymbolattrs = [deco.stroked]
438 def __init__(self, symbol=changecross, size=0.2*unit.v_cm, symbolattrs=[]):
439 self.symbol = symbol
440 self.size = size
441 self.symbolattrs = symbolattrs
443 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
444 privatedata.symbol = attr.selectattr(self.symbol, selectindex, selecttotal)
445 privatedata.size_pt = unit.topt(attr.selectattr(self.size, selectindex, selecttotal))
446 if self.symbolattrs is not None:
447 privatedata.symbolattrs = attr.selectattrs(self.defaultsymbolattrs + self.symbolattrs, selectindex, selecttotal)
448 else:
449 privatedata.symbolattrs = None
451 def initdrawpoints(self, privatedata, sharedata, graph):
452 privatedata.symbolcanvas = canvas.canvas()
454 def drawpoint(self, privatedata, sharedata, graph, point):
455 if sharedata.vposvalid and privatedata.symbolattrs is not None:
456 x_pt, y_pt = graph.vpos_pt(*sharedata.vpos)
457 privatedata.symbol(privatedata.symbolcanvas, x_pt, y_pt, privatedata.size_pt, privatedata.symbolattrs)
459 def donedrawpoints(self, privatedata, sharedata, graph):
460 graph.insert(privatedata.symbolcanvas)
462 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
463 if privatedata.symbolattrs is not None:
464 privatedata.symbol(graph, x_pt+0.5*width_pt, y_pt+0.5*height_pt, privatedata.size_pt, privatedata.symbolattrs)
467 class _line(_styleneedingpointpos):
469 # this style is not a complete style, but it provides the basic functionality to
470 # create a line, which is cut at the graph boundaries (or at otherwise invalid points)
472 def initpointstopath(self, privatedata):
473 privatedata.path = path.path()
474 privatedata.linebasepoints = []
475 privatedata.lastvpos = None
477 def addpointstopath(self, privatedata):
478 # add baselinepoints to privatedata.path
479 if len(privatedata.linebasepoints) > 1:
480 privatedata.path.append(path.moveto_pt(*privatedata.linebasepoints[0]))
481 if len(privatedata.linebasepoints) > 2:
482 privatedata.path.append(path.multilineto_pt(privatedata.linebasepoints[1:]))
483 else:
484 privatedata.path.append(path.lineto_pt(*privatedata.linebasepoints[1]))
485 privatedata.linebasepoints = []
487 def addpoint(self, privatedata, graphvpos_pt, vposavailable, vposvalid, vpos):
488 # append linebasepoints
489 if vposavailable:
490 if len(privatedata.linebasepoints):
491 # the last point was inside the graph
492 if vposvalid: # shortcut for the common case
493 privatedata.linebasepoints.append(graphvpos_pt(*vpos))
494 else:
495 # cut end
496 cut = 1
497 for vstart, vend in zip(privatedata.lastvpos, vpos):
498 newcut = None
499 if vend > 1:
500 # 1 = vstart + (vend - vstart) * cut
501 try:
502 newcut = (1 - vstart)/(vend - vstart)
503 except (ArithmeticError, TypeError):
504 break
505 if vend < 0:
506 # 0 = vstart + (vend - vstart) * cut
507 try:
508 newcut = - vstart/(vend - vstart)
509 except (ArithmeticError, TypeError):
510 break
511 if newcut is not None and newcut < cut:
512 cut = newcut
513 else:
514 cutvpos = []
515 for vstart, vend in zip(privatedata.lastvpos, vpos):
516 cutvpos.append(vstart + (vend - vstart) * cut)
517 privatedata.linebasepoints.append(graphvpos_pt(*cutvpos))
518 self.addpointstopath(privatedata)
519 else:
520 # the last point was outside the graph
521 if privatedata.lastvpos is not None:
522 if vposvalid:
523 # cut beginning
524 cut = 0
525 for vstart, vend in zip(privatedata.lastvpos, vpos):
526 newcut = None
527 if vstart > 1:
528 # 1 = vstart + (vend - vstart) * cut
529 try:
530 newcut = (1 - vstart)/(vend - vstart)
531 except (ArithmeticError, TypeError):
532 break
533 if vstart < 0:
534 # 0 = vstart + (vend - vstart) * cut
535 try:
536 newcut = - vstart/(vend - vstart)
537 except (ArithmeticError, TypeError):
538 break
539 if newcut is not None and newcut > cut:
540 cut = newcut
541 else:
542 cutvpos = []
543 for vstart, vend in zip(privatedata.lastvpos, vpos):
544 cutvpos.append(vstart + (vend - vstart) * cut)
545 privatedata.linebasepoints.append(graphvpos_pt(*cutvpos))
546 privatedata.linebasepoints.append(graphvpos_pt(*vpos))
547 else:
548 # sometimes cut beginning and end
549 cutfrom = 0
550 cutto = 1
551 for vstart, vend in zip(privatedata.lastvpos, vpos):
552 newcutfrom = None
553 if vstart > 1:
554 if vend > 1:
555 break
556 # 1 = vstart + (vend - vstart) * cutfrom
557 try:
558 newcutfrom = (1 - vstart)/(vend - vstart)
559 except (ArithmeticError, TypeError):
560 break
561 if vstart < 0:
562 if vend < 0:
563 break
564 # 0 = vstart + (vend - vstart) * cutfrom
565 try:
566 newcutfrom = - vstart/(vend - vstart)
567 except (ArithmeticError, TypeError):
568 break
569 if newcutfrom is not None and newcutfrom > cutfrom:
570 cutfrom = newcutfrom
571 newcutto = None
572 if vend > 1:
573 # 1 = vstart + (vend - vstart) * cutto
574 try:
575 newcutto = (1 - vstart)/(vend - vstart)
576 except (ArithmeticError, TypeError):
577 break
578 if vend < 0:
579 # 0 = vstart + (vend - vstart) * cutto
580 try:
581 newcutto = - vstart/(vend - vstart)
582 except (ArithmeticError, TypeError):
583 break
584 if newcutto is not None and newcutto < cutto:
585 cutto = newcutto
586 else:
587 if cutfrom < cutto:
588 cutfromvpos = []
589 cuttovpos = []
590 for vstart, vend in zip(privatedata.lastvpos, vpos):
591 cutfromvpos.append(vstart + (vend - vstart) * cutfrom)
592 cuttovpos.append(vstart + (vend - vstart) * cutto)
593 privatedata.linebasepoints.append(graphvpos_pt(*cutfromvpos))
594 privatedata.linebasepoints.append(graphvpos_pt(*cuttovpos))
595 self.addpointstopath(privatedata)
596 privatedata.lastvpos = vpos[:]
597 else:
598 if len(privatedata.linebasepoints) > 1:
599 self.addpointstopath(privatedata)
600 privatedata.lastvpos = None
602 def addinvalid(self, privatedata):
603 if len(privatedata.linebasepoints) > 1:
604 self.addpointstopath(privatedata)
605 privatedata.lastvpos = None
607 def donepointstopath(self, privatedata):
608 if len(privatedata.linebasepoints) > 1:
609 self.addpointstopath(privatedata)
610 return privatedata.path
613 class line(_line):
615 needsdata = ["vpos", "vposmissing", "vposavailable", "vposvalid"]
617 changelinestyle = attr.changelist([style.linestyle.solid,
618 style.linestyle.dashed,
619 style.linestyle.dotted,
620 style.linestyle.dashdotted])
622 defaultlineattrs = [changelinestyle]
624 def __init__(self, lineattrs=[]):
625 self.lineattrs = lineattrs
627 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
628 if self.lineattrs is not None:
629 privatedata.lineattrs = attr.selectattrs(self.defaultlineattrs + self.lineattrs, selectindex, selecttotal)
630 else:
631 privatedata.lineattrs = None
633 def initdrawpoints(self, privatedata, sharedata, graph):
634 self.initpointstopath(privatedata)
636 def drawpoint(self, privatedata, sharedata, graph, point):
637 self.addpoint(privatedata, graph.vpos_pt, sharedata.vposavailable, sharedata.vposvalid, sharedata.vpos)
639 def donedrawpoints(self, privatedata, sharedata, graph):
640 path = self.donepointstopath(privatedata)
641 if privatedata.lineattrs is not None and len(path):
642 graph.stroke(path, privatedata.lineattrs)
644 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
645 if privatedata.lineattrs is not None:
646 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)
649 class impulses(_styleneedingpointpos):
651 needsdata = ["vpos", "vposmissing", "vposavailable", "vposvalid", "poscolumnnames"]
653 defaultlineattrs = [line.changelinestyle]
654 defaultfrompathattrs = []
656 def __init__(self, lineattrs=[], fromvalue=0, frompathattrs=[], valueaxisindex=1):
657 self.lineattrs = lineattrs
658 self.fromvalue = fromvalue
659 self.frompathattrs = frompathattrs
660 self.valueaxisindex = valueaxisindex
662 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
663 privatedata.insertfrompath = selectindex == 0
664 if self.lineattrs is not None:
665 privatedata.lineattrs = attr.selectattrs(self.defaultlineattrs + self.lineattrs, selectindex, selecttotal)
666 else:
667 privatedata.lineattrs = None
669 def adjustaxis(self, privatedata, sharedata, graph, columnname, data):
670 if self.fromvalue is not None:
671 try:
672 i = sharedata.poscolumnnames.index(columnname)
673 except ValueError:
674 pass
675 else:
676 if i == self.valueaxisindex:
677 graph.axes[sharedata.poscolumnnames[i]].adjustaxis([self.fromvalue])
679 def initdrawpoints(self, privatedata, sharedata, graph):
680 privatedata.impulsescanvas = canvas.canvas()
681 if self.fromvalue is not None:
682 valueaxisname = sharedata.poscolumnnames[self.valueaxisindex]
683 privatedata.vfromvalue = graph.axes[valueaxisname].convert(self.fromvalue)
684 privatedata.vfromvaluecut = 0
685 if privatedata.vfromvalue < 0:
686 privatedata.vfromvalue = 0
687 if privatedata.vfromvalue > 1:
688 privatedata.vfromvalue = 1
689 if self.frompathattrs is not None and privatedata.insertfrompath:
690 graph.stroke(graph.axes[valueaxisname].vgridpath(privatedata.vfromvalue),
691 self.defaultfrompathattrs + self.frompathattrs)
692 else:
693 privatedata.vfromvalue = 0
695 def drawpoint(self, privatedata, sharedata, graph, point):
696 if sharedata.vposvalid and privatedata.lineattrs is not None:
697 vpos = sharedata.vpos[:]
698 vpos[self.valueaxisindex] = privatedata.vfromvalue
699 privatedata.impulsescanvas.stroke(graph.vgeodesic(*(vpos + sharedata.vpos)), privatedata.lineattrs)
701 def donedrawpoints(self, privatedata, sharedata, graph):
702 graph.insert(privatedata.impulsescanvas)
704 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
705 if privatedata.lineattrs is not None:
706 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)
709 class errorbar(_style):
711 needsdata = ["vpos", "vposmissing", "vposavailable", "vposvalid", "vrange", "vrangeminmissing", "vrangemaxmissing"]
713 defaulterrorbarattrs = []
715 def __init__(self, size=0.1*unit.v_cm,
716 errorbarattrs=[],
717 epsilon=1e-10):
718 self.size = size
719 self.errorbarattrs = errorbarattrs
720 self.epsilon = epsilon
722 def columnnames(self, privatedata, sharedata, graph, columnnames):
723 for i in sharedata.vposmissing:
724 if i in sharedata.vrangeminmissing and i in sharedata.vrangemaxmissing:
725 raise ValueError("position and range for a graph dimension missing")
726 return []
728 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
729 privatedata.errorsize_pt = unit.topt(attr.selectattr(self.size, selectindex, selecttotal))
730 privatedata.errorbarattrs = attr.selectattrs(self.defaulterrorbarattrs + self.errorbarattrs, selectindex, selecttotal)
732 def initdrawpoints(self, privatedata, sharedata, graph):
733 if privatedata.errorbarattrs is not None:
734 privatedata.errorbarcanvas = canvas.canvas(privatedata.errorbarattrs)
735 privatedata.dimensionlist = list(xrange(len(sharedata.vpos)))
737 def drawpoint(self, privatedata, sharedata, graph, point):
738 if privatedata.errorbarattrs is not None:
739 for i in privatedata.dimensionlist:
740 for j in privatedata.dimensionlist:
741 if (i != j and
742 (sharedata.vpos[j] is None or
743 sharedata.vpos[j] < -self.epsilon or
744 sharedata.vpos[j] > 1+self.epsilon)):
745 break
746 else:
747 if ((sharedata.vrange[i][0] is None and sharedata.vpos[i] is None) or
748 (sharedata.vrange[i][1] is None and sharedata.vpos[i] is None) or
749 (sharedata.vrange[i][0] is None and sharedata.vrange[i][1] is None)):
750 continue
751 vminpos = sharedata.vpos[:]
752 if sharedata.vrange[i][0] is not None:
753 vminpos[i] = sharedata.vrange[i][0]
754 mincap = 1
755 else:
756 mincap = 0
757 if vminpos[i] > 1+self.epsilon:
758 continue
759 if vminpos[i] < -self.epsilon:
760 vminpos[i] = 0
761 mincap = 0
762 vmaxpos = sharedata.vpos[:]
763 if sharedata.vrange[i][1] is not None:
764 vmaxpos[i] = sharedata.vrange[i][1]
765 maxcap = 1
766 else:
767 maxcap = 0
768 if vmaxpos[i] < -self.epsilon:
769 continue
770 if vmaxpos[i] > 1+self.epsilon:
771 vmaxpos[i] = 1
772 maxcap = 0
773 privatedata.errorbarcanvas.stroke(graph.vgeodesic(*(vminpos + vmaxpos)))
774 for j in privatedata.dimensionlist:
775 if i != j:
776 if mincap:
777 privatedata.errorbarcanvas.stroke(graph.vcap_pt(j, privatedata.errorsize_pt, *vminpos))
778 if maxcap:
779 privatedata.errorbarcanvas.stroke(graph.vcap_pt(j, privatedata.errorsize_pt, *vmaxpos))
781 def donedrawpoints(self, privatedata, sharedata, graph):
782 if privatedata.errorbarattrs is not None:
783 graph.insert(privatedata.errorbarcanvas)
786 class text(_styleneedingpointpos):
788 needsdata = ["vpos", "vposmissing", "vposvalid"]
790 defaulttextattrs = [textmodule.halign.center, textmodule.vshift.mathaxis]
792 def __init__(self, textname="text", dxname=None, dyname=None,
793 dxunit=0.3*unit.v_cm, dyunit=0.3*unit.v_cm,
794 textdx=0*unit.v_cm, textdy=0.3*unit.v_cm, textattrs=[]):
795 self.textname = textname
796 self.dxname = dxname
797 self.dyname = dyname
798 self.dxunit = dxunit
799 self.dyunit = dyunit
800 self.textdx = textdx
801 self.textdy = textdy
802 self.textattrs = textattrs
804 def columnnames(self, privatedata, sharedata, graph, columnnames):
805 if self.textname not in columnnames:
806 raise ValueError("column '%s' missing" % self.textname)
807 names = [self.textname]
808 if self.dxname is not None:
809 if self.dxname not in columnnames:
810 raise ValueError("column '%s' missing" % self.dxname)
811 names.append(self.dxname)
812 if self.dyname is not None:
813 if self.dyname not in columnnames:
814 raise ValueError("column '%s' missing" % self.dyname)
815 names.append(self.dyname)
816 return names + _styleneedingpointpos.columnnames(self, privatedata, sharedata, graph, columnnames)
818 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
819 if self.textattrs is not None:
820 privatedata.textattrs = attr.selectattrs(self.defaulttextattrs + self.textattrs, selectindex, selecttotal)
821 else:
822 privatedata.textattrs = None
824 def initdrawpoints(self, privatedata, sharedata, grap):
825 if self.dxname is None:
826 privatedata.textdx_pt = unit.topt(self.textdx)
827 else:
828 privatedata.dxunit_pt = unit.topt(self.dxunit)
829 if self.dyname is None:
830 privatedata.textdy_pt = unit.topt(self.textdy)
831 else:
832 privatedata.dyunit_pt = unit.topt(self.dyunit)
834 def drawpoint(self, privatedata, sharedata, graph, point):
835 if privatedata.textattrs is not None and sharedata.vposvalid:
836 x_pt, y_pt = graph.vpos_pt(*sharedata.vpos)
837 try:
838 text = str(point[self.textname])
839 except:
840 pass
841 else:
842 if self.dxname is None:
843 dx_pt = privatedata.textdx_pt
844 else:
845 dx_pt = float(point[self.dxname]) * privatedata.dxunit_pt
846 if self.dyname is None:
847 dy_pt = privatedata.textdy_pt
848 else:
849 dy_pt = float(point[self.dyname]) * privatedata.dyunit_pt
850 graph.text_pt(x_pt + dx_pt, y_pt + dy_pt, text, privatedata.textattrs)
852 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
853 raise RuntimeError("Style currently doesn't provide a graph key")
856 class arrow(_styleneedingpointpos):
858 needsdata = ["vpos", "vposmissing", "vposvalid"]
860 defaultlineattrs = []
861 defaultarrowattrs = []
863 def __init__(self, linelength=0.25*unit.v_cm, arrowsize=0.15*unit.v_cm, lineattrs=[], arrowattrs=[], arrowpos=0.5, epsilon=1e-5):
864 self.linelength = linelength
865 self.arrowsize = arrowsize
866 self.lineattrs = lineattrs
867 self.arrowattrs = arrowattrs
868 self.arrowpos = arrowpos
869 self.epsilon = epsilon
871 def columnnames(self, privatedata, sharedata, graph, columnnames):
872 if len(graph.axesnames) != 2:
873 raise ValueError("arrow style restricted on two-dimensional graphs")
874 if "size" not in columnnames:
875 raise ValueError("size missing")
876 if "angle" not in columnnames:
877 raise ValueError("angle missing")
878 return ["size", "angle"] + _styleneedingpointpos.columnnames(self, privatedata, sharedata, graph, columnnames)
880 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
881 if self.lineattrs is not None:
882 privatedata.lineattrs = attr.selectattrs(self.defaultlineattrs + self.lineattrs, selectindex, selecttotal)
883 else:
884 privatedata.lineattrs = None
885 if self.arrowattrs is not None:
886 privatedata.arrowattrs = attr.selectattrs(self.defaultarrowattrs + self.arrowattrs, selectindex, selecttotal)
887 else:
888 privatedata.arrowattrs = None
890 def initdrawpoints(self, privatedata, sharedata, graph):
891 privatedata.arrowcanvas = canvas.canvas()
893 def drawpoint(self, privatedata, sharedata, graph, point):
894 if privatedata.lineattrs is not None and privatedata.arrowattrs is not None and sharedata.vposvalid:
895 linelength_pt = unit.topt(self.linelength)
896 x_pt, y_pt = graph.vpos_pt(*sharedata.vpos)
897 try:
898 angle = point["angle"] + 0.0
899 size = point["size"] + 0.0
900 except:
901 pass
902 else:
903 if point["size"] > self.epsilon:
904 dx = math.cos(angle*math.pi/180)
905 dy = math.sin(angle*math.pi/180)
906 x1 = x_pt-self.arrowpos*dx*linelength_pt*size
907 y1 = y_pt-self.arrowpos*dy*linelength_pt*size
908 x2 = x_pt+(1-self.arrowpos)*dx*linelength_pt*size
909 y2 = y_pt+(1-self.arrowpos)*dy*linelength_pt*size
910 privatedata.arrowcanvas.stroke(path.line_pt(x1, y1, x2, y2), privatedata.lineattrs +
911 [deco.earrow(privatedata.arrowattrs, size=self.arrowsize*size)])
913 def donedrawpoints(self, privatedata, sharedata, graph):
914 graph.insert(privatedata.arrowcanvas)
916 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
917 raise RuntimeError("Style currently doesn't provide a graph key")
920 class rect(_style):
922 needsdata = ["vrange", "vrangeminmissing", "vrangemaxmissing"]
924 def __init__(self, gradient=color.gradient.Grey):
925 self.gradient = gradient
927 def columnnames(self, privatedata, sharedata, graph, columnnames):
928 if len(graph.axesnames) != 2:
929 raise TypeError("arrow style restricted on two-dimensional graphs")
930 if "color" not in columnnames:
931 raise ValueError("color missing")
932 if len(sharedata.vrangeminmissing) + len(sharedata.vrangemaxmissing):
933 raise ValueError("incomplete range")
934 return ["color"]
936 def initdrawpoints(self, privatedata, sharedata, graph):
937 privatedata.rectcanvas = graph.insert(canvas.canvas())
939 def drawpoint(self, privatedata, sharedata, graph, point):
940 xvmin = sharedata.vrange[0][0]
941 xvmax = sharedata.vrange[0][1]
942 yvmin = sharedata.vrange[1][0]
943 yvmax = sharedata.vrange[1][1]
944 if (xvmin is not None and xvmin < 1 and
945 xvmax is not None and xvmax > 0 and
946 yvmin is not None and yvmin < 1 and
947 yvmax is not None and yvmax > 0):
948 if xvmin < 0:
949 xvmin = 0
950 elif xvmax > 1:
951 xvmax = 1
952 if yvmin < 0:
953 yvmin = 0
954 elif yvmax > 1:
955 yvmax = 1
956 p = graph.vgeodesic(xvmin, yvmin, xvmax, yvmin)
957 p.append(graph.vgeodesic_el(xvmax, yvmin, xvmax, yvmax))
958 p.append(graph.vgeodesic_el(xvmax, yvmax, xvmin, yvmax))
959 p.append(graph.vgeodesic_el(xvmin, yvmax, xvmin, yvmin))
960 p.append(path.closepath())
961 privatedata.rectcanvas.fill(p, [self.gradient.getcolor(point["color"])])
963 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
964 raise RuntimeError("Style currently doesn't provide a graph key")
967 class histogram(_style):
969 needsdata = ["vpos", "vposmissing", "vrange", "vrangeminmissing", "vrangemaxmissing"]
971 defaultlineattrs = [deco.stroked]
972 defaultfrompathattrs = []
974 def __init__(self, lineattrs=[], steps=0, fromvalue=0, frompathattrs=[], fillable=0, rectkey=0,
975 autohistogramaxisindex=0, autohistogrampointpos=0.5, epsilon=1e-10):
976 self.lineattrs = lineattrs
977 self.steps = steps
978 self.fromvalue = fromvalue
979 self.frompathattrs = frompathattrs
980 self.fillable = fillable # TODO: fillable paths might not properly be closed by straight lines on curved graph geometries
981 self.rectkey = rectkey
982 self.autohistogramaxisindex = autohistogramaxisindex
983 self.autohistogrampointpos = autohistogrampointpos
984 self.epsilon = epsilon
986 def columnnames(self, privatedata, sharedata, graph, columnnames):
987 if len(graph.axesnames) != 2:
988 raise TypeError("histogram style restricted on two-dimensional graphs")
989 privatedata.rangeaxisindex = None
990 for i in builtinrange(len(graph.axesnames)):
991 if i in sharedata.vrangeminmissing or i in sharedata.vrangemaxmissing:
992 if i in sharedata.vposmissing:
993 raise ValueError("pos and range missing")
994 else:
995 if privatedata.rangeaxisindex is not None:
996 raise ValueError("multiple ranges")
997 privatedata.rangeaxisindex = i
998 if privatedata.rangeaxisindex is None:
999 privatedata.rangeaxisindex = self.autohistogramaxisindex
1000 privatedata.autohistogram = 1
1001 else:
1002 privatedata.autohistogram = 0
1003 return []
1005 def adjustaxis(self, privatedata, sharedata, graph, columnname, data):
1006 if privatedata.autohistogram and columnname == sharedata.poscolumnnames[privatedata.rangeaxisindex]:
1007 if len(data) == 1:
1008 raise ValueError("several data points needed for automatic histogram width calculation")
1009 if data:
1010 delta = data[1] - data[0]
1011 min = data[0] - self.autohistogrampointpos * delta
1012 max = data[-1] + (1-self.autohistogrampointpos) * delta
1013 graph.axes[columnname].adjustaxis([min, max])
1014 elif self.fromvalue is not None and columnname == sharedata.poscolumnnames[1-privatedata.rangeaxisindex]:
1015 graph.axes[columnname].adjustaxis([self.fromvalue])
1017 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1018 privatedata.insertfrompath = selectindex == 0
1019 if self.lineattrs is not None:
1020 privatedata.lineattrs = attr.selectattrs(self.defaultlineattrs + self.lineattrs, selectindex, selecttotal)
1021 else:
1022 privatedata.lineattrs = None
1024 def vmoveto(self, privatedata, sharedata, graph, vpos, vvalue):
1025 if -self.epsilon < vpos < 1+self.epsilon and -self.epsilon < vvalue < 1+self.epsilon:
1026 if privatedata.rangeaxisindex:
1027 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue, vpos)))
1028 else:
1029 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vpos, vvalue)))
1031 def vposline(self, privatedata, sharedata, graph, vpos, vvalue1, vvalue2):
1032 if -self.epsilon < vpos < 1+self.epsilon:
1033 vvalue1cut = 0
1034 if vvalue1 < 0:
1035 vvalue1 = 0
1036 vvalue1cut = -1
1037 elif vvalue1 > 1:
1038 vvalue1 = 1
1039 vvalue1cut = 1
1040 vvalue2cut = 0
1041 if vvalue2 < 0:
1042 vvalue2 = 0
1043 vvalue2cut = -1
1044 elif vvalue2 > 1:
1045 vvalue2 = 1
1046 vvalue2cut = 1
1047 if abs(vvalue1cut + vvalue2cut) <= 1:
1048 if vvalue1cut and not self.fillable:
1049 if privatedata.rangeaxisindex:
1050 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue1, vpos)))
1051 else:
1052 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vpos, vvalue1)))
1053 if privatedata.rangeaxisindex:
1054 privatedata.path.append(graph.vgeodesic_el(vvalue1, vpos, vvalue2, vpos))
1055 else:
1056 privatedata.path.append(graph.vgeodesic_el(vpos, vvalue1, vpos, vvalue2))
1058 def vvalueline(self, privatedata, sharedata, graph, vvalue, vpos1, vpos2):
1059 if self.fillable:
1060 if vvalue < -self.epsilon:
1061 vvalue = 0
1062 warnings.warn("cut at graph boundary adds artificial lines to fillable step histogram path")
1063 if vvalue > 1+self.epsilon:
1064 vvalue = 1
1065 warnings.warn("cut at graph boundary adds artificial lines to fillable step histogram path")
1066 if self.fillable or (-self.epsilon < vvalue < 1+self.epsilon):
1067 vpos1cut = 0
1068 if vpos1 < 0:
1069 vpos1 = 0
1070 vpos1cut = -1
1071 elif vpos1 > 1:
1072 vpos1 = 1
1073 vpos1cut = 1
1074 vpos2cut = 0
1075 if vpos2 < 0:
1076 vpos2 = 0
1077 vpos2cut = -1
1078 elif vpos2 > 1:
1079 vpos2 = 1
1080 vpos2cut = 1
1081 if abs(vpos1cut + vpos2cut) <= 1:
1082 if vpos1cut:
1083 if self.fillable:
1084 if privatedata.rangeaxisindex:
1085 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(privatedata.vfromvalue, vpos1)))
1086 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vpos1, vvalue, vpos1))
1087 else:
1088 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vpos1, privatedata.vfromvalue)))
1089 privatedata.path.append(graph.vgeodesic_el(vpos1, privatedata.vfromvalue, vpos1, vvalue))
1090 else:
1091 if privatedata.rangeaxisindex:
1092 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue, vpos1)))
1093 else:
1094 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vpos1, vvalue)))
1095 if privatedata.rangeaxisindex:
1096 privatedata.path.append(graph.vgeodesic_el(vvalue, vpos1, vvalue, vpos2))
1097 else:
1098 privatedata.path.append(graph.vgeodesic_el(vpos1, vvalue, vpos2, vvalue))
1099 if self.fillable and vpos2cut:
1100 warnings.warn("cut at graph boundary adds artificial lines to fillable step histogram path")
1101 if privatedata.rangeaxisindex:
1102 privatedata.path.append(graph.vgeodesic_el(vvalue, vpos2, privatedata.vfromvalue, vpos2))
1103 else:
1104 privatedata.path.append(graph.vgeodesic_el(vpos2, vvalue, vpos2, privatedata.vfromvalue))
1106 def drawvalue(self, privatedata, sharedata, graph, vmin, vmax, vvalue):
1107 currentvalid = vmin is not None and vmax is not None and vvalue is not None
1108 if self.fillable and not self.steps:
1109 if not currentvalid:
1110 return
1111 vmincut = 0
1112 if vmin < -self.epsilon:
1113 vmin = 0
1114 vmincut = -1
1115 elif vmin > 1+self.epsilon:
1116 vmin = 1
1117 vmincut = 1
1118 vmaxcut = 0
1119 if vmax < -self.epsilon:
1120 vmax = 0
1121 vmaxcut = -1
1122 if vmax > 1+self.epsilon:
1123 vmax = 1
1124 vmaxcut = 1
1125 vvaluecut = 0
1126 if vvalue < -self.epsilon:
1127 vvalue = 0
1128 vvaluecut = -1
1129 if vvalue > 1+self.epsilon:
1130 vvalue = 1
1131 vvaluecut = 1
1132 done = 0
1133 if abs(vmincut) + abs(vmaxcut) + abs(vvaluecut) + abs(privatedata.vfromvaluecut) > 1:
1134 if abs(vmincut + vmaxcut) > 1 or abs(vvaluecut+privatedata.vfromvaluecut) > 1:
1135 done = 1
1136 else:
1137 warnings.warn("multiple cuts at graph boundary add artificial lines to fillable rectangle histogram path")
1138 elif vmincut:
1139 done = 1
1140 if privatedata.rangeaxisindex:
1141 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(privatedata.vfromvalue, vmin)))
1142 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmin, privatedata.vfromvalue, vmax))
1143 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmax, vvalue, vmax))
1144 privatedata.path.append(graph.vgeodesic_el(vvalue, vmax, vvalue, vmin))
1145 else:
1146 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmin, privatedata.vfromvalue)))
1147 privatedata.path.append(graph.vgeodesic_el(vmin, privatedata.vfromvalue, vmax, privatedata.vfromvalue))
1148 privatedata.path.append(graph.vgeodesic_el(vmax, privatedata.vfromvalue, vmax, vvalue))
1149 privatedata.path.append(graph.vgeodesic_el(vmax, vvalue, vmin, vvalue))
1150 elif vmaxcut:
1151 done = 1
1152 if privatedata.rangeaxisindex:
1153 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue, vmax)))
1154 privatedata.path.append(graph.vgeodesic_el(vvalue, vmax, vvalue, vmin))
1155 privatedata.path.append(graph.vgeodesic_el(vvalue, vmin, privatedata.vfromvalue, vmin))
1156 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmin, privatedata.vfromvalue, vmax))
1157 else:
1158 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmax, vvalue)))
1159 privatedata.path.append(graph.vgeodesic_el(vmax, vvalue, vmin, vvalue))
1160 privatedata.path.append(graph.vgeodesic_el(vmin, vvalue, vmin, privatedata.vfromvalue))
1161 privatedata.path.append(graph.vgeodesic_el(vmin, privatedata.vfromvalue, vmax, privatedata.vfromvalue))
1162 elif privatedata.vfromvaluecut:
1163 done = 1
1164 if privatedata.rangeaxisindex:
1165 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(privatedata.vfromvalue, vmax)))
1166 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmax, vvalue, vmax))
1167 privatedata.path.append(graph.vgeodesic_el(vvalue, vmax, vvalue, vmin))
1168 privatedata.path.append(graph.vgeodesic_el(vvalue, vmin, privatedata.vfromvalue, vmin))
1169 else:
1170 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmax, privatedata.vfromvalue)))
1171 privatedata.path.append(graph.vgeodesic_el(vmax, privatedata.vfromvalue, vmax, vvalue))
1172 privatedata.path.append(graph.vgeodesic_el(vmax, vvalue, vmin, vvalue))
1173 privatedata.path.append(graph.vgeodesic_el(vmin, vvalue, vmin, privatedata.vfromvalue))
1174 elif vvaluecut:
1175 done = 1
1176 if privatedata.rangeaxisindex:
1177 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue, vmin)))
1178 privatedata.path.append(graph.vgeodesic_el(vvalue, vmin, privatedata.vfromvalue, vmin))
1179 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmin, privatedata.vfromvalue, vmax))
1180 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmax, vvalue, vmax))
1181 else:
1182 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmin, vvalue)))
1183 privatedata.path.append(graph.vgeodesic_el(vmin, vvalue, vmin, privatedata.vfromvalue))
1184 privatedata.path.append(graph.vgeodesic_el(vmin, privatedata.vfromvalue, vmax, privatedata.vfromvalue))
1185 privatedata.path.append(graph.vgeodesic_el(vmax, privatedata.vfromvalue, vmax, vvalue))
1186 if not done:
1187 if privatedata.rangeaxisindex:
1188 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(privatedata.vfromvalue, vmin)))
1189 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmin, privatedata.vfromvalue, vmax))
1190 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmax, vvalue, vmax))
1191 privatedata.path.append(graph.vgeodesic_el(vvalue, vmax, vvalue, vmin))
1192 privatedata.path.append(graph.vgeodesic_el(vvalue, vmin, privatedata.vfromvalue, vmin))
1193 privatedata.path.append(path.closepath())
1194 else:
1195 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmin, privatedata.vfromvalue)))
1196 privatedata.path.append(graph.vgeodesic_el(vmin, privatedata.vfromvalue, vmax, privatedata.vfromvalue))
1197 privatedata.path.append(graph.vgeodesic_el(vmax, privatedata.vfromvalue, vmax, vvalue))
1198 privatedata.path.append(graph.vgeodesic_el(vmax, vvalue, vmin, vvalue))
1199 privatedata.path.append(graph.vgeodesic_el(vmin, vvalue, vmin, privatedata.vfromvalue))
1200 privatedata.path.append(path.closepath())
1201 else:
1202 try:
1203 gap = abs(vmin - privatedata.lastvmax) > self.epsilon
1204 except (ArithmeticError, ValueError, TypeError):
1205 gap = 1
1206 if (privatedata.lastvvalue is not None and currentvalid and not gap and
1207 (self.steps or (privatedata.lastvvalue-privatedata.vfromvalue)*(vvalue-privatedata.vfromvalue) < 0)):
1208 self.vposline(privatedata, sharedata, graph,
1209 vmin, privatedata.lastvvalue, vvalue)
1210 else:
1211 if privatedata.lastvvalue is not None and currentvalid:
1212 currentbigger = abs(privatedata.lastvvalue-privatedata.vfromvalue) < abs(vvalue-privatedata.vfromvalue)
1213 if privatedata.lastvvalue is not None and (not currentvalid or not currentbigger or gap):
1214 self.vposline(privatedata, sharedata, graph,
1215 privatedata.lastvmax, privatedata.lastvvalue, privatedata.vfromvalue)
1216 if currentvalid:
1217 self.vmoveto(privatedata, sharedata, graph,
1218 vmin, vvalue)
1219 if currentvalid and (privatedata.lastvvalue is None or currentbigger or gap):
1220 self.vmoveto(privatedata, sharedata, graph,
1221 vmin, privatedata.vfromvalue)
1222 self.vposline(privatedata, sharedata, graph,
1223 vmin, privatedata.vfromvalue, vvalue)
1224 if currentvalid:
1225 self.vvalueline(privatedata, sharedata, graph,
1226 vvalue, vmin, vmax)
1227 privatedata.lastvvalue = vvalue
1228 privatedata.lastvmax = vmax
1229 else:
1230 privatedata.lastvvalue = privatedata.lastvmax = None
1232 def initdrawpoints(self, privatedata, sharedata, graph):
1233 privatedata.path = path.path()
1234 privatedata.lastvvalue = privatedata.lastvmax = None
1235 privatedata.vcurrentpoint = None
1236 privatedata.count = 0
1237 if self.fromvalue is not None:
1238 valueaxisname = sharedata.poscolumnnames[1-privatedata.rangeaxisindex]
1239 privatedata.vfromvalue = graph.axes[valueaxisname].convert(self.fromvalue)
1240 privatedata.vfromvaluecut = 0
1241 if privatedata.vfromvalue < 0:
1242 privatedata.vfromvalue = 0
1243 privatedata.vfromvaluecut = -1
1244 if privatedata.vfromvalue > 1:
1245 privatedata.vfromvalue = 1
1246 privatedata.vfromvaluecut = 1
1247 if self.frompathattrs is not None and privatedata.insertfrompath:
1248 graph.stroke(graph.axes[valueaxisname].vgridpath(privatedata.vfromvalue),
1249 self.defaultfrompathattrs + self.frompathattrs)
1250 else:
1251 privatedata.vfromvalue = 0
1253 def drawpoint(self, privatedata, sharedata, graph, point):
1254 if privatedata.autohistogram:
1255 # automatic range handling
1256 privatedata.count += 1
1257 if privatedata.count == 2:
1258 if privatedata.rangeaxisindex:
1259 privatedata.vrange = sharedata.vpos[1] - privatedata.lastvpos[1]
1260 self.drawvalue(privatedata, sharedata, graph,
1261 privatedata.lastvpos[1] - self.autohistogrampointpos*privatedata.vrange,
1262 privatedata.lastvpos[1] + (1-self.autohistogrampointpos)*privatedata.vrange,
1263 privatedata.lastvpos[0])
1264 else:
1265 privatedata.vrange = sharedata.vpos[0] - privatedata.lastvpos[0]
1266 self.drawvalue(privatedata, sharedata, graph,
1267 privatedata.lastvpos[0] - self.autohistogrampointpos*privatedata.vrange,
1268 privatedata.lastvpos[0] + (1-self.autohistogrampointpos)*privatedata.vrange,
1269 privatedata.lastvpos[1])
1270 elif privatedata.count > 2:
1271 if privatedata.rangeaxisindex:
1272 vrange = sharedata.vpos[1] - privatedata.lastvpos[1]
1273 else:
1274 vrange = sharedata.vpos[0] - privatedata.lastvpos[0]
1275 if abs(privatedata.vrange - vrange) > self.epsilon:
1276 raise ValueError("equal steps (in graph coordinates) needed for automatic width calculation")
1277 if privatedata.count > 1:
1278 if privatedata.rangeaxisindex:
1279 self.drawvalue(privatedata, sharedata, graph,
1280 sharedata.vpos[1] - self.autohistogrampointpos*privatedata.vrange,
1281 sharedata.vpos[1] + (1-self.autohistogrampointpos)*privatedata.vrange,
1282 sharedata.vpos[0])
1283 else:
1284 self.drawvalue(privatedata, sharedata, graph,
1285 sharedata.vpos[0] - self.autohistogrampointpos*privatedata.vrange,
1286 sharedata.vpos[0] + (1-self.autohistogrampointpos)*privatedata.vrange,
1287 sharedata.vpos[1])
1288 privatedata.lastvpos = sharedata.vpos[:]
1289 else:
1290 if privatedata.rangeaxisindex:
1291 self.drawvalue(privatedata, sharedata, graph,
1292 sharedata.vrange[1][0], sharedata.vrange[1][1], sharedata.vpos[0])
1293 else:
1294 self.drawvalue(privatedata, sharedata, graph,
1295 sharedata.vrange[0][0], sharedata.vrange[0][1], sharedata.vpos[1])
1297 def donedrawpoints(self, privatedata, sharedata, graph):
1298 self.drawvalue(privatedata, sharedata, graph, None, None, None)
1299 if privatedata.lineattrs is not None and len(privatedata.path):
1300 graph.draw(privatedata.path, privatedata.lineattrs)
1302 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
1303 if privatedata.lineattrs is not None:
1304 if self.rectkey:
1305 p = path.rect_pt(x_pt, y_pt, width_pt, height_pt)
1306 else:
1307 p = path.line_pt(x_pt, y_pt+0.5*height_pt, x_pt+width_pt, y_pt+0.5*height_pt)
1308 graph.draw(p, privatedata.lineattrs)
1311 class barpos(_style):
1313 providesdata = ["vpos", "vposmissing", "vposavailable", "vposvalid", "vbarrange", "barposcolumnnames", "barvalueindex", "lastbarvalue", "stackedbar", "stackedbardraw"]
1315 defaultfrompathattrs = []
1317 def __init__(self, fromvalue=None, frompathattrs=[], epsilon=1e-10):
1318 self.fromvalue = fromvalue
1319 self.frompathattrs = frompathattrs
1320 self.epsilon = epsilon
1322 def columnnames(self, privatedata, sharedata, graph, columnnames):
1323 sharedata.barposcolumnnames = []
1324 sharedata.barvalueindex = None
1325 for dimension, axisnames in enumerate(graph.axesnames):
1326 found = 0
1327 for axisname in axisnames:
1328 if axisname in columnnames:
1329 if sharedata.barvalueindex is not None:
1330 raise ValueError("multiple values")
1331 sharedata.barvalueindex = dimension
1332 sharedata.barposcolumnnames.append(axisname)
1333 found += 1
1334 if (axisname + "name") in columnnames:
1335 sharedata.barposcolumnnames.append(axisname + "name")
1336 found += 1
1337 if found > 1:
1338 raise ValueError("multiple names and value")
1339 if not found:
1340 raise ValueError("value/name missing")
1341 if sharedata.barvalueindex is None:
1342 raise ValueError("missing value")
1343 sharedata.vposmissing = []
1344 return sharedata.barposcolumnnames
1346 def addsubvalue(self, value, subvalue):
1347 try:
1348 value + ""
1349 except:
1350 try:
1351 return value[0], self.addsubvalue(value[1], subvalue)
1352 except:
1353 return value, subvalue
1354 else:
1355 return value, subvalue
1357 def adjustaxis(self, privatedata, sharedata, graph, columnname, data):
1358 try:
1359 i = sharedata.barposcolumnnames.index(columnname)
1360 except ValueError:
1361 pass
1362 else:
1363 if i == sharedata.barvalueindex:
1364 if self.fromvalue is not None:
1365 graph.axes[sharedata.barposcolumnnames[i]].adjustaxis([self.fromvalue])
1366 graph.axes[sharedata.barposcolumnnames[i]].adjustaxis(data)
1367 else:
1368 graph.axes[sharedata.barposcolumnnames[i][:-4]].adjustaxis([self.addsubvalue(x, 0) for x in data])
1369 graph.axes[sharedata.barposcolumnnames[i][:-4]].adjustaxis([self.addsubvalue(x, 1) for x in data])
1371 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1372 privatedata.insertfrompath = selectindex == 0
1374 def initdrawpoints(self, privatedata, sharedata, graph):
1375 sharedata.vpos = [None]*(len(sharedata.barposcolumnnames))
1376 sharedata.vbarrange = [[None for i in xrange(2)] for x in sharedata.barposcolumnnames]
1377 sharedata.stackedbar = sharedata.stackedbardraw = 0
1379 if self.fromvalue is not None:
1380 privatedata.vfromvalue = graph.axes[sharedata.barposcolumnnames[sharedata.barvalueindex][0]].convert(self.fromvalue)
1381 if privatedata.vfromvalue < 0:
1382 privatedata.vfromvalue = 0
1383 if privatedata.vfromvalue > 1:
1384 privatedata.vfromvalue = 1
1385 if self.frompathattrs is not None and privatedata.insertfrompath:
1386 graph.stroke(graph.axes[sharedata.barposcolumnnames[sharedata.barvalueindex][0]].vgridpath(privatedata.vfromvalue),
1387 self.defaultfrompathattrs + self.frompathattrs)
1388 else:
1389 privatedata.vfromvalue = 0
1391 def drawpoint(self, privatedata, sharedata, graph, point):
1392 sharedata.vposavailable = sharedata.vposvalid = 1
1393 for i, barname in enumerate(sharedata.barposcolumnnames):
1394 if i == sharedata.barvalueindex:
1395 sharedata.vbarrange[i][0] = privatedata.vfromvalue
1396 sharedata.lastbarvalue = point[barname]
1397 try:
1398 sharedata.vpos[i] = sharedata.vbarrange[i][1] = graph.axes[barname].convert(sharedata.lastbarvalue)
1399 except (ArithmeticError, ValueError, TypeError):
1400 sharedata.vpos[i] = sharedata.vbarrange[i][1] = None
1401 else:
1402 for j in xrange(2):
1403 try:
1404 sharedata.vbarrange[i][j] = graph.axes[barname[:-4]].convert(self.addsubvalue(point[barname], j))
1405 except (ArithmeticError, ValueError, TypeError):
1406 sharedata.vbarrange[i][j] = None
1407 try:
1408 sharedata.vpos[i] = 0.5*(sharedata.vbarrange[i][0]+sharedata.vbarrange[i][1])
1409 except (ArithmeticError, ValueError, TypeError):
1410 sharedata.vpos[i] = None
1411 if sharedata.vpos[i] is None:
1412 sharedata.vposavailable = sharedata.vposvalid = 0
1413 elif sharedata.vpos[i] < -self.epsilon or sharedata.vpos[i] > 1+self.epsilon:
1414 sharedata.vposvalid = 0
1416 registerdefaultprovider(barpos(), ["vbarrange", "barposcolumnnames", "barvalueindex", "lastbarvalue", "stackedbar", "stackedbardraw"])
1419 class stackedbarpos(_style):
1421 # provides no additional data, but needs some data (and modifies some of them)
1422 needsdata = ["vbarrange", "barposcolumnnames", "barvalueindex", "lastbarvalue", "stackedbar", "stackedbardraw"]
1424 def __init__(self, stackname, addontop=0, epsilon=1e-10):
1425 self.stackname = stackname
1426 self.epsilon = epsilon
1427 self.addontop = addontop
1429 def columnnames(self, privatedata, sharedata, graph, columnnames):
1430 if self.stackname not in columnnames:
1431 raise ValueError("column '%s' missing" % self.stackname)
1432 return [self.stackname]
1434 def adjustaxis(self, privatedata, sharedata, graph, columnname, data):
1435 if columnname == self.stackname:
1436 graph.axes[sharedata.barposcolumnnames[sharedata.barvalueindex]].adjustaxis(data)
1438 def initdrawpoints(self, privatedata, sharedata, graph):
1439 if sharedata.stackedbardraw: # do not count the start bar when not gets painted
1440 sharedata.stackedbar += 1
1442 def drawpoint(self, privatedata, sharedata, graph, point):
1443 sharedata.vbarrange[sharedata.barvalueindex][0] = sharedata.vbarrange[sharedata.barvalueindex][1]
1444 if self.addontop:
1445 try:
1446 sharedata.lastbarvalue += point[self.stackname]
1447 except (ArithmeticError, ValueError, TypeError):
1448 sharedata.lastbarvalue = None
1449 else:
1450 sharedata.lastbarvalue = point[self.stackname]
1451 try:
1452 sharedata.vpos[sharedata.barvalueindex] = sharedata.vbarrange[sharedata.barvalueindex][1] = graph.axes[sharedata.barposcolumnnames[sharedata.barvalueindex]].convert(sharedata.lastbarvalue)
1453 except (ArithmeticError, ValueError, TypeError):
1454 sharedata.vpos[sharedata.barvalueindex] = sharedata.vbarrange[sharedata.barvalueindex][1] = None
1455 sharedata.vposavailable = sharedata.vposvalid = 0
1456 else:
1457 if not sharedata.vposavailable or not sharedata.vposvalid:
1458 sharedata.vposavailable = sharedata.vposvalid = 1
1459 for v in sharedata.vpos:
1460 if v is None:
1461 sharedata.vposavailable = sharedata.vposvalid = 0
1462 break
1463 if v < -self.epsilon or v > 1+self.epsilon:
1464 sharedata.vposvalid = 0
1467 class bar(_style):
1469 needsdata = ["vbarrange"]
1471 defaultbarattrs = [color.gradient.Rainbow, deco.stroked([color.grey.black])]
1473 def __init__(self, barattrs=[]):
1474 self.barattrs = barattrs
1476 def columnnames(self, privatedata, sharedata, graph, columnnames):
1477 if len(graph.axesnames) != 2:
1478 raise TypeError("bar style restricted on two-dimensional graphs")
1479 return []
1481 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1482 privatedata.barattrs = attr.selectattrs(self.defaultbarattrs + self.barattrs, selectindex, selecttotal)
1484 def initdrawpoints(self, privatedata, sharedata, graph):
1485 privatedata.rectcanvas = graph.insert(canvas.canvas())
1486 sharedata.stackedbardraw = 1
1487 privatedata.stackedbar = sharedata.stackedbar
1489 def drawpointfill(self, privatedata, p):
1490 if p:
1491 privatedata.rectcanvas.fill(p, privatedata.barattrs)
1493 def drawpoint(self, privatedata, sharedata, graph, point):
1494 xvmin = sharedata.vbarrange[0][0]
1495 xvmax = sharedata.vbarrange[0][1]
1496 yvmin = sharedata.vbarrange[1][0]
1497 yvmax = sharedata.vbarrange[1][1]
1498 try:
1499 if xvmin > xvmax:
1500 xvmin, xvmax = xvmax, xvmin
1501 except:
1502 pass
1503 try:
1504 if yvmin > yvmax:
1505 yvmin, yvmax = yvmax, yvmin
1506 except:
1507 pass
1508 if (xvmin is not None and xvmin < 1 and
1509 xvmax is not None and xvmax > 0 and
1510 yvmin is not None and yvmin < 1 and
1511 yvmax is not None and yvmax > 0):
1512 if xvmin < 0:
1513 xvmin = 0
1514 elif xvmax > 1:
1515 xvmax = 1
1516 if yvmin < 0:
1517 yvmin = 0
1518 elif yvmax > 1:
1519 yvmax = 1
1520 p = graph.vgeodesic(xvmin, yvmin, xvmax, yvmin)
1521 p.append(graph.vgeodesic_el(xvmax, yvmin, xvmax, yvmax))
1522 p.append(graph.vgeodesic_el(xvmax, yvmax, xvmin, yvmax))
1523 p.append(graph.vgeodesic_el(xvmin, yvmax, xvmin, yvmin))
1524 p.append(path.closepath())
1525 self.drawpointfill(privatedata, p)
1526 else:
1527 self.drawpointfill(privatedata, None)
1529 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
1530 selectindex = privatedata.stackedbar
1531 selecttotal = sharedata.stackedbar + 1
1532 graph.fill(path.rect_pt(x_pt + width_pt*selectindex/float(selecttotal), y_pt, width_pt/float(selecttotal), height_pt), privatedata.barattrs)
1535 class changebar(bar):
1537 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1538 if selecttotal != 1:
1539 raise RuntimeError("Changebar can't change its appearance. Thus you can't use it to plot several bars side by side on a subaxis.")
1541 def initdrawpoints(self, privatedata, sharedata, graph):
1542 bar.initdrawpoints(self, privatedata, sharedata, graph)
1543 privatedata.bars = []
1545 def drawpointfill(self, privatedata, p):
1546 privatedata.bars.append(p)
1548 def donedrawpoints(self, privatedata, sharedata, graph):
1549 selecttotal = len(privatedata.bars)
1550 for selectindex, p in enumerate(privatedata.bars):
1551 if p:
1552 barattrs = attr.selectattrs(self.defaultbarattrs + self.barattrs, selectindex, selecttotal)
1553 privatedata.rectcanvas.fill(p, barattrs)
1555 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
1556 raise RuntimeError("Style currently doesn't provide a graph key")
1559 class gridpos(_style):
1561 needsdata = ["vpos", "vposmissing", "vposavailable", "vposvalid"]
1562 providesdata = ["values1", "values2", "data12", "data21", "index1", "index2"]
1564 def __init__(self, index1=0, index2=1, epsilon=1e-10):
1565 self.index1 = index1
1566 self.index2 = index2
1567 self.epsilon = epsilon
1569 def initdrawpoints(self, privatedata, sharedata, graph):
1570 sharedata.index1 = self.index1
1571 sharedata.index2 = self.index2
1572 sharedata.values1 = {}
1573 sharedata.values2 = {}
1574 sharedata.data12 = {}
1575 sharedata.data21 = {}
1577 def drawpoint(self, privatedata, sharedata, graph, point):
1578 if sharedata.vposavailable:
1579 sharedata.value1 = sharedata.vpos[self.index1]
1580 sharedata.value2 = sharedata.vpos[self.index2]
1581 if not sharedata.values1.has_key(sharedata.value1):
1582 for hasvalue in sharedata.values1.keys():
1583 if hasvalue - self.epsilon <= sharedata.value1 <= hasvalue + self.epsilon:
1584 sharedata.value1 = hasvalue
1585 break
1586 else:
1587 sharedata.values1[sharedata.value1] = 1
1588 if not sharedata.values2.has_key(sharedata.value2):
1589 for hasvalue in sharedata.values2.keys():
1590 if hasvalue - self.epsilon <= sharedata.value2 <= hasvalue + self.epsilon:
1591 sharedata.value2 = hasvalue
1592 break
1593 else:
1594 sharedata.values2[sharedata.value2] = 1
1595 data = sharedata.vposavailable, sharedata.vposvalid, sharedata.vpos[:]
1596 sharedata.data12.setdefault(sharedata.value1, {})[sharedata.value2] = data
1597 sharedata.data21.setdefault(sharedata.value2, {})[sharedata.value1] = data
1599 registerdefaultprovider(gridpos(), gridpos.providesdata)
1602 class grid(_line, _style):
1604 needsdata = ["values1", "values2", "data12", "data21"]
1606 defaultgridattrs = [line.changelinestyle]
1608 def __init__(self, gridlines1=1, gridlines2=1, gridattrs=[]):
1609 self.gridlines1 = gridlines1
1610 self.gridlines2 = gridlines2
1611 self.gridattrs = gridattrs
1613 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1614 if self.gridattrs is not None:
1615 privatedata.gridattrs = attr.selectattrs(self.defaultgridattrs + self.gridattrs, selectindex, selecttotal)
1616 else:
1617 privatedata.gridattrs = None
1619 def donedrawpoints(self, privatedata, sharedata, graph):
1620 values1 = sharedata.values1.keys()
1621 values1.sort()
1622 values2 = sharedata.values2.keys()
1623 values2.sort()
1624 if self.gridlines1:
1625 for value2 in values2:
1626 data1 = sharedata.data21[value2]
1627 self.initpointstopath(privatedata)
1628 for value1 in values1:
1629 try:
1630 data = data1[value1]
1631 except KeyError:
1632 self.addinvalid(privatedata)
1633 else:
1634 self.addpoint(privatedata, graph.vpos_pt, *data)
1635 p = self.donepointstopath(privatedata)
1636 if len(p):
1637 graph.stroke(p, privatedata.gridattrs)
1638 if self.gridlines2:
1639 for value1 in values1:
1640 data2 = sharedata.data12[value1]
1641 self.initpointstopath(privatedata)
1642 for value2 in values2:
1643 try:
1644 data = data2[value2]
1645 except KeyError:
1646 self.addinvalid(privatedata)
1647 else:
1648 self.addpoint(privatedata, graph.vpos_pt, *data)
1649 p = self.donepointstopath(privatedata)
1650 if len(p):
1651 graph.stroke(p, privatedata.gridattrs)
1654 class surface(_style):
1656 needsdata = ["values1", "values2", "data12", "data21"]
1658 def __init__(self, colorname="color", gradient=color.gradient.Grey, mincolor=None, maxcolor=None,
1659 gridlines1=0.05, gridlines2=0.05, gridcolor=None,
1660 backcolor=color.gray.black, **kwargs):
1661 self.colorname = colorname
1662 self.gradient = gradient
1663 self.mincolor = mincolor
1664 self.maxcolor = maxcolor
1665 self.gridlines1 = gridlines1
1666 self.gridlines2 = gridlines2
1667 self.gridcolor = gridcolor
1668 self.backcolor = backcolor
1670 colorspacestring = gradient.getcolor(0).colorspacestring()
1671 if self.gridcolor is not None and self.gridcolor.colorspacestring() != colorspacestring:
1672 raise RuntimeError("colorspace mismatch (gradient/grid)")
1673 if self.backcolor is not None and self.backcolor.colorspacestring() != colorspacestring:
1674 raise RuntimeError("colorspace mismatch (gradient/back)")
1676 def midvalue(self, v1, v2, v3, v4):
1677 return [0.25*sum(values) for values in zip(v1, v2, v3, v4)]
1679 def midcolor(self, c1, c2, c3, c4):
1680 return 0.25*(c1+c2+c3+c4)
1682 def lightning(self, angle, zindex):
1683 if angle < 0 and self.backcolor is not None:
1684 return self.backcolor
1685 return self.gradient.getcolor(0.7-0.4*abs(angle)+0.1*zindex)
1687 def columnnames(self, privatedata, sharedata, graph, columnnames):
1688 privatedata.colorize = self.colorname in columnnames
1689 if privatedata.colorize:
1690 return [self.colorname]
1691 return []
1693 def initdrawpoints(self, privatedata, sharedata, graph):
1694 privatedata.colors = {}
1695 privatedata.mincolor = privatedata.maxcolor = None
1697 def drawpoint(self, privatedata, sharedata, graph, point):
1698 if privatedata.colorize:
1699 try:
1700 color = point[self.colorname] + 0
1701 except:
1702 pass
1703 else:
1704 privatedata.colors.setdefault(sharedata.value1, {})[sharedata.value2] = color
1705 if privatedata.mincolor is None or color < privatedata.mincolor:
1706 privatedata.mincolor = color
1707 if privatedata.mincolor is None or privatedata.maxcolor < color:
1708 privatedata.maxcolor = color
1710 def donedrawpoints(self, privatedata, sharedata, graph):
1711 v1 = [0]*len(graph.axesnames)
1712 v2 = [0]*len(graph.axesnames)
1713 v3 = [0]*len(graph.axesnames)
1714 v4 = [0]*len(graph.axesnames)
1715 v1[sharedata.index2] = 0.5
1716 v2[sharedata.index1] = 0.5
1717 v3[sharedata.index1] = 0.5
1718 v3[sharedata.index2] = 1
1719 v4[sharedata.index1] = 1
1720 v4[sharedata.index2] = 0.5
1721 sortElements = [-graph.vzindex(*v1),
1722 -graph.vzindex(*v2),
1723 -graph.vzindex(*v3),
1724 -graph.vzindex(*v4)]
1726 values1 = sharedata.values1.keys()
1727 values1.sort()
1728 v1 = [0]*len(graph.axesnames)
1729 v2 = [0]*len(graph.axesnames)
1730 v1[sharedata.index1] = -1
1731 v2[sharedata.index1] = 1
1732 sign = 1
1733 if graph.vzindex(*v1) < graph.vzindex(*v2):
1734 values1.reverse()
1735 sign *= -1
1736 sortElements = [sortElements[3], sortElements[1], sortElements[2], sortElements[0]]
1738 values2 = sharedata.values2.keys()
1739 values2.sort()
1740 v1 = [0]*len(graph.axesnames)
1741 v2 = [0]*len(graph.axesnames)
1742 v1[sharedata.index2] = -1
1743 v2[sharedata.index2] = 1
1744 if graph.vzindex(*v1) < graph.vzindex(*v2):
1745 values2.reverse()
1746 sign *= -1
1747 sortElements = [sortElements[0], sortElements[2], sortElements[1], sortElements[3]]
1749 sortElements = [(zindex, i) for i, zindex in enumerate(sortElements)]
1750 sortElements.sort()
1752 if self.mincolor is not None:
1753 mincolor = self.mincolor
1754 if self.maxcolor is not None:
1755 maxcolor = self.maxcolor
1756 nodes = []
1757 elements = []
1758 for value1a, value1b in zip(values1[:-1], values1[1:]):
1759 for value2a, value2b in zip(values2[:-1], values2[1:]):
1760 try:
1761 available1, valid1, v1 = sharedata.data12[value1a][value2a]
1762 available2, valid2, v2 = sharedata.data12[value1a][value2b]
1763 available3, valid3, v3 = sharedata.data12[value1b][value2a]
1764 available4, valid4, v4 = sharedata.data12[value1b][value2b]
1765 except KeyError:
1766 continue
1767 if not available1 or not available2 or not available3 or not available4:
1768 continue
1769 if not valid1 or not valid2 or not valid3 or not valid4:
1770 warnings.warn("surface elements partially outside of the graph are (currently) skipped completely")
1771 continue
1772 def shrink(index, v1, v2, by):
1773 v1 = v1[:]
1774 v2 = v2[:]
1775 for i in builtinrange(3):
1776 if i != index:
1777 v1[i], v2[i] = v1[i] + by*(v2[i]-v1[i]), v2[i] + by*(v1[i]-v2[i])
1778 return v1, v2
1779 v1f, v2f, v3f, v4f = v1, v2, v3, v4
1780 if self.gridcolor is not None and self.gridlines1:
1781 v1, v2 = shrink(sharedata.index1, v1, v2, self.gridlines1)
1782 v3, v4 = shrink(sharedata.index1, v3, v4, self.gridlines1)
1783 if self.gridcolor is not None and self.gridlines2:
1784 v1, v3 = shrink(sharedata.index2, v1, v3, self.gridlines2)
1785 v2, v4 = shrink(sharedata.index2, v2, v4, self.gridlines2)
1786 v5 = self.midvalue(v1, v2, v3, v4)
1787 x1_pt, y1_pt = graph.vpos_pt(*v1)
1788 x2_pt, y2_pt = graph.vpos_pt(*v2)
1789 x3_pt, y3_pt = graph.vpos_pt(*v3)
1790 x4_pt, y4_pt = graph.vpos_pt(*v4)
1791 x5_pt, y5_pt = graph.vpos_pt(*v5)
1792 if privatedata.colorize:
1793 def colorfromgradient(c):
1794 return self.gradient.getcolor((c - privatedata.mincolor) /
1795 float(privatedata.maxcolor - privatedata.mincolor))
1796 c1 = privatedata.colors[value1a][value2a]
1797 c2 = privatedata.colors[value1a][value2b]
1798 c3 = privatedata.colors[value1b][value2a]
1799 c4 = privatedata.colors[value1b][value2b]
1800 c5 = self.midcolor(c1, c2, c3, c4)
1801 c1a = c1b = colorfromgradient(c1)
1802 c2a = c2c = colorfromgradient(c2)
1803 c3b = c3d = colorfromgradient(c3)
1804 c4c = c4d = colorfromgradient(c4)
1805 c5a = c5b = c5c = c5d = colorfromgradient(c5)
1806 if self.backcolor is not None and sign*graph.vangle(*(v1+v2+v5)) < 0:
1807 c1a = c2a = c5a = self.backcolor
1808 if self.backcolor is not None and sign*graph.vangle(*(v3+v1+v5)) < 0:
1809 c3b = c1b = c5b = self.backcolor
1810 if self.backcolor is not None and sign*graph.vangle(*(v2+v4+v5)) < 0:
1811 c2c = c4c = c5c = self.backcolor
1812 if self.backcolor is not None and sign*graph.vangle(*(v4+v3+v5)) < 0:
1813 c4d = c3d = c5d = self.backcolor
1814 else:
1815 zindex = graph.vzindex(*v5)
1816 c1a = c2a = c5a = self.lightning(sign*graph.vangle(*(v1+v2+v5)), zindex)
1817 c3b = c1b = c5b = self.lightning(sign*graph.vangle(*(v3+v1+v5)), zindex)
1818 c2c = c4c = c5c = self.lightning(sign*graph.vangle(*(v2+v4+v5)), zindex)
1819 c4d = c3d = c5d = self.lightning(sign*graph.vangle(*(v4+v3+v5)), zindex)
1820 for zindex, i in sortElements:
1821 if i == 0:
1822 elements.append(mesh.element((mesh.node_pt((x1_pt, y1_pt), c1a),
1823 mesh.node_pt((x2_pt, y2_pt), c2a),
1824 mesh.node_pt((x5_pt, y5_pt), c5a))))
1825 if self.gridcolor is not None and self.gridlines2:
1826 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v1f), self.gridcolor),
1827 mesh.node_pt(graph.vpos_pt(*v2), self.gridcolor),
1828 mesh.node_pt(graph.vpos_pt(*v1), self.gridcolor))))
1829 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v1f), self.gridcolor),
1830 mesh.node_pt(graph.vpos_pt(*v2), self.gridcolor),
1831 mesh.node_pt(graph.vpos_pt(*v2f), self.gridcolor))))
1832 elif i == 1:
1833 elements.append(mesh.element((mesh.node_pt((x3_pt, y3_pt), c3b),
1834 mesh.node_pt((x1_pt, y1_pt), c1b),
1835 mesh.node_pt((x5_pt, y5_pt), c5b))))
1836 if self.gridcolor is not None and self.gridlines1:
1837 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v1f), self.gridcolor),
1838 mesh.node_pt(graph.vpos_pt(*v3), self.gridcolor),
1839 mesh.node_pt(graph.vpos_pt(*v1), self.gridcolor))))
1840 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v1f), self.gridcolor),
1841 mesh.node_pt(graph.vpos_pt(*v3), self.gridcolor),
1842 mesh.node_pt(graph.vpos_pt(*v3f), self.gridcolor))))
1843 elif i == 2:
1844 elements.append(mesh.element((mesh.node_pt((x2_pt, y2_pt), c2c),
1845 mesh.node_pt((x4_pt, y4_pt), c4c),
1846 mesh.node_pt((x5_pt, y5_pt), c5c))))
1847 if self.gridcolor is not None and self.gridlines1:
1848 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v2f), self.gridcolor),
1849 mesh.node_pt(graph.vpos_pt(*v4), self.gridcolor),
1850 mesh.node_pt(graph.vpos_pt(*v2), self.gridcolor))))
1851 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v2f), self.gridcolor),
1852 mesh.node_pt(graph.vpos_pt(*v4), self.gridcolor),
1853 mesh.node_pt(graph.vpos_pt(*v4f), self.gridcolor))))
1854 elif i == 3:
1855 elements.append(mesh.element((mesh.node_pt((x4_pt, y4_pt), c4d),
1856 mesh.node_pt((x3_pt, y3_pt), c3d),
1857 mesh.node_pt((x5_pt, y5_pt), c5d))))
1858 if self.gridcolor is not None and self.gridlines2:
1859 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v3f), self.gridcolor),
1860 mesh.node_pt(graph.vpos_pt(*v4), self.gridcolor),
1861 mesh.node_pt(graph.vpos_pt(*v3), self.gridcolor))))
1862 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v3f), self.gridcolor),
1863 mesh.node_pt(graph.vpos_pt(*v4), self.gridcolor),
1864 mesh.node_pt(graph.vpos_pt(*v4f), self.gridcolor))))
1865 m = mesh.mesh(elements, check=0)
1866 graph.insert(m)