optional textdx/textdy columns to the text style added
[PyX/mjg.git] / pyx / graph / style.py
blob033cdbbfeef4da6dfa6e11e0d6721bf015cb0b02
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=[], epsilon=1e-5):
805 self.linelength = linelength
806 self.arrowsize = arrowsize
807 self.lineattrs = lineattrs
808 self.arrowattrs = arrowattrs
809 self.epsilon = epsilon
811 def columnnames(self, privatedata, sharedata, graph, columnnames):
812 if len(graph.axesnames) != 2:
813 raise ValueError("arrow style restricted on two-dimensional graphs")
814 if "size" not in columnnames:
815 raise ValueError("size missing")
816 if "angle" not in columnnames:
817 raise ValueError("angle missing")
818 return ["size", "angle"] + _styleneedingpointpos.columnnames(self, privatedata, sharedata, graph, columnnames)
820 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
821 if self.lineattrs is not None:
822 privatedata.lineattrs = attr.selectattrs(self.defaultlineattrs + self.lineattrs, selectindex, selecttotal)
823 else:
824 privatedata.lineattrs = None
825 if self.arrowattrs is not None:
826 privatedata.arrowattrs = attr.selectattrs(self.defaultarrowattrs + self.arrowattrs, selectindex, selecttotal)
827 else:
828 privatedata.arrowattrs = None
830 def initdrawpoints(self, privatedata, sharedata, graph):
831 privatedata.arrowcanvas = canvas.canvas()
833 def drawpoint(self, privatedata, sharedata, graph, point):
834 if privatedata.lineattrs is not None and privatedata.arrowattrs is not None and sharedata.vposvalid:
835 linelength_pt = unit.topt(self.linelength)
836 x_pt, y_pt = graph.vpos_pt(*sharedata.vpos)
837 try:
838 angle = point["angle"] + 0.0
839 size = point["size"] + 0.0
840 except:
841 pass
842 else:
843 if point["size"] > self.epsilon:
844 dx = math.cos(angle*math.pi/180)
845 dy = math.sin(angle*math.pi/180)
846 x1 = x_pt-0.5*dx*linelength_pt*size
847 y1 = y_pt-0.5*dy*linelength_pt*size
848 x2 = x_pt+0.5*dx*linelength_pt*size
849 y2 = y_pt+0.5*dy*linelength_pt*size
850 privatedata.arrowcanvas.stroke(path.line_pt(x1, y1, x2, y2), privatedata.lineattrs +
851 [deco.earrow(privatedata.arrowattrs, size=self.arrowsize*size)])
853 def donedrawpoints(self, privatedata, sharedata, graph):
854 graph.insert(privatedata.arrowcanvas)
856 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
857 raise RuntimeError("Style currently doesn't provide a graph key")
860 class rect(_style):
862 needsdata = ["vrange", "vrangeminmissing", "vrangemaxmissing"]
864 def __init__(self, gradient=color.gradient.Grey):
865 self.gradient = gradient
867 def columnnames(self, privatedata, sharedata, graph, columnnames):
868 if len(graph.axesnames) != 2:
869 raise TypeError("arrow style restricted on two-dimensional graphs")
870 if "color" not in columnnames:
871 raise ValueError("color missing")
872 if len(sharedata.vrangeminmissing) + len(sharedata.vrangemaxmissing):
873 raise ValueError("incomplete range")
874 return ["color"]
876 def initdrawpoints(self, privatedata, sharedata, graph):
877 privatedata.rectcanvas = graph.insert(canvas.canvas())
879 def drawpoint(self, privatedata, sharedata, graph, point):
880 xvmin = sharedata.vrange[0][0]
881 xvmax = sharedata.vrange[0][1]
882 yvmin = sharedata.vrange[1][0]
883 yvmax = sharedata.vrange[1][1]
884 if (xvmin is not None and xvmin < 1 and
885 xvmax is not None and xvmax > 0 and
886 yvmin is not None and yvmin < 1 and
887 yvmax is not None and yvmax > 0):
888 if xvmin < 0:
889 xvmin = 0
890 elif xvmax > 1:
891 xvmax = 1
892 if yvmin < 0:
893 yvmin = 0
894 elif yvmax > 1:
895 yvmax = 1
896 p = graph.vgeodesic(xvmin, yvmin, xvmax, yvmin)
897 p.append(graph.vgeodesic_el(xvmax, yvmin, xvmax, yvmax))
898 p.append(graph.vgeodesic_el(xvmax, yvmax, xvmin, yvmax))
899 p.append(graph.vgeodesic_el(xvmin, yvmax, xvmin, yvmin))
900 p.append(path.closepath())
901 privatedata.rectcanvas.fill(p, [self.gradient.getcolor(point["color"])])
903 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
904 raise RuntimeError("Style currently doesn't provide a graph key")
907 class histogram(_style):
909 needsdata = ["vpos", "vposmissing", "vrange", "vrangeminmissing", "vrangemaxmissing"]
911 defaultlineattrs = [deco.stroked]
912 defaultfrompathattrs = []
914 def __init__(self, lineattrs=[], steps=0, fromvalue=0, frompathattrs=[], fillable=0,
915 autohistogramaxisindex=0, autohistogrampointpos=0.5, epsilon=1e-10):
916 self.lineattrs = lineattrs
917 self.steps = steps
918 self.fromvalue = fromvalue
919 self.frompathattrs = frompathattrs
920 self.fillable = fillable # TODO: fillable paths might not properly be closed by straight lines on curved graph geometries
921 self.autohistogramaxisindex = autohistogramaxisindex
922 self.autohistogrampointpos = autohistogrampointpos
923 self.epsilon = epsilon
925 def columnnames(self, privatedata, sharedata, graph, columnnames):
926 if len(graph.axesnames) != 2:
927 raise TypeError("histogram style restricted on two-dimensional graphs")
928 privatedata.rangeaxisindex = None
929 for i in builtinrange(len(graph.axesnames)):
930 if i in sharedata.vrangeminmissing or i in sharedata.vrangemaxmissing:
931 if i in sharedata.vposmissing:
932 raise ValueError("pos and range missing")
933 else:
934 if privatedata.rangeaxisindex is not None:
935 raise ValueError("multiple ranges")
936 privatedata.rangeaxisindex = i
937 if privatedata.rangeaxisindex is None:
938 privatedata.rangeaxisindex = self.autohistogramaxisindex
939 privatedata.autohistogram = 1
940 else:
941 privatedata.autohistogram = 0
942 return []
944 def adjustaxis(self, privatedata, sharedata, graph, columnname, data):
945 if privatedata.autohistogram and columnname == sharedata.poscolumnnames[privatedata.rangeaxisindex]:
946 if len(data) == 1:
947 raise ValueError("several data points needed for automatic histogram width calculation")
948 if data:
949 delta = data[1] - data[0]
950 min = data[0] - self.autohistogrampointpos * delta
951 max = data[-1] + (1-self.autohistogrampointpos) * delta
952 graph.axes[columnname].adjustaxis([min, max])
954 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
955 privatedata.insertfrompath = selectindex == 0
956 if self.lineattrs is not None:
957 privatedata.lineattrs = attr.selectattrs(self.defaultlineattrs + self.lineattrs, selectindex, selecttotal)
958 else:
959 privatedata.lineattrs = None
961 def vmoveto(self, privatedata, sharedata, graph, vpos, vvalue):
962 if -self.epsilon < vpos < 1+self.epsilon and -self.epsilon < vvalue < 1+self.epsilon:
963 if privatedata.rangeaxisindex:
964 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue, vpos)))
965 else:
966 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vpos, vvalue)))
968 def vposline(self, privatedata, sharedata, graph, vpos, vvalue1, vvalue2):
969 if -self.epsilon < vpos < 1+self.epsilon:
970 vvalue1cut = 0
971 if vvalue1 < 0:
972 vvalue1 = 0
973 vvalue1cut = -1
974 elif vvalue1 > 1:
975 vvalue1 = 1
976 vvalue1cut = 1
977 vvalue2cut = 0
978 if vvalue2 < 0:
979 vvalue2 = 0
980 vvalue2cut = -1
981 elif vvalue2 > 1:
982 vvalue2 = 1
983 vvalue2cut = 1
984 if abs(vvalue1cut + vvalue2cut) <= 1:
985 if vvalue1cut and not self.fillable:
986 if privatedata.rangeaxisindex:
987 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue1, vpos)))
988 else:
989 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vpos, vvalue1)))
990 if privatedata.rangeaxisindex:
991 privatedata.path.append(graph.vgeodesic_el(vvalue1, vpos, vvalue2, vpos))
992 else:
993 privatedata.path.append(graph.vgeodesic_el(vpos, vvalue1, vpos, vvalue2))
995 def vvalueline(self, privatedata, sharedata, graph, vvalue, vpos1, vpos2):
996 if self.fillable:
997 if vvalue < -self.epsilon:
998 vvalue = 0
999 warnings.warn("cut at graph boundary adds artificial lines to fillable step histogram path")
1000 if vvalue > 1+self.epsilon:
1001 vvalue = 1
1002 warnings.warn("cut at graph boundary adds artificial lines to fillable step histogram path")
1003 if self.fillable or (-self.epsilon < vvalue < 1+self.epsilon):
1004 vpos1cut = 0
1005 if vpos1 < 0:
1006 vpos1 = 0
1007 vpos1cut = -1
1008 elif vpos1 > 1:
1009 vpos1 = 1
1010 vpos1cut = 1
1011 vpos2cut = 0
1012 if vpos2 < 0:
1013 vpos2 = 0
1014 vpos2cut = -1
1015 elif vpos2 > 1:
1016 vpos2 = 1
1017 vpos2cut = 1
1018 if abs(vpos1cut + vpos2cut) <= 1:
1019 if vpos1cut:
1020 if self.fillable:
1021 if privatedata.rangeaxisindex:
1022 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(privatedata.vfromvalue, vpos1)))
1023 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vpos1, vvalue, vpos1))
1024 else:
1025 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vpos1, privatedata.vfromvalue)))
1026 privatedata.path.append(graph.vgeodesic_el(vpos1, privatedata.vfromvalue, vpos1, vvalue))
1027 else:
1028 if privatedata.rangeaxisindex:
1029 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue, vpos1)))
1030 else:
1031 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vpos1, vvalue)))
1032 if privatedata.rangeaxisindex:
1033 privatedata.path.append(graph.vgeodesic_el(vvalue, vpos1, vvalue, vpos2))
1034 else:
1035 privatedata.path.append(graph.vgeodesic_el(vpos1, vvalue, vpos2, vvalue))
1036 if self.fillable and vpos2cut:
1037 warnings.warn("cut at graph boundary adds artificial lines to fillable step histogram path")
1038 if privatedata.rangeaxisindex:
1039 privatedata.path.append(graph.vgeodesic_el(vvalue, vpos2, privatedata.vfromvalue, vpos2))
1040 else:
1041 privatedata.path.append(graph.vgeodesic_el(vpos2, vvalue, vpos2, privatedata.vfromvalue))
1043 def drawvalue(self, privatedata, sharedata, graph, vmin, vmax, vvalue):
1044 currentvalid = vmin is not None and vmax is not None and vvalue is not None
1045 if self.fillable and not self.steps:
1046 if not currentvalid:
1047 return
1048 vmincut = 0
1049 if vmin < -self.epsilon:
1050 vmin = 0
1051 vmincut = -1
1052 elif vmin > 1+self.epsilon:
1053 vmin = 1
1054 vmincut = 1
1055 vmaxcut = 0
1056 if vmax < -self.epsilon:
1057 vmax = 0
1058 vmaxcut = -1
1059 if vmax > 1+self.epsilon:
1060 vmax = 1
1061 vmaxcut = 1
1062 vvaluecut = 0
1063 if vvalue < -self.epsilon:
1064 vvalue = 0
1065 vvaluecut = -1
1066 if vvalue > 1+self.epsilon:
1067 vvalue = 1
1068 vvaluecut = 1
1069 done = 0
1070 if abs(vmincut) + abs(vmaxcut) + abs(vvaluecut) + abs(privatedata.vfromvaluecut) > 1:
1071 if abs(vmincut + vmaxcut) > 1 or abs(vvaluecut+privatedata.vfromvaluecut) > 1:
1072 done = 1
1073 else:
1074 warnings.warn("multiple cuts at graph boundary add artificial lines to fillable rectangle histogram path")
1075 elif vmincut:
1076 done = 1
1077 if privatedata.rangeaxisindex:
1078 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(privatedata.vfromvalue, vmin)))
1079 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmin, privatedata.vfromvalue, vmax))
1080 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmax, vvalue, vmax))
1081 privatedata.path.append(graph.vgeodesic_el(vvalue, vmax, vvalue, vmin))
1082 else:
1083 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmin, privatedata.vfromvalue)))
1084 privatedata.path.append(graph.vgeodesic_el(vmin, privatedata.vfromvalue, vmax, privatedata.vfromvalue))
1085 privatedata.path.append(graph.vgeodesic_el(vmax, privatedata.vfromvalue, vmax, vvalue))
1086 privatedata.path.append(graph.vgeodesic_el(vmax, vvalue, vmin, vvalue))
1087 elif vmaxcut:
1088 done = 1
1089 if privatedata.rangeaxisindex:
1090 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue, vmax)))
1091 privatedata.path.append(graph.vgeodesic_el(vvalue, vmax, vvalue, vmin))
1092 privatedata.path.append(graph.vgeodesic_el(vvalue, vmin, privatedata.vfromvalue, vmin))
1093 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmin, privatedata.vfromvalue, vmax))
1094 else:
1095 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmax, vvalue)))
1096 privatedata.path.append(graph.vgeodesic_el(vmax, vvalue, vmin, vvalue))
1097 privatedata.path.append(graph.vgeodesic_el(vmin, vvalue, vmin, privatedata.vfromvalue))
1098 privatedata.path.append(graph.vgeodesic_el(vmin, privatedata.vfromvalue, vmax, privatedata.vfromvalue))
1099 elif privatedata.vfromvaluecut:
1100 done = 1
1101 if privatedata.rangeaxisindex:
1102 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(privatedata.vfromvalue, vmax)))
1103 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmax, vvalue, vmax))
1104 privatedata.path.append(graph.vgeodesic_el(vvalue, vmax, vvalue, vmin))
1105 privatedata.path.append(graph.vgeodesic_el(vvalue, vmin, privatedata.vfromvalue, vmin))
1106 else:
1107 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmax, privatedata.vfromvalue)))
1108 privatedata.path.append(graph.vgeodesic_el(vmax, privatedata.vfromvalue, vmax, vvalue))
1109 privatedata.path.append(graph.vgeodesic_el(vmax, vvalue, vmin, vvalue))
1110 privatedata.path.append(graph.vgeodesic_el(vmin, vvalue, vmin, privatedata.vfromvalue))
1111 elif vvaluecut:
1112 done = 1
1113 if privatedata.rangeaxisindex:
1114 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue, vmin)))
1115 privatedata.path.append(graph.vgeodesic_el(vvalue, vmin, privatedata.vfromvalue, vmin))
1116 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmin, privatedata.vfromvalue, vmax))
1117 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmax, vvalue, vmax))
1118 else:
1119 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmin, vvalue)))
1120 privatedata.path.append(graph.vgeodesic_el(vmin, vvalue, vmin, privatedata.vfromvalue))
1121 privatedata.path.append(graph.vgeodesic_el(vmin, privatedata.vfromvalue, vmax, privatedata.vfromvalue))
1122 privatedata.path.append(graph.vgeodesic_el(vmax, privatedata.vfromvalue, vmax, vvalue))
1123 if not done:
1124 if privatedata.rangeaxisindex:
1125 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(privatedata.vfromvalue, vmin)))
1126 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmin, privatedata.vfromvalue, vmax))
1127 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmax, vvalue, vmax))
1128 privatedata.path.append(graph.vgeodesic_el(vvalue, vmax, vvalue, vmin))
1129 privatedata.path.append(graph.vgeodesic_el(vvalue, vmin, privatedata.vfromvalue, vmin))
1130 privatedata.path.append(path.closepath())
1131 else:
1132 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmin, privatedata.vfromvalue)))
1133 privatedata.path.append(graph.vgeodesic_el(vmin, privatedata.vfromvalue, vmax, privatedata.vfromvalue))
1134 privatedata.path.append(graph.vgeodesic_el(vmax, privatedata.vfromvalue, vmax, vvalue))
1135 privatedata.path.append(graph.vgeodesic_el(vmax, vvalue, vmin, vvalue))
1136 privatedata.path.append(graph.vgeodesic_el(vmin, vvalue, vmin, privatedata.vfromvalue))
1137 privatedata.path.append(path.closepath())
1138 else:
1139 try:
1140 gap = abs(vmin - privatedata.lastvmax) > self.epsilon
1141 except (ArithmeticError, ValueError, TypeError):
1142 gap = 1
1143 if (privatedata.lastvvalue is not None and currentvalid and not gap and
1144 (self.steps or (privatedata.lastvvalue-privatedata.vfromvalue)*(vvalue-privatedata.vfromvalue) < 0)):
1145 self.vposline(privatedata, sharedata, graph,
1146 vmin, privatedata.lastvvalue, vvalue)
1147 else:
1148 if privatedata.lastvvalue is not None and currentvalid:
1149 currentbigger = abs(privatedata.lastvvalue-privatedata.vfromvalue) < abs(vvalue-privatedata.vfromvalue)
1150 if privatedata.lastvvalue is not None and (not currentvalid or not currentbigger or gap):
1151 self.vposline(privatedata, sharedata, graph,
1152 privatedata.lastvmax, privatedata.lastvvalue, privatedata.vfromvalue)
1153 if currentvalid:
1154 self.vmoveto(privatedata, sharedata, graph,
1155 vmin, vvalue)
1156 if currentvalid and (privatedata.lastvvalue is None or currentbigger or gap):
1157 self.vmoveto(privatedata, sharedata, graph,
1158 vmin, privatedata.vfromvalue)
1159 self.vposline(privatedata, sharedata, graph,
1160 vmin, privatedata.vfromvalue, vvalue)
1161 if currentvalid:
1162 self.vvalueline(privatedata, sharedata, graph,
1163 vvalue, vmin, vmax)
1164 privatedata.lastvvalue = vvalue
1165 privatedata.lastvmax = vmax
1166 else:
1167 privatedata.lastvvalue = privatedata.lastvmax = None
1169 def initdrawpoints(self, privatedata, sharedata, graph):
1170 privatedata.path = path.path()
1171 privatedata.lastvvalue = privatedata.lastvmax = None
1172 privatedata.vcurrentpoint = None
1173 privatedata.count = 0
1174 if self.fromvalue is not None:
1175 valueaxisname = sharedata.poscolumnnames[1-privatedata.rangeaxisindex]
1176 privatedata.vfromvalue = graph.axes[valueaxisname].convert(self.fromvalue)
1177 privatedata.vfromvaluecut = 0
1178 if privatedata.vfromvalue < 0:
1179 privatedata.vfromvalue = 0
1180 privatedata.vfromvaluecut = -1
1181 if privatedata.vfromvalue > 1:
1182 privatedata.vfromvalue = 1
1183 privatedata.vfromvaluecut = 1
1184 if self.frompathattrs is not None and privatedata.insertfrompath:
1185 graph.stroke(graph.axes[valueaxisname].vgridpath(privatedata.vfromvalue),
1186 self.defaultfrompathattrs + self.frompathattrs)
1187 else:
1188 privatedata.vfromvalue = 0
1190 def drawpoint(self, privatedata, sharedata, graph, point):
1191 if privatedata.autohistogram:
1192 # automatic range handling
1193 privatedata.count += 1
1194 if privatedata.count == 2:
1195 if privatedata.rangeaxisindex:
1196 privatedata.vrange = sharedata.vpos[1] - privatedata.lastvpos[1]
1197 self.drawvalue(privatedata, sharedata, graph,
1198 privatedata.lastvpos[1] - self.autohistogrampointpos*privatedata.vrange,
1199 privatedata.lastvpos[1] + (1-self.autohistogrampointpos)*privatedata.vrange,
1200 privatedata.lastvpos[0])
1201 else:
1202 privatedata.vrange = sharedata.vpos[0] - privatedata.lastvpos[0]
1203 self.drawvalue(privatedata, sharedata, graph,
1204 privatedata.lastvpos[0] - self.autohistogrampointpos*privatedata.vrange,
1205 privatedata.lastvpos[0] + (1-self.autohistogrampointpos)*privatedata.vrange,
1206 privatedata.lastvpos[1])
1207 elif privatedata.count > 2:
1208 if privatedata.rangeaxisindex:
1209 vrange = sharedata.vpos[1] - privatedata.lastvpos[1]
1210 else:
1211 vrange = sharedata.vpos[0] - privatedata.lastvpos[0]
1212 if abs(privatedata.vrange - vrange) > self.epsilon:
1213 raise ValueError("equal steps (in graph coordinates) needed for automatic width calculation")
1214 if privatedata.count > 1:
1215 if privatedata.rangeaxisindex:
1216 self.drawvalue(privatedata, sharedata, graph,
1217 sharedata.vpos[1] - self.autohistogrampointpos*privatedata.vrange,
1218 sharedata.vpos[1] + (1-self.autohistogrampointpos)*privatedata.vrange,
1219 sharedata.vpos[0])
1220 else:
1221 self.drawvalue(privatedata, sharedata, graph,
1222 sharedata.vpos[0] - self.autohistogrampointpos*privatedata.vrange,
1223 sharedata.vpos[0] + (1-self.autohistogrampointpos)*privatedata.vrange,
1224 sharedata.vpos[1])
1225 privatedata.lastvpos = sharedata.vpos[:]
1226 else:
1227 if privatedata.rangeaxisindex:
1228 self.drawvalue(privatedata, sharedata, graph,
1229 sharedata.vrange[1][0], sharedata.vrange[1][1], sharedata.vpos[0])
1230 else:
1231 self.drawvalue(privatedata, sharedata, graph,
1232 sharedata.vrange[0][0], sharedata.vrange[0][1], sharedata.vpos[1])
1234 def donedrawpoints(self, privatedata, sharedata, graph):
1235 self.drawvalue(privatedata, sharedata, graph, None, None, None)
1236 if privatedata.lineattrs is not None and len(privatedata.path):
1237 graph.draw(privatedata.path, privatedata.lineattrs)
1239 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
1240 if privatedata.lineattrs is not None:
1241 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)
1244 class barpos(_style):
1246 providesdata = ["vpos", "vposmissing", "vposavailable", "vposvalid", "vbarrange", "barposcolumnnames", "barvalueindex", "lastbarvalue", "stackedbar", "stackedbardraw"]
1248 defaultfrompathattrs = []
1250 def __init__(self, fromvalue=None, frompathattrs=[], epsilon=1e-10):
1251 self.fromvalue = fromvalue
1252 self.frompathattrs = frompathattrs
1253 self.epsilon = epsilon
1255 def columnnames(self, privatedata, sharedata, graph, columnnames):
1256 sharedata.barposcolumnnames = []
1257 sharedata.barvalueindex = None
1258 for dimension, axisnames in enumerate(graph.axesnames):
1259 found = 0
1260 for axisname in axisnames:
1261 if axisname in columnnames:
1262 if sharedata.barvalueindex is not None:
1263 raise ValueError("multiple values")
1264 sharedata.barvalueindex = dimension
1265 sharedata.barposcolumnnames.append(axisname)
1266 found += 1
1267 if (axisname + "name") in columnnames:
1268 sharedata.barposcolumnnames.append(axisname + "name")
1269 found += 1
1270 if found > 1:
1271 raise ValueError("multiple names and value")
1272 if not found:
1273 raise ValueError("value/name missing")
1274 if sharedata.barvalueindex is None:
1275 raise ValueError("missing value")
1276 sharedata.vposmissing = []
1277 return sharedata.barposcolumnnames
1279 def addsubvalue(self, value, subvalue):
1280 try:
1281 value + ""
1282 except:
1283 try:
1284 return value[0], self.addsubvalue(value[1], subvalue)
1285 except:
1286 return value, subvalue
1287 else:
1288 return value, subvalue
1290 def adjustaxis(self, privatedata, sharedata, graph, columnname, data):
1291 try:
1292 i = sharedata.barposcolumnnames.index(columnname)
1293 except ValueError:
1294 pass
1295 else:
1296 if i == sharedata.barvalueindex:
1297 if self.fromvalue is not None:
1298 graph.axes[sharedata.barposcolumnnames[i]].adjustaxis([self.fromvalue])
1299 graph.axes[sharedata.barposcolumnnames[i]].adjustaxis(data)
1300 else:
1301 graph.axes[sharedata.barposcolumnnames[i][:-4]].adjustaxis([self.addsubvalue(x, 0) for x in data])
1302 graph.axes[sharedata.barposcolumnnames[i][:-4]].adjustaxis([self.addsubvalue(x, 1) for x in data])
1304 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1305 privatedata.insertfrompath = selectindex == 0
1307 def initdrawpoints(self, privatedata, sharedata, graph):
1308 sharedata.vpos = [None]*(len(sharedata.barposcolumnnames))
1309 sharedata.vbarrange = [[None for i in xrange(2)] for x in sharedata.barposcolumnnames]
1310 sharedata.stackedbar = sharedata.stackedbardraw = 0
1312 if self.fromvalue is not None:
1313 privatedata.vfromvalue = graph.axes[sharedata.barposcolumnnames[sharedata.barvalueindex][0]].convert(self.fromvalue)
1314 if privatedata.vfromvalue < 0:
1315 privatedata.vfromvalue = 0
1316 if privatedata.vfromvalue > 1:
1317 privatedata.vfromvalue = 1
1318 if self.frompathattrs is not None and privatedata.insertfrompath:
1319 graph.stroke(graph.axes[sharedata.barposcolumnnames[sharedata.barvalueindex][0]].vgridpath(privatedata.vfromvalue),
1320 self.defaultfrompathattrs + self.frompathattrs)
1321 else:
1322 privatedata.vfromvalue = 0
1324 def drawpoint(self, privatedata, sharedata, graph, point):
1325 sharedata.vposavailable = sharedata.vposvalid = 1
1326 for i, barname in enumerate(sharedata.barposcolumnnames):
1327 if i == sharedata.barvalueindex:
1328 sharedata.vbarrange[i][0] = privatedata.vfromvalue
1329 sharedata.lastbarvalue = point[barname]
1330 try:
1331 sharedata.vpos[i] = sharedata.vbarrange[i][1] = graph.axes[barname].convert(sharedata.lastbarvalue)
1332 except (ArithmeticError, ValueError, TypeError):
1333 sharedata.vpos[i] = sharedata.vbarrange[i][1] = None
1334 else:
1335 for j in xrange(2):
1336 try:
1337 sharedata.vbarrange[i][j] = graph.axes[barname[:-4]].convert(self.addsubvalue(point[barname], j))
1338 except (ArithmeticError, ValueError, TypeError):
1339 sharedata.vbarrange[i][j] = None
1340 try:
1341 sharedata.vpos[i] = 0.5*(sharedata.vbarrange[i][0]+sharedata.vbarrange[i][1])
1342 except (ArithmeticError, ValueError, TypeError):
1343 sharedata.vpos[i] = None
1344 if sharedata.vpos[i] is None:
1345 sharedata.vposavailable = sharedata.vposvalid = 0
1346 elif sharedata.vpos[i] < -self.epsilon or sharedata.vpos[i] > 1+self.epsilon:
1347 sharedata.vposvalid = 0
1349 registerdefaultprovider(barpos(), ["vbarrange", "barposcolumnnames", "barvalueindex", "lastbarvalue", "stackedbar", "stackedbardraw"])
1352 class stackedbarpos(_style):
1354 # provides no additional data, but needs some data (and modifies some of them)
1355 needsdata = ["vbarrange", "barposcolumnnames", "barvalueindex", "lastbarvalue", "stackedbar", "stackedbardraw"]
1357 def __init__(self, stackname, addontop=0, epsilon=1e-10):
1358 self.stackname = stackname
1359 self.epsilon = epsilon
1360 self.addontop = addontop
1362 def columnnames(self, privatedata, sharedata, graph, columnnames):
1363 if self.stackname not in columnnames:
1364 raise ValueError("column '%s' missing" % self.stackname)
1365 return [self.stackname]
1367 def adjustaxis(self, privatedata, sharedata, graph, columnname, data):
1368 if columnname == self.stackname:
1369 graph.axes[sharedata.barposcolumnnames[sharedata.barvalueindex]].adjustaxis(data)
1371 def initdrawpoints(self, privatedata, sharedata, graph):
1372 if sharedata.stackedbardraw: # do not count the start bar when not gets painted
1373 sharedata.stackedbar += 1
1375 def drawpoint(self, privatedata, sharedata, graph, point):
1376 sharedata.vbarrange[sharedata.barvalueindex][0] = sharedata.vbarrange[sharedata.barvalueindex][1]
1377 if self.addontop:
1378 try:
1379 sharedata.lastbarvalue += point[self.stackname]
1380 except (ArithmeticError, ValueError, TypeError):
1381 sharedata.lastbarvalue = None
1382 else:
1383 sharedata.lastbarvalue = point[self.stackname]
1384 try:
1385 sharedata.vpos[sharedata.barvalueindex] = sharedata.vbarrange[sharedata.barvalueindex][1] = graph.axes[sharedata.barposcolumnnames[sharedata.barvalueindex]].convert(sharedata.lastbarvalue)
1386 except (ArithmeticError, ValueError, TypeError):
1387 sharedata.vpos[sharedata.barvalueindex] = sharedata.vbarrange[sharedata.barvalueindex][1] = None
1388 sharedata.vposavailable = sharedata.vposvalid = 0
1389 else:
1390 if not sharedata.vposavailable or not sharedata.vposvalid:
1391 sharedata.vposavailable = sharedata.vposvalid = 1
1392 for v in sharedata.vpos:
1393 if v is None:
1394 sharedata.vposavailable = sharedata.vposvalid = 0
1395 break
1396 if v < -self.epsilon or v > 1+self.epsilon:
1397 sharedata.vposvalid = 0
1400 class bar(_style):
1402 needsdata = ["vbarrange"]
1404 defaultbarattrs = [color.gradient.Rainbow, deco.stroked([color.grey.black])]
1406 def __init__(self, barattrs=[]):
1407 self.barattrs = barattrs
1409 def columnnames(self, privatedata, sharedata, graph, columnnames):
1410 if len(graph.axesnames) != 2:
1411 raise TypeError("bar style restricted on two-dimensional graphs")
1412 return []
1414 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1415 privatedata.barattrs = attr.selectattrs(self.defaultbarattrs + self.barattrs, selectindex, selecttotal)
1417 def initdrawpoints(self, privatedata, sharedata, graph):
1418 privatedata.rectcanvas = graph.insert(canvas.canvas())
1419 sharedata.stackedbardraw = 1
1420 privatedata.stackedbar = sharedata.stackedbar
1422 def drawpointfill(self, privatedata, p):
1423 if p:
1424 privatedata.rectcanvas.fill(p, privatedata.barattrs)
1426 def drawpoint(self, privatedata, sharedata, graph, point):
1427 xvmin = sharedata.vbarrange[0][0]
1428 xvmax = sharedata.vbarrange[0][1]
1429 yvmin = sharedata.vbarrange[1][0]
1430 yvmax = sharedata.vbarrange[1][1]
1431 try:
1432 if xvmin > xvmax:
1433 xvmin, xvmax = xvmax, xvmin
1434 except:
1435 pass
1436 try:
1437 if yvmin > yvmax:
1438 yvmin, yvmax = yvmax, yvmin
1439 except:
1440 pass
1441 if (xvmin is not None and xvmin < 1 and
1442 xvmax is not None and xvmax > 0 and
1443 yvmin is not None and yvmin < 1 and
1444 yvmax is not None and yvmax > 0):
1445 if xvmin < 0:
1446 xvmin = 0
1447 elif xvmax > 1:
1448 xvmax = 1
1449 if yvmin < 0:
1450 yvmin = 0
1451 elif yvmax > 1:
1452 yvmax = 1
1453 p = graph.vgeodesic(xvmin, yvmin, xvmax, yvmin)
1454 p.append(graph.vgeodesic_el(xvmax, yvmin, xvmax, yvmax))
1455 p.append(graph.vgeodesic_el(xvmax, yvmax, xvmin, yvmax))
1456 p.append(graph.vgeodesic_el(xvmin, yvmax, xvmin, yvmin))
1457 p.append(path.closepath())
1458 self.drawpointfill(privatedata, p)
1459 else:
1460 self.drawpointfill(privatedata, None)
1462 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
1463 selectindex = privatedata.stackedbar
1464 selecttotal = sharedata.stackedbar + 1
1465 graph.fill(path.rect_pt(x_pt + width_pt*selectindex/float(selecttotal), y_pt, width_pt/float(selecttotal), height_pt), privatedata.barattrs)
1468 class changebar(bar):
1470 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1471 if selecttotal != 1:
1472 raise RuntimeError("Changebar can't change its appearance. Thus you can't use it to plot several bars side by side on a subaxis.")
1474 def initdrawpoints(self, privatedata, sharedata, graph):
1475 bar.initdrawpoints(self, privatedata, sharedata, graph)
1476 privatedata.bars = []
1478 def drawpointfill(self, privatedata, p):
1479 privatedata.bars.append(p)
1481 def donedrawpoints(self, privatedata, sharedata, graph):
1482 selecttotal = len(privatedata.bars)
1483 for selectindex, p in enumerate(privatedata.bars):
1484 if p:
1485 barattrs = attr.selectattrs(self.defaultbarattrs + self.barattrs, selectindex, selecttotal)
1486 privatedata.rectcanvas.fill(p, barattrs)
1488 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
1489 raise RuntimeError("Style currently doesn't provide a graph key")
1492 class surface(_line):
1494 needsdata = ["vpos", "vposmissing", "vposavailable", "vposvalid"]
1496 defaultgridattrs = []
1498 def __init__(self, colorname="color", gradient=color.gradient.Rainbow,
1499 mincolor=None, maxcolor=None,
1500 index1=0, index2=1, strokelines1=1, strokelines2=1, gridattrs=[],
1501 epsilon=1e-10):
1502 self.colorname = colorname
1503 self.gradient = gradient
1504 self.mincolor = mincolor
1505 self.maxcolor = maxcolor
1506 self.index1 = index1
1507 self.index2 = index2
1508 self.strokelines1 = strokelines1
1509 self.strokelines2 = strokelines2
1510 self.gridattrs = gridattrs
1511 self.epsilon = epsilon
1513 def columnnames(self, privatedata, sharedata, graph, columnnames):
1514 privatedata.colorize = self.colorname in columnnames
1515 return privatedata.colorize and [self.colorname] or [] + _line.columnnames(self, privatedata, sharedata, graph, columnnames)
1517 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1518 if self.gridattrs is not None:
1519 privatedata.gridattrs = attr.selectattrs(self.defaultgridattrs + self.gridattrs, selectindex, selecttotal)
1520 else:
1521 privatedata.gridattrs = None
1524 def initdrawpoints(self, privatedata, sharedata, graph):
1525 privatedata.values1 = {}
1526 privatedata.values2 = {}
1527 privatedata.data12 = {}
1528 privatedata.data21 = {}
1529 privatedata.colors = {}
1530 privatedata.mincolor = privatedata.maxcolor = None
1532 def drawpoint(self, privatedata, sharedata, graph, point):
1533 if sharedata.vposavailable:
1534 value1 = sharedata.vpos[self.index1]
1535 value2 = sharedata.vpos[self.index2]
1536 if not privatedata.values1.has_key(value1):
1537 for hasvalue in privatedata.values1.keys():
1538 if hasvalue - self.epsilon <= value1 <= hasvalue + self.epsilon:
1539 value1 = hasvalue
1540 break
1541 else:
1542 privatedata.values1[value1] = 1
1543 if not privatedata.values2.has_key(value2):
1544 for hasvalue in privatedata.values2.keys():
1545 if hasvalue - self.epsilon <= value2 <= hasvalue + self.epsilon:
1546 value2 = hasvalue
1547 break
1548 else:
1549 privatedata.values2[value2] = 1
1550 data = sharedata.vposavailable, sharedata.vposvalid, sharedata.vpos[:]
1551 privatedata.data12.setdefault(value1, {})[value2] = data
1552 privatedata.data21.setdefault(value2, {})[value1] = data
1553 if privatedata.colorize:
1554 try:
1555 color = point[self.colorname] + 0
1556 except:
1557 pass
1558 else:
1559 privatedata.colors.setdefault(value1, {})[value2] = color
1560 if privatedata.mincolor is None or color < privatedata.mincolor:
1561 privatedata.mincolor = color
1562 if privatedata.mincolor is None or privatedata.maxcolor < color:
1563 privatedata.maxcolor = color
1565 def donedrawpoints(self, privatedata, sharedata, graph):
1566 values1 = privatedata.values1.keys()
1567 values1.sort()
1568 values2 = privatedata.values2.keys()
1569 values2.sort()
1570 if self.mincolor is not None:
1571 mincolor = self.mincolor
1572 if self.maxcolor is not None:
1573 maxcolor = self.maxcolor
1574 if self.strokelines2:
1575 for value1 in values1:
1576 data2 = privatedata.data12[value1]
1577 self.initpointstopath(privatedata)
1578 for value2 in values2:
1579 try:
1580 data = data2[value2]
1581 except KeyError:
1582 self.addinvalid(privatedata)
1583 else:
1584 self.addpoint(privatedata, graph.vpos_pt, *data)
1585 p = self.donepointstopath(privatedata)
1586 if len(p):
1587 graph.stroke(p, privatedata.gridattrs)
1588 if self.strokelines1:
1589 for value2 in values2:
1590 data1 = privatedata.data21[value2]
1591 self.initpointstopath(privatedata)
1592 for value1 in values1:
1593 try:
1594 data = data1[value1]
1595 except KeyError:
1596 self.addinvalid(privatedata)
1597 else:
1598 self.addpoint(privatedata, graph.vpos_pt, *data)
1599 p = self.donepointstopath(privatedata)
1600 if len(p):
1601 graph.stroke(p, privatedata.gridattrs)
1602 if privatedata.colorize:
1603 for value1 in values1:
1604 data2 = privatedata.data12[value1]
1605 self.initpointstopath(privatedata)
1606 for value2 in values2:
1607 try:
1608 vposavailable, vposvalid, vpos = data2[value2]
1609 except KeyError:
1610 pass
1611 else:
1612 if vposvalid:
1613 x_pt, y_pt = graph.vpos_pt(*vpos)
1614 try:
1615 color = privatedata.colors[value1][value2]
1616 except KeyError:
1617 pass
1618 else:
1619 color = (color - privatedata.mincolor) / float(privatedata.maxcolor - privatedata.mincolor)
1620 graph.fill(path.circle_pt(x_pt, y_pt, 1), [self.gradient.getcolor(color)])
1622 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
1623 raise NotImplementedError