stackedbarpos separated from barpos
[PyX/mjg.git] / pyx / graph / style.py
blob671f6426dc3982c6197332e6f0538dbbe6dec9d9
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", "vbarrange", "barcolumns", "barvalueindex", "stackedbar"]
844 defaultfrompathattrs = []
846 def __init__(self, fromvalue=None, frompathattrs=[], subindex=0, subnames=None, epsilon=1e-10):
847 # NOTE subindex is a perspective for higher dimensional plots
848 # (just ignore it for the moment -- we don't even need to document about it)
849 self.fromvalue = fromvalue
850 self.frompathattrs = frompathattrs
851 self.subindex = subindex
852 self.subnames = subnames
853 self.epsilon = epsilon
855 def columns(self, privatedata, sharedata, graph, columns):
856 sharedata.barcolumns = []
857 sharedata.barvalueindex = None
858 for dimension, axisnames in enumerate(graph.axesnames):
859 found = 0
860 for axisname in axisnames:
861 if axisname in columns:
862 if sharedata.barvalueindex is not None:
863 raise ValueError("multiple values")
864 sharedata.barvalueindex = dimension
865 sharedata.barcolumns.append(axisname)
866 found += 1
867 if (axisname + "name") in columns:
868 sharedata.barcolumns.append(axisname + "name")
869 found += 1
870 if found > 1:
871 raise ValueError("multiple names")
872 if not found:
873 raise ValueError("value/name missing")
874 if sharedata.barvalueindex is None:
875 raise ValueError("missing value")
876 if self.subindex >= sharedata.barvalueindex:
877 privatedata.barpossubindex = self.subindex + 1
878 else:
879 privatedata.barpossubindex = self.subindex
880 sharedata.vposmissing = []
881 return sharedata.barcolumns
883 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
884 if selecttotal == 1:
885 if self.subnames is not None:
886 raise ValueError("subnames set for single-bar data")
887 privatedata.barpossubname = []
888 else:
889 if self.subnames is not None:
890 privatedata.barpossubname = [self.subnames[selectindex]]
891 else:
892 privatedata.barpossubname = [selectindex]
894 def adjustaxis(self, privatedata, sharedata, graph, column, data, index):
895 try:
896 i = sharedata.barcolumns.index(column)
897 except ValueError:
898 pass
899 else:
900 if i == sharedata.barvalueindex:
901 if self.fromvalue is not None:
902 graph.axes[sharedata.barcolumns[i]].adjustrange([self.fromvalue], None)
903 graph.axes[sharedata.barcolumns[i]].adjustrange(data, index)
904 else:
905 if i == privatedata.barpossubindex:
906 graph.axes[sharedata.barcolumns[i][:-4]].adjustrange(data, index, privatedata.barpossubname)
907 else:
908 graph.axes[sharedata.barcolumns[i][:-4]].adjustrange(data, index)
910 def initdrawpoints(self, privatedata, sharedata, graph):
911 sharedata.vpos = [None]*(len(sharedata.barcolumns))
912 sharedata.vbarrange = [[None for i in range(2)] for x in sharedata.barcolumns]
913 sharedata.stackedbar = 0
915 if self.fromvalue is not None:
916 privatedata.vfromvalue = graph.axes[sharedata.barcolumns[sharedata.barvalueindex][0]].convert(self.fromvalue)
917 if privatedata.vfromvalue < 0:
918 privatedata.vfromvalue = 0
919 if privatedata.vfromvalue > 1:
920 privatedata.vfromvalue = 1
921 if self.frompathattrs is not None:
922 # TODO 2d only
923 graph.stroke(graph.axespos[sharedata.barcolumns[sharedata.barvalueindex][0]].vgridpath(privatedata.vfromvalue),
924 self.defaultfrompathattrs + self.frompathattrs)
925 else:
926 privatedata.vfromvalue = 0
928 def drawpoint(self, privatedata, sharedata, graph):
929 sharedata.vposavailable = sharedata.vposvalid = 1
930 for i, barname in enumerate(sharedata.barcolumns):
931 if i == sharedata.barvalueindex:
932 sharedata.vbarrange[i][0] = privatedata.vfromvalue
933 try:
934 sharedata.vpos[i] = sharedata.vbarrange[i][1] = graph.axes[barname].convert(sharedata.point[barname])
935 except (ArithmeticError, ValueError, TypeError):
936 sharedata.vpos[i] = sharedata.vbarrange[i][1] = None
937 else:
938 for j in range(2):
939 try:
940 if i == privatedata.barpossubindex:
941 sharedata.vbarrange[i][j] = graph.axes[barname[:-4]].convert(([sharedata.point[barname]] + privatedata.barpossubname + [j]))
942 else:
943 sharedata.vbarrange[i][j] = graph.axes[barname[:-4]].convert((sharedata.point[barname], j))
944 except (ArithmeticError, ValueError, TypeError):
945 sharedata.vbarrange[i][j] = None
946 try:
947 sharedata.vpos[i] = 0.5*(sharedata.vbarrange[i][0]+sharedata.vbarrange[i][1])
948 except (ArithmeticError, ValueError, TypeError):
949 sharedata.vpos[i] = None
950 if sharedata.vpos[i] is None:
951 sharedata.vposavailable = sharedata.vposvalid = 0
952 elif sharedata.vpos[i] < -self.epsilon or sharedata.vpos[i] > 1+self.epsilon:
953 sharedata.vposvalid = 0
955 registerdefaultprovider(barpos(), ["vbarrange", "barcolumns", "barvalueindex"])
958 class stackedbarpos(_style):
960 # provides no additional data, but needs some data (and modifies some of them)
961 needsdata = ["vbarrange", "barcolumns", "barvalueindex"]
963 def __init__(self, stackname, epsilon=1e-10):
964 self.stackname = stackname
965 self.epsilon = epsilon
967 def columns(self, privatedata, sharedata, graph, columns):
968 if self.stackname not in columns:
969 raise ValueError("stackname column missing")
970 return [self.stackname]
972 def adjustaxis(self, privatedata, sharedata, graph, column, data, index):
973 if column == self.stackname:
974 graph.axes[sharedata.barcolumns[sharedata.barvalueindex]].adjustrange(data, index)
976 def drawpoint(self, privatedata, sharedata, graph):
977 sharedata.vbarrange[sharedata.barvalueindex][0] = sharedata.vbarrange[sharedata.barvalueindex][1]
978 try:
979 sharedata.vpos[sharedata.barvalueindex] = sharedata.vbarrange[sharedata.barvalueindex][1] = graph.axes[sharedata.barcolumns[sharedata.barvalueindex]].convert(sharedata.point[self.stackname])
980 except (ArithmeticError, ValueError, TypeError):
981 sharedata.vpos[sharedata.barvalueindex] = sharedata.vbarrange[sharedata.barvalueindex][1] = None
982 sharedata.vposavailable = sharedata.vposvalid = 0
983 else:
984 if not sharedata.vposavailable or not sharedata.vposvalid:
985 sharedata.vposavailable = sharedata.vposvalid = 1
986 for v in sharedata.vpos:
987 if v is None:
988 sharedata.vposavailable = sharedata.vposvalid = 0
989 break
990 if v < -self.epsilon or v > 1+self.epsilon:
991 sharedata.vposvalid = 0
994 class bar(_style):
996 needsdata = ["vbarrange"]
998 defaultbarattrs = [color.palette.Rainbow, deco.stroked([color.gray.black])]
1000 def __init__(self, barattrs=[]):
1001 self.barattrs = barattrs
1003 def columns(self, privatedata, sharedata, graph, columns):
1004 if len(graph.axesnames) != 2:
1005 raise TypeError("bar style restricted on two-dimensional graphs")
1006 return []
1008 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1009 if selecttotal > 1:
1010 privatedata.barattrs = attr.selectattrs(self.defaultbarattrs + self.barattrs, selectindex, selecttotal)
1011 else:
1012 privatedata.barattrs = self.defaultbarattrs + self.barattrs
1014 def initdrawpoints(self, privatedata, sharedata, graph):
1015 privatedata.rectcanvas = graph.insert(canvas.canvas())
1017 def drawpoint(self, privatedata, sharedata, graph):
1018 xvmin = sharedata.vbarrange[0][0]
1019 xvmax = sharedata.vbarrange[0][1]
1020 yvmin = sharedata.vbarrange[1][0]
1021 yvmax = sharedata.vbarrange[1][1]
1022 if None not in [xvmin, xvmax, yvmin, yvmax]:
1023 # TODO range check
1024 p = graph.vgeodesic(xvmin, yvmin, xvmax, yvmin)
1025 p.append(graph.vgeodesic_el(xvmax, yvmin, xvmax, yvmax))
1026 p.append(graph.vgeodesic_el(xvmax, yvmax, xvmin, yvmax))
1027 p.append(graph.vgeodesic_el(xvmin, yvmax, xvmin, yvmin))
1028 p.append(path.closepath())
1029 privatedata.rectcanvas.fill(p, privatedata.barattrs)
1031 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt, dy_pt, selectindex, selecttotal):
1032 #raise "TODO"
1033 pass