new style bars become are stackable now
[PyX.git] / pyx / graph / style.py
blobcb8fd125a7299cad6f726c94354f15cc4d228ba5
1 #!/usr/bin/env python
2 # -*- coding: ISO-8859-1 -*-
5 # Copyright (C) 2002-2004 Jörg Lehmann <joergl@users.sourceforge.net>
6 # Copyright (C) 2003-2004 Michael Schindler <m-schindler@users.sourceforge.net>
7 # Copyright (C) 2002-2004 André Wobst <wobsta@users.sourceforge.net>
9 # This file is part of PyX (http://pyx.sourceforge.net/).
11 # PyX is free software; you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation; either version 2 of the License, or
14 # (at your option) any later version.
16 # PyX is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # GNU General Public License for more details.
21 # You should have received a copy of the GNU General Public License
22 # along with PyX; if not, write to the Free Software
23 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
26 import math
27 from pyx import attr, deco, style, color, unit, canvas, path
28 from pyx import text as textmodule
30 try:
31 enumerate([])
32 except NameError:
33 # fallback implementation for Python 2.2. and below
34 def enumerate(list):
35 return zip(xrange(len(list)), list)
37 class _style:
38 """Interface class for graph styles
40 Each graph style must support the methods described in this
41 class. However, since a graph style might not need to perform
42 actions on all the various events, it does not need to overwrite
43 all methods of this base class (e.g. this class is not an abstract
44 class in any respect).
46 A style should never store private data by istance variables
47 (i.e. accessing self), but it should use the sharedata and privatedata
48 instances instead. A style instance can be used multiple times with
49 different sharedata and privatedata instances at the very same time.
50 The sharedata and privatedata instances act as data containers and
51 sharedata allows for sharing information across several styles.
53 Every style contains two class variables, which are not to be
54 modified:
55 - providesdata is a list of variable names a style offers via
56 the sharedata instance. This list is used to determine whether
57 all needs of subsequent styles are fullfilled. Otherwise
58 getdefaultprovider should return a proper style to be used.
59 - needsdata is a list of variable names the style needs to access in the
60 sharedata instance.
61 """
63 providesdata = [] # by default, we provide nothing
64 needsdata = [] # and do not depend on anything
66 def columns(self, privatedata, sharedata, graph, columns):
67 """Set column information
69 This method is used setup the column information to be
70 accessible to the style later on. The style should analyse
71 the list of strings columns, which contain the column names
72 of the data. The method should return a list of column names
73 which the style will make use of."""
74 return []
76 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
77 """Select stroke/fill attributes
79 This method is called to allow for the selection of
80 changable attributes of a style."""
81 pass
83 def adjustaxis(self, privatedata, sharedata, graph, column, data, index):
84 """Adjust axis range
86 This method is called in order to adjust the axis range to
87 the provided data. Column is the name of the column (each
88 style is subsequently called for all column names). If index
89 is not None, data is a list of points and index is the index
90 of the column within a point. Otherwise data is already the
91 axis data. Note, that data might be different for different
92 columns, e.i. data might come from various places and is
93 combined without copying but keeping references."""
94 pass
96 def initdrawpoints(self, privatedata, sharedata, graph):
97 """Initialize drawing of data
99 This method might be used to initialize the drawing of data."""
100 pass
102 def drawpoint(self, privatedata, sharedata, graph):
103 """Draw data
105 This method is called for each data point. The data is
106 available in the dictionary sharedata.point. The dictionary
107 keys are the column names."""
108 pass
110 def donedrawpoints(self, privatedata, sharedata, graph):
111 """Finalize drawing of data
113 This method is called after the last data point was
114 drawn using the drawpoint method above."""
115 pass
117 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt, dy_pt, selectindex, selecttotal):
118 """Draw graph key
120 This method draws a key for the style to graph at the given
121 position x_pt, y_pt indicating the lower left corner of the
122 given area width_pt, height_pt. The style might draw several
123 key entries shifted vertically by dy_pt. The method returns
124 the number of key entries or None, when nothing was drawn."""
125 return None
128 # The following two methods are used to register and get a default provider
129 # for keys. A key is a variable name in sharedata. A provider is a style
130 # which creates variables in sharedata.
132 _defaultprovider = {}
134 def registerdefaultprovider(style, keys):
135 """sets a style as a default creator for sharedata variables 'keys'"""
136 assert not len(style.needsdata), "currently we state, that a style should not depend on other sharedata variables"
137 for key in keys:
138 assert key in style.providesdata, "key not provided by style"
139 # we might allow for overwriting the defaults, i.e. the following is not checked:
140 # assert key in _defaultprovider.keys(), "default provider already registered for key"
141 _defaultprovider[key] = style
143 def getdefaultprovider(key):
144 """returns a style, which acts as a default creator for the
145 sharedata variable 'key'"""
146 return _defaultprovider[key]
149 class _pos(_style):
151 providesdata = ["vpos", "vposmissing", "vposavailable", "vposvalid"]
153 def __init__(self, epsilon=1e-10):
154 self.epsilon = epsilon
156 def columns(self, privatedata, sharedata, graph, columns):
157 privatedata.pointposcolumns = []
158 sharedata.vposmissing = []
159 for count, axisnames in enumerate(graph.axesnames):
160 for axisname in axisnames:
161 for column in columns:
162 if axisname == column:
163 privatedata.pointposcolumns.append(column)
164 if len(privatedata.pointposcolumns) + len(sharedata.vposmissing) > count+1:
165 raise ValueError("multiple axes per graph dimension")
166 elif len(privatedata.pointposcolumns) + len(sharedata.vposmissing) < count+1:
167 sharedata.vposmissing.append(count)
168 return privatedata.pointposcolumns
170 def adjustaxis(self, privatedata, sharedata, graph, column, data, index):
171 if column in privatedata.pointposcolumns:
172 graph.axes[column].adjustrange(data, index)
174 def initdrawpoints(self, privatedata, sharedata, graph):
175 sharedata.vpos = [None]*(len(privatedata.pointposcolumns) + len(sharedata.vposmissing))
176 privatedata.pointpostmplist = [[column, index, graph.axes[column]] # temporarily used by drawpoint only
177 for index, column in enumerate(privatedata.pointposcolumns)]
178 for missing in sharedata.vposmissing:
179 for pointpostmp in privatedata.pointpostmplist:
180 if pointpostmp[1] >= missing:
181 pointpostmp[1] += 1
183 def drawpoint(self, privatedata, sharedata, graph):
184 sharedata.vposavailable = 1 # valid position (but might be outside of the graph)
185 sharedata.vposvalid = 1 # valid position inside the graph
186 for column, index, axis in privatedata.pointpostmplist:
187 try:
188 v = axis.convert(sharedata.point[column])
189 except (ArithmeticError, ValueError, TypeError):
190 sharedata.vposavailable = sharedata.vposvalid = 0
191 sharedata.vpos[index] = None
192 else:
193 if v < -self.epsilon or v > 1+self.epsilon:
194 sharedata.vposvalid = 0
195 sharedata.vpos[index] = v
198 registerdefaultprovider(_pos(), _pos.providesdata)
201 class _range(_style):
203 providesdata = ["vrange", "vrangemissing", "vrangeminmissing", "vrangemaxmissing"]
205 # internal bit masks
206 mask_value = 1
207 mask_min = 2
208 mask_max = 4
209 mask_dmin = 8
210 mask_dmax = 16
211 mask_d = 32
213 def __init__(self, epsilon=1e-10):
214 self.epsilon = epsilon
216 def columns(self, privatedata, sharedata, graph, columns):
217 def numberofbits(mask):
218 if not mask:
219 return 0
220 if mask & 1:
221 return numberofbits(mask >> 1) + 1
222 else:
223 return numberofbits(mask >> 1)
224 usecolumns = []
225 privatedata.rangeposcolumns = []
226 sharedata.vrangemissing = []
227 sharedata.vrangeminmissing = []
228 sharedata.vrangemaxmissing = []
229 privatedata.rangeposdeltacolumns = {} # temporarily used by adjustaxis only
230 for count, axisnames in enumerate(graph.axesnames):
231 for axisname in axisnames:
232 mask = 0
233 for column in columns:
234 addusecolumns = 1
235 if axisname == column:
236 mask += self.mask_value
237 elif axisname + "min" == column:
238 mask += self.mask_min
239 elif axisname + "max" == column:
240 mask += self.mask_max
241 elif "d" + axisname + "min" == column:
242 mask += self.mask_dmin
243 elif "d" + axisname + "max" == column:
244 mask += self.mask_dmax
245 elif "d" + axisname == column:
246 mask += self.mask_d
247 else:
248 addusecolumns = 0
249 if addusecolumns:
250 usecolumns.append(column)
251 if mask & (self.mask_min | self.mask_max | self.mask_dmin | self.mask_dmax | self.mask_d):
252 if (numberofbits(mask & (self.mask_min | self.mask_dmin | self.mask_d)) > 1 or
253 numberofbits(mask & (self.mask_max | self.mask_dmax | self.mask_d)) > 1):
254 raise ValueError("multiple range definition")
255 if mask & (self.mask_dmin | self.mask_dmax | self.mask_d):
256 if not (mask & self.mask_value):
257 raise ValueError("missing value for delta")
258 privatedata.rangeposdeltacolumns[axisname] = {}
259 privatedata.rangeposcolumns.append((axisname, mask))
260 elif mask == self.mask_value:
261 usecolumns = usecolumns[:-1]
262 if len(privatedata.rangeposcolumns) + len(sharedata.vrangemissing) > count+1:
263 raise ValueError("multiple axes per graph dimension")
264 elif len(privatedata.rangeposcolumns) + len(sharedata.vrangemissing) < count+1:
265 sharedata.vrangemissing.append(count)
266 else:
267 if not (privatedata.rangeposcolumns[-1][1] & (self.mask_min | self.mask_dmin | self.mask_d)):
268 sharedata.vrangeminmissing.append(count)
269 if not (privatedata.rangeposcolumns[-1][1] & (self.mask_max | self.mask_dmax | self.mask_d)):
270 sharedata.vrangemaxmissing.append(count)
271 return usecolumns
273 def adjustaxis(self, privatedata, sharedata, graph, column, data, index):
274 if column in [c + "min" for c, m in privatedata.rangeposcolumns if m & self.mask_min]:
275 graph.axes[column[:-3]].adjustrange(data, index)
276 if column in [c + "max" for c, m in privatedata.rangeposcolumns if m & self.mask_max]:
277 graph.axes[column[:-3]].adjustrange(data, index)
279 # delta handling: fill rangeposdeltacolumns
280 if column in [c for c, m in privatedata.rangeposcolumns if m & (self.mask_dmin | self.mask_dmax | self.mask_d)]:
281 privatedata.rangeposdeltacolumns[column][self.mask_value] = data, index
282 if column in ["d" + c + "min" for c, m in privatedata.rangeposcolumns if m & self.mask_dmin]:
283 privatedata.rangeposdeltacolumns[column[1:-3]][self.mask_dmin] = data, index
284 if column in ["d" + c + "max" for c, m in privatedata.rangeposcolumns if m & self.mask_dmax]:
285 privatedata.rangeposdeltacolumns[column[1:-3]][self.mask_dmax] = data, index
286 if column in ["d" + c for c, m in privatedata.rangeposcolumns if m & self.mask_d]:
287 privatedata.rangeposdeltacolumns[column[1:]][self.mask_d] = data, index
289 # delta handling: process rangeposdeltacolumns
290 for c, d in privatedata.rangeposdeltacolumns.items():
291 if d.has_key(self.mask_value):
292 for k in d.keys():
293 if k != self.mask_value:
294 if k & (self.mask_dmin | self.mask_d):
295 graph.axes[c].adjustrange(d[self.mask_value][0], d[self.mask_value][1],
296 deltamindata=d[k][0], deltaminindex=d[k][1])
297 if k & (self.mask_dmax | self.mask_d):
298 graph.axes[c].adjustrange(d[self.mask_value][0], d[self.mask_value][1],
299 deltamaxdata=d[k][0], deltamaxindex=d[k][1])
300 del d[k]
302 def initdrawpoints(self, privatedata, sharedata, graph):
303 sharedata.vrange = [[None for x in range(2)] for y in privatedata.rangeposcolumns + sharedata.vrangemissing]
304 privatedata.rangepostmplist = [[column, mask, index, graph.axes[column]] # temporarily used by drawpoint only
305 for index, (column, mask) in enumerate(privatedata.rangeposcolumns)]
306 for missing in sharedata.vrangemissing:
307 for rangepostmp in privatedata.rangepostmplist:
308 if rangepostmp[2] >= missing:
309 rangepostmp[2] += 1
311 def drawpoint(self, privatedata, sharedata, graph):
312 for column, mask, index, axis in privatedata.rangepostmplist:
313 try:
314 if mask & self.mask_min:
315 sharedata.vrange[index][0] = axis.convert(sharedata.point[column + "min"])
316 if mask & self.mask_dmin:
317 sharedata.vrange[index][0] = axis.convert(sharedata.point[column] - sharedata.point["d" + column + "min"])
318 if mask & self.mask_d:
319 sharedata.vrange[index][0] = axis.convert(sharedata.point[column] - sharedata.point["d" + column])
320 except (ArithmeticError, ValueError, TypeError):
321 sharedata.vrange[index][0] = None
322 try:
323 if mask & self.mask_max:
324 sharedata.vrange[index][1] = axis.convert(sharedata.point[column + "max"])
325 if mask & self.mask_dmax:
326 sharedata.vrange[index][1] = axis.convert(sharedata.point[column] + sharedata.point["d" + column + "max"])
327 if mask & self.mask_d:
328 sharedata.vrange[index][1] = axis.convert(sharedata.point[column] + sharedata.point["d" + column])
329 except (ArithmeticError, ValueError, TypeError):
330 sharedata.vrange[index][1] = None
332 # some range checks for data consistency
333 if (sharedata.vrange[index][0] is not None and sharedata.vrange[index][1] is not None and
334 sharedata.vrange[index][0] > sharedata.vrange[index][1] + self.epsilon):
335 raise ValueError("inverse range")
336 #if (sharedata.vrange[index][0] is not None and sharedata.vpos[index] is not None and
337 # sharedata.vrange[index][0] > sharedata.vpos[index] + self.epsilon):
338 # raise ValueError("negative minimum errorbar")
339 #if (sharedata.vrange[index][1] is not None and sharedata.vpos[index] is not None and
340 # sharedata.vrange[index][1] < sharedata.vpos[index] - self.epsilon):
341 # raise ValueError("negative maximum errorbar")
344 registerdefaultprovider(_range(), _range.providesdata)
347 def _crosssymbol(c, x_pt, y_pt, size_pt, attrs):
348 c.draw(path.path(path.moveto_pt(x_pt-0.5*size_pt, y_pt-0.5*size_pt),
349 path.lineto_pt(x_pt+0.5*size_pt, y_pt+0.5*size_pt),
350 path.moveto_pt(x_pt-0.5*size_pt, y_pt+0.5*size_pt),
351 path.lineto_pt(x_pt+0.5*size_pt, y_pt-0.5*size_pt)), attrs)
353 def _plussymbol(c, x_pt, y_pt, size_pt, attrs):
354 c.draw(path.path(path.moveto_pt(x_pt-0.707106781*size_pt, y_pt),
355 path.lineto_pt(x_pt+0.707106781*size_pt, y_pt),
356 path.moveto_pt(x_pt, y_pt-0.707106781*size_pt),
357 path.lineto_pt(x_pt, y_pt+0.707106781*size_pt)), attrs)
359 def _squaresymbol(c, x_pt, y_pt, size_pt, attrs):
360 c.draw(path.path(path.moveto_pt(x_pt-0.5*size_pt, y_pt-0.5*size_pt),
361 path.lineto_pt(x_pt+0.5*size_pt, y_pt-0.5*size_pt),
362 path.lineto_pt(x_pt+0.5*size_pt, y_pt+0.5*size_pt),
363 path.lineto_pt(x_pt-0.5*size_pt, y_pt+0.5*size_pt),
364 path.closepath()), attrs)
366 def _trianglesymbol(c, x_pt, y_pt, size_pt, attrs):
367 c.draw(path.path(path.moveto_pt(x_pt-0.759835685*size_pt, y_pt-0.438691337*size_pt),
368 path.lineto_pt(x_pt+0.759835685*size_pt, y_pt-0.438691337*size_pt),
369 path.lineto_pt(x_pt, y_pt+0.877382675*size_pt),
370 path.closepath()), attrs)
372 def _circlesymbol(c, x_pt, y_pt, size_pt, attrs):
373 c.draw(path.path(path.arc_pt(x_pt, y_pt, 0.564189583*size_pt, 0, 360),
374 path.closepath()), attrs)
376 def _diamondsymbol(c, x_pt, y_pt, size_pt, attrs):
377 c.draw(path.path(path.moveto_pt(x_pt-0.537284965*size_pt, y_pt),
378 path.lineto_pt(x_pt, y_pt-0.930604859*size_pt),
379 path.lineto_pt(x_pt+0.537284965*size_pt, y_pt),
380 path.lineto_pt(x_pt, y_pt+0.930604859*size_pt),
381 path.closepath()), attrs)
384 class _styleneedingpointpos(_style):
386 needsdata = ["vposmissing"]
388 def columns(self, privatedata, sharedata, graph, columns):
389 if len(sharedata.vposmissing):
390 raise ValueError("position columns incomplete")
391 return []
394 class symbol(_styleneedingpointpos):
396 needsdata = ["vpos", "vposmissing", "vposvalid"]
398 # insert symbols
399 # note, that statements like cross = _crosssymbol are
400 # invalid, since the would lead to unbound methods, but
401 # a single entry changeable list does the trick
402 cross = attr.changelist([_crosssymbol])
403 plus = attr.changelist([_plussymbol])
404 square = attr.changelist([_squaresymbol])
405 triangle = attr.changelist([_trianglesymbol])
406 circle = attr.changelist([_circlesymbol])
407 diamond = attr.changelist([_diamondsymbol])
409 changecross = attr.changelist([_crosssymbol, _plussymbol, _squaresymbol, _trianglesymbol, _circlesymbol, _diamondsymbol])
410 changeplus = attr.changelist([_plussymbol, _squaresymbol, _trianglesymbol, _circlesymbol, _diamondsymbol, cross])
411 changesquare = attr.changelist([_squaresymbol, _trianglesymbol, _circlesymbol, _diamondsymbol, cross, _plussymbol])
412 changetriangle = attr.changelist([_trianglesymbol, _circlesymbol, _diamondsymbol, cross, _plussymbol, _squaresymbol])
413 changecircle = attr.changelist([_circlesymbol, _diamondsymbol, cross, _plussymbol, _squaresymbol, _trianglesymbol])
414 changediamond = attr.changelist([_diamondsymbol, cross, _plussymbol, _squaresymbol, _trianglesymbol, _circlesymbol])
415 changesquaretwice = attr.changelist([_squaresymbol, _squaresymbol, _trianglesymbol, _trianglesymbol, _circlesymbol, _circlesymbol, _diamondsymbol, _diamondsymbol])
416 changetriangletwice = attr.changelist([_trianglesymbol, _trianglesymbol, _circlesymbol, _circlesymbol, _diamondsymbol, _diamondsymbol, _squaresymbol, _squaresymbol])
417 changecircletwice = attr.changelist([_circlesymbol, _circlesymbol, _diamondsymbol, _diamondsymbol, _squaresymbol, _squaresymbol, _trianglesymbol, _trianglesymbol])
418 changediamondtwice = attr.changelist([_diamondsymbol, _diamondsymbol, _squaresymbol, _squaresymbol, _trianglesymbol, _trianglesymbol, _circlesymbol, _circlesymbol])
420 changestrokedfilled = attr.changelist([deco.stroked, deco.filled])
421 changefilledstroked = attr.changelist([deco.filled, deco.stroked])
423 defaultsymbolattrs = [deco.stroked]
425 def __init__(self, symbol=changecross, size=0.2*unit.v_cm, symbolattrs=[]):
426 self.symbol = symbol
427 self.size = size
428 self.symbolattrs = symbolattrs
430 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
431 privatedata.symbol = attr.selectattr(self.symbol, selectindex, selecttotal)
432 privatedata.size_pt = unit.topt(attr.selectattr(self.size, selectindex, selecttotal))
433 if self.symbolattrs is not None:
434 privatedata.symbolattrs = attr.selectattrs(self.defaultsymbolattrs + self.symbolattrs, selectindex, selecttotal)
435 else:
436 privatedata.symbolattrs = None
438 def initdrawpoints(self, privatedata, sharedata, graph):
439 privatedata.symbolcanvas = graph.insert(canvas.canvas())
441 def drawpoint(self, privatedata, sharedata, graph):
442 if sharedata.vposvalid and privatedata.symbolattrs is not None:
443 xpos, ypos = graph.vpos_pt(*sharedata.vpos)
444 privatedata.symbol(privatedata.symbolcanvas, xpos, ypos, privatedata.size_pt, privatedata.symbolattrs)
446 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt, dy_pt, selectindex, selecttotal):
447 if privatedata.symbolattrs is not None:
448 privatedata.symbol(graph, x_pt+0.5*width_pt, y_pt+0.5*height_pt, privatedata.size_pt, privatedata.symbolattrs)
449 return 1
452 class line(_styleneedingpointpos):
454 needsdata = ["vpos", "vposmissing", "vposavailable", "vposvalid"]
456 changelinestyle = attr.changelist([style.linestyle.solid,
457 style.linestyle.dashed,
458 style.linestyle.dotted,
459 style.linestyle.dashdotted])
461 defaultlineattrs = [changelinestyle]
463 def __init__(self, lineattrs=[]):
464 self.lineattrs = lineattrs
466 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
467 privatedata.lineattrs = attr.selectattrs(self.defaultlineattrs + self.lineattrs, selectindex, selecttotal)
469 def initdrawpoints(self, privatedata, sharedata, graph):
470 if privatedata.lineattrs is not None:
471 privatedata.linecanvas = graph.insert(canvas.canvas())
472 privatedata.linecanvas.set(privatedata.lineattrs)
473 privatedata.path = path.path()
474 privatedata.linebasepoints = []
475 privatedata.lastvpos = None
477 def addpointstopath(self, privatedata, sharedata):
478 # add baselinepoints to privatedata.path
479 if len(privatedata.linebasepoints) > 1:
480 privatedata.path.append(path.moveto_pt(*privatedata.linebasepoints[0]))
481 if len(privatedata.linebasepoints) > 2:
482 privatedata.path.append(path.multilineto_pt(privatedata.linebasepoints[1:]))
483 else:
484 privatedata.path.append(path.lineto_pt(*privatedata.linebasepoints[1]))
485 privatedata.linebasepoints = []
487 def drawpoint(self, privatedata, sharedata, graph):
488 # append linebasepoints
489 if sharedata.vposavailable:
490 if len(privatedata.linebasepoints):
491 # the last point was inside the graph
492 if sharedata.vposvalid: # shortcut for the common case
493 privatedata.linebasepoints.append(graph.vpos_pt(*sharedata.vpos))
494 else:
495 # cut end
496 cut = 1
497 for vstart, vend in zip(privatedata.lastvpos, sharedata.vpos):
498 newcut = None
499 if vend > 1:
500 # 1 = vstart + (vend - vstart) * cut
501 try:
502 newcut = (1 - vstart)/(vend - vstart)
503 except ArithmeticError:
504 break
505 if vend < 0:
506 # 0 = vstart + (vend - vstart) * cut
507 try:
508 newcut = - vstart/(vend - vstart)
509 except ArithmeticError:
510 break
511 if newcut is not None and newcut < cut:
512 cut = newcut
513 else:
514 cutvpos = []
515 for vstart, vend in zip(privatedata.lastvpos, sharedata.vpos):
516 cutvpos.append(vstart + (vend - vstart) * cut)
517 privatedata.linebasepoints.append(graph.vpos_pt(*cutvpos))
518 self.addpointstopath(privatedata, sharedata)
519 else:
520 # the last point was outside the graph
521 if privatedata.lastvpos is not None:
522 if sharedata.vposvalid:
523 # cut beginning
524 cut = 0
525 for vstart, vend in zip(privatedata.lastvpos, sharedata.vpos):
526 newcut = None
527 if vstart > 1:
528 # 1 = vstart + (vend - vstart) * cut
529 try:
530 newcut = (1 - vstart)/(vend - vstart)
531 except ArithmeticError:
532 break
533 if vstart < 0:
534 # 0 = vstart + (vend - vstart) * cut
535 try:
536 newcut = - vstart/(vend - vstart)
537 except ArithmeticError:
538 break
539 if newcut is not None and newcut > cut:
540 cut = newcut
541 else:
542 cutvpos = []
543 for vstart, vend in zip(privatedata.lastvpos, sharedata.vpos):
544 cutvpos.append(vstart + (vend - vstart) * cut)
545 privatedata.linebasepoints.append(graph.vpos_pt(*cutvpos))
546 privatedata.linebasepoints.append(graph.vpos_pt(*sharedata.vpos))
547 else:
548 # sometimes cut beginning and end
549 cutfrom = 0
550 cutto = 1
551 for vstart, vend in zip(privatedata.lastvpos, sharedata.vpos):
552 newcutfrom = None
553 if vstart > 1:
554 if vend > 1:
555 break
556 # 1 = vstart + (vend - vstart) * cutfrom
557 try:
558 newcutfrom = (1 - vstart)/(vend - vstart)
559 except ArithmeticError:
560 break
561 if vstart < 0:
562 if vend < 0:
563 break
564 # 0 = vstart + (vend - vstart) * cutfrom
565 try:
566 newcutfrom = - vstart/(vend - vstart)
567 except ArithmeticError:
568 break
569 if newcutfrom is not None and newcutfrom > cutfrom:
570 cutfrom = newcutfrom
571 newcutto = None
572 if vend > 1:
573 # 1 = vstart + (vend - vstart) * cutto
574 try:
575 newcutto = (1 - vstart)/(vend - vstart)
576 except ArithmeticError:
577 break
578 if vend < 0:
579 # 0 = vstart + (vend - vstart) * cutto
580 try:
581 newcutto = - vstart/(vend - vstart)
582 except ArithmeticError:
583 break
584 if newcutto is not None and newcutto < cutto:
585 cutto = newcutto
586 else:
587 if cutfrom < cutto:
588 cutfromvpos = []
589 cuttovpos = []
590 for vstart, vend in zip(privatedata.lastvpos, sharedata.vpos):
591 cutfromvpos.append(vstart + (vend - vstart) * cutfrom)
592 cuttovpos.append(vstart + (vend - vstart) * cutto)
593 privatedata.linebasepoints.append(graph.vpos_pt(*cutfromvpos))
594 privatedata.linebasepoints.append(graph.vpos_pt(*cuttovpos))
595 self.addpointstopath(privatedata, sharedata)
596 privatedata.lastvpos = sharedata.vpos[:]
597 else:
598 if len(privatedata.linebasepoints) > 1:
599 self.addpointstopath(privatedata, sharedata)
600 privatedata.lastvpos = None
602 def donedrawpoints(self, privatedata, sharedata, graph):
603 if len(privatedata.linebasepoints) > 1:
604 self.addpointstopath(privatedata, sharedata)
605 if privatedata.lineattrs is not None and len(privatedata.path.path):
606 privatedata.linecanvas.stroke(privatedata.path)
608 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt, dy_pt, selectindex, selecttotal):
609 if privatedata.lineattrs is not None:
610 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)
611 return 1
614 class errorbar(_style):
616 needsdata = ["vpos", "vposmissing", "vposavailable", "vposvalid", "vrange", "vrangemissing"]
618 defaulterrorbarattrs = []
620 def __init__(self, size=0.1*unit.v_cm,
621 errorbarattrs=[],
622 epsilon=1e-10):
623 self.size = size
624 self.errorbarattrs = errorbarattrs
625 self.epsilon = epsilon
627 def columns(self, privatedata, sharedata, graph, columns):
628 for i in sharedata.vposmissing:
629 if i in sharedata.vrangemissing:
630 raise ValueError("position and range for a graph dimension missing")
631 return []
633 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
634 privatedata.errorsize_pt = unit.topt(attr.selectattr(self.size, selectindex, selecttotal))
635 privatedata.errorbarattrs = attr.selectattrs(self.defaulterrorbarattrs + self.errorbarattrs, selectindex, selecttotal)
637 def initdrawpoints(self, privatedata, sharedata, graph):
638 if privatedata.errorbarattrs is not None:
639 privatedata.errorbarcanvas = graph.insert(canvas.canvas())
640 privatedata.errorbarcanvas.set(privatedata.errorbarattrs)
641 privatedata.dimensionlist = range(len(sharedata.vpos))
643 def drawpoint(self, privatedata, sharedata, graph):
644 if privatedata.errorbarattrs is not None:
645 for i in privatedata.dimensionlist:
646 for j in privatedata.dimensionlist:
647 if (i != j and
648 (sharedata.vpos[j] is None or
649 sharedata.vpos[j] < -self.epsilon or
650 sharedata.vpos[j] > 1+self.epsilon)):
651 break
652 else:
653 if ((sharedata.vrange[i][0] is None and sharedata.vpos[i] is None) or
654 (sharedata.vrange[i][1] is None and sharedata.vpos[i] is None) or
655 (sharedata.vrange[i][0] is None and sharedata.vrange[i][1] is None)):
656 continue
657 vminpos = sharedata.vpos[:]
658 if sharedata.vrange[i][0] is not None:
659 vminpos[i] = sharedata.vrange[i][0]
660 mincap = 1
661 else:
662 mincap = 0
663 if vminpos[i] > 1+self.epsilon:
664 continue
665 if vminpos[i] < -self.epsilon:
666 vminpos[i] = 0
667 mincap = 0
668 vmaxpos = sharedata.vpos[:]
669 if sharedata.vrange[i][1] is not None:
670 vmaxpos[i] = sharedata.vrange[i][1]
671 maxcap = 1
672 else:
673 maxcap = 0
674 if vmaxpos[i] < -self.epsilon:
675 continue
676 if vmaxpos[i] > 1+self.epsilon:
677 vmaxpos[i] = 1
678 maxcap = 0
679 privatedata.errorbarcanvas.stroke(graph.vgeodesic(*(vminpos + vmaxpos)))
680 for j in privatedata.dimensionlist:
681 if i != j:
682 if mincap:
683 privatedata.errorbarcanvas.stroke(graph.vcap_pt(j, privatedata.errorsize_pt, *vminpos))
684 if maxcap:
685 privatedata.errorbarcanvas.stroke(graph.vcap_pt(j, privatedata.errorsize_pt, *vmaxpos))
688 class text(_styleneedingpointpos):
690 needsdata = ["vpos", "vposmissing", "vposvalid"]
692 defaulttextattrs = [textmodule.halign.center, textmodule.vshift.mathaxis]
694 def __init__(self, textdx=0*unit.v_cm, textdy=0.3*unit.v_cm, textattrs=[], **kwargs):
695 self.textdx = textdx
696 self.textdy = textdy
697 self.textattrs = textattrs
699 def columns(self, privatedata, sharedata, graph, columns):
700 if "text" not in columns:
701 raise ValueError("text missing")
702 return ["text"] + _styleneedingpointpos.columns(self, privatedata, sharedata, graph, columns)
704 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
705 if self.textattrs is not None:
706 privatedata.textattrs = attr.selectattrs(self.defaulttextattrs + self.textattrs, selectindex, selecttotal)
707 else:
708 privatedata.textattrs = None
710 def initdrawpoints(self, privatedata, sharedata, grap):
711 privatedata.textdx_pt = unit.topt(self.textdx)
712 privatedata.textdy_pt = unit.topt(self.textdy)
714 def drawpoint(self, privatedata, sharedata, graph):
715 if privatedata.textattrs is not None and sharedata.vposvalid:
716 x_pt, y_pt = graph.vpos_pt(*sharedata.vpos)
717 try:
718 text = str(sharedata.point["text"])
719 except:
720 pass
721 else:
722 graph.text_pt(x_pt + privatedata.textdx_pt, y_pt + privatedata.textdy_pt, text, privatedata.textattrs)
725 class arrow(_styleneedingpointpos):
727 needsdata = ["vpos", "vposmissing", "vposvalid"]
729 defaultlineattrs = []
730 defaultarrowattrs = []
732 def __init__(self, linelength=0.25*unit.v_cm, arrowsize=0.15*unit.v_cm, lineattrs=[], arrowattrs=[], epsilon=1e-10):
733 self.linelength = linelength
734 self.arrowsize = arrowsize
735 self.lineattrs = lineattrs
736 self.arrowattrs = arrowattrs
737 self.epsilon = epsilon
739 def columns(self, privatedata, sharedata, graph, columns):
740 if len(graph.axesnames) != 2:
741 raise ValueError("arrow style restricted on two-dimensional graphs")
742 if "size" not in columns:
743 raise ValueError("size missing")
744 if "angle" not in columns:
745 raise ValueError("angle missing")
746 return ["size", "angle"] + _styleneedingpointpos.columns(self, privatedata, sharedata, graph, columns)
748 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
749 if self.lineattrs is not None:
750 privatedata.lineattrs = attr.selectattrs(self.defaultlineattrs + self.lineattrs, selectindex, selecttotal)
751 else:
752 privatedata.lineattrs = None
753 if self.arrowattrs is not None:
754 privatedata.arrowattrs = attr.selectattrs(self.defaultarrowattrs + self.arrowattrs, selectindex, selecttotal)
755 else:
756 privatedata.arrowattrs = None
758 def initdrawpoints(self, privatedata, sharedata, graph):
759 privatedata.arrowcanvas = graph.insert(canvas.canvas())
761 def drawpoint(self, privatedata, sharedata, graph):
762 if privatedata.lineattrs is not None and privatedata.arrowattrs is not None and sharedata.vposvalid:
763 linelength_pt = unit.topt(self.linelength)
764 x_pt, y_pt = graph.vpos_pt(*sharedata.vpos)
765 try:
766 angle = sharedata.point["angle"] + 0.0
767 size = sharedata.point["size"] + 0.0
768 except:
769 pass
770 else:
771 if sharedata.point["size"] > self.epsilon:
772 dx = math.cos(angle*math.pi/180)
773 dy = math.sin(angle*math.pi/180)
774 x1 = x_pt-0.5*dx*linelength_pt*size
775 y1 = y_pt-0.5*dy*linelength_pt*size
776 x2 = x_pt+0.5*dx*linelength_pt*size
777 y2 = y_pt+0.5*dy*linelength_pt*size
778 privatedata.arrowcanvas.stroke(path.line_pt(x1, y1, x2, y2), privatedata.lineattrs +
779 [deco.earrow(privatedata.arrowattrs, size=self.arrowsize*size)])
781 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt, dy_pt, selectindex, selecttotal):
782 raise "TODO"
785 class rect(_style):
787 needsdata = ["vrange", "vrangeminmissing", "vrangemaxmissing"]
789 def __init__(self, palette=color.palette.Gray):
790 self.palette = palette
792 def columns(self, privatedata, sharedata, graph, columns):
793 if len(graph.axesnames) != 2:
794 raise TypeError("arrow style restricted on two-dimensional graphs")
795 if "color" not in columns:
796 raise ValueError("color missing")
797 if len(sharedata.vrangeminmissing) + len(sharedata.vrangemaxmissing):
798 raise ValueError("range columns incomplete")
799 return ["color"]
801 def initdrawpoints(self, privatedata, sharedata, graph):
802 privatedata.rectcanvas = graph.insert(canvas.canvas())
803 privatedata.lastcolorvalue = None
805 def drawpoint(self, privatedata, sharedata, graph):
806 xvmin = sharedata.vrange[0][0]
807 xvmax = sharedata.vrange[0][1]
808 yvmin = sharedata.vrange[1][0]
809 yvmax = sharedata.vrange[1][1]
810 if (xvmin is not None and xvmin < 1 and
811 xvmax is not None and xvmax > 0 and
812 yvmin is not None and yvmin < 1 and
813 yvmax is not None and yvmax > 0):
814 if xvmin < 0:
815 xvmin = 0
816 elif xvmax > 1:
817 xvmax = 1
818 if yvmin < 0:
819 yvmin = 0
820 elif yvmax > 1:
821 yvmax = 1
822 p = graph.vgeodesic(xvmin, yvmin, xvmax, yvmin)
823 p.append(graph.vgeodesic_el(xvmax, yvmin, xvmax, yvmax))
824 p.append(graph.vgeodesic_el(xvmax, yvmax, xvmin, yvmax))
825 p.append(graph.vgeodesic_el(xvmin, yvmax, xvmin, yvmin))
826 p.append(path.closepath())
827 colorvalue = sharedata.point["color"]
828 try:
829 if colorvalue != privatedata.lastcolorvalue:
830 privatedata.rectcanvas.set([self.palette.getcolor(colorvalue)])
831 except:
832 pass
833 else:
834 privatedata.rectcanvas.fill(p)
836 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt, dy_pt, selectindex, selecttotal):
837 raise "TODO"
840 class barpos(_style):
842 providesdata = ["vpos", "vposmissing", "vposavailable", "vposvalid", "barcolumns", "barvalueindex", "vbarpos"]
844 def __init__(self, fromvalue=None, subindex=0, subnames=None, epsilon=1e-10):
845 # TODO: vpos configuration ...
846 self.fromvalue = fromvalue
847 self.subnames = subnames
848 self.subindex = subindex
849 self.epsilon = epsilon
851 def columns(self, privatedata, sharedata, graph, columns):
852 # TODO: we might check whether barcolumns/barvalueindex is already available
853 sharedata.barcolumns = []
854 sharedata.barvalueindex = None
855 for dimension, axisnames in enumerate(graph.axesnames):
856 for axisname in axisnames:
857 if axisname in columns:
858 if sharedata.barvalueindex is not None:
859 raise ValueError("multiple values")
860 valuecolumns = [axisname]
861 while 1:
862 stackedvalue = "%sstack%i" % (axisname, len(valuecolumns))
863 if stackedvalue in columns:
864 valuecolumns.append(stackedvalue)
865 else:
866 break
867 sharedata.barcolumns.append(valuecolumns)
868 sharedata.barvalueindex = dimension
869 break
870 else:
871 found = 0
872 for axisname in axisnames:
873 if (axisname + "name") in columns:
874 if found > 1:
875 raise ValueError("multiple names")
876 found = 1
877 sharedata.barcolumns.append(axisname + "name")
878 if not found:
879 raise ValueError("value/name missing")
880 if sharedata.barvalueindex is None:
881 raise ValueError("missing value")
882 if self.subindex >= sharedata.barvalueindex:
883 privatedata.barpossubindex = self.subindex + 1
884 else:
885 privatedata.barpossubindex = self.subindex
886 sharedata.vposmissing = []
887 return sharedata.barcolumns[sharedata.barvalueindex] + [sharedata.barcolumns[i] for i in range(len(sharedata.barcolumns)) if i != sharedata.barvalueindex]
889 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
890 if selecttotal == 1:
891 if self.subnames is not None:
892 raise ValueError("subnames set for single-bar data")
893 privatedata.barpossubname = []
894 else:
895 if self.subnames is not None:
896 privatedata.barpossubname = [self.subnames[selectindex]]
897 else:
898 privatedata.barpossubname = [selectindex]
900 def adjustaxis(self, privatedata, sharedata, graph, column, data, index):
901 if column in sharedata.barcolumns[sharedata.barvalueindex]:
902 graph.axes[sharedata.barcolumns[sharedata.barvalueindex][0]].adjustrange(data, index)
903 if self.fromvalue is not None and column == sharedata.barcolumns[sharedata.barvalueindex][0]:
904 graph.axes[sharedata.barcolumns[sharedata.barvalueindex][0]].adjustrange([self.fromvalue], None)
905 else:
906 try:
907 i = sharedata.barcolumns.index(column)
908 except ValueError:
909 pass
910 else:
911 if i == privatedata.barpossubindex:
912 graph.axes[column[:-4]].adjustrange(data, index, privatedata.barpossubname)
913 else:
914 graph.axes[column[:-4]].adjustrange(data, index)
916 def initdrawpoints(self, privatedata, sharedata, graph):
917 sharedata.vpos = [None]*(len(sharedata.barcolumns))
918 sharedata.vbarpos = [[None for i in range(2)] for x in sharedata.barcolumns]
920 if self.fromvalue is not None:
921 vfromvalue = graph.axes[sharedata.barcolumns[sharedata.barvalueindex][0]].convert(self.fromvalue)
922 if vfromvalue < 0:
923 vfromvalue = 0
924 if vfromvalue > 1:
925 vfromvalue = 1
926 else:
927 vfromvalue = 0
929 sharedata.vbarpos[sharedata.barvalueindex] = [vfromvalue] + [None]*len(sharedata.barcolumns[sharedata.barvalueindex])
931 def drawpoint(self, privatedata, sharedata, graph):
932 sharedata.vposavailable = sharedata.vposvalid = 1
933 for i, barname in enumerate(sharedata.barcolumns):
934 if i == sharedata.barvalueindex:
935 for j, valuename in enumerate(sharedata.barcolumns[sharedata.barvalueindex]):
936 try:
937 sharedata.vbarpos[i][j+1] = graph.axes[sharedata.barcolumns[i][0]].convert(sharedata.point[valuename])
938 except (ArithmeticError, ValueError, TypeError):
939 sharedata.vbarpos[i][j+1] = None
940 sharedata.vpos[i] = sharedata.vbarpos[i][-1]
941 else:
942 for j in range(2):
943 try:
944 if i == privatedata.barpossubindex:
945 sharedata.vbarpos[i][j] = graph.axes[barname[:-4]].convert(([sharedata.point[barname]] + privatedata.barpossubname + [j]))
946 else:
947 sharedata.vbarpos[i][j] = graph.axes[barname[:-4]].convert((sharedata.point[barname], j))
948 except (ArithmeticError, ValueError, TypeError):
949 sharedata.vbarpos[i][j] = None
950 try:
951 sharedata.vpos[i] = 0.5*(sharedata.vbarpos[i][0]+sharedata.vbarpos[i][1])
952 except (ArithmeticError, ValueError, TypeError):
953 sharedata.vpos[i] = None
954 if sharedata.vpos[i] is None:
955 sharedata.vposavailable = sharedata.vposvalid = 0
956 elif sharedata.vpos[i] < -self.epsilon or sharedata.vpos[i] > 1+self.epsilon:
957 sharedata.vposvalid = 0
959 registerdefaultprovider(barpos(), ["barcolumns", "barvalueindex", "vbarpos"])
962 class bar(_style):
964 needsdata = ["barvalueindex", "vbarpos"]
966 defaultfrompathattrs = []
967 defaultbarattrs = [color.palette.Rainbow, deco.stroked([color.gray.black])]
969 def __init__(self, frompathattrs=[], barattrs=[], subnames=None, multikey=0, epsilon=1e-10):
970 self.frompathattrs = frompathattrs
971 self.barattrs = barattrs
972 self.subnames = subnames
973 self.multikey = multikey
974 self.epsilon = epsilon
976 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
977 if selectindex:
978 privatedata.frompathattrs = None
979 else:
980 privatedata.frompathattrs = self.defaultfrompathattrs + self.frompathattrs
981 if selecttotal > 1:
982 if self.barattrs is not None:
983 privatedata.barattrs = attr.selectattrs(self.defaultbarattrs + self.barattrs, selectindex, selecttotal)
984 else:
985 privatedata.barattrs = None
986 else:
987 privatedata.barattrs = self.defaultbarattrs + self.barattrs
988 privatedata.barselectindex = selectindex
989 privatedata.barselecttotal = selecttotal
990 if privatedata.barselecttotal != 1 and self.subnames is not None:
991 raise ValueError("subnames not allowed when iterating over bars")
993 def initdrawpoints(self, privatedata, sharedata, graph):
994 privatedata.bartmpvpos = [None]*4
995 l = len(sharedata.vbarpos[sharedata.barvalueindex])
996 if l > 1:
997 privatedata.bartmplist = []
998 for i in xrange(1, l):
999 barattrs = attr.selectattrs(privatedata.barattrs, i-1, l)
1000 if barattrs is not None:
1001 privatedata.bartmplist.append((i, barattrs))
1002 else:
1003 privatedata.bartmplist = [(1, privatedata.barattrs)]
1004 if privatedata.frompathattrs is not None:
1005 vfromvalue = sharedata.vbarpos[sharedata.barvalueindex][0]
1006 if vfromvalue > self.epsilon and vfromvalue < 1 - self.epsilon:
1007 if sharedata.barvalueindex:
1008 p = graph.vgeodesic(0, vfromvalue, 1, vfromvalue)
1009 else:
1010 p = graph.vgeodesic(vfromvalue, 0, vfromvalue, 1)
1011 graph.stroke(p, privatedata.frompathattrs)
1012 privatedata.barcanvas = graph.insert(canvas.canvas())
1014 def drawpoint(self, privatedata, sharedata, graph):
1015 if privatedata.barattrs is not None:
1016 for i, barattrs in privatedata.bartmplist:
1017 if None not in sharedata.vbarpos[1-sharedata.barvalueindex]+sharedata.vbarpos[sharedata.barvalueindex][i-1:i+1]:
1018 privatedata.bartmpvpos[1-sharedata.barvalueindex] = sharedata.vbarpos[1-sharedata.barvalueindex][0]
1019 privatedata.bartmpvpos[ sharedata.barvalueindex] = sharedata.vbarpos[sharedata.barvalueindex][i-1]
1020 privatedata.bartmpvpos[3-sharedata.barvalueindex] = sharedata.vbarpos[1-sharedata.barvalueindex][0]
1021 privatedata.bartmpvpos[2+sharedata.barvalueindex] = sharedata.vbarpos[sharedata.barvalueindex][i]
1022 p = graph.vgeodesic(*privatedata.bartmpvpos)
1023 privatedata.bartmpvpos[1-sharedata.barvalueindex] = sharedata.vbarpos[1-sharedata.barvalueindex][0]
1024 privatedata.bartmpvpos[ sharedata.barvalueindex] = sharedata.vbarpos[sharedata.barvalueindex][i]
1025 privatedata.bartmpvpos[3-sharedata.barvalueindex] = sharedata.vbarpos[1-sharedata.barvalueindex][1]
1026 privatedata.bartmpvpos[2+sharedata.barvalueindex] = sharedata.vbarpos[sharedata.barvalueindex][i]
1027 p.append(graph.vgeodesic_el(*privatedata.bartmpvpos))
1028 privatedata.bartmpvpos[1-sharedata.barvalueindex] = sharedata.vbarpos[1-sharedata.barvalueindex][1]
1029 privatedata.bartmpvpos[ sharedata.barvalueindex] = sharedata.vbarpos[sharedata.barvalueindex][i]
1030 privatedata.bartmpvpos[3-sharedata.barvalueindex] = sharedata.vbarpos[1-sharedata.barvalueindex][1]
1031 privatedata.bartmpvpos[2+sharedata.barvalueindex] = sharedata.vbarpos[sharedata.barvalueindex][i-1]
1032 p.append(graph.vgeodesic_el(*privatedata.bartmpvpos))
1033 privatedata.bartmpvpos[1-sharedata.barvalueindex] = sharedata.vbarpos[1-sharedata.barvalueindex][1]
1034 privatedata.bartmpvpos[ sharedata.barvalueindex] = sharedata.vbarpos[sharedata.barvalueindex][i-1]
1035 privatedata.bartmpvpos[3-sharedata.barvalueindex] = sharedata.vbarpos[1-sharedata.barvalueindex][0]
1036 privatedata.bartmpvpos[2+sharedata.barvalueindex] = sharedata.vbarpos[sharedata.barvalueindex][i-1]
1037 p.append(graph.vgeodesic_el(*privatedata.bartmpvpos))
1038 p.append(path.closepath())
1039 privatedata.barcanvas.fill(p, barattrs)
1041 def key_pt(self, privatedata, sharedata, c, x_pt, y_pt, width_pt, height_pt, dy_pt, selectindex, selecttotal):
1042 if self.multikey:
1043 l = 0
1044 for i, barattrs in privatedata.bartmplist:
1045 c.fill(path.rect_pt(x_pt, y_pt-l*dy_pt, width_pt, height_pt), barattrs)
1046 l += 1
1047 return l
1048 else:
1049 for i, barattrs in privatedata.bartmplist:
1050 c.fill(path.rect_pt(x_pt+(i-1)*width_pt/privatedata.bartmplist[-1][0], y_pt,
1051 width_pt/privatedata.bartmplist[-1][0], height_pt), barattrs)
1052 return 1
1055 class barpos_new(_style):
1057 providesdata = ["vpos", "vposmissing", "vposavailable", "vposvalid", "vbarrange", "stacked"]
1059 # defaultfrompathattrs = []
1061 def __init__(self, fromvalue=None, stackname=None, subindex=0, subnames=None, epsilon=1e-10):
1062 # TODO vpos configuration ...
1063 # NOTE subindex is a perspective for higher dimensional plots
1064 # (just ignore it for the moment -- we don't even need to document about it)
1065 if fromvalue is not None and stackname is not None:
1066 raise ValueError("you can either start at a fromvalue or stack bars")
1067 self.fromvalue = fromvalue
1068 self.stackname = stackname
1069 self.subindex = subindex
1070 self.subnames = subnames
1071 self.epsilon = epsilon
1073 def columns(self, privatedata, sharedata, graph, columns):
1074 privatedata.barcolumns = []
1075 privatedata.barvalueindex = None
1076 for dimension, axisnames in enumerate(graph.axesnames):
1077 found = 0
1078 for axisname in axisnames:
1079 if axisname in columns:
1080 if privatedata.barvalueindex is not None:
1081 raise ValueError("multiple values")
1082 privatedata.barvalueindex = dimension
1083 privatedata.barcolumns.append(axisname)
1084 found += 1
1085 if (axisname + "name") in columns:
1086 privatedata.barcolumns.append(axisname + "name")
1087 found += 1
1088 if found > 1:
1089 raise ValueError("multiple names")
1090 if not found:
1091 raise ValueError("value/name missing")
1092 if privatedata.barvalueindex is None:
1093 raise ValueError("missing value")
1094 if self.stackname is not None and self.stackname not in columns:
1095 raise ValueError("stackname column missing")
1096 if self.subindex >= privatedata.barvalueindex:
1097 privatedata.barpossubindex = self.subindex + 1
1098 else:
1099 privatedata.barpossubindex = self.subindex
1100 sharedata.vposmissing = []
1101 if self.stackname is not None:
1102 return privatedata.barcolumns + [self.stackname]
1103 else:
1104 return privatedata.barcolumns
1106 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1107 if selecttotal == 1:
1108 if self.subnames is not None:
1109 raise ValueError("subnames set for single-bar data")
1110 privatedata.barpossubname = []
1111 else:
1112 if self.subnames is not None:
1113 privatedata.barpossubname = [self.subnames[selectindex]]
1114 else:
1115 privatedata.barpossubname = [selectindex]
1117 def adjustaxis(self, privatedata, sharedata, graph, column, data, index):
1118 try:
1119 if column == self.stackname:
1120 i = privatedata.barvalueindex
1121 else:
1122 i = privatedata.barcolumns.index(column)
1123 except ValueError:
1124 pass
1125 else:
1126 if i == privatedata.barvalueindex:
1127 if self.fromvalue is not None:
1128 graph.axes[privatedata.barcolumns[i]].adjustrange([self.fromvalue], None)
1129 if self.stackname is None or column == self.stackname:
1130 graph.axes[privatedata.barcolumns[i]].adjustrange(data, index)
1131 else:
1132 if i == privatedata.barpossubindex:
1133 graph.axes[privatedata.barcolumns[i][:-4]].adjustrange(data, index, privatedata.barpossubname)
1134 else:
1135 graph.axes[privatedata.barcolumns[i][:-4]].adjustrange(data, index)
1137 def initdrawpoints(self, privatedata, sharedata, graph):
1138 sharedata.vpos = [None]*(len(privatedata.barcolumns))
1139 sharedata.vbarrange = [[None for i in range(2)] for x in privatedata.barcolumns]
1141 if self.fromvalue is not None:
1142 privatedata.vfromvalue = graph.axes[privatedata.barcolumns[privatedata.barvalueindex][0]].convert(self.fromvalue)
1143 if privatedata.vfromvalue < 0:
1144 privatedata.vfromvalue = 0
1145 if privatedata.vfromvalue > 1:
1146 privatedata.vfromvalue = 1
1147 else:
1148 privatedata.vfromvalue = 0
1150 def drawpoint(self, privatedata, sharedata, graph):
1151 sharedata.vposavailable = sharedata.vposvalid = 1
1152 for i, barname in enumerate(privatedata.barcolumns):
1153 if i == privatedata.barvalueindex:
1154 try:
1155 if self.stackname is None:
1156 sharedata.vbarrange[i][0] = privatedata.vfromvalue
1157 sharedata.vbarrange[i][1] = graph.axes[barname].convert(sharedata.point[barname])
1158 else:
1159 sharedata.vbarrange[i][0] = sharedata.vbarrange[i][1]
1160 sharedata.vbarrange[i][1] = graph.axes[barname].convert(sharedata.point[self.stackname])
1161 except (ArithmeticError, ValueError, TypeError):
1162 sharedata.vbarrange[i][1] = None
1163 else:
1164 sharedata.vpos[i] = sharedata.vbarrange[i][1]
1165 else:
1166 for j in range(2):
1167 try:
1168 if i == privatedata.barpossubindex:
1169 sharedata.vbarrange[i][j] = graph.axes[barname[:-4]].convert(([sharedata.point[barname]] + privatedata.barpossubname + [j]))
1170 else:
1171 sharedata.vbarrange[i][j] = graph.axes[barname[:-4]].convert((sharedata.point[barname], j))
1172 except (ArithmeticError, ValueError, TypeError):
1173 sharedata.vbarrange[i][j] = None
1174 try:
1175 sharedata.vpos[i] = 0.5*(sharedata.vbarrange[i][0]+sharedata.vbarrange[i][1])
1176 except (ArithmeticError, ValueError, TypeError):
1177 sharedata.vpos[i] = None
1178 if sharedata.vpos[i] is None:
1179 sharedata.vposavailable = sharedata.vposvalid = 0
1180 elif sharedata.vpos[i] < -self.epsilon or sharedata.vpos[i] > 1+self.epsilon:
1181 sharedata.vposvalid = 0
1183 registerdefaultprovider(barpos_new(), ["vbarrange", "stacked"])
1186 class bar_new(_style):
1188 needsdata = ["vbarrange"]
1190 defaultbarattrs = [color.palette.Rainbow, deco.stroked([color.gray.black])]
1192 def __init__(self, barattrs=[]):
1193 self.barattrs = barattrs
1195 def columns(self, privatedata, sharedata, graph, columns):
1196 if len(graph.axesnames) != 2:
1197 raise TypeError("bar style restricted on two-dimensional graphs")
1198 return []
1200 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1201 if selecttotal > 1:
1202 privatedata.barattrs = attr.selectattrs(self.defaultbarattrs + self.barattrs, selectindex, selecttotal)
1203 else:
1204 privatedata.barattrs = self.defaultbarattrs + self.barattrs
1206 def initdrawpoints(self, privatedata, sharedata, graph):
1207 privatedata.rectcanvas = graph.insert(canvas.canvas())
1209 def drawpoint(self, privatedata, sharedata, graph):
1210 xvmin = sharedata.vbarrange[0][0]
1211 xvmax = sharedata.vbarrange[0][1]
1212 yvmin = sharedata.vbarrange[1][0]
1213 yvmax = sharedata.vbarrange[1][1]
1214 if None not in [xvmin, xvmax, yvmin, yvmax]:
1215 # TODO range check
1216 p = graph.vgeodesic(xvmin, yvmin, xvmax, yvmin)
1217 p.append(graph.vgeodesic_el(xvmax, yvmin, xvmax, yvmax))
1218 p.append(graph.vgeodesic_el(xvmax, yvmax, xvmin, yvmax))
1219 p.append(graph.vgeodesic_el(xvmin, yvmax, xvmin, yvmin))
1220 p.append(path.closepath())
1221 privatedata.rectcanvas.fill(p, privatedata.barattrs)
1223 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt, dy_pt, selectindex, selecttotal):
1224 #raise "TODO"
1225 pass