tagging 0.12.1
[PyX.git] / graph / style.py
blob6b8d23a717d416dc48a658a0f2c6d6aad0af0113
1 # -*- encoding: utf-8 -*-
4 # Copyright (C) 2002-2012 Jörg Lehmann <joergl@users.sourceforge.net>
5 # Copyright (C) 2003-2004 Michael Schindler <m-schindler@users.sourceforge.net>
6 # Copyright (C) 2002-2012 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, bitmap, style, color, unit, canvas, path, mesh, pycompat, trafo
27 from pyx import text as textmodule
28 from graph import registerdefaultprovider, graphx
29 import data as datamodule
30 import axis
32 builtinrange = range
35 class _style:
36 """Interface class for graph styles
38 Each graph style must support the methods described in this
39 class. However, since a graph style might not need to perform
40 actions on all the various events, it does not need to overwrite
41 all methods of this base class (e.g. this class is not an abstract
42 class in any respect).
44 A style should never store private data by instance variables
45 (i.e. accessing self), but it should use the sharedata and privatedata
46 instances instead. A style instance can be used multiple times with
47 different sharedata and privatedata instances at the very same time.
48 The sharedata and privatedata instances act as data containers and
49 sharedata allows for sharing information across several styles.
51 Every style contains two class variables, which are not to be
52 modified:
53 - providesdata is a list of variable names a style offers via
54 the sharedata instance. This list is used to determine whether
55 all needs of subsequent styles are fulfilled. Otherwise
56 getdefaultprovider should return a proper style to be used.
57 - needsdata is a list of variable names the style needs to access in the
58 sharedata instance.
59 """
61 providesdata = [] # by default, we provide nothing
62 needsdata = [] # and do not depend on anything
64 def columnnames(self, privatedata, sharedata, graph, columnnames, dataaxisnames):
65 """Set column information
67 This method is used setup the column name information to be
68 accessible to the style later on. The style should analyse
69 the list of column names. The method should return a list of
70 column names which the style will make use of. If a style
71 uses some column data to feed into an axis with a different
72 name, it should add an entry into the dataaxisnames dictionary
73 with key begin the column name and the value being the axis
74 name."""
75 return []
77 def adjustaxis(self, privatedata, sharedata, graph, plotitem, columnname, data):
78 """Adjust axis range
80 This method is called in order to adjust the axis range to
81 the provided data. columnname is the column name (each style
82 is subsequently called for all column names)."""
83 pass
85 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
86 """Select stroke/fill attributes
88 This method is called to allow for the selection of
89 changable attributes of a style."""
90 pass
92 def initdrawpoints(self, privatedata, sharedata, graph):
93 """Initialize drawing of data
95 This method might be used to initialize the drawing of data."""
96 pass
98 def drawpoint(self, privatedata, sharedata, graph, point):
99 """Draw data
101 This method is called for each data point. The data is
102 available in the dictionary point. The dictionary
103 keys are the column names."""
104 pass
106 def donedrawpoints(self, privatedata, sharedata, graph):
107 """Finalize drawing of data
109 This method is called after the last data point was
110 drawn using the drawpoint method above."""
111 pass
113 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
114 """Draw graph key"""
115 pass
118 class _marker: pass
119 class _autokeygraph: pass
122 class _keygraphstyle(_style):
124 autographkey = _autokeygraph
126 def __init__(self, colorname="color", gradient=color.gradient.Grey, coloraxis=None, keygraph=_autokeygraph):
127 self.colorname = colorname
128 self.gradient = gradient
129 self.coloraxis = coloraxis
130 self.keygraph = keygraph
132 def columnnames(self, privatedata, sharedata, graph, columnnames, dataaxisnames):
133 return [self.colorname]
135 def adjustaxis(self, privatedata, sharedata, graph, plotitem, columnname, data):
136 if columnname == self.colorname:
137 if self.keygraph is None:
138 # we always need a keygraph, but we might not show it
139 if self.coloraxis is None:
140 coloraxis = axis.lin()
141 else:
142 coloraxis = self.coloraxis
143 privatedata.keygraph = graphx(length=10, direction="vertical", x=coloraxis)
144 elif self.keygraph is _autokeygraph:
145 if self.coloraxis is None:
146 coloraxis = axis.lin(title=plotitem.title)
147 plotitem.title = None # Huui!?
148 else:
149 coloraxis = self.coloraxis
150 privatedata.keygraph = graphx(x=coloraxis, **graph.autokeygraphattrs())
151 else:
152 privatedata.keygraph = self.keygraph
153 # TODO: we shouldn't have multiple plotitems
154 privatedata.keygraph.plot(datamodule.values(x=data), [gradient(gradient=self.gradient)])
156 def color(self, privatedata, c):
157 vc = privatedata.keygraph.axes["x"].convert(c)
158 if vc < 0:
159 warnings.warn("gradiend color range is exceeded (lower bound)")
160 vc = 0
161 if vc > 1:
162 warnings.warn("gradiend color range is exceeded (upper bound)")
163 vc = 1
164 return self.gradient.getcolor(vc)
166 def donedrawpoints(self, privatedata, sharedata, graph):
167 if self.keygraph is _autokeygraph:
168 graph.layer("key").insert(privatedata.keygraph, [graph.autokeygraphtrafo(privatedata.keygraph)])
171 class pos(_style):
173 providesdata = ["vpos", "vposmissing", "vposavailable", "vposvalid", "poscolumnnames"]
175 def __init__(self, usenames={}, epsilon=1e-10):
176 self.usenames = usenames
177 self.epsilon = epsilon
179 def columnnames(self, privatedata, sharedata, graph, columnnames, dataaxisnames):
180 privatedata.poscolumnnames = []
181 privatedata.vposmissing = []
182 privatedata.axisnames = {}
183 for count, axisnames in enumerate(graph.axesnames):
184 for axisname in axisnames:
185 try:
186 usename = self.usenames[axisname]
187 except KeyError:
188 usename = axisname
189 for columnname in columnnames:
190 if usename == columnname:
191 privatedata.poscolumnnames.append(columnname)
192 privatedata.axisnames[columnname] = axisname
193 if len(privatedata.poscolumnnames) > count+1:
194 raise ValueError("multiple axes per graph dimension")
195 elif len(privatedata.poscolumnnames) < count+1:
196 privatedata.vposmissing.append(count)
197 privatedata.poscolumnnames.append(None)
198 # Make poscolumnnames and vposmissing available to the outside,
199 # but keep a private reference. A copy is not needed, because
200 # the data is not altered in place, but might be exchanged my a
201 # later, different pos style in the styles list (due to different
202 # usenames).
203 sharedata.poscolumnnames = privatedata.poscolumnnames
204 sharedata.vposmissing = privatedata.vposmissing
205 dataaxisnames.update(privatedata.axisnames)
206 return [columnname for columnname in privatedata.poscolumnnames if columnname is not None]
208 def adjustaxis(self, privatedata, sharedata, graph, plotitem, columnname, data):
209 if columnname in privatedata.axisnames:
210 graph.axes[privatedata.axisnames[columnname]].adjustaxis(data)
212 def initdrawpoints(self, privatedata, sharedata, graph):
213 sharedata.vpos = [None]*(len(graph.axesnames))
214 privatedata.pointpostmplist = [[columnname, index, graph.axes[privatedata.axisnames[columnname]]] # temporarily used by drawpoint only
215 for index, columnname in enumerate([columnname for columnname in privatedata.poscolumnnames if columnname is not None])]
216 for missing in privatedata.vposmissing:
217 for pointpostmp in privatedata.pointpostmplist:
218 if pointpostmp[1] >= missing:
219 pointpostmp[1] += 1
221 def drawpoint(self, privatedata, sharedata, graph, point):
222 sharedata.vposavailable = 1 # valid position (but might be outside of the graph)
223 sharedata.vposvalid = 1 # valid position inside the graph
224 for columnname, index, axis in privatedata.pointpostmplist:
225 try:
226 v = axis.convert(point[columnname])
227 except (ArithmeticError, ValueError, TypeError):
228 sharedata.vposavailable = sharedata.vposvalid = 0
229 sharedata.vpos[index] = None
230 else:
231 if v < -self.epsilon or v > 1+self.epsilon:
232 sharedata.vposvalid = 0
233 sharedata.vpos[index] = v
236 registerdefaultprovider(pos(), pos.providesdata)
239 class range(_style):
241 providesdata = ["vrange", "vrangemissing", "vrangeminmissing", "vrangemaxmissing"]
243 # internal bit masks
244 mask_value = 1
245 mask_min = 2
246 mask_max = 4
247 mask_dmin = 8
248 mask_dmax = 16
249 mask_d = 32
251 def __init__(self, usenames={}, epsilon=1e-10):
252 self.usenames = usenames
253 self.epsilon = epsilon
255 def _numberofbits(self, mask):
256 if not mask:
257 return 0
258 if mask & 1:
259 return self._numberofbits(mask >> 1) + 1
260 else:
261 return self._numberofbits(mask >> 1)
263 def columnnames(self, privatedata, sharedata, graph, columnnames, dataaxisnames):
264 usecolumns = []
265 privatedata.rangeposcolumns = []
266 sharedata.vrangemissing = []
267 sharedata.vrangeminmissing = []
268 sharedata.vrangemaxmissing = []
269 privatedata.rangeposdeltacolumns = {} # temporarily used by adjustaxis only
270 for count, axisnames in enumerate(graph.axesnames):
271 for axisname in axisnames:
272 try:
273 usename = self.usenames[axisname]
274 except KeyError:
275 usename = axisname
276 mask = 0
277 for columnname in columnnames:
278 addusecolumns = 1
279 if usename == columnname:
280 mask += self.mask_value
281 elif usename + "min" == columnname:
282 mask += self.mask_min
283 elif usename + "max" == columnname:
284 mask += self.mask_max
285 elif "d" + usename + "min" == columnname:
286 mask += self.mask_dmin
287 elif "d" + usename + "max" == columnname:
288 mask += self.mask_dmax
289 elif "d" + usename == columnname:
290 mask += self.mask_d
291 else:
292 addusecolumns = 0
293 if addusecolumns:
294 usecolumns.append(columnname)
295 dataaxisnames[columnname] = axisname
296 if mask & (self.mask_min | self.mask_max | self.mask_dmin | self.mask_dmax | self.mask_d):
297 if (self._numberofbits(mask & (self.mask_min | self.mask_dmin | self.mask_d)) > 1 or
298 self._numberofbits(mask & (self.mask_max | self.mask_dmax | self.mask_d)) > 1):
299 raise ValueError("multiple range definition")
300 if mask & (self.mask_dmin | self.mask_dmax | self.mask_d):
301 if not (mask & self.mask_value):
302 raise ValueError("missing value for delta")
303 privatedata.rangeposdeltacolumns[axisname] = {}
304 privatedata.rangeposcolumns.append((axisname, usename, mask))
305 elif mask == self.mask_value:
306 usecolumns = usecolumns[:-1]
307 if len(privatedata.rangeposcolumns) + len(sharedata.vrangemissing) > count+1:
308 raise ValueError("multiple axes per graph dimension")
309 elif len(privatedata.rangeposcolumns) + len(sharedata.vrangemissing) < count+1:
310 sharedata.vrangemissing.append(count)
311 sharedata.vrangeminmissing.append(count)
312 sharedata.vrangemaxmissing.append(count)
313 else:
314 if not (privatedata.rangeposcolumns[-1][2] & (self.mask_min | self.mask_dmin | self.mask_d)):
315 sharedata.vrangeminmissing.append(count)
316 if not (privatedata.rangeposcolumns[-1][2] & (self.mask_max | self.mask_dmax | self.mask_d)):
317 sharedata.vrangemaxmissing.append(count)
318 return usecolumns
320 def adjustaxis(self, privatedata, sharedata, graph, plotitem, columnname, data):
321 for axisname, usename, mask in privatedata.rangeposcolumns:
322 if columnname == usename + "min" and mask & self.mask_min:
323 graph.axes[axisname].adjustaxis(data)
324 if columnname == usename + "max" and mask & self.mask_max:
325 graph.axes[axisname].adjustaxis(data)
327 # delta handling: fill rangeposdeltacolumns
328 for axisname, usename, mask in privatedata.rangeposcolumns:
329 if columnname == usename and mask & (self.mask_dmin | self.mask_dmax | self.mask_d):
330 privatedata.rangeposdeltacolumns[axisname][self.mask_value] = data
331 if columnname == "d" + usename + "min" and mask & self.mask_dmin:
332 privatedata.rangeposdeltacolumns[axisname][self.mask_dmin] = data
333 if columnname == "d" + usename + "max" and mask & self.mask_dmax:
334 privatedata.rangeposdeltacolumns[axisname][self.mask_dmax] = data
335 if columnname == "d" + usename and mask & self.mask_d:
336 privatedata.rangeposdeltacolumns[axisname][self.mask_d] = data
338 # delta handling: process rangeposdeltacolumns
339 for a, d in privatedata.rangeposdeltacolumns.items():
340 if d.has_key(self.mask_value):
341 for k in d.keys():
342 if k != self.mask_value:
343 if k & (self.mask_dmin | self.mask_d):
344 mindata = []
345 for value, delta in zip(d[self.mask_value], d[k]):
346 try:
347 mindata.append(value-delta)
348 except:
349 pass
350 graph.axes[a].adjustaxis(mindata)
351 if k & (self.mask_dmax | self.mask_d):
352 maxdata = []
353 for value, delta in zip(d[self.mask_value], d[k]):
354 try:
355 maxdata.append(value+delta)
356 except:
357 pass
358 graph.axes[a].adjustaxis(maxdata)
359 del d[k]
361 def initdrawpoints(self, privatedata, sharedata, graph):
362 sharedata.vrange = [[None for x in xrange(2)] for y in privatedata.rangeposcolumns + sharedata.vrangemissing]
363 privatedata.rangepostmplist = [[usename, mask, index, graph.axes[axisname]] # temporarily used by drawpoint only
364 for index, (axisname, usename, mask) in enumerate(privatedata.rangeposcolumns)]
365 for missing in sharedata.vrangemissing:
366 for rangepostmp in privatedata.rangepostmplist:
367 if rangepostmp[2] >= missing:
368 rangepostmp[2] += 1
370 def drawpoint(self, privatedata, sharedata, graph, point):
371 for usename, mask, index, axis in privatedata.rangepostmplist:
372 try:
373 if mask & self.mask_min:
374 sharedata.vrange[index][0] = axis.convert(point[usename + "min"])
375 if mask & self.mask_dmin:
376 sharedata.vrange[index][0] = axis.convert(point[usename] - point["d" + usename + "min"])
377 if mask & self.mask_d:
378 sharedata.vrange[index][0] = axis.convert(point[usename] - point["d" + usename])
379 except (ArithmeticError, ValueError, TypeError):
380 sharedata.vrange[index][0] = None
381 try:
382 if mask & self.mask_max:
383 sharedata.vrange[index][1] = axis.convert(point[usename + "max"])
384 if mask & self.mask_dmax:
385 sharedata.vrange[index][1] = axis.convert(point[usename] + point["d" + usename + "max"])
386 if mask & self.mask_d:
387 sharedata.vrange[index][1] = axis.convert(point[usename] + point["d" + usename])
388 except (ArithmeticError, ValueError, TypeError):
389 sharedata.vrange[index][1] = None
391 # some range checks for data consistency
392 if (sharedata.vrange[index][0] is not None and sharedata.vrange[index][1] is not None and
393 sharedata.vrange[index][0] > sharedata.vrange[index][1] + self.epsilon):
394 raise ValueError("inverse range")
395 # disabled due to missing vpos access:
396 # if (sharedata.vrange[index][0] is not None and sharedata.vpos[index] is not None and
397 # sharedata.vrange[index][0] > sharedata.vpos[index] + self.epsilon):
398 # raise ValueError("negative minimum errorbar")
399 # if (sharedata.vrange[index][1] is not None and sharedata.vpos[index] is not None and
400 # sharedata.vrange[index][1] < sharedata.vpos[index] - self.epsilon):
401 # raise ValueError("negative maximum errorbar")
404 registerdefaultprovider(range(), range.providesdata)
407 def _crosssymbol(c, x_pt, y_pt, size_pt, attrs):
408 c.draw(path.path(path.moveto_pt(x_pt-0.5*size_pt, y_pt-0.5*size_pt),
409 path.lineto_pt(x_pt+0.5*size_pt, y_pt+0.5*size_pt),
410 path.moveto_pt(x_pt-0.5*size_pt, y_pt+0.5*size_pt),
411 path.lineto_pt(x_pt+0.5*size_pt, y_pt-0.5*size_pt)), attrs)
413 def _plussymbol(c, x_pt, y_pt, size_pt, attrs):
414 c.draw(path.path(path.moveto_pt(x_pt-0.707106781*size_pt, y_pt),
415 path.lineto_pt(x_pt+0.707106781*size_pt, y_pt),
416 path.moveto_pt(x_pt, y_pt-0.707106781*size_pt),
417 path.lineto_pt(x_pt, y_pt+0.707106781*size_pt)), attrs)
419 def _squaresymbol(c, x_pt, y_pt, size_pt, attrs):
420 c.draw(path.path(path.moveto_pt(x_pt-0.5*size_pt, y_pt-0.5*size_pt),
421 path.lineto_pt(x_pt+0.5*size_pt, y_pt-0.5*size_pt),
422 path.lineto_pt(x_pt+0.5*size_pt, y_pt+0.5*size_pt),
423 path.lineto_pt(x_pt-0.5*size_pt, y_pt+0.5*size_pt),
424 path.closepath()), attrs)
426 def _trianglesymbol(c, x_pt, y_pt, size_pt, attrs):
427 c.draw(path.path(path.moveto_pt(x_pt-0.759835685*size_pt, y_pt-0.438691337*size_pt),
428 path.lineto_pt(x_pt+0.759835685*size_pt, y_pt-0.438691337*size_pt),
429 path.lineto_pt(x_pt, y_pt+0.877382675*size_pt),
430 path.closepath()), attrs)
432 def _circlesymbol(c, x_pt, y_pt, size_pt, attrs):
433 c.draw(path.path(path.arc_pt(x_pt, y_pt, 0.564189583*size_pt, 0, 360),
434 path.closepath()), attrs)
436 def _diamondsymbol(c, x_pt, y_pt, size_pt, attrs):
437 c.draw(path.path(path.moveto_pt(x_pt-0.537284965*size_pt, y_pt),
438 path.lineto_pt(x_pt, y_pt-0.930604859*size_pt),
439 path.lineto_pt(x_pt+0.537284965*size_pt, y_pt),
440 path.lineto_pt(x_pt, y_pt+0.930604859*size_pt),
441 path.closepath()), attrs)
444 class _styleneedingpointpos(_style):
446 needsdata = ["vposmissing"]
448 def columnnames(self, privatedata, sharedata, graph, columnnames, dataaxisnames):
449 if len(sharedata.vposmissing):
450 raise ValueError("incomplete position information")
451 return []
454 class symbol(_styleneedingpointpos):
456 needsdata = ["vpos", "vposmissing", "vposvalid"]
458 # "inject" the predefinied symbols into the class:
460 # Note, that statements like cross = _crosssymbol are
461 # invalid, since the would lead to unbound methods, but
462 # a single entry changeable list does the trick.
464 # Once we require Python 2.2+ we should use staticmethods
465 # to implement the default symbols inplace.
467 cross = attr.changelist([_crosssymbol])
468 plus = attr.changelist([_plussymbol])
469 square = attr.changelist([_squaresymbol])
470 triangle = attr.changelist([_trianglesymbol])
471 circle = attr.changelist([_circlesymbol])
472 diamond = attr.changelist([_diamondsymbol])
474 changecross = attr.changelist([_crosssymbol, _plussymbol, _squaresymbol, _trianglesymbol, _circlesymbol, _diamondsymbol])
475 changeplus = attr.changelist([_plussymbol, _squaresymbol, _trianglesymbol, _circlesymbol, _diamondsymbol, _crosssymbol])
476 changesquare = attr.changelist([_squaresymbol, _trianglesymbol, _circlesymbol, _diamondsymbol, _crosssymbol, _plussymbol])
477 changetriangle = attr.changelist([_trianglesymbol, _circlesymbol, _diamondsymbol, _crosssymbol, _plussymbol, _squaresymbol])
478 changecircle = attr.changelist([_circlesymbol, _diamondsymbol, _crosssymbol, _plussymbol, _squaresymbol, _trianglesymbol])
479 changediamond = attr.changelist([_diamondsymbol, _crosssymbol, _plussymbol, _squaresymbol, _trianglesymbol, _circlesymbol])
480 changesquaretwice = attr.changelist([_squaresymbol, _squaresymbol, _trianglesymbol, _trianglesymbol, _circlesymbol, _circlesymbol, _diamondsymbol, _diamondsymbol])
481 changetriangletwice = attr.changelist([_trianglesymbol, _trianglesymbol, _circlesymbol, _circlesymbol, _diamondsymbol, _diamondsymbol, _squaresymbol, _squaresymbol])
482 changecircletwice = attr.changelist([_circlesymbol, _circlesymbol, _diamondsymbol, _diamondsymbol, _squaresymbol, _squaresymbol, _trianglesymbol, _trianglesymbol])
483 changediamondtwice = attr.changelist([_diamondsymbol, _diamondsymbol, _squaresymbol, _squaresymbol, _trianglesymbol, _trianglesymbol, _circlesymbol, _circlesymbol])
485 changestrokedfilled = attr.changelist([deco.stroked, deco.filled])
486 changefilledstroked = attr.changelist([deco.filled, deco.stroked])
488 defaultsymbolattrs = [deco.stroked]
490 def __init__(self, symbol=changecross, size=0.2*unit.v_cm, symbolattrs=[]):
491 self.symbol = symbol
492 self.size = size
493 self.symbolattrs = symbolattrs
495 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
496 privatedata.symbol = attr.selectattr(self.symbol, selectindex, selecttotal)
497 privatedata.size_pt = unit.topt(attr.selectattr(self.size, selectindex, selecttotal))
498 if self.symbolattrs is not None:
499 privatedata.symbolattrs = attr.selectattrs(self.defaultsymbolattrs + self.symbolattrs, selectindex, selecttotal)
500 else:
501 privatedata.symbolattrs = None
503 def initdrawpoints(self, privatedata, sharedata, graph):
504 privatedata.symbolcanvas = canvas.canvas()
506 def drawpoint(self, privatedata, sharedata, graph, point):
507 if sharedata.vposvalid and privatedata.symbolattrs is not None:
508 x_pt, y_pt = graph.vpos_pt(*sharedata.vpos)
509 privatedata.symbol(privatedata.symbolcanvas, x_pt, y_pt, privatedata.size_pt, privatedata.symbolattrs)
511 def donedrawpoints(self, privatedata, sharedata, graph):
512 graph.layer("data").insert(privatedata.symbolcanvas)
514 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
515 if privatedata.symbolattrs is not None:
516 privatedata.symbol(graph, x_pt+0.5*width_pt, y_pt+0.5*height_pt, privatedata.size_pt, privatedata.symbolattrs)
519 class _line(_styleneedingpointpos):
521 # this style is not a complete style, but it provides the basic functionality to
522 # create a line, which is cut at the graph boundaries (or at otherwise invalid points)
524 def initpointstopath(self, privatedata):
525 privatedata.path = path.path()
526 privatedata.linebasepoints = []
527 privatedata.lastvpos = None
529 def addpointstopath(self, privatedata):
530 # add baselinepoints to privatedata.path
531 if len(privatedata.linebasepoints) > 1:
532 privatedata.path.append(path.moveto_pt(*privatedata.linebasepoints[0]))
533 if len(privatedata.linebasepoints) > 2:
534 privatedata.path.append(path.multilineto_pt(privatedata.linebasepoints[1:]))
535 else:
536 privatedata.path.append(path.lineto_pt(*privatedata.linebasepoints[1]))
537 privatedata.linebasepoints = []
539 def addpoint(self, privatedata, graphvpos_pt, vposavailable, vposvalid, vpos):
540 # append linebasepoints
541 if vposavailable:
542 if len(privatedata.linebasepoints):
543 # the last point was inside the graph
544 if vposvalid: # shortcut for the common case
545 privatedata.linebasepoints.append(graphvpos_pt(*vpos))
546 else:
547 # cut end
548 cut = 1
549 for vstart, vend in zip(privatedata.lastvpos, vpos):
550 newcut = None
551 if vend > 1:
552 # 1 = vstart + (vend - vstart) * cut
553 try:
554 newcut = (1 - vstart)/(vend - vstart)
555 except (ArithmeticError, TypeError):
556 break
557 if vend < 0:
558 # 0 = vstart + (vend - vstart) * cut
559 try:
560 newcut = - vstart/(vend - vstart)
561 except (ArithmeticError, TypeError):
562 break
563 if newcut is not None and newcut < cut:
564 cut = newcut
565 else:
566 cutvpos = []
567 for vstart, vend in zip(privatedata.lastvpos, vpos):
568 cutvpos.append(vstart + (vend - vstart) * cut)
569 privatedata.linebasepoints.append(graphvpos_pt(*cutvpos))
570 self.addpointstopath(privatedata)
571 else:
572 # the last point was outside the graph
573 if privatedata.lastvpos is not None:
574 if vposvalid:
575 # cut beginning
576 cut = 0
577 for vstart, vend in zip(privatedata.lastvpos, vpos):
578 newcut = None
579 if vstart > 1:
580 # 1 = vstart + (vend - vstart) * cut
581 try:
582 newcut = (1 - vstart)/(vend - vstart)
583 except (ArithmeticError, TypeError):
584 break
585 if vstart < 0:
586 # 0 = vstart + (vend - vstart) * cut
587 try:
588 newcut = - vstart/(vend - vstart)
589 except (ArithmeticError, TypeError):
590 break
591 if newcut is not None and newcut > cut:
592 cut = newcut
593 else:
594 cutvpos = []
595 for vstart, vend in zip(privatedata.lastvpos, vpos):
596 cutvpos.append(vstart + (vend - vstart) * cut)
597 privatedata.linebasepoints.append(graphvpos_pt(*cutvpos))
598 privatedata.linebasepoints.append(graphvpos_pt(*vpos))
599 else:
600 # sometimes cut beginning and end
601 cutfrom = 0
602 cutto = 1
603 for vstart, vend in zip(privatedata.lastvpos, vpos):
604 newcutfrom = None
605 if vstart > 1:
606 if vend > 1:
607 break
608 # 1 = vstart + (vend - vstart) * cutfrom
609 try:
610 newcutfrom = (1 - vstart)/(vend - vstart)
611 except (ArithmeticError, TypeError):
612 break
613 if vstart < 0:
614 if vend < 0:
615 break
616 # 0 = vstart + (vend - vstart) * cutfrom
617 try:
618 newcutfrom = - vstart/(vend - vstart)
619 except (ArithmeticError, TypeError):
620 break
621 if newcutfrom is not None and newcutfrom > cutfrom:
622 cutfrom = newcutfrom
623 newcutto = None
624 if vend > 1:
625 # 1 = vstart + (vend - vstart) * cutto
626 try:
627 newcutto = (1 - vstart)/(vend - vstart)
628 except (ArithmeticError, TypeError):
629 break
630 if vend < 0:
631 # 0 = vstart + (vend - vstart) * cutto
632 try:
633 newcutto = - vstart/(vend - vstart)
634 except (ArithmeticError, TypeError):
635 break
636 if newcutto is not None and newcutto < cutto:
637 cutto = newcutto
638 else:
639 if cutfrom < cutto:
640 cutfromvpos = []
641 cuttovpos = []
642 for vstart, vend in zip(privatedata.lastvpos, vpos):
643 cutfromvpos.append(vstart + (vend - vstart) * cutfrom)
644 cuttovpos.append(vstart + (vend - vstart) * cutto)
645 privatedata.linebasepoints.append(graphvpos_pt(*cutfromvpos))
646 privatedata.linebasepoints.append(graphvpos_pt(*cuttovpos))
647 self.addpointstopath(privatedata)
648 privatedata.lastvpos = vpos[:]
649 else:
650 if len(privatedata.linebasepoints) > 1:
651 self.addpointstopath(privatedata)
652 privatedata.lastvpos = None
654 def addinvalid(self, privatedata):
655 if len(privatedata.linebasepoints) > 1:
656 self.addpointstopath(privatedata)
657 privatedata.lastvpos = None
659 def donepointstopath(self, privatedata):
660 if len(privatedata.linebasepoints) > 1:
661 self.addpointstopath(privatedata)
662 return privatedata.path
665 class line(_line):
667 needsdata = ["vpos", "vposmissing", "vposavailable", "vposvalid"]
669 changelinestyle = attr.changelist([style.linestyle.solid,
670 style.linestyle.dashed,
671 style.linestyle.dotted,
672 style.linestyle.dashdotted])
674 defaultlineattrs = [changelinestyle]
676 def __init__(self, lineattrs=[]):
677 self.lineattrs = lineattrs
679 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
680 if self.lineattrs is not None:
681 privatedata.lineattrs = attr.selectattrs(self.defaultlineattrs + self.lineattrs, selectindex, selecttotal)
682 else:
683 privatedata.lineattrs = None
685 def initdrawpoints(self, privatedata, sharedata, graph):
686 self.initpointstopath(privatedata)
688 def drawpoint(self, privatedata, sharedata, graph, point):
689 self.addpoint(privatedata, graph.vpos_pt, sharedata.vposavailable, sharedata.vposvalid, sharedata.vpos)
691 def donedrawpoints(self, privatedata, sharedata, graph):
692 path = self.donepointstopath(privatedata)
693 if privatedata.lineattrs is not None and len(path):
694 graph.layer("data").stroke(path, privatedata.lineattrs)
696 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
697 if privatedata.lineattrs is not None:
698 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)
701 class impulses(_styleneedingpointpos):
703 needsdata = ["vpos", "vposmissing", "vposavailable", "vposvalid", "poscolumnnames"]
705 defaultlineattrs = [line.changelinestyle]
706 defaultfrompathattrs = []
708 def __init__(self, lineattrs=[], fromvalue=0, frompathattrs=[], valueaxisindex=1):
709 self.lineattrs = lineattrs
710 self.fromvalue = fromvalue
711 self.frompathattrs = frompathattrs
712 self.valueaxisindex = valueaxisindex
714 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
715 privatedata.insertfrompath = selectindex == 0
716 if self.lineattrs is not None:
717 privatedata.lineattrs = attr.selectattrs(self.defaultlineattrs + self.lineattrs, selectindex, selecttotal)
718 else:
719 privatedata.lineattrs = None
721 def adjustaxis(self, privatedata, sharedata, graph, plotitem, columnname, data):
722 if self.fromvalue is not None:
723 try:
724 i = sharedata.poscolumnnames.index(columnname)
725 except ValueError:
726 pass
727 else:
728 if i == self.valueaxisindex:
729 graph.axes[sharedata.poscolumnnames[i]].adjustaxis([self.fromvalue])
731 def initdrawpoints(self, privatedata, sharedata, graph):
732 privatedata.impulsescanvas = canvas.canvas()
733 if self.fromvalue is not None:
734 valueaxisname = sharedata.poscolumnnames[self.valueaxisindex]
735 privatedata.vfromvalue = graph.axes[valueaxisname].convert(self.fromvalue)
736 privatedata.vfromvaluecut = 0
737 if privatedata.vfromvalue < 0:
738 privatedata.vfromvalue = 0
739 if privatedata.vfromvalue > 1:
740 privatedata.vfromvalue = 1
741 if self.frompathattrs is not None and privatedata.insertfrompath:
742 graph.layer("data").stroke(graph.axes[valueaxisname].vgridpath(privatedata.vfromvalue),
743 self.defaultfrompathattrs + self.frompathattrs)
744 else:
745 privatedata.vfromvalue = 0
747 def drawpoint(self, privatedata, sharedata, graph, point):
748 if sharedata.vposvalid and privatedata.lineattrs is not None:
749 vpos = sharedata.vpos[:]
750 vpos[self.valueaxisindex] = privatedata.vfromvalue
751 privatedata.impulsescanvas.stroke(graph.vgeodesic(*(vpos + sharedata.vpos)), privatedata.lineattrs)
753 def donedrawpoints(self, privatedata, sharedata, graph):
754 graph.layer("data").insert(privatedata.impulsescanvas)
756 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
757 if privatedata.lineattrs is not None:
758 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)
761 class errorbar(_style):
763 needsdata = ["vpos", "vposmissing", "vposavailable", "vposvalid", "vrange", "vrangeminmissing", "vrangemaxmissing"]
765 defaulterrorbarattrs = []
767 def __init__(self, size=0.1*unit.v_cm,
768 errorbarattrs=[],
769 epsilon=1e-10):
770 self.size = size
771 self.errorbarattrs = errorbarattrs
772 self.epsilon = epsilon
774 def columnnames(self, privatedata, sharedata, graph, columnnames, dataaxisnames):
775 for i in sharedata.vposmissing:
776 if i in sharedata.vrangeminmissing and i in sharedata.vrangemaxmissing:
777 raise ValueError("position and range for a graph dimension missing")
778 return []
780 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
781 privatedata.errorsize_pt = unit.topt(attr.selectattr(self.size, selectindex, selecttotal))
782 privatedata.errorbarattrs = attr.selectattrs(self.defaulterrorbarattrs + self.errorbarattrs, selectindex, selecttotal)
784 def initdrawpoints(self, privatedata, sharedata, graph):
785 if privatedata.errorbarattrs is not None:
786 privatedata.errorbarcanvas = canvas.canvas(privatedata.errorbarattrs)
787 privatedata.dimensionlist = list(xrange(len(sharedata.vpos)))
789 def drawpoint(self, privatedata, sharedata, graph, point):
790 if privatedata.errorbarattrs is not None:
791 for i in privatedata.dimensionlist:
792 for j in privatedata.dimensionlist:
793 if (i != j and
794 (sharedata.vpos[j] is None or
795 sharedata.vpos[j] < -self.epsilon or
796 sharedata.vpos[j] > 1+self.epsilon)):
797 break
798 else:
799 if ((sharedata.vrange[i][0] is None and sharedata.vpos[i] is None) or
800 (sharedata.vrange[i][1] is None and sharedata.vpos[i] is None) or
801 (sharedata.vrange[i][0] is None and sharedata.vrange[i][1] is None)):
802 continue
803 vminpos = sharedata.vpos[:]
804 if sharedata.vrange[i][0] is not None:
805 vminpos[i] = sharedata.vrange[i][0]
806 mincap = 1
807 else:
808 mincap = 0
809 if vminpos[i] > 1+self.epsilon:
810 continue
811 if vminpos[i] < -self.epsilon:
812 vminpos[i] = 0
813 mincap = 0
814 vmaxpos = sharedata.vpos[:]
815 if sharedata.vrange[i][1] is not None:
816 vmaxpos[i] = sharedata.vrange[i][1]
817 maxcap = 1
818 else:
819 maxcap = 0
820 if vmaxpos[i] < -self.epsilon:
821 continue
822 if vmaxpos[i] > 1+self.epsilon:
823 vmaxpos[i] = 1
824 maxcap = 0
825 privatedata.errorbarcanvas.stroke(graph.vgeodesic(*(vminpos + vmaxpos)))
826 for j in privatedata.dimensionlist:
827 if i != j:
828 if mincap:
829 privatedata.errorbarcanvas.stroke(graph.vcap_pt(j, privatedata.errorsize_pt, *vminpos))
830 if maxcap:
831 privatedata.errorbarcanvas.stroke(graph.vcap_pt(j, privatedata.errorsize_pt, *vmaxpos))
833 def donedrawpoints(self, privatedata, sharedata, graph):
834 if privatedata.errorbarattrs is not None:
835 graph.layer("data").insert(privatedata.errorbarcanvas)
838 class text(_styleneedingpointpos):
840 needsdata = ["vpos", "vposmissing", "vposvalid"]
842 defaulttextattrs = [textmodule.halign.center, textmodule.vshift.mathaxis]
844 def __init__(self, textname="text", dxname=None, dyname=None,
845 dxunit=0.3*unit.v_cm, dyunit=0.3*unit.v_cm,
846 textdx=0*unit.v_cm, textdy=0.3*unit.v_cm, textattrs=[]):
847 self.textname = textname
848 self.dxname = dxname
849 self.dyname = dyname
850 self.dxunit = dxunit
851 self.dyunit = dyunit
852 self.textdx = textdx
853 self.textdy = textdy
854 self.textattrs = textattrs
856 def columnnames(self, privatedata, sharedata, graph, columnnames, dataaxisnames):
857 if self.textname not in columnnames:
858 raise ValueError("column '%s' missing" % self.textname)
859 names = [self.textname]
860 if self.dxname is not None:
861 if self.dxname not in columnnames:
862 raise ValueError("column '%s' missing" % self.dxname)
863 names.append(self.dxname)
864 if self.dyname is not None:
865 if self.dyname not in columnnames:
866 raise ValueError("column '%s' missing" % self.dyname)
867 names.append(self.dyname)
868 return names + _styleneedingpointpos.columnnames(self, privatedata, sharedata, graph, columnnames, dataaxisnames)
870 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
871 if self.textattrs is not None:
872 privatedata.textattrs = attr.selectattrs(self.defaulttextattrs + self.textattrs, selectindex, selecttotal)
873 else:
874 privatedata.textattrs = None
876 def initdrawpoints(self, privatedata, sharedata, grap):
877 if self.dxname is None:
878 privatedata.textdx_pt = unit.topt(self.textdx)
879 else:
880 privatedata.dxunit_pt = unit.topt(self.dxunit)
881 if self.dyname is None:
882 privatedata.textdy_pt = unit.topt(self.textdy)
883 else:
884 privatedata.dyunit_pt = unit.topt(self.dyunit)
886 def drawpoint(self, privatedata, sharedata, graph, point):
887 if privatedata.textattrs is not None and sharedata.vposvalid:
888 x_pt, y_pt = graph.vpos_pt(*sharedata.vpos)
889 try:
890 text = str(point[self.textname])
891 except:
892 pass
893 else:
894 if self.dxname is None:
895 dx_pt = privatedata.textdx_pt
896 else:
897 dx_pt = float(point[self.dxname]) * privatedata.dxunit_pt
898 if self.dyname is None:
899 dy_pt = privatedata.textdy_pt
900 else:
901 dy_pt = float(point[self.dyname]) * privatedata.dyunit_pt
902 graph.layer("data").text_pt(x_pt + dx_pt, y_pt + dy_pt, text, privatedata.textattrs)
904 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
905 raise RuntimeError("Style currently doesn't provide a graph key")
908 class arrow(_styleneedingpointpos):
910 needsdata = ["vpos", "vposmissing", "vposvalid"]
912 defaultlineattrs = []
913 defaultarrowattrs = []
915 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):
916 self.linelength = linelength
917 self.arrowsize = arrowsize
918 self.lineattrs = lineattrs
919 self.arrowattrs = arrowattrs
920 self.arrowpos = arrowpos
921 self.epsilon = epsilon
922 self.decorator = decorator
924 def columnnames(self, privatedata, sharedata, graph, columnnames, dataaxisnames):
925 if len(graph.axesnames) != 2:
926 raise ValueError("arrow style restricted on two-dimensional graphs")
927 if "size" not in columnnames:
928 raise ValueError("size missing")
929 if "angle" not in columnnames:
930 raise ValueError("angle missing")
931 return ["size", "angle"] + _styleneedingpointpos.columnnames(self, privatedata, sharedata, graph, columnnames, dataaxisnames)
933 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
934 if self.lineattrs is not None:
935 privatedata.lineattrs = attr.selectattrs(self.defaultlineattrs + self.lineattrs, selectindex, selecttotal)
936 else:
937 privatedata.lineattrs = None
938 if self.arrowattrs is not None:
939 privatedata.arrowattrs = attr.selectattrs(self.defaultarrowattrs + self.arrowattrs, selectindex, selecttotal)
940 else:
941 privatedata.arrowattrs = None
943 def initdrawpoints(self, privatedata, sharedata, graph):
944 privatedata.arrowcanvas = canvas.canvas()
946 def drawpoint(self, privatedata, sharedata, graph, point):
947 if privatedata.lineattrs is not None and privatedata.arrowattrs is not None and sharedata.vposvalid:
948 linelength_pt = unit.topt(self.linelength)
949 x_pt, y_pt = graph.vpos_pt(*sharedata.vpos)
950 try:
951 angle = point["angle"] + 0.0
952 size = point["size"] + 0.0
953 except:
954 pass
955 else:
956 if point["size"] > self.epsilon:
957 dx = math.cos(angle*math.pi/180)
958 dy = math.sin(angle*math.pi/180)
959 x1 = x_pt-self.arrowpos*dx*linelength_pt*size
960 y1 = y_pt-self.arrowpos*dy*linelength_pt*size
961 x2 = x_pt+(1-self.arrowpos)*dx*linelength_pt*size
962 y2 = y_pt+(1-self.arrowpos)*dy*linelength_pt*size
963 if self.decorator:
964 privatedata.arrowcanvas.stroke(path.line_pt(x1, y1, x2, y2),
965 privatedata.lineattrs+[self.decorator(privatedata.arrowattrs, size=self.arrowsize*size)])
966 else:
967 privatedata.arrowcanvas.stroke(path.line_pt(x1, y1, x2, y2), privatedata.lineattrs)
969 def donedrawpoints(self, privatedata, sharedata, graph):
970 graph.layer("data").insert(privatedata.arrowcanvas)
972 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
973 raise RuntimeError("Style currently doesn't provide a graph key")
976 class rect(_keygraphstyle):
978 needsdata = ["vrange", "vrangeminmissing", "vrangemaxmissing"]
980 def columnnames(self, privatedata, sharedata, graph, columnnames, dataaxisnames):
981 if len(graph.axesnames) != 2:
982 raise TypeError("rect style restricted on two-dimensional graphs")
983 if len(sharedata.vrangeminmissing) + len(sharedata.vrangemaxmissing):
984 raise ValueError("incomplete range")
985 return _keygraphstyle.columnnames(self, privatedata, sharedata, graph, columnnames, dataaxisnames)
987 def initdrawpoints(self, privatedata, sharedata, graph):
988 privatedata.rectcanvas = graph.layer("filldata").insert(canvas.canvas())
990 def drawpoint(self, privatedata, sharedata, graph, point):
991 xvmin = sharedata.vrange[0][0]
992 xvmax = sharedata.vrange[0][1]
993 yvmin = sharedata.vrange[1][0]
994 yvmax = sharedata.vrange[1][1]
995 if (xvmin is not None and xvmin < 1 and
996 xvmax is not None and xvmax > 0 and
997 yvmin is not None and yvmin < 1 and
998 yvmax is not None and yvmax > 0):
999 if xvmin < 0:
1000 xvmin = 0
1001 elif xvmax > 1:
1002 xvmax = 1
1003 if yvmin < 0:
1004 yvmin = 0
1005 elif yvmax > 1:
1006 yvmax = 1
1007 p = graph.vgeodesic(xvmin, yvmin, xvmax, yvmin)
1008 p.append(graph.vgeodesic_el(xvmax, yvmin, xvmax, yvmax))
1009 p.append(graph.vgeodesic_el(xvmax, yvmax, xvmin, yvmax))
1010 p.append(graph.vgeodesic_el(xvmin, yvmax, xvmin, yvmin))
1011 p.append(path.closepath())
1012 privatedata.rectcanvas.fill(p, [self.color(privatedata, point["color"])])
1015 class histogram(_style):
1017 needsdata = ["vpos", "vposmissing", "vrange", "vrangeminmissing", "vrangemaxmissing"]
1019 defaultlineattrs = [deco.stroked]
1020 defaultfrompathattrs = []
1022 def __init__(self, lineattrs=[], steps=0, fromvalue=0, frompathattrs=[], fillable=0, rectkey=0,
1023 autohistogramaxisindex=0, autohistogrampointpos=0.5, epsilon=1e-10):
1024 self.lineattrs = lineattrs
1025 self.steps = steps
1026 self.fromvalue = fromvalue
1027 self.frompathattrs = frompathattrs
1028 self.fillable = fillable # TODO: fillable paths might not properly be closed by straight lines on curved graph geometries
1029 self.rectkey = rectkey
1030 self.autohistogramaxisindex = autohistogramaxisindex
1031 self.autohistogrampointpos = autohistogrampointpos
1032 self.epsilon = epsilon
1034 def columnnames(self, privatedata, sharedata, graph, columnnames, dataaxisnames):
1035 if len(graph.axesnames) != 2:
1036 raise TypeError("histogram style restricted on two-dimensional graphs")
1037 privatedata.rangeaxisindex = None
1038 for i in builtinrange(len(graph.axesnames)):
1039 if i in sharedata.vrangeminmissing or i in sharedata.vrangemaxmissing:
1040 if i in sharedata.vposmissing:
1041 raise ValueError("pos and range missing")
1042 else:
1043 if privatedata.rangeaxisindex is not None:
1044 raise ValueError("multiple ranges")
1045 privatedata.rangeaxisindex = i
1046 if privatedata.rangeaxisindex is None:
1047 privatedata.rangeaxisindex = self.autohistogramaxisindex
1048 privatedata.autohistogram = 1
1049 else:
1050 privatedata.autohistogram = 0
1051 return []
1053 def adjustaxis(self, privatedata, sharedata, graph, plotitem, columnname, data):
1054 if privatedata.autohistogram and columnname == sharedata.poscolumnnames[privatedata.rangeaxisindex]:
1055 if len(data) == 1:
1056 raise ValueError("several data points needed for automatic histogram width calculation")
1057 if len(data) > 1:
1058 delta = data[1] - data[0]
1059 min = data[0] - self.autohistogrampointpos * delta
1060 max = data[-1] + (1-self.autohistogrampointpos) * delta
1061 graph.axes[columnname].adjustaxis([min, max])
1062 elif self.fromvalue is not None and columnname == sharedata.poscolumnnames[1-privatedata.rangeaxisindex]:
1063 graph.axes[columnname].adjustaxis([self.fromvalue])
1065 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1066 privatedata.insertfrompath = selectindex == 0
1067 if self.lineattrs is not None:
1068 privatedata.lineattrs = attr.selectattrs(self.defaultlineattrs + self.lineattrs, selectindex, selecttotal)
1069 else:
1070 privatedata.lineattrs = None
1072 def vmoveto(self, privatedata, sharedata, graph, vpos, vvalue):
1073 if -self.epsilon < vpos < 1+self.epsilon and -self.epsilon < vvalue < 1+self.epsilon:
1074 if privatedata.rangeaxisindex:
1075 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue, vpos)))
1076 else:
1077 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vpos, vvalue)))
1079 def vposline(self, privatedata, sharedata, graph, vpos, vvalue1, vvalue2):
1080 if -self.epsilon < vpos < 1+self.epsilon:
1081 vvalue1cut = 0
1082 if vvalue1 < 0:
1083 vvalue1 = 0
1084 vvalue1cut = -1
1085 elif vvalue1 > 1:
1086 vvalue1 = 1
1087 vvalue1cut = 1
1088 vvalue2cut = 0
1089 if vvalue2 < 0:
1090 vvalue2 = 0
1091 vvalue2cut = -1
1092 elif vvalue2 > 1:
1093 vvalue2 = 1
1094 vvalue2cut = 1
1095 if abs(vvalue1cut + vvalue2cut) <= 1:
1096 if vvalue1cut and not self.fillable:
1097 if privatedata.rangeaxisindex:
1098 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue1, vpos)))
1099 else:
1100 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vpos, vvalue1)))
1101 if privatedata.rangeaxisindex:
1102 privatedata.path.append(graph.vgeodesic_el(vvalue1, vpos, vvalue2, vpos))
1103 else:
1104 privatedata.path.append(graph.vgeodesic_el(vpos, vvalue1, vpos, vvalue2))
1106 def vvalueline(self, privatedata, sharedata, graph, vvalue, vpos1, vpos2):
1107 if self.fillable:
1108 if vvalue < -self.epsilon:
1109 vvalue = 0
1110 warnings.warn("cut at graph boundary adds artificial lines to fillable step histogram path")
1111 if vvalue > 1+self.epsilon:
1112 vvalue = 1
1113 warnings.warn("cut at graph boundary adds artificial lines to fillable step histogram path")
1114 if self.fillable or (-self.epsilon < vvalue < 1+self.epsilon):
1115 vpos1cut = 0
1116 if vpos1 < 0:
1117 vpos1 = 0
1118 vpos1cut = -1
1119 elif vpos1 > 1:
1120 vpos1 = 1
1121 vpos1cut = 1
1122 vpos2cut = 0
1123 if vpos2 < 0:
1124 vpos2 = 0
1125 vpos2cut = -1
1126 elif vpos2 > 1:
1127 vpos2 = 1
1128 vpos2cut = 1
1129 if abs(vpos1cut + vpos2cut) <= 1:
1130 if vpos1cut:
1131 if self.fillable:
1132 if privatedata.rangeaxisindex:
1133 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(privatedata.vfromvalue, vpos1)))
1134 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vpos1, vvalue, vpos1))
1135 else:
1136 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vpos1, privatedata.vfromvalue)))
1137 privatedata.path.append(graph.vgeodesic_el(vpos1, privatedata.vfromvalue, vpos1, vvalue))
1138 else:
1139 if privatedata.rangeaxisindex:
1140 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue, vpos1)))
1141 else:
1142 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vpos1, vvalue)))
1143 if privatedata.rangeaxisindex:
1144 privatedata.path.append(graph.vgeodesic_el(vvalue, vpos1, vvalue, vpos2))
1145 else:
1146 privatedata.path.append(graph.vgeodesic_el(vpos1, vvalue, vpos2, vvalue))
1147 if self.fillable and vpos2cut:
1148 warnings.warn("cut at graph boundary adds artificial lines to fillable step histogram path")
1149 if privatedata.rangeaxisindex:
1150 privatedata.path.append(graph.vgeodesic_el(vvalue, vpos2, privatedata.vfromvalue, vpos2))
1151 else:
1152 privatedata.path.append(graph.vgeodesic_el(vpos2, vvalue, vpos2, privatedata.vfromvalue))
1154 def drawvalue(self, privatedata, sharedata, graph, vmin, vmax, vvalue):
1155 currentvalid = vmin is not None and vmax is not None and vvalue is not None
1156 if self.fillable and not self.steps:
1157 if not currentvalid:
1158 return
1159 vmincut = 0
1160 if vmin < -self.epsilon:
1161 vmin = 0
1162 vmincut = -1
1163 elif vmin > 1+self.epsilon:
1164 vmin = 1
1165 vmincut = 1
1166 vmaxcut = 0
1167 if vmax < -self.epsilon:
1168 vmax = 0
1169 vmaxcut = -1
1170 if vmax > 1+self.epsilon:
1171 vmax = 1
1172 vmaxcut = 1
1173 vvaluecut = 0
1174 if vvalue < -self.epsilon:
1175 vvalue = 0
1176 vvaluecut = -1
1177 if vvalue > 1+self.epsilon:
1178 vvalue = 1
1179 vvaluecut = 1
1180 done = 0
1181 if abs(vmincut) + abs(vmaxcut) + abs(vvaluecut) + abs(privatedata.vfromvaluecut) > 1:
1182 if abs(vmincut + vmaxcut) > 1 or abs(vvaluecut+privatedata.vfromvaluecut) > 1:
1183 done = 1
1184 else:
1185 warnings.warn("multiple cuts at graph boundary add artificial lines to fillable rectangle histogram path")
1186 elif vmincut:
1187 done = 1
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 else:
1194 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmin, privatedata.vfromvalue)))
1195 privatedata.path.append(graph.vgeodesic_el(vmin, privatedata.vfromvalue, vmax, privatedata.vfromvalue))
1196 privatedata.path.append(graph.vgeodesic_el(vmax, privatedata.vfromvalue, vmax, vvalue))
1197 privatedata.path.append(graph.vgeodesic_el(vmax, vvalue, vmin, vvalue))
1198 elif vmaxcut:
1199 done = 1
1200 if privatedata.rangeaxisindex:
1201 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue, vmax)))
1202 privatedata.path.append(graph.vgeodesic_el(vvalue, vmax, vvalue, vmin))
1203 privatedata.path.append(graph.vgeodesic_el(vvalue, vmin, privatedata.vfromvalue, vmin))
1204 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmin, privatedata.vfromvalue, vmax))
1205 else:
1206 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmax, vvalue)))
1207 privatedata.path.append(graph.vgeodesic_el(vmax, vvalue, vmin, vvalue))
1208 privatedata.path.append(graph.vgeodesic_el(vmin, vvalue, vmin, privatedata.vfromvalue))
1209 privatedata.path.append(graph.vgeodesic_el(vmin, privatedata.vfromvalue, vmax, privatedata.vfromvalue))
1210 elif privatedata.vfromvaluecut:
1211 done = 1
1212 if privatedata.rangeaxisindex:
1213 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(privatedata.vfromvalue, vmax)))
1214 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmax, vvalue, vmax))
1215 privatedata.path.append(graph.vgeodesic_el(vvalue, vmax, vvalue, vmin))
1216 privatedata.path.append(graph.vgeodesic_el(vvalue, vmin, privatedata.vfromvalue, vmin))
1217 else:
1218 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmax, privatedata.vfromvalue)))
1219 privatedata.path.append(graph.vgeodesic_el(vmax, privatedata.vfromvalue, vmax, vvalue))
1220 privatedata.path.append(graph.vgeodesic_el(vmax, vvalue, vmin, vvalue))
1221 privatedata.path.append(graph.vgeodesic_el(vmin, vvalue, vmin, privatedata.vfromvalue))
1222 elif vvaluecut:
1223 done = 1
1224 if privatedata.rangeaxisindex:
1225 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue, vmin)))
1226 privatedata.path.append(graph.vgeodesic_el(vvalue, vmin, privatedata.vfromvalue, vmin))
1227 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmin, privatedata.vfromvalue, vmax))
1228 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmax, vvalue, vmax))
1229 else:
1230 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmin, vvalue)))
1231 privatedata.path.append(graph.vgeodesic_el(vmin, vvalue, vmin, privatedata.vfromvalue))
1232 privatedata.path.append(graph.vgeodesic_el(vmin, privatedata.vfromvalue, vmax, privatedata.vfromvalue))
1233 privatedata.path.append(graph.vgeodesic_el(vmax, privatedata.vfromvalue, vmax, vvalue))
1234 if not done:
1235 if privatedata.rangeaxisindex:
1236 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(privatedata.vfromvalue, vmin)))
1237 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmin, privatedata.vfromvalue, vmax))
1238 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmax, vvalue, vmax))
1239 privatedata.path.append(graph.vgeodesic_el(vvalue, vmax, vvalue, vmin))
1240 privatedata.path.append(graph.vgeodesic_el(vvalue, vmin, privatedata.vfromvalue, vmin))
1241 privatedata.path.append(path.closepath())
1242 else:
1243 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmin, privatedata.vfromvalue)))
1244 privatedata.path.append(graph.vgeodesic_el(vmin, privatedata.vfromvalue, vmax, privatedata.vfromvalue))
1245 privatedata.path.append(graph.vgeodesic_el(vmax, privatedata.vfromvalue, vmax, vvalue))
1246 privatedata.path.append(graph.vgeodesic_el(vmax, vvalue, vmin, vvalue))
1247 privatedata.path.append(graph.vgeodesic_el(vmin, vvalue, vmin, privatedata.vfromvalue))
1248 privatedata.path.append(path.closepath())
1249 else:
1250 try:
1251 gap = abs(vmin - privatedata.lastvmax) > self.epsilon
1252 except (ArithmeticError, ValueError, TypeError):
1253 gap = 1
1254 if (privatedata.lastvvalue is not None and currentvalid and not gap and
1255 (self.steps or (privatedata.lastvvalue-privatedata.vfromvalue)*(vvalue-privatedata.vfromvalue) < 0)):
1256 self.vposline(privatedata, sharedata, graph,
1257 vmin, privatedata.lastvvalue, vvalue)
1258 else:
1259 if privatedata.lastvvalue is not None and currentvalid:
1260 currentbigger = abs(privatedata.lastvvalue-privatedata.vfromvalue) < abs(vvalue-privatedata.vfromvalue)
1261 if privatedata.lastvvalue is not None and (not currentvalid or not currentbigger or gap):
1262 self.vposline(privatedata, sharedata, graph,
1263 privatedata.lastvmax, privatedata.lastvvalue, privatedata.vfromvalue)
1264 if currentvalid:
1265 self.vmoveto(privatedata, sharedata, graph,
1266 vmin, vvalue)
1267 if currentvalid and (privatedata.lastvvalue is None or currentbigger or gap):
1268 self.vmoveto(privatedata, sharedata, graph,
1269 vmin, privatedata.vfromvalue)
1270 self.vposline(privatedata, sharedata, graph,
1271 vmin, privatedata.vfromvalue, vvalue)
1272 if currentvalid:
1273 self.vvalueline(privatedata, sharedata, graph,
1274 vvalue, vmin, vmax)
1275 privatedata.lastvvalue = vvalue
1276 privatedata.lastvmax = vmax
1277 else:
1278 privatedata.lastvvalue = privatedata.lastvmax = None
1280 def initdrawpoints(self, privatedata, sharedata, graph):
1281 privatedata.path = path.path()
1282 privatedata.lastvvalue = privatedata.lastvmax = None
1283 privatedata.vcurrentpoint = None
1284 privatedata.count = 0
1285 if self.fromvalue is not None:
1286 valueaxisname = sharedata.poscolumnnames[1-privatedata.rangeaxisindex]
1287 privatedata.vfromvalue = graph.axes[valueaxisname].convert(self.fromvalue)
1288 privatedata.vfromvaluecut = 0
1289 if privatedata.vfromvalue < 0:
1290 privatedata.vfromvalue = 0
1291 privatedata.vfromvaluecut = -1
1292 if privatedata.vfromvalue > 1:
1293 privatedata.vfromvalue = 1
1294 privatedata.vfromvaluecut = 1
1295 if self.frompathattrs is not None and privatedata.insertfrompath:
1296 graph.layer("data").stroke(graph.axes[valueaxisname].vgridpath(privatedata.vfromvalue),
1297 self.defaultfrompathattrs + self.frompathattrs)
1298 else:
1299 privatedata.vfromvalue = 0
1301 def drawpoint(self, privatedata, sharedata, graph, point):
1302 if privatedata.autohistogram:
1303 # automatic range handling
1304 privatedata.count += 1
1305 if privatedata.count == 2:
1306 if privatedata.rangeaxisindex:
1307 privatedata.vrange = sharedata.vpos[1] - privatedata.lastvpos[1]
1308 self.drawvalue(privatedata, sharedata, graph,
1309 privatedata.lastvpos[1] - self.autohistogrampointpos*privatedata.vrange,
1310 privatedata.lastvpos[1] + (1-self.autohistogrampointpos)*privatedata.vrange,
1311 privatedata.lastvpos[0])
1312 else:
1313 privatedata.vrange = sharedata.vpos[0] - privatedata.lastvpos[0]
1314 self.drawvalue(privatedata, sharedata, graph,
1315 privatedata.lastvpos[0] - self.autohistogrampointpos*privatedata.vrange,
1316 privatedata.lastvpos[0] + (1-self.autohistogrampointpos)*privatedata.vrange,
1317 privatedata.lastvpos[1])
1318 elif privatedata.count > 2:
1319 if privatedata.rangeaxisindex:
1320 vrange = sharedata.vpos[1] - privatedata.lastvpos[1]
1321 else:
1322 vrange = sharedata.vpos[0] - privatedata.lastvpos[0]
1323 if abs(privatedata.vrange - vrange) > self.epsilon:
1324 raise ValueError("equal steps (in graph coordinates) needed for automatic width calculation")
1325 if privatedata.count > 1:
1326 if privatedata.rangeaxisindex:
1327 self.drawvalue(privatedata, sharedata, graph,
1328 sharedata.vpos[1] - self.autohistogrampointpos*privatedata.vrange,
1329 sharedata.vpos[1] + (1-self.autohistogrampointpos)*privatedata.vrange,
1330 sharedata.vpos[0])
1331 else:
1332 self.drawvalue(privatedata, sharedata, graph,
1333 sharedata.vpos[0] - self.autohistogrampointpos*privatedata.vrange,
1334 sharedata.vpos[0] + (1-self.autohistogrampointpos)*privatedata.vrange,
1335 sharedata.vpos[1])
1336 privatedata.lastvpos = sharedata.vpos[:]
1337 else:
1338 if privatedata.rangeaxisindex:
1339 self.drawvalue(privatedata, sharedata, graph,
1340 sharedata.vrange[1][0], sharedata.vrange[1][1], sharedata.vpos[0])
1341 else:
1342 self.drawvalue(privatedata, sharedata, graph,
1343 sharedata.vrange[0][0], sharedata.vrange[0][1], sharedata.vpos[1])
1345 def donedrawpoints(self, privatedata, sharedata, graph):
1346 self.drawvalue(privatedata, sharedata, graph, None, None, None)
1347 if privatedata.lineattrs is not None and len(privatedata.path):
1348 graph.layer("data").draw(privatedata.path, privatedata.lineattrs)
1350 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
1351 if privatedata.lineattrs is not None:
1352 if self.rectkey:
1353 p = path.rect_pt(x_pt, y_pt, width_pt, height_pt)
1354 else:
1355 p = path.line_pt(x_pt, y_pt+0.5*height_pt, x_pt+width_pt, y_pt+0.5*height_pt)
1356 graph.draw(p, privatedata.lineattrs)
1359 class barpos(_style):
1361 providesdata = ["vpos", "vposmissing", "vposavailable", "vposvalid", "vbarrange", "barposcolumnnames", "barvalueindex", "lastbarvalue", "stackedbar", "stackedbardraw"]
1363 defaultfrompathattrs = []
1365 def __init__(self, fromvalue=None, frompathattrs=[], epsilon=1e-10):
1366 self.fromvalue = fromvalue
1367 self.frompathattrs = frompathattrs
1368 self.epsilon = epsilon
1370 def columnnames(self, privatedata, sharedata, graph, columnnames, dataaxisnames):
1371 sharedata.barposcolumnnames = []
1372 sharedata.barvalueindex = None
1373 for dimension, axisnames in enumerate(graph.axesnames):
1374 found = 0
1375 for axisname in axisnames:
1376 if axisname in columnnames:
1377 if sharedata.barvalueindex is not None:
1378 raise ValueError("multiple values")
1379 sharedata.barvalueindex = dimension
1380 sharedata.barposcolumnnames.append(axisname)
1381 found += 1
1382 if (axisname + "name") in columnnames:
1383 sharedata.barposcolumnnames.append(axisname + "name")
1384 found += 1
1385 if found > 1:
1386 raise ValueError("multiple names and value")
1387 if not found:
1388 raise ValueError("value/name missing")
1389 if sharedata.barvalueindex is None:
1390 raise ValueError("missing value")
1391 sharedata.vposmissing = []
1392 return sharedata.barposcolumnnames
1394 def addsubvalue(self, value, subvalue):
1395 try:
1396 value + ""
1397 except:
1398 try:
1399 return value[0], self.addsubvalue(value[1], subvalue)
1400 except:
1401 return value, subvalue
1402 else:
1403 return value, subvalue
1405 def adjustaxis(self, privatedata, sharedata, graph, plotitem, columnname, data):
1406 try:
1407 i = sharedata.barposcolumnnames.index(columnname)
1408 except ValueError:
1409 pass
1410 else:
1411 if i == sharedata.barvalueindex:
1412 if self.fromvalue is not None:
1413 graph.axes[sharedata.barposcolumnnames[i]].adjustaxis([self.fromvalue])
1414 graph.axes[sharedata.barposcolumnnames[i]].adjustaxis(data)
1415 else:
1416 graph.axes[sharedata.barposcolumnnames[i][:-4]].adjustaxis([self.addsubvalue(x, 0) for x in data])
1417 graph.axes[sharedata.barposcolumnnames[i][:-4]].adjustaxis([self.addsubvalue(x, 1) for x in data])
1419 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1420 privatedata.insertfrompath = selectindex == 0
1422 def initdrawpoints(self, privatedata, sharedata, graph):
1423 sharedata.vpos = [None]*(len(sharedata.barposcolumnnames))
1424 sharedata.vbarrange = [[None for i in xrange(2)] for x in sharedata.barposcolumnnames]
1425 sharedata.stackedbar = sharedata.stackedbardraw = 0
1427 if self.fromvalue is not None:
1428 privatedata.vfromvalue = graph.axes[sharedata.barposcolumnnames[sharedata.barvalueindex]].convert(self.fromvalue)
1429 if privatedata.vfromvalue < 0:
1430 privatedata.vfromvalue = 0
1431 if privatedata.vfromvalue > 1:
1432 privatedata.vfromvalue = 1
1433 if self.frompathattrs is not None and privatedata.insertfrompath:
1434 graph.layer("data").stroke(graph.axes[sharedata.barposcolumnnames[sharedata.barvalueindex]].vgridpath(privatedata.vfromvalue),
1435 self.defaultfrompathattrs + self.frompathattrs)
1436 else:
1437 privatedata.vfromvalue = 0
1439 def drawpoint(self, privatedata, sharedata, graph, point):
1440 sharedata.vposavailable = sharedata.vposvalid = 1
1441 for i, barname in enumerate(sharedata.barposcolumnnames):
1442 if i == sharedata.barvalueindex:
1443 sharedata.vbarrange[i][0] = privatedata.vfromvalue
1444 sharedata.lastbarvalue = point[barname]
1445 try:
1446 sharedata.vpos[i] = sharedata.vbarrange[i][1] = graph.axes[barname].convert(sharedata.lastbarvalue)
1447 except (ArithmeticError, ValueError, TypeError):
1448 sharedata.vpos[i] = sharedata.vbarrange[i][1] = None
1449 else:
1450 for j in xrange(2):
1451 try:
1452 sharedata.vbarrange[i][j] = graph.axes[barname[:-4]].convert(self.addsubvalue(point[barname], j))
1453 except (ArithmeticError, ValueError, TypeError):
1454 sharedata.vbarrange[i][j] = None
1455 try:
1456 sharedata.vpos[i] = 0.5*(sharedata.vbarrange[i][0]+sharedata.vbarrange[i][1])
1457 except (ArithmeticError, ValueError, TypeError):
1458 sharedata.vpos[i] = None
1459 if sharedata.vpos[i] is None:
1460 sharedata.vposavailable = sharedata.vposvalid = 0
1461 elif sharedata.vpos[i] < -self.epsilon or sharedata.vpos[i] > 1+self.epsilon:
1462 sharedata.vposvalid = 0
1464 registerdefaultprovider(barpos(), ["vbarrange", "barposcolumnnames", "barvalueindex", "lastbarvalue", "stackedbar", "stackedbardraw"])
1467 class stackedbarpos(_style):
1469 # provides no additional data, but needs some data (and modifies some of them)
1470 needsdata = ["vbarrange", "barposcolumnnames", "barvalueindex", "lastbarvalue", "stackedbar", "stackedbardraw"]
1472 def __init__(self, stackname, addontop=0, epsilon=1e-10):
1473 self.stackname = stackname
1474 self.epsilon = epsilon
1475 self.addontop = addontop
1477 def columnnames(self, privatedata, sharedata, graph, columnnames, dataaxisnames):
1478 if self.stackname not in columnnames:
1479 raise ValueError("column '%s' missing" % self.stackname)
1480 return [self.stackname]
1482 def adjustaxis(self, privatedata, sharedata, graph, plotitem, columnname, data):
1483 if columnname == self.stackname:
1484 graph.axes[sharedata.barposcolumnnames[sharedata.barvalueindex]].adjustaxis(data)
1486 def initdrawpoints(self, privatedata, sharedata, graph):
1487 if sharedata.stackedbardraw: # do not count the start bar when not gets painted
1488 sharedata.stackedbar += 1
1490 def drawpoint(self, privatedata, sharedata, graph, point):
1491 sharedata.vbarrange[sharedata.barvalueindex][0] = sharedata.vbarrange[sharedata.barvalueindex][1]
1492 if self.addontop:
1493 try:
1494 sharedata.lastbarvalue += point[self.stackname]
1495 except (ArithmeticError, ValueError, TypeError):
1496 sharedata.lastbarvalue = None
1497 else:
1498 sharedata.lastbarvalue = point[self.stackname]
1499 try:
1500 sharedata.vpos[sharedata.barvalueindex] = sharedata.vbarrange[sharedata.barvalueindex][1] = graph.axes[sharedata.barposcolumnnames[sharedata.barvalueindex]].convert(sharedata.lastbarvalue)
1501 except (ArithmeticError, ValueError, TypeError):
1502 sharedata.vpos[sharedata.barvalueindex] = sharedata.vbarrange[sharedata.barvalueindex][1] = None
1503 sharedata.vposavailable = sharedata.vposvalid = 0
1504 else:
1505 if not sharedata.vposavailable or not sharedata.vposvalid:
1506 sharedata.vposavailable = sharedata.vposvalid = 1
1507 for v in sharedata.vpos:
1508 if v is None:
1509 sharedata.vposavailable = sharedata.vposvalid = 0
1510 break
1511 if v < -self.epsilon or v > 1+self.epsilon:
1512 sharedata.vposvalid = 0
1515 class bar(_style):
1517 needsdata = ["vbarrange"]
1519 defaultbarattrs = [color.gradient.Rainbow, deco.stroked([color.grey.black])]
1521 def __init__(self, barattrs=[], epsilon=1e-10, gradient=color.gradient.RedBlack):
1522 self.barattrs = barattrs
1523 self.epsilon = epsilon
1524 self.gradient = gradient
1526 def lighting(self, angle, zindex):
1527 return self.gradient.getcolor(0.7-0.4*abs(angle)+0.1*zindex)
1529 def columnnames(self, privatedata, sharedata, graph, columnnames, dataaxisnames):
1530 return []
1532 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1533 privatedata.barattrs = attr.selectattrs(self.defaultbarattrs + self.barattrs, selectindex, selecttotal)
1535 def initdrawpoints(self, privatedata, sharedata, graph):
1536 privatedata.barcanvas = graph.layer("filldata").insert(canvas.canvas())
1537 sharedata.stackedbardraw = 1
1538 privatedata.stackedbar = sharedata.stackedbar
1539 privatedata.todraw = []
1541 def drawpointfill(self, privatedata, p):
1542 if p:
1543 privatedata.barcanvas.fill(p, privatedata.barattrs)
1545 def drawpoint(self, privatedata, sharedata, graph, point):
1546 vbarrange = []
1547 for vmin, vmax in sharedata.vbarrange:
1548 if vmin is None or vmax is None:
1549 self.drawpointfill(privatedata, None)
1550 return
1551 if vmin > vmax:
1552 vmin, vmax = vmax, vmin
1553 if vmin > 1 or vmax < 0:
1554 self.drawpointfill(privatedata, None)
1555 return
1556 if vmin < 0:
1557 vmin = 0
1558 if vmax > 1:
1559 vmax = 1
1560 vbarrange.append((vmin, vmax))
1561 if len(vbarrange) == 2:
1562 p = graph.vgeodesic(vbarrange[0][0], vbarrange[1][0], vbarrange[0][1], vbarrange[1][0])
1563 p.append(graph.vgeodesic_el(vbarrange[0][1], vbarrange[1][0], vbarrange[0][1], vbarrange[1][1]))
1564 p.append(graph.vgeodesic_el(vbarrange[0][1], vbarrange[1][1], vbarrange[0][0], vbarrange[1][1]))
1565 p.append(graph.vgeodesic_el(vbarrange[0][0], vbarrange[1][1], vbarrange[0][0], vbarrange[1][0]))
1566 p.append(path.closepath())
1567 self.drawpointfill(privatedata, p)
1568 elif len(vbarrange) == 3:
1569 planes = []
1570 if abs(vbarrange[0][0] - vbarrange[0][1]) > self.epsilon and abs(vbarrange[1][0] - vbarrange[1][1]):
1571 planes.append((vbarrange[0][0], vbarrange[1][0], vbarrange[2][0],
1572 vbarrange[0][1], vbarrange[1][0], vbarrange[2][0],
1573 vbarrange[0][1], vbarrange[1][1], vbarrange[2][0],
1574 vbarrange[0][0], vbarrange[1][1], vbarrange[2][0]))
1575 planes.append((vbarrange[0][0], vbarrange[1][0], vbarrange[2][1],
1576 vbarrange[0][0], vbarrange[1][1], vbarrange[2][1],
1577 vbarrange[0][1], vbarrange[1][1], vbarrange[2][1],
1578 vbarrange[0][1], vbarrange[1][0], vbarrange[2][1]))
1579 if abs(vbarrange[0][0] - vbarrange[0][1]) > self.epsilon and abs(vbarrange[2][0] - vbarrange[2][1]):
1580 planes.append((vbarrange[0][0], vbarrange[1][0], vbarrange[2][0],
1581 vbarrange[0][0], vbarrange[1][0], vbarrange[2][1],
1582 vbarrange[0][1], vbarrange[1][0], vbarrange[2][1],
1583 vbarrange[0][1], vbarrange[1][0], vbarrange[2][0]))
1584 planes.append((vbarrange[0][0], vbarrange[1][1], vbarrange[2][0],
1585 vbarrange[0][1], vbarrange[1][1], vbarrange[2][0],
1586 vbarrange[0][1], vbarrange[1][1], vbarrange[2][1],
1587 vbarrange[0][0], vbarrange[1][1], vbarrange[2][1]))
1588 if abs(vbarrange[1][0] - vbarrange[1][1]) > self.epsilon and abs(vbarrange[2][0] - vbarrange[2][1]):
1589 planes.append((vbarrange[0][0], vbarrange[1][0], vbarrange[2][0],
1590 vbarrange[0][0], vbarrange[1][1], vbarrange[2][0],
1591 vbarrange[0][0], vbarrange[1][1], vbarrange[2][1],
1592 vbarrange[0][0], vbarrange[1][0], vbarrange[2][1]))
1593 planes.append((vbarrange[0][1], vbarrange[1][0], vbarrange[2][0],
1594 vbarrange[0][1], vbarrange[1][0], vbarrange[2][1],
1595 vbarrange[0][1], vbarrange[1][1], vbarrange[2][1],
1596 vbarrange[0][1], vbarrange[1][1], vbarrange[2][0]))
1597 v = [0.5 * (vbarrange[0][0] + vbarrange[0][1]),
1598 0.5 * (vbarrange[1][0] + vbarrange[1][1]),
1599 0.5 * (vbarrange[2][0] + vbarrange[2][1])]
1600 v[sharedata.barvalueindex] = 0.5
1601 zindex = graph.vzindex(*v)
1602 for v11, v12, v13, v21, v22, v23, v31, v32, v33, v41, v42, v43 in planes:
1603 angle = graph.vangle(v11, v12, v13, v21, v22, v23, v41, v42, v43)
1604 if angle > 0:
1605 p = graph.vgeodesic(v11, v12, v13, v21, v22, v23)
1606 p.append(graph.vgeodesic_el(v21, v22, v23, v31, v32, v33))
1607 p.append(graph.vgeodesic_el(v31, v32, v33, v41, v42, v43))
1608 p.append(graph.vgeodesic_el(v41, v42, v43, v11, v12, v13))
1609 p.append(path.closepath())
1610 if self.gradient:
1611 privatedata.todraw.append((-zindex, p, privatedata.barattrs + [self.lighting(angle, zindex)]))
1612 else:
1613 privatedata.todraw.append((-zindex, p, privatedata.barattrs))
1614 else:
1615 raise TypeError("bar style restricted to two- and three dimensional graphs")
1617 def donedrawpoints(self, privatedata, sharedata, graph):
1618 privatedata.todraw.sort()
1619 for vzindex, p, a in privatedata.todraw:
1620 privatedata.barcanvas.fill(p, a)
1622 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
1623 selectindex = privatedata.stackedbar
1624 selecttotal = sharedata.stackedbar + 1
1625 graph.fill(path.rect_pt(x_pt + width_pt*selectindex/float(selecttotal), y_pt, width_pt/float(selecttotal), height_pt), privatedata.barattrs)
1628 class changebar(bar):
1630 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1631 if selecttotal != 1:
1632 raise RuntimeError("Changebar can't change its appearance. Thus you can't use it to plot several bars side by side on a subaxis.")
1634 def initdrawpoints(self, privatedata, sharedata, graph):
1635 if len(graph.axesnames) != 2:
1636 raise TypeError("changebar style restricted on two-dimensional graphs (at least for the moment)")
1637 bar.initdrawpoints(self, privatedata, sharedata, graph)
1638 privatedata.bars = []
1640 def drawpointfill(self, privatedata, p):
1641 privatedata.bars.append(p)
1643 def donedrawpoints(self, privatedata, sharedata, graph):
1644 selecttotal = len(privatedata.bars)
1645 for selectindex, p in enumerate(privatedata.bars):
1646 if p:
1647 barattrs = attr.selectattrs(self.defaultbarattrs + self.barattrs, selectindex, selecttotal)
1648 privatedata.barcanvas.fill(p, barattrs)
1650 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
1651 raise RuntimeError("Style currently doesn't provide a graph key")
1654 class gridpos(_style):
1656 needsdata = ["vpos", "vposmissing", "vposavailable", "vposvalid"]
1657 providesdata = ["values1", "values2", "data12", "data21", "index1", "index2"]
1659 def __init__(self, index1=0, index2=1, epsilon=1e-10):
1660 self.index1 = index1
1661 self.index2 = index2
1662 self.epsilon = epsilon
1664 def initdrawpoints(self, privatedata, sharedata, graph):
1665 sharedata.index1 = self.index1
1666 sharedata.index2 = self.index2
1667 sharedata.values1 = {}
1668 sharedata.values2 = {}
1669 sharedata.data12 = {}
1670 sharedata.data21 = {}
1672 def drawpoint(self, privatedata, sharedata, graph, point):
1673 if sharedata.vposavailable:
1674 sharedata.value1 = sharedata.vpos[self.index1]
1675 sharedata.value2 = sharedata.vpos[self.index2]
1676 if not sharedata.values1.has_key(sharedata.value1):
1677 for hasvalue in sharedata.values1.keys():
1678 if hasvalue - self.epsilon <= sharedata.value1 <= hasvalue + self.epsilon:
1679 sharedata.value1 = hasvalue
1680 break
1681 else:
1682 sharedata.values1[sharedata.value1] = 1
1683 if not sharedata.values2.has_key(sharedata.value2):
1684 for hasvalue in sharedata.values2.keys():
1685 if hasvalue - self.epsilon <= sharedata.value2 <= hasvalue + self.epsilon:
1686 sharedata.value2 = hasvalue
1687 break
1688 else:
1689 sharedata.values2[sharedata.value2] = 1
1690 data = sharedata.vposavailable, sharedata.vposvalid, sharedata.vpos[:]
1691 sharedata.data12.setdefault(sharedata.value1, {})[sharedata.value2] = data
1692 sharedata.data21.setdefault(sharedata.value2, {})[sharedata.value1] = data
1694 registerdefaultprovider(gridpos(), gridpos.providesdata)
1697 class grid(_line, _style):
1699 needsdata = ["values1", "values2", "data12", "data21"]
1701 defaultgridattrs = [line.changelinestyle]
1703 def __init__(self, gridlines1=1, gridlines2=1, gridattrs=[]):
1704 self.gridlines1 = gridlines1
1705 self.gridlines2 = gridlines2
1706 self.gridattrs = gridattrs
1708 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1709 if self.gridattrs is not None:
1710 privatedata.gridattrs = attr.selectattrs(self.defaultgridattrs + self.gridattrs, selectindex, selecttotal)
1711 else:
1712 privatedata.gridattrs = None
1714 def donedrawpoints(self, privatedata, sharedata, graph):
1715 values1 = sharedata.values1.keys()
1716 values1.sort()
1717 values2 = sharedata.values2.keys()
1718 values2.sort()
1719 if self.gridlines1:
1720 for value2 in values2:
1721 data1 = sharedata.data21[value2]
1722 self.initpointstopath(privatedata)
1723 for value1 in values1:
1724 try:
1725 data = data1[value1]
1726 except KeyError:
1727 self.addinvalid(privatedata)
1728 else:
1729 self.addpoint(privatedata, graph.vpos_pt, *data)
1730 p = self.donepointstopath(privatedata)
1731 if len(p):
1732 graph.layer("data").stroke(p, privatedata.gridattrs)
1733 if self.gridlines2:
1734 for value1 in values1:
1735 data2 = sharedata.data12[value1]
1736 self.initpointstopath(privatedata)
1737 for value2 in values2:
1738 try:
1739 data = data2[value2]
1740 except KeyError:
1741 self.addinvalid(privatedata)
1742 else:
1743 self.addpoint(privatedata, graph.vpos_pt, *data)
1744 p = self.donepointstopath(privatedata)
1745 if len(p):
1746 graph.layer("data").stroke(p, privatedata.gridattrs)
1749 class surface(_keygraphstyle):
1751 needsdata = ["values1", "values2", "data12", "data21"]
1753 def __init__(self, gridlines1=0.05, gridlines2=0.05, gridcolor=None,
1754 backcolor=color.gray.black, **kwargs):
1755 _keygraphstyle.__init__(self, **kwargs)
1756 self.gridlines1 = gridlines1
1757 self.gridlines2 = gridlines2
1758 self.gridcolor = gridcolor
1759 self.backcolor = backcolor
1761 colorspacestring = self.gradient.getcolor(0).colorspacestring()
1762 if self.gridcolor is not None and self.gridcolor.colorspacestring() != colorspacestring:
1763 raise RuntimeError("colorspace mismatch (gradient/grid)")
1764 if self.backcolor is not None and self.backcolor.colorspacestring() != colorspacestring:
1765 raise RuntimeError("colorspace mismatch (gradient/back)")
1767 def midvalue(self, v1, v2, v3, v4):
1768 return [0.25*sum(values) for values in zip(v1, v2, v3, v4)]
1770 def midcolor(self, c1, c2, c3, c4):
1771 return 0.25*(c1+c2+c3+c4)
1773 def lighting(self, angle, zindex):
1774 if angle < 0 and self.backcolor is not None:
1775 return self.backcolor
1776 return self.gradient.getcolor(0.7-0.4*abs(angle)+0.1*zindex)
1778 def columnnames(self, privatedata, sharedata, graph, columnnames, dataaxisnames):
1779 privatedata.colorize = self.colorname in columnnames
1780 if privatedata.colorize:
1781 return _keygraphstyle.columnnames(self, privatedata, sharedata, graph, columnnames, dataaxisnames)
1782 return []
1784 def initdrawpoints(self, privatedata, sharedata, graph):
1785 privatedata.colors = {}
1786 privatedata.mincolor = privatedata.maxcolor = None
1788 def drawpoint(self, privatedata, sharedata, graph, point):
1789 if privatedata.colorize:
1790 try:
1791 color = point[self.colorname] + 0
1792 except:
1793 pass
1794 else:
1795 privatedata.colors.setdefault(sharedata.value1, {})[sharedata.value2] = color
1796 if privatedata.mincolor is None or color < privatedata.mincolor:
1797 privatedata.mincolor = color
1798 if privatedata.mincolor is None or privatedata.maxcolor < color:
1799 privatedata.maxcolor = color
1801 def donedrawpoints(self, privatedata, sharedata, graph):
1802 v1 = [0]*len(graph.axesnames)
1803 v2 = [0]*len(graph.axesnames)
1804 v3 = [0]*len(graph.axesnames)
1805 v4 = [0]*len(graph.axesnames)
1806 v1[sharedata.index2] = 0.5
1807 v2[sharedata.index1] = 0.5
1808 v3[sharedata.index1] = 0.5
1809 v3[sharedata.index2] = 1
1810 v4[sharedata.index1] = 1
1811 v4[sharedata.index2] = 0.5
1812 sortElements = [-graph.vzindex(*v1),
1813 -graph.vzindex(*v2),
1814 -graph.vzindex(*v3),
1815 -graph.vzindex(*v4)]
1817 values1 = sharedata.values1.keys()
1818 values1.sort()
1819 v1 = [0]*len(graph.axesnames)
1820 v2 = [0]*len(graph.axesnames)
1821 v1[sharedata.index1] = -1
1822 v2[sharedata.index1] = 1
1823 sign = 1
1824 if graph.vzindex(*v1) < graph.vzindex(*v2):
1825 values1.reverse()
1826 sign *= -1
1827 sortElements = [sortElements[3], sortElements[1], sortElements[2], sortElements[0]]
1829 values2 = sharedata.values2.keys()
1830 values2.sort()
1831 v1 = [0]*len(graph.axesnames)
1832 v2 = [0]*len(graph.axesnames)
1833 v1[sharedata.index2] = -1
1834 v2[sharedata.index2] = 1
1835 if graph.vzindex(*v1) < graph.vzindex(*v2):
1836 values2.reverse()
1837 sign *= -1
1838 sortElements = [sortElements[0], sortElements[2], sortElements[1], sortElements[3]]
1840 sortElements = [(zindex, i) for i, zindex in enumerate(sortElements)]
1841 sortElements.sort()
1843 nodes = []
1844 elements = []
1845 for value1a, value1b in zip(values1[:-1], values1[1:]):
1846 for value2a, value2b in zip(values2[:-1], values2[1:]):
1847 try:
1848 available1, valid1, v1 = sharedata.data12[value1a][value2a]
1849 available2, valid2, v2 = sharedata.data12[value1a][value2b]
1850 available3, valid3, v3 = sharedata.data12[value1b][value2a]
1851 available4, valid4, v4 = sharedata.data12[value1b][value2b]
1852 except KeyError:
1853 continue
1854 if not available1 or not available2 or not available3 or not available4:
1855 continue
1856 if not valid1 or not valid2 or not valid3 or not valid4:
1857 warnings.warn("surface elements partially outside of the graph are (currently) skipped completely")
1858 continue
1859 def shrink(index, v1, v2, by):
1860 v1 = v1[:]
1861 v2 = v2[:]
1862 for i in builtinrange(3):
1863 if i != index:
1864 v1[i], v2[i] = v1[i] + by*(v2[i]-v1[i]), v2[i] + by*(v1[i]-v2[i])
1865 return v1, v2
1866 v1f, v2f, v3f, v4f = v1, v2, v3, v4
1867 if self.gridcolor is not None and self.gridlines1:
1868 v1, v2 = shrink(sharedata.index1, v1, v2, self.gridlines1)
1869 v3, v4 = shrink(sharedata.index1, v3, v4, self.gridlines1)
1870 if self.gridcolor is not None and self.gridlines2:
1871 v1, v3 = shrink(sharedata.index2, v1, v3, self.gridlines2)
1872 v2, v4 = shrink(sharedata.index2, v2, v4, self.gridlines2)
1873 v5 = self.midvalue(v1, v2, v3, v4)
1874 x1_pt, y1_pt = graph.vpos_pt(*v1)
1875 x2_pt, y2_pt = graph.vpos_pt(*v2)
1876 x3_pt, y3_pt = graph.vpos_pt(*v3)
1877 x4_pt, y4_pt = graph.vpos_pt(*v4)
1878 x5_pt, y5_pt = graph.vpos_pt(*v5)
1879 if privatedata.colorize:
1880 c1 = privatedata.colors[value1a][value2a]
1881 c2 = privatedata.colors[value1a][value2b]
1882 c3 = privatedata.colors[value1b][value2a]
1883 c4 = privatedata.colors[value1b][value2b]
1884 c5 = self.midcolor(c1, c2, c3, c4)
1885 c1a = c1b = self.color(privatedata, c1)
1886 c2a = c2c = self.color(privatedata, c2)
1887 c3b = c3d = self.color(privatedata, c3)
1888 c4c = c4d = self.color(privatedata, c4)
1889 c5a = c5b = c5c = c5d = self.color(privatedata, c5)
1890 if self.backcolor is not None and sign*graph.vangle(*(v1+v2+v5)) < 0:
1891 c1a = c2a = c5a = self.backcolor
1892 if self.backcolor is not None and sign*graph.vangle(*(v3+v1+v5)) < 0:
1893 c3b = c1b = c5b = self.backcolor
1894 if self.backcolor is not None and sign*graph.vangle(*(v2+v4+v5)) < 0:
1895 c2c = c4c = c5c = self.backcolor
1896 if self.backcolor is not None and sign*graph.vangle(*(v4+v3+v5)) < 0:
1897 c4d = c3d = c5d = self.backcolor
1898 else:
1899 zindex = graph.vzindex(*v5)
1900 c1a = c2a = c5a = self.lighting(sign*graph.vangle(*(v1+v2+v5)), zindex)
1901 c3b = c1b = c5b = self.lighting(sign*graph.vangle(*(v3+v1+v5)), zindex)
1902 c2c = c4c = c5c = self.lighting(sign*graph.vangle(*(v2+v4+v5)), zindex)
1903 c4d = c3d = c5d = self.lighting(sign*graph.vangle(*(v4+v3+v5)), zindex)
1904 for zindex, i in sortElements:
1905 if i == 0:
1906 elements.append(mesh.element((mesh.node_pt((x1_pt, y1_pt), c1a),
1907 mesh.node_pt((x2_pt, y2_pt), c2a),
1908 mesh.node_pt((x5_pt, y5_pt), c5a))))
1909 if self.gridcolor is not None and self.gridlines2:
1910 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v1f), self.gridcolor),
1911 mesh.node_pt(graph.vpos_pt(*v2), self.gridcolor),
1912 mesh.node_pt(graph.vpos_pt(*v1), self.gridcolor))))
1913 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v1f), self.gridcolor),
1914 mesh.node_pt(graph.vpos_pt(*v2), self.gridcolor),
1915 mesh.node_pt(graph.vpos_pt(*v2f), self.gridcolor))))
1916 elif i == 1:
1917 elements.append(mesh.element((mesh.node_pt((x3_pt, y3_pt), c3b),
1918 mesh.node_pt((x1_pt, y1_pt), c1b),
1919 mesh.node_pt((x5_pt, y5_pt), c5b))))
1920 if self.gridcolor is not None and self.gridlines1:
1921 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v1f), self.gridcolor),
1922 mesh.node_pt(graph.vpos_pt(*v3), self.gridcolor),
1923 mesh.node_pt(graph.vpos_pt(*v1), self.gridcolor))))
1924 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v1f), self.gridcolor),
1925 mesh.node_pt(graph.vpos_pt(*v3), self.gridcolor),
1926 mesh.node_pt(graph.vpos_pt(*v3f), self.gridcolor))))
1927 elif i == 2:
1928 elements.append(mesh.element((mesh.node_pt((x2_pt, y2_pt), c2c),
1929 mesh.node_pt((x4_pt, y4_pt), c4c),
1930 mesh.node_pt((x5_pt, y5_pt), c5c))))
1931 if self.gridcolor is not None and self.gridlines1:
1932 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v2f), self.gridcolor),
1933 mesh.node_pt(graph.vpos_pt(*v4), self.gridcolor),
1934 mesh.node_pt(graph.vpos_pt(*v2), self.gridcolor))))
1935 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v2f), self.gridcolor),
1936 mesh.node_pt(graph.vpos_pt(*v4), self.gridcolor),
1937 mesh.node_pt(graph.vpos_pt(*v4f), self.gridcolor))))
1938 elif i == 3:
1939 elements.append(mesh.element((mesh.node_pt((x4_pt, y4_pt), c4d),
1940 mesh.node_pt((x3_pt, y3_pt), c3d),
1941 mesh.node_pt((x5_pt, y5_pt), c5d))))
1942 if self.gridcolor is not None and self.gridlines2:
1943 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v3f), self.gridcolor),
1944 mesh.node_pt(graph.vpos_pt(*v4), self.gridcolor),
1945 mesh.node_pt(graph.vpos_pt(*v3), self.gridcolor))))
1946 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v3f), self.gridcolor),
1947 mesh.node_pt(graph.vpos_pt(*v4), self.gridcolor),
1948 mesh.node_pt(graph.vpos_pt(*v4f), self.gridcolor))))
1949 m = mesh.mesh(elements, check=0)
1950 graph.layer("filldata").insert(m)
1952 if privatedata.colorize:
1953 _keygraphstyle.donedrawpoints(self, privatedata, sharedata, graph)
1956 class density(_keygraphstyle):
1958 needsdata = ["values1", "values2", "data12", "data21"]
1960 def __init__(self, epsilon=1e-10, **kwargs):
1961 _keygraphstyle.__init__(self, **kwargs)
1962 self.epsilon = epsilon
1964 def initdrawpoints(self, privatedata, sharedata, graph):
1965 privatedata.colors = {}
1966 privatedata.vfixed = [None]*len(graph.axesnames)
1968 def drawpoint(self, privatedata, sharedata, graph, point):
1969 try:
1970 color = point[self.colorname] + 0
1971 except:
1972 pass
1973 else:
1974 privatedata.colors.setdefault(sharedata.value1, {})[sharedata.value2] = color
1975 if len(privatedata.vfixed) > 2 and sharedata.vposavailable:
1976 for i, (v1, v2) in enumerate(zip(privatedata.vfixed, sharedata.vpos)):
1977 if i != sharedata.index1 and i != sharedata.index2:
1978 if v1 is None:
1979 privatedata.vfixed[i] = v2
1980 elif abs(v1-v2) > self.epsilon:
1981 raise ValueError("data must be in a plane for the bitmap style")
1983 def donedrawpoints(self, privatedata, sharedata, graph):
1984 privatedata.keygraph.doaxes()
1986 values1 = pycompat.sorted(sharedata.values1.keys())
1987 values2 = pycompat.sorted(sharedata.values2.keys())
1988 def equidistant(values):
1989 l = len(values) - 1
1990 if l < 1:
1991 raise ValueError("several data points required by the bitmap style in each dimension")
1992 range = values[-1] - values[0]
1993 for i, value in enumerate(values):
1994 if abs(value - values[0] - i * range / l) > self.epsilon:
1995 raise ValueError("data must be equidistant for the bitmap style")
1996 equidistant(values1)
1997 equidistant(values2)
1998 needalpha = False
1999 for value2 in values2:
2000 for value1 in values1:
2001 try:
2002 available, valid, v = sharedata.data12[value1][value2]
2003 except KeyError:
2004 needalpha = True
2005 break
2006 if not available:
2007 needalpha = True
2008 continue
2009 else:
2010 continue
2011 break
2012 mode = {"/DeviceGray": "L",
2013 "/DeviceRGB": "RGB",
2014 "/DeviceCMYK": "CMYK"}[self.gradient.getcolor(0).colorspacestring()]
2015 if needalpha:
2016 mode = "A" + mode
2017 empty = "\0"*len(mode)
2018 data = cStringIO.StringIO()
2019 for value2 in values2:
2020 for value1 in values1:
2021 try:
2022 available, valid, v = sharedata.data12[value1][value2]
2023 except KeyError:
2024 data.write(empty)
2025 continue
2026 if not available:
2027 data.write(empty)
2028 continue
2029 c = privatedata.colors[value1][value2]
2030 c = self.color(privatedata, c)
2031 if needalpha:
2032 data.write(chr(255))
2033 data.write(c.to8bitstring())
2034 i = bitmap.image(len(values1), len(values2), mode, data.getvalue())
2036 v1enlargement = (values1[-1]-values1[0])*0.5/len(values1)
2037 v2enlargement = (values2[-1]-values2[0])*0.5/len(values2)
2039 privatedata.vfixed[sharedata.index1] = values1[0]-v1enlargement
2040 privatedata.vfixed[sharedata.index2] = values2[-1]+v2enlargement
2041 x1_pt, y1_pt = graph.vpos_pt(*privatedata.vfixed)
2042 privatedata.vfixed[sharedata.index1] = values1[-1]+v1enlargement
2043 privatedata.vfixed[sharedata.index2] = values2[-1]+v2enlargement
2044 x2_pt, y2_pt = graph.vpos_pt(*privatedata.vfixed)
2045 privatedata.vfixed[sharedata.index1] = values1[0]-v1enlargement
2046 privatedata.vfixed[sharedata.index2] = values2[0]-v2enlargement
2047 x3_pt, y3_pt = graph.vpos_pt(*privatedata.vfixed)
2048 t = trafo.trafo_pt(((x2_pt-x1_pt, x3_pt-x1_pt), (y2_pt-y1_pt, y3_pt-y1_pt)), (x1_pt, y1_pt))
2050 privatedata.vfixed[sharedata.index1] = values1[-1]+v1enlargement
2051 privatedata.vfixed[sharedata.index2] = values2[0]-v2enlargement
2052 vx4, vy4 = t.inverse().apply_pt(*graph.vpos_pt(*privatedata.vfixed))
2053 if abs(vx4 - 1) > self.epsilon or abs(vy4 - 1) > self.epsilon:
2054 raise ValueError("invalid graph layout for bitmap style (bitmap positioning by affine transformation failed)")
2056 p = path.path()
2057 privatedata.vfixed[sharedata.index1] = 0
2058 privatedata.vfixed[sharedata.index2] = 0
2059 p.append(path.moveto_pt(*graph.vpos_pt(*privatedata.vfixed)))
2060 vfixed2 = privatedata.vfixed + privatedata.vfixed
2061 vfixed2[sharedata.index1] = 0
2062 vfixed2[sharedata.index2] = 0
2063 vfixed2[sharedata.index1 + len(graph.axesnames)] = 1
2064 vfixed2[sharedata.index2 + len(graph.axesnames)] = 0
2065 p.append(graph.vgeodesic_el(*vfixed2))
2066 vfixed2[sharedata.index1] = 1
2067 vfixed2[sharedata.index2] = 0
2068 vfixed2[sharedata.index1 + len(graph.axesnames)] = 1
2069 vfixed2[sharedata.index2 + len(graph.axesnames)] = 1
2070 p.append(graph.vgeodesic_el(*vfixed2))
2071 vfixed2[sharedata.index1] = 1
2072 vfixed2[sharedata.index2] = 1
2073 vfixed2[sharedata.index1 + len(graph.axesnames)] = 0
2074 vfixed2[sharedata.index2 + len(graph.axesnames)] = 1
2075 p.append(graph.vgeodesic_el(*vfixed2))
2076 vfixed2[sharedata.index1] = 0
2077 vfixed2[sharedata.index2] = 1
2078 vfixed2[sharedata.index1 + len(graph.axesnames)] = 0
2079 vfixed2[sharedata.index2 + len(graph.axesnames)] = 0
2080 p.append(graph.vgeodesic_el(*vfixed2))
2081 p.append(path.closepath())
2083 c = canvas.canvas([canvas.clip(p)])
2084 b = bitmap.bitmap_trafo(t, i)
2085 c.insert(b)
2086 graph.layer("filldata").insert(c)
2088 _keygraphstyle.donedrawpoints(self, privatedata, sharedata, graph)
2092 class gradient(_style):
2094 defaultbarattrs = [color.gradient.Rainbow, deco.stroked([color.grey.black])]
2096 def __init__(self, gradient=color.gradient.Gray, resolution=100, columnname="x"):
2097 self.gradient = gradient
2098 self.resolution = resolution
2099 self.columnname = columnname
2101 def columnnames(self, privatedata, sharedata, graph, columnnames, dataaxisnames):
2102 return [self.columnname]
2104 def adjustaxis(self, privatedata, sharedata, graph, plotitem, columnname, data):
2105 graph.axes[self.columnname].adjustaxis(data)
2107 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
2108 pass
2110 def initdrawpoints(self, privatedata, sharedata, graph):
2111 pass
2113 def drawpoint(self, privatedata, sharedata, graph, point):
2114 pass
2116 def donedrawpoints(self, privatedata, sharedata, graph):
2117 mode = {"/DeviceGray": "L",
2118 "/DeviceRGB": "RGB",
2119 "/DeviceCMYK": "CMYK"}[self.gradient.getcolor(0).colorspacestring()]
2120 data = cStringIO.StringIO()
2121 for i in builtinrange(self.resolution):
2122 c = self.gradient.getcolor(i*1.0/self.resolution)
2123 data.write(c.to8bitstring())
2124 i = bitmap.image(self.resolution, 1, mode, data.getvalue())
2126 llx_pt, lly_pt = graph.vpos_pt(0)
2127 urx_pt, ury_pt = graph.vpos_pt(1)
2129 if graph.direction == "horizontal":
2130 lly_pt -= 0.5*graph.height_pt
2131 ury_pt += 0.5*graph.height_pt
2132 else:
2133 llx_pt -= 0.5*graph.width_pt
2134 urx_pt += 0.5*graph.width_pt
2136 c = canvas.canvas([canvas.clip(path.rect_pt(llx_pt, lly_pt, urx_pt-llx_pt, ury_pt-lly_pt))])
2138 if graph.direction == "horizontal":
2139 add_pt = (urx_pt-llx_pt)*0.5/(self.resolution-1)
2140 llx_pt -= add_pt
2141 urx_pt += add_pt
2142 else:
2143 add_pt = (ury_pt-lly_pt)*0.5/(self.resolution-1)
2144 lly_pt -= add_pt
2145 ury_pt += add_pt
2147 if graph.direction == "horizontal":
2148 t = trafo.trafo_pt(((urx_pt-llx_pt,0 ), (0, ury_pt-lly_pt)), (llx_pt, lly_pt))
2149 else:
2150 t = trafo.trafo_pt(((0, urx_pt-llx_pt), (ury_pt-lly_pt, 0)), (llx_pt, lly_pt))
2152 b = bitmap.bitmap_trafo(t, i)
2153 c.insert(b)
2154 graph.layer("filldata").insert(c)