add arrowpos to the arrow style of the graph
[PyX.git] / pyx / graph / style.py
blobfdfb23bdb3c5efaf53b8329800e19dc616a54b51
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
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 try:
295 mindata.append(d[self.mask_value] - d[k])
296 except:
297 pass
298 graph.axes[a].adjustaxis(mindata)
299 if k & (self.mask_dmax | self.mask_d):
300 maxdata = []
301 try:
302 maxdata.append(d[self.mask_value] + d[k])
303 except:
304 pass
305 graph.axes[a].adjustaxis(maxdata)
306 del d[k]
308 def initdrawpoints(self, privatedata, sharedata, graph):
309 sharedata.vrange = [[None for x in xrange(2)] for y in privatedata.rangeposcolumns + sharedata.vrangemissing]
310 privatedata.rangepostmplist = [[usename, mask, index, graph.axes[axisname]] # temporarily used by drawpoint only
311 for index, (axisname, usename, mask) in enumerate(privatedata.rangeposcolumns)]
312 for missing in sharedata.vrangemissing:
313 for rangepostmp in privatedata.rangepostmplist:
314 if rangepostmp[2] >= missing:
315 rangepostmp[2] += 1
317 def drawpoint(self, privatedata, sharedata, graph, point):
318 for usename, mask, index, axis in privatedata.rangepostmplist:
319 try:
320 if mask & self.mask_min:
321 sharedata.vrange[index][0] = axis.convert(point[usename + "min"])
322 if mask & self.mask_dmin:
323 sharedata.vrange[index][0] = axis.convert(point[usename] - point["d" + usename + "min"])
324 if mask & self.mask_d:
325 sharedata.vrange[index][0] = axis.convert(point[usename] - point["d" + usename])
326 except (ArithmeticError, ValueError, TypeError):
327 sharedata.vrange[index][0] = None
328 try:
329 if mask & self.mask_max:
330 sharedata.vrange[index][1] = axis.convert(point[usename + "max"])
331 if mask & self.mask_dmax:
332 sharedata.vrange[index][1] = axis.convert(point[usename] + point["d" + usename + "max"])
333 if mask & self.mask_d:
334 sharedata.vrange[index][1] = axis.convert(point[usename] + point["d" + usename])
335 except (ArithmeticError, ValueError, TypeError):
336 sharedata.vrange[index][1] = None
338 # some range checks for data consistency
339 if (sharedata.vrange[index][0] is not None and sharedata.vrange[index][1] is not None and
340 sharedata.vrange[index][0] > sharedata.vrange[index][1] + self.epsilon):
341 raise ValueError("inverse range")
342 # disabled due to missing vpos access:
343 # if (sharedata.vrange[index][0] is not None and sharedata.vpos[index] is not None and
344 # sharedata.vrange[index][0] > sharedata.vpos[index] + self.epsilon):
345 # raise ValueError("negative minimum errorbar")
346 # if (sharedata.vrange[index][1] is not None and sharedata.vpos[index] is not None and
347 # sharedata.vrange[index][1] < sharedata.vpos[index] - self.epsilon):
348 # raise ValueError("negative maximum errorbar")
351 registerdefaultprovider(range(), range.providesdata)
354 def _crosssymbol(c, x_pt, y_pt, size_pt, attrs):
355 c.draw(path.path(path.moveto_pt(x_pt-0.5*size_pt, y_pt-0.5*size_pt),
356 path.lineto_pt(x_pt+0.5*size_pt, y_pt+0.5*size_pt),
357 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)), attrs)
360 def _plussymbol(c, x_pt, y_pt, size_pt, attrs):
361 c.draw(path.path(path.moveto_pt(x_pt-0.707106781*size_pt, y_pt),
362 path.lineto_pt(x_pt+0.707106781*size_pt, y_pt),
363 path.moveto_pt(x_pt, y_pt-0.707106781*size_pt),
364 path.lineto_pt(x_pt, y_pt+0.707106781*size_pt)), attrs)
366 def _squaresymbol(c, x_pt, y_pt, size_pt, attrs):
367 c.draw(path.path(path.moveto_pt(x_pt-0.5*size_pt, y_pt-0.5*size_pt),
368 path.lineto_pt(x_pt+0.5*size_pt, y_pt-0.5*size_pt),
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.closepath()), attrs)
373 def _trianglesymbol(c, x_pt, y_pt, size_pt, attrs):
374 c.draw(path.path(path.moveto_pt(x_pt-0.759835685*size_pt, y_pt-0.438691337*size_pt),
375 path.lineto_pt(x_pt+0.759835685*size_pt, y_pt-0.438691337*size_pt),
376 path.lineto_pt(x_pt, y_pt+0.877382675*size_pt),
377 path.closepath()), attrs)
379 def _circlesymbol(c, x_pt, y_pt, size_pt, attrs):
380 c.draw(path.path(path.arc_pt(x_pt, y_pt, 0.564189583*size_pt, 0, 360),
381 path.closepath()), attrs)
383 def _diamondsymbol(c, x_pt, y_pt, size_pt, attrs):
384 c.draw(path.path(path.moveto_pt(x_pt-0.537284965*size_pt, y_pt),
385 path.lineto_pt(x_pt, y_pt-0.930604859*size_pt),
386 path.lineto_pt(x_pt+0.537284965*size_pt, y_pt),
387 path.lineto_pt(x_pt, y_pt+0.930604859*size_pt),
388 path.closepath()), attrs)
391 class _styleneedingpointpos(_style):
393 needsdata = ["vposmissing"]
395 def columnnames(self, privatedata, sharedata, graph, columnnames):
396 if len(sharedata.vposmissing):
397 raise ValueError("incomplete position information")
398 return []
401 class symbol(_styleneedingpointpos):
403 needsdata = ["vpos", "vposmissing", "vposvalid"]
405 # "inject" the predefinied symbols into the class:
407 # Note, that statements like cross = _crosssymbol are
408 # invalid, since the would lead to unbound methods, but
409 # a single entry changeable list does the trick.
411 # Once we require Python 2.2+ we should use staticmethods
412 # to implement the default symbols inplace.
414 cross = attr.changelist([_crosssymbol])
415 plus = attr.changelist([_plussymbol])
416 square = attr.changelist([_squaresymbol])
417 triangle = attr.changelist([_trianglesymbol])
418 circle = attr.changelist([_circlesymbol])
419 diamond = attr.changelist([_diamondsymbol])
421 changecross = attr.changelist([_crosssymbol, _plussymbol, _squaresymbol, _trianglesymbol, _circlesymbol, _diamondsymbol])
422 changeplus = attr.changelist([_plussymbol, _squaresymbol, _trianglesymbol, _circlesymbol, _diamondsymbol, _crosssymbol])
423 changesquare = attr.changelist([_squaresymbol, _trianglesymbol, _circlesymbol, _diamondsymbol, _crosssymbol, _plussymbol])
424 changetriangle = attr.changelist([_trianglesymbol, _circlesymbol, _diamondsymbol, _crosssymbol, _plussymbol, _squaresymbol])
425 changecircle = attr.changelist([_circlesymbol, _diamondsymbol, _crosssymbol, _plussymbol, _squaresymbol, _trianglesymbol])
426 changediamond = attr.changelist([_diamondsymbol, _crosssymbol, _plussymbol, _squaresymbol, _trianglesymbol, _circlesymbol])
427 changesquaretwice = attr.changelist([_squaresymbol, _squaresymbol, _trianglesymbol, _trianglesymbol, _circlesymbol, _circlesymbol, _diamondsymbol, _diamondsymbol])
428 changetriangletwice = attr.changelist([_trianglesymbol, _trianglesymbol, _circlesymbol, _circlesymbol, _diamondsymbol, _diamondsymbol, _squaresymbol, _squaresymbol])
429 changecircletwice = attr.changelist([_circlesymbol, _circlesymbol, _diamondsymbol, _diamondsymbol, _squaresymbol, _squaresymbol, _trianglesymbol, _trianglesymbol])
430 changediamondtwice = attr.changelist([_diamondsymbol, _diamondsymbol, _squaresymbol, _squaresymbol, _trianglesymbol, _trianglesymbol, _circlesymbol, _circlesymbol])
432 changestrokedfilled = attr.changelist([deco.stroked, deco.filled])
433 changefilledstroked = attr.changelist([deco.filled, deco.stroked])
435 defaultsymbolattrs = [deco.stroked]
437 def __init__(self, symbol=changecross, size=0.2*unit.v_cm, symbolattrs=[]):
438 self.symbol = symbol
439 self.size = size
440 self.symbolattrs = symbolattrs
442 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
443 privatedata.symbol = attr.selectattr(self.symbol, selectindex, selecttotal)
444 privatedata.size_pt = unit.topt(attr.selectattr(self.size, selectindex, selecttotal))
445 if self.symbolattrs is not None:
446 privatedata.symbolattrs = attr.selectattrs(self.defaultsymbolattrs + self.symbolattrs, selectindex, selecttotal)
447 else:
448 privatedata.symbolattrs = None
450 def initdrawpoints(self, privatedata, sharedata, graph):
451 privatedata.symbolcanvas = canvas.canvas()
453 def drawpoint(self, privatedata, sharedata, graph, point):
454 if sharedata.vposvalid and privatedata.symbolattrs is not None:
455 x_pt, y_pt = graph.vpos_pt(*sharedata.vpos)
456 privatedata.symbol(privatedata.symbolcanvas, x_pt, y_pt, privatedata.size_pt, privatedata.symbolattrs)
458 def donedrawpoints(self, privatedata, sharedata, graph):
459 graph.insert(privatedata.symbolcanvas)
461 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
462 if privatedata.symbolattrs is not None:
463 privatedata.symbol(graph, x_pt+0.5*width_pt, y_pt+0.5*height_pt, privatedata.size_pt, privatedata.symbolattrs)
466 class _line(_styleneedingpointpos):
468 # this style is not a complete style, but it provides the basic functionality to
469 # create a line, which is cut at the graph boundaries (or at otherwise invalid points)
471 needsdata = ["vposmissing"]
473 def initpointstopath(self, privatedata):
474 privatedata.path = path.path()
475 privatedata.linebasepoints = []
476 privatedata.lastvpos = None
478 def addpointstopath(self, privatedata):
479 # add baselinepoints to privatedata.path
480 if len(privatedata.linebasepoints) > 1:
481 privatedata.path.append(path.moveto_pt(*privatedata.linebasepoints[0]))
482 if len(privatedata.linebasepoints) > 2:
483 privatedata.path.append(path.multilineto_pt(privatedata.linebasepoints[1:]))
484 else:
485 privatedata.path.append(path.lineto_pt(*privatedata.linebasepoints[1]))
486 privatedata.linebasepoints = []
488 def addpoint(self, privatedata, graphvpos_pt, vposavailable, vposvalid, vpos):
489 # append linebasepoints
490 if vposavailable:
491 if len(privatedata.linebasepoints):
492 # the last point was inside the graph
493 if vposvalid: # shortcut for the common case
494 privatedata.linebasepoints.append(graphvpos_pt(*vpos))
495 else:
496 # cut end
497 cut = 1
498 for vstart, vend in zip(privatedata.lastvpos, vpos):
499 newcut = None
500 if vend > 1:
501 # 1 = vstart + (vend - vstart) * cut
502 try:
503 newcut = (1 - vstart)/(vend - vstart)
504 except (ArithmeticError, TypeError):
505 break
506 if vend < 0:
507 # 0 = vstart + (vend - vstart) * cut
508 try:
509 newcut = - vstart/(vend - vstart)
510 except (ArithmeticError, TypeError):
511 break
512 if newcut is not None and newcut < cut:
513 cut = newcut
514 else:
515 cutvpos = []
516 for vstart, vend in zip(privatedata.lastvpos, vpos):
517 cutvpos.append(vstart + (vend - vstart) * cut)
518 privatedata.linebasepoints.append(graphvpos_pt(*cutvpos))
519 self.addpointstopath(privatedata)
520 else:
521 # the last point was outside the graph
522 if privatedata.lastvpos is not None:
523 if vposvalid:
524 # cut beginning
525 cut = 0
526 for vstart, vend in zip(privatedata.lastvpos, vpos):
527 newcut = None
528 if vstart > 1:
529 # 1 = vstart + (vend - vstart) * cut
530 try:
531 newcut = (1 - vstart)/(vend - vstart)
532 except (ArithmeticError, TypeError):
533 break
534 if vstart < 0:
535 # 0 = vstart + (vend - vstart) * cut
536 try:
537 newcut = - vstart/(vend - vstart)
538 except (ArithmeticError, TypeError):
539 break
540 if newcut is not None and newcut > cut:
541 cut = newcut
542 else:
543 cutvpos = []
544 for vstart, vend in zip(privatedata.lastvpos, vpos):
545 cutvpos.append(vstart + (vend - vstart) * cut)
546 privatedata.linebasepoints.append(graphvpos_pt(*cutvpos))
547 privatedata.linebasepoints.append(graphvpos_pt(*vpos))
548 else:
549 # sometimes cut beginning and end
550 cutfrom = 0
551 cutto = 1
552 for vstart, vend in zip(privatedata.lastvpos, vpos):
553 newcutfrom = None
554 if vstart > 1:
555 if vend > 1:
556 break
557 # 1 = vstart + (vend - vstart) * cutfrom
558 try:
559 newcutfrom = (1 - vstart)/(vend - vstart)
560 except (ArithmeticError, TypeError):
561 break
562 if vstart < 0:
563 if vend < 0:
564 break
565 # 0 = vstart + (vend - vstart) * cutfrom
566 try:
567 newcutfrom = - vstart/(vend - vstart)
568 except (ArithmeticError, TypeError):
569 break
570 if newcutfrom is not None and newcutfrom > cutfrom:
571 cutfrom = newcutfrom
572 newcutto = None
573 if vend > 1:
574 # 1 = vstart + (vend - vstart) * cutto
575 try:
576 newcutto = (1 - vstart)/(vend - vstart)
577 except (ArithmeticError, TypeError):
578 break
579 if vend < 0:
580 # 0 = vstart + (vend - vstart) * cutto
581 try:
582 newcutto = - vstart/(vend - vstart)
583 except (ArithmeticError, TypeError):
584 break
585 if newcutto is not None and newcutto < cutto:
586 cutto = newcutto
587 else:
588 if cutfrom < cutto:
589 cutfromvpos = []
590 cuttovpos = []
591 for vstart, vend in zip(privatedata.lastvpos, vpos):
592 cutfromvpos.append(vstart + (vend - vstart) * cutfrom)
593 cuttovpos.append(vstart + (vend - vstart) * cutto)
594 privatedata.linebasepoints.append(graphvpos_pt(*cutfromvpos))
595 privatedata.linebasepoints.append(graphvpos_pt(*cuttovpos))
596 self.addpointstopath(privatedata)
597 privatedata.lastvpos = vpos[:]
598 else:
599 if len(privatedata.linebasepoints) > 1:
600 self.addpointstopath(privatedata)
601 privatedata.lastvpos = None
603 def addinvalid(self, privatedata):
604 if len(privatedata.linebasepoints) > 1:
605 self.addpointstopath(privatedata)
606 privatedata.lastvpos = None
608 def donepointstopath(self, privatedata):
609 if len(privatedata.linebasepoints) > 1:
610 self.addpointstopath(privatedata)
611 return privatedata.path
614 class line(_line):
616 needsdata = ["vpos", "vposmissing", "vposavailable", "vposvalid"]
618 changelinestyle = attr.changelist([style.linestyle.solid,
619 style.linestyle.dashed,
620 style.linestyle.dotted,
621 style.linestyle.dashdotted])
623 defaultlineattrs = [changelinestyle]
625 def __init__(self, lineattrs=[]):
626 self.lineattrs = lineattrs
628 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
629 if self.lineattrs is not None:
630 privatedata.lineattrs = attr.selectattrs(self.defaultlineattrs + self.lineattrs, selectindex, selecttotal)
631 else:
632 privatedata.lineattrs = None
634 def initdrawpoints(self, privatedata, sharedata, graph):
635 self.initpointstopath(privatedata)
637 def drawpoint(self, privatedata, sharedata, graph, point):
638 self.addpoint(privatedata, graph.vpos_pt, sharedata.vposavailable, sharedata.vposvalid, sharedata.vpos)
640 def donedrawpoints(self, privatedata, sharedata, graph):
641 path = self.donepointstopath(privatedata)
642 if privatedata.lineattrs is not None and len(path):
643 graph.stroke(path, privatedata.lineattrs)
645 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
646 if privatedata.lineattrs is not None:
647 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)
650 class errorbar(_style):
652 needsdata = ["vpos", "vposmissing", "vposavailable", "vposvalid", "vrange", "vrangeminmissing", "vrangemaxmissing"]
654 defaulterrorbarattrs = []
656 def __init__(self, size=0.1*unit.v_cm,
657 errorbarattrs=[],
658 epsilon=1e-10):
659 self.size = size
660 self.errorbarattrs = errorbarattrs
661 self.epsilon = epsilon
663 def columnnames(self, privatedata, sharedata, graph, columnnames):
664 for i in sharedata.vposmissing:
665 if i in sharedata.vrangeminmissing and i in sharedata.vrangemaxmissing:
666 raise ValueError("position and range for a graph dimension missing")
667 return []
669 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
670 privatedata.errorsize_pt = unit.topt(attr.selectattr(self.size, selectindex, selecttotal))
671 privatedata.errorbarattrs = attr.selectattrs(self.defaulterrorbarattrs + self.errorbarattrs, selectindex, selecttotal)
673 def initdrawpoints(self, privatedata, sharedata, graph):
674 if privatedata.errorbarattrs is not None:
675 privatedata.errorbarcanvas = canvas.canvas(privatedata.errorbarattrs)
676 privatedata.dimensionlist = list(xrange(len(sharedata.vpos)))
678 def drawpoint(self, privatedata, sharedata, graph, point):
679 if privatedata.errorbarattrs is not None:
680 for i in privatedata.dimensionlist:
681 for j in privatedata.dimensionlist:
682 if (i != j and
683 (sharedata.vpos[j] is None or
684 sharedata.vpos[j] < -self.epsilon or
685 sharedata.vpos[j] > 1+self.epsilon)):
686 break
687 else:
688 if ((sharedata.vrange[i][0] is None and sharedata.vpos[i] is None) or
689 (sharedata.vrange[i][1] is None and sharedata.vpos[i] is None) or
690 (sharedata.vrange[i][0] is None and sharedata.vrange[i][1] is None)):
691 continue
692 vminpos = sharedata.vpos[:]
693 if sharedata.vrange[i][0] is not None:
694 vminpos[i] = sharedata.vrange[i][0]
695 mincap = 1
696 else:
697 mincap = 0
698 if vminpos[i] > 1+self.epsilon:
699 continue
700 if vminpos[i] < -self.epsilon:
701 vminpos[i] = 0
702 mincap = 0
703 vmaxpos = sharedata.vpos[:]
704 if sharedata.vrange[i][1] is not None:
705 vmaxpos[i] = sharedata.vrange[i][1]
706 maxcap = 1
707 else:
708 maxcap = 0
709 if vmaxpos[i] < -self.epsilon:
710 continue
711 if vmaxpos[i] > 1+self.epsilon:
712 vmaxpos[i] = 1
713 maxcap = 0
714 privatedata.errorbarcanvas.stroke(graph.vgeodesic(*(vminpos + vmaxpos)))
715 for j in privatedata.dimensionlist:
716 if i != j:
717 if mincap:
718 privatedata.errorbarcanvas.stroke(graph.vcap_pt(j, privatedata.errorsize_pt, *vminpos))
719 if maxcap:
720 privatedata.errorbarcanvas.stroke(graph.vcap_pt(j, privatedata.errorsize_pt, *vmaxpos))
722 def donedrawpoints(self, privatedata, sharedata, graph):
723 if privatedata.errorbarattrs is not None:
724 graph.insert(privatedata.errorbarcanvas)
727 class text(_styleneedingpointpos):
729 needsdata = ["vpos", "vposmissing", "vposvalid"]
731 defaulttextattrs = [textmodule.halign.center, textmodule.vshift.mathaxis]
733 def __init__(self, textname="text", dxname=None, dyname=None,
734 dxunit=0.3*unit.v_cm, dyunit=0.3*unit.v_cm,
735 textdx=0*unit.v_cm, textdy=0.3*unit.v_cm, textattrs=[]):
736 self.textname = textname
737 self.dxname = dxname
738 self.dyname = dyname
739 self.dxunit = dxunit
740 self.dyunit = dyunit
741 self.textdx = textdx
742 self.textdy = textdy
743 self.textattrs = textattrs
745 def columnnames(self, privatedata, sharedata, graph, columnnames):
746 if self.textname not in columnnames:
747 raise ValueError("column '%s' missing" % self.textname)
748 names = [self.textname]
749 if self.dxname is not None:
750 if self.dxname not in columnnames:
751 raise ValueError("column '%s' missing" % self.dxname)
752 names.append(self.dxname)
753 if self.dyname is not None:
754 if self.dyname not in columnnames:
755 raise ValueError("column '%s' missing" % self.dyname)
756 names.append(self.dyname)
757 return names + _styleneedingpointpos.columnnames(self, privatedata, sharedata, graph, columnnames)
759 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
760 if self.textattrs is not None:
761 privatedata.textattrs = attr.selectattrs(self.defaulttextattrs + self.textattrs, selectindex, selecttotal)
762 else:
763 privatedata.textattrs = None
765 def initdrawpoints(self, privatedata, sharedata, grap):
766 if self.dxname is None:
767 privatedata.textdx_pt = unit.topt(self.textdx)
768 else:
769 privatedata.dxunit_pt = unit.topt(self.dxunit)
770 if self.dyname is None:
771 privatedata.textdy_pt = unit.topt(self.textdy)
772 else:
773 privatedata.dyunit_pt = unit.topt(self.dyunit)
775 def drawpoint(self, privatedata, sharedata, graph, point):
776 if privatedata.textattrs is not None and sharedata.vposvalid:
777 x_pt, y_pt = graph.vpos_pt(*sharedata.vpos)
778 try:
779 text = str(point[self.textname])
780 except:
781 pass
782 else:
783 if self.dxname is None:
784 dx_pt = privatedata.textdx_pt
785 else:
786 dx_pt = float(point[self.dxname]) * privatedata.dxunit_pt
787 if self.dyname is None:
788 dy_pt = privatedata.textdy_pt
789 else:
790 dy_pt = float(point[self.dyname]) * privatedata.dyunit_pt
791 graph.text_pt(x_pt + dx_pt, y_pt + dy_pt, text, privatedata.textattrs)
793 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
794 raise RuntimeError("Style currently doesn't provide a graph key")
797 class arrow(_styleneedingpointpos):
799 needsdata = ["vpos", "vposmissing", "vposvalid"]
801 defaultlineattrs = []
802 defaultarrowattrs = []
804 def __init__(self, linelength=0.25*unit.v_cm, arrowsize=0.15*unit.v_cm, lineattrs=[], arrowattrs=[], arrowpos=0.5, epsilon=1e-5):
805 self.linelength = linelength
806 self.arrowsize = arrowsize
807 self.lineattrs = lineattrs
808 self.arrowattrs = arrowattrs
809 self.arrowpos = arrowpos
810 self.epsilon = epsilon
812 def columnnames(self, privatedata, sharedata, graph, columnnames):
813 if len(graph.axesnames) != 2:
814 raise ValueError("arrow style restricted on two-dimensional graphs")
815 if "size" not in columnnames:
816 raise ValueError("size missing")
817 if "angle" not in columnnames:
818 raise ValueError("angle missing")
819 return ["size", "angle"] + _styleneedingpointpos.columnnames(self, privatedata, sharedata, graph, columnnames)
821 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
822 if self.lineattrs is not None:
823 privatedata.lineattrs = attr.selectattrs(self.defaultlineattrs + self.lineattrs, selectindex, selecttotal)
824 else:
825 privatedata.lineattrs = None
826 if self.arrowattrs is not None:
827 privatedata.arrowattrs = attr.selectattrs(self.defaultarrowattrs + self.arrowattrs, selectindex, selecttotal)
828 else:
829 privatedata.arrowattrs = None
831 def initdrawpoints(self, privatedata, sharedata, graph):
832 privatedata.arrowcanvas = canvas.canvas()
834 def drawpoint(self, privatedata, sharedata, graph, point):
835 if privatedata.lineattrs is not None and privatedata.arrowattrs is not None and sharedata.vposvalid:
836 linelength_pt = unit.topt(self.linelength)
837 x_pt, y_pt = graph.vpos_pt(*sharedata.vpos)
838 try:
839 angle = point["angle"] + 0.0
840 size = point["size"] + 0.0
841 except:
842 pass
843 else:
844 if point["size"] > self.epsilon:
845 dx = math.cos(angle*math.pi/180)
846 dy = math.sin(angle*math.pi/180)
847 x1 = x_pt-self.arrowpos*dx*linelength_pt*size
848 y1 = y_pt-self.arrowpos*dy*linelength_pt*size
849 x2 = x_pt+(1-self.arrowpos)*dx*linelength_pt*size
850 y2 = y_pt+(1-self.arrowpos)*dy*linelength_pt*size
851 privatedata.arrowcanvas.stroke(path.line_pt(x1, y1, x2, y2), privatedata.lineattrs +
852 [deco.earrow(privatedata.arrowattrs, size=self.arrowsize*size)])
854 def donedrawpoints(self, privatedata, sharedata, graph):
855 graph.insert(privatedata.arrowcanvas)
857 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
858 raise RuntimeError("Style currently doesn't provide a graph key")
861 class rect(_style):
863 needsdata = ["vrange", "vrangeminmissing", "vrangemaxmissing"]
865 def __init__(self, gradient=color.gradient.Grey):
866 self.gradient = gradient
868 def columnnames(self, privatedata, sharedata, graph, columnnames):
869 if len(graph.axesnames) != 2:
870 raise TypeError("arrow style restricted on two-dimensional graphs")
871 if "color" not in columnnames:
872 raise ValueError("color missing")
873 if len(sharedata.vrangeminmissing) + len(sharedata.vrangemaxmissing):
874 raise ValueError("incomplete range")
875 return ["color"]
877 def initdrawpoints(self, privatedata, sharedata, graph):
878 privatedata.rectcanvas = graph.insert(canvas.canvas())
880 def drawpoint(self, privatedata, sharedata, graph, point):
881 xvmin = sharedata.vrange[0][0]
882 xvmax = sharedata.vrange[0][1]
883 yvmin = sharedata.vrange[1][0]
884 yvmax = sharedata.vrange[1][1]
885 if (xvmin is not None and xvmin < 1 and
886 xvmax is not None and xvmax > 0 and
887 yvmin is not None and yvmin < 1 and
888 yvmax is not None and yvmax > 0):
889 if xvmin < 0:
890 xvmin = 0
891 elif xvmax > 1:
892 xvmax = 1
893 if yvmin < 0:
894 yvmin = 0
895 elif yvmax > 1:
896 yvmax = 1
897 p = graph.vgeodesic(xvmin, yvmin, xvmax, yvmin)
898 p.append(graph.vgeodesic_el(xvmax, yvmin, xvmax, yvmax))
899 p.append(graph.vgeodesic_el(xvmax, yvmax, xvmin, yvmax))
900 p.append(graph.vgeodesic_el(xvmin, yvmax, xvmin, yvmin))
901 p.append(path.closepath())
902 privatedata.rectcanvas.fill(p, [self.gradient.getcolor(point["color"])])
904 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
905 raise RuntimeError("Style currently doesn't provide a graph key")
908 class histogram(_style):
910 needsdata = ["vpos", "vposmissing", "vrange", "vrangeminmissing", "vrangemaxmissing"]
912 defaultlineattrs = [deco.stroked]
913 defaultfrompathattrs = []
915 def __init__(self, lineattrs=[], steps=0, fromvalue=0, frompathattrs=[], fillable=0,
916 autohistogramaxisindex=0, autohistogrampointpos=0.5, epsilon=1e-10):
917 self.lineattrs = lineattrs
918 self.steps = steps
919 self.fromvalue = fromvalue
920 self.frompathattrs = frompathattrs
921 self.fillable = fillable # TODO: fillable paths might not properly be closed by straight lines on curved graph geometries
922 self.autohistogramaxisindex = autohistogramaxisindex
923 self.autohistogrampointpos = autohistogrampointpos
924 self.epsilon = epsilon
926 def columnnames(self, privatedata, sharedata, graph, columnnames):
927 if len(graph.axesnames) != 2:
928 raise TypeError("histogram style restricted on two-dimensional graphs")
929 privatedata.rangeaxisindex = None
930 for i in builtinrange(len(graph.axesnames)):
931 if i in sharedata.vrangeminmissing or i in sharedata.vrangemaxmissing:
932 if i in sharedata.vposmissing:
933 raise ValueError("pos and range missing")
934 else:
935 if privatedata.rangeaxisindex is not None:
936 raise ValueError("multiple ranges")
937 privatedata.rangeaxisindex = i
938 if privatedata.rangeaxisindex is None:
939 privatedata.rangeaxisindex = self.autohistogramaxisindex
940 privatedata.autohistogram = 1
941 else:
942 privatedata.autohistogram = 0
943 return []
945 def adjustaxis(self, privatedata, sharedata, graph, columnname, data):
946 if privatedata.autohistogram and columnname == sharedata.poscolumnnames[privatedata.rangeaxisindex]:
947 if len(data) == 1:
948 raise ValueError("several data points needed for automatic histogram width calculation")
949 if data:
950 delta = data[1] - data[0]
951 min = data[0] - self.autohistogrampointpos * delta
952 max = data[-1] + (1-self.autohistogrampointpos) * delta
953 graph.axes[columnname].adjustaxis([min, max])
955 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
956 privatedata.insertfrompath = selectindex == 0
957 if self.lineattrs is not None:
958 privatedata.lineattrs = attr.selectattrs(self.defaultlineattrs + self.lineattrs, selectindex, selecttotal)
959 else:
960 privatedata.lineattrs = None
962 def vmoveto(self, privatedata, sharedata, graph, vpos, vvalue):
963 if -self.epsilon < vpos < 1+self.epsilon and -self.epsilon < vvalue < 1+self.epsilon:
964 if privatedata.rangeaxisindex:
965 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue, vpos)))
966 else:
967 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vpos, vvalue)))
969 def vposline(self, privatedata, sharedata, graph, vpos, vvalue1, vvalue2):
970 if -self.epsilon < vpos < 1+self.epsilon:
971 vvalue1cut = 0
972 if vvalue1 < 0:
973 vvalue1 = 0
974 vvalue1cut = -1
975 elif vvalue1 > 1:
976 vvalue1 = 1
977 vvalue1cut = 1
978 vvalue2cut = 0
979 if vvalue2 < 0:
980 vvalue2 = 0
981 vvalue2cut = -1
982 elif vvalue2 > 1:
983 vvalue2 = 1
984 vvalue2cut = 1
985 if abs(vvalue1cut + vvalue2cut) <= 1:
986 if vvalue1cut and not self.fillable:
987 if privatedata.rangeaxisindex:
988 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue1, vpos)))
989 else:
990 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vpos, vvalue1)))
991 if privatedata.rangeaxisindex:
992 privatedata.path.append(graph.vgeodesic_el(vvalue1, vpos, vvalue2, vpos))
993 else:
994 privatedata.path.append(graph.vgeodesic_el(vpos, vvalue1, vpos, vvalue2))
996 def vvalueline(self, privatedata, sharedata, graph, vvalue, vpos1, vpos2):
997 if self.fillable:
998 if vvalue < -self.epsilon:
999 vvalue = 0
1000 warnings.warn("cut at graph boundary adds artificial lines to fillable step histogram path")
1001 if vvalue > 1+self.epsilon:
1002 vvalue = 1
1003 warnings.warn("cut at graph boundary adds artificial lines to fillable step histogram path")
1004 if self.fillable or (-self.epsilon < vvalue < 1+self.epsilon):
1005 vpos1cut = 0
1006 if vpos1 < 0:
1007 vpos1 = 0
1008 vpos1cut = -1
1009 elif vpos1 > 1:
1010 vpos1 = 1
1011 vpos1cut = 1
1012 vpos2cut = 0
1013 if vpos2 < 0:
1014 vpos2 = 0
1015 vpos2cut = -1
1016 elif vpos2 > 1:
1017 vpos2 = 1
1018 vpos2cut = 1
1019 if abs(vpos1cut + vpos2cut) <= 1:
1020 if vpos1cut:
1021 if self.fillable:
1022 if privatedata.rangeaxisindex:
1023 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(privatedata.vfromvalue, vpos1)))
1024 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vpos1, vvalue, vpos1))
1025 else:
1026 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vpos1, privatedata.vfromvalue)))
1027 privatedata.path.append(graph.vgeodesic_el(vpos1, privatedata.vfromvalue, vpos1, vvalue))
1028 else:
1029 if privatedata.rangeaxisindex:
1030 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue, vpos1)))
1031 else:
1032 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vpos1, vvalue)))
1033 if privatedata.rangeaxisindex:
1034 privatedata.path.append(graph.vgeodesic_el(vvalue, vpos1, vvalue, vpos2))
1035 else:
1036 privatedata.path.append(graph.vgeodesic_el(vpos1, vvalue, vpos2, vvalue))
1037 if self.fillable and vpos2cut:
1038 warnings.warn("cut at graph boundary adds artificial lines to fillable step histogram path")
1039 if privatedata.rangeaxisindex:
1040 privatedata.path.append(graph.vgeodesic_el(vvalue, vpos2, privatedata.vfromvalue, vpos2))
1041 else:
1042 privatedata.path.append(graph.vgeodesic_el(vpos2, vvalue, vpos2, privatedata.vfromvalue))
1044 def drawvalue(self, privatedata, sharedata, graph, vmin, vmax, vvalue):
1045 currentvalid = vmin is not None and vmax is not None and vvalue is not None
1046 if self.fillable and not self.steps:
1047 if not currentvalid:
1048 return
1049 vmincut = 0
1050 if vmin < -self.epsilon:
1051 vmin = 0
1052 vmincut = -1
1053 elif vmin > 1+self.epsilon:
1054 vmin = 1
1055 vmincut = 1
1056 vmaxcut = 0
1057 if vmax < -self.epsilon:
1058 vmax = 0
1059 vmaxcut = -1
1060 if vmax > 1+self.epsilon:
1061 vmax = 1
1062 vmaxcut = 1
1063 vvaluecut = 0
1064 if vvalue < -self.epsilon:
1065 vvalue = 0
1066 vvaluecut = -1
1067 if vvalue > 1+self.epsilon:
1068 vvalue = 1
1069 vvaluecut = 1
1070 done = 0
1071 if abs(vmincut) + abs(vmaxcut) + abs(vvaluecut) + abs(privatedata.vfromvaluecut) > 1:
1072 if abs(vmincut + vmaxcut) > 1 or abs(vvaluecut+privatedata.vfromvaluecut) > 1:
1073 done = 1
1074 else:
1075 warnings.warn("multiple cuts at graph boundary add artificial lines to fillable rectangle histogram path")
1076 elif vmincut:
1077 done = 1
1078 if privatedata.rangeaxisindex:
1079 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(privatedata.vfromvalue, vmin)))
1080 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmin, privatedata.vfromvalue, vmax))
1081 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmax, vvalue, vmax))
1082 privatedata.path.append(graph.vgeodesic_el(vvalue, vmax, vvalue, vmin))
1083 else:
1084 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmin, privatedata.vfromvalue)))
1085 privatedata.path.append(graph.vgeodesic_el(vmin, privatedata.vfromvalue, vmax, privatedata.vfromvalue))
1086 privatedata.path.append(graph.vgeodesic_el(vmax, privatedata.vfromvalue, vmax, vvalue))
1087 privatedata.path.append(graph.vgeodesic_el(vmax, vvalue, vmin, vvalue))
1088 elif vmaxcut:
1089 done = 1
1090 if privatedata.rangeaxisindex:
1091 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue, vmax)))
1092 privatedata.path.append(graph.vgeodesic_el(vvalue, vmax, vvalue, vmin))
1093 privatedata.path.append(graph.vgeodesic_el(vvalue, vmin, privatedata.vfromvalue, vmin))
1094 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmin, privatedata.vfromvalue, vmax))
1095 else:
1096 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmax, vvalue)))
1097 privatedata.path.append(graph.vgeodesic_el(vmax, vvalue, vmin, vvalue))
1098 privatedata.path.append(graph.vgeodesic_el(vmin, vvalue, vmin, privatedata.vfromvalue))
1099 privatedata.path.append(graph.vgeodesic_el(vmin, privatedata.vfromvalue, vmax, privatedata.vfromvalue))
1100 elif privatedata.vfromvaluecut:
1101 done = 1
1102 if privatedata.rangeaxisindex:
1103 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(privatedata.vfromvalue, vmax)))
1104 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmax, vvalue, vmax))
1105 privatedata.path.append(graph.vgeodesic_el(vvalue, vmax, vvalue, vmin))
1106 privatedata.path.append(graph.vgeodesic_el(vvalue, vmin, privatedata.vfromvalue, vmin))
1107 else:
1108 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmax, privatedata.vfromvalue)))
1109 privatedata.path.append(graph.vgeodesic_el(vmax, privatedata.vfromvalue, vmax, vvalue))
1110 privatedata.path.append(graph.vgeodesic_el(vmax, vvalue, vmin, vvalue))
1111 privatedata.path.append(graph.vgeodesic_el(vmin, vvalue, vmin, privatedata.vfromvalue))
1112 elif vvaluecut:
1113 done = 1
1114 if privatedata.rangeaxisindex:
1115 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue, vmin)))
1116 privatedata.path.append(graph.vgeodesic_el(vvalue, vmin, privatedata.vfromvalue, vmin))
1117 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmin, privatedata.vfromvalue, vmax))
1118 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmax, vvalue, vmax))
1119 else:
1120 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmin, vvalue)))
1121 privatedata.path.append(graph.vgeodesic_el(vmin, vvalue, vmin, privatedata.vfromvalue))
1122 privatedata.path.append(graph.vgeodesic_el(vmin, privatedata.vfromvalue, vmax, privatedata.vfromvalue))
1123 privatedata.path.append(graph.vgeodesic_el(vmax, privatedata.vfromvalue, vmax, vvalue))
1124 if not done:
1125 if privatedata.rangeaxisindex:
1126 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(privatedata.vfromvalue, vmin)))
1127 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmin, privatedata.vfromvalue, vmax))
1128 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmax, vvalue, vmax))
1129 privatedata.path.append(graph.vgeodesic_el(vvalue, vmax, vvalue, vmin))
1130 privatedata.path.append(graph.vgeodesic_el(vvalue, vmin, privatedata.vfromvalue, vmin))
1131 privatedata.path.append(path.closepath())
1132 else:
1133 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmin, privatedata.vfromvalue)))
1134 privatedata.path.append(graph.vgeodesic_el(vmin, privatedata.vfromvalue, vmax, privatedata.vfromvalue))
1135 privatedata.path.append(graph.vgeodesic_el(vmax, privatedata.vfromvalue, vmax, vvalue))
1136 privatedata.path.append(graph.vgeodesic_el(vmax, vvalue, vmin, vvalue))
1137 privatedata.path.append(graph.vgeodesic_el(vmin, vvalue, vmin, privatedata.vfromvalue))
1138 privatedata.path.append(path.closepath())
1139 else:
1140 try:
1141 gap = abs(vmin - privatedata.lastvmax) > self.epsilon
1142 except (ArithmeticError, ValueError, TypeError):
1143 gap = 1
1144 if (privatedata.lastvvalue is not None and currentvalid and not gap and
1145 (self.steps or (privatedata.lastvvalue-privatedata.vfromvalue)*(vvalue-privatedata.vfromvalue) < 0)):
1146 self.vposline(privatedata, sharedata, graph,
1147 vmin, privatedata.lastvvalue, vvalue)
1148 else:
1149 if privatedata.lastvvalue is not None and currentvalid:
1150 currentbigger = abs(privatedata.lastvvalue-privatedata.vfromvalue) < abs(vvalue-privatedata.vfromvalue)
1151 if privatedata.lastvvalue is not None and (not currentvalid or not currentbigger or gap):
1152 self.vposline(privatedata, sharedata, graph,
1153 privatedata.lastvmax, privatedata.lastvvalue, privatedata.vfromvalue)
1154 if currentvalid:
1155 self.vmoveto(privatedata, sharedata, graph,
1156 vmin, vvalue)
1157 if currentvalid and (privatedata.lastvvalue is None or currentbigger or gap):
1158 self.vmoveto(privatedata, sharedata, graph,
1159 vmin, privatedata.vfromvalue)
1160 self.vposline(privatedata, sharedata, graph,
1161 vmin, privatedata.vfromvalue, vvalue)
1162 if currentvalid:
1163 self.vvalueline(privatedata, sharedata, graph,
1164 vvalue, vmin, vmax)
1165 privatedata.lastvvalue = vvalue
1166 privatedata.lastvmax = vmax
1167 else:
1168 privatedata.lastvvalue = privatedata.lastvmax = None
1170 def initdrawpoints(self, privatedata, sharedata, graph):
1171 privatedata.path = path.path()
1172 privatedata.lastvvalue = privatedata.lastvmax = None
1173 privatedata.vcurrentpoint = None
1174 privatedata.count = 0
1175 if self.fromvalue is not None:
1176 valueaxisname = sharedata.poscolumnnames[1-privatedata.rangeaxisindex]
1177 privatedata.vfromvalue = graph.axes[valueaxisname].convert(self.fromvalue)
1178 privatedata.vfromvaluecut = 0
1179 if privatedata.vfromvalue < 0:
1180 privatedata.vfromvalue = 0
1181 privatedata.vfromvaluecut = -1
1182 if privatedata.vfromvalue > 1:
1183 privatedata.vfromvalue = 1
1184 privatedata.vfromvaluecut = 1
1185 if self.frompathattrs is not None and privatedata.insertfrompath:
1186 graph.stroke(graph.axes[valueaxisname].vgridpath(privatedata.vfromvalue),
1187 self.defaultfrompathattrs + self.frompathattrs)
1188 else:
1189 privatedata.vfromvalue = 0
1191 def drawpoint(self, privatedata, sharedata, graph, point):
1192 if privatedata.autohistogram:
1193 # automatic range handling
1194 privatedata.count += 1
1195 if privatedata.count == 2:
1196 if privatedata.rangeaxisindex:
1197 privatedata.vrange = sharedata.vpos[1] - privatedata.lastvpos[1]
1198 self.drawvalue(privatedata, sharedata, graph,
1199 privatedata.lastvpos[1] - self.autohistogrampointpos*privatedata.vrange,
1200 privatedata.lastvpos[1] + (1-self.autohistogrampointpos)*privatedata.vrange,
1201 privatedata.lastvpos[0])
1202 else:
1203 privatedata.vrange = sharedata.vpos[0] - privatedata.lastvpos[0]
1204 self.drawvalue(privatedata, sharedata, graph,
1205 privatedata.lastvpos[0] - self.autohistogrampointpos*privatedata.vrange,
1206 privatedata.lastvpos[0] + (1-self.autohistogrampointpos)*privatedata.vrange,
1207 privatedata.lastvpos[1])
1208 elif privatedata.count > 2:
1209 if privatedata.rangeaxisindex:
1210 vrange = sharedata.vpos[1] - privatedata.lastvpos[1]
1211 else:
1212 vrange = sharedata.vpos[0] - privatedata.lastvpos[0]
1213 if abs(privatedata.vrange - vrange) > self.epsilon:
1214 raise ValueError("equal steps (in graph coordinates) needed for automatic width calculation")
1215 if privatedata.count > 1:
1216 if privatedata.rangeaxisindex:
1217 self.drawvalue(privatedata, sharedata, graph,
1218 sharedata.vpos[1] - self.autohistogrampointpos*privatedata.vrange,
1219 sharedata.vpos[1] + (1-self.autohistogrampointpos)*privatedata.vrange,
1220 sharedata.vpos[0])
1221 else:
1222 self.drawvalue(privatedata, sharedata, graph,
1223 sharedata.vpos[0] - self.autohistogrampointpos*privatedata.vrange,
1224 sharedata.vpos[0] + (1-self.autohistogrampointpos)*privatedata.vrange,
1225 sharedata.vpos[1])
1226 privatedata.lastvpos = sharedata.vpos[:]
1227 else:
1228 if privatedata.rangeaxisindex:
1229 self.drawvalue(privatedata, sharedata, graph,
1230 sharedata.vrange[1][0], sharedata.vrange[1][1], sharedata.vpos[0])
1231 else:
1232 self.drawvalue(privatedata, sharedata, graph,
1233 sharedata.vrange[0][0], sharedata.vrange[0][1], sharedata.vpos[1])
1235 def donedrawpoints(self, privatedata, sharedata, graph):
1236 self.drawvalue(privatedata, sharedata, graph, None, None, None)
1237 if privatedata.lineattrs is not None and len(privatedata.path):
1238 graph.draw(privatedata.path, privatedata.lineattrs)
1240 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
1241 if privatedata.lineattrs is not None:
1242 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)
1245 class barpos(_style):
1247 providesdata = ["vpos", "vposmissing", "vposavailable", "vposvalid", "vbarrange", "barposcolumnnames", "barvalueindex", "lastbarvalue", "stackedbar", "stackedbardraw"]
1249 defaultfrompathattrs = []
1251 def __init__(self, fromvalue=None, frompathattrs=[], epsilon=1e-10):
1252 self.fromvalue = fromvalue
1253 self.frompathattrs = frompathattrs
1254 self.epsilon = epsilon
1256 def columnnames(self, privatedata, sharedata, graph, columnnames):
1257 sharedata.barposcolumnnames = []
1258 sharedata.barvalueindex = None
1259 for dimension, axisnames in enumerate(graph.axesnames):
1260 found = 0
1261 for axisname in axisnames:
1262 if axisname in columnnames:
1263 if sharedata.barvalueindex is not None:
1264 raise ValueError("multiple values")
1265 sharedata.barvalueindex = dimension
1266 sharedata.barposcolumnnames.append(axisname)
1267 found += 1
1268 if (axisname + "name") in columnnames:
1269 sharedata.barposcolumnnames.append(axisname + "name")
1270 found += 1
1271 if found > 1:
1272 raise ValueError("multiple names and value")
1273 if not found:
1274 raise ValueError("value/name missing")
1275 if sharedata.barvalueindex is None:
1276 raise ValueError("missing value")
1277 sharedata.vposmissing = []
1278 return sharedata.barposcolumnnames
1280 def addsubvalue(self, value, subvalue):
1281 try:
1282 value + ""
1283 except:
1284 try:
1285 return value[0], self.addsubvalue(value[1], subvalue)
1286 except:
1287 return value, subvalue
1288 else:
1289 return value, subvalue
1291 def adjustaxis(self, privatedata, sharedata, graph, columnname, data):
1292 try:
1293 i = sharedata.barposcolumnnames.index(columnname)
1294 except ValueError:
1295 pass
1296 else:
1297 if i == sharedata.barvalueindex:
1298 if self.fromvalue is not None:
1299 graph.axes[sharedata.barposcolumnnames[i]].adjustaxis([self.fromvalue])
1300 graph.axes[sharedata.barposcolumnnames[i]].adjustaxis(data)
1301 else:
1302 graph.axes[sharedata.barposcolumnnames[i][:-4]].adjustaxis([self.addsubvalue(x, 0) for x in data])
1303 graph.axes[sharedata.barposcolumnnames[i][:-4]].adjustaxis([self.addsubvalue(x, 1) for x in data])
1305 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1306 privatedata.insertfrompath = selectindex == 0
1308 def initdrawpoints(self, privatedata, sharedata, graph):
1309 sharedata.vpos = [None]*(len(sharedata.barposcolumnnames))
1310 sharedata.vbarrange = [[None for i in xrange(2)] for x in sharedata.barposcolumnnames]
1311 sharedata.stackedbar = sharedata.stackedbardraw = 0
1313 if self.fromvalue is not None:
1314 privatedata.vfromvalue = graph.axes[sharedata.barposcolumnnames[sharedata.barvalueindex][0]].convert(self.fromvalue)
1315 if privatedata.vfromvalue < 0:
1316 privatedata.vfromvalue = 0
1317 if privatedata.vfromvalue > 1:
1318 privatedata.vfromvalue = 1
1319 if self.frompathattrs is not None and privatedata.insertfrompath:
1320 graph.stroke(graph.axes[sharedata.barposcolumnnames[sharedata.barvalueindex][0]].vgridpath(privatedata.vfromvalue),
1321 self.defaultfrompathattrs + self.frompathattrs)
1322 else:
1323 privatedata.vfromvalue = 0
1325 def drawpoint(self, privatedata, sharedata, graph, point):
1326 sharedata.vposavailable = sharedata.vposvalid = 1
1327 for i, barname in enumerate(sharedata.barposcolumnnames):
1328 if i == sharedata.barvalueindex:
1329 sharedata.vbarrange[i][0] = privatedata.vfromvalue
1330 sharedata.lastbarvalue = point[barname]
1331 try:
1332 sharedata.vpos[i] = sharedata.vbarrange[i][1] = graph.axes[barname].convert(sharedata.lastbarvalue)
1333 except (ArithmeticError, ValueError, TypeError):
1334 sharedata.vpos[i] = sharedata.vbarrange[i][1] = None
1335 else:
1336 for j in xrange(2):
1337 try:
1338 sharedata.vbarrange[i][j] = graph.axes[barname[:-4]].convert(self.addsubvalue(point[barname], j))
1339 except (ArithmeticError, ValueError, TypeError):
1340 sharedata.vbarrange[i][j] = None
1341 try:
1342 sharedata.vpos[i] = 0.5*(sharedata.vbarrange[i][0]+sharedata.vbarrange[i][1])
1343 except (ArithmeticError, ValueError, TypeError):
1344 sharedata.vpos[i] = None
1345 if sharedata.vpos[i] is None:
1346 sharedata.vposavailable = sharedata.vposvalid = 0
1347 elif sharedata.vpos[i] < -self.epsilon or sharedata.vpos[i] > 1+self.epsilon:
1348 sharedata.vposvalid = 0
1350 registerdefaultprovider(barpos(), ["vbarrange", "barposcolumnnames", "barvalueindex", "lastbarvalue", "stackedbar", "stackedbardraw"])
1353 class stackedbarpos(_style):
1355 # provides no additional data, but needs some data (and modifies some of them)
1356 needsdata = ["vbarrange", "barposcolumnnames", "barvalueindex", "lastbarvalue", "stackedbar", "stackedbardraw"]
1358 def __init__(self, stackname, addontop=0, epsilon=1e-10):
1359 self.stackname = stackname
1360 self.epsilon = epsilon
1361 self.addontop = addontop
1363 def columnnames(self, privatedata, sharedata, graph, columnnames):
1364 if self.stackname not in columnnames:
1365 raise ValueError("column '%s' missing" % self.stackname)
1366 return [self.stackname]
1368 def adjustaxis(self, privatedata, sharedata, graph, columnname, data):
1369 if columnname == self.stackname:
1370 graph.axes[sharedata.barposcolumnnames[sharedata.barvalueindex]].adjustaxis(data)
1372 def initdrawpoints(self, privatedata, sharedata, graph):
1373 if sharedata.stackedbardraw: # do not count the start bar when not gets painted
1374 sharedata.stackedbar += 1
1376 def drawpoint(self, privatedata, sharedata, graph, point):
1377 sharedata.vbarrange[sharedata.barvalueindex][0] = sharedata.vbarrange[sharedata.barvalueindex][1]
1378 if self.addontop:
1379 try:
1380 sharedata.lastbarvalue += point[self.stackname]
1381 except (ArithmeticError, ValueError, TypeError):
1382 sharedata.lastbarvalue = None
1383 else:
1384 sharedata.lastbarvalue = point[self.stackname]
1385 try:
1386 sharedata.vpos[sharedata.barvalueindex] = sharedata.vbarrange[sharedata.barvalueindex][1] = graph.axes[sharedata.barposcolumnnames[sharedata.barvalueindex]].convert(sharedata.lastbarvalue)
1387 except (ArithmeticError, ValueError, TypeError):
1388 sharedata.vpos[sharedata.barvalueindex] = sharedata.vbarrange[sharedata.barvalueindex][1] = None
1389 sharedata.vposavailable = sharedata.vposvalid = 0
1390 else:
1391 if not sharedata.vposavailable or not sharedata.vposvalid:
1392 sharedata.vposavailable = sharedata.vposvalid = 1
1393 for v in sharedata.vpos:
1394 if v is None:
1395 sharedata.vposavailable = sharedata.vposvalid = 0
1396 break
1397 if v < -self.epsilon or v > 1+self.epsilon:
1398 sharedata.vposvalid = 0
1401 class bar(_style):
1403 needsdata = ["vbarrange"]
1405 defaultbarattrs = [color.gradient.Rainbow, deco.stroked([color.grey.black])]
1407 def __init__(self, barattrs=[]):
1408 self.barattrs = barattrs
1410 def columnnames(self, privatedata, sharedata, graph, columnnames):
1411 if len(graph.axesnames) != 2:
1412 raise TypeError("bar style restricted on two-dimensional graphs")
1413 return []
1415 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1416 privatedata.barattrs = attr.selectattrs(self.defaultbarattrs + self.barattrs, selectindex, selecttotal)
1418 def initdrawpoints(self, privatedata, sharedata, graph):
1419 privatedata.rectcanvas = graph.insert(canvas.canvas())
1420 sharedata.stackedbardraw = 1
1421 privatedata.stackedbar = sharedata.stackedbar
1423 def drawpointfill(self, privatedata, p):
1424 if p:
1425 privatedata.rectcanvas.fill(p, privatedata.barattrs)
1427 def drawpoint(self, privatedata, sharedata, graph, point):
1428 xvmin = sharedata.vbarrange[0][0]
1429 xvmax = sharedata.vbarrange[0][1]
1430 yvmin = sharedata.vbarrange[1][0]
1431 yvmax = sharedata.vbarrange[1][1]
1432 try:
1433 if xvmin > xvmax:
1434 xvmin, xvmax = xvmax, xvmin
1435 except:
1436 pass
1437 try:
1438 if yvmin > yvmax:
1439 yvmin, yvmax = yvmax, yvmin
1440 except:
1441 pass
1442 if (xvmin is not None and xvmin < 1 and
1443 xvmax is not None and xvmax > 0 and
1444 yvmin is not None and yvmin < 1 and
1445 yvmax is not None and yvmax > 0):
1446 if xvmin < 0:
1447 xvmin = 0
1448 elif xvmax > 1:
1449 xvmax = 1
1450 if yvmin < 0:
1451 yvmin = 0
1452 elif yvmax > 1:
1453 yvmax = 1
1454 p = graph.vgeodesic(xvmin, yvmin, xvmax, yvmin)
1455 p.append(graph.vgeodesic_el(xvmax, yvmin, xvmax, yvmax))
1456 p.append(graph.vgeodesic_el(xvmax, yvmax, xvmin, yvmax))
1457 p.append(graph.vgeodesic_el(xvmin, yvmax, xvmin, yvmin))
1458 p.append(path.closepath())
1459 self.drawpointfill(privatedata, p)
1460 else:
1461 self.drawpointfill(privatedata, None)
1463 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
1464 selectindex = privatedata.stackedbar
1465 selecttotal = sharedata.stackedbar + 1
1466 graph.fill(path.rect_pt(x_pt + width_pt*selectindex/float(selecttotal), y_pt, width_pt/float(selecttotal), height_pt), privatedata.barattrs)
1469 class changebar(bar):
1471 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1472 if selecttotal != 1:
1473 raise RuntimeError("Changebar can't change its appearance. Thus you can't use it to plot several bars side by side on a subaxis.")
1475 def initdrawpoints(self, privatedata, sharedata, graph):
1476 bar.initdrawpoints(self, privatedata, sharedata, graph)
1477 privatedata.bars = []
1479 def drawpointfill(self, privatedata, p):
1480 privatedata.bars.append(p)
1482 def donedrawpoints(self, privatedata, sharedata, graph):
1483 selecttotal = len(privatedata.bars)
1484 for selectindex, p in enumerate(privatedata.bars):
1485 if p:
1486 barattrs = attr.selectattrs(self.defaultbarattrs + self.barattrs, selectindex, selecttotal)
1487 privatedata.rectcanvas.fill(p, barattrs)
1489 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
1490 raise RuntimeError("Style currently doesn't provide a graph key")
1493 class surface(_line):
1495 needsdata = ["vpos", "vposmissing", "vposavailable", "vposvalid"]
1497 defaultgridattrs = []
1499 def __init__(self, colorname="color", gradient=color.gradient.Rainbow,
1500 mincolor=None, maxcolor=None,
1501 index1=0, index2=1, strokelines1=1, strokelines2=1, gridattrs=[],
1502 epsilon=1e-10):
1503 self.colorname = colorname
1504 self.gradient = gradient
1505 self.mincolor = mincolor
1506 self.maxcolor = maxcolor
1507 self.index1 = index1
1508 self.index2 = index2
1509 self.strokelines1 = strokelines1
1510 self.strokelines2 = strokelines2
1511 self.gridattrs = gridattrs
1512 self.epsilon = epsilon
1514 def columnnames(self, privatedata, sharedata, graph, columnnames):
1515 privatedata.colorize = self.colorname in columnnames
1516 return privatedata.colorize and [self.colorname] or [] + _line.columnnames(self, privatedata, sharedata, graph, columnnames)
1518 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1519 if self.gridattrs is not None:
1520 privatedata.gridattrs = attr.selectattrs(self.defaultgridattrs + self.gridattrs, selectindex, selecttotal)
1521 else:
1522 privatedata.gridattrs = None
1525 def initdrawpoints(self, privatedata, sharedata, graph):
1526 privatedata.values1 = {}
1527 privatedata.values2 = {}
1528 privatedata.data12 = {}
1529 privatedata.data21 = {}
1530 privatedata.colors = {}
1531 privatedata.mincolor = privatedata.maxcolor = None
1533 def drawpoint(self, privatedata, sharedata, graph, point):
1534 if sharedata.vposavailable:
1535 value1 = sharedata.vpos[self.index1]
1536 value2 = sharedata.vpos[self.index2]
1537 if not privatedata.values1.has_key(value1):
1538 for hasvalue in privatedata.values1.keys():
1539 if hasvalue - self.epsilon <= value1 <= hasvalue + self.epsilon:
1540 value1 = hasvalue
1541 break
1542 else:
1543 privatedata.values1[value1] = 1
1544 if not privatedata.values2.has_key(value2):
1545 for hasvalue in privatedata.values2.keys():
1546 if hasvalue - self.epsilon <= value2 <= hasvalue + self.epsilon:
1547 value2 = hasvalue
1548 break
1549 else:
1550 privatedata.values2[value2] = 1
1551 data = sharedata.vposavailable, sharedata.vposvalid, sharedata.vpos[:]
1552 privatedata.data12.setdefault(value1, {})[value2] = data
1553 privatedata.data21.setdefault(value2, {})[value1] = data
1554 if privatedata.colorize:
1555 try:
1556 color = point[self.colorname] + 0
1557 except:
1558 pass
1559 else:
1560 privatedata.colors.setdefault(value1, {})[value2] = color
1561 if privatedata.mincolor is None or color < privatedata.mincolor:
1562 privatedata.mincolor = color
1563 if privatedata.mincolor is None or privatedata.maxcolor < color:
1564 privatedata.maxcolor = color
1566 def donedrawpoints(self, privatedata, sharedata, graph):
1567 values1 = privatedata.values1.keys()
1568 values1.sort()
1569 values2 = privatedata.values2.keys()
1570 values2.sort()
1571 if self.mincolor is not None:
1572 mincolor = self.mincolor
1573 if self.maxcolor is not None:
1574 maxcolor = self.maxcolor
1575 if self.strokelines2:
1576 for value1 in values1:
1577 data2 = privatedata.data12[value1]
1578 self.initpointstopath(privatedata)
1579 for value2 in values2:
1580 try:
1581 data = data2[value2]
1582 except KeyError:
1583 self.addinvalid(privatedata)
1584 else:
1585 self.addpoint(privatedata, graph.vpos_pt, *data)
1586 p = self.donepointstopath(privatedata)
1587 if len(p):
1588 graph.stroke(p, privatedata.gridattrs)
1589 if self.strokelines1:
1590 for value2 in values2:
1591 data1 = privatedata.data21[value2]
1592 self.initpointstopath(privatedata)
1593 for value1 in values1:
1594 try:
1595 data = data1[value1]
1596 except KeyError:
1597 self.addinvalid(privatedata)
1598 else:
1599 self.addpoint(privatedata, graph.vpos_pt, *data)
1600 p = self.donepointstopath(privatedata)
1601 if len(p):
1602 graph.stroke(p, privatedata.gridattrs)
1603 if privatedata.colorize:
1604 from pyx import mesh
1605 nodes = []
1606 elements = []
1607 for value1a, value1b in zip(values1[:-1], values1[1:]):
1608 for value2a, value2b in zip(values2[:-1], values2[1:]):
1609 try:
1610 available1, valid1, v1 = privatedata.data12[value1a][value2a]
1611 available2, valid2, v2 = privatedata.data12[value1a][value2b]
1612 available3, valid3, v3 = privatedata.data12[value1b][value2a]
1613 available4, valid4, v4 = privatedata.data12[value1b][value2b]
1614 except KeyError:
1615 continue
1616 if not available1 or not available2 or not available3 or not available4:
1617 continue
1618 if not valid1 or not valid2 or not valid3 or not valid4:
1619 continue
1620 v5 = [0.25*sum(values) for values in zip(v1, v2, v3, v4)]
1621 x1_pt, y1_pt = graph.vpos_pt(*v1)
1622 x2_pt, y2_pt = graph.vpos_pt(*v2)
1623 x3_pt, y3_pt = graph.vpos_pt(*v3)
1624 x4_pt, y4_pt = graph.vpos_pt(*v4)
1625 x5_pt, y5_pt = graph.vpos_pt(*v5)
1626 c1 = privatedata.colors[value1a][value2a]
1627 c2 = privatedata.colors[value1a][value2b]
1628 c3 = privatedata.colors[value1b][value2a]
1629 c4 = privatedata.colors[value1b][value2b]
1630 c5 = 0.25*(c1+c2+c3+c4)
1631 def color(c):
1632 return self.gradient.getcolor((c - privatedata.mincolor) / float(privatedata.maxcolor - privatedata.mincolor)).rgb()
1633 color1 = color(c1)
1634 color2 = color(c2)
1635 color3 = color(c3)
1636 color4 = color(c4)
1637 color5 = color(c5)
1638 n1 = mesh.node_pt((x1_pt, y1_pt), color1)
1639 n2 = mesh.node_pt((x2_pt, y2_pt), color2)
1640 n3 = mesh.node_pt((x3_pt, y3_pt), color3)
1641 n4 = mesh.node_pt((x4_pt, y4_pt), color4)
1642 n5 = mesh.node_pt((x5_pt, y5_pt), color5)
1643 e1 = mesh.element((n1, n2, n5))
1644 e2 = mesh.element((n1, n3, n5))
1645 e3 = mesh.element((n2, n4, n5))
1646 e4 = mesh.element((n3, n4, n5))
1647 nodes.extend([n1, n2, n3, n4, n5])
1648 elements.extend([e1, e2, e3, e4])
1649 m = mesh.canvasmesh(elements, nodes)
1650 graph.insert(m)
1652 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
1653 raise NotImplementedError