further adjust some copyright dates
[PyX/mjg.git] / pyx / graph / style.py
blobf61b90b0c79b51c3ef6137d481e4904b9e5eee88
1 #!/usr/bin/env python
2 # -*- coding: ISO-8859-1 -*-
5 # Copyright (C) 2002-2004 Jörg Lehmann <joergl@users.sourceforge.net>
6 # Copyright (C) 2003-2004 Michael Schindler <m-schindler@users.sourceforge.net>
7 # Copyright (C) 2002-2005 André Wobst <wobsta@users.sourceforge.net>
9 # This file is part of PyX (http://pyx.sourceforge.net/).
11 # PyX is free software; you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation; either version 2 of the License, or
14 # (at your option) any later version.
16 # PyX is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # GNU General Public License for more details.
21 # You should have received a copy of the GNU General Public License
22 # along with PyX; if not, write to the Free Software
23 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
26 import math, warnings
27 from pyx import attr, deco, style, color, unit, canvas, path
28 from pyx import text as textmodule
30 builtinrange = range
32 try:
33 enumerate([])
34 except NameError:
35 # fallback implementation for Python 2.2. and below
36 def enumerate(list):
37 return zip(xrange(len(list)), list)
39 class _style:
40 """Interface class for graph styles
42 Each graph style must support the methods described in this
43 class. However, since a graph style might not need to perform
44 actions on all the various events, it does not need to overwrite
45 all methods of this base class (e.g. this class is not an abstract
46 class in any respect).
48 A style should never store private data by istance variables
49 (i.e. accessing self), but it should use the sharedata and privatedata
50 instances instead. A style instance can be used multiple times with
51 different sharedata and privatedata instances at the very same time.
52 The sharedata and privatedata instances act as data containers and
53 sharedata allows for sharing information across several styles.
55 Every style contains two class variables, which are not to be
56 modified:
57 - providesdata is a list of variable names a style offers via
58 the sharedata instance. This list is used to determine whether
59 all needs of subsequent styles are fullfilled. Otherwise
60 getdefaultprovider should return a proper style to be used.
61 - needsdata is a list of variable names the style needs to access in the
62 sharedata instance.
63 """
65 providesdata = [] # by default, we provide nothing
66 needsdata = [] # and do not depend on anything
68 def columnnames(self, privatedata, sharedata, graph, columnnames):
69 """Set column information
71 This method is used setup the column name information to be
72 accessible to the style later on. The style should analyse
73 the list of column names. The method should return a list of
74 column names which the style will make use of."""
75 return []
77 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
78 """Select stroke/fill attributes
80 This method is called to allow for the selection of
81 changable attributes of a style."""
82 pass
84 def adjustaxis(self, privatedata, sharedata, graph, columnname, data):
85 """Adjust axis range
87 This method is called in order to adjust the axis range to
88 the provided data. columnname is the column name (each style
89 is subsequently called for all column names)."""
90 pass
92 def initdrawpoints(self, privatedata, sharedata, graph):
93 """Initialize drawing of data
95 This method might be used to initialize the drawing of data."""
96 pass
98 def drawpoint(self, privatedata, sharedata, graph, point):
99 """Draw data
101 This method is called for each data point. The data is
102 available in the dictionary point. The dictionary
103 keys are the column names."""
104 pass
106 def donedrawpoints(self, privatedata, sharedata, graph):
107 """Finalize drawing of data
109 This method is called after the last data point was
110 drawn using the drawpoint method above."""
111 pass
113 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
114 """Draw graph key"""
117 # The following two methods are used to register and get a default provider
118 # for keys. A key is a variable name in sharedata. A provider is a style
119 # which creates variables in sharedata.
121 _defaultprovider = {}
123 def registerdefaultprovider(style, keys):
124 """sets a style as a default creator for sharedata variables 'keys'"""
125 assert not len(style.needsdata), "currently we state, that a style should not depend on other sharedata variables"
126 for key in keys:
127 assert key in style.providesdata, "key not provided by style"
128 # we might allow for overwriting the defaults, i.e. the following is not checked:
129 # assert key in _defaultprovider.keys(), "default provider already registered for key"
130 _defaultprovider[key] = style
132 def getdefaultprovider(key):
133 """returns a style, which acts as a default creator for the
134 sharedata variable 'key'"""
135 return _defaultprovider[key]
138 class pos(_style):
140 providesdata = ["vpos", "vposmissing", "vposavailable", "vposvalid", "poscolumnnames"]
142 def __init__(self, epsilon=1e-10):
143 self.epsilon = epsilon
145 def columnnames(self, privatedata, sharedata, graph, columnnames):
146 sharedata.poscolumnnames = []
147 sharedata.vposmissing = []
148 for count, axisnames in enumerate(graph.axesnames):
149 for axisname in axisnames:
150 for columnname in columnnames:
151 if axisname == columnname:
152 sharedata.poscolumnnames.append(columnname)
153 if len(sharedata.poscolumnnames) > count+1:
154 raise ValueError("multiple axes per graph dimension")
155 elif len(sharedata.poscolumnnames) < count+1:
156 sharedata.vposmissing.append(count)
157 sharedata.poscolumnnames.append(None)
158 return [columnname for columnname in sharedata.poscolumnnames if columnname is not None]
160 def adjustaxis(self, privatedata, sharedata, graph, columnname, data):
161 if columnname in sharedata.poscolumnnames:
162 graph.axes[columnname].adjustaxis(data)
164 def initdrawpoints(self, privatedata, sharedata, graph):
165 sharedata.vpos = [None]*(len(graph.axesnames))
166 privatedata.pointpostmplist = [[columnname, index, graph.axes[columnname]] # temporarily used by drawpoint only
167 for index, columnname in enumerate([columnname for columnname in sharedata.poscolumnnames if columnname is not None])]
168 for missing in sharedata.vposmissing:
169 for pointpostmp in privatedata.pointpostmplist:
170 if pointpostmp[1] >= missing:
171 pointpostmp[1] += 1
173 def drawpoint(self, privatedata, sharedata, graph, point):
174 sharedata.vposavailable = 1 # valid position (but might be outside of the graph)
175 sharedata.vposvalid = 1 # valid position inside the graph
176 for columnname, index, axis in privatedata.pointpostmplist:
177 try:
178 v = axis.convert(point[columnname])
179 except (ArithmeticError, ValueError, TypeError):
180 sharedata.vposavailable = sharedata.vposvalid = 0
181 sharedata.vpos[index] = None
182 else:
183 if v < -self.epsilon or v > 1+self.epsilon:
184 sharedata.vposvalid = 0
185 sharedata.vpos[index] = v
188 registerdefaultprovider(pos(), pos.providesdata)
191 class range(_style):
193 providesdata = ["vrange", "vrangemissing", "vrangeminmissing", "vrangemaxmissing"]
195 # internal bit masks
196 mask_value = 1
197 mask_min = 2
198 mask_max = 4
199 mask_dmin = 8
200 mask_dmax = 16
201 mask_d = 32
203 def __init__(self, usenames={}, epsilon=1e-10):
204 self.usenames = usenames
205 self.epsilon = epsilon
207 def _numberofbits(self, mask):
208 if not mask:
209 return 0
210 if mask & 1:
211 return self._numberofbits(mask >> 1) + 1
212 else:
213 return self._numberofbits(mask >> 1)
215 def columnnames(self, privatedata, sharedata, graph, columnnames):
216 usecolumns = []
217 privatedata.rangeposcolumns = []
218 sharedata.vrangemissing = []
219 sharedata.vrangeminmissing = []
220 sharedata.vrangemaxmissing = []
221 privatedata.rangeposdeltacolumns = {} # temporarily used by adjustaxis only
222 for count, axisnames in enumerate(graph.axesnames):
223 for axisname in axisnames:
224 try:
225 usename = self.usenames[axisname]
226 except KeyError:
227 usename = axisname
228 mask = 0
229 for columnname in columnnames:
230 addusecolumns = 1
231 if usename == columnname:
232 mask += self.mask_value
233 elif usename + "min" == columnname:
234 mask += self.mask_min
235 elif usename + "max" == columnname:
236 mask += self.mask_max
237 elif "d" + usename + "min" == columnname:
238 mask += self.mask_dmin
239 elif "d" + usename + "max" == columnname:
240 mask += self.mask_dmax
241 elif "d" + usename == columnname:
242 mask += self.mask_d
243 else:
244 addusecolumns = 0
245 if addusecolumns:
246 usecolumns.append(columnname)
247 if mask & (self.mask_min | self.mask_max | self.mask_dmin | self.mask_dmax | self.mask_d):
248 if (self._numberofbits(mask & (self.mask_min | self.mask_dmin | self.mask_d)) > 1 or
249 self._numberofbits(mask & (self.mask_max | self.mask_dmax | self.mask_d)) > 1):
250 raise ValueError("multiple range definition")
251 if mask & (self.mask_dmin | self.mask_dmax | self.mask_d):
252 if not (mask & self.mask_value):
253 raise ValueError("missing value for delta")
254 privatedata.rangeposdeltacolumns[axisname] = {}
255 privatedata.rangeposcolumns.append((axisname, usename, mask))
256 elif mask == self.mask_value:
257 usecolumns = usecolumns[:-1]
258 if len(privatedata.rangeposcolumns) + len(sharedata.vrangemissing) > count+1:
259 raise ValueError("multiple axes per graph dimension")
260 elif len(privatedata.rangeposcolumns) + len(sharedata.vrangemissing) < count+1:
261 sharedata.vrangemissing.append(count)
262 sharedata.vrangeminmissing.append(count)
263 sharedata.vrangemaxmissing.append(count)
264 else:
265 if not (privatedata.rangeposcolumns[-1][2] & (self.mask_min | self.mask_dmin | self.mask_d)):
266 sharedata.vrangeminmissing.append(count)
267 if not (privatedata.rangeposcolumns[-1][2] & (self.mask_max | self.mask_dmax | self.mask_d)):
268 sharedata.vrangemaxmissing.append(count)
269 return usecolumns
271 def adjustaxis(self, privatedata, sharedata, graph, columnname, data):
272 if columnname in [c + "min" for a, c, m in privatedata.rangeposcolumns if m & self.mask_min]:
273 graph.axes[columnname[:-3]].adjustaxis(data)
274 if columnname in [c + "max" for a, c, m in privatedata.rangeposcolumns if m & self.mask_max]:
275 graph.axes[columnname[:-3]].adjustaxis(data)
277 # delta handling: fill rangeposdeltacolumns
278 for axisname, usename, mask in privatedata.rangeposcolumns:
279 if columnname == usename and mask & (self.mask_dmin | self.mask_dmax | self.mask_d):
280 privatedata.rangeposdeltacolumns[axisname][self.mask_value] = data
281 if columnname == "d" + usename + "min" and mask & self.mask_dmin:
282 privatedata.rangeposdeltacolumns[axisname][self.mask_dmin] = data
283 if columnname == "d" + usename + "max" and mask & self.mask_dmax:
284 privatedata.rangeposdeltacolumns[axisname][self.mask_dmax] = data
285 if columnname == "d" + usename and mask & self.mask_d:
286 privatedata.rangeposdeltacolumns[axisname][self.mask_d] = data
288 # delta handling: process rangeposdeltacolumns
289 for a, d in privatedata.rangeposdeltacolumns.items():
290 if d.has_key(self.mask_value):
291 for k in d.keys():
292 if k != self.mask_value:
293 if k & (self.mask_dmin | self.mask_d):
294 mindata = []
295 try:
296 mindata.append(d[self.mask_value] - d[k])
297 except:
298 pass
299 graph.axes[a].adjustaxis(mindata)
300 if k & (self.mask_dmax | self.mask_d):
301 maxdata = []
302 try:
303 maxdata.append(d[self.mask_value] + d[k])
304 except:
305 pass
306 graph.axes[a].adjustaxis(maxdata)
307 del d[k]
309 def initdrawpoints(self, privatedata, sharedata, graph):
310 sharedata.vrange = [[None for x in xrange(2)] for y in privatedata.rangeposcolumns + sharedata.vrangemissing]
311 privatedata.rangepostmplist = [[usename, mask, index, graph.axes[axisname]] # temporarily used by drawpoint only
312 for index, (axisname, usename, mask) in enumerate(privatedata.rangeposcolumns)]
313 for missing in sharedata.vrangemissing:
314 for rangepostmp in privatedata.rangepostmplist:
315 if rangepostmp[2] >= missing:
316 rangepostmp[2] += 1
318 def drawpoint(self, privatedata, sharedata, graph, point):
319 for usename, mask, index, axis in privatedata.rangepostmplist:
320 try:
321 if mask & self.mask_min:
322 sharedata.vrange[index][0] = axis.convert(point[usename + "min"])
323 if mask & self.mask_dmin:
324 sharedata.vrange[index][0] = axis.convert(point[usename] - point["d" + usename + "min"])
325 if mask & self.mask_d:
326 sharedata.vrange[index][0] = axis.convert(point[usename] - point["d" + usename])
327 except (ArithmeticError, ValueError, TypeError):
328 sharedata.vrange[index][0] = None
329 try:
330 if mask & self.mask_max:
331 sharedata.vrange[index][1] = axis.convert(point[usename + "max"])
332 if mask & self.mask_dmax:
333 sharedata.vrange[index][1] = axis.convert(point[usename] + point["d" + usename + "max"])
334 if mask & self.mask_d:
335 sharedata.vrange[index][1] = axis.convert(point[usename] + point["d" + usename])
336 except (ArithmeticError, ValueError, TypeError):
337 sharedata.vrange[index][1] = None
339 # some range checks for data consistency
340 if (sharedata.vrange[index][0] is not None and sharedata.vrange[index][1] is not None and
341 sharedata.vrange[index][0] > sharedata.vrange[index][1] + self.epsilon):
342 raise ValueError("inverse range")
343 # disabled due to missing vpos access:
344 # if (sharedata.vrange[index][0] is not None and sharedata.vpos[index] is not None and
345 # sharedata.vrange[index][0] > sharedata.vpos[index] + self.epsilon):
346 # raise ValueError("negative minimum errorbar")
347 # if (sharedata.vrange[index][1] is not None and sharedata.vpos[index] is not None and
348 # sharedata.vrange[index][1] < sharedata.vpos[index] - self.epsilon):
349 # raise ValueError("negative maximum errorbar")
352 registerdefaultprovider(range(), range.providesdata)
355 def _crosssymbol(c, x_pt, y_pt, size_pt, attrs):
356 c.draw(path.path(path.moveto_pt(x_pt-0.5*size_pt, y_pt-0.5*size_pt),
357 path.lineto_pt(x_pt+0.5*size_pt, y_pt+0.5*size_pt),
358 path.moveto_pt(x_pt-0.5*size_pt, y_pt+0.5*size_pt),
359 path.lineto_pt(x_pt+0.5*size_pt, y_pt-0.5*size_pt)), attrs)
361 def _plussymbol(c, x_pt, y_pt, size_pt, attrs):
362 c.draw(path.path(path.moveto_pt(x_pt-0.707106781*size_pt, y_pt),
363 path.lineto_pt(x_pt+0.707106781*size_pt, y_pt),
364 path.moveto_pt(x_pt, y_pt-0.707106781*size_pt),
365 path.lineto_pt(x_pt, y_pt+0.707106781*size_pt)), attrs)
367 def _squaresymbol(c, x_pt, y_pt, size_pt, attrs):
368 c.draw(path.path(path.moveto_pt(x_pt-0.5*size_pt, y_pt-0.5*size_pt),
369 path.lineto_pt(x_pt+0.5*size_pt, y_pt-0.5*size_pt),
370 path.lineto_pt(x_pt+0.5*size_pt, y_pt+0.5*size_pt),
371 path.lineto_pt(x_pt-0.5*size_pt, y_pt+0.5*size_pt),
372 path.closepath()), attrs)
374 def _trianglesymbol(c, x_pt, y_pt, size_pt, attrs):
375 c.draw(path.path(path.moveto_pt(x_pt-0.759835685*size_pt, y_pt-0.438691337*size_pt),
376 path.lineto_pt(x_pt+0.759835685*size_pt, y_pt-0.438691337*size_pt),
377 path.lineto_pt(x_pt, y_pt+0.877382675*size_pt),
378 path.closepath()), attrs)
380 def _circlesymbol(c, x_pt, y_pt, size_pt, attrs):
381 c.draw(path.path(path.arc_pt(x_pt, y_pt, 0.564189583*size_pt, 0, 360),
382 path.closepath()), attrs)
384 def _diamondsymbol(c, x_pt, y_pt, size_pt, attrs):
385 c.draw(path.path(path.moveto_pt(x_pt-0.537284965*size_pt, y_pt),
386 path.lineto_pt(x_pt, y_pt-0.930604859*size_pt),
387 path.lineto_pt(x_pt+0.537284965*size_pt, y_pt),
388 path.lineto_pt(x_pt, y_pt+0.930604859*size_pt),
389 path.closepath()), attrs)
392 class _styleneedingpointpos(_style):
394 needsdata = ["vposmissing"]
396 def columnnames(self, privatedata, sharedata, graph, columnnames):
397 if len(sharedata.vposmissing):
398 raise ValueError("incomplete position information")
399 return []
402 class symbol(_styleneedingpointpos):
404 needsdata = ["vpos", "vposmissing", "vposvalid"]
406 # "inject" the predefinied symbols into the class:
408 # Note, that statements like cross = _crosssymbol are
409 # invalid, since the would lead to unbound methods, but
410 # a single entry changeable list does the trick.
412 # Once we require Python 2.2+ we should use staticmethods
413 # to implement the default symbols inplace.
415 cross = attr.changelist([_crosssymbol])
416 plus = attr.changelist([_plussymbol])
417 square = attr.changelist([_squaresymbol])
418 triangle = attr.changelist([_trianglesymbol])
419 circle = attr.changelist([_circlesymbol])
420 diamond = attr.changelist([_diamondsymbol])
422 changecross = attr.changelist([_crosssymbol, _plussymbol, _squaresymbol, _trianglesymbol, _circlesymbol, _diamondsymbol])
423 changeplus = attr.changelist([_plussymbol, _squaresymbol, _trianglesymbol, _circlesymbol, _diamondsymbol, _crosssymbol])
424 changesquare = attr.changelist([_squaresymbol, _trianglesymbol, _circlesymbol, _diamondsymbol, _crosssymbol, _plussymbol])
425 changetriangle = attr.changelist([_trianglesymbol, _circlesymbol, _diamondsymbol, _crosssymbol, _plussymbol, _squaresymbol])
426 changecircle = attr.changelist([_circlesymbol, _diamondsymbol, _crosssymbol, _plussymbol, _squaresymbol, _trianglesymbol])
427 changediamond = attr.changelist([_diamondsymbol, _crosssymbol, _plussymbol, _squaresymbol, _trianglesymbol, _circlesymbol])
428 changesquaretwice = attr.changelist([_squaresymbol, _squaresymbol, _trianglesymbol, _trianglesymbol, _circlesymbol, _circlesymbol, _diamondsymbol, _diamondsymbol])
429 changetriangletwice = attr.changelist([_trianglesymbol, _trianglesymbol, _circlesymbol, _circlesymbol, _diamondsymbol, _diamondsymbol, _squaresymbol, _squaresymbol])
430 changecircletwice = attr.changelist([_circlesymbol, _circlesymbol, _diamondsymbol, _diamondsymbol, _squaresymbol, _squaresymbol, _trianglesymbol, _trianglesymbol])
431 changediamondtwice = attr.changelist([_diamondsymbol, _diamondsymbol, _squaresymbol, _squaresymbol, _trianglesymbol, _trianglesymbol, _circlesymbol, _circlesymbol])
433 changestrokedfilled = attr.changelist([deco.stroked, deco.filled])
434 changefilledstroked = attr.changelist([deco.filled, deco.stroked])
436 defaultsymbolattrs = [deco.stroked]
438 def __init__(self, symbol=changecross, size=0.2*unit.v_cm, symbolattrs=[]):
439 self.symbol = symbol
440 self.size = size
441 self.symbolattrs = symbolattrs
443 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
444 privatedata.symbol = attr.selectattr(self.symbol, selectindex, selecttotal)
445 privatedata.size_pt = unit.topt(attr.selectattr(self.size, selectindex, selecttotal))
446 if self.symbolattrs is not None:
447 privatedata.symbolattrs = attr.selectattrs(self.defaultsymbolattrs + self.symbolattrs, selectindex, selecttotal)
448 else:
449 privatedata.symbolattrs = None
451 def initdrawpoints(self, privatedata, sharedata, graph):
452 privatedata.symbolcanvas = canvas.canvas()
454 def drawpoint(self, privatedata, sharedata, graph, point):
455 if sharedata.vposvalid and privatedata.symbolattrs is not None:
456 x_pt, y_pt = graph.vpos_pt(*sharedata.vpos)
457 privatedata.symbol(privatedata.symbolcanvas, x_pt, y_pt, privatedata.size_pt, privatedata.symbolattrs)
459 def donedrawpoints(self, privatedata, sharedata, graph):
460 graph.insert(privatedata.symbolcanvas)
462 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
463 if privatedata.symbolattrs is not None:
464 privatedata.symbol(graph, x_pt+0.5*width_pt, y_pt+0.5*height_pt, privatedata.size_pt, privatedata.symbolattrs)
467 class line(_styleneedingpointpos):
469 needsdata = ["vpos", "vposmissing", "vposavailable", "vposvalid"]
471 changelinestyle = attr.changelist([style.linestyle.solid,
472 style.linestyle.dashed,
473 style.linestyle.dotted,
474 style.linestyle.dashdotted])
476 defaultlineattrs = [changelinestyle]
478 def __init__(self, lineattrs=[]):
479 self.lineattrs = lineattrs
481 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
482 if self.lineattrs is not None:
483 privatedata.lineattrs = attr.selectattrs(self.defaultlineattrs + self.lineattrs, selectindex, selecttotal)
484 else:
485 privatedata.lineattrs = None
487 def initdrawpoints(self, privatedata, sharedata, graph):
488 privatedata.path = path.path()
489 privatedata.linebasepoints = []
490 privatedata.lastvpos = None
492 def addpointstopath(self, privatedata, sharedata):
493 # add baselinepoints to privatedata.path
494 if len(privatedata.linebasepoints) > 1:
495 privatedata.path.append(path.moveto_pt(*privatedata.linebasepoints[0]))
496 if len(privatedata.linebasepoints) > 2:
497 privatedata.path.append(path.multilineto_pt(privatedata.linebasepoints[1:]))
498 else:
499 privatedata.path.append(path.lineto_pt(*privatedata.linebasepoints[1]))
500 privatedata.linebasepoints = []
502 def drawpoint(self, privatedata, sharedata, graph, point):
503 # append linebasepoints
504 if sharedata.vposavailable:
505 if len(privatedata.linebasepoints):
506 # the last point was inside the graph
507 if sharedata.vposvalid: # shortcut for the common case
508 privatedata.linebasepoints.append(graph.vpos_pt(*sharedata.vpos))
509 else:
510 # cut end
511 cut = 1
512 for vstart, vend in zip(privatedata.lastvpos, sharedata.vpos):
513 newcut = None
514 if vend > 1:
515 # 1 = vstart + (vend - vstart) * cut
516 try:
517 newcut = (1 - vstart)/(vend - vstart)
518 except (ArithmeticError, TypeError):
519 break
520 if vend < 0:
521 # 0 = vstart + (vend - vstart) * cut
522 try:
523 newcut = - vstart/(vend - vstart)
524 except (ArithmeticError, TypeError):
525 break
526 if newcut is not None and newcut < cut:
527 cut = newcut
528 else:
529 cutvpos = []
530 for vstart, vend in zip(privatedata.lastvpos, sharedata.vpos):
531 cutvpos.append(vstart + (vend - vstart) * cut)
532 privatedata.linebasepoints.append(graph.vpos_pt(*cutvpos))
533 self.addpointstopath(privatedata, sharedata)
534 else:
535 # the last point was outside the graph
536 if privatedata.lastvpos is not None:
537 if sharedata.vposvalid:
538 # cut beginning
539 cut = 0
540 for vstart, vend in zip(privatedata.lastvpos, sharedata.vpos):
541 newcut = None
542 if vstart > 1:
543 # 1 = vstart + (vend - vstart) * cut
544 try:
545 newcut = (1 - vstart)/(vend - vstart)
546 except (ArithmeticError, TypeError):
547 break
548 if vstart < 0:
549 # 0 = vstart + (vend - vstart) * cut
550 try:
551 newcut = - vstart/(vend - vstart)
552 except (ArithmeticError, TypeError):
553 break
554 if newcut is not None and newcut > cut:
555 cut = newcut
556 else:
557 cutvpos = []
558 for vstart, vend in zip(privatedata.lastvpos, sharedata.vpos):
559 cutvpos.append(vstart + (vend - vstart) * cut)
560 privatedata.linebasepoints.append(graph.vpos_pt(*cutvpos))
561 privatedata.linebasepoints.append(graph.vpos_pt(*sharedata.vpos))
562 else:
563 # sometimes cut beginning and end
564 cutfrom = 0
565 cutto = 1
566 for vstart, vend in zip(privatedata.lastvpos, sharedata.vpos):
567 newcutfrom = None
568 if vstart > 1:
569 if vend > 1:
570 break
571 # 1 = vstart + (vend - vstart) * cutfrom
572 try:
573 newcutfrom = (1 - vstart)/(vend - vstart)
574 except (ArithmeticError, TypeError):
575 break
576 if vstart < 0:
577 if vend < 0:
578 break
579 # 0 = vstart + (vend - vstart) * cutfrom
580 try:
581 newcutfrom = - vstart/(vend - vstart)
582 except (ArithmeticError, TypeError):
583 break
584 if newcutfrom is not None and newcutfrom > cutfrom:
585 cutfrom = newcutfrom
586 newcutto = None
587 if vend > 1:
588 # 1 = vstart + (vend - vstart) * cutto
589 try:
590 newcutto = (1 - vstart)/(vend - vstart)
591 except (ArithmeticError, TypeError):
592 break
593 if vend < 0:
594 # 0 = vstart + (vend - vstart) * cutto
595 try:
596 newcutto = - vstart/(vend - vstart)
597 except (ArithmeticError, TypeError):
598 break
599 if newcutto is not None and newcutto < cutto:
600 cutto = newcutto
601 else:
602 if cutfrom < cutto:
603 cutfromvpos = []
604 cuttovpos = []
605 for vstart, vend in zip(privatedata.lastvpos, sharedata.vpos):
606 cutfromvpos.append(vstart + (vend - vstart) * cutfrom)
607 cuttovpos.append(vstart + (vend - vstart) * cutto)
608 privatedata.linebasepoints.append(graph.vpos_pt(*cutfromvpos))
609 privatedata.linebasepoints.append(graph.vpos_pt(*cuttovpos))
610 self.addpointstopath(privatedata, sharedata)
611 privatedata.lastvpos = sharedata.vpos[:]
612 else:
613 if len(privatedata.linebasepoints) > 1:
614 self.addpointstopath(privatedata, sharedata)
615 privatedata.lastvpos = None
617 def donedrawpoints(self, privatedata, sharedata, graph):
618 if len(privatedata.linebasepoints) > 1:
619 self.addpointstopath(privatedata, sharedata)
620 if privatedata.lineattrs is not None and len(privatedata.path):
621 graph.stroke(privatedata.path, privatedata.lineattrs)
623 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
624 if privatedata.lineattrs is not None:
625 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)
628 class errorbar(_style):
630 needsdata = ["vpos", "vposmissing", "vposavailable", "vposvalid", "vrange", "vrangeminmissing", "vrangemaxmissing"]
632 defaulterrorbarattrs = []
634 def __init__(self, size=0.1*unit.v_cm,
635 errorbarattrs=[],
636 epsilon=1e-10):
637 self.size = size
638 self.errorbarattrs = errorbarattrs
639 self.epsilon = epsilon
641 def columnnames(self, privatedata, sharedata, graph, columnnames):
642 for i in sharedata.vposmissing:
643 if i in sharedata.vrangeminmissing and i in sharedata.vrangemaxmissing:
644 raise ValueError("position and range for a graph dimension missing")
645 return []
647 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
648 privatedata.errorsize_pt = unit.topt(attr.selectattr(self.size, selectindex, selecttotal))
649 privatedata.errorbarattrs = attr.selectattrs(self.defaulterrorbarattrs + self.errorbarattrs, selectindex, selecttotal)
651 def initdrawpoints(self, privatedata, sharedata, graph):
652 if privatedata.errorbarattrs is not None:
653 privatedata.errorbarcanvas = canvas.canvas()
654 privatedata.errorbarcanvas.set(privatedata.errorbarattrs)
655 privatedata.dimensionlist = list(xrange(len(sharedata.vpos)))
657 def drawpoint(self, privatedata, sharedata, graph, point):
658 if privatedata.errorbarattrs is not None:
659 for i in privatedata.dimensionlist:
660 for j in privatedata.dimensionlist:
661 if (i != j and
662 (sharedata.vpos[j] is None or
663 sharedata.vpos[j] < -self.epsilon or
664 sharedata.vpos[j] > 1+self.epsilon)):
665 break
666 else:
667 if ((sharedata.vrange[i][0] is None and sharedata.vpos[i] is None) or
668 (sharedata.vrange[i][1] is None and sharedata.vpos[i] is None) or
669 (sharedata.vrange[i][0] is None and sharedata.vrange[i][1] is None)):
670 continue
671 vminpos = sharedata.vpos[:]
672 if sharedata.vrange[i][0] is not None:
673 vminpos[i] = sharedata.vrange[i][0]
674 mincap = 1
675 else:
676 mincap = 0
677 if vminpos[i] > 1+self.epsilon:
678 continue
679 if vminpos[i] < -self.epsilon:
680 vminpos[i] = 0
681 mincap = 0
682 vmaxpos = sharedata.vpos[:]
683 if sharedata.vrange[i][1] is not None:
684 vmaxpos[i] = sharedata.vrange[i][1]
685 maxcap = 1
686 else:
687 maxcap = 0
688 if vmaxpos[i] < -self.epsilon:
689 continue
690 if vmaxpos[i] > 1+self.epsilon:
691 vmaxpos[i] = 1
692 maxcap = 0
693 privatedata.errorbarcanvas.stroke(graph.vgeodesic(*(vminpos + vmaxpos)))
694 for j in privatedata.dimensionlist:
695 if i != j:
696 if mincap:
697 privatedata.errorbarcanvas.stroke(graph.vcap_pt(j, privatedata.errorsize_pt, *vminpos))
698 if maxcap:
699 privatedata.errorbarcanvas.stroke(graph.vcap_pt(j, privatedata.errorsize_pt, *vmaxpos))
701 def donedrawpoints(self, privatedata, sharedata, graph):
702 if privatedata.errorbarattrs is not None:
703 graph.insert(privatedata.errorbarcanvas)
706 class text(_styleneedingpointpos):
708 needsdata = ["vpos", "vposmissing", "vposvalid"]
710 defaulttextattrs = [textmodule.halign.center, textmodule.vshift.mathaxis]
712 def __init__(self, textname="text", textdx=0*unit.v_cm, textdy=0.3*unit.v_cm, textattrs=[]):
713 self.textname = textname
714 self.textdx = textdx
715 self.textdy = textdy
716 self.textattrs = textattrs
718 def columnnames(self, privatedata, sharedata, graph, columnnames):
719 if self.textname not in columnnames:
720 raise ValueError("column '%s' missing" % self.textname)
721 return [self.textname] + _styleneedingpointpos.columnnames(self, privatedata, sharedata, graph, columnnames)
723 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
724 if self.textattrs is not None:
725 privatedata.textattrs = attr.selectattrs(self.defaulttextattrs + self.textattrs, selectindex, selecttotal)
726 else:
727 privatedata.textattrs = None
729 def initdrawpoints(self, privatedata, sharedata, grap):
730 privatedata.textdx_pt = unit.topt(self.textdx)
731 privatedata.textdy_pt = unit.topt(self.textdy)
733 def drawpoint(self, privatedata, sharedata, graph, point):
734 if privatedata.textattrs is not None and sharedata.vposvalid:
735 x_pt, y_pt = graph.vpos_pt(*sharedata.vpos)
736 try:
737 text = str(point[self.textname])
738 except:
739 pass
740 else:
741 graph.text_pt(x_pt + privatedata.textdx_pt, y_pt + privatedata.textdy_pt, text, privatedata.textattrs)
743 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
744 raise RuntimeError("Style currently doesn't provide a graph key")
747 class arrow(_styleneedingpointpos):
749 needsdata = ["vpos", "vposmissing", "vposvalid"]
751 defaultlineattrs = []
752 defaultarrowattrs = []
754 def __init__(self, linelength=0.25*unit.v_cm, arrowsize=0.15*unit.v_cm, lineattrs=[], arrowattrs=[], epsilon=1e-5):
755 self.linelength = linelength
756 self.arrowsize = arrowsize
757 self.lineattrs = lineattrs
758 self.arrowattrs = arrowattrs
759 self.epsilon = epsilon
761 def columnnames(self, privatedata, sharedata, graph, columnnames):
762 if len(graph.axesnames) != 2:
763 raise ValueError("arrow style restricted on two-dimensional graphs")
764 if "size" not in columnnames:
765 raise ValueError("size missing")
766 if "angle" not in columnnames:
767 raise ValueError("angle missing")
768 return ["size", "angle"] + _styleneedingpointpos.columnnames(self, privatedata, sharedata, graph, columnnames)
770 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
771 if self.lineattrs is not None:
772 privatedata.lineattrs = attr.selectattrs(self.defaultlineattrs + self.lineattrs, selectindex, selecttotal)
773 else:
774 privatedata.lineattrs = None
775 if self.arrowattrs is not None:
776 privatedata.arrowattrs = attr.selectattrs(self.defaultarrowattrs + self.arrowattrs, selectindex, selecttotal)
777 else:
778 privatedata.arrowattrs = None
780 def initdrawpoints(self, privatedata, sharedata, graph):
781 privatedata.arrowcanvas = canvas.canvas()
783 def drawpoint(self, privatedata, sharedata, graph, point):
784 if privatedata.lineattrs is not None and privatedata.arrowattrs is not None and sharedata.vposvalid:
785 linelength_pt = unit.topt(self.linelength)
786 x_pt, y_pt = graph.vpos_pt(*sharedata.vpos)
787 try:
788 angle = point["angle"] + 0.0
789 size = point["size"] + 0.0
790 except:
791 pass
792 else:
793 if point["size"] > self.epsilon:
794 dx = math.cos(angle*math.pi/180)
795 dy = math.sin(angle*math.pi/180)
796 x1 = x_pt-0.5*dx*linelength_pt*size
797 y1 = y_pt-0.5*dy*linelength_pt*size
798 x2 = x_pt+0.5*dx*linelength_pt*size
799 y2 = y_pt+0.5*dy*linelength_pt*size
800 privatedata.arrowcanvas.stroke(path.line_pt(x1, y1, x2, y2), privatedata.lineattrs +
801 [deco.earrow(privatedata.arrowattrs, size=self.arrowsize*size)])
803 def donedrawpoints(self, privatedata, sharedata, graph):
804 graph.insert(privatedata.arrowcanvas)
806 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
807 raise RuntimeError("Style currently doesn't provide a graph key")
810 class rect(_style):
812 needsdata = ["vrange", "vrangeminmissing", "vrangemaxmissing"]
814 def __init__(self, palette=color.palette.Grey):
815 self.palette = palette
817 def columnnames(self, privatedata, sharedata, graph, columnnames):
818 if len(graph.axesnames) != 2:
819 raise TypeError("arrow style restricted on two-dimensional graphs")
820 if "color" not in columnnames:
821 raise ValueError("color missing")
822 if len(sharedata.vrangeminmissing) + len(sharedata.vrangemaxmissing):
823 raise ValueError("incomplete range")
824 return ["color"]
826 def initdrawpoints(self, privatedata, sharedata, graph):
827 privatedata.rectcanvas = graph.insert(canvas.canvas())
828 privatedata.lastcolorvalue = None
830 def drawpoint(self, privatedata, sharedata, graph, point):
831 xvmin = sharedata.vrange[0][0]
832 xvmax = sharedata.vrange[0][1]
833 yvmin = sharedata.vrange[1][0]
834 yvmax = sharedata.vrange[1][1]
835 if (xvmin is not None and xvmin < 1 and
836 xvmax is not None and xvmax > 0 and
837 yvmin is not None and yvmin < 1 and
838 yvmax is not None and yvmax > 0):
839 if xvmin < 0:
840 xvmin = 0
841 elif xvmax > 1:
842 xvmax = 1
843 if yvmin < 0:
844 yvmin = 0
845 elif yvmax > 1:
846 yvmax = 1
847 p = graph.vgeodesic(xvmin, yvmin, xvmax, yvmin)
848 p.append(graph.vgeodesic_el(xvmax, yvmin, xvmax, yvmax))
849 p.append(graph.vgeodesic_el(xvmax, yvmax, xvmin, yvmax))
850 p.append(graph.vgeodesic_el(xvmin, yvmax, xvmin, yvmin))
851 p.append(path.closepath())
852 colorvalue = point["color"]
853 try:
854 if colorvalue != privatedata.lastcolorvalue:
855 privatedata.rectcanvas.set([self.palette.getcolor(colorvalue)])
856 except:
857 pass
858 else:
859 privatedata.rectcanvas.fill(p)
861 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
862 raise RuntimeError("Style currently doesn't provide a graph key")
865 class histogram(_style):
867 needsdata = ["vpos", "vposmissing", "vrange", "vrangeminmissing", "vrangemaxmissing"]
869 defaultlineattrs = [deco.stroked]
870 defaultfrompathattrs = []
872 def __init__(self, lineattrs=[], steps=0, fromvalue=0, frompathattrs=[], fillable=0,
873 autohistogramaxisindex=0, autohistogrampointpos=0.5, epsilon=1e-10):
874 self.lineattrs = lineattrs
875 self.steps = steps
876 self.fromvalue = fromvalue
877 self.frompathattrs = frompathattrs
878 self.fillable = fillable # TODO: fillable paths might not properly be closed by straight lines on curved graph geometries
879 self.autohistogramaxisindex = autohistogramaxisindex
880 self.autohistogrampointpos = autohistogrampointpos
881 self.epsilon = epsilon
883 def columnnames(self, privatedata, sharedata, graph, columnnames):
884 if len(graph.axesnames) != 2:
885 raise TypeError("histogram style restricted on two-dimensional graphs")
886 privatedata.rangeaxisindex = None
887 for i in builtinrange(len(graph.axesnames)):
888 if i in sharedata.vrangeminmissing or i in sharedata.vrangemaxmissing:
889 if i in sharedata.vposmissing:
890 raise ValueError("pos and range missing")
891 else:
892 if privatedata.rangeaxisindex is not None:
893 raise ValueError("multiple ranges")
894 privatedata.rangeaxisindex = i
895 if privatedata.rangeaxisindex is None:
896 privatedata.rangeaxisindex = self.autohistogramaxisindex
897 privatedata.autohistogram = 1
898 else:
899 privatedata.autohistogram = 0
900 return []
902 def adjustaxis(self, privatedata, sharedata, graph, columnname, data):
903 if privatedata.autohistogram and columnname == sharedata.poscolumnnames[privatedata.rangeaxisindex]:
904 if len(data) == 1:
905 raise ValueError("several data points needed for automatic histogram width calculation")
906 if data:
907 delta = data[1] - data[0]
908 min = data[0] - self.autohistogrampointpos * delta
909 max = data[-1] + (1-self.autohistogrampointpos) * delta
910 graph.axes[columnname].adjustaxis([min, max])
912 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
913 if self.lineattrs is not None:
914 privatedata.lineattrs = attr.selectattrs(self.defaultlineattrs + self.lineattrs, selectindex, selecttotal)
915 else:
916 privatedata.lineattrs = None
918 def vmoveto(self, privatedata, sharedata, graph, vpos, vvalue):
919 if -self.epsilon < vpos < 1+self.epsilon and -self.epsilon < vvalue < 1+self.epsilon:
920 if privatedata.rangeaxisindex:
921 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue, vpos)))
922 else:
923 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vpos, vvalue)))
925 def vposline(self, privatedata, sharedata, graph, vpos, vvalue1, vvalue2):
926 if -self.epsilon < vpos < 1+self.epsilon:
927 vvalue1cut = 0
928 if vvalue1 < 0:
929 vvalue1 = 0
930 vvalue1cut = -1
931 elif vvalue1 > 1:
932 vvalue1 = 1
933 vvalue1cut = 1
934 vvalue2cut = 0
935 if vvalue2 < 0:
936 vvalue2 = 0
937 vvalue2cut = -1
938 elif vvalue2 > 1:
939 vvalue2 = 1
940 vvalue2cut = 1
941 if abs(vvalue1cut + vvalue2cut) <= 1:
942 if vvalue1cut and not self.fillable:
943 if privatedata.rangeaxisindex:
944 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue1, vpos)))
945 else:
946 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vpos, vvalue1)))
947 if privatedata.rangeaxisindex:
948 privatedata.path.append(graph.vgeodesic_el(vvalue1, vpos, vvalue2, vpos))
949 else:
950 privatedata.path.append(graph.vgeodesic_el(vpos, vvalue1, vpos, vvalue2))
952 def vvalueline(self, privatedata, sharedata, graph, vvalue, vpos1, vpos2):
953 if self.fillable:
954 if vvalue < -self.epsilon:
955 vvalue = 0
956 warnings.warn("cut at graph boundary adds artificial lines to fillable step histogram path")
957 if vvalue > 1+self.epsilon:
958 vvalue = 1
959 warnings.warn("cut at graph boundary adds artificial lines to fillable step histogram path")
960 if self.fillable or (-self.epsilon < vvalue < 1+self.epsilon):
961 vpos1cut = 0
962 if vpos1 < 0:
963 vpos1 = 0
964 vpos1cut = -1
965 elif vpos1 > 1:
966 vpos1 = 1
967 vpos1cut = 1
968 vpos2cut = 0
969 if vpos2 < 0:
970 vpos2 = 0
971 vpos2cut = -1
972 elif vpos2 > 1:
973 vpos2 = 1
974 vpos2cut = 1
975 if abs(vpos1cut + vpos2cut) <= 1:
976 if vpos1cut:
977 if self.fillable:
978 if privatedata.rangeaxisindex:
979 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(privatedata.vfromvalue, vpos1)))
980 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vpos1, vvalue, vpos1))
981 else:
982 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vpos1, privatedata.vfromvalue)))
983 privatedata.path.append(graph.vgeodesic_el(vpos1, privatedata.vfromvalue, vpos1, vvalue))
984 else:
985 if privatedata.rangeaxisindex:
986 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue, vpos1)))
987 else:
988 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vpos1, vvalue)))
989 if privatedata.rangeaxisindex:
990 privatedata.path.append(graph.vgeodesic_el(vvalue, vpos1, vvalue, vpos2))
991 else:
992 privatedata.path.append(graph.vgeodesic_el(vpos1, vvalue, vpos2, vvalue))
993 if self.fillable and vpos2cut:
994 warnings.warn("cut at graph boundary adds artificial lines to fillable step histogram path")
995 if privatedata.rangeaxisindex:
996 privatedata.path.append(graph.vgeodesic_el(vvalue, vpos2, privatedata.vfromvalue, vpos2))
997 else:
998 privatedata.path.append(graph.vgeodesic_el(vpos2, vvalue, vpos2, privatedata.vfromvalue))
1000 def drawvalue(self, privatedata, sharedata, graph, vmin, vmax, vvalue):
1001 currentvalid = vmin is not None and vmax is not None and vvalue is not None
1002 if self.fillable and not self.steps:
1003 if not currentvalid:
1004 return
1005 vmincut = 0
1006 if vmin < -self.epsilon:
1007 vmin = 0
1008 vmincut = -1
1009 elif vmin > 1+self.epsilon:
1010 vmin = 1
1011 vmincut = 1
1012 vmaxcut = 0
1013 if vmax < -self.epsilon:
1014 vmax = 0
1015 vmaxcut = -1
1016 if vmax > 1+self.epsilon:
1017 vmax = 1
1018 vmaxcut = 1
1019 vvaluecut = 0
1020 if vvalue < -self.epsilon:
1021 vvalue = 0
1022 vvaluecut = -1
1023 if vvalue > 1+self.epsilon:
1024 vvalue = 1
1025 vvaluecut = 1
1026 done = 0
1027 if abs(vmincut) + abs(vmaxcut) + abs(vvaluecut) + abs(privatedata.vfromvaluecut) > 1:
1028 if abs(vmincut + vmaxcut) > 1 or abs(vvaluecut+privatedata.vfromvaluecut) > 1:
1029 done = 1
1030 else:
1031 warnings.warn("multiple cuts at graph boundary add artificial lines to fillable rectangle histogram path")
1032 elif vmincut:
1033 done = 1
1034 if privatedata.rangeaxisindex:
1035 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(privatedata.vfromvalue, vmin)))
1036 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmin, privatedata.vfromvalue, vmax))
1037 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmax, vvalue, vmax))
1038 privatedata.path.append(graph.vgeodesic_el(vvalue, vmax, vvalue, vmin))
1039 else:
1040 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmin, privatedata.vfromvalue)))
1041 privatedata.path.append(graph.vgeodesic_el(vmin, privatedata.vfromvalue, vmax, privatedata.vfromvalue))
1042 privatedata.path.append(graph.vgeodesic_el(vmax, privatedata.vfromvalue, vmax, vvalue))
1043 privatedata.path.append(graph.vgeodesic_el(vmax, vvalue, vmin, vvalue))
1044 elif vmaxcut:
1045 done = 1
1046 if privatedata.rangeaxisindex:
1047 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue, vmax)))
1048 privatedata.path.append(graph.vgeodesic_el(vvalue, vmax, vvalue, vmin))
1049 privatedata.path.append(graph.vgeodesic_el(vvalue, vmin, privatedata.vfromvalue, vmin))
1050 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmin, privatedata.vfromvalue, vmax))
1051 else:
1052 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmax, vvalue)))
1053 privatedata.path.append(graph.vgeodesic_el(vmax, vvalue, vmin, vvalue))
1054 privatedata.path.append(graph.vgeodesic_el(vmin, vvalue, vmin, privatedata.vfromvalue))
1055 privatedata.path.append(graph.vgeodesic_el(vmin, privatedata.vfromvalue, vmax, privatedata.vfromvalue))
1056 elif privatedata.vfromvaluecut:
1057 done = 1
1058 if privatedata.rangeaxisindex:
1059 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(privatedata.vfromvalue, vmax)))
1060 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmax, vvalue, vmax))
1061 privatedata.path.append(graph.vgeodesic_el(vvalue, vmax, vvalue, vmin))
1062 privatedata.path.append(graph.vgeodesic_el(vvalue, vmin, privatedata.vfromvalue, vmin))
1063 else:
1064 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmax, privatedata.vfromvalue)))
1065 privatedata.path.append(graph.vgeodesic_el(vmax, privatedata.vfromvalue, vmax, vvalue))
1066 privatedata.path.append(graph.vgeodesic_el(vmax, vvalue, vmin, vvalue))
1067 privatedata.path.append(graph.vgeodesic_el(vmin, vvalue, vmin, privatedata.vfromvalue))
1068 elif vvaluecut:
1069 done = 1
1070 if privatedata.rangeaxisindex:
1071 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue, vmin)))
1072 privatedata.path.append(graph.vgeodesic_el(vvalue, vmin, privatedata.vfromvalue, vmin))
1073 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmin, privatedata.vfromvalue, vmax))
1074 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmax, vvalue, vmax))
1075 else:
1076 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmin, vvalue)))
1077 privatedata.path.append(graph.vgeodesic_el(vmin, vvalue, vmin, privatedata.vfromvalue))
1078 privatedata.path.append(graph.vgeodesic_el(vmin, privatedata.vfromvalue, vmax, privatedata.vfromvalue))
1079 privatedata.path.append(graph.vgeodesic_el(vmax, privatedata.vfromvalue, vmax, vvalue))
1080 if not done:
1081 if privatedata.rangeaxisindex:
1082 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(privatedata.vfromvalue, vmin)))
1083 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmin, privatedata.vfromvalue, vmax))
1084 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmax, vvalue, vmax))
1085 privatedata.path.append(graph.vgeodesic_el(vvalue, vmax, vvalue, vmin))
1086 privatedata.path.append(graph.vgeodesic_el(vvalue, vmin, privatedata.vfromvalue, vmin))
1087 privatedata.path.append(path.closepath())
1088 else:
1089 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmin, privatedata.vfromvalue)))
1090 privatedata.path.append(graph.vgeodesic_el(vmin, privatedata.vfromvalue, vmax, privatedata.vfromvalue))
1091 privatedata.path.append(graph.vgeodesic_el(vmax, privatedata.vfromvalue, vmax, vvalue))
1092 privatedata.path.append(graph.vgeodesic_el(vmax, vvalue, vmin, vvalue))
1093 privatedata.path.append(graph.vgeodesic_el(vmin, vvalue, vmin, privatedata.vfromvalue))
1094 privatedata.path.append(path.closepath())
1095 else:
1096 try:
1097 gap = abs(vmin - privatedata.lastvmax) > self.epsilon
1098 except (ArithmeticError, ValueError, TypeError):
1099 gap = 1
1100 if (privatedata.lastvvalue is not None and
1101 currentvalid and
1102 not gap and
1103 (self.steps or
1104 (privatedata.lastvvalue-privatedata.vfromvalue)*(vvalue-privatedata.vfromvalue) < 0)):
1105 self.vposline(privatedata, sharedata, graph,
1106 vmin, privatedata.lastvvalue, vvalue)
1107 else:
1108 if (privatedata.lastvvalue is not None and
1109 (not currentvalid or
1110 privatedata.lastvvalue > vvalue or
1111 gap)):
1112 self.vposline(privatedata, sharedata, graph,
1113 privatedata.lastvmax, privatedata.lastvvalue, privatedata.vfromvalue)
1114 if currentvalid:
1115 self.vmoveto(privatedata, sharedata, graph,
1116 vmin, vvalue)
1117 if (currentvalid and
1118 (privatedata.lastvvalue is None or
1119 privatedata.lastvvalue < vvalue or
1120 gap)):
1121 self.vmoveto(privatedata, sharedata, graph,
1122 vmin, privatedata.vfromvalue)
1123 self.vposline(privatedata, sharedata, graph,
1124 vmin, privatedata.vfromvalue, vvalue)
1125 if currentvalid:
1126 self.vvalueline(privatedata, sharedata, graph,
1127 vvalue, vmin, vmax)
1128 privatedata.lastvvalue = vvalue
1129 privatedata.lastvmax = vmax
1130 else:
1131 privatedata.lastvvalue = privatedata.lastvmax = None
1133 def initdrawpoints(self, privatedata, sharedata, graph):
1134 privatedata.path = path.path()
1135 privatedata.lastvvalue = privatedata.lastvmax = None
1136 privatedata.vcurrentpoint = None
1137 privatedata.count = 0
1138 if self.fromvalue is not None:
1139 valueaxisname = sharedata.poscolumnnames[1-privatedata.rangeaxisindex]
1140 privatedata.vfromvalue = graph.axes[valueaxisname].convert(self.fromvalue)
1141 privatedata.vfromvaluecut = 0
1142 if privatedata.vfromvalue < 0:
1143 privatedata.vfromvalue = 0
1144 privatedata.vfromvaluecut = -1
1145 if privatedata.vfromvalue > 1:
1146 privatedata.vfromvalue = 1
1147 privatedata.vfromvaluecut = 1
1148 if self.frompathattrs is not None:
1149 graph.stroke(graph.axes[valueaxisname].vgridpath(privatedata.vfromvalue),
1150 self.defaultfrompathattrs + self.frompathattrs)
1151 else:
1152 privatedata.vfromvalue = 0
1154 def drawpoint(self, privatedata, sharedata, graph, point):
1155 if privatedata.autohistogram:
1156 # automatic range handling
1157 privatedata.count += 1
1158 if privatedata.count == 2:
1159 if privatedata.rangeaxisindex:
1160 privatedata.vrange = sharedata.vpos[1] - privatedata.lastvpos[1]
1161 self.drawvalue(privatedata, sharedata, graph,
1162 privatedata.lastvpos[1] - self.autohistogrampointpos*privatedata.vrange,
1163 privatedata.lastvpos[1] + (1-self.autohistogrampointpos)*privatedata.vrange,
1164 privatedata.lastvpos[0])
1165 else:
1166 privatedata.vrange = sharedata.vpos[0] - privatedata.lastvpos[0]
1167 self.drawvalue(privatedata, sharedata, graph,
1168 privatedata.lastvpos[0] - self.autohistogrampointpos*privatedata.vrange,
1169 privatedata.lastvpos[0] + (1-self.autohistogrampointpos)*privatedata.vrange,
1170 privatedata.lastvpos[1])
1171 elif privatedata.count > 2:
1172 if privatedata.rangeaxisindex:
1173 vrange = sharedata.vpos[1] - privatedata.lastvpos[1]
1174 else:
1175 vrange = sharedata.vpos[0] - privatedata.lastvpos[0]
1176 if abs(privatedata.vrange - vrange) > self.epsilon:
1177 raise ValueError("equal steps (in graph coordinates) needed for automatic width calculation")
1178 if privatedata.count > 1:
1179 if privatedata.rangeaxisindex:
1180 self.drawvalue(privatedata, sharedata, graph,
1181 sharedata.vpos[1] - self.autohistogrampointpos*privatedata.vrange,
1182 sharedata.vpos[1] + (1-self.autohistogrampointpos)*privatedata.vrange,
1183 sharedata.vpos[0])
1184 else:
1185 self.drawvalue(privatedata, sharedata, graph,
1186 sharedata.vpos[0] - self.autohistogrampointpos*privatedata.vrange,
1187 sharedata.vpos[0] + (1-self.autohistogrampointpos)*privatedata.vrange,
1188 sharedata.vpos[1])
1189 privatedata.lastvpos = sharedata.vpos[:]
1190 else:
1191 if privatedata.rangeaxisindex:
1192 self.drawvalue(privatedata, sharedata, graph,
1193 sharedata.vrange[1][0], sharedata.vrange[1][1], sharedata.vpos[0])
1194 else:
1195 self.drawvalue(privatedata, sharedata, graph,
1196 sharedata.vrange[0][0], sharedata.vrange[0][1], sharedata.vpos[1])
1198 def donedrawpoints(self, privatedata, sharedata, graph):
1199 self.drawvalue(privatedata, sharedata, graph, None, None, None)
1200 if privatedata.lineattrs is not None and len(privatedata.path):
1201 graph.draw(privatedata.path, privatedata.lineattrs)
1204 class barpos(_style):
1206 providesdata = ["vpos", "vposmissing", "vposavailable", "vposvalid", "vbarrange", "barposcolumnnames", "barvalueindex", "lastbarvalue", "stackedbar", "stackedbardraw"]
1208 defaultfrompathattrs = []
1210 def __init__(self, fromvalue=None, frompathattrs=[], subindex=0, subnames=None, epsilon=1e-10):
1211 # NOTE subindex is a perspective for higher dimensional plots
1212 # (just ignore it for the moment -- we don't even need to document about it)
1213 self.fromvalue = fromvalue
1214 self.frompathattrs = frompathattrs
1215 self.subindex = subindex
1216 self.subnames = subnames
1217 self.epsilon = epsilon
1219 def columnnames(self, privatedata, sharedata, graph, columnnames):
1220 sharedata.barposcolumnnames = []
1221 sharedata.barvalueindex = None
1222 for dimension, axisnames in enumerate(graph.axesnames):
1223 found = 0
1224 for axisname in axisnames:
1225 if axisname in columnnames:
1226 if sharedata.barvalueindex is not None:
1227 raise ValueError("multiple values")
1228 sharedata.barvalueindex = dimension
1229 sharedata.barposcolumnnames.append(axisname)
1230 found += 1
1231 if (axisname + "name") in columnnames:
1232 sharedata.barposcolumnnames.append(axisname + "name")
1233 found += 1
1234 if found > 1:
1235 raise ValueError("multiple names and value")
1236 if not found:
1237 raise ValueError("value/name missing")
1238 if sharedata.barvalueindex is None:
1239 raise ValueError("missing value")
1240 if self.subindex >= sharedata.barvalueindex:
1241 privatedata.barpossubindex = self.subindex + 1
1242 else:
1243 privatedata.barpossubindex = self.subindex
1244 sharedata.vposmissing = []
1245 return sharedata.barposcolumnnames
1247 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1248 if selecttotal == 1:
1249 if self.subnames is not None:
1250 raise ValueError("subnames set for single-bar data")
1251 privatedata.barpossubname = None
1252 else:
1253 if self.subnames is not None:
1254 privatedata.barpossubname = self.subnames[selectindex]
1255 else:
1256 privatedata.barpossubname = selectindex
1258 def adjustaxis(self, privatedata, sharedata, graph, columnname, data):
1259 try:
1260 i = sharedata.barposcolumnnames.index(columnname)
1261 except ValueError:
1262 pass
1263 else:
1264 if i == sharedata.barvalueindex:
1265 if self.fromvalue is not None:
1266 graph.axes[sharedata.barposcolumnnames[i]].adjustaxis([self.fromvalue])
1267 graph.axes[sharedata.barposcolumnnames[i]].adjustaxis(data)
1268 else:
1269 if i == privatedata.barpossubindex and privatedata.barpossubname is not None:
1270 graph.axes[sharedata.barposcolumnnames[i][:-4]].adjustaxis([(x, (privatedata.barpossubname, 0)) for x in data])
1271 graph.axes[sharedata.barposcolumnnames[i][:-4]].adjustaxis([(x, (privatedata.barpossubname, 1)) for x in data])
1272 else:
1273 graph.axes[sharedata.barposcolumnnames[i][:-4]].adjustaxis([(x, 0) for x in data])
1274 graph.axes[sharedata.barposcolumnnames[i][:-4]].adjustaxis([(x, 1) for x in data])
1276 def initdrawpoints(self, privatedata, sharedata, graph):
1277 sharedata.vpos = [None]*(len(sharedata.barposcolumnnames))
1278 sharedata.vbarrange = [[None for i in xrange(2)] for x in sharedata.barposcolumnnames]
1279 sharedata.stackedbar = sharedata.stackedbardraw = 0
1281 if self.fromvalue is not None:
1282 privatedata.vfromvalue = graph.axes[sharedata.barposcolumnnames[sharedata.barvalueindex][0]].convert(self.fromvalue)
1283 if privatedata.vfromvalue < 0:
1284 privatedata.vfromvalue = 0
1285 if privatedata.vfromvalue > 1:
1286 privatedata.vfromvalue = 1
1287 if self.frompathattrs is not None:
1288 # TODO 2d only
1289 graph.stroke(graph.axes[sharedata.barposcolumnnames[sharedata.barvalueindex][0]].vgridpath(privatedata.vfromvalue),
1290 self.defaultfrompathattrs + self.frompathattrs)
1291 else:
1292 privatedata.vfromvalue = 0
1294 def drawpoint(self, privatedata, sharedata, graph, point):
1295 sharedata.vposavailable = sharedata.vposvalid = 1
1296 for i, barname in enumerate(sharedata.barposcolumnnames):
1297 if i == sharedata.barvalueindex:
1298 sharedata.vbarrange[i][0] = privatedata.vfromvalue
1299 sharedata.lastbarvalue = point[barname]
1300 try:
1301 sharedata.vpos[i] = sharedata.vbarrange[i][1] = graph.axes[barname].convert(sharedata.lastbarvalue)
1302 except (ArithmeticError, ValueError, TypeError):
1303 sharedata.vpos[i] = sharedata.vbarrange[i][1] = None
1304 else:
1305 for j in xrange(2):
1306 try:
1307 if i == privatedata.barpossubindex and privatedata.barpossubname is not None:
1308 sharedata.vbarrange[i][j] = graph.axes[barname[:-4]].convert((point[barname], (privatedata.barpossubname, j)))
1309 else:
1310 sharedata.vbarrange[i][j] = graph.axes[barname[:-4]].convert((point[barname], j))
1311 except (ArithmeticError, ValueError, TypeError):
1312 sharedata.vbarrange[i][j] = None
1313 try:
1314 sharedata.vpos[i] = 0.5*(sharedata.vbarrange[i][0]+sharedata.vbarrange[i][1])
1315 except (ArithmeticError, ValueError, TypeError):
1316 sharedata.vpos[i] = None
1317 if sharedata.vpos[i] is None:
1318 sharedata.vposavailable = sharedata.vposvalid = 0
1319 elif sharedata.vpos[i] < -self.epsilon or sharedata.vpos[i] > 1+self.epsilon:
1320 sharedata.vposvalid = 0
1322 registerdefaultprovider(barpos(), ["vbarrange", "barposcolumnnames", "barvalueindex", "lastbarvalue", "stackedbar", "stackedbardraw"])
1325 class stackedbarpos(_style):
1327 # provides no additional data, but needs some data (and modifies some of them)
1328 needsdata = ["vbarrange", "barposcolumnnames", "barvalueindex", "lastbarvalue", "stackedbar", "stackedbardraw"]
1330 def __init__(self, stackname, addontop=0, epsilon=1e-10):
1331 self.stackname = stackname
1332 self.epsilon = epsilon
1333 self.addontop = addontop
1335 def columnnames(self, privatedata, sharedata, graph, columnnames):
1336 if self.stackname not in columnnames:
1337 raise ValueError("column '%s' missing" % self.stackname)
1338 return [self.stackname]
1340 def adjustaxis(self, privatedata, sharedata, graph, columnname, data):
1341 if columnname == self.stackname:
1342 graph.axes[sharedata.barposcolumnnames[sharedata.barvalueindex]].adjustaxis(data)
1344 def initdrawpoints(self, privatedata, sharedata, graph):
1345 if sharedata.stackedbardraw: # do not count the start bar when not gets painted
1346 sharedata.stackedbar += 1
1348 def drawpoint(self, privatedata, sharedata, graph, point):
1349 sharedata.vbarrange[sharedata.barvalueindex][0] = sharedata.vbarrange[sharedata.barvalueindex][1]
1350 if self.addontop:
1351 try:
1352 sharedata.lastbarvalue += point[self.stackname]
1353 except (ArithmeticError, ValueError, TypeError):
1354 sharedata.lastbarvalue = None
1355 else:
1356 sharedata.lastbarvalue = point[self.stackname]
1357 try:
1358 sharedata.vpos[sharedata.barvalueindex] = sharedata.vbarrange[sharedata.barvalueindex][1] = graph.axes[sharedata.barposcolumnnames[sharedata.barvalueindex]].convert(sharedata.lastbarvalue)
1359 except (ArithmeticError, ValueError, TypeError):
1360 sharedata.vpos[sharedata.barvalueindex] = sharedata.vbarrange[sharedata.barvalueindex][1] = None
1361 sharedata.vposavailable = sharedata.vposvalid = 0
1362 else:
1363 if not sharedata.vposavailable or not sharedata.vposvalid:
1364 sharedata.vposavailable = sharedata.vposvalid = 1
1365 for v in sharedata.vpos:
1366 if v is None:
1367 sharedata.vposavailable = sharedata.vposvalid = 0
1368 break
1369 if v < -self.epsilon or v > 1+self.epsilon:
1370 sharedata.vposvalid = 0
1373 class bar(_style):
1375 needsdata = ["vbarrange"]
1377 defaultbarattrs = [color.palette.Rainbow, deco.stroked([color.grey.black])]
1379 def __init__(self, barattrs=[]):
1380 self.barattrs = barattrs
1382 def columnnames(self, privatedata, sharedata, graph, columnnames):
1383 if len(graph.axesnames) != 2:
1384 raise TypeError("bar style restricted on two-dimensional graphs")
1385 return []
1387 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1388 if selecttotal > 1:
1389 privatedata.barattrs = attr.selectattrs(self.defaultbarattrs + self.barattrs, selectindex, selecttotal)
1390 else:
1391 privatedata.barattrs = self.defaultbarattrs + self.barattrs
1393 def initdrawpoints(self, privatedata, sharedata, graph):
1394 privatedata.rectcanvas = graph.insert(canvas.canvas())
1395 sharedata.stackedbardraw = 1
1396 privatedata.stackedbar = sharedata.stackedbar
1398 def drawpoint(self, privatedata, sharedata, graph, point):
1399 xvmin = sharedata.vbarrange[0][0]
1400 xvmax = sharedata.vbarrange[0][1]
1401 yvmin = sharedata.vbarrange[1][0]
1402 yvmax = sharedata.vbarrange[1][1]
1403 try:
1404 if xvmin > xvmax:
1405 xvmin, xvmax = xvmax, xvmin
1406 except:
1407 pass
1408 try:
1409 if yvmin > yvmax:
1410 yvmin, yvmax = yvmax, yvmin
1411 except:
1412 pass
1413 if (xvmin is not None and xvmin < 1 and
1414 xvmax is not None and xvmax > 0 and
1415 yvmin is not None and yvmin < 1 and
1416 yvmax is not None and yvmax > 0):
1417 if xvmin < 0:
1418 xvmin = 0
1419 elif xvmax > 1:
1420 xvmax = 1
1421 if yvmin < 0:
1422 yvmin = 0
1423 elif yvmax > 1:
1424 yvmax = 1
1425 p = graph.vgeodesic(xvmin, yvmin, xvmax, yvmin)
1426 p.append(graph.vgeodesic_el(xvmax, yvmin, xvmax, yvmax))
1427 p.append(graph.vgeodesic_el(xvmax, yvmax, xvmin, yvmax))
1428 p.append(graph.vgeodesic_el(xvmin, yvmax, xvmin, yvmin))
1429 p.append(path.closepath())
1430 privatedata.rectcanvas.fill(p, privatedata.barattrs)
1432 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
1433 selectindex = privatedata.stackedbar
1434 selecttotal = sharedata.stackedbar + 1
1435 graph.fill(path.rect_pt(x_pt + width_pt*selectindex/float(selecttotal), y_pt, width_pt/float(selecttotal), height_pt), privatedata.barattrs)