use the original axis name in the adjustaxis method of the range style for absolute...
[PyX/mjg.git] / pyx / graph / style.py
blobda4771bf1c80920b17cae24462cece5d4c35894c
1 # -*- encoding: utf-8 -*-
4 # Copyright (C) 2002-2004 Jörg Lehmann <joergl@users.sourceforge.net>
5 # Copyright (C) 2003-2004 Michael Schindler <m-schindler@users.sourceforge.net>
6 # Copyright (C) 2002-2011 André Wobst <wobsta@users.sourceforge.net>
8 # This file is part of PyX (http://pyx.sourceforge.net/).
10 # PyX is free software; you can redistribute it and/or modify
11 # it under the terms of the GNU General Public License as published by
12 # the Free Software Foundation; either version 2 of the License, or
13 # (at your option) any later version.
15 # PyX is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 # GNU General Public License for more details.
20 # You should have received a copy of the GNU General Public License
21 # along with PyX; if not, write to the Free Software
22 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
25 import math, warnings, cStringIO
26 from pyx import attr, deco, style, color, unit, canvas, path, mesh, pycompat, trafo
27 from pyx import text as textmodule
28 from pyx import bitmap as bitmapmodule
30 builtinrange = range
33 class _style:
34 """Interface class for graph styles
36 Each graph style must support the methods described in this
37 class. However, since a graph style might not need to perform
38 actions on all the various events, it does not need to overwrite
39 all methods of this base class (e.g. this class is not an abstract
40 class in any respect).
42 A style should never store private data by instance variables
43 (i.e. accessing self), but it should use the sharedata and privatedata
44 instances instead. A style instance can be used multiple times with
45 different sharedata and privatedata instances at the very same time.
46 The sharedata and privatedata instances act as data containers and
47 sharedata allows for sharing information across several styles.
49 Every style contains two class variables, which are not to be
50 modified:
51 - providesdata is a list of variable names a style offers via
52 the sharedata instance. This list is used to determine whether
53 all needs of subsequent styles are fulfilled. Otherwise
54 getdefaultprovider should return a proper style to be used.
55 - needsdata is a list of variable names the style needs to access in the
56 sharedata instance.
57 """
59 providesdata = [] # by default, we provide nothing
60 needsdata = [] # and do not depend on anything
62 def columnnames(self, privatedata, sharedata, graph, columnnames):
63 """Set column information
65 This method is used setup the column name information to be
66 accessible to the style later on. The style should analyse
67 the list of column names. The method should return a list of
68 column names which the style will make use of."""
69 return []
71 def adjustaxis(self, privatedata, sharedata, graph, columnname, data):
72 """Adjust axis range
74 This method is called in order to adjust the axis range to
75 the provided data. columnname is the column name (each style
76 is subsequently called for all column names)."""
77 pass
79 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
80 """Select stroke/fill attributes
82 This method is called to allow for the selection of
83 changable attributes of a style."""
84 pass
86 def initdrawpoints(self, privatedata, sharedata, graph):
87 """Initialize drawing of data
89 This method might be used to initialize the drawing of data."""
90 pass
92 def drawpoint(self, privatedata, sharedata, graph, point):
93 """Draw data
95 This method is called for each data point. The data is
96 available in the dictionary point. The dictionary
97 keys are the column names."""
98 pass
100 def donedrawpoints(self, privatedata, sharedata, graph):
101 """Finalize drawing of data
103 This method is called after the last data point was
104 drawn using the drawpoint method above."""
105 pass
107 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
108 """Draw graph key"""
111 # The following two methods are used to register and get a default provider
112 # for keys. A key is a variable name in sharedata. A provider is a style
113 # which creates variables in sharedata.
115 _defaultprovider = {}
117 def registerdefaultprovider(style, keys):
118 """sets a style as a default creator for sharedata variables 'keys'"""
119 for key in keys:
120 assert key in style.providesdata, "key not provided by style"
121 # we might allow for overwriting the defaults, i.e. the following is not checked:
122 # assert key in _defaultprovider.keys(), "default provider already registered for key"
123 _defaultprovider[key] = style
125 def getdefaultprovider(key):
126 """returns a style, which acts as a default creator for the
127 sharedata variable 'key'"""
128 return _defaultprovider[key]
131 class pos(_style):
133 providesdata = ["vpos", "vposmissing", "vposavailable", "vposvalid", "poscolumnnames"]
135 def __init__(self, epsilon=1e-10):
136 self.epsilon = epsilon
138 def columnnames(self, privatedata, sharedata, graph, columnnames):
139 sharedata.poscolumnnames = []
140 sharedata.vposmissing = []
141 for count, axisnames in enumerate(graph.axesnames):
142 # all used axisnames are also data columns
143 for axisname in axisnames:
144 for columnname in columnnames:
145 if axisname == columnname:
146 sharedata.poscolumnnames.append(columnname)
147 if len(sharedata.poscolumnnames) > count+1:
148 raise ValueError("multiple axes per graph dimension")
149 elif len(sharedata.poscolumnnames) < count+1:
150 sharedata.vposmissing.append(count)
151 sharedata.poscolumnnames.append(None)
152 return [columnname for columnname in sharedata.poscolumnnames if columnname is not None]
154 def adjustaxis(self, privatedata, sharedata, graph, columnname, data):
155 if columnname in sharedata.poscolumnnames:
156 graph.axes[columnname].adjustaxis(data)
158 def initdrawpoints(self, privatedata, sharedata, graph):
159 sharedata.vpos = [None]*(len(graph.axesnames))
160 privatedata.pointpostmplist = [[columnname, index, graph.axes[columnname]] # temporarily used by drawpoint only
161 for index, columnname in enumerate([columnname for columnname in sharedata.poscolumnnames if columnname is not None])]
162 for missing in sharedata.vposmissing:
163 for pointpostmp in privatedata.pointpostmplist:
164 if pointpostmp[1] >= missing:
165 pointpostmp[1] += 1
167 def drawpoint(self, privatedata, sharedata, graph, point):
168 sharedata.vposavailable = 1 # valid position (but might be outside of the graph)
169 sharedata.vposvalid = 1 # valid position inside the graph
170 for columnname, index, axis in privatedata.pointpostmplist:
171 try:
172 v = axis.convert(point[columnname])
173 except (ArithmeticError, ValueError, TypeError):
174 sharedata.vposavailable = sharedata.vposvalid = 0
175 sharedata.vpos[index] = None
176 else:
177 if v < -self.epsilon or v > 1+self.epsilon:
178 sharedata.vposvalid = 0
179 sharedata.vpos[index] = v
182 registerdefaultprovider(pos(), pos.providesdata)
185 class range(_style):
187 providesdata = ["vrange", "vrangemissing", "vrangeminmissing", "vrangemaxmissing"]
189 # internal bit masks
190 mask_value = 1
191 mask_min = 2
192 mask_max = 4
193 mask_dmin = 8
194 mask_dmax = 16
195 mask_d = 32
197 def __init__(self, usenames={}, epsilon=1e-10):
198 self.usenames = usenames
199 self.epsilon = epsilon
201 def _numberofbits(self, mask):
202 if not mask:
203 return 0
204 if mask & 1:
205 return self._numberofbits(mask >> 1) + 1
206 else:
207 return self._numberofbits(mask >> 1)
209 def columnnames(self, privatedata, sharedata, graph, columnnames):
210 usecolumns = []
211 privatedata.rangeposcolumns = []
212 sharedata.vrangemissing = []
213 sharedata.vrangeminmissing = []
214 sharedata.vrangemaxmissing = []
215 privatedata.rangeposdeltacolumns = {} # temporarily used by adjustaxis only
216 for count, axisnames in enumerate(graph.axesnames):
217 for axisname in axisnames:
218 try:
219 usename = self.usenames[axisname]
220 except KeyError:
221 usename = axisname
222 mask = 0
223 for columnname in columnnames:
224 addusecolumns = 1
225 if usename == columnname:
226 mask += self.mask_value
227 elif usename + "min" == columnname:
228 mask += self.mask_min
229 elif usename + "max" == columnname:
230 mask += self.mask_max
231 elif "d" + usename + "min" == columnname:
232 mask += self.mask_dmin
233 elif "d" + usename + "max" == columnname:
234 mask += self.mask_dmax
235 elif "d" + usename == columnname:
236 mask += self.mask_d
237 else:
238 addusecolumns = 0
239 if addusecolumns:
240 usecolumns.append(columnname)
241 if mask & (self.mask_min | self.mask_max | self.mask_dmin | self.mask_dmax | self.mask_d):
242 if (self._numberofbits(mask & (self.mask_min | self.mask_dmin | self.mask_d)) > 1 or
243 self._numberofbits(mask & (self.mask_max | self.mask_dmax | self.mask_d)) > 1):
244 raise ValueError("multiple range definition")
245 if mask & (self.mask_dmin | self.mask_dmax | self.mask_d):
246 if not (mask & self.mask_value):
247 raise ValueError("missing value for delta")
248 privatedata.rangeposdeltacolumns[axisname] = {}
249 privatedata.rangeposcolumns.append((axisname, usename, mask))
250 elif mask == self.mask_value:
251 usecolumns = usecolumns[:-1]
252 if len(privatedata.rangeposcolumns) + len(sharedata.vrangemissing) > count+1:
253 raise ValueError("multiple axes per graph dimension")
254 elif len(privatedata.rangeposcolumns) + len(sharedata.vrangemissing) < count+1:
255 sharedata.vrangemissing.append(count)
256 sharedata.vrangeminmissing.append(count)
257 sharedata.vrangemaxmissing.append(count)
258 else:
259 if not (privatedata.rangeposcolumns[-1][2] & (self.mask_min | self.mask_dmin | self.mask_d)):
260 sharedata.vrangeminmissing.append(count)
261 if not (privatedata.rangeposcolumns[-1][2] & (self.mask_max | self.mask_dmax | self.mask_d)):
262 sharedata.vrangemaxmissing.append(count)
263 return usecolumns
265 def adjustaxis(self, privatedata, sharedata, graph, columnname, data):
266 for axisname, usename, mask in privatedata.rangeposcolumns:
267 if columnname == usename + "min" and mask & self.mask_min:
268 graph.axes[axisname].adjustaxis(data)
269 if columnname == usename + "max" and mask & self.mask_max:
270 graph.axes[axisname].adjustaxis(data)
272 # delta handling: fill rangeposdeltacolumns
273 for axisname, usename, mask in privatedata.rangeposcolumns:
274 if columnname == usename and mask & (self.mask_dmin | self.mask_dmax | self.mask_d):
275 privatedata.rangeposdeltacolumns[axisname][self.mask_value] = data
276 if columnname == "d" + usename + "min" and mask & self.mask_dmin:
277 privatedata.rangeposdeltacolumns[axisname][self.mask_dmin] = data
278 if columnname == "d" + usename + "max" and mask & self.mask_dmax:
279 privatedata.rangeposdeltacolumns[axisname][self.mask_dmax] = data
280 if columnname == "d" + usename and mask & self.mask_d:
281 privatedata.rangeposdeltacolumns[axisname][self.mask_d] = data
283 # delta handling: process rangeposdeltacolumns
284 for a, d in privatedata.rangeposdeltacolumns.items():
285 if d.has_key(self.mask_value):
286 for k in d.keys():
287 if k != self.mask_value:
288 if k & (self.mask_dmin | self.mask_d):
289 mindata = []
290 for value, delta in zip(d[self.mask_value], d[k]):
291 try:
292 mindata.append(value-delta)
293 except:
294 pass
295 graph.axes[a].adjustaxis(mindata)
296 if k & (self.mask_dmax | self.mask_d):
297 maxdata = []
298 for value, delta in zip(d[self.mask_value], d[k]):
299 try:
300 maxdata.append(value+delta)
301 except:
302 pass
303 graph.axes[a].adjustaxis(maxdata)
304 del d[k]
306 def initdrawpoints(self, privatedata, sharedata, graph):
307 sharedata.vrange = [[None for x in xrange(2)] for y in privatedata.rangeposcolumns + sharedata.vrangemissing]
308 privatedata.rangepostmplist = [[usename, mask, index, graph.axes[axisname]] # temporarily used by drawpoint only
309 for index, (axisname, usename, mask) in enumerate(privatedata.rangeposcolumns)]
310 for missing in sharedata.vrangemissing:
311 for rangepostmp in privatedata.rangepostmplist:
312 if rangepostmp[2] >= missing:
313 rangepostmp[2] += 1
315 def drawpoint(self, privatedata, sharedata, graph, point):
316 for usename, mask, index, axis in privatedata.rangepostmplist:
317 try:
318 if mask & self.mask_min:
319 sharedata.vrange[index][0] = axis.convert(point[usename + "min"])
320 if mask & self.mask_dmin:
321 sharedata.vrange[index][0] = axis.convert(point[usename] - point["d" + usename + "min"])
322 if mask & self.mask_d:
323 sharedata.vrange[index][0] = axis.convert(point[usename] - point["d" + usename])
324 except (ArithmeticError, ValueError, TypeError):
325 sharedata.vrange[index][0] = None
326 try:
327 if mask & self.mask_max:
328 sharedata.vrange[index][1] = axis.convert(point[usename + "max"])
329 if mask & self.mask_dmax:
330 sharedata.vrange[index][1] = axis.convert(point[usename] + point["d" + usename + "max"])
331 if mask & self.mask_d:
332 sharedata.vrange[index][1] = axis.convert(point[usename] + point["d" + usename])
333 except (ArithmeticError, ValueError, TypeError):
334 sharedata.vrange[index][1] = None
336 # some range checks for data consistency
337 if (sharedata.vrange[index][0] is not None and sharedata.vrange[index][1] is not None and
338 sharedata.vrange[index][0] > sharedata.vrange[index][1] + self.epsilon):
339 raise ValueError("inverse range")
340 # disabled due to missing vpos access:
341 # if (sharedata.vrange[index][0] is not None and sharedata.vpos[index] is not None and
342 # sharedata.vrange[index][0] > sharedata.vpos[index] + self.epsilon):
343 # raise ValueError("negative minimum errorbar")
344 # if (sharedata.vrange[index][1] is not None and sharedata.vpos[index] is not None and
345 # sharedata.vrange[index][1] < sharedata.vpos[index] - self.epsilon):
346 # raise ValueError("negative maximum errorbar")
349 registerdefaultprovider(range(), range.providesdata)
352 def _crosssymbol(c, x_pt, y_pt, size_pt, attrs):
353 c.draw(path.path(path.moveto_pt(x_pt-0.5*size_pt, y_pt-0.5*size_pt),
354 path.lineto_pt(x_pt+0.5*size_pt, y_pt+0.5*size_pt),
355 path.moveto_pt(x_pt-0.5*size_pt, y_pt+0.5*size_pt),
356 path.lineto_pt(x_pt+0.5*size_pt, y_pt-0.5*size_pt)), attrs)
358 def _plussymbol(c, x_pt, y_pt, size_pt, attrs):
359 c.draw(path.path(path.moveto_pt(x_pt-0.707106781*size_pt, y_pt),
360 path.lineto_pt(x_pt+0.707106781*size_pt, y_pt),
361 path.moveto_pt(x_pt, y_pt-0.707106781*size_pt),
362 path.lineto_pt(x_pt, y_pt+0.707106781*size_pt)), attrs)
364 def _squaresymbol(c, x_pt, y_pt, size_pt, attrs):
365 c.draw(path.path(path.moveto_pt(x_pt-0.5*size_pt, y_pt-0.5*size_pt),
366 path.lineto_pt(x_pt+0.5*size_pt, y_pt-0.5*size_pt),
367 path.lineto_pt(x_pt+0.5*size_pt, y_pt+0.5*size_pt),
368 path.lineto_pt(x_pt-0.5*size_pt, y_pt+0.5*size_pt),
369 path.closepath()), attrs)
371 def _trianglesymbol(c, x_pt, y_pt, size_pt, attrs):
372 c.draw(path.path(path.moveto_pt(x_pt-0.759835685*size_pt, y_pt-0.438691337*size_pt),
373 path.lineto_pt(x_pt+0.759835685*size_pt, y_pt-0.438691337*size_pt),
374 path.lineto_pt(x_pt, y_pt+0.877382675*size_pt),
375 path.closepath()), attrs)
377 def _circlesymbol(c, x_pt, y_pt, size_pt, attrs):
378 c.draw(path.path(path.arc_pt(x_pt, y_pt, 0.564189583*size_pt, 0, 360),
379 path.closepath()), attrs)
381 def _diamondsymbol(c, x_pt, y_pt, size_pt, attrs):
382 c.draw(path.path(path.moveto_pt(x_pt-0.537284965*size_pt, y_pt),
383 path.lineto_pt(x_pt, y_pt-0.930604859*size_pt),
384 path.lineto_pt(x_pt+0.537284965*size_pt, y_pt),
385 path.lineto_pt(x_pt, y_pt+0.930604859*size_pt),
386 path.closepath()), attrs)
389 class _styleneedingpointpos(_style):
391 needsdata = ["vposmissing"]
393 def columnnames(self, privatedata, sharedata, graph, columnnames):
394 if len(sharedata.vposmissing):
395 raise ValueError("incomplete position information")
396 return []
399 class symbol(_styleneedingpointpos):
401 needsdata = ["vpos", "vposmissing", "vposvalid"]
403 # "inject" the predefinied symbols into the class:
405 # Note, that statements like cross = _crosssymbol are
406 # invalid, since the would lead to unbound methods, but
407 # a single entry changeable list does the trick.
409 # Once we require Python 2.2+ we should use staticmethods
410 # to implement the default symbols inplace.
412 cross = attr.changelist([_crosssymbol])
413 plus = attr.changelist([_plussymbol])
414 square = attr.changelist([_squaresymbol])
415 triangle = attr.changelist([_trianglesymbol])
416 circle = attr.changelist([_circlesymbol])
417 diamond = attr.changelist([_diamondsymbol])
419 changecross = attr.changelist([_crosssymbol, _plussymbol, _squaresymbol, _trianglesymbol, _circlesymbol, _diamondsymbol])
420 changeplus = attr.changelist([_plussymbol, _squaresymbol, _trianglesymbol, _circlesymbol, _diamondsymbol, _crosssymbol])
421 changesquare = attr.changelist([_squaresymbol, _trianglesymbol, _circlesymbol, _diamondsymbol, _crosssymbol, _plussymbol])
422 changetriangle = attr.changelist([_trianglesymbol, _circlesymbol, _diamondsymbol, _crosssymbol, _plussymbol, _squaresymbol])
423 changecircle = attr.changelist([_circlesymbol, _diamondsymbol, _crosssymbol, _plussymbol, _squaresymbol, _trianglesymbol])
424 changediamond = attr.changelist([_diamondsymbol, _crosssymbol, _plussymbol, _squaresymbol, _trianglesymbol, _circlesymbol])
425 changesquaretwice = attr.changelist([_squaresymbol, _squaresymbol, _trianglesymbol, _trianglesymbol, _circlesymbol, _circlesymbol, _diamondsymbol, _diamondsymbol])
426 changetriangletwice = attr.changelist([_trianglesymbol, _trianglesymbol, _circlesymbol, _circlesymbol, _diamondsymbol, _diamondsymbol, _squaresymbol, _squaresymbol])
427 changecircletwice = attr.changelist([_circlesymbol, _circlesymbol, _diamondsymbol, _diamondsymbol, _squaresymbol, _squaresymbol, _trianglesymbol, _trianglesymbol])
428 changediamondtwice = attr.changelist([_diamondsymbol, _diamondsymbol, _squaresymbol, _squaresymbol, _trianglesymbol, _trianglesymbol, _circlesymbol, _circlesymbol])
430 changestrokedfilled = attr.changelist([deco.stroked, deco.filled])
431 changefilledstroked = attr.changelist([deco.filled, deco.stroked])
433 defaultsymbolattrs = [deco.stroked]
435 def __init__(self, symbol=changecross, size=0.2*unit.v_cm, symbolattrs=[]):
436 self.symbol = symbol
437 self.size = size
438 self.symbolattrs = symbolattrs
440 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
441 privatedata.symbol = attr.selectattr(self.symbol, selectindex, selecttotal)
442 privatedata.size_pt = unit.topt(attr.selectattr(self.size, selectindex, selecttotal))
443 if self.symbolattrs is not None:
444 privatedata.symbolattrs = attr.selectattrs(self.defaultsymbolattrs + self.symbolattrs, selectindex, selecttotal)
445 else:
446 privatedata.symbolattrs = None
448 def initdrawpoints(self, privatedata, sharedata, graph):
449 privatedata.symbolcanvas = canvas.canvas()
451 def drawpoint(self, privatedata, sharedata, graph, point):
452 if sharedata.vposvalid and privatedata.symbolattrs is not None:
453 x_pt, y_pt = graph.vpos_pt(*sharedata.vpos)
454 privatedata.symbol(privatedata.symbolcanvas, x_pt, y_pt, privatedata.size_pt, privatedata.symbolattrs)
456 def donedrawpoints(self, privatedata, sharedata, graph):
457 graph.insert(privatedata.symbolcanvas)
459 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
460 if privatedata.symbolattrs is not None:
461 privatedata.symbol(graph, x_pt+0.5*width_pt, y_pt+0.5*height_pt, privatedata.size_pt, privatedata.symbolattrs)
464 class _line(_styleneedingpointpos):
466 # this style is not a complete style, but it provides the basic functionality to
467 # create a line, which is cut at the graph boundaries (or at otherwise invalid points)
469 def initpointstopath(self, privatedata):
470 privatedata.path = path.path()
471 privatedata.linebasepoints = []
472 privatedata.lastvpos = None
474 def addpointstopath(self, privatedata):
475 # add baselinepoints to privatedata.path
476 if len(privatedata.linebasepoints) > 1:
477 privatedata.path.append(path.moveto_pt(*privatedata.linebasepoints[0]))
478 if len(privatedata.linebasepoints) > 2:
479 privatedata.path.append(path.multilineto_pt(privatedata.linebasepoints[1:]))
480 else:
481 privatedata.path.append(path.lineto_pt(*privatedata.linebasepoints[1]))
482 privatedata.linebasepoints = []
484 def addpoint(self, privatedata, graphvpos_pt, vposavailable, vposvalid, vpos):
485 # append linebasepoints
486 if vposavailable:
487 if len(privatedata.linebasepoints):
488 # the last point was inside the graph
489 if vposvalid: # shortcut for the common case
490 privatedata.linebasepoints.append(graphvpos_pt(*vpos))
491 else:
492 # cut end
493 cut = 1
494 for vstart, vend in zip(privatedata.lastvpos, vpos):
495 newcut = None
496 if vend > 1:
497 # 1 = vstart + (vend - vstart) * cut
498 try:
499 newcut = (1 - vstart)/(vend - vstart)
500 except (ArithmeticError, TypeError):
501 break
502 if vend < 0:
503 # 0 = vstart + (vend - vstart) * cut
504 try:
505 newcut = - vstart/(vend - vstart)
506 except (ArithmeticError, TypeError):
507 break
508 if newcut is not None and newcut < cut:
509 cut = newcut
510 else:
511 cutvpos = []
512 for vstart, vend in zip(privatedata.lastvpos, vpos):
513 cutvpos.append(vstart + (vend - vstart) * cut)
514 privatedata.linebasepoints.append(graphvpos_pt(*cutvpos))
515 self.addpointstopath(privatedata)
516 else:
517 # the last point was outside the graph
518 if privatedata.lastvpos is not None:
519 if vposvalid:
520 # cut beginning
521 cut = 0
522 for vstart, vend in zip(privatedata.lastvpos, vpos):
523 newcut = None
524 if vstart > 1:
525 # 1 = vstart + (vend - vstart) * cut
526 try:
527 newcut = (1 - vstart)/(vend - vstart)
528 except (ArithmeticError, TypeError):
529 break
530 if vstart < 0:
531 # 0 = vstart + (vend - vstart) * cut
532 try:
533 newcut = - vstart/(vend - vstart)
534 except (ArithmeticError, TypeError):
535 break
536 if newcut is not None and newcut > cut:
537 cut = newcut
538 else:
539 cutvpos = []
540 for vstart, vend in zip(privatedata.lastvpos, vpos):
541 cutvpos.append(vstart + (vend - vstart) * cut)
542 privatedata.linebasepoints.append(graphvpos_pt(*cutvpos))
543 privatedata.linebasepoints.append(graphvpos_pt(*vpos))
544 else:
545 # sometimes cut beginning and end
546 cutfrom = 0
547 cutto = 1
548 for vstart, vend in zip(privatedata.lastvpos, vpos):
549 newcutfrom = None
550 if vstart > 1:
551 if vend > 1:
552 break
553 # 1 = vstart + (vend - vstart) * cutfrom
554 try:
555 newcutfrom = (1 - vstart)/(vend - vstart)
556 except (ArithmeticError, TypeError):
557 break
558 if vstart < 0:
559 if vend < 0:
560 break
561 # 0 = vstart + (vend - vstart) * cutfrom
562 try:
563 newcutfrom = - vstart/(vend - vstart)
564 except (ArithmeticError, TypeError):
565 break
566 if newcutfrom is not None and newcutfrom > cutfrom:
567 cutfrom = newcutfrom
568 newcutto = None
569 if vend > 1:
570 # 1 = vstart + (vend - vstart) * cutto
571 try:
572 newcutto = (1 - vstart)/(vend - vstart)
573 except (ArithmeticError, TypeError):
574 break
575 if vend < 0:
576 # 0 = vstart + (vend - vstart) * cutto
577 try:
578 newcutto = - vstart/(vend - vstart)
579 except (ArithmeticError, TypeError):
580 break
581 if newcutto is not None and newcutto < cutto:
582 cutto = newcutto
583 else:
584 if cutfrom < cutto:
585 cutfromvpos = []
586 cuttovpos = []
587 for vstart, vend in zip(privatedata.lastvpos, vpos):
588 cutfromvpos.append(vstart + (vend - vstart) * cutfrom)
589 cuttovpos.append(vstart + (vend - vstart) * cutto)
590 privatedata.linebasepoints.append(graphvpos_pt(*cutfromvpos))
591 privatedata.linebasepoints.append(graphvpos_pt(*cuttovpos))
592 self.addpointstopath(privatedata)
593 privatedata.lastvpos = vpos[:]
594 else:
595 if len(privatedata.linebasepoints) > 1:
596 self.addpointstopath(privatedata)
597 privatedata.lastvpos = None
599 def addinvalid(self, privatedata):
600 if len(privatedata.linebasepoints) > 1:
601 self.addpointstopath(privatedata)
602 privatedata.lastvpos = None
604 def donepointstopath(self, privatedata):
605 if len(privatedata.linebasepoints) > 1:
606 self.addpointstopath(privatedata)
607 return privatedata.path
610 class line(_line):
612 needsdata = ["vpos", "vposmissing", "vposavailable", "vposvalid"]
614 changelinestyle = attr.changelist([style.linestyle.solid,
615 style.linestyle.dashed,
616 style.linestyle.dotted,
617 style.linestyle.dashdotted])
619 defaultlineattrs = [changelinestyle]
621 def __init__(self, lineattrs=[]):
622 self.lineattrs = lineattrs
624 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
625 if self.lineattrs is not None:
626 privatedata.lineattrs = attr.selectattrs(self.defaultlineattrs + self.lineattrs, selectindex, selecttotal)
627 else:
628 privatedata.lineattrs = None
630 def initdrawpoints(self, privatedata, sharedata, graph):
631 self.initpointstopath(privatedata)
633 def drawpoint(self, privatedata, sharedata, graph, point):
634 self.addpoint(privatedata, graph.vpos_pt, sharedata.vposavailable, sharedata.vposvalid, sharedata.vpos)
636 def donedrawpoints(self, privatedata, sharedata, graph):
637 path = self.donepointstopath(privatedata)
638 if privatedata.lineattrs is not None and len(path):
639 graph.stroke(path, privatedata.lineattrs)
641 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
642 if privatedata.lineattrs is not None:
643 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)
646 class impulses(_styleneedingpointpos):
648 needsdata = ["vpos", "vposmissing", "vposavailable", "vposvalid", "poscolumnnames"]
650 defaultlineattrs = [line.changelinestyle]
651 defaultfrompathattrs = []
653 def __init__(self, lineattrs=[], fromvalue=0, frompathattrs=[], valueaxisindex=1):
654 self.lineattrs = lineattrs
655 self.fromvalue = fromvalue
656 self.frompathattrs = frompathattrs
657 self.valueaxisindex = valueaxisindex
659 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
660 privatedata.insertfrompath = selectindex == 0
661 if self.lineattrs is not None:
662 privatedata.lineattrs = attr.selectattrs(self.defaultlineattrs + self.lineattrs, selectindex, selecttotal)
663 else:
664 privatedata.lineattrs = None
666 def adjustaxis(self, privatedata, sharedata, graph, columnname, data):
667 if self.fromvalue is not None:
668 try:
669 i = sharedata.poscolumnnames.index(columnname)
670 except ValueError:
671 pass
672 else:
673 if i == self.valueaxisindex:
674 graph.axes[sharedata.poscolumnnames[i]].adjustaxis([self.fromvalue])
676 def initdrawpoints(self, privatedata, sharedata, graph):
677 privatedata.impulsescanvas = canvas.canvas()
678 if self.fromvalue is not None:
679 valueaxisname = sharedata.poscolumnnames[self.valueaxisindex]
680 privatedata.vfromvalue = graph.axes[valueaxisname].convert(self.fromvalue)
681 privatedata.vfromvaluecut = 0
682 if privatedata.vfromvalue < 0:
683 privatedata.vfromvalue = 0
684 if privatedata.vfromvalue > 1:
685 privatedata.vfromvalue = 1
686 if self.frompathattrs is not None and privatedata.insertfrompath:
687 graph.stroke(graph.axes[valueaxisname].vgridpath(privatedata.vfromvalue),
688 self.defaultfrompathattrs + self.frompathattrs)
689 else:
690 privatedata.vfromvalue = 0
692 def drawpoint(self, privatedata, sharedata, graph, point):
693 if sharedata.vposvalid and privatedata.lineattrs is not None:
694 vpos = sharedata.vpos[:]
695 vpos[self.valueaxisindex] = privatedata.vfromvalue
696 privatedata.impulsescanvas.stroke(graph.vgeodesic(*(vpos + sharedata.vpos)), privatedata.lineattrs)
698 def donedrawpoints(self, privatedata, sharedata, graph):
699 graph.insert(privatedata.impulsescanvas)
701 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
702 if privatedata.lineattrs is not None:
703 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)
706 class errorbar(_style):
708 needsdata = ["vpos", "vposmissing", "vposavailable", "vposvalid", "vrange", "vrangeminmissing", "vrangemaxmissing"]
710 defaulterrorbarattrs = []
712 def __init__(self, size=0.1*unit.v_cm,
713 errorbarattrs=[],
714 epsilon=1e-10):
715 self.size = size
716 self.errorbarattrs = errorbarattrs
717 self.epsilon = epsilon
719 def columnnames(self, privatedata, sharedata, graph, columnnames):
720 for i in sharedata.vposmissing:
721 if i in sharedata.vrangeminmissing and i in sharedata.vrangemaxmissing:
722 raise ValueError("position and range for a graph dimension missing")
723 return []
725 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
726 privatedata.errorsize_pt = unit.topt(attr.selectattr(self.size, selectindex, selecttotal))
727 privatedata.errorbarattrs = attr.selectattrs(self.defaulterrorbarattrs + self.errorbarattrs, selectindex, selecttotal)
729 def initdrawpoints(self, privatedata, sharedata, graph):
730 if privatedata.errorbarattrs is not None:
731 privatedata.errorbarcanvas = canvas.canvas(privatedata.errorbarattrs)
732 privatedata.dimensionlist = list(xrange(len(sharedata.vpos)))
734 def drawpoint(self, privatedata, sharedata, graph, point):
735 if privatedata.errorbarattrs is not None:
736 for i in privatedata.dimensionlist:
737 for j in privatedata.dimensionlist:
738 if (i != j and
739 (sharedata.vpos[j] is None or
740 sharedata.vpos[j] < -self.epsilon or
741 sharedata.vpos[j] > 1+self.epsilon)):
742 break
743 else:
744 if ((sharedata.vrange[i][0] is None and sharedata.vpos[i] is None) or
745 (sharedata.vrange[i][1] is None and sharedata.vpos[i] is None) or
746 (sharedata.vrange[i][0] is None and sharedata.vrange[i][1] is None)):
747 continue
748 vminpos = sharedata.vpos[:]
749 if sharedata.vrange[i][0] is not None:
750 vminpos[i] = sharedata.vrange[i][0]
751 mincap = 1
752 else:
753 mincap = 0
754 if vminpos[i] > 1+self.epsilon:
755 continue
756 if vminpos[i] < -self.epsilon:
757 vminpos[i] = 0
758 mincap = 0
759 vmaxpos = sharedata.vpos[:]
760 if sharedata.vrange[i][1] is not None:
761 vmaxpos[i] = sharedata.vrange[i][1]
762 maxcap = 1
763 else:
764 maxcap = 0
765 if vmaxpos[i] < -self.epsilon:
766 continue
767 if vmaxpos[i] > 1+self.epsilon:
768 vmaxpos[i] = 1
769 maxcap = 0
770 privatedata.errorbarcanvas.stroke(graph.vgeodesic(*(vminpos + vmaxpos)))
771 for j in privatedata.dimensionlist:
772 if i != j:
773 if mincap:
774 privatedata.errorbarcanvas.stroke(graph.vcap_pt(j, privatedata.errorsize_pt, *vminpos))
775 if maxcap:
776 privatedata.errorbarcanvas.stroke(graph.vcap_pt(j, privatedata.errorsize_pt, *vmaxpos))
778 def donedrawpoints(self, privatedata, sharedata, graph):
779 if privatedata.errorbarattrs is not None:
780 graph.insert(privatedata.errorbarcanvas)
783 class text(_styleneedingpointpos):
785 needsdata = ["vpos", "vposmissing", "vposvalid"]
787 defaulttextattrs = [textmodule.halign.center, textmodule.vshift.mathaxis]
789 def __init__(self, textname="text", dxname=None, dyname=None,
790 dxunit=0.3*unit.v_cm, dyunit=0.3*unit.v_cm,
791 textdx=0*unit.v_cm, textdy=0.3*unit.v_cm, textattrs=[]):
792 self.textname = textname
793 self.dxname = dxname
794 self.dyname = dyname
795 self.dxunit = dxunit
796 self.dyunit = dyunit
797 self.textdx = textdx
798 self.textdy = textdy
799 self.textattrs = textattrs
801 def columnnames(self, privatedata, sharedata, graph, columnnames):
802 if self.textname not in columnnames:
803 raise ValueError("column '%s' missing" % self.textname)
804 names = [self.textname]
805 if self.dxname is not None:
806 if self.dxname not in columnnames:
807 raise ValueError("column '%s' missing" % self.dxname)
808 names.append(self.dxname)
809 if self.dyname is not None:
810 if self.dyname not in columnnames:
811 raise ValueError("column '%s' missing" % self.dyname)
812 names.append(self.dyname)
813 return names + _styleneedingpointpos.columnnames(self, privatedata, sharedata, graph, columnnames)
815 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
816 if self.textattrs is not None:
817 privatedata.textattrs = attr.selectattrs(self.defaulttextattrs + self.textattrs, selectindex, selecttotal)
818 else:
819 privatedata.textattrs = None
821 def initdrawpoints(self, privatedata, sharedata, grap):
822 if self.dxname is None:
823 privatedata.textdx_pt = unit.topt(self.textdx)
824 else:
825 privatedata.dxunit_pt = unit.topt(self.dxunit)
826 if self.dyname is None:
827 privatedata.textdy_pt = unit.topt(self.textdy)
828 else:
829 privatedata.dyunit_pt = unit.topt(self.dyunit)
831 def drawpoint(self, privatedata, sharedata, graph, point):
832 if privatedata.textattrs is not None and sharedata.vposvalid:
833 x_pt, y_pt = graph.vpos_pt(*sharedata.vpos)
834 try:
835 text = str(point[self.textname])
836 except:
837 pass
838 else:
839 if self.dxname is None:
840 dx_pt = privatedata.textdx_pt
841 else:
842 dx_pt = float(point[self.dxname]) * privatedata.dxunit_pt
843 if self.dyname is None:
844 dy_pt = privatedata.textdy_pt
845 else:
846 dy_pt = float(point[self.dyname]) * privatedata.dyunit_pt
847 graph.text_pt(x_pt + dx_pt, y_pt + dy_pt, text, privatedata.textattrs)
849 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
850 raise RuntimeError("Style currently doesn't provide a graph key")
853 class arrow(_styleneedingpointpos):
855 needsdata = ["vpos", "vposmissing", "vposvalid"]
857 defaultlineattrs = []
858 defaultarrowattrs = []
860 def __init__(self, linelength=0.25*unit.v_cm, arrowsize=0.15*unit.v_cm, lineattrs=[], arrowattrs=[], arrowpos=0.5, epsilon=1e-5, decorator=deco.earrow):
861 self.linelength = linelength
862 self.arrowsize = arrowsize
863 self.lineattrs = lineattrs
864 self.arrowattrs = arrowattrs
865 self.arrowpos = arrowpos
866 self.epsilon = epsilon
867 self.decorator = decorator
869 def columnnames(self, privatedata, sharedata, graph, columnnames):
870 if len(graph.axesnames) != 2:
871 raise ValueError("arrow style restricted on two-dimensional graphs")
872 if "size" not in columnnames:
873 raise ValueError("size missing")
874 if "angle" not in columnnames:
875 raise ValueError("angle missing")
876 return ["size", "angle"] + _styleneedingpointpos.columnnames(self, privatedata, sharedata, graph, columnnames)
878 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
879 if self.lineattrs is not None:
880 privatedata.lineattrs = attr.selectattrs(self.defaultlineattrs + self.lineattrs, selectindex, selecttotal)
881 else:
882 privatedata.lineattrs = None
883 if self.arrowattrs is not None:
884 privatedata.arrowattrs = attr.selectattrs(self.defaultarrowattrs + self.arrowattrs, selectindex, selecttotal)
885 else:
886 privatedata.arrowattrs = None
888 def initdrawpoints(self, privatedata, sharedata, graph):
889 privatedata.arrowcanvas = canvas.canvas()
891 def drawpoint(self, privatedata, sharedata, graph, point):
892 if privatedata.lineattrs is not None and privatedata.arrowattrs is not None and sharedata.vposvalid:
893 linelength_pt = unit.topt(self.linelength)
894 x_pt, y_pt = graph.vpos_pt(*sharedata.vpos)
895 try:
896 angle = point["angle"] + 0.0
897 size = point["size"] + 0.0
898 except:
899 pass
900 else:
901 if point["size"] > self.epsilon:
902 dx = math.cos(angle*math.pi/180)
903 dy = math.sin(angle*math.pi/180)
904 x1 = x_pt-self.arrowpos*dx*linelength_pt*size
905 y1 = y_pt-self.arrowpos*dy*linelength_pt*size
906 x2 = x_pt+(1-self.arrowpos)*dx*linelength_pt*size
907 y2 = y_pt+(1-self.arrowpos)*dy*linelength_pt*size
908 if self.decorator:
909 privatedata.arrowcanvas.stroke(path.line_pt(x1, y1, x2, y2),
910 privatedata.lineattrs+[self.decorator(privatedata.arrowattrs, size=self.arrowsize*size)])
911 else:
912 privatedata.arrowcanvas.stroke(path.line_pt(x1, y1, x2, y2), privatedata.lineattrs)
914 def donedrawpoints(self, privatedata, sharedata, graph):
915 graph.insert(privatedata.arrowcanvas)
917 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
918 raise RuntimeError("Style currently doesn't provide a graph key")
921 class rect(_style):
923 needsdata = ["vrange", "vrangeminmissing", "vrangemaxmissing"]
925 def __init__(self, gradient=color.gradient.Grey):
926 self.gradient = gradient
928 def columnnames(self, privatedata, sharedata, graph, columnnames):
929 if len(graph.axesnames) != 2:
930 raise TypeError("arrow style restricted on two-dimensional graphs")
931 if "color" not in columnnames:
932 raise ValueError("color missing")
933 if len(sharedata.vrangeminmissing) + len(sharedata.vrangemaxmissing):
934 raise ValueError("incomplete range")
935 return ["color"]
937 def initdrawpoints(self, privatedata, sharedata, graph):
938 privatedata.rectcanvas = graph.insert(canvas.canvas())
940 def drawpoint(self, privatedata, sharedata, graph, point):
941 xvmin = sharedata.vrange[0][0]
942 xvmax = sharedata.vrange[0][1]
943 yvmin = sharedata.vrange[1][0]
944 yvmax = sharedata.vrange[1][1]
945 if (xvmin is not None and xvmin < 1 and
946 xvmax is not None and xvmax > 0 and
947 yvmin is not None and yvmin < 1 and
948 yvmax is not None and yvmax > 0):
949 if xvmin < 0:
950 xvmin = 0
951 elif xvmax > 1:
952 xvmax = 1
953 if yvmin < 0:
954 yvmin = 0
955 elif yvmax > 1:
956 yvmax = 1
957 p = graph.vgeodesic(xvmin, yvmin, xvmax, yvmin)
958 p.append(graph.vgeodesic_el(xvmax, yvmin, xvmax, yvmax))
959 p.append(graph.vgeodesic_el(xvmax, yvmax, xvmin, yvmax))
960 p.append(graph.vgeodesic_el(xvmin, yvmax, xvmin, yvmin))
961 p.append(path.closepath())
962 privatedata.rectcanvas.fill(p, [self.gradient.getcolor(point["color"])])
964 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
965 raise RuntimeError("Style currently doesn't provide a graph key")
968 class histogram(_style):
970 needsdata = ["vpos", "vposmissing", "vrange", "vrangeminmissing", "vrangemaxmissing"]
972 defaultlineattrs = [deco.stroked]
973 defaultfrompathattrs = []
975 def __init__(self, lineattrs=[], steps=0, fromvalue=0, frompathattrs=[], fillable=0, rectkey=0,
976 autohistogramaxisindex=0, autohistogrampointpos=0.5, epsilon=1e-10):
977 self.lineattrs = lineattrs
978 self.steps = steps
979 self.fromvalue = fromvalue
980 self.frompathattrs = frompathattrs
981 self.fillable = fillable # TODO: fillable paths might not properly be closed by straight lines on curved graph geometries
982 self.rectkey = rectkey
983 self.autohistogramaxisindex = autohistogramaxisindex
984 self.autohistogrampointpos = autohistogrampointpos
985 self.epsilon = epsilon
987 def columnnames(self, privatedata, sharedata, graph, columnnames):
988 if len(graph.axesnames) != 2:
989 raise TypeError("histogram style restricted on two-dimensional graphs")
990 privatedata.rangeaxisindex = None
991 for i in builtinrange(len(graph.axesnames)):
992 if i in sharedata.vrangeminmissing or i in sharedata.vrangemaxmissing:
993 if i in sharedata.vposmissing:
994 raise ValueError("pos and range missing")
995 else:
996 if privatedata.rangeaxisindex is not None:
997 raise ValueError("multiple ranges")
998 privatedata.rangeaxisindex = i
999 if privatedata.rangeaxisindex is None:
1000 privatedata.rangeaxisindex = self.autohistogramaxisindex
1001 privatedata.autohistogram = 1
1002 else:
1003 privatedata.autohistogram = 0
1004 return []
1006 def adjustaxis(self, privatedata, sharedata, graph, columnname, data):
1007 if privatedata.autohistogram and columnname == sharedata.poscolumnnames[privatedata.rangeaxisindex]:
1008 if len(data) == 1:
1009 raise ValueError("several data points needed for automatic histogram width calculation")
1010 if len(data) > 1:
1011 delta = data[1] - data[0]
1012 min = data[0] - self.autohistogrampointpos * delta
1013 max = data[-1] + (1-self.autohistogrampointpos) * delta
1014 graph.axes[columnname].adjustaxis([min, max])
1015 elif self.fromvalue is not None and columnname == sharedata.poscolumnnames[1-privatedata.rangeaxisindex]:
1016 graph.axes[columnname].adjustaxis([self.fromvalue])
1018 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1019 privatedata.insertfrompath = selectindex == 0
1020 if self.lineattrs is not None:
1021 privatedata.lineattrs = attr.selectattrs(self.defaultlineattrs + self.lineattrs, selectindex, selecttotal)
1022 else:
1023 privatedata.lineattrs = None
1025 def vmoveto(self, privatedata, sharedata, graph, vpos, vvalue):
1026 if -self.epsilon < vpos < 1+self.epsilon and -self.epsilon < vvalue < 1+self.epsilon:
1027 if privatedata.rangeaxisindex:
1028 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue, vpos)))
1029 else:
1030 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vpos, vvalue)))
1032 def vposline(self, privatedata, sharedata, graph, vpos, vvalue1, vvalue2):
1033 if -self.epsilon < vpos < 1+self.epsilon:
1034 vvalue1cut = 0
1035 if vvalue1 < 0:
1036 vvalue1 = 0
1037 vvalue1cut = -1
1038 elif vvalue1 > 1:
1039 vvalue1 = 1
1040 vvalue1cut = 1
1041 vvalue2cut = 0
1042 if vvalue2 < 0:
1043 vvalue2 = 0
1044 vvalue2cut = -1
1045 elif vvalue2 > 1:
1046 vvalue2 = 1
1047 vvalue2cut = 1
1048 if abs(vvalue1cut + vvalue2cut) <= 1:
1049 if vvalue1cut and not self.fillable:
1050 if privatedata.rangeaxisindex:
1051 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue1, vpos)))
1052 else:
1053 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vpos, vvalue1)))
1054 if privatedata.rangeaxisindex:
1055 privatedata.path.append(graph.vgeodesic_el(vvalue1, vpos, vvalue2, vpos))
1056 else:
1057 privatedata.path.append(graph.vgeodesic_el(vpos, vvalue1, vpos, vvalue2))
1059 def vvalueline(self, privatedata, sharedata, graph, vvalue, vpos1, vpos2):
1060 if self.fillable:
1061 if vvalue < -self.epsilon:
1062 vvalue = 0
1063 warnings.warn("cut at graph boundary adds artificial lines to fillable step histogram path")
1064 if vvalue > 1+self.epsilon:
1065 vvalue = 1
1066 warnings.warn("cut at graph boundary adds artificial lines to fillable step histogram path")
1067 if self.fillable or (-self.epsilon < vvalue < 1+self.epsilon):
1068 vpos1cut = 0
1069 if vpos1 < 0:
1070 vpos1 = 0
1071 vpos1cut = -1
1072 elif vpos1 > 1:
1073 vpos1 = 1
1074 vpos1cut = 1
1075 vpos2cut = 0
1076 if vpos2 < 0:
1077 vpos2 = 0
1078 vpos2cut = -1
1079 elif vpos2 > 1:
1080 vpos2 = 1
1081 vpos2cut = 1
1082 if abs(vpos1cut + vpos2cut) <= 1:
1083 if vpos1cut:
1084 if self.fillable:
1085 if privatedata.rangeaxisindex:
1086 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(privatedata.vfromvalue, vpos1)))
1087 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vpos1, vvalue, vpos1))
1088 else:
1089 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vpos1, privatedata.vfromvalue)))
1090 privatedata.path.append(graph.vgeodesic_el(vpos1, privatedata.vfromvalue, vpos1, vvalue))
1091 else:
1092 if privatedata.rangeaxisindex:
1093 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue, vpos1)))
1094 else:
1095 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vpos1, vvalue)))
1096 if privatedata.rangeaxisindex:
1097 privatedata.path.append(graph.vgeodesic_el(vvalue, vpos1, vvalue, vpos2))
1098 else:
1099 privatedata.path.append(graph.vgeodesic_el(vpos1, vvalue, vpos2, vvalue))
1100 if self.fillable and vpos2cut:
1101 warnings.warn("cut at graph boundary adds artificial lines to fillable step histogram path")
1102 if privatedata.rangeaxisindex:
1103 privatedata.path.append(graph.vgeodesic_el(vvalue, vpos2, privatedata.vfromvalue, vpos2))
1104 else:
1105 privatedata.path.append(graph.vgeodesic_el(vpos2, vvalue, vpos2, privatedata.vfromvalue))
1107 def drawvalue(self, privatedata, sharedata, graph, vmin, vmax, vvalue):
1108 currentvalid = vmin is not None and vmax is not None and vvalue is not None
1109 if self.fillable and not self.steps:
1110 if not currentvalid:
1111 return
1112 vmincut = 0
1113 if vmin < -self.epsilon:
1114 vmin = 0
1115 vmincut = -1
1116 elif vmin > 1+self.epsilon:
1117 vmin = 1
1118 vmincut = 1
1119 vmaxcut = 0
1120 if vmax < -self.epsilon:
1121 vmax = 0
1122 vmaxcut = -1
1123 if vmax > 1+self.epsilon:
1124 vmax = 1
1125 vmaxcut = 1
1126 vvaluecut = 0
1127 if vvalue < -self.epsilon:
1128 vvalue = 0
1129 vvaluecut = -1
1130 if vvalue > 1+self.epsilon:
1131 vvalue = 1
1132 vvaluecut = 1
1133 done = 0
1134 if abs(vmincut) + abs(vmaxcut) + abs(vvaluecut) + abs(privatedata.vfromvaluecut) > 1:
1135 if abs(vmincut + vmaxcut) > 1 or abs(vvaluecut+privatedata.vfromvaluecut) > 1:
1136 done = 1
1137 else:
1138 warnings.warn("multiple cuts at graph boundary add artificial lines to fillable rectangle histogram path")
1139 elif vmincut:
1140 done = 1
1141 if privatedata.rangeaxisindex:
1142 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(privatedata.vfromvalue, vmin)))
1143 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmin, privatedata.vfromvalue, vmax))
1144 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmax, vvalue, vmax))
1145 privatedata.path.append(graph.vgeodesic_el(vvalue, vmax, vvalue, vmin))
1146 else:
1147 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmin, privatedata.vfromvalue)))
1148 privatedata.path.append(graph.vgeodesic_el(vmin, privatedata.vfromvalue, vmax, privatedata.vfromvalue))
1149 privatedata.path.append(graph.vgeodesic_el(vmax, privatedata.vfromvalue, vmax, vvalue))
1150 privatedata.path.append(graph.vgeodesic_el(vmax, vvalue, vmin, vvalue))
1151 elif vmaxcut:
1152 done = 1
1153 if privatedata.rangeaxisindex:
1154 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue, vmax)))
1155 privatedata.path.append(graph.vgeodesic_el(vvalue, vmax, vvalue, vmin))
1156 privatedata.path.append(graph.vgeodesic_el(vvalue, vmin, privatedata.vfromvalue, vmin))
1157 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmin, privatedata.vfromvalue, vmax))
1158 else:
1159 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmax, vvalue)))
1160 privatedata.path.append(graph.vgeodesic_el(vmax, vvalue, vmin, vvalue))
1161 privatedata.path.append(graph.vgeodesic_el(vmin, vvalue, vmin, privatedata.vfromvalue))
1162 privatedata.path.append(graph.vgeodesic_el(vmin, privatedata.vfromvalue, vmax, privatedata.vfromvalue))
1163 elif privatedata.vfromvaluecut:
1164 done = 1
1165 if privatedata.rangeaxisindex:
1166 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(privatedata.vfromvalue, vmax)))
1167 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmax, vvalue, vmax))
1168 privatedata.path.append(graph.vgeodesic_el(vvalue, vmax, vvalue, vmin))
1169 privatedata.path.append(graph.vgeodesic_el(vvalue, vmin, privatedata.vfromvalue, vmin))
1170 else:
1171 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmax, privatedata.vfromvalue)))
1172 privatedata.path.append(graph.vgeodesic_el(vmax, privatedata.vfromvalue, vmax, vvalue))
1173 privatedata.path.append(graph.vgeodesic_el(vmax, vvalue, vmin, vvalue))
1174 privatedata.path.append(graph.vgeodesic_el(vmin, vvalue, vmin, privatedata.vfromvalue))
1175 elif vvaluecut:
1176 done = 1
1177 if privatedata.rangeaxisindex:
1178 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue, vmin)))
1179 privatedata.path.append(graph.vgeodesic_el(vvalue, vmin, privatedata.vfromvalue, vmin))
1180 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmin, privatedata.vfromvalue, vmax))
1181 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmax, vvalue, vmax))
1182 else:
1183 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmin, vvalue)))
1184 privatedata.path.append(graph.vgeodesic_el(vmin, vvalue, vmin, privatedata.vfromvalue))
1185 privatedata.path.append(graph.vgeodesic_el(vmin, privatedata.vfromvalue, vmax, privatedata.vfromvalue))
1186 privatedata.path.append(graph.vgeodesic_el(vmax, privatedata.vfromvalue, vmax, vvalue))
1187 if not done:
1188 if privatedata.rangeaxisindex:
1189 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(privatedata.vfromvalue, vmin)))
1190 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmin, privatedata.vfromvalue, vmax))
1191 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmax, vvalue, vmax))
1192 privatedata.path.append(graph.vgeodesic_el(vvalue, vmax, vvalue, vmin))
1193 privatedata.path.append(graph.vgeodesic_el(vvalue, vmin, privatedata.vfromvalue, vmin))
1194 privatedata.path.append(path.closepath())
1195 else:
1196 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmin, privatedata.vfromvalue)))
1197 privatedata.path.append(graph.vgeodesic_el(vmin, privatedata.vfromvalue, vmax, privatedata.vfromvalue))
1198 privatedata.path.append(graph.vgeodesic_el(vmax, privatedata.vfromvalue, vmax, vvalue))
1199 privatedata.path.append(graph.vgeodesic_el(vmax, vvalue, vmin, vvalue))
1200 privatedata.path.append(graph.vgeodesic_el(vmin, vvalue, vmin, privatedata.vfromvalue))
1201 privatedata.path.append(path.closepath())
1202 else:
1203 try:
1204 gap = abs(vmin - privatedata.lastvmax) > self.epsilon
1205 except (ArithmeticError, ValueError, TypeError):
1206 gap = 1
1207 if (privatedata.lastvvalue is not None and currentvalid and not gap and
1208 (self.steps or (privatedata.lastvvalue-privatedata.vfromvalue)*(vvalue-privatedata.vfromvalue) < 0)):
1209 self.vposline(privatedata, sharedata, graph,
1210 vmin, privatedata.lastvvalue, vvalue)
1211 else:
1212 if privatedata.lastvvalue is not None and currentvalid:
1213 currentbigger = abs(privatedata.lastvvalue-privatedata.vfromvalue) < abs(vvalue-privatedata.vfromvalue)
1214 if privatedata.lastvvalue is not None and (not currentvalid or not currentbigger or gap):
1215 self.vposline(privatedata, sharedata, graph,
1216 privatedata.lastvmax, privatedata.lastvvalue, privatedata.vfromvalue)
1217 if currentvalid:
1218 self.vmoveto(privatedata, sharedata, graph,
1219 vmin, vvalue)
1220 if currentvalid and (privatedata.lastvvalue is None or currentbigger or gap):
1221 self.vmoveto(privatedata, sharedata, graph,
1222 vmin, privatedata.vfromvalue)
1223 self.vposline(privatedata, sharedata, graph,
1224 vmin, privatedata.vfromvalue, vvalue)
1225 if currentvalid:
1226 self.vvalueline(privatedata, sharedata, graph,
1227 vvalue, vmin, vmax)
1228 privatedata.lastvvalue = vvalue
1229 privatedata.lastvmax = vmax
1230 else:
1231 privatedata.lastvvalue = privatedata.lastvmax = None
1233 def initdrawpoints(self, privatedata, sharedata, graph):
1234 privatedata.path = path.path()
1235 privatedata.lastvvalue = privatedata.lastvmax = None
1236 privatedata.vcurrentpoint = None
1237 privatedata.count = 0
1238 if self.fromvalue is not None:
1239 valueaxisname = sharedata.poscolumnnames[1-privatedata.rangeaxisindex]
1240 privatedata.vfromvalue = graph.axes[valueaxisname].convert(self.fromvalue)
1241 privatedata.vfromvaluecut = 0
1242 if privatedata.vfromvalue < 0:
1243 privatedata.vfromvalue = 0
1244 privatedata.vfromvaluecut = -1
1245 if privatedata.vfromvalue > 1:
1246 privatedata.vfromvalue = 1
1247 privatedata.vfromvaluecut = 1
1248 if self.frompathattrs is not None and privatedata.insertfrompath:
1249 graph.stroke(graph.axes[valueaxisname].vgridpath(privatedata.vfromvalue),
1250 self.defaultfrompathattrs + self.frompathattrs)
1251 else:
1252 privatedata.vfromvalue = 0
1254 def drawpoint(self, privatedata, sharedata, graph, point):
1255 if privatedata.autohistogram:
1256 # automatic range handling
1257 privatedata.count += 1
1258 if privatedata.count == 2:
1259 if privatedata.rangeaxisindex:
1260 privatedata.vrange = sharedata.vpos[1] - privatedata.lastvpos[1]
1261 self.drawvalue(privatedata, sharedata, graph,
1262 privatedata.lastvpos[1] - self.autohistogrampointpos*privatedata.vrange,
1263 privatedata.lastvpos[1] + (1-self.autohistogrampointpos)*privatedata.vrange,
1264 privatedata.lastvpos[0])
1265 else:
1266 privatedata.vrange = sharedata.vpos[0] - privatedata.lastvpos[0]
1267 self.drawvalue(privatedata, sharedata, graph,
1268 privatedata.lastvpos[0] - self.autohistogrampointpos*privatedata.vrange,
1269 privatedata.lastvpos[0] + (1-self.autohistogrampointpos)*privatedata.vrange,
1270 privatedata.lastvpos[1])
1271 elif privatedata.count > 2:
1272 if privatedata.rangeaxisindex:
1273 vrange = sharedata.vpos[1] - privatedata.lastvpos[1]
1274 else:
1275 vrange = sharedata.vpos[0] - privatedata.lastvpos[0]
1276 if abs(privatedata.vrange - vrange) > self.epsilon:
1277 raise ValueError("equal steps (in graph coordinates) needed for automatic width calculation")
1278 if privatedata.count > 1:
1279 if privatedata.rangeaxisindex:
1280 self.drawvalue(privatedata, sharedata, graph,
1281 sharedata.vpos[1] - self.autohistogrampointpos*privatedata.vrange,
1282 sharedata.vpos[1] + (1-self.autohistogrampointpos)*privatedata.vrange,
1283 sharedata.vpos[0])
1284 else:
1285 self.drawvalue(privatedata, sharedata, graph,
1286 sharedata.vpos[0] - self.autohistogrampointpos*privatedata.vrange,
1287 sharedata.vpos[0] + (1-self.autohistogrampointpos)*privatedata.vrange,
1288 sharedata.vpos[1])
1289 privatedata.lastvpos = sharedata.vpos[:]
1290 else:
1291 if privatedata.rangeaxisindex:
1292 self.drawvalue(privatedata, sharedata, graph,
1293 sharedata.vrange[1][0], sharedata.vrange[1][1], sharedata.vpos[0])
1294 else:
1295 self.drawvalue(privatedata, sharedata, graph,
1296 sharedata.vrange[0][0], sharedata.vrange[0][1], sharedata.vpos[1])
1298 def donedrawpoints(self, privatedata, sharedata, graph):
1299 self.drawvalue(privatedata, sharedata, graph, None, None, None)
1300 if privatedata.lineattrs is not None and len(privatedata.path):
1301 graph.draw(privatedata.path, privatedata.lineattrs)
1303 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
1304 if privatedata.lineattrs is not None:
1305 if self.rectkey:
1306 p = path.rect_pt(x_pt, y_pt, width_pt, height_pt)
1307 else:
1308 p = path.line_pt(x_pt, y_pt+0.5*height_pt, x_pt+width_pt, y_pt+0.5*height_pt)
1309 graph.draw(p, privatedata.lineattrs)
1312 class barpos(_style):
1314 providesdata = ["vpos", "vposmissing", "vposavailable", "vposvalid", "vbarrange", "barposcolumnnames", "barvalueindex", "lastbarvalue", "stackedbar", "stackedbardraw"]
1316 defaultfrompathattrs = []
1318 def __init__(self, fromvalue=None, frompathattrs=[], epsilon=1e-10):
1319 self.fromvalue = fromvalue
1320 self.frompathattrs = frompathattrs
1321 self.epsilon = epsilon
1323 def columnnames(self, privatedata, sharedata, graph, columnnames):
1324 sharedata.barposcolumnnames = []
1325 sharedata.barvalueindex = None
1326 for dimension, axisnames in enumerate(graph.axesnames):
1327 found = 0
1328 for axisname in axisnames:
1329 if axisname in columnnames:
1330 if sharedata.barvalueindex is not None:
1331 raise ValueError("multiple values")
1332 sharedata.barvalueindex = dimension
1333 sharedata.barposcolumnnames.append(axisname)
1334 found += 1
1335 if (axisname + "name") in columnnames:
1336 sharedata.barposcolumnnames.append(axisname + "name")
1337 found += 1
1338 if found > 1:
1339 raise ValueError("multiple names and value")
1340 if not found:
1341 raise ValueError("value/name missing")
1342 if sharedata.barvalueindex is None:
1343 raise ValueError("missing value")
1344 sharedata.vposmissing = []
1345 return sharedata.barposcolumnnames
1347 def addsubvalue(self, value, subvalue):
1348 try:
1349 value + ""
1350 except:
1351 try:
1352 return value[0], self.addsubvalue(value[1], subvalue)
1353 except:
1354 return value, subvalue
1355 else:
1356 return value, subvalue
1358 def adjustaxis(self, privatedata, sharedata, graph, columnname, data):
1359 try:
1360 i = sharedata.barposcolumnnames.index(columnname)
1361 except ValueError:
1362 pass
1363 else:
1364 if i == sharedata.barvalueindex:
1365 if self.fromvalue is not None:
1366 graph.axes[sharedata.barposcolumnnames[i]].adjustaxis([self.fromvalue])
1367 graph.axes[sharedata.barposcolumnnames[i]].adjustaxis(data)
1368 else:
1369 graph.axes[sharedata.barposcolumnnames[i][:-4]].adjustaxis([self.addsubvalue(x, 0) for x in data])
1370 graph.axes[sharedata.barposcolumnnames[i][:-4]].adjustaxis([self.addsubvalue(x, 1) for x in data])
1372 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1373 privatedata.insertfrompath = selectindex == 0
1375 def initdrawpoints(self, privatedata, sharedata, graph):
1376 sharedata.vpos = [None]*(len(sharedata.barposcolumnnames))
1377 sharedata.vbarrange = [[None for i in xrange(2)] for x in sharedata.barposcolumnnames]
1378 sharedata.stackedbar = sharedata.stackedbardraw = 0
1380 if self.fromvalue is not None:
1381 privatedata.vfromvalue = graph.axes[sharedata.barposcolumnnames[sharedata.barvalueindex]].convert(self.fromvalue)
1382 if privatedata.vfromvalue < 0:
1383 privatedata.vfromvalue = 0
1384 if privatedata.vfromvalue > 1:
1385 privatedata.vfromvalue = 1
1386 if self.frompathattrs is not None and privatedata.insertfrompath:
1387 graph.stroke(graph.axes[sharedata.barposcolumnnames[sharedata.barvalueindex]].vgridpath(privatedata.vfromvalue),
1388 self.defaultfrompathattrs + self.frompathattrs)
1389 else:
1390 privatedata.vfromvalue = 0
1392 def drawpoint(self, privatedata, sharedata, graph, point):
1393 sharedata.vposavailable = sharedata.vposvalid = 1
1394 for i, barname in enumerate(sharedata.barposcolumnnames):
1395 if i == sharedata.barvalueindex:
1396 sharedata.vbarrange[i][0] = privatedata.vfromvalue
1397 sharedata.lastbarvalue = point[barname]
1398 try:
1399 sharedata.vpos[i] = sharedata.vbarrange[i][1] = graph.axes[barname].convert(sharedata.lastbarvalue)
1400 except (ArithmeticError, ValueError, TypeError):
1401 sharedata.vpos[i] = sharedata.vbarrange[i][1] = None
1402 else:
1403 for j in xrange(2):
1404 try:
1405 sharedata.vbarrange[i][j] = graph.axes[barname[:-4]].convert(self.addsubvalue(point[barname], j))
1406 except (ArithmeticError, ValueError, TypeError):
1407 sharedata.vbarrange[i][j] = None
1408 try:
1409 sharedata.vpos[i] = 0.5*(sharedata.vbarrange[i][0]+sharedata.vbarrange[i][1])
1410 except (ArithmeticError, ValueError, TypeError):
1411 sharedata.vpos[i] = None
1412 if sharedata.vpos[i] is None:
1413 sharedata.vposavailable = sharedata.vposvalid = 0
1414 elif sharedata.vpos[i] < -self.epsilon or sharedata.vpos[i] > 1+self.epsilon:
1415 sharedata.vposvalid = 0
1417 registerdefaultprovider(barpos(), ["vbarrange", "barposcolumnnames", "barvalueindex", "lastbarvalue", "stackedbar", "stackedbardraw"])
1420 class stackedbarpos(_style):
1422 # provides no additional data, but needs some data (and modifies some of them)
1423 needsdata = ["vbarrange", "barposcolumnnames", "barvalueindex", "lastbarvalue", "stackedbar", "stackedbardraw"]
1425 def __init__(self, stackname, addontop=0, epsilon=1e-10):
1426 self.stackname = stackname
1427 self.epsilon = epsilon
1428 self.addontop = addontop
1430 def columnnames(self, privatedata, sharedata, graph, columnnames):
1431 if self.stackname not in columnnames:
1432 raise ValueError("column '%s' missing" % self.stackname)
1433 return [self.stackname]
1435 def adjustaxis(self, privatedata, sharedata, graph, columnname, data):
1436 if columnname == self.stackname:
1437 graph.axes[sharedata.barposcolumnnames[sharedata.barvalueindex]].adjustaxis(data)
1439 def initdrawpoints(self, privatedata, sharedata, graph):
1440 if sharedata.stackedbardraw: # do not count the start bar when not gets painted
1441 sharedata.stackedbar += 1
1443 def drawpoint(self, privatedata, sharedata, graph, point):
1444 sharedata.vbarrange[sharedata.barvalueindex][0] = sharedata.vbarrange[sharedata.barvalueindex][1]
1445 if self.addontop:
1446 try:
1447 sharedata.lastbarvalue += point[self.stackname]
1448 except (ArithmeticError, ValueError, TypeError):
1449 sharedata.lastbarvalue = None
1450 else:
1451 sharedata.lastbarvalue = point[self.stackname]
1452 try:
1453 sharedata.vpos[sharedata.barvalueindex] = sharedata.vbarrange[sharedata.barvalueindex][1] = graph.axes[sharedata.barposcolumnnames[sharedata.barvalueindex]].convert(sharedata.lastbarvalue)
1454 except (ArithmeticError, ValueError, TypeError):
1455 sharedata.vpos[sharedata.barvalueindex] = sharedata.vbarrange[sharedata.barvalueindex][1] = None
1456 sharedata.vposavailable = sharedata.vposvalid = 0
1457 else:
1458 if not sharedata.vposavailable or not sharedata.vposvalid:
1459 sharedata.vposavailable = sharedata.vposvalid = 1
1460 for v in sharedata.vpos:
1461 if v is None:
1462 sharedata.vposavailable = sharedata.vposvalid = 0
1463 break
1464 if v < -self.epsilon or v > 1+self.epsilon:
1465 sharedata.vposvalid = 0
1468 class bar(_style):
1470 needsdata = ["vbarrange"]
1472 defaultbarattrs = [color.gradient.Rainbow, deco.stroked([color.grey.black])]
1474 def __init__(self, barattrs=[], epsilon=1e-10, gradient=color.gradient.RedBlack):
1475 self.barattrs = barattrs
1476 self.epsilon = epsilon
1477 self.gradient = gradient
1479 def lighting(self, angle, zindex):
1480 return self.gradient.getcolor(0.7-0.4*abs(angle)+0.1*zindex)
1482 def columnnames(self, privatedata, sharedata, graph, columnnames):
1483 return []
1485 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1486 privatedata.barattrs = attr.selectattrs(self.defaultbarattrs + self.barattrs, selectindex, selecttotal)
1488 def initdrawpoints(self, privatedata, sharedata, graph):
1489 privatedata.barcanvas = graph.insert(canvas.canvas())
1490 sharedata.stackedbardraw = 1
1491 privatedata.stackedbar = sharedata.stackedbar
1492 privatedata.todraw = []
1494 def drawpointfill(self, privatedata, p):
1495 if p:
1496 privatedata.barcanvas.fill(p, privatedata.barattrs)
1498 def drawpoint(self, privatedata, sharedata, graph, point):
1499 vbarrange = []
1500 for vmin, vmax in sharedata.vbarrange:
1501 if vmin is None or vmax is None:
1502 self.drawpointfill(privatedata, None)
1503 return
1504 if vmin > vmax:
1505 vmin, vmax = vmax, vmin
1506 if vmin > 1 or vmax < 0:
1507 self.drawpointfill(privatedata, None)
1508 return
1509 if vmin < 0:
1510 vmin = 0
1511 if vmax > 1:
1512 vmax = 1
1513 vbarrange.append((vmin, vmax))
1514 if len(vbarrange) == 2:
1515 p = graph.vgeodesic(vbarrange[0][0], vbarrange[1][0], vbarrange[0][1], vbarrange[1][0])
1516 p.append(graph.vgeodesic_el(vbarrange[0][1], vbarrange[1][0], vbarrange[0][1], vbarrange[1][1]))
1517 p.append(graph.vgeodesic_el(vbarrange[0][1], vbarrange[1][1], vbarrange[0][0], vbarrange[1][1]))
1518 p.append(graph.vgeodesic_el(vbarrange[0][0], vbarrange[1][1], vbarrange[0][0], vbarrange[1][0]))
1519 p.append(path.closepath())
1520 self.drawpointfill(privatedata, p)
1521 elif len(vbarrange) == 3:
1522 planes = []
1523 if abs(vbarrange[0][0] - vbarrange[0][1]) > self.epsilon and abs(vbarrange[1][0] - vbarrange[1][1]):
1524 planes.append((vbarrange[0][0], vbarrange[1][0], vbarrange[2][0],
1525 vbarrange[0][1], vbarrange[1][0], vbarrange[2][0],
1526 vbarrange[0][1], vbarrange[1][1], vbarrange[2][0],
1527 vbarrange[0][0], vbarrange[1][1], vbarrange[2][0]))
1528 planes.append((vbarrange[0][0], vbarrange[1][0], vbarrange[2][1],
1529 vbarrange[0][0], vbarrange[1][1], vbarrange[2][1],
1530 vbarrange[0][1], vbarrange[1][1], vbarrange[2][1],
1531 vbarrange[0][1], vbarrange[1][0], vbarrange[2][1]))
1532 if abs(vbarrange[0][0] - vbarrange[0][1]) > self.epsilon and abs(vbarrange[2][0] - vbarrange[2][1]):
1533 planes.append((vbarrange[0][0], vbarrange[1][0], vbarrange[2][0],
1534 vbarrange[0][0], vbarrange[1][0], vbarrange[2][1],
1535 vbarrange[0][1], vbarrange[1][0], vbarrange[2][1],
1536 vbarrange[0][1], vbarrange[1][0], vbarrange[2][0]))
1537 planes.append((vbarrange[0][0], vbarrange[1][1], vbarrange[2][0],
1538 vbarrange[0][1], vbarrange[1][1], vbarrange[2][0],
1539 vbarrange[0][1], vbarrange[1][1], vbarrange[2][1],
1540 vbarrange[0][0], vbarrange[1][1], vbarrange[2][1]))
1541 if abs(vbarrange[1][0] - vbarrange[1][1]) > self.epsilon and abs(vbarrange[2][0] - vbarrange[2][1]):
1542 planes.append((vbarrange[0][0], vbarrange[1][0], vbarrange[2][0],
1543 vbarrange[0][0], vbarrange[1][1], vbarrange[2][0],
1544 vbarrange[0][0], vbarrange[1][1], vbarrange[2][1],
1545 vbarrange[0][0], vbarrange[1][0], vbarrange[2][1]))
1546 planes.append((vbarrange[0][1], vbarrange[1][0], vbarrange[2][0],
1547 vbarrange[0][1], vbarrange[1][0], vbarrange[2][1],
1548 vbarrange[0][1], vbarrange[1][1], vbarrange[2][1],
1549 vbarrange[0][1], vbarrange[1][1], vbarrange[2][0]))
1550 v = [0.5 * (vbarrange[0][0] + vbarrange[0][1]),
1551 0.5 * (vbarrange[1][0] + vbarrange[1][1]),
1552 0.5 * (vbarrange[2][0] + vbarrange[2][1])]
1553 v[sharedata.barvalueindex] = 0.5
1554 zindex = graph.vzindex(*v)
1555 for v11, v12, v13, v21, v22, v23, v31, v32, v33, v41, v42, v43 in planes:
1556 angle = graph.vangle(v11, v12, v13, v21, v22, v23, v41, v42, v43)
1557 if angle > 0:
1558 p = graph.vgeodesic(v11, v12, v13, v21, v22, v23)
1559 p.append(graph.vgeodesic_el(v21, v22, v23, v31, v32, v33))
1560 p.append(graph.vgeodesic_el(v31, v32, v33, v41, v42, v43))
1561 p.append(graph.vgeodesic_el(v41, v42, v43, v11, v12, v13))
1562 p.append(path.closepath())
1563 if self.gradient:
1564 privatedata.todraw.append((-zindex, p, privatedata.barattrs + [self.lighting(angle, zindex)]))
1565 else:
1566 privatedata.todraw.append((-zindex, p, privatedata.barattrs))
1567 else:
1568 raise TypeError("bar style restricted to two- and three dimensional graphs")
1570 def donedrawpoints(self, privatedata, sharedata, graph):
1571 privatedata.todraw.sort()
1572 for vzindex, p, a in privatedata.todraw:
1573 privatedata.barcanvas.fill(p, a)
1575 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
1576 selectindex = privatedata.stackedbar
1577 selecttotal = sharedata.stackedbar + 1
1578 graph.fill(path.rect_pt(x_pt + width_pt*selectindex/float(selecttotal), y_pt, width_pt/float(selecttotal), height_pt), privatedata.barattrs)
1581 class changebar(bar):
1583 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1584 if selecttotal != 1:
1585 raise RuntimeError("Changebar can't change its appearance. Thus you can't use it to plot several bars side by side on a subaxis.")
1587 def initdrawpoints(self, privatedata, sharedata, graph):
1588 if len(graph.axesnames) != 2:
1589 raise TypeError("changebar style restricted on two-dimensional graphs (at least for the moment)")
1590 bar.initdrawpoints(self, privatedata, sharedata, graph)
1591 privatedata.bars = []
1593 def drawpointfill(self, privatedata, p):
1594 privatedata.bars.append(p)
1596 def donedrawpoints(self, privatedata, sharedata, graph):
1597 selecttotal = len(privatedata.bars)
1598 for selectindex, p in enumerate(privatedata.bars):
1599 if p:
1600 barattrs = attr.selectattrs(self.defaultbarattrs + self.barattrs, selectindex, selecttotal)
1601 privatedata.barcanvas.fill(p, barattrs)
1603 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
1604 raise RuntimeError("Style currently doesn't provide a graph key")
1607 class gridpos(_style):
1609 needsdata = ["vpos", "vposmissing", "vposavailable", "vposvalid"]
1610 providesdata = ["values1", "values2", "data12", "data21", "index1", "index2"]
1612 def __init__(self, index1=0, index2=1, epsilon=1e-10):
1613 self.index1 = index1
1614 self.index2 = index2
1615 self.epsilon = epsilon
1617 def initdrawpoints(self, privatedata, sharedata, graph):
1618 sharedata.index1 = self.index1
1619 sharedata.index2 = self.index2
1620 sharedata.values1 = {}
1621 sharedata.values2 = {}
1622 sharedata.data12 = {}
1623 sharedata.data21 = {}
1625 def drawpoint(self, privatedata, sharedata, graph, point):
1626 if sharedata.vposavailable:
1627 sharedata.value1 = sharedata.vpos[self.index1]
1628 sharedata.value2 = sharedata.vpos[self.index2]
1629 if not sharedata.values1.has_key(sharedata.value1):
1630 for hasvalue in sharedata.values1.keys():
1631 if hasvalue - self.epsilon <= sharedata.value1 <= hasvalue + self.epsilon:
1632 sharedata.value1 = hasvalue
1633 break
1634 else:
1635 sharedata.values1[sharedata.value1] = 1
1636 if not sharedata.values2.has_key(sharedata.value2):
1637 for hasvalue in sharedata.values2.keys():
1638 if hasvalue - self.epsilon <= sharedata.value2 <= hasvalue + self.epsilon:
1639 sharedata.value2 = hasvalue
1640 break
1641 else:
1642 sharedata.values2[sharedata.value2] = 1
1643 data = sharedata.vposavailable, sharedata.vposvalid, sharedata.vpos[:]
1644 sharedata.data12.setdefault(sharedata.value1, {})[sharedata.value2] = data
1645 sharedata.data21.setdefault(sharedata.value2, {})[sharedata.value1] = data
1647 registerdefaultprovider(gridpos(), gridpos.providesdata)
1650 class grid(_line, _style):
1652 needsdata = ["values1", "values2", "data12", "data21"]
1654 defaultgridattrs = [line.changelinestyle]
1656 def __init__(self, gridlines1=1, gridlines2=1, gridattrs=[]):
1657 self.gridlines1 = gridlines1
1658 self.gridlines2 = gridlines2
1659 self.gridattrs = gridattrs
1661 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1662 if self.gridattrs is not None:
1663 privatedata.gridattrs = attr.selectattrs(self.defaultgridattrs + self.gridattrs, selectindex, selecttotal)
1664 else:
1665 privatedata.gridattrs = None
1667 def donedrawpoints(self, privatedata, sharedata, graph):
1668 values1 = sharedata.values1.keys()
1669 values1.sort()
1670 values2 = sharedata.values2.keys()
1671 values2.sort()
1672 if self.gridlines1:
1673 for value2 in values2:
1674 data1 = sharedata.data21[value2]
1675 self.initpointstopath(privatedata)
1676 for value1 in values1:
1677 try:
1678 data = data1[value1]
1679 except KeyError:
1680 self.addinvalid(privatedata)
1681 else:
1682 self.addpoint(privatedata, graph.vpos_pt, *data)
1683 p = self.donepointstopath(privatedata)
1684 if len(p):
1685 graph.stroke(p, privatedata.gridattrs)
1686 if self.gridlines2:
1687 for value1 in values1:
1688 data2 = sharedata.data12[value1]
1689 self.initpointstopath(privatedata)
1690 for value2 in values2:
1691 try:
1692 data = data2[value2]
1693 except KeyError:
1694 self.addinvalid(privatedata)
1695 else:
1696 self.addpoint(privatedata, graph.vpos_pt, *data)
1697 p = self.donepointstopath(privatedata)
1698 if len(p):
1699 graph.stroke(p, privatedata.gridattrs)
1702 class surface(_style):
1704 needsdata = ["values1", "values2", "data12", "data21"]
1706 def __init__(self, colorname="color", gradient=color.gradient.Grey, mincolor=None, maxcolor=None,
1707 gridlines1=0.05, gridlines2=0.05, gridcolor=None,
1708 backcolor=color.gray.black):
1709 self.colorname = colorname
1710 self.gradient = gradient
1711 self.mincolor = mincolor
1712 self.maxcolor = maxcolor
1713 self.gridlines1 = gridlines1
1714 self.gridlines2 = gridlines2
1715 self.gridcolor = gridcolor
1716 self.backcolor = backcolor
1718 colorspacestring = gradient.getcolor(0).colorspacestring()
1719 if self.gridcolor is not None and self.gridcolor.colorspacestring() != colorspacestring:
1720 raise RuntimeError("colorspace mismatch (gradient/grid)")
1721 if self.backcolor is not None and self.backcolor.colorspacestring() != colorspacestring:
1722 raise RuntimeError("colorspace mismatch (gradient/back)")
1724 def midvalue(self, v1, v2, v3, v4):
1725 return [0.25*sum(values) for values in zip(v1, v2, v3, v4)]
1727 def midcolor(self, c1, c2, c3, c4):
1728 return 0.25*(c1+c2+c3+c4)
1730 def lighting(self, angle, zindex):
1731 if angle < 0 and self.backcolor is not None:
1732 return self.backcolor
1733 return self.gradient.getcolor(0.7-0.4*abs(angle)+0.1*zindex)
1735 def columnnames(self, privatedata, sharedata, graph, columnnames):
1736 privatedata.colorize = self.colorname in columnnames
1737 if privatedata.colorize:
1738 return [self.colorname]
1739 return []
1741 def initdrawpoints(self, privatedata, sharedata, graph):
1742 privatedata.colors = {}
1743 privatedata.mincolor = privatedata.maxcolor = None
1745 def drawpoint(self, privatedata, sharedata, graph, point):
1746 if privatedata.colorize:
1747 try:
1748 color = point[self.colorname] + 0
1749 except:
1750 pass
1751 else:
1752 privatedata.colors.setdefault(sharedata.value1, {})[sharedata.value2] = color
1753 if privatedata.mincolor is None or color < privatedata.mincolor:
1754 privatedata.mincolor = color
1755 if privatedata.mincolor is None or privatedata.maxcolor < color:
1756 privatedata.maxcolor = color
1758 def donedrawpoints(self, privatedata, sharedata, graph):
1759 v1 = [0]*len(graph.axesnames)
1760 v2 = [0]*len(graph.axesnames)
1761 v3 = [0]*len(graph.axesnames)
1762 v4 = [0]*len(graph.axesnames)
1763 v1[sharedata.index2] = 0.5
1764 v2[sharedata.index1] = 0.5
1765 v3[sharedata.index1] = 0.5
1766 v3[sharedata.index2] = 1
1767 v4[sharedata.index1] = 1
1768 v4[sharedata.index2] = 0.5
1769 sortElements = [-graph.vzindex(*v1),
1770 -graph.vzindex(*v2),
1771 -graph.vzindex(*v3),
1772 -graph.vzindex(*v4)]
1774 values1 = sharedata.values1.keys()
1775 values1.sort()
1776 v1 = [0]*len(graph.axesnames)
1777 v2 = [0]*len(graph.axesnames)
1778 v1[sharedata.index1] = -1
1779 v2[sharedata.index1] = 1
1780 sign = 1
1781 if graph.vzindex(*v1) < graph.vzindex(*v2):
1782 values1.reverse()
1783 sign *= -1
1784 sortElements = [sortElements[3], sortElements[1], sortElements[2], sortElements[0]]
1786 values2 = sharedata.values2.keys()
1787 values2.sort()
1788 v1 = [0]*len(graph.axesnames)
1789 v2 = [0]*len(graph.axesnames)
1790 v1[sharedata.index2] = -1
1791 v2[sharedata.index2] = 1
1792 if graph.vzindex(*v1) < graph.vzindex(*v2):
1793 values2.reverse()
1794 sign *= -1
1795 sortElements = [sortElements[0], sortElements[2], sortElements[1], sortElements[3]]
1797 sortElements = [(zindex, i) for i, zindex in enumerate(sortElements)]
1798 sortElements.sort()
1800 mincolor, maxcolor = privatedata.mincolor, privatedata.maxcolor
1801 if self.mincolor is not None:
1802 mincolor = self.mincolor
1803 if self.maxcolor is not None:
1804 maxcolor = self.maxcolor
1805 nodes = []
1806 elements = []
1807 for value1a, value1b in zip(values1[:-1], values1[1:]):
1808 for value2a, value2b in zip(values2[:-1], values2[1:]):
1809 try:
1810 available1, valid1, v1 = sharedata.data12[value1a][value2a]
1811 available2, valid2, v2 = sharedata.data12[value1a][value2b]
1812 available3, valid3, v3 = sharedata.data12[value1b][value2a]
1813 available4, valid4, v4 = sharedata.data12[value1b][value2b]
1814 except KeyError:
1815 continue
1816 if not available1 or not available2 or not available3 or not available4:
1817 continue
1818 if not valid1 or not valid2 or not valid3 or not valid4:
1819 warnings.warn("surface elements partially outside of the graph are (currently) skipped completely")
1820 continue
1821 def shrink(index, v1, v2, by):
1822 v1 = v1[:]
1823 v2 = v2[:]
1824 for i in builtinrange(3):
1825 if i != index:
1826 v1[i], v2[i] = v1[i] + by*(v2[i]-v1[i]), v2[i] + by*(v1[i]-v2[i])
1827 return v1, v2
1828 v1f, v2f, v3f, v4f = v1, v2, v3, v4
1829 if self.gridcolor is not None and self.gridlines1:
1830 v1, v2 = shrink(sharedata.index1, v1, v2, self.gridlines1)
1831 v3, v4 = shrink(sharedata.index1, v3, v4, self.gridlines1)
1832 if self.gridcolor is not None and self.gridlines2:
1833 v1, v3 = shrink(sharedata.index2, v1, v3, self.gridlines2)
1834 v2, v4 = shrink(sharedata.index2, v2, v4, self.gridlines2)
1835 v5 = self.midvalue(v1, v2, v3, v4)
1836 x1_pt, y1_pt = graph.vpos_pt(*v1)
1837 x2_pt, y2_pt = graph.vpos_pt(*v2)
1838 x3_pt, y3_pt = graph.vpos_pt(*v3)
1839 x4_pt, y4_pt = graph.vpos_pt(*v4)
1840 x5_pt, y5_pt = graph.vpos_pt(*v5)
1841 if privatedata.colorize:
1842 def colorfromgradient(c):
1843 vc = (c - mincolor) / float(maxcolor - mincolor)
1844 if vc < 0:
1845 warnings.warn("gradiend color range is exceeded due to mincolor setting")
1846 vc = 0
1847 if vc > 1:
1848 warnings.warn("gradiend color range is exceeded due to maxcolor setting")
1849 vc = 1
1850 return self.gradient.getcolor(vc)
1851 c1 = privatedata.colors[value1a][value2a]
1852 c2 = privatedata.colors[value1a][value2b]
1853 c3 = privatedata.colors[value1b][value2a]
1854 c4 = privatedata.colors[value1b][value2b]
1855 c5 = self.midcolor(c1, c2, c3, c4)
1856 c1a = c1b = colorfromgradient(c1)
1857 c2a = c2c = colorfromgradient(c2)
1858 c3b = c3d = colorfromgradient(c3)
1859 c4c = c4d = colorfromgradient(c4)
1860 c5a = c5b = c5c = c5d = colorfromgradient(c5)
1861 if self.backcolor is not None and sign*graph.vangle(*(v1+v2+v5)) < 0:
1862 c1a = c2a = c5a = self.backcolor
1863 if self.backcolor is not None and sign*graph.vangle(*(v3+v1+v5)) < 0:
1864 c3b = c1b = c5b = self.backcolor
1865 if self.backcolor is not None and sign*graph.vangle(*(v2+v4+v5)) < 0:
1866 c2c = c4c = c5c = self.backcolor
1867 if self.backcolor is not None and sign*graph.vangle(*(v4+v3+v5)) < 0:
1868 c4d = c3d = c5d = self.backcolor
1869 else:
1870 zindex = graph.vzindex(*v5)
1871 c1a = c2a = c5a = self.lighting(sign*graph.vangle(*(v1+v2+v5)), zindex)
1872 c3b = c1b = c5b = self.lighting(sign*graph.vangle(*(v3+v1+v5)), zindex)
1873 c2c = c4c = c5c = self.lighting(sign*graph.vangle(*(v2+v4+v5)), zindex)
1874 c4d = c3d = c5d = self.lighting(sign*graph.vangle(*(v4+v3+v5)), zindex)
1875 for zindex, i in sortElements:
1876 if i == 0:
1877 elements.append(mesh.element((mesh.node_pt((x1_pt, y1_pt), c1a),
1878 mesh.node_pt((x2_pt, y2_pt), c2a),
1879 mesh.node_pt((x5_pt, y5_pt), c5a))))
1880 if self.gridcolor is not None and self.gridlines2:
1881 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v1f), self.gridcolor),
1882 mesh.node_pt(graph.vpos_pt(*v2), self.gridcolor),
1883 mesh.node_pt(graph.vpos_pt(*v1), self.gridcolor))))
1884 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v1f), self.gridcolor),
1885 mesh.node_pt(graph.vpos_pt(*v2), self.gridcolor),
1886 mesh.node_pt(graph.vpos_pt(*v2f), self.gridcolor))))
1887 elif i == 1:
1888 elements.append(mesh.element((mesh.node_pt((x3_pt, y3_pt), c3b),
1889 mesh.node_pt((x1_pt, y1_pt), c1b),
1890 mesh.node_pt((x5_pt, y5_pt), c5b))))
1891 if self.gridcolor is not None and self.gridlines1:
1892 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v1f), self.gridcolor),
1893 mesh.node_pt(graph.vpos_pt(*v3), self.gridcolor),
1894 mesh.node_pt(graph.vpos_pt(*v1), self.gridcolor))))
1895 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v1f), self.gridcolor),
1896 mesh.node_pt(graph.vpos_pt(*v3), self.gridcolor),
1897 mesh.node_pt(graph.vpos_pt(*v3f), self.gridcolor))))
1898 elif i == 2:
1899 elements.append(mesh.element((mesh.node_pt((x2_pt, y2_pt), c2c),
1900 mesh.node_pt((x4_pt, y4_pt), c4c),
1901 mesh.node_pt((x5_pt, y5_pt), c5c))))
1902 if self.gridcolor is not None and self.gridlines1:
1903 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v2f), self.gridcolor),
1904 mesh.node_pt(graph.vpos_pt(*v4), self.gridcolor),
1905 mesh.node_pt(graph.vpos_pt(*v2), self.gridcolor))))
1906 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v2f), self.gridcolor),
1907 mesh.node_pt(graph.vpos_pt(*v4), self.gridcolor),
1908 mesh.node_pt(graph.vpos_pt(*v4f), self.gridcolor))))
1909 elif i == 3:
1910 elements.append(mesh.element((mesh.node_pt((x4_pt, y4_pt), c4d),
1911 mesh.node_pt((x3_pt, y3_pt), c3d),
1912 mesh.node_pt((x5_pt, y5_pt), c5d))))
1913 if self.gridcolor is not None and self.gridlines2:
1914 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v3f), self.gridcolor),
1915 mesh.node_pt(graph.vpos_pt(*v4), self.gridcolor),
1916 mesh.node_pt(graph.vpos_pt(*v3), self.gridcolor))))
1917 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v3f), self.gridcolor),
1918 mesh.node_pt(graph.vpos_pt(*v4), self.gridcolor),
1919 mesh.node_pt(graph.vpos_pt(*v4f), self.gridcolor))))
1920 m = mesh.mesh(elements, check=0)
1921 graph.insert(m)
1924 class bitmap(_style):
1926 needsdata = ["values1", "values2", "data12", "data21"]
1928 def __init__(self, colorname="color", gradient=color.gradient.Grey, mincolor=None, maxcolor=None, epsilon=1e-10):
1929 self.colorname = colorname
1930 self.gradient = gradient
1931 self.mincolor = mincolor
1932 self.maxcolor = maxcolor
1933 self.epsilon = epsilon
1935 def columnnames(self, privatedata, sharedata, graph, columnnames):
1936 return [self.colorname]
1938 def initdrawpoints(self, privatedata, sharedata, graph):
1939 privatedata.colors = {}
1940 privatedata.mincolor = privatedata.maxcolor = None
1941 privatedata.vfixed = [None]*len(graph.axesnames)
1943 def drawpoint(self, privatedata, sharedata, graph, point):
1944 try:
1945 color = point[self.colorname] + 0
1946 except:
1947 pass
1948 else:
1949 privatedata.colors.setdefault(sharedata.value1, {})[sharedata.value2] = color
1950 if privatedata.mincolor is None or color < privatedata.mincolor:
1951 privatedata.mincolor = color
1952 if privatedata.mincolor is None or privatedata.maxcolor < color:
1953 privatedata.maxcolor = color
1954 if len(privatedata.vfixed) > 2 and sharedata.vposavailable:
1955 for i, (v1, v2) in enumerate(zip(privatedata.vfixed, sharedata.vpos)):
1956 if i != sharedata.index1 and i != sharedata.index2:
1957 if v1 is None:
1958 privatedata.vfixed[i] = v2
1959 elif abs(v1-v2) > self.epsilon:
1960 raise ValueError("data must be in a plane for the bitmap style")
1962 def donedrawpoints(self, privatedata, sharedata, graph):
1963 mincolor, maxcolor = privatedata.mincolor, privatedata.maxcolor
1964 if self.mincolor is not None:
1965 mincolor = self.mincolor
1966 if self.maxcolor is not None:
1967 maxcolor = self.maxcolor
1969 values1 = pycompat.sorted(sharedata.values1.keys())
1970 values2 = pycompat.sorted(sharedata.values2.keys())
1971 def equidistant(values):
1972 l = len(values) - 1
1973 if l < 1:
1974 raise ValueError("several data points required by the bitmap style in each dimension")
1975 range = values[-1] - values[0]
1976 for i, value in enumerate(values):
1977 if abs(value - values[0] - i * range / l) > self.epsilon:
1978 raise ValueError("data must be equidistant for the bitmap style")
1979 equidistant(values1)
1980 equidistant(values2)
1981 needalpha = False
1982 for value2 in values2:
1983 for value1 in values1:
1984 try:
1985 available, valid, v = sharedata.data12[value1][value2]
1986 except KeyError:
1987 needalpha = True
1988 break
1989 if not available:
1990 needalpha = True
1991 continue
1992 else:
1993 continue
1994 break
1995 mode = {"/DeviceGray": "L",
1996 "/DeviceRGB": "RGB",
1997 "/DeviceCMYK": "CMYK"}[self.gradient.getcolor(0).colorspacestring()]
1998 if needalpha:
1999 mode = "A" + mode
2000 empty = "\0"*len(mode)
2001 data = cStringIO.StringIO()
2002 for value2 in values2:
2003 for value1 in values1:
2004 try:
2005 available, valid, v = sharedata.data12[value1][value2]
2006 except KeyError:
2007 data.write(empty)
2008 continue
2009 if not available:
2010 data.write(empty)
2011 continue
2012 c = privatedata.colors[value1][value2]
2013 vc = (c - mincolor) / float(maxcolor - mincolor)
2014 if vc < 0:
2015 warnings.warn("gradiend color range is exceeded due to mincolor setting")
2016 vc = 0
2017 if vc > 1:
2018 warnings.warn("gradiend color range is exceeded due to maxcolor setting")
2019 vc = 1
2020 c = self.gradient.getcolor(vc)
2021 if needalpha:
2022 data.write(chr(255))
2023 data.write(c.to8bitstring())
2024 i = bitmapmodule.image(len(values1), len(values2), mode, data.getvalue())
2026 v1enlargement = (values1[-1]-values1[0])*0.5/len(values1)
2027 v2enlargement = (values2[-1]-values2[0])*0.5/len(values2)
2029 privatedata.vfixed[sharedata.index1] = values1[0]-v1enlargement
2030 privatedata.vfixed[sharedata.index2] = values2[-1]+v2enlargement
2031 x1_pt, y1_pt = graph.vpos_pt(*privatedata.vfixed)
2032 privatedata.vfixed[sharedata.index1] = values1[-1]+v1enlargement
2033 privatedata.vfixed[sharedata.index2] = values2[-1]+v2enlargement
2034 x2_pt, y2_pt = graph.vpos_pt(*privatedata.vfixed)
2035 privatedata.vfixed[sharedata.index1] = values1[0]-v1enlargement
2036 privatedata.vfixed[sharedata.index2] = values2[0]-v2enlargement
2037 x3_pt, y3_pt = graph.vpos_pt(*privatedata.vfixed)
2038 t = trafo.trafo_pt(((x2_pt-x1_pt, x3_pt-x1_pt), (y2_pt-y1_pt, y3_pt-y1_pt)), (x1_pt, y1_pt))
2040 privatedata.vfixed[sharedata.index1] = values1[-1]+v1enlargement
2041 privatedata.vfixed[sharedata.index2] = values2[0]-v2enlargement
2042 vx4, vy4 = t.inverse().apply_pt(*graph.vpos_pt(*privatedata.vfixed))
2043 if abs(vx4 - 1) > self.epsilon or abs(vy4 - 1) > self.epsilon:
2044 raise ValueError("invalid graph layout for bitmap style (bitmap positioning by affine transformation failed)")
2046 p = path.path()
2047 privatedata.vfixed[sharedata.index1] = 0
2048 privatedata.vfixed[sharedata.index2] = 0
2049 p.append(path.moveto_pt(*graph.vpos_pt(*privatedata.vfixed)))
2050 vfixed2 = privatedata.vfixed + privatedata.vfixed
2051 vfixed2[sharedata.index1] = 0
2052 vfixed2[sharedata.index2] = 0
2053 vfixed2[sharedata.index1 + len(graph.axesnames)] = 1
2054 vfixed2[sharedata.index2 + len(graph.axesnames)] = 0
2055 p.append(graph.vgeodesic_el(*vfixed2))
2056 vfixed2[sharedata.index1] = 1
2057 vfixed2[sharedata.index2] = 0
2058 vfixed2[sharedata.index1 + len(graph.axesnames)] = 1
2059 vfixed2[sharedata.index2 + len(graph.axesnames)] = 1
2060 p.append(graph.vgeodesic_el(*vfixed2))
2061 vfixed2[sharedata.index1] = 1
2062 vfixed2[sharedata.index2] = 1
2063 vfixed2[sharedata.index1 + len(graph.axesnames)] = 0
2064 vfixed2[sharedata.index2 + len(graph.axesnames)] = 1
2065 p.append(graph.vgeodesic_el(*vfixed2))
2066 vfixed2[sharedata.index1] = 0
2067 vfixed2[sharedata.index2] = 1
2068 vfixed2[sharedata.index1 + len(graph.axesnames)] = 0
2069 vfixed2[sharedata.index2 + len(graph.axesnames)] = 0
2070 p.append(graph.vgeodesic_el(*vfixed2))
2071 p.append(path.closepath())
2073 c = canvas.canvas([canvas.clip(p)])
2074 b = bitmapmodule.bitmap_trafo(t, i)
2075 c.insert(b)
2076 graph.insert(c)