fix histograms for negative y-coordinates
[PyX/mjg.git] / pyx / graph / style.py
blob69c7a49e85086bef61cf2b8328564cd8f35c6fd4
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 adjustaxis(self, privatedata, sharedata, graph, columnname, data):
78 """Adjust axis range
80 This method is called in order to adjust the axis range to
81 the provided data. columnname is the column name (each style
82 is subsequently called for all column names)."""
83 pass
85 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
86 """Select stroke/fill attributes
88 This method is called to allow for the selection of
89 changable attributes of a style."""
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(privatedata.errorbarattrs)
654 privatedata.dimensionlist = list(xrange(len(sharedata.vpos)))
656 def drawpoint(self, privatedata, sharedata, graph, point):
657 if privatedata.errorbarattrs is not None:
658 for i in privatedata.dimensionlist:
659 for j in privatedata.dimensionlist:
660 if (i != j and
661 (sharedata.vpos[j] is None or
662 sharedata.vpos[j] < -self.epsilon or
663 sharedata.vpos[j] > 1+self.epsilon)):
664 break
665 else:
666 if ((sharedata.vrange[i][0] is None and sharedata.vpos[i] is None) or
667 (sharedata.vrange[i][1] is None and sharedata.vpos[i] is None) or
668 (sharedata.vrange[i][0] is None and sharedata.vrange[i][1] is None)):
669 continue
670 vminpos = sharedata.vpos[:]
671 if sharedata.vrange[i][0] is not None:
672 vminpos[i] = sharedata.vrange[i][0]
673 mincap = 1
674 else:
675 mincap = 0
676 if vminpos[i] > 1+self.epsilon:
677 continue
678 if vminpos[i] < -self.epsilon:
679 vminpos[i] = 0
680 mincap = 0
681 vmaxpos = sharedata.vpos[:]
682 if sharedata.vrange[i][1] is not None:
683 vmaxpos[i] = sharedata.vrange[i][1]
684 maxcap = 1
685 else:
686 maxcap = 0
687 if vmaxpos[i] < -self.epsilon:
688 continue
689 if vmaxpos[i] > 1+self.epsilon:
690 vmaxpos[i] = 1
691 maxcap = 0
692 privatedata.errorbarcanvas.stroke(graph.vgeodesic(*(vminpos + vmaxpos)))
693 for j in privatedata.dimensionlist:
694 if i != j:
695 if mincap:
696 privatedata.errorbarcanvas.stroke(graph.vcap_pt(j, privatedata.errorsize_pt, *vminpos))
697 if maxcap:
698 privatedata.errorbarcanvas.stroke(graph.vcap_pt(j, privatedata.errorsize_pt, *vmaxpos))
700 def donedrawpoints(self, privatedata, sharedata, graph):
701 if privatedata.errorbarattrs is not None:
702 graph.insert(privatedata.errorbarcanvas)
705 class text(_styleneedingpointpos):
707 needsdata = ["vpos", "vposmissing", "vposvalid"]
709 defaulttextattrs = [textmodule.halign.center, textmodule.vshift.mathaxis]
711 def __init__(self, textname="text", textdx=0*unit.v_cm, textdy=0.3*unit.v_cm, textattrs=[]):
712 self.textname = textname
713 self.textdx = textdx
714 self.textdy = textdy
715 self.textattrs = textattrs
717 def columnnames(self, privatedata, sharedata, graph, columnnames):
718 if self.textname not in columnnames:
719 raise ValueError("column '%s' missing" % self.textname)
720 return [self.textname] + _styleneedingpointpos.columnnames(self, privatedata, sharedata, graph, columnnames)
722 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
723 if self.textattrs is not None:
724 privatedata.textattrs = attr.selectattrs(self.defaulttextattrs + self.textattrs, selectindex, selecttotal)
725 else:
726 privatedata.textattrs = None
728 def initdrawpoints(self, privatedata, sharedata, grap):
729 privatedata.textdx_pt = unit.topt(self.textdx)
730 privatedata.textdy_pt = unit.topt(self.textdy)
732 def drawpoint(self, privatedata, sharedata, graph, point):
733 if privatedata.textattrs is not None and sharedata.vposvalid:
734 x_pt, y_pt = graph.vpos_pt(*sharedata.vpos)
735 try:
736 text = str(point[self.textname])
737 except:
738 pass
739 else:
740 graph.text_pt(x_pt + privatedata.textdx_pt, y_pt + privatedata.textdy_pt, text, privatedata.textattrs)
742 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
743 raise RuntimeError("Style currently doesn't provide a graph key")
746 class arrow(_styleneedingpointpos):
748 needsdata = ["vpos", "vposmissing", "vposvalid"]
750 defaultlineattrs = []
751 defaultarrowattrs = []
753 def __init__(self, linelength=0.25*unit.v_cm, arrowsize=0.15*unit.v_cm, lineattrs=[], arrowattrs=[], epsilon=1e-5):
754 self.linelength = linelength
755 self.arrowsize = arrowsize
756 self.lineattrs = lineattrs
757 self.arrowattrs = arrowattrs
758 self.epsilon = epsilon
760 def columnnames(self, privatedata, sharedata, graph, columnnames):
761 if len(graph.axesnames) != 2:
762 raise ValueError("arrow style restricted on two-dimensional graphs")
763 if "size" not in columnnames:
764 raise ValueError("size missing")
765 if "angle" not in columnnames:
766 raise ValueError("angle missing")
767 return ["size", "angle"] + _styleneedingpointpos.columnnames(self, privatedata, sharedata, graph, columnnames)
769 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
770 if self.lineattrs is not None:
771 privatedata.lineattrs = attr.selectattrs(self.defaultlineattrs + self.lineattrs, selectindex, selecttotal)
772 else:
773 privatedata.lineattrs = None
774 if self.arrowattrs is not None:
775 privatedata.arrowattrs = attr.selectattrs(self.defaultarrowattrs + self.arrowattrs, selectindex, selecttotal)
776 else:
777 privatedata.arrowattrs = None
779 def initdrawpoints(self, privatedata, sharedata, graph):
780 privatedata.arrowcanvas = canvas.canvas()
782 def drawpoint(self, privatedata, sharedata, graph, point):
783 if privatedata.lineattrs is not None and privatedata.arrowattrs is not None and sharedata.vposvalid:
784 linelength_pt = unit.topt(self.linelength)
785 x_pt, y_pt = graph.vpos_pt(*sharedata.vpos)
786 try:
787 angle = point["angle"] + 0.0
788 size = point["size"] + 0.0
789 except:
790 pass
791 else:
792 if point["size"] > self.epsilon:
793 dx = math.cos(angle*math.pi/180)
794 dy = math.sin(angle*math.pi/180)
795 x1 = x_pt-0.5*dx*linelength_pt*size
796 y1 = y_pt-0.5*dy*linelength_pt*size
797 x2 = x_pt+0.5*dx*linelength_pt*size
798 y2 = y_pt+0.5*dy*linelength_pt*size
799 privatedata.arrowcanvas.stroke(path.line_pt(x1, y1, x2, y2), privatedata.lineattrs +
800 [deco.earrow(privatedata.arrowattrs, size=self.arrowsize*size)])
802 def donedrawpoints(self, privatedata, sharedata, graph):
803 graph.insert(privatedata.arrowcanvas)
805 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
806 raise RuntimeError("Style currently doesn't provide a graph key")
809 class rect(_style):
811 needsdata = ["vrange", "vrangeminmissing", "vrangemaxmissing"]
813 def __init__(self, palette=color.palette.Grey):
814 self.palette = palette
816 def columnnames(self, privatedata, sharedata, graph, columnnames):
817 if len(graph.axesnames) != 2:
818 raise TypeError("arrow style restricted on two-dimensional graphs")
819 if "color" not in columnnames:
820 raise ValueError("color missing")
821 if len(sharedata.vrangeminmissing) + len(sharedata.vrangemaxmissing):
822 raise ValueError("incomplete range")
823 return ["color"]
825 def initdrawpoints(self, privatedata, sharedata, graph):
826 privatedata.rectcanvas = graph.insert(canvas.canvas())
828 def drawpoint(self, privatedata, sharedata, graph, point):
829 xvmin = sharedata.vrange[0][0]
830 xvmax = sharedata.vrange[0][1]
831 yvmin = sharedata.vrange[1][0]
832 yvmax = sharedata.vrange[1][1]
833 if (xvmin is not None and xvmin < 1 and
834 xvmax is not None and xvmax > 0 and
835 yvmin is not None and yvmin < 1 and
836 yvmax is not None and yvmax > 0):
837 if xvmin < 0:
838 xvmin = 0
839 elif xvmax > 1:
840 xvmax = 1
841 if yvmin < 0:
842 yvmin = 0
843 elif yvmax > 1:
844 yvmax = 1
845 p = graph.vgeodesic(xvmin, yvmin, xvmax, yvmin)
846 p.append(graph.vgeodesic_el(xvmax, yvmin, xvmax, yvmax))
847 p.append(graph.vgeodesic_el(xvmax, yvmax, xvmin, yvmax))
848 p.append(graph.vgeodesic_el(xvmin, yvmax, xvmin, yvmin))
849 p.append(path.closepath())
850 privatedata.rectcanvas.fill(p, [self.palette.getcolor(point["color"])])
852 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
853 raise RuntimeError("Style currently doesn't provide a graph key")
856 class histogram(_style):
858 needsdata = ["vpos", "vposmissing", "vrange", "vrangeminmissing", "vrangemaxmissing"]
860 defaultlineattrs = [deco.stroked]
861 defaultfrompathattrs = []
863 def __init__(self, lineattrs=[], steps=0, fromvalue=0, frompathattrs=[], fillable=0,
864 autohistogramaxisindex=0, autohistogrampointpos=0.5, epsilon=1e-10):
865 self.lineattrs = lineattrs
866 self.steps = steps
867 self.fromvalue = fromvalue
868 self.frompathattrs = frompathattrs
869 self.fillable = fillable # TODO: fillable paths might not properly be closed by straight lines on curved graph geometries
870 self.autohistogramaxisindex = autohistogramaxisindex
871 self.autohistogrampointpos = autohistogrampointpos
872 self.epsilon = epsilon
874 def columnnames(self, privatedata, sharedata, graph, columnnames):
875 if len(graph.axesnames) != 2:
876 raise TypeError("histogram style restricted on two-dimensional graphs")
877 privatedata.rangeaxisindex = None
878 for i in builtinrange(len(graph.axesnames)):
879 if i in sharedata.vrangeminmissing or i in sharedata.vrangemaxmissing:
880 if i in sharedata.vposmissing:
881 raise ValueError("pos and range missing")
882 else:
883 if privatedata.rangeaxisindex is not None:
884 raise ValueError("multiple ranges")
885 privatedata.rangeaxisindex = i
886 if privatedata.rangeaxisindex is None:
887 privatedata.rangeaxisindex = self.autohistogramaxisindex
888 privatedata.autohistogram = 1
889 else:
890 privatedata.autohistogram = 0
891 return []
893 def adjustaxis(self, privatedata, sharedata, graph, columnname, data):
894 if privatedata.autohistogram and columnname == sharedata.poscolumnnames[privatedata.rangeaxisindex]:
895 if len(data) == 1:
896 raise ValueError("several data points needed for automatic histogram width calculation")
897 if data:
898 delta = data[1] - data[0]
899 min = data[0] - self.autohistogrampointpos * delta
900 max = data[-1] + (1-self.autohistogrampointpos) * delta
901 graph.axes[columnname].adjustaxis([min, max])
903 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
904 privatedata.insertfrompath = selectindex == 0
905 if self.lineattrs is not None:
906 privatedata.lineattrs = attr.selectattrs(self.defaultlineattrs + self.lineattrs, selectindex, selecttotal)
907 else:
908 privatedata.lineattrs = None
910 def vmoveto(self, privatedata, sharedata, graph, vpos, vvalue):
911 if -self.epsilon < vpos < 1+self.epsilon and -self.epsilon < vvalue < 1+self.epsilon:
912 if privatedata.rangeaxisindex:
913 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue, vpos)))
914 else:
915 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vpos, vvalue)))
917 def vposline(self, privatedata, sharedata, graph, vpos, vvalue1, vvalue2):
918 if -self.epsilon < vpos < 1+self.epsilon:
919 vvalue1cut = 0
920 if vvalue1 < 0:
921 vvalue1 = 0
922 vvalue1cut = -1
923 elif vvalue1 > 1:
924 vvalue1 = 1
925 vvalue1cut = 1
926 vvalue2cut = 0
927 if vvalue2 < 0:
928 vvalue2 = 0
929 vvalue2cut = -1
930 elif vvalue2 > 1:
931 vvalue2 = 1
932 vvalue2cut = 1
933 if abs(vvalue1cut + vvalue2cut) <= 1:
934 if vvalue1cut and not self.fillable:
935 if privatedata.rangeaxisindex:
936 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue1, vpos)))
937 else:
938 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vpos, vvalue1)))
939 if privatedata.rangeaxisindex:
940 privatedata.path.append(graph.vgeodesic_el(vvalue1, vpos, vvalue2, vpos))
941 else:
942 privatedata.path.append(graph.vgeodesic_el(vpos, vvalue1, vpos, vvalue2))
944 def vvalueline(self, privatedata, sharedata, graph, vvalue, vpos1, vpos2):
945 if self.fillable:
946 if vvalue < -self.epsilon:
947 vvalue = 0
948 warnings.warn("cut at graph boundary adds artificial lines to fillable step histogram path")
949 if vvalue > 1+self.epsilon:
950 vvalue = 1
951 warnings.warn("cut at graph boundary adds artificial lines to fillable step histogram path")
952 if self.fillable or (-self.epsilon < vvalue < 1+self.epsilon):
953 vpos1cut = 0
954 if vpos1 < 0:
955 vpos1 = 0
956 vpos1cut = -1
957 elif vpos1 > 1:
958 vpos1 = 1
959 vpos1cut = 1
960 vpos2cut = 0
961 if vpos2 < 0:
962 vpos2 = 0
963 vpos2cut = -1
964 elif vpos2 > 1:
965 vpos2 = 1
966 vpos2cut = 1
967 if abs(vpos1cut + vpos2cut) <= 1:
968 if vpos1cut:
969 if self.fillable:
970 if privatedata.rangeaxisindex:
971 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(privatedata.vfromvalue, vpos1)))
972 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vpos1, vvalue, vpos1))
973 else:
974 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vpos1, privatedata.vfromvalue)))
975 privatedata.path.append(graph.vgeodesic_el(vpos1, privatedata.vfromvalue, vpos1, vvalue))
976 else:
977 if privatedata.rangeaxisindex:
978 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue, vpos1)))
979 else:
980 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vpos1, vvalue)))
981 if privatedata.rangeaxisindex:
982 privatedata.path.append(graph.vgeodesic_el(vvalue, vpos1, vvalue, vpos2))
983 else:
984 privatedata.path.append(graph.vgeodesic_el(vpos1, vvalue, vpos2, vvalue))
985 if self.fillable and vpos2cut:
986 warnings.warn("cut at graph boundary adds artificial lines to fillable step histogram path")
987 if privatedata.rangeaxisindex:
988 privatedata.path.append(graph.vgeodesic_el(vvalue, vpos2, privatedata.vfromvalue, vpos2))
989 else:
990 privatedata.path.append(graph.vgeodesic_el(vpos2, vvalue, vpos2, privatedata.vfromvalue))
992 def drawvalue(self, privatedata, sharedata, graph, vmin, vmax, vvalue):
993 currentvalid = vmin is not None and vmax is not None and vvalue is not None
994 if self.fillable and not self.steps:
995 if not currentvalid:
996 return
997 vmincut = 0
998 if vmin < -self.epsilon:
999 vmin = 0
1000 vmincut = -1
1001 elif vmin > 1+self.epsilon:
1002 vmin = 1
1003 vmincut = 1
1004 vmaxcut = 0
1005 if vmax < -self.epsilon:
1006 vmax = 0
1007 vmaxcut = -1
1008 if vmax > 1+self.epsilon:
1009 vmax = 1
1010 vmaxcut = 1
1011 vvaluecut = 0
1012 if vvalue < -self.epsilon:
1013 vvalue = 0
1014 vvaluecut = -1
1015 if vvalue > 1+self.epsilon:
1016 vvalue = 1
1017 vvaluecut = 1
1018 done = 0
1019 if abs(vmincut) + abs(vmaxcut) + abs(vvaluecut) + abs(privatedata.vfromvaluecut) > 1:
1020 if abs(vmincut + vmaxcut) > 1 or abs(vvaluecut+privatedata.vfromvaluecut) > 1:
1021 done = 1
1022 else:
1023 warnings.warn("multiple cuts at graph boundary add artificial lines to fillable rectangle histogram path")
1024 elif vmincut:
1025 done = 1
1026 if privatedata.rangeaxisindex:
1027 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(privatedata.vfromvalue, vmin)))
1028 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmin, privatedata.vfromvalue, vmax))
1029 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmax, vvalue, vmax))
1030 privatedata.path.append(graph.vgeodesic_el(vvalue, vmax, vvalue, vmin))
1031 else:
1032 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmin, privatedata.vfromvalue)))
1033 privatedata.path.append(graph.vgeodesic_el(vmin, privatedata.vfromvalue, vmax, privatedata.vfromvalue))
1034 privatedata.path.append(graph.vgeodesic_el(vmax, privatedata.vfromvalue, vmax, vvalue))
1035 privatedata.path.append(graph.vgeodesic_el(vmax, vvalue, vmin, vvalue))
1036 elif vmaxcut:
1037 done = 1
1038 if privatedata.rangeaxisindex:
1039 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue, vmax)))
1040 privatedata.path.append(graph.vgeodesic_el(vvalue, vmax, vvalue, vmin))
1041 privatedata.path.append(graph.vgeodesic_el(vvalue, vmin, privatedata.vfromvalue, vmin))
1042 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmin, privatedata.vfromvalue, vmax))
1043 else:
1044 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmax, vvalue)))
1045 privatedata.path.append(graph.vgeodesic_el(vmax, vvalue, vmin, vvalue))
1046 privatedata.path.append(graph.vgeodesic_el(vmin, vvalue, vmin, privatedata.vfromvalue))
1047 privatedata.path.append(graph.vgeodesic_el(vmin, privatedata.vfromvalue, vmax, privatedata.vfromvalue))
1048 elif privatedata.vfromvaluecut:
1049 done = 1
1050 if privatedata.rangeaxisindex:
1051 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(privatedata.vfromvalue, vmax)))
1052 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmax, vvalue, vmax))
1053 privatedata.path.append(graph.vgeodesic_el(vvalue, vmax, vvalue, vmin))
1054 privatedata.path.append(graph.vgeodesic_el(vvalue, vmin, privatedata.vfromvalue, vmin))
1055 else:
1056 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmax, privatedata.vfromvalue)))
1057 privatedata.path.append(graph.vgeodesic_el(vmax, privatedata.vfromvalue, vmax, vvalue))
1058 privatedata.path.append(graph.vgeodesic_el(vmax, vvalue, vmin, vvalue))
1059 privatedata.path.append(graph.vgeodesic_el(vmin, vvalue, vmin, privatedata.vfromvalue))
1060 elif vvaluecut:
1061 done = 1
1062 if privatedata.rangeaxisindex:
1063 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue, vmin)))
1064 privatedata.path.append(graph.vgeodesic_el(vvalue, vmin, privatedata.vfromvalue, vmin))
1065 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmin, privatedata.vfromvalue, vmax))
1066 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmax, vvalue, vmax))
1067 else:
1068 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmin, vvalue)))
1069 privatedata.path.append(graph.vgeodesic_el(vmin, vvalue, vmin, privatedata.vfromvalue))
1070 privatedata.path.append(graph.vgeodesic_el(vmin, privatedata.vfromvalue, vmax, privatedata.vfromvalue))
1071 privatedata.path.append(graph.vgeodesic_el(vmax, privatedata.vfromvalue, vmax, vvalue))
1072 if not done:
1073 if privatedata.rangeaxisindex:
1074 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(privatedata.vfromvalue, vmin)))
1075 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmin, privatedata.vfromvalue, vmax))
1076 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmax, vvalue, vmax))
1077 privatedata.path.append(graph.vgeodesic_el(vvalue, vmax, vvalue, vmin))
1078 privatedata.path.append(graph.vgeodesic_el(vvalue, vmin, privatedata.vfromvalue, vmin))
1079 privatedata.path.append(path.closepath())
1080 else:
1081 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmin, privatedata.vfromvalue)))
1082 privatedata.path.append(graph.vgeodesic_el(vmin, privatedata.vfromvalue, vmax, privatedata.vfromvalue))
1083 privatedata.path.append(graph.vgeodesic_el(vmax, privatedata.vfromvalue, vmax, vvalue))
1084 privatedata.path.append(graph.vgeodesic_el(vmax, vvalue, vmin, vvalue))
1085 privatedata.path.append(graph.vgeodesic_el(vmin, vvalue, vmin, privatedata.vfromvalue))
1086 privatedata.path.append(path.closepath())
1087 else:
1088 try:
1089 gap = abs(vmin - privatedata.lastvmax) > self.epsilon
1090 except (ArithmeticError, ValueError, TypeError):
1091 gap = 1
1092 if (privatedata.lastvvalue is not None and
1093 currentvalid and
1094 not gap and
1095 (self.steps or
1096 (privatedata.lastvvalue-privatedata.vfromvalue)*(vvalue-privatedata.vfromvalue) < 0)):
1097 self.vposline(privatedata, sharedata, graph,
1098 vmin, privatedata.lastvvalue, vvalue)
1099 else:
1100 if (privatedata.lastvvalue is not None and
1101 (not currentvalid or
1102 abs(privatedata.lastvvalue-privatedata.vfromvalue) > abs(vvalue-privatedata.vfromvalue) or
1103 gap)):
1104 self.vposline(privatedata, sharedata, graph,
1105 privatedata.lastvmax, privatedata.lastvvalue, privatedata.vfromvalue)
1106 if currentvalid:
1107 self.vmoveto(privatedata, sharedata, graph,
1108 vmin, vvalue)
1109 if (currentvalid and
1110 (privatedata.lastvvalue is None or
1111 abs(privatedata.lastvvalue-privatedata.vfromvalue) < abs(vvalue-privatedata.vfromvalue) or
1112 gap)):
1113 self.vmoveto(privatedata, sharedata, graph,
1114 vmin, privatedata.vfromvalue)
1115 self.vposline(privatedata, sharedata, graph,
1116 vmin, privatedata.vfromvalue, vvalue)
1117 if currentvalid:
1118 self.vvalueline(privatedata, sharedata, graph,
1119 vvalue, vmin, vmax)
1120 privatedata.lastvvalue = vvalue
1121 privatedata.lastvmax = vmax
1122 else:
1123 privatedata.lastvvalue = privatedata.lastvmax = None
1125 def initdrawpoints(self, privatedata, sharedata, graph):
1126 privatedata.path = path.path()
1127 privatedata.lastvvalue = privatedata.lastvmax = None
1128 privatedata.vcurrentpoint = None
1129 privatedata.count = 0
1130 if self.fromvalue is not None:
1131 valueaxisname = sharedata.poscolumnnames[1-privatedata.rangeaxisindex]
1132 privatedata.vfromvalue = graph.axes[valueaxisname].convert(self.fromvalue)
1133 privatedata.vfromvaluecut = 0
1134 if privatedata.vfromvalue < 0:
1135 privatedata.vfromvalue = 0
1136 privatedata.vfromvaluecut = -1
1137 if privatedata.vfromvalue > 1:
1138 privatedata.vfromvalue = 1
1139 privatedata.vfromvaluecut = 1
1140 if self.frompathattrs is not None and privatedata.insertfrompath:
1141 graph.stroke(graph.axes[valueaxisname].vgridpath(privatedata.vfromvalue),
1142 self.defaultfrompathattrs + self.frompathattrs)
1143 else:
1144 privatedata.vfromvalue = 0
1146 def drawpoint(self, privatedata, sharedata, graph, point):
1147 if privatedata.autohistogram:
1148 # automatic range handling
1149 privatedata.count += 1
1150 if privatedata.count == 2:
1151 if privatedata.rangeaxisindex:
1152 privatedata.vrange = sharedata.vpos[1] - privatedata.lastvpos[1]
1153 self.drawvalue(privatedata, sharedata, graph,
1154 privatedata.lastvpos[1] - self.autohistogrampointpos*privatedata.vrange,
1155 privatedata.lastvpos[1] + (1-self.autohistogrampointpos)*privatedata.vrange,
1156 privatedata.lastvpos[0])
1157 else:
1158 privatedata.vrange = sharedata.vpos[0] - privatedata.lastvpos[0]
1159 self.drawvalue(privatedata, sharedata, graph,
1160 privatedata.lastvpos[0] - self.autohistogrampointpos*privatedata.vrange,
1161 privatedata.lastvpos[0] + (1-self.autohistogrampointpos)*privatedata.vrange,
1162 privatedata.lastvpos[1])
1163 elif privatedata.count > 2:
1164 if privatedata.rangeaxisindex:
1165 vrange = sharedata.vpos[1] - privatedata.lastvpos[1]
1166 else:
1167 vrange = sharedata.vpos[0] - privatedata.lastvpos[0]
1168 if abs(privatedata.vrange - vrange) > self.epsilon:
1169 raise ValueError("equal steps (in graph coordinates) needed for automatic width calculation")
1170 if privatedata.count > 1:
1171 if privatedata.rangeaxisindex:
1172 self.drawvalue(privatedata, sharedata, graph,
1173 sharedata.vpos[1] - self.autohistogrampointpos*privatedata.vrange,
1174 sharedata.vpos[1] + (1-self.autohistogrampointpos)*privatedata.vrange,
1175 sharedata.vpos[0])
1176 else:
1177 self.drawvalue(privatedata, sharedata, graph,
1178 sharedata.vpos[0] - self.autohistogrampointpos*privatedata.vrange,
1179 sharedata.vpos[0] + (1-self.autohistogrampointpos)*privatedata.vrange,
1180 sharedata.vpos[1])
1181 privatedata.lastvpos = sharedata.vpos[:]
1182 else:
1183 if privatedata.rangeaxisindex:
1184 self.drawvalue(privatedata, sharedata, graph,
1185 sharedata.vrange[1][0], sharedata.vrange[1][1], sharedata.vpos[0])
1186 else:
1187 self.drawvalue(privatedata, sharedata, graph,
1188 sharedata.vrange[0][0], sharedata.vrange[0][1], sharedata.vpos[1])
1190 def donedrawpoints(self, privatedata, sharedata, graph):
1191 self.drawvalue(privatedata, sharedata, graph, None, None, None)
1192 if privatedata.lineattrs is not None and len(privatedata.path):
1193 graph.draw(privatedata.path, privatedata.lineattrs)
1196 class barpos(_style):
1198 providesdata = ["vpos", "vposmissing", "vposavailable", "vposvalid", "vbarrange", "barposcolumnnames", "barvalueindex", "lastbarvalue", "stackedbar", "stackedbardraw"]
1200 defaultfrompathattrs = []
1202 def __init__(self, fromvalue=None, frompathattrs=[], epsilon=1e-10):
1203 self.fromvalue = fromvalue
1204 self.frompathattrs = frompathattrs
1205 self.epsilon = epsilon
1207 def columnnames(self, privatedata, sharedata, graph, columnnames):
1208 sharedata.barposcolumnnames = []
1209 sharedata.barvalueindex = None
1210 for dimension, axisnames in enumerate(graph.axesnames):
1211 found = 0
1212 for axisname in axisnames:
1213 if axisname in columnnames:
1214 if sharedata.barvalueindex is not None:
1215 raise ValueError("multiple values")
1216 sharedata.barvalueindex = dimension
1217 sharedata.barposcolumnnames.append(axisname)
1218 found += 1
1219 if (axisname + "name") in columnnames:
1220 sharedata.barposcolumnnames.append(axisname + "name")
1221 found += 1
1222 if found > 1:
1223 raise ValueError("multiple names and value")
1224 if not found:
1225 raise ValueError("value/name missing")
1226 if sharedata.barvalueindex is None:
1227 raise ValueError("missing value")
1228 sharedata.vposmissing = []
1229 return sharedata.barposcolumnnames
1231 def addsubvalue(self, value, subvalue):
1232 try:
1233 value + ""
1234 except:
1235 try:
1236 return value[0], self.addsubvalue(value[1], subvalue)
1237 except:
1238 return value, subvalue
1239 else:
1240 return value, subvalue
1242 def adjustaxis(self, privatedata, sharedata, graph, columnname, data):
1243 try:
1244 i = sharedata.barposcolumnnames.index(columnname)
1245 except ValueError:
1246 pass
1247 else:
1248 if i == sharedata.barvalueindex:
1249 if self.fromvalue is not None:
1250 graph.axes[sharedata.barposcolumnnames[i]].adjustaxis([self.fromvalue])
1251 graph.axes[sharedata.barposcolumnnames[i]].adjustaxis(data)
1252 else:
1253 graph.axes[sharedata.barposcolumnnames[i][:-4]].adjustaxis([self.addsubvalue(x, 0) for x in data])
1254 graph.axes[sharedata.barposcolumnnames[i][:-4]].adjustaxis([self.addsubvalue(x, 1) for x in data])
1256 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1257 privatedata.insertfrompath = selectindex == 0
1259 def initdrawpoints(self, privatedata, sharedata, graph):
1260 sharedata.vpos = [None]*(len(sharedata.barposcolumnnames))
1261 sharedata.vbarrange = [[None for i in xrange(2)] for x in sharedata.barposcolumnnames]
1262 sharedata.stackedbar = sharedata.stackedbardraw = 0
1264 if self.fromvalue is not None:
1265 privatedata.vfromvalue = graph.axes[sharedata.barposcolumnnames[sharedata.barvalueindex][0]].convert(self.fromvalue)
1266 if privatedata.vfromvalue < 0:
1267 privatedata.vfromvalue = 0
1268 if privatedata.vfromvalue > 1:
1269 privatedata.vfromvalue = 1
1270 if self.frompathattrs is not None and privatedata.insertfrompath:
1271 graph.stroke(graph.axes[sharedata.barposcolumnnames[sharedata.barvalueindex][0]].vgridpath(privatedata.vfromvalue),
1272 self.defaultfrompathattrs + self.frompathattrs)
1273 else:
1274 privatedata.vfromvalue = 0
1276 def drawpoint(self, privatedata, sharedata, graph, point):
1277 sharedata.vposavailable = sharedata.vposvalid = 1
1278 for i, barname in enumerate(sharedata.barposcolumnnames):
1279 if i == sharedata.barvalueindex:
1280 sharedata.vbarrange[i][0] = privatedata.vfromvalue
1281 sharedata.lastbarvalue = point[barname]
1282 try:
1283 sharedata.vpos[i] = sharedata.vbarrange[i][1] = graph.axes[barname].convert(sharedata.lastbarvalue)
1284 except (ArithmeticError, ValueError, TypeError):
1285 sharedata.vpos[i] = sharedata.vbarrange[i][1] = None
1286 else:
1287 for j in xrange(2):
1288 try:
1289 sharedata.vbarrange[i][j] = graph.axes[barname[:-4]].convert(self.addsubvalue(point[barname], j))
1290 except (ArithmeticError, ValueError, TypeError):
1291 sharedata.vbarrange[i][j] = None
1292 try:
1293 sharedata.vpos[i] = 0.5*(sharedata.vbarrange[i][0]+sharedata.vbarrange[i][1])
1294 except (ArithmeticError, ValueError, TypeError):
1295 sharedata.vpos[i] = None
1296 if sharedata.vpos[i] is None:
1297 sharedata.vposavailable = sharedata.vposvalid = 0
1298 elif sharedata.vpos[i] < -self.epsilon or sharedata.vpos[i] > 1+self.epsilon:
1299 sharedata.vposvalid = 0
1301 registerdefaultprovider(barpos(), ["vbarrange", "barposcolumnnames", "barvalueindex", "lastbarvalue", "stackedbar", "stackedbardraw"])
1304 class stackedbarpos(_style):
1306 # provides no additional data, but needs some data (and modifies some of them)
1307 needsdata = ["vbarrange", "barposcolumnnames", "barvalueindex", "lastbarvalue", "stackedbar", "stackedbardraw"]
1309 def __init__(self, stackname, addontop=0, epsilon=1e-10):
1310 self.stackname = stackname
1311 self.epsilon = epsilon
1312 self.addontop = addontop
1314 def columnnames(self, privatedata, sharedata, graph, columnnames):
1315 if self.stackname not in columnnames:
1316 raise ValueError("column '%s' missing" % self.stackname)
1317 return [self.stackname]
1319 def adjustaxis(self, privatedata, sharedata, graph, columnname, data):
1320 if columnname == self.stackname:
1321 graph.axes[sharedata.barposcolumnnames[sharedata.barvalueindex]].adjustaxis(data)
1323 def initdrawpoints(self, privatedata, sharedata, graph):
1324 if sharedata.stackedbardraw: # do not count the start bar when not gets painted
1325 sharedata.stackedbar += 1
1327 def drawpoint(self, privatedata, sharedata, graph, point):
1328 sharedata.vbarrange[sharedata.barvalueindex][0] = sharedata.vbarrange[sharedata.barvalueindex][1]
1329 if self.addontop:
1330 try:
1331 sharedata.lastbarvalue += point[self.stackname]
1332 except (ArithmeticError, ValueError, TypeError):
1333 sharedata.lastbarvalue = None
1334 else:
1335 sharedata.lastbarvalue = point[self.stackname]
1336 try:
1337 sharedata.vpos[sharedata.barvalueindex] = sharedata.vbarrange[sharedata.barvalueindex][1] = graph.axes[sharedata.barposcolumnnames[sharedata.barvalueindex]].convert(sharedata.lastbarvalue)
1338 except (ArithmeticError, ValueError, TypeError):
1339 sharedata.vpos[sharedata.barvalueindex] = sharedata.vbarrange[sharedata.barvalueindex][1] = None
1340 sharedata.vposavailable = sharedata.vposvalid = 0
1341 else:
1342 if not sharedata.vposavailable or not sharedata.vposvalid:
1343 sharedata.vposavailable = sharedata.vposvalid = 1
1344 for v in sharedata.vpos:
1345 if v is None:
1346 sharedata.vposavailable = sharedata.vposvalid = 0
1347 break
1348 if v < -self.epsilon or v > 1+self.epsilon:
1349 sharedata.vposvalid = 0
1352 class bar(_style):
1354 needsdata = ["vbarrange"]
1356 defaultbarattrs = [color.palette.Rainbow, deco.stroked([color.grey.black])]
1358 def __init__(self, barattrs=[]):
1359 self.barattrs = barattrs
1361 def columnnames(self, privatedata, sharedata, graph, columnnames):
1362 if len(graph.axesnames) != 2:
1363 raise TypeError("bar style restricted on two-dimensional graphs")
1364 return []
1366 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1367 privatedata.barattrs = attr.selectattrs(self.defaultbarattrs + self.barattrs, selectindex, selecttotal)
1369 def initdrawpoints(self, privatedata, sharedata, graph):
1370 privatedata.rectcanvas = graph.insert(canvas.canvas())
1371 sharedata.stackedbardraw = 1
1372 privatedata.stackedbar = sharedata.stackedbar
1374 def drawpointfill(self, privatedata, p):
1375 if p:
1376 privatedata.rectcanvas.fill(p, privatedata.barattrs)
1378 def drawpoint(self, privatedata, sharedata, graph, point):
1379 xvmin = sharedata.vbarrange[0][0]
1380 xvmax = sharedata.vbarrange[0][1]
1381 yvmin = sharedata.vbarrange[1][0]
1382 yvmax = sharedata.vbarrange[1][1]
1383 try:
1384 if xvmin > xvmax:
1385 xvmin, xvmax = xvmax, xvmin
1386 except:
1387 pass
1388 try:
1389 if yvmin > yvmax:
1390 yvmin, yvmax = yvmax, yvmin
1391 except:
1392 pass
1393 if (xvmin is not None and xvmin < 1 and
1394 xvmax is not None and xvmax > 0 and
1395 yvmin is not None and yvmin < 1 and
1396 yvmax is not None and yvmax > 0):
1397 if xvmin < 0:
1398 xvmin = 0
1399 elif xvmax > 1:
1400 xvmax = 1
1401 if yvmin < 0:
1402 yvmin = 0
1403 elif yvmax > 1:
1404 yvmax = 1
1405 p = graph.vgeodesic(xvmin, yvmin, xvmax, yvmin)
1406 p.append(graph.vgeodesic_el(xvmax, yvmin, xvmax, yvmax))
1407 p.append(graph.vgeodesic_el(xvmax, yvmax, xvmin, yvmax))
1408 p.append(graph.vgeodesic_el(xvmin, yvmax, xvmin, yvmin))
1409 p.append(path.closepath())
1410 self.drawpointfill(privatedata, p)
1411 else:
1412 self.drawpointfill(privatedata, None)
1414 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
1415 selectindex = privatedata.stackedbar
1416 selecttotal = sharedata.stackedbar + 1
1417 graph.fill(path.rect_pt(x_pt + width_pt*selectindex/float(selecttotal), y_pt, width_pt/float(selecttotal), height_pt), privatedata.barattrs)
1420 class changebar(bar):
1422 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1423 if selecttotal != 1:
1424 raise RuntimeError("Changebar can't change its appearance. Thus you can't use it to plot several bars side by side on a subaxis.")
1426 def initdrawpoints(self, privatedata, sharedata, graph):
1427 bar.initdrawpoints(self, privatedata, sharedata, graph)
1428 privatedata.bars = []
1430 def drawpointfill(self, privatedata, p):
1431 privatedata.bars.append(p)
1433 def donedrawpoints(self, privatedata, sharedata, graph):
1434 selecttotal = len(privatedata.bars)
1435 for selectindex, p in enumerate(privatedata.bars):
1436 if p:
1437 barattrs = attr.selectattrs(self.defaultbarattrs + self.barattrs, selectindex, selecttotal)
1438 privatedata.rectcanvas.fill(p, barattrs)
1440 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
1441 raise RuntimeError("Style currently doesn't provide a graph key")