make bitmap style working for planes in 3d graphs with parallel projection
[PyX.git] / pyx / graph / style.py
blob57a0b7083320f566eb77742d1234d5ab8f0c68c7
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 if columnname in [c + "min" for a, c, m in privatedata.rangeposcolumns if m & self.mask_min]:
267 graph.axes[columnname[:-3]].adjustaxis(data)
268 if columnname in [c + "max" for a, c, m in privatedata.rangeposcolumns if m & self.mask_max]:
269 graph.axes[columnname[:-3]].adjustaxis(data)
271 # delta handling: fill rangeposdeltacolumns
272 for axisname, usename, mask in privatedata.rangeposcolumns:
273 if columnname == usename and mask & (self.mask_dmin | self.mask_dmax | self.mask_d):
274 privatedata.rangeposdeltacolumns[axisname][self.mask_value] = data
275 if columnname == "d" + usename + "min" and mask & self.mask_dmin:
276 privatedata.rangeposdeltacolumns[axisname][self.mask_dmin] = data
277 if columnname == "d" + usename + "max" and mask & self.mask_dmax:
278 privatedata.rangeposdeltacolumns[axisname][self.mask_dmax] = data
279 if columnname == "d" + usename and mask & self.mask_d:
280 privatedata.rangeposdeltacolumns[axisname][self.mask_d] = data
282 # delta handling: process rangeposdeltacolumns
283 for a, d in privatedata.rangeposdeltacolumns.items():
284 if d.has_key(self.mask_value):
285 for k in d.keys():
286 if k != self.mask_value:
287 if k & (self.mask_dmin | self.mask_d):
288 mindata = []
289 for value, delta in zip(d[self.mask_value], d[k]):
290 try:
291 mindata.append(value-delta)
292 except:
293 pass
294 graph.axes[a].adjustaxis(mindata)
295 if k & (self.mask_dmax | self.mask_d):
296 maxdata = []
297 for value, delta in zip(d[self.mask_value], d[k]):
298 try:
299 maxdata.append(value+delta)
300 except:
301 pass
302 graph.axes[a].adjustaxis(maxdata)
303 del d[k]
305 def initdrawpoints(self, privatedata, sharedata, graph):
306 sharedata.vrange = [[None for x in xrange(2)] for y in privatedata.rangeposcolumns + sharedata.vrangemissing]
307 privatedata.rangepostmplist = [[usename, mask, index, graph.axes[axisname]] # temporarily used by drawpoint only
308 for index, (axisname, usename, mask) in enumerate(privatedata.rangeposcolumns)]
309 for missing in sharedata.vrangemissing:
310 for rangepostmp in privatedata.rangepostmplist:
311 if rangepostmp[2] >= missing:
312 rangepostmp[2] += 1
314 def drawpoint(self, privatedata, sharedata, graph, point):
315 for usename, mask, index, axis in privatedata.rangepostmplist:
316 try:
317 if mask & self.mask_min:
318 sharedata.vrange[index][0] = axis.convert(point[usename + "min"])
319 if mask & self.mask_dmin:
320 sharedata.vrange[index][0] = axis.convert(point[usename] - point["d" + usename + "min"])
321 if mask & self.mask_d:
322 sharedata.vrange[index][0] = axis.convert(point[usename] - point["d" + usename])
323 except (ArithmeticError, ValueError, TypeError):
324 sharedata.vrange[index][0] = None
325 try:
326 if mask & self.mask_max:
327 sharedata.vrange[index][1] = axis.convert(point[usename + "max"])
328 if mask & self.mask_dmax:
329 sharedata.vrange[index][1] = axis.convert(point[usename] + point["d" + usename + "max"])
330 if mask & self.mask_d:
331 sharedata.vrange[index][1] = axis.convert(point[usename] + point["d" + usename])
332 except (ArithmeticError, ValueError, TypeError):
333 sharedata.vrange[index][1] = None
335 # some range checks for data consistency
336 if (sharedata.vrange[index][0] is not None and sharedata.vrange[index][1] is not None and
337 sharedata.vrange[index][0] > sharedata.vrange[index][1] + self.epsilon):
338 raise ValueError("inverse range")
339 # disabled due to missing vpos access:
340 # if (sharedata.vrange[index][0] is not None and sharedata.vpos[index] is not None and
341 # sharedata.vrange[index][0] > sharedata.vpos[index] + self.epsilon):
342 # raise ValueError("negative minimum errorbar")
343 # if (sharedata.vrange[index][1] is not None and sharedata.vpos[index] is not None and
344 # sharedata.vrange[index][1] < sharedata.vpos[index] - self.epsilon):
345 # raise ValueError("negative maximum errorbar")
348 registerdefaultprovider(range(), range.providesdata)
351 def _crosssymbol(c, x_pt, y_pt, size_pt, attrs):
352 c.draw(path.path(path.moveto_pt(x_pt-0.5*size_pt, y_pt-0.5*size_pt),
353 path.lineto_pt(x_pt+0.5*size_pt, y_pt+0.5*size_pt),
354 path.moveto_pt(x_pt-0.5*size_pt, y_pt+0.5*size_pt),
355 path.lineto_pt(x_pt+0.5*size_pt, y_pt-0.5*size_pt)), attrs)
357 def _plussymbol(c, x_pt, y_pt, size_pt, attrs):
358 c.draw(path.path(path.moveto_pt(x_pt-0.707106781*size_pt, y_pt),
359 path.lineto_pt(x_pt+0.707106781*size_pt, y_pt),
360 path.moveto_pt(x_pt, y_pt-0.707106781*size_pt),
361 path.lineto_pt(x_pt, y_pt+0.707106781*size_pt)), attrs)
363 def _squaresymbol(c, x_pt, y_pt, size_pt, attrs):
364 c.draw(path.path(path.moveto_pt(x_pt-0.5*size_pt, y_pt-0.5*size_pt),
365 path.lineto_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.closepath()), attrs)
370 def _trianglesymbol(c, x_pt, y_pt, size_pt, attrs):
371 c.draw(path.path(path.moveto_pt(x_pt-0.759835685*size_pt, y_pt-0.438691337*size_pt),
372 path.lineto_pt(x_pt+0.759835685*size_pt, y_pt-0.438691337*size_pt),
373 path.lineto_pt(x_pt, y_pt+0.877382675*size_pt),
374 path.closepath()), attrs)
376 def _circlesymbol(c, x_pt, y_pt, size_pt, attrs):
377 c.draw(path.path(path.arc_pt(x_pt, y_pt, 0.564189583*size_pt, 0, 360),
378 path.closepath()), attrs)
380 def _diamondsymbol(c, x_pt, y_pt, size_pt, attrs):
381 c.draw(path.path(path.moveto_pt(x_pt-0.537284965*size_pt, y_pt),
382 path.lineto_pt(x_pt, y_pt-0.930604859*size_pt),
383 path.lineto_pt(x_pt+0.537284965*size_pt, y_pt),
384 path.lineto_pt(x_pt, y_pt+0.930604859*size_pt),
385 path.closepath()), attrs)
388 class _styleneedingpointpos(_style):
390 needsdata = ["vposmissing"]
392 def columnnames(self, privatedata, sharedata, graph, columnnames):
393 if len(sharedata.vposmissing):
394 raise ValueError("incomplete position information")
395 return []
398 class symbol(_styleneedingpointpos):
400 needsdata = ["vpos", "vposmissing", "vposvalid"]
402 # "inject" the predefinied symbols into the class:
404 # Note, that statements like cross = _crosssymbol are
405 # invalid, since the would lead to unbound methods, but
406 # a single entry changeable list does the trick.
408 # Once we require Python 2.2+ we should use staticmethods
409 # to implement the default symbols inplace.
411 cross = attr.changelist([_crosssymbol])
412 plus = attr.changelist([_plussymbol])
413 square = attr.changelist([_squaresymbol])
414 triangle = attr.changelist([_trianglesymbol])
415 circle = attr.changelist([_circlesymbol])
416 diamond = attr.changelist([_diamondsymbol])
418 changecross = attr.changelist([_crosssymbol, _plussymbol, _squaresymbol, _trianglesymbol, _circlesymbol, _diamondsymbol])
419 changeplus = attr.changelist([_plussymbol, _squaresymbol, _trianglesymbol, _circlesymbol, _diamondsymbol, _crosssymbol])
420 changesquare = attr.changelist([_squaresymbol, _trianglesymbol, _circlesymbol, _diamondsymbol, _crosssymbol, _plussymbol])
421 changetriangle = attr.changelist([_trianglesymbol, _circlesymbol, _diamondsymbol, _crosssymbol, _plussymbol, _squaresymbol])
422 changecircle = attr.changelist([_circlesymbol, _diamondsymbol, _crosssymbol, _plussymbol, _squaresymbol, _trianglesymbol])
423 changediamond = attr.changelist([_diamondsymbol, _crosssymbol, _plussymbol, _squaresymbol, _trianglesymbol, _circlesymbol])
424 changesquaretwice = attr.changelist([_squaresymbol, _squaresymbol, _trianglesymbol, _trianglesymbol, _circlesymbol, _circlesymbol, _diamondsymbol, _diamondsymbol])
425 changetriangletwice = attr.changelist([_trianglesymbol, _trianglesymbol, _circlesymbol, _circlesymbol, _diamondsymbol, _diamondsymbol, _squaresymbol, _squaresymbol])
426 changecircletwice = attr.changelist([_circlesymbol, _circlesymbol, _diamondsymbol, _diamondsymbol, _squaresymbol, _squaresymbol, _trianglesymbol, _trianglesymbol])
427 changediamondtwice = attr.changelist([_diamondsymbol, _diamondsymbol, _squaresymbol, _squaresymbol, _trianglesymbol, _trianglesymbol, _circlesymbol, _circlesymbol])
429 changestrokedfilled = attr.changelist([deco.stroked, deco.filled])
430 changefilledstroked = attr.changelist([deco.filled, deco.stroked])
432 defaultsymbolattrs = [deco.stroked]
434 def __init__(self, symbol=changecross, size=0.2*unit.v_cm, symbolattrs=[]):
435 self.symbol = symbol
436 self.size = size
437 self.symbolattrs = symbolattrs
439 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
440 privatedata.symbol = attr.selectattr(self.symbol, selectindex, selecttotal)
441 privatedata.size_pt = unit.topt(attr.selectattr(self.size, selectindex, selecttotal))
442 if self.symbolattrs is not None:
443 privatedata.symbolattrs = attr.selectattrs(self.defaultsymbolattrs + self.symbolattrs, selectindex, selecttotal)
444 else:
445 privatedata.symbolattrs = None
447 def initdrawpoints(self, privatedata, sharedata, graph):
448 privatedata.symbolcanvas = canvas.canvas()
450 def drawpoint(self, privatedata, sharedata, graph, point):
451 if sharedata.vposvalid and privatedata.symbolattrs is not None:
452 x_pt, y_pt = graph.vpos_pt(*sharedata.vpos)
453 privatedata.symbol(privatedata.symbolcanvas, x_pt, y_pt, privatedata.size_pt, privatedata.symbolattrs)
455 def donedrawpoints(self, privatedata, sharedata, graph):
456 graph.insert(privatedata.symbolcanvas)
458 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
459 if privatedata.symbolattrs is not None:
460 privatedata.symbol(graph, x_pt+0.5*width_pt, y_pt+0.5*height_pt, privatedata.size_pt, privatedata.symbolattrs)
463 class _line(_styleneedingpointpos):
465 # this style is not a complete style, but it provides the basic functionality to
466 # create a line, which is cut at the graph boundaries (or at otherwise invalid points)
468 def initpointstopath(self, privatedata):
469 privatedata.path = path.path()
470 privatedata.linebasepoints = []
471 privatedata.lastvpos = None
473 def addpointstopath(self, privatedata):
474 # add baselinepoints to privatedata.path
475 if len(privatedata.linebasepoints) > 1:
476 privatedata.path.append(path.moveto_pt(*privatedata.linebasepoints[0]))
477 if len(privatedata.linebasepoints) > 2:
478 privatedata.path.append(path.multilineto_pt(privatedata.linebasepoints[1:]))
479 else:
480 privatedata.path.append(path.lineto_pt(*privatedata.linebasepoints[1]))
481 privatedata.linebasepoints = []
483 def addpoint(self, privatedata, graphvpos_pt, vposavailable, vposvalid, vpos):
484 # append linebasepoints
485 if vposavailable:
486 if len(privatedata.linebasepoints):
487 # the last point was inside the graph
488 if vposvalid: # shortcut for the common case
489 privatedata.linebasepoints.append(graphvpos_pt(*vpos))
490 else:
491 # cut end
492 cut = 1
493 for vstart, vend in zip(privatedata.lastvpos, vpos):
494 newcut = None
495 if vend > 1:
496 # 1 = vstart + (vend - vstart) * cut
497 try:
498 newcut = (1 - vstart)/(vend - vstart)
499 except (ArithmeticError, TypeError):
500 break
501 if vend < 0:
502 # 0 = vstart + (vend - vstart) * cut
503 try:
504 newcut = - vstart/(vend - vstart)
505 except (ArithmeticError, TypeError):
506 break
507 if newcut is not None and newcut < cut:
508 cut = newcut
509 else:
510 cutvpos = []
511 for vstart, vend in zip(privatedata.lastvpos, vpos):
512 cutvpos.append(vstart + (vend - vstart) * cut)
513 privatedata.linebasepoints.append(graphvpos_pt(*cutvpos))
514 self.addpointstopath(privatedata)
515 else:
516 # the last point was outside the graph
517 if privatedata.lastvpos is not None:
518 if vposvalid:
519 # cut beginning
520 cut = 0
521 for vstart, vend in zip(privatedata.lastvpos, vpos):
522 newcut = None
523 if vstart > 1:
524 # 1 = vstart + (vend - vstart) * cut
525 try:
526 newcut = (1 - vstart)/(vend - vstart)
527 except (ArithmeticError, TypeError):
528 break
529 if vstart < 0:
530 # 0 = vstart + (vend - vstart) * cut
531 try:
532 newcut = - vstart/(vend - vstart)
533 except (ArithmeticError, TypeError):
534 break
535 if newcut is not None and newcut > cut:
536 cut = newcut
537 else:
538 cutvpos = []
539 for vstart, vend in zip(privatedata.lastvpos, vpos):
540 cutvpos.append(vstart + (vend - vstart) * cut)
541 privatedata.linebasepoints.append(graphvpos_pt(*cutvpos))
542 privatedata.linebasepoints.append(graphvpos_pt(*vpos))
543 else:
544 # sometimes cut beginning and end
545 cutfrom = 0
546 cutto = 1
547 for vstart, vend in zip(privatedata.lastvpos, vpos):
548 newcutfrom = None
549 if vstart > 1:
550 if vend > 1:
551 break
552 # 1 = vstart + (vend - vstart) * cutfrom
553 try:
554 newcutfrom = (1 - vstart)/(vend - vstart)
555 except (ArithmeticError, TypeError):
556 break
557 if vstart < 0:
558 if vend < 0:
559 break
560 # 0 = vstart + (vend - vstart) * cutfrom
561 try:
562 newcutfrom = - vstart/(vend - vstart)
563 except (ArithmeticError, TypeError):
564 break
565 if newcutfrom is not None and newcutfrom > cutfrom:
566 cutfrom = newcutfrom
567 newcutto = None
568 if vend > 1:
569 # 1 = vstart + (vend - vstart) * cutto
570 try:
571 newcutto = (1 - vstart)/(vend - vstart)
572 except (ArithmeticError, TypeError):
573 break
574 if vend < 0:
575 # 0 = vstart + (vend - vstart) * cutto
576 try:
577 newcutto = - vstart/(vend - vstart)
578 except (ArithmeticError, TypeError):
579 break
580 if newcutto is not None and newcutto < cutto:
581 cutto = newcutto
582 else:
583 if cutfrom < cutto:
584 cutfromvpos = []
585 cuttovpos = []
586 for vstart, vend in zip(privatedata.lastvpos, vpos):
587 cutfromvpos.append(vstart + (vend - vstart) * cutfrom)
588 cuttovpos.append(vstart + (vend - vstart) * cutto)
589 privatedata.linebasepoints.append(graphvpos_pt(*cutfromvpos))
590 privatedata.linebasepoints.append(graphvpos_pt(*cuttovpos))
591 self.addpointstopath(privatedata)
592 privatedata.lastvpos = vpos[:]
593 else:
594 if len(privatedata.linebasepoints) > 1:
595 self.addpointstopath(privatedata)
596 privatedata.lastvpos = None
598 def addinvalid(self, privatedata):
599 if len(privatedata.linebasepoints) > 1:
600 self.addpointstopath(privatedata)
601 privatedata.lastvpos = None
603 def donepointstopath(self, privatedata):
604 if len(privatedata.linebasepoints) > 1:
605 self.addpointstopath(privatedata)
606 return privatedata.path
609 class line(_line):
611 needsdata = ["vpos", "vposmissing", "vposavailable", "vposvalid"]
613 changelinestyle = attr.changelist([style.linestyle.solid,
614 style.linestyle.dashed,
615 style.linestyle.dotted,
616 style.linestyle.dashdotted])
618 defaultlineattrs = [changelinestyle]
620 def __init__(self, lineattrs=[]):
621 self.lineattrs = lineattrs
623 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
624 if self.lineattrs is not None:
625 privatedata.lineattrs = attr.selectattrs(self.defaultlineattrs + self.lineattrs, selectindex, selecttotal)
626 else:
627 privatedata.lineattrs = None
629 def initdrawpoints(self, privatedata, sharedata, graph):
630 self.initpointstopath(privatedata)
632 def drawpoint(self, privatedata, sharedata, graph, point):
633 self.addpoint(privatedata, graph.vpos_pt, sharedata.vposavailable, sharedata.vposvalid, sharedata.vpos)
635 def donedrawpoints(self, privatedata, sharedata, graph):
636 path = self.donepointstopath(privatedata)
637 if privatedata.lineattrs is not None and len(path):
638 graph.stroke(path, privatedata.lineattrs)
640 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
641 if privatedata.lineattrs is not None:
642 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)
645 class impulses(_styleneedingpointpos):
647 needsdata = ["vpos", "vposmissing", "vposavailable", "vposvalid", "poscolumnnames"]
649 defaultlineattrs = [line.changelinestyle]
650 defaultfrompathattrs = []
652 def __init__(self, lineattrs=[], fromvalue=0, frompathattrs=[], valueaxisindex=1):
653 self.lineattrs = lineattrs
654 self.fromvalue = fromvalue
655 self.frompathattrs = frompathattrs
656 self.valueaxisindex = valueaxisindex
658 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
659 privatedata.insertfrompath = selectindex == 0
660 if self.lineattrs is not None:
661 privatedata.lineattrs = attr.selectattrs(self.defaultlineattrs + self.lineattrs, selectindex, selecttotal)
662 else:
663 privatedata.lineattrs = None
665 def adjustaxis(self, privatedata, sharedata, graph, columnname, data):
666 if self.fromvalue is not None:
667 try:
668 i = sharedata.poscolumnnames.index(columnname)
669 except ValueError:
670 pass
671 else:
672 if i == self.valueaxisindex:
673 graph.axes[sharedata.poscolumnnames[i]].adjustaxis([self.fromvalue])
675 def initdrawpoints(self, privatedata, sharedata, graph):
676 privatedata.impulsescanvas = canvas.canvas()
677 if self.fromvalue is not None:
678 valueaxisname = sharedata.poscolumnnames[self.valueaxisindex]
679 privatedata.vfromvalue = graph.axes[valueaxisname].convert(self.fromvalue)
680 privatedata.vfromvaluecut = 0
681 if privatedata.vfromvalue < 0:
682 privatedata.vfromvalue = 0
683 if privatedata.vfromvalue > 1:
684 privatedata.vfromvalue = 1
685 if self.frompathattrs is not None and privatedata.insertfrompath:
686 graph.stroke(graph.axes[valueaxisname].vgridpath(privatedata.vfromvalue),
687 self.defaultfrompathattrs + self.frompathattrs)
688 else:
689 privatedata.vfromvalue = 0
691 def drawpoint(self, privatedata, sharedata, graph, point):
692 if sharedata.vposvalid and privatedata.lineattrs is not None:
693 vpos = sharedata.vpos[:]
694 vpos[self.valueaxisindex] = privatedata.vfromvalue
695 privatedata.impulsescanvas.stroke(graph.vgeodesic(*(vpos + sharedata.vpos)), privatedata.lineattrs)
697 def donedrawpoints(self, privatedata, sharedata, graph):
698 graph.insert(privatedata.impulsescanvas)
700 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
701 if privatedata.lineattrs is not None:
702 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)
705 class errorbar(_style):
707 needsdata = ["vpos", "vposmissing", "vposavailable", "vposvalid", "vrange", "vrangeminmissing", "vrangemaxmissing"]
709 defaulterrorbarattrs = []
711 def __init__(self, size=0.1*unit.v_cm,
712 errorbarattrs=[],
713 epsilon=1e-10):
714 self.size = size
715 self.errorbarattrs = errorbarattrs
716 self.epsilon = epsilon
718 def columnnames(self, privatedata, sharedata, graph, columnnames):
719 for i in sharedata.vposmissing:
720 if i in sharedata.vrangeminmissing and i in sharedata.vrangemaxmissing:
721 raise ValueError("position and range for a graph dimension missing")
722 return []
724 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
725 privatedata.errorsize_pt = unit.topt(attr.selectattr(self.size, selectindex, selecttotal))
726 privatedata.errorbarattrs = attr.selectattrs(self.defaulterrorbarattrs + self.errorbarattrs, selectindex, selecttotal)
728 def initdrawpoints(self, privatedata, sharedata, graph):
729 if privatedata.errorbarattrs is not None:
730 privatedata.errorbarcanvas = canvas.canvas(privatedata.errorbarattrs)
731 privatedata.dimensionlist = list(xrange(len(sharedata.vpos)))
733 def drawpoint(self, privatedata, sharedata, graph, point):
734 if privatedata.errorbarattrs is not None:
735 for i in privatedata.dimensionlist:
736 for j in privatedata.dimensionlist:
737 if (i != j and
738 (sharedata.vpos[j] is None or
739 sharedata.vpos[j] < -self.epsilon or
740 sharedata.vpos[j] > 1+self.epsilon)):
741 break
742 else:
743 if ((sharedata.vrange[i][0] is None and sharedata.vpos[i] is None) or
744 (sharedata.vrange[i][1] is None and sharedata.vpos[i] is None) or
745 (sharedata.vrange[i][0] is None and sharedata.vrange[i][1] is None)):
746 continue
747 vminpos = sharedata.vpos[:]
748 if sharedata.vrange[i][0] is not None:
749 vminpos[i] = sharedata.vrange[i][0]
750 mincap = 1
751 else:
752 mincap = 0
753 if vminpos[i] > 1+self.epsilon:
754 continue
755 if vminpos[i] < -self.epsilon:
756 vminpos[i] = 0
757 mincap = 0
758 vmaxpos = sharedata.vpos[:]
759 if sharedata.vrange[i][1] is not None:
760 vmaxpos[i] = sharedata.vrange[i][1]
761 maxcap = 1
762 else:
763 maxcap = 0
764 if vmaxpos[i] < -self.epsilon:
765 continue
766 if vmaxpos[i] > 1+self.epsilon:
767 vmaxpos[i] = 1
768 maxcap = 0
769 privatedata.errorbarcanvas.stroke(graph.vgeodesic(*(vminpos + vmaxpos)))
770 for j in privatedata.dimensionlist:
771 if i != j:
772 if mincap:
773 privatedata.errorbarcanvas.stroke(graph.vcap_pt(j, privatedata.errorsize_pt, *vminpos))
774 if maxcap:
775 privatedata.errorbarcanvas.stroke(graph.vcap_pt(j, privatedata.errorsize_pt, *vmaxpos))
777 def donedrawpoints(self, privatedata, sharedata, graph):
778 if privatedata.errorbarattrs is not None:
779 graph.insert(privatedata.errorbarcanvas)
782 class text(_styleneedingpointpos):
784 needsdata = ["vpos", "vposmissing", "vposvalid"]
786 defaulttextattrs = [textmodule.halign.center, textmodule.vshift.mathaxis]
788 def __init__(self, textname="text", dxname=None, dyname=None,
789 dxunit=0.3*unit.v_cm, dyunit=0.3*unit.v_cm,
790 textdx=0*unit.v_cm, textdy=0.3*unit.v_cm, textattrs=[]):
791 self.textname = textname
792 self.dxname = dxname
793 self.dyname = dyname
794 self.dxunit = dxunit
795 self.dyunit = dyunit
796 self.textdx = textdx
797 self.textdy = textdy
798 self.textattrs = textattrs
800 def columnnames(self, privatedata, sharedata, graph, columnnames):
801 if self.textname not in columnnames:
802 raise ValueError("column '%s' missing" % self.textname)
803 names = [self.textname]
804 if self.dxname is not None:
805 if self.dxname not in columnnames:
806 raise ValueError("column '%s' missing" % self.dxname)
807 names.append(self.dxname)
808 if self.dyname is not None:
809 if self.dyname not in columnnames:
810 raise ValueError("column '%s' missing" % self.dyname)
811 names.append(self.dyname)
812 return names + _styleneedingpointpos.columnnames(self, privatedata, sharedata, graph, columnnames)
814 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
815 if self.textattrs is not None:
816 privatedata.textattrs = attr.selectattrs(self.defaulttextattrs + self.textattrs, selectindex, selecttotal)
817 else:
818 privatedata.textattrs = None
820 def initdrawpoints(self, privatedata, sharedata, grap):
821 if self.dxname is None:
822 privatedata.textdx_pt = unit.topt(self.textdx)
823 else:
824 privatedata.dxunit_pt = unit.topt(self.dxunit)
825 if self.dyname is None:
826 privatedata.textdy_pt = unit.topt(self.textdy)
827 else:
828 privatedata.dyunit_pt = unit.topt(self.dyunit)
830 def drawpoint(self, privatedata, sharedata, graph, point):
831 if privatedata.textattrs is not None and sharedata.vposvalid:
832 x_pt, y_pt = graph.vpos_pt(*sharedata.vpos)
833 try:
834 text = str(point[self.textname])
835 except:
836 pass
837 else:
838 if self.dxname is None:
839 dx_pt = privatedata.textdx_pt
840 else:
841 dx_pt = float(point[self.dxname]) * privatedata.dxunit_pt
842 if self.dyname is None:
843 dy_pt = privatedata.textdy_pt
844 else:
845 dy_pt = float(point[self.dyname]) * privatedata.dyunit_pt
846 graph.text_pt(x_pt + dx_pt, y_pt + dy_pt, text, privatedata.textattrs)
848 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
849 raise RuntimeError("Style currently doesn't provide a graph key")
852 class arrow(_styleneedingpointpos):
854 needsdata = ["vpos", "vposmissing", "vposvalid"]
856 defaultlineattrs = []
857 defaultarrowattrs = []
859 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):
860 self.linelength = linelength
861 self.arrowsize = arrowsize
862 self.lineattrs = lineattrs
863 self.arrowattrs = arrowattrs
864 self.arrowpos = arrowpos
865 self.epsilon = epsilon
866 self.decorator = decorator
868 def columnnames(self, privatedata, sharedata, graph, columnnames):
869 if len(graph.axesnames) != 2:
870 raise ValueError("arrow style restricted on two-dimensional graphs")
871 if "size" not in columnnames:
872 raise ValueError("size missing")
873 if "angle" not in columnnames:
874 raise ValueError("angle missing")
875 return ["size", "angle"] + _styleneedingpointpos.columnnames(self, privatedata, sharedata, graph, columnnames)
877 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
878 if self.lineattrs is not None:
879 privatedata.lineattrs = attr.selectattrs(self.defaultlineattrs + self.lineattrs, selectindex, selecttotal)
880 else:
881 privatedata.lineattrs = None
882 if self.arrowattrs is not None:
883 privatedata.arrowattrs = attr.selectattrs(self.defaultarrowattrs + self.arrowattrs, selectindex, selecttotal)
884 else:
885 privatedata.arrowattrs = None
887 def initdrawpoints(self, privatedata, sharedata, graph):
888 privatedata.arrowcanvas = canvas.canvas()
890 def drawpoint(self, privatedata, sharedata, graph, point):
891 if privatedata.lineattrs is not None and privatedata.arrowattrs is not None and sharedata.vposvalid:
892 linelength_pt = unit.topt(self.linelength)
893 x_pt, y_pt = graph.vpos_pt(*sharedata.vpos)
894 try:
895 angle = point["angle"] + 0.0
896 size = point["size"] + 0.0
897 except:
898 pass
899 else:
900 if point["size"] > self.epsilon:
901 dx = math.cos(angle*math.pi/180)
902 dy = math.sin(angle*math.pi/180)
903 x1 = x_pt-self.arrowpos*dx*linelength_pt*size
904 y1 = y_pt-self.arrowpos*dy*linelength_pt*size
905 x2 = x_pt+(1-self.arrowpos)*dx*linelength_pt*size
906 y2 = y_pt+(1-self.arrowpos)*dy*linelength_pt*size
907 if self.decorator:
908 privatedata.arrowcanvas.stroke(path.line_pt(x1, y1, x2, y2),
909 privatedata.lineattrs+[self.decorator(privatedata.arrowattrs, size=self.arrowsize*size)])
910 else:
911 privatedata.arrowcanvas.stroke(path.line_pt(x1, y1, x2, y2), privatedata.lineattrs)
913 def donedrawpoints(self, privatedata, sharedata, graph):
914 graph.insert(privatedata.arrowcanvas)
916 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
917 raise RuntimeError("Style currently doesn't provide a graph key")
920 class rect(_style):
922 needsdata = ["vrange", "vrangeminmissing", "vrangemaxmissing"]
924 def __init__(self, gradient=color.gradient.Grey):
925 self.gradient = gradient
927 def columnnames(self, privatedata, sharedata, graph, columnnames):
928 if len(graph.axesnames) != 2:
929 raise TypeError("arrow style restricted on two-dimensional graphs")
930 if "color" not in columnnames:
931 raise ValueError("color missing")
932 if len(sharedata.vrangeminmissing) + len(sharedata.vrangemaxmissing):
933 raise ValueError("incomplete range")
934 return ["color"]
936 def initdrawpoints(self, privatedata, sharedata, graph):
937 privatedata.rectcanvas = graph.insert(canvas.canvas())
939 def drawpoint(self, privatedata, sharedata, graph, point):
940 xvmin = sharedata.vrange[0][0]
941 xvmax = sharedata.vrange[0][1]
942 yvmin = sharedata.vrange[1][0]
943 yvmax = sharedata.vrange[1][1]
944 if (xvmin is not None and xvmin < 1 and
945 xvmax is not None and xvmax > 0 and
946 yvmin is not None and yvmin < 1 and
947 yvmax is not None and yvmax > 0):
948 if xvmin < 0:
949 xvmin = 0
950 elif xvmax > 1:
951 xvmax = 1
952 if yvmin < 0:
953 yvmin = 0
954 elif yvmax > 1:
955 yvmax = 1
956 p = graph.vgeodesic(xvmin, yvmin, xvmax, yvmin)
957 p.append(graph.vgeodesic_el(xvmax, yvmin, xvmax, yvmax))
958 p.append(graph.vgeodesic_el(xvmax, yvmax, xvmin, yvmax))
959 p.append(graph.vgeodesic_el(xvmin, yvmax, xvmin, yvmin))
960 p.append(path.closepath())
961 privatedata.rectcanvas.fill(p, [self.gradient.getcolor(point["color"])])
963 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
964 raise RuntimeError("Style currently doesn't provide a graph key")
967 class histogram(_style):
969 needsdata = ["vpos", "vposmissing", "vrange", "vrangeminmissing", "vrangemaxmissing"]
971 defaultlineattrs = [deco.stroked]
972 defaultfrompathattrs = []
974 def __init__(self, lineattrs=[], steps=0, fromvalue=0, frompathattrs=[], fillable=0, rectkey=0,
975 autohistogramaxisindex=0, autohistogrampointpos=0.5, epsilon=1e-10):
976 self.lineattrs = lineattrs
977 self.steps = steps
978 self.fromvalue = fromvalue
979 self.frompathattrs = frompathattrs
980 self.fillable = fillable # TODO: fillable paths might not properly be closed by straight lines on curved graph geometries
981 self.rectkey = rectkey
982 self.autohistogramaxisindex = autohistogramaxisindex
983 self.autohistogrampointpos = autohistogrampointpos
984 self.epsilon = epsilon
986 def columnnames(self, privatedata, sharedata, graph, columnnames):
987 if len(graph.axesnames) != 2:
988 raise TypeError("histogram style restricted on two-dimensional graphs")
989 privatedata.rangeaxisindex = None
990 for i in builtinrange(len(graph.axesnames)):
991 if i in sharedata.vrangeminmissing or i in sharedata.vrangemaxmissing:
992 if i in sharedata.vposmissing:
993 raise ValueError("pos and range missing")
994 else:
995 if privatedata.rangeaxisindex is not None:
996 raise ValueError("multiple ranges")
997 privatedata.rangeaxisindex = i
998 if privatedata.rangeaxisindex is None:
999 privatedata.rangeaxisindex = self.autohistogramaxisindex
1000 privatedata.autohistogram = 1
1001 else:
1002 privatedata.autohistogram = 0
1003 return []
1005 def adjustaxis(self, privatedata, sharedata, graph, columnname, data):
1006 if privatedata.autohistogram and columnname == sharedata.poscolumnnames[privatedata.rangeaxisindex]:
1007 if len(data) == 1:
1008 raise ValueError("several data points needed for automatic histogram width calculation")
1009 if len(data) > 1:
1010 delta = data[1] - data[0]
1011 min = data[0] - self.autohistogrampointpos * delta
1012 max = data[-1] + (1-self.autohistogrampointpos) * delta
1013 graph.axes[columnname].adjustaxis([min, max])
1014 elif self.fromvalue is not None and columnname == sharedata.poscolumnnames[1-privatedata.rangeaxisindex]:
1015 graph.axes[columnname].adjustaxis([self.fromvalue])
1017 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1018 privatedata.insertfrompath = selectindex == 0
1019 if self.lineattrs is not None:
1020 privatedata.lineattrs = attr.selectattrs(self.defaultlineattrs + self.lineattrs, selectindex, selecttotal)
1021 else:
1022 privatedata.lineattrs = None
1024 def vmoveto(self, privatedata, sharedata, graph, vpos, vvalue):
1025 if -self.epsilon < vpos < 1+self.epsilon and -self.epsilon < vvalue < 1+self.epsilon:
1026 if privatedata.rangeaxisindex:
1027 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue, vpos)))
1028 else:
1029 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vpos, vvalue)))
1031 def vposline(self, privatedata, sharedata, graph, vpos, vvalue1, vvalue2):
1032 if -self.epsilon < vpos < 1+self.epsilon:
1033 vvalue1cut = 0
1034 if vvalue1 < 0:
1035 vvalue1 = 0
1036 vvalue1cut = -1
1037 elif vvalue1 > 1:
1038 vvalue1 = 1
1039 vvalue1cut = 1
1040 vvalue2cut = 0
1041 if vvalue2 < 0:
1042 vvalue2 = 0
1043 vvalue2cut = -1
1044 elif vvalue2 > 1:
1045 vvalue2 = 1
1046 vvalue2cut = 1
1047 if abs(vvalue1cut + vvalue2cut) <= 1:
1048 if vvalue1cut and not self.fillable:
1049 if privatedata.rangeaxisindex:
1050 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue1, vpos)))
1051 else:
1052 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vpos, vvalue1)))
1053 if privatedata.rangeaxisindex:
1054 privatedata.path.append(graph.vgeodesic_el(vvalue1, vpos, vvalue2, vpos))
1055 else:
1056 privatedata.path.append(graph.vgeodesic_el(vpos, vvalue1, vpos, vvalue2))
1058 def vvalueline(self, privatedata, sharedata, graph, vvalue, vpos1, vpos2):
1059 if self.fillable:
1060 if vvalue < -self.epsilon:
1061 vvalue = 0
1062 warnings.warn("cut at graph boundary adds artificial lines to fillable step histogram path")
1063 if vvalue > 1+self.epsilon:
1064 vvalue = 1
1065 warnings.warn("cut at graph boundary adds artificial lines to fillable step histogram path")
1066 if self.fillable or (-self.epsilon < vvalue < 1+self.epsilon):
1067 vpos1cut = 0
1068 if vpos1 < 0:
1069 vpos1 = 0
1070 vpos1cut = -1
1071 elif vpos1 > 1:
1072 vpos1 = 1
1073 vpos1cut = 1
1074 vpos2cut = 0
1075 if vpos2 < 0:
1076 vpos2 = 0
1077 vpos2cut = -1
1078 elif vpos2 > 1:
1079 vpos2 = 1
1080 vpos2cut = 1
1081 if abs(vpos1cut + vpos2cut) <= 1:
1082 if vpos1cut:
1083 if self.fillable:
1084 if privatedata.rangeaxisindex:
1085 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(privatedata.vfromvalue, vpos1)))
1086 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vpos1, vvalue, vpos1))
1087 else:
1088 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vpos1, privatedata.vfromvalue)))
1089 privatedata.path.append(graph.vgeodesic_el(vpos1, privatedata.vfromvalue, vpos1, vvalue))
1090 else:
1091 if privatedata.rangeaxisindex:
1092 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue, vpos1)))
1093 else:
1094 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vpos1, vvalue)))
1095 if privatedata.rangeaxisindex:
1096 privatedata.path.append(graph.vgeodesic_el(vvalue, vpos1, vvalue, vpos2))
1097 else:
1098 privatedata.path.append(graph.vgeodesic_el(vpos1, vvalue, vpos2, vvalue))
1099 if self.fillable and vpos2cut:
1100 warnings.warn("cut at graph boundary adds artificial lines to fillable step histogram path")
1101 if privatedata.rangeaxisindex:
1102 privatedata.path.append(graph.vgeodesic_el(vvalue, vpos2, privatedata.vfromvalue, vpos2))
1103 else:
1104 privatedata.path.append(graph.vgeodesic_el(vpos2, vvalue, vpos2, privatedata.vfromvalue))
1106 def drawvalue(self, privatedata, sharedata, graph, vmin, vmax, vvalue):
1107 currentvalid = vmin is not None and vmax is not None and vvalue is not None
1108 if self.fillable and not self.steps:
1109 if not currentvalid:
1110 return
1111 vmincut = 0
1112 if vmin < -self.epsilon:
1113 vmin = 0
1114 vmincut = -1
1115 elif vmin > 1+self.epsilon:
1116 vmin = 1
1117 vmincut = 1
1118 vmaxcut = 0
1119 if vmax < -self.epsilon:
1120 vmax = 0
1121 vmaxcut = -1
1122 if vmax > 1+self.epsilon:
1123 vmax = 1
1124 vmaxcut = 1
1125 vvaluecut = 0
1126 if vvalue < -self.epsilon:
1127 vvalue = 0
1128 vvaluecut = -1
1129 if vvalue > 1+self.epsilon:
1130 vvalue = 1
1131 vvaluecut = 1
1132 done = 0
1133 if abs(vmincut) + abs(vmaxcut) + abs(vvaluecut) + abs(privatedata.vfromvaluecut) > 1:
1134 if abs(vmincut + vmaxcut) > 1 or abs(vvaluecut+privatedata.vfromvaluecut) > 1:
1135 done = 1
1136 else:
1137 warnings.warn("multiple cuts at graph boundary add artificial lines to fillable rectangle histogram path")
1138 elif vmincut:
1139 done = 1
1140 if privatedata.rangeaxisindex:
1141 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(privatedata.vfromvalue, vmin)))
1142 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmin, privatedata.vfromvalue, vmax))
1143 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmax, vvalue, vmax))
1144 privatedata.path.append(graph.vgeodesic_el(vvalue, vmax, vvalue, vmin))
1145 else:
1146 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmin, privatedata.vfromvalue)))
1147 privatedata.path.append(graph.vgeodesic_el(vmin, privatedata.vfromvalue, vmax, privatedata.vfromvalue))
1148 privatedata.path.append(graph.vgeodesic_el(vmax, privatedata.vfromvalue, vmax, vvalue))
1149 privatedata.path.append(graph.vgeodesic_el(vmax, vvalue, vmin, vvalue))
1150 elif vmaxcut:
1151 done = 1
1152 if privatedata.rangeaxisindex:
1153 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue, vmax)))
1154 privatedata.path.append(graph.vgeodesic_el(vvalue, vmax, vvalue, vmin))
1155 privatedata.path.append(graph.vgeodesic_el(vvalue, vmin, privatedata.vfromvalue, vmin))
1156 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmin, privatedata.vfromvalue, vmax))
1157 else:
1158 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmax, vvalue)))
1159 privatedata.path.append(graph.vgeodesic_el(vmax, vvalue, vmin, vvalue))
1160 privatedata.path.append(graph.vgeodesic_el(vmin, vvalue, vmin, privatedata.vfromvalue))
1161 privatedata.path.append(graph.vgeodesic_el(vmin, privatedata.vfromvalue, vmax, privatedata.vfromvalue))
1162 elif privatedata.vfromvaluecut:
1163 done = 1
1164 if privatedata.rangeaxisindex:
1165 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(privatedata.vfromvalue, vmax)))
1166 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmax, vvalue, vmax))
1167 privatedata.path.append(graph.vgeodesic_el(vvalue, vmax, vvalue, vmin))
1168 privatedata.path.append(graph.vgeodesic_el(vvalue, vmin, privatedata.vfromvalue, vmin))
1169 else:
1170 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmax, privatedata.vfromvalue)))
1171 privatedata.path.append(graph.vgeodesic_el(vmax, privatedata.vfromvalue, vmax, vvalue))
1172 privatedata.path.append(graph.vgeodesic_el(vmax, vvalue, vmin, vvalue))
1173 privatedata.path.append(graph.vgeodesic_el(vmin, vvalue, vmin, privatedata.vfromvalue))
1174 elif vvaluecut:
1175 done = 1
1176 if privatedata.rangeaxisindex:
1177 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue, vmin)))
1178 privatedata.path.append(graph.vgeodesic_el(vvalue, vmin, privatedata.vfromvalue, vmin))
1179 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmin, privatedata.vfromvalue, vmax))
1180 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmax, vvalue, vmax))
1181 else:
1182 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmin, vvalue)))
1183 privatedata.path.append(graph.vgeodesic_el(vmin, vvalue, vmin, privatedata.vfromvalue))
1184 privatedata.path.append(graph.vgeodesic_el(vmin, privatedata.vfromvalue, vmax, privatedata.vfromvalue))
1185 privatedata.path.append(graph.vgeodesic_el(vmax, privatedata.vfromvalue, vmax, vvalue))
1186 if not done:
1187 if privatedata.rangeaxisindex:
1188 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(privatedata.vfromvalue, vmin)))
1189 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmin, privatedata.vfromvalue, vmax))
1190 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmax, vvalue, vmax))
1191 privatedata.path.append(graph.vgeodesic_el(vvalue, vmax, vvalue, vmin))
1192 privatedata.path.append(graph.vgeodesic_el(vvalue, vmin, privatedata.vfromvalue, vmin))
1193 privatedata.path.append(path.closepath())
1194 else:
1195 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmin, privatedata.vfromvalue)))
1196 privatedata.path.append(graph.vgeodesic_el(vmin, privatedata.vfromvalue, vmax, privatedata.vfromvalue))
1197 privatedata.path.append(graph.vgeodesic_el(vmax, privatedata.vfromvalue, vmax, vvalue))
1198 privatedata.path.append(graph.vgeodesic_el(vmax, vvalue, vmin, vvalue))
1199 privatedata.path.append(graph.vgeodesic_el(vmin, vvalue, vmin, privatedata.vfromvalue))
1200 privatedata.path.append(path.closepath())
1201 else:
1202 try:
1203 gap = abs(vmin - privatedata.lastvmax) > self.epsilon
1204 except (ArithmeticError, ValueError, TypeError):
1205 gap = 1
1206 if (privatedata.lastvvalue is not None and currentvalid and not gap and
1207 (self.steps or (privatedata.lastvvalue-privatedata.vfromvalue)*(vvalue-privatedata.vfromvalue) < 0)):
1208 self.vposline(privatedata, sharedata, graph,
1209 vmin, privatedata.lastvvalue, vvalue)
1210 else:
1211 if privatedata.lastvvalue is not None and currentvalid:
1212 currentbigger = abs(privatedata.lastvvalue-privatedata.vfromvalue) < abs(vvalue-privatedata.vfromvalue)
1213 if privatedata.lastvvalue is not None and (not currentvalid or not currentbigger or gap):
1214 self.vposline(privatedata, sharedata, graph,
1215 privatedata.lastvmax, privatedata.lastvvalue, privatedata.vfromvalue)
1216 if currentvalid:
1217 self.vmoveto(privatedata, sharedata, graph,
1218 vmin, vvalue)
1219 if currentvalid and (privatedata.lastvvalue is None or currentbigger or gap):
1220 self.vmoveto(privatedata, sharedata, graph,
1221 vmin, privatedata.vfromvalue)
1222 self.vposline(privatedata, sharedata, graph,
1223 vmin, privatedata.vfromvalue, vvalue)
1224 if currentvalid:
1225 self.vvalueline(privatedata, sharedata, graph,
1226 vvalue, vmin, vmax)
1227 privatedata.lastvvalue = vvalue
1228 privatedata.lastvmax = vmax
1229 else:
1230 privatedata.lastvvalue = privatedata.lastvmax = None
1232 def initdrawpoints(self, privatedata, sharedata, graph):
1233 privatedata.path = path.path()
1234 privatedata.lastvvalue = privatedata.lastvmax = None
1235 privatedata.vcurrentpoint = None
1236 privatedata.count = 0
1237 if self.fromvalue is not None:
1238 valueaxisname = sharedata.poscolumnnames[1-privatedata.rangeaxisindex]
1239 privatedata.vfromvalue = graph.axes[valueaxisname].convert(self.fromvalue)
1240 privatedata.vfromvaluecut = 0
1241 if privatedata.vfromvalue < 0:
1242 privatedata.vfromvalue = 0
1243 privatedata.vfromvaluecut = -1
1244 if privatedata.vfromvalue > 1:
1245 privatedata.vfromvalue = 1
1246 privatedata.vfromvaluecut = 1
1247 if self.frompathattrs is not None and privatedata.insertfrompath:
1248 graph.stroke(graph.axes[valueaxisname].vgridpath(privatedata.vfromvalue),
1249 self.defaultfrompathattrs + self.frompathattrs)
1250 else:
1251 privatedata.vfromvalue = 0
1253 def drawpoint(self, privatedata, sharedata, graph, point):
1254 if privatedata.autohistogram:
1255 # automatic range handling
1256 privatedata.count += 1
1257 if privatedata.count == 2:
1258 if privatedata.rangeaxisindex:
1259 privatedata.vrange = sharedata.vpos[1] - privatedata.lastvpos[1]
1260 self.drawvalue(privatedata, sharedata, graph,
1261 privatedata.lastvpos[1] - self.autohistogrampointpos*privatedata.vrange,
1262 privatedata.lastvpos[1] + (1-self.autohistogrampointpos)*privatedata.vrange,
1263 privatedata.lastvpos[0])
1264 else:
1265 privatedata.vrange = sharedata.vpos[0] - privatedata.lastvpos[0]
1266 self.drawvalue(privatedata, sharedata, graph,
1267 privatedata.lastvpos[0] - self.autohistogrampointpos*privatedata.vrange,
1268 privatedata.lastvpos[0] + (1-self.autohistogrampointpos)*privatedata.vrange,
1269 privatedata.lastvpos[1])
1270 elif privatedata.count > 2:
1271 if privatedata.rangeaxisindex:
1272 vrange = sharedata.vpos[1] - privatedata.lastvpos[1]
1273 else:
1274 vrange = sharedata.vpos[0] - privatedata.lastvpos[0]
1275 if abs(privatedata.vrange - vrange) > self.epsilon:
1276 raise ValueError("equal steps (in graph coordinates) needed for automatic width calculation")
1277 if privatedata.count > 1:
1278 if privatedata.rangeaxisindex:
1279 self.drawvalue(privatedata, sharedata, graph,
1280 sharedata.vpos[1] - self.autohistogrampointpos*privatedata.vrange,
1281 sharedata.vpos[1] + (1-self.autohistogrampointpos)*privatedata.vrange,
1282 sharedata.vpos[0])
1283 else:
1284 self.drawvalue(privatedata, sharedata, graph,
1285 sharedata.vpos[0] - self.autohistogrampointpos*privatedata.vrange,
1286 sharedata.vpos[0] + (1-self.autohistogrampointpos)*privatedata.vrange,
1287 sharedata.vpos[1])
1288 privatedata.lastvpos = sharedata.vpos[:]
1289 else:
1290 if privatedata.rangeaxisindex:
1291 self.drawvalue(privatedata, sharedata, graph,
1292 sharedata.vrange[1][0], sharedata.vrange[1][1], sharedata.vpos[0])
1293 else:
1294 self.drawvalue(privatedata, sharedata, graph,
1295 sharedata.vrange[0][0], sharedata.vrange[0][1], sharedata.vpos[1])
1297 def donedrawpoints(self, privatedata, sharedata, graph):
1298 self.drawvalue(privatedata, sharedata, graph, None, None, None)
1299 if privatedata.lineattrs is not None and len(privatedata.path):
1300 graph.draw(privatedata.path, privatedata.lineattrs)
1302 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
1303 if privatedata.lineattrs is not None:
1304 if self.rectkey:
1305 p = path.rect_pt(x_pt, y_pt, width_pt, height_pt)
1306 else:
1307 p = path.line_pt(x_pt, y_pt+0.5*height_pt, x_pt+width_pt, y_pt+0.5*height_pt)
1308 graph.draw(p, privatedata.lineattrs)
1311 class barpos(_style):
1313 providesdata = ["vpos", "vposmissing", "vposavailable", "vposvalid", "vbarrange", "barposcolumnnames", "barvalueindex", "lastbarvalue", "stackedbar", "stackedbardraw"]
1315 defaultfrompathattrs = []
1317 def __init__(self, fromvalue=None, frompathattrs=[], epsilon=1e-10):
1318 self.fromvalue = fromvalue
1319 self.frompathattrs = frompathattrs
1320 self.epsilon = epsilon
1322 def columnnames(self, privatedata, sharedata, graph, columnnames):
1323 sharedata.barposcolumnnames = []
1324 sharedata.barvalueindex = None
1325 for dimension, axisnames in enumerate(graph.axesnames):
1326 found = 0
1327 for axisname in axisnames:
1328 if axisname in columnnames:
1329 if sharedata.barvalueindex is not None:
1330 raise ValueError("multiple values")
1331 sharedata.barvalueindex = dimension
1332 sharedata.barposcolumnnames.append(axisname)
1333 found += 1
1334 if (axisname + "name") in columnnames:
1335 sharedata.barposcolumnnames.append(axisname + "name")
1336 found += 1
1337 if found > 1:
1338 raise ValueError("multiple names and value")
1339 if not found:
1340 raise ValueError("value/name missing")
1341 if sharedata.barvalueindex is None:
1342 raise ValueError("missing value")
1343 sharedata.vposmissing = []
1344 return sharedata.barposcolumnnames
1346 def addsubvalue(self, value, subvalue):
1347 try:
1348 value + ""
1349 except:
1350 try:
1351 return value[0], self.addsubvalue(value[1], subvalue)
1352 except:
1353 return value, subvalue
1354 else:
1355 return value, subvalue
1357 def adjustaxis(self, privatedata, sharedata, graph, columnname, data):
1358 try:
1359 i = sharedata.barposcolumnnames.index(columnname)
1360 except ValueError:
1361 pass
1362 else:
1363 if i == sharedata.barvalueindex:
1364 if self.fromvalue is not None:
1365 graph.axes[sharedata.barposcolumnnames[i]].adjustaxis([self.fromvalue])
1366 graph.axes[sharedata.barposcolumnnames[i]].adjustaxis(data)
1367 else:
1368 graph.axes[sharedata.barposcolumnnames[i][:-4]].adjustaxis([self.addsubvalue(x, 0) for x in data])
1369 graph.axes[sharedata.barposcolumnnames[i][:-4]].adjustaxis([self.addsubvalue(x, 1) for x in data])
1371 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1372 privatedata.insertfrompath = selectindex == 0
1374 def initdrawpoints(self, privatedata, sharedata, graph):
1375 sharedata.vpos = [None]*(len(sharedata.barposcolumnnames))
1376 sharedata.vbarrange = [[None for i in xrange(2)] for x in sharedata.barposcolumnnames]
1377 sharedata.stackedbar = sharedata.stackedbardraw = 0
1379 if self.fromvalue is not None:
1380 privatedata.vfromvalue = graph.axes[sharedata.barposcolumnnames[sharedata.barvalueindex]].convert(self.fromvalue)
1381 if privatedata.vfromvalue < 0:
1382 privatedata.vfromvalue = 0
1383 if privatedata.vfromvalue > 1:
1384 privatedata.vfromvalue = 1
1385 if self.frompathattrs is not None and privatedata.insertfrompath:
1386 graph.stroke(graph.axes[sharedata.barposcolumnnames[sharedata.barvalueindex]].vgridpath(privatedata.vfromvalue),
1387 self.defaultfrompathattrs + self.frompathattrs)
1388 else:
1389 privatedata.vfromvalue = 0
1391 def drawpoint(self, privatedata, sharedata, graph, point):
1392 sharedata.vposavailable = sharedata.vposvalid = 1
1393 for i, barname in enumerate(sharedata.barposcolumnnames):
1394 if i == sharedata.barvalueindex:
1395 sharedata.vbarrange[i][0] = privatedata.vfromvalue
1396 sharedata.lastbarvalue = point[barname]
1397 try:
1398 sharedata.vpos[i] = sharedata.vbarrange[i][1] = graph.axes[barname].convert(sharedata.lastbarvalue)
1399 except (ArithmeticError, ValueError, TypeError):
1400 sharedata.vpos[i] = sharedata.vbarrange[i][1] = None
1401 else:
1402 for j in xrange(2):
1403 try:
1404 sharedata.vbarrange[i][j] = graph.axes[barname[:-4]].convert(self.addsubvalue(point[barname], j))
1405 except (ArithmeticError, ValueError, TypeError):
1406 sharedata.vbarrange[i][j] = None
1407 try:
1408 sharedata.vpos[i] = 0.5*(sharedata.vbarrange[i][0]+sharedata.vbarrange[i][1])
1409 except (ArithmeticError, ValueError, TypeError):
1410 sharedata.vpos[i] = None
1411 if sharedata.vpos[i] is None:
1412 sharedata.vposavailable = sharedata.vposvalid = 0
1413 elif sharedata.vpos[i] < -self.epsilon or sharedata.vpos[i] > 1+self.epsilon:
1414 sharedata.vposvalid = 0
1416 registerdefaultprovider(barpos(), ["vbarrange", "barposcolumnnames", "barvalueindex", "lastbarvalue", "stackedbar", "stackedbardraw"])
1419 class stackedbarpos(_style):
1421 # provides no additional data, but needs some data (and modifies some of them)
1422 needsdata = ["vbarrange", "barposcolumnnames", "barvalueindex", "lastbarvalue", "stackedbar", "stackedbardraw"]
1424 def __init__(self, stackname, addontop=0, epsilon=1e-10):
1425 self.stackname = stackname
1426 self.epsilon = epsilon
1427 self.addontop = addontop
1429 def columnnames(self, privatedata, sharedata, graph, columnnames):
1430 if self.stackname not in columnnames:
1431 raise ValueError("column '%s' missing" % self.stackname)
1432 return [self.stackname]
1434 def adjustaxis(self, privatedata, sharedata, graph, columnname, data):
1435 if columnname == self.stackname:
1436 graph.axes[sharedata.barposcolumnnames[sharedata.barvalueindex]].adjustaxis(data)
1438 def initdrawpoints(self, privatedata, sharedata, graph):
1439 if sharedata.stackedbardraw: # do not count the start bar when not gets painted
1440 sharedata.stackedbar += 1
1442 def drawpoint(self, privatedata, sharedata, graph, point):
1443 sharedata.vbarrange[sharedata.barvalueindex][0] = sharedata.vbarrange[sharedata.barvalueindex][1]
1444 if self.addontop:
1445 try:
1446 sharedata.lastbarvalue += point[self.stackname]
1447 except (ArithmeticError, ValueError, TypeError):
1448 sharedata.lastbarvalue = None
1449 else:
1450 sharedata.lastbarvalue = point[self.stackname]
1451 try:
1452 sharedata.vpos[sharedata.barvalueindex] = sharedata.vbarrange[sharedata.barvalueindex][1] = graph.axes[sharedata.barposcolumnnames[sharedata.barvalueindex]].convert(sharedata.lastbarvalue)
1453 except (ArithmeticError, ValueError, TypeError):
1454 sharedata.vpos[sharedata.barvalueindex] = sharedata.vbarrange[sharedata.barvalueindex][1] = None
1455 sharedata.vposavailable = sharedata.vposvalid = 0
1456 else:
1457 if not sharedata.vposavailable or not sharedata.vposvalid:
1458 sharedata.vposavailable = sharedata.vposvalid = 1
1459 for v in sharedata.vpos:
1460 if v is None:
1461 sharedata.vposavailable = sharedata.vposvalid = 0
1462 break
1463 if v < -self.epsilon or v > 1+self.epsilon:
1464 sharedata.vposvalid = 0
1467 class bar(_style):
1469 needsdata = ["vbarrange"]
1471 defaultbarattrs = [color.gradient.Rainbow, deco.stroked([color.grey.black])]
1473 def __init__(self, barattrs=[], epsilon=1e-10, gradient=color.gradient.RedBlack):
1474 self.barattrs = barattrs
1475 self.epsilon = epsilon
1476 self.gradient = gradient
1478 def lighting(self, angle, zindex):
1479 return self.gradient.getcolor(0.7-0.4*abs(angle)+0.1*zindex)
1481 def columnnames(self, privatedata, sharedata, graph, columnnames):
1482 return []
1484 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1485 privatedata.barattrs = attr.selectattrs(self.defaultbarattrs + self.barattrs, selectindex, selecttotal)
1487 def initdrawpoints(self, privatedata, sharedata, graph):
1488 privatedata.barcanvas = graph.insert(canvas.canvas())
1489 sharedata.stackedbardraw = 1
1490 privatedata.stackedbar = sharedata.stackedbar
1491 privatedata.todraw = []
1493 def drawpointfill(self, privatedata, p):
1494 if p:
1495 privatedata.barcanvas.fill(p, privatedata.barattrs)
1497 def drawpoint(self, privatedata, sharedata, graph, point):
1498 vbarrange = []
1499 for vmin, vmax in sharedata.vbarrange:
1500 if vmin is None or vmax is None:
1501 self.drawpointfill(privatedata, None)
1502 return
1503 if vmin > vmax:
1504 vmin, vmax = vmax, vmin
1505 if vmin > 1 or vmax < 0:
1506 self.drawpointfill(privatedata, None)
1507 return
1508 if vmin < 0:
1509 vmin = 0
1510 if vmax > 1:
1511 vmax = 1
1512 vbarrange.append((vmin, vmax))
1513 if len(vbarrange) == 2:
1514 p = graph.vgeodesic(vbarrange[0][0], vbarrange[1][0], vbarrange[0][1], vbarrange[1][0])
1515 p.append(graph.vgeodesic_el(vbarrange[0][1], vbarrange[1][0], vbarrange[0][1], vbarrange[1][1]))
1516 p.append(graph.vgeodesic_el(vbarrange[0][1], vbarrange[1][1], vbarrange[0][0], vbarrange[1][1]))
1517 p.append(graph.vgeodesic_el(vbarrange[0][0], vbarrange[1][1], vbarrange[0][0], vbarrange[1][0]))
1518 p.append(path.closepath())
1519 self.drawpointfill(privatedata, p)
1520 elif len(vbarrange) == 3:
1521 planes = []
1522 if abs(vbarrange[0][0] - vbarrange[0][1]) > self.epsilon and abs(vbarrange[1][0] - vbarrange[1][1]):
1523 planes.append((vbarrange[0][0], vbarrange[1][0], vbarrange[2][0],
1524 vbarrange[0][1], vbarrange[1][0], vbarrange[2][0],
1525 vbarrange[0][1], vbarrange[1][1], vbarrange[2][0],
1526 vbarrange[0][0], vbarrange[1][1], vbarrange[2][0]))
1527 planes.append((vbarrange[0][0], vbarrange[1][0], vbarrange[2][1],
1528 vbarrange[0][0], vbarrange[1][1], vbarrange[2][1],
1529 vbarrange[0][1], vbarrange[1][1], vbarrange[2][1],
1530 vbarrange[0][1], vbarrange[1][0], vbarrange[2][1]))
1531 if abs(vbarrange[0][0] - vbarrange[0][1]) > self.epsilon and abs(vbarrange[2][0] - vbarrange[2][1]):
1532 planes.append((vbarrange[0][0], vbarrange[1][0], vbarrange[2][0],
1533 vbarrange[0][0], vbarrange[1][0], vbarrange[2][1],
1534 vbarrange[0][1], vbarrange[1][0], vbarrange[2][1],
1535 vbarrange[0][1], vbarrange[1][0], vbarrange[2][0]))
1536 planes.append((vbarrange[0][0], vbarrange[1][1], vbarrange[2][0],
1537 vbarrange[0][1], vbarrange[1][1], vbarrange[2][0],
1538 vbarrange[0][1], vbarrange[1][1], vbarrange[2][1],
1539 vbarrange[0][0], vbarrange[1][1], vbarrange[2][1]))
1540 if abs(vbarrange[1][0] - vbarrange[1][1]) > self.epsilon and abs(vbarrange[2][0] - vbarrange[2][1]):
1541 planes.append((vbarrange[0][0], vbarrange[1][0], vbarrange[2][0],
1542 vbarrange[0][0], vbarrange[1][1], vbarrange[2][0],
1543 vbarrange[0][0], vbarrange[1][1], vbarrange[2][1],
1544 vbarrange[0][0], vbarrange[1][0], vbarrange[2][1]))
1545 planes.append((vbarrange[0][1], vbarrange[1][0], vbarrange[2][0],
1546 vbarrange[0][1], vbarrange[1][0], vbarrange[2][1],
1547 vbarrange[0][1], vbarrange[1][1], vbarrange[2][1],
1548 vbarrange[0][1], vbarrange[1][1], vbarrange[2][0]))
1549 v = [0.5 * (vbarrange[0][0] + vbarrange[0][1]),
1550 0.5 * (vbarrange[1][0] + vbarrange[1][1]),
1551 0.5 * (vbarrange[2][0] + vbarrange[2][1])]
1552 v[sharedata.barvalueindex] = 0.5
1553 zindex = graph.vzindex(*v)
1554 for v11, v12, v13, v21, v22, v23, v31, v32, v33, v41, v42, v43 in planes:
1555 angle = graph.vangle(v11, v12, v13, v21, v22, v23, v41, v42, v43)
1556 if angle > 0:
1557 p = graph.vgeodesic(v11, v12, v13, v21, v22, v23)
1558 p.append(graph.vgeodesic_el(v21, v22, v23, v31, v32, v33))
1559 p.append(graph.vgeodesic_el(v31, v32, v33, v41, v42, v43))
1560 p.append(graph.vgeodesic_el(v41, v42, v43, v11, v12, v13))
1561 p.append(path.closepath())
1562 if self.gradient:
1563 privatedata.todraw.append((-zindex, p, privatedata.barattrs + [self.lighting(angle, zindex)]))
1564 else:
1565 privatedata.todraw.append((-zindex, p, privatedata.barattrs))
1566 else:
1567 raise TypeError("bar style restricted to two- and three dimensional graphs")
1569 def donedrawpoints(self, privatedata, sharedata, graph):
1570 privatedata.todraw.sort()
1571 for vzindex, p, a in privatedata.todraw:
1572 privatedata.barcanvas.fill(p, a)
1574 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
1575 selectindex = privatedata.stackedbar
1576 selecttotal = sharedata.stackedbar + 1
1577 graph.fill(path.rect_pt(x_pt + width_pt*selectindex/float(selecttotal), y_pt, width_pt/float(selecttotal), height_pt), privatedata.barattrs)
1580 class changebar(bar):
1582 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1583 if selecttotal != 1:
1584 raise RuntimeError("Changebar can't change its appearance. Thus you can't use it to plot several bars side by side on a subaxis.")
1586 def initdrawpoints(self, privatedata, sharedata, graph):
1587 if len(graph.axesnames) != 2:
1588 raise TypeError("changebar style restricted on two-dimensional graphs (at least for the moment)")
1589 bar.initdrawpoints(self, privatedata, sharedata, graph)
1590 privatedata.bars = []
1592 def drawpointfill(self, privatedata, p):
1593 privatedata.bars.append(p)
1595 def donedrawpoints(self, privatedata, sharedata, graph):
1596 selecttotal = len(privatedata.bars)
1597 for selectindex, p in enumerate(privatedata.bars):
1598 if p:
1599 barattrs = attr.selectattrs(self.defaultbarattrs + self.barattrs, selectindex, selecttotal)
1600 privatedata.barcanvas.fill(p, barattrs)
1602 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
1603 raise RuntimeError("Style currently doesn't provide a graph key")
1606 class gridpos(_style):
1608 needsdata = ["vpos", "vposmissing", "vposavailable", "vposvalid"]
1609 providesdata = ["values1", "values2", "data12", "data21", "index1", "index2"]
1611 def __init__(self, index1=0, index2=1, epsilon=1e-10):
1612 self.index1 = index1
1613 self.index2 = index2
1614 self.epsilon = epsilon
1616 def initdrawpoints(self, privatedata, sharedata, graph):
1617 sharedata.index1 = self.index1
1618 sharedata.index2 = self.index2
1619 sharedata.values1 = {}
1620 sharedata.values2 = {}
1621 sharedata.data12 = {}
1622 sharedata.data21 = {}
1624 def drawpoint(self, privatedata, sharedata, graph, point):
1625 if sharedata.vposavailable:
1626 sharedata.value1 = sharedata.vpos[self.index1]
1627 sharedata.value2 = sharedata.vpos[self.index2]
1628 if not sharedata.values1.has_key(sharedata.value1):
1629 for hasvalue in sharedata.values1.keys():
1630 if hasvalue - self.epsilon <= sharedata.value1 <= hasvalue + self.epsilon:
1631 sharedata.value1 = hasvalue
1632 break
1633 else:
1634 sharedata.values1[sharedata.value1] = 1
1635 if not sharedata.values2.has_key(sharedata.value2):
1636 for hasvalue in sharedata.values2.keys():
1637 if hasvalue - self.epsilon <= sharedata.value2 <= hasvalue + self.epsilon:
1638 sharedata.value2 = hasvalue
1639 break
1640 else:
1641 sharedata.values2[sharedata.value2] = 1
1642 data = sharedata.vposavailable, sharedata.vposvalid, sharedata.vpos[:]
1643 sharedata.data12.setdefault(sharedata.value1, {})[sharedata.value2] = data
1644 sharedata.data21.setdefault(sharedata.value2, {})[sharedata.value1] = data
1646 registerdefaultprovider(gridpos(), gridpos.providesdata)
1649 class grid(_line, _style):
1651 needsdata = ["values1", "values2", "data12", "data21"]
1653 defaultgridattrs = [line.changelinestyle]
1655 def __init__(self, gridlines1=1, gridlines2=1, gridattrs=[]):
1656 self.gridlines1 = gridlines1
1657 self.gridlines2 = gridlines2
1658 self.gridattrs = gridattrs
1660 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1661 if self.gridattrs is not None:
1662 privatedata.gridattrs = attr.selectattrs(self.defaultgridattrs + self.gridattrs, selectindex, selecttotal)
1663 else:
1664 privatedata.gridattrs = None
1666 def donedrawpoints(self, privatedata, sharedata, graph):
1667 values1 = sharedata.values1.keys()
1668 values1.sort()
1669 values2 = sharedata.values2.keys()
1670 values2.sort()
1671 if self.gridlines1:
1672 for value2 in values2:
1673 data1 = sharedata.data21[value2]
1674 self.initpointstopath(privatedata)
1675 for value1 in values1:
1676 try:
1677 data = data1[value1]
1678 except KeyError:
1679 self.addinvalid(privatedata)
1680 else:
1681 self.addpoint(privatedata, graph.vpos_pt, *data)
1682 p = self.donepointstopath(privatedata)
1683 if len(p):
1684 graph.stroke(p, privatedata.gridattrs)
1685 if self.gridlines2:
1686 for value1 in values1:
1687 data2 = sharedata.data12[value1]
1688 self.initpointstopath(privatedata)
1689 for value2 in values2:
1690 try:
1691 data = data2[value2]
1692 except KeyError:
1693 self.addinvalid(privatedata)
1694 else:
1695 self.addpoint(privatedata, graph.vpos_pt, *data)
1696 p = self.donepointstopath(privatedata)
1697 if len(p):
1698 graph.stroke(p, privatedata.gridattrs)
1701 class surface(_style):
1703 needsdata = ["values1", "values2", "data12", "data21"]
1705 def __init__(self, colorname="color", gradient=color.gradient.Grey, mincolor=None, maxcolor=None,
1706 gridlines1=0.05, gridlines2=0.05, gridcolor=None,
1707 backcolor=color.gray.black):
1708 self.colorname = colorname
1709 self.gradient = gradient
1710 self.mincolor = mincolor
1711 self.maxcolor = maxcolor
1712 self.gridlines1 = gridlines1
1713 self.gridlines2 = gridlines2
1714 self.gridcolor = gridcolor
1715 self.backcolor = backcolor
1717 colorspacestring = gradient.getcolor(0).colorspacestring()
1718 if self.gridcolor is not None and self.gridcolor.colorspacestring() != colorspacestring:
1719 raise RuntimeError("colorspace mismatch (gradient/grid)")
1720 if self.backcolor is not None and self.backcolor.colorspacestring() != colorspacestring:
1721 raise RuntimeError("colorspace mismatch (gradient/back)")
1723 def midvalue(self, v1, v2, v3, v4):
1724 return [0.25*sum(values) for values in zip(v1, v2, v3, v4)]
1726 def midcolor(self, c1, c2, c3, c4):
1727 return 0.25*(c1+c2+c3+c4)
1729 def lighting(self, angle, zindex):
1730 if angle < 0 and self.backcolor is not None:
1731 return self.backcolor
1732 return self.gradient.getcolor(0.7-0.4*abs(angle)+0.1*zindex)
1734 def columnnames(self, privatedata, sharedata, graph, columnnames):
1735 privatedata.colorize = self.colorname in columnnames
1736 if privatedata.colorize:
1737 return [self.colorname]
1738 return []
1740 def initdrawpoints(self, privatedata, sharedata, graph):
1741 privatedata.colors = {}
1742 privatedata.mincolor = privatedata.maxcolor = None
1744 def drawpoint(self, privatedata, sharedata, graph, point):
1745 if privatedata.colorize:
1746 try:
1747 color = point[self.colorname] + 0
1748 except:
1749 pass
1750 else:
1751 privatedata.colors.setdefault(sharedata.value1, {})[sharedata.value2] = color
1752 if privatedata.mincolor is None or color < privatedata.mincolor:
1753 privatedata.mincolor = color
1754 if privatedata.mincolor is None or privatedata.maxcolor < color:
1755 privatedata.maxcolor = color
1757 def donedrawpoints(self, privatedata, sharedata, graph):
1758 v1 = [0]*len(graph.axesnames)
1759 v2 = [0]*len(graph.axesnames)
1760 v3 = [0]*len(graph.axesnames)
1761 v4 = [0]*len(graph.axesnames)
1762 v1[sharedata.index2] = 0.5
1763 v2[sharedata.index1] = 0.5
1764 v3[sharedata.index1] = 0.5
1765 v3[sharedata.index2] = 1
1766 v4[sharedata.index1] = 1
1767 v4[sharedata.index2] = 0.5
1768 sortElements = [-graph.vzindex(*v1),
1769 -graph.vzindex(*v2),
1770 -graph.vzindex(*v3),
1771 -graph.vzindex(*v4)]
1773 values1 = sharedata.values1.keys()
1774 values1.sort()
1775 v1 = [0]*len(graph.axesnames)
1776 v2 = [0]*len(graph.axesnames)
1777 v1[sharedata.index1] = -1
1778 v2[sharedata.index1] = 1
1779 sign = 1
1780 if graph.vzindex(*v1) < graph.vzindex(*v2):
1781 values1.reverse()
1782 sign *= -1
1783 sortElements = [sortElements[3], sortElements[1], sortElements[2], sortElements[0]]
1785 values2 = sharedata.values2.keys()
1786 values2.sort()
1787 v1 = [0]*len(graph.axesnames)
1788 v2 = [0]*len(graph.axesnames)
1789 v1[sharedata.index2] = -1
1790 v2[sharedata.index2] = 1
1791 if graph.vzindex(*v1) < graph.vzindex(*v2):
1792 values2.reverse()
1793 sign *= -1
1794 sortElements = [sortElements[0], sortElements[2], sortElements[1], sortElements[3]]
1796 sortElements = [(zindex, i) for i, zindex in enumerate(sortElements)]
1797 sortElements.sort()
1799 mincolor, maxcolor = privatedata.mincolor, privatedata.maxcolor
1800 if self.mincolor is not None:
1801 mincolor = self.mincolor
1802 if self.maxcolor is not None:
1803 maxcolor = self.maxcolor
1804 nodes = []
1805 elements = []
1806 for value1a, value1b in zip(values1[:-1], values1[1:]):
1807 for value2a, value2b in zip(values2[:-1], values2[1:]):
1808 try:
1809 available1, valid1, v1 = sharedata.data12[value1a][value2a]
1810 available2, valid2, v2 = sharedata.data12[value1a][value2b]
1811 available3, valid3, v3 = sharedata.data12[value1b][value2a]
1812 available4, valid4, v4 = sharedata.data12[value1b][value2b]
1813 except KeyError:
1814 continue
1815 if not available1 or not available2 or not available3 or not available4:
1816 continue
1817 if not valid1 or not valid2 or not valid3 or not valid4:
1818 warnings.warn("surface elements partially outside of the graph are (currently) skipped completely")
1819 continue
1820 def shrink(index, v1, v2, by):
1821 v1 = v1[:]
1822 v2 = v2[:]
1823 for i in builtinrange(3):
1824 if i != index:
1825 v1[i], v2[i] = v1[i] + by*(v2[i]-v1[i]), v2[i] + by*(v1[i]-v2[i])
1826 return v1, v2
1827 v1f, v2f, v3f, v4f = v1, v2, v3, v4
1828 if self.gridcolor is not None and self.gridlines1:
1829 v1, v2 = shrink(sharedata.index1, v1, v2, self.gridlines1)
1830 v3, v4 = shrink(sharedata.index1, v3, v4, self.gridlines1)
1831 if self.gridcolor is not None and self.gridlines2:
1832 v1, v3 = shrink(sharedata.index2, v1, v3, self.gridlines2)
1833 v2, v4 = shrink(sharedata.index2, v2, v4, self.gridlines2)
1834 v5 = self.midvalue(v1, v2, v3, v4)
1835 x1_pt, y1_pt = graph.vpos_pt(*v1)
1836 x2_pt, y2_pt = graph.vpos_pt(*v2)
1837 x3_pt, y3_pt = graph.vpos_pt(*v3)
1838 x4_pt, y4_pt = graph.vpos_pt(*v4)
1839 x5_pt, y5_pt = graph.vpos_pt(*v5)
1840 if privatedata.colorize:
1841 def colorfromgradient(c):
1842 vc = (c - mincolor) / float(maxcolor - mincolor)
1843 if vc < 0:
1844 warnings.warn("gradiend color range is exceeded due to mincolor setting")
1845 vc = 0
1846 if vc > 1:
1847 warnings.warn("gradiend color range is exceeded due to maxcolor setting")
1848 vc = 1
1849 return self.gradient.getcolor(vc)
1850 c1 = privatedata.colors[value1a][value2a]
1851 c2 = privatedata.colors[value1a][value2b]
1852 c3 = privatedata.colors[value1b][value2a]
1853 c4 = privatedata.colors[value1b][value2b]
1854 c5 = self.midcolor(c1, c2, c3, c4)
1855 c1a = c1b = colorfromgradient(c1)
1856 c2a = c2c = colorfromgradient(c2)
1857 c3b = c3d = colorfromgradient(c3)
1858 c4c = c4d = colorfromgradient(c4)
1859 c5a = c5b = c5c = c5d = colorfromgradient(c5)
1860 if self.backcolor is not None and sign*graph.vangle(*(v1+v2+v5)) < 0:
1861 c1a = c2a = c5a = self.backcolor
1862 if self.backcolor is not None and sign*graph.vangle(*(v3+v1+v5)) < 0:
1863 c3b = c1b = c5b = self.backcolor
1864 if self.backcolor is not None and sign*graph.vangle(*(v2+v4+v5)) < 0:
1865 c2c = c4c = c5c = self.backcolor
1866 if self.backcolor is not None and sign*graph.vangle(*(v4+v3+v5)) < 0:
1867 c4d = c3d = c5d = self.backcolor
1868 else:
1869 zindex = graph.vzindex(*v5)
1870 c1a = c2a = c5a = self.lighting(sign*graph.vangle(*(v1+v2+v5)), zindex)
1871 c3b = c1b = c5b = self.lighting(sign*graph.vangle(*(v3+v1+v5)), zindex)
1872 c2c = c4c = c5c = self.lighting(sign*graph.vangle(*(v2+v4+v5)), zindex)
1873 c4d = c3d = c5d = self.lighting(sign*graph.vangle(*(v4+v3+v5)), zindex)
1874 for zindex, i in sortElements:
1875 if i == 0:
1876 elements.append(mesh.element((mesh.node_pt((x1_pt, y1_pt), c1a),
1877 mesh.node_pt((x2_pt, y2_pt), c2a),
1878 mesh.node_pt((x5_pt, y5_pt), c5a))))
1879 if self.gridcolor is not None and self.gridlines2:
1880 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v1f), self.gridcolor),
1881 mesh.node_pt(graph.vpos_pt(*v2), self.gridcolor),
1882 mesh.node_pt(graph.vpos_pt(*v1), self.gridcolor))))
1883 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v1f), self.gridcolor),
1884 mesh.node_pt(graph.vpos_pt(*v2), self.gridcolor),
1885 mesh.node_pt(graph.vpos_pt(*v2f), self.gridcolor))))
1886 elif i == 1:
1887 elements.append(mesh.element((mesh.node_pt((x3_pt, y3_pt), c3b),
1888 mesh.node_pt((x1_pt, y1_pt), c1b),
1889 mesh.node_pt((x5_pt, y5_pt), c5b))))
1890 if self.gridcolor is not None and self.gridlines1:
1891 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v1f), self.gridcolor),
1892 mesh.node_pt(graph.vpos_pt(*v3), self.gridcolor),
1893 mesh.node_pt(graph.vpos_pt(*v1), self.gridcolor))))
1894 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v1f), self.gridcolor),
1895 mesh.node_pt(graph.vpos_pt(*v3), self.gridcolor),
1896 mesh.node_pt(graph.vpos_pt(*v3f), self.gridcolor))))
1897 elif i == 2:
1898 elements.append(mesh.element((mesh.node_pt((x2_pt, y2_pt), c2c),
1899 mesh.node_pt((x4_pt, y4_pt), c4c),
1900 mesh.node_pt((x5_pt, y5_pt), c5c))))
1901 if self.gridcolor is not None and self.gridlines1:
1902 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v2f), self.gridcolor),
1903 mesh.node_pt(graph.vpos_pt(*v4), self.gridcolor),
1904 mesh.node_pt(graph.vpos_pt(*v2), self.gridcolor))))
1905 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v2f), self.gridcolor),
1906 mesh.node_pt(graph.vpos_pt(*v4), self.gridcolor),
1907 mesh.node_pt(graph.vpos_pt(*v4f), self.gridcolor))))
1908 elif i == 3:
1909 elements.append(mesh.element((mesh.node_pt((x4_pt, y4_pt), c4d),
1910 mesh.node_pt((x3_pt, y3_pt), c3d),
1911 mesh.node_pt((x5_pt, y5_pt), c5d))))
1912 if self.gridcolor is not None and self.gridlines2:
1913 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v3f), self.gridcolor),
1914 mesh.node_pt(graph.vpos_pt(*v4), self.gridcolor),
1915 mesh.node_pt(graph.vpos_pt(*v3), self.gridcolor))))
1916 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v3f), self.gridcolor),
1917 mesh.node_pt(graph.vpos_pt(*v4), self.gridcolor),
1918 mesh.node_pt(graph.vpos_pt(*v4f), self.gridcolor))))
1919 m = mesh.mesh(elements, check=0)
1920 graph.insert(m)
1923 class bitmap(_style):
1925 needsdata = ["values1", "values2", "data12", "data21"]
1927 def __init__(self, colorname="color", gradient=color.gradient.Grey, mincolor=None, maxcolor=None, epsilon=1e-10):
1928 self.colorname = colorname
1929 self.gradient = gradient
1930 self.mincolor = mincolor
1931 self.maxcolor = maxcolor
1932 self.epsilon = epsilon
1934 def columnnames(self, privatedata, sharedata, graph, columnnames):
1935 return [self.colorname]
1937 def initdrawpoints(self, privatedata, sharedata, graph):
1938 privatedata.colors = {}
1939 privatedata.mincolor = privatedata.maxcolor = None
1940 privatedata.vfixed = [None]*len(graph.axesnames)
1942 def drawpoint(self, privatedata, sharedata, graph, point):
1943 try:
1944 color = point[self.colorname] + 0
1945 except:
1946 pass
1947 else:
1948 privatedata.colors.setdefault(sharedata.value1, {})[sharedata.value2] = color
1949 if privatedata.mincolor is None or color < privatedata.mincolor:
1950 privatedata.mincolor = color
1951 if privatedata.mincolor is None or privatedata.maxcolor < color:
1952 privatedata.maxcolor = color
1953 if len(privatedata.vfixed) > 2 and sharedata.vposavailable:
1954 for i, (v1, v2) in enumerate(zip(privatedata.vfixed, sharedata.vpos)):
1955 if i != sharedata.index1 and i != sharedata.index2:
1956 if v1 is None:
1957 privatedata.vfixed[i] = v2
1958 elif abs(v1-v2) > self.epsilon:
1959 raise ValueError("data must be in a plane for the bitmap style")
1961 def donedrawpoints(self, privatedata, sharedata, graph):
1962 mincolor, maxcolor = privatedata.mincolor, privatedata.maxcolor
1963 if self.mincolor is not None:
1964 mincolor = self.mincolor
1965 if self.maxcolor is not None:
1966 maxcolor = self.maxcolor
1968 values1 = pycompat.sorted(sharedata.values1.keys())
1969 values2 = pycompat.sorted(sharedata.values2.keys())
1970 def equidistant(values):
1971 l = len(values) - 1
1972 if l < 1:
1973 raise ValueError("several data points required by the bitmap style in each dimension")
1974 range = values[-1] - values[0]
1975 for i, value in enumerate(values):
1976 if abs(value - values[0] - i * range / l) > self.epsilon:
1977 raise ValueError("data must be equidistant for the bitmap style")
1978 equidistant(values1)
1979 equidistant(values2)
1980 needalpha = False
1981 for value2 in values2:
1982 for value1 in values1:
1983 try:
1984 available, valid, v = sharedata.data12[value1][value2]
1985 except KeyError:
1986 needalpha = True
1987 break
1988 if not available:
1989 needalpha = True
1990 continue
1991 else:
1992 continue
1993 break
1994 mode = {"/DeviceGray": "L",
1995 "/DeviceRGB": "RGB",
1996 "/DeviceCMYK": "CMYK"}[self.gradient.getcolor(0).colorspacestring()]
1997 if needalpha:
1998 mode = "A" + mode
1999 empty = "\0"*len(mode)
2000 data = cStringIO.StringIO()
2001 for value2 in values2:
2002 for value1 in values1:
2003 try:
2004 available, valid, v = sharedata.data12[value1][value2]
2005 except KeyError:
2006 data.write(empty)
2007 continue
2008 if not available:
2009 data.write(empty)
2010 continue
2011 c = privatedata.colors[value1][value2]
2012 vc = (c - mincolor) / float(maxcolor - mincolor)
2013 if vc < 0:
2014 warnings.warn("gradiend color range is exceeded due to mincolor setting")
2015 vc = 0
2016 if vc > 1:
2017 warnings.warn("gradiend color range is exceeded due to maxcolor setting")
2018 vc = 1
2019 c = self.gradient.getcolor(vc)
2020 if needalpha:
2021 data.write(chr(255))
2022 data.write(c.to8bitstring())
2023 i = bitmapmodule.image(len(values1), len(values2), mode, data.getvalue())
2025 v1enlargement = (values1[-1]-values1[0])*0.5/len(values1)
2026 v2enlargement = (values2[-1]-values2[0])*0.5/len(values2)
2028 privatedata.vfixed[sharedata.index1] = values1[0]-v1enlargement
2029 privatedata.vfixed[sharedata.index2] = values2[-1]+v2enlargement
2030 x1_pt, y1_pt = graph.vpos_pt(*privatedata.vfixed)
2031 privatedata.vfixed[sharedata.index1] = values1[-1]+v1enlargement
2032 privatedata.vfixed[sharedata.index2] = values2[-1]+v2enlargement
2033 x2_pt, y2_pt = graph.vpos_pt(*privatedata.vfixed)
2034 privatedata.vfixed[sharedata.index1] = values1[0]-v1enlargement
2035 privatedata.vfixed[sharedata.index2] = values2[0]-v2enlargement
2036 x3_pt, y3_pt = graph.vpos_pt(*privatedata.vfixed)
2037 t = trafo.trafo_pt(((x2_pt-x1_pt, x3_pt-x1_pt), (y2_pt-y1_pt, y3_pt-y1_pt)), (x1_pt, y1_pt))
2039 privatedata.vfixed[sharedata.index1] = values1[-1]+v1enlargement
2040 privatedata.vfixed[sharedata.index2] = values2[0]-v2enlargement
2041 vx4, vy4 = t.inverse().apply_pt(*graph.vpos_pt(*privatedata.vfixed))
2042 if abs(vx4 - 1) > self.epsilon or abs(vy4 - 1) > self.epsilon:
2043 raise ValueError("invalid graph layout for bitmap style (bitmap positioning by affine transformation failed)")
2045 p = path.path()
2046 privatedata.vfixed[sharedata.index1] = 0
2047 privatedata.vfixed[sharedata.index2] = 0
2048 p.append(path.moveto_pt(*graph.vpos_pt(*privatedata.vfixed)))
2049 vfixed2 = privatedata.vfixed + privatedata.vfixed
2050 vfixed2[sharedata.index1] = 0
2051 vfixed2[sharedata.index2] = 0
2052 vfixed2[sharedata.index1 + len(graph.axesnames)] = 1
2053 vfixed2[sharedata.index2 + len(graph.axesnames)] = 0
2054 p.append(graph.vgeodesic_el(*vfixed2))
2055 vfixed2[sharedata.index1] = 1
2056 vfixed2[sharedata.index2] = 0
2057 vfixed2[sharedata.index1 + len(graph.axesnames)] = 1
2058 vfixed2[sharedata.index2 + len(graph.axesnames)] = 1
2059 p.append(graph.vgeodesic_el(*vfixed2))
2060 vfixed2[sharedata.index1] = 1
2061 vfixed2[sharedata.index2] = 1
2062 vfixed2[sharedata.index1 + len(graph.axesnames)] = 0
2063 vfixed2[sharedata.index2 + len(graph.axesnames)] = 1
2064 p.append(graph.vgeodesic_el(*vfixed2))
2065 vfixed2[sharedata.index1] = 0
2066 vfixed2[sharedata.index2] = 1
2067 vfixed2[sharedata.index1 + len(graph.axesnames)] = 0
2068 vfixed2[sharedata.index2 + len(graph.axesnames)] = 0
2069 p.append(graph.vgeodesic_el(*vfixed2))
2070 p.append(path.closepath())
2072 c = canvas.canvas([canvas.clip(p)])
2073 b = bitmapmodule.bitmap_trafo(t, i)
2074 c.insert(b)
2075 graph.insert(c)