graph style rework continues
[PyX/mjg.git] / pyx / graph / style.py
blobc980ba8c5f44fc24d1c1398824ed02644feacca5
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 re, math
27 from pyx import attr, deco, style, color, unit, canvas, path
28 from pyx import text as textmodule
31 class _style:
32 """Interface class for graph styles
34 Each graph style must support the methods described in this
35 class. However, since a graph style might not need to perform
36 actions on all the various events, it does not need to overwrite
37 all methods of this base class (e.g. this class is not an abstract
38 class in any respect).
40 A style should never store private data by instance variables
41 (i.e. accessing self), but it should use the styledata instance
42 instead. A style instance can be used multiple times with different
43 styledata instances at the very same time. The styledata instance
44 acts as a data container and furthermore allows for sharing
45 information across several styles.
47 A style contains two class variables, which are not to be
48 modified. provide is a of variable names a style provides via
49 the styledata instance. This list should be used to find, whether
50 all needs of subsequent styles are fullfilled. Otherwise the
51 provider dictionary described below should list a proper style
52 to be inserted. Contrary, need is a list of variable names the
53 style needs to access in the styledata instance."""
55 provide = [] # by default, we provide nothing
56 need = [] # and do not depend on anything
58 def columns(self, styledata, graph, columns):
59 """Set column information
61 This method is used setup the column information to be
62 accessible to the style later on. The style should analyse
63 the list of strings columns, which contain the column names
64 of the data. The method should return a list of column names
65 which the style will make use of."""
66 return []
68 def selectstyle(self, styledata, graph, selectindex, selecttotal):
69 """Select stroke/fill attributes
71 This method is called to allow for the selection of
72 changable attributes of a style."""
73 pass
75 def adjustaxis(self, styledata, graph, column, data, index):
76 """Adjust axis range
78 This method is called in order to adjust the axis range to
79 the provided data. Column is the name of the column (each
80 style is subsequently called for all column names), data is
81 a list of points and index is the index of the column within
82 a point."""
83 pass
85 def initdrawpoints(self, styledata, graph):
86 """Initialize drawing of data
88 This method might be used to initialize the drawing of data."""
89 pass
91 def drawpoint(self, styledata, graph):
92 """Draw data
94 This method is called for each data point. The data is
95 available in the dictionary styledata.data. The dictionary
96 keys are the column names."""
97 pass
99 def donedrawpoints(self, styledata, graph):
100 """Finalize drawing of data
102 This method is called after the last data point was
103 drawn using the drawpoint method above."""
104 pass
106 def key_pt(self, styledata, graph, x_pt, y_pt, width_pt, height_pt):
107 """Draw a graph key
109 Draw a graph key to graph."""
110 pass
113 # provider is a dictionary, which maps styledata variable names
114 # to default styles, which provide a default way to create the
115 # corresponding styledata variable.
117 provider = {}
120 class _pos(_style):
122 provide = ["vpos", "vposmissing", "vposavailable", "vposvalid"]
124 def __init__(self, epsilon=1e-10):
125 self.epsilon = epsilon
127 def columns(self, styledata, graph, columns):
128 styledata.pointposcolumns = []
129 styledata.vposmissing = []
130 for count, axisnames in enumerate(graph.axesnames):
131 for axisname in axisnames:
132 for column in columns:
133 if axisname == column:
134 styledata.pointposcolumns.append(column)
135 if len(styledata.pointposcolumns) + len(styledata.vposmissing) > count+1:
136 raise ValueError("multiple axes per graph dimension")
137 elif len(styledata.pointposcolumns) + len(styledata.vposmissing) < count+1:
138 styledata.vposmissing.append(count)
139 return styledata.pointposcolumns
141 def adjustaxis(self, styledata, graph, column, data, index):
142 if column in styledata.pointposcolumns:
143 graph.axes[column].adjustrange(data, index)
145 def initdrawpoints(self, styledata, graph):
146 styledata.vpos = [None]*(len(styledata.pointposcolumns) + len(styledata.vposmissing))
147 styledata.pointpostmplist = [[column, index, graph.axes[column]] # temporarily used by drawpoint only
148 for index, column in enumerate(styledata.pointposcolumns)]
149 for missing in styledata.vposmissing:
150 for pointpostmp in styledata.pointpostmplist:
151 if pointpostmp[1] >= missing:
152 pointpostmp[1] += 1
154 def drawpoint(self, styledata, graph):
155 styledata.vposavailable = 1 # valid position (but might be outside of the graph)
156 styledata.vposvalid = 1 # valid position inside the graph
157 for column, index, axis in styledata.pointpostmplist:
158 try:
159 v = axis.convert(styledata.point[column])
160 except (ArithmeticError, ValueError, TypeError):
161 styledata.vposavailable = styledata.vposvalid = 0
162 styledata.vpos[index] = None
163 else:
164 if v < - self.epsilon or v > 1 + self.epsilon:
165 styledata.vposvalid = 0
166 styledata.vpos[index] = v
169 provider["vpos"] = provider["vposmissing"] = provider["vposavailable"] = provider["vposvalid"] = _pos()
172 class _range(_style):
174 provide = ["vrange", "vrangemissing"]
176 # internal bit masks
177 mask_value = 1
178 mask_min = 2
179 mask_max = 4
180 mask_dmin = 8
181 mask_dmax = 16
182 mask_d = 32
184 def __init__(self, epsilon=1e-10):
185 self.epsilon = epsilon
187 def columns(self, styledata, graph, columns):
188 def numberofbits(mask):
189 if not mask:
190 return 0
191 if mask & 1:
192 return numberofbits(mask >> 1) + 1
193 else:
194 return numberofbits(mask >> 1)
195 usecolumns = []
196 styledata.rangeposcolumns = []
197 styledata.vrangemissing = []
198 styledata.rangeposdeltacolumns = {} # temporarily used by adjustaxis only
199 for count, axisnames in enumerate(graph.axesnames):
200 for axisname in axisnames:
201 mask = 0
202 for column in columns:
203 addusecolumns = 1
204 if axisname == column:
205 mask += self.mask_value
206 elif axisname + "min" == column:
207 mask += self.mask_min
208 elif axisname + "max" == column:
209 mask += self.mask_max
210 elif "d" + axisname + "min" == column:
211 mask += self.mask_dmin
212 elif "d" + axisname + "max" == column:
213 mask += self.mask_dmax
214 elif "d" + axisname == column:
215 mask += self.mask_d
216 else:
217 addusecolumns = 0
218 if addusecolumns:
219 usecolumns.append(column)
220 if mask & (self.mask_min | self.mask_max | self.mask_dmin | self.mask_dmax | self.mask_d):
221 if (numberofbits(mask & (self.mask_min | self.mask_dmin | self.mask_d)) > 1 or
222 numberofbits(mask & (self.mask_max | self.mask_dmax | self.mask_d)) > 1):
223 raise ValueError("multiple errorbar definition")
224 if mask & (self.mask_dmin | self.mask_dmax | self.mask_d):
225 if not (mask & self.mask_value):
226 raise ValueError("missing value for delta")
227 styledata.rangeposdeltacolumns[axisname] = {}
228 styledata.rangeposcolumns.append((axisname, mask))
229 elif mask == self.mask_value:
230 usecolumns = usecolumns[:-1]
231 if len(styledata.rangeposcolumns) + len(styledata.vrangemissing) > count+1:
232 raise ValueError("multiple axes per graph dimension")
233 elif len(styledata.rangeposcolumns) + len(styledata.vrangemissing) < count+1:
234 styledata.vrangemissing.append(count)
235 return usecolumns
237 def adjustaxis(self, styledata, graph, column, data, index):
238 if column in [c + "min" for c, m in styledata.rangeposcolumns if m & self.mask_min]:
239 graph.axes[column[:-3]].adjustrange(data, index)
240 if column in [c + "max" for c, m in styledata.rangeposcolumns if m & self.mask_max]:
241 graph.axes[column[:-3]].adjustrange(data, index)
243 # delta handling: fill rangeposdeltacolumns
244 if column in [c for c, m in styledata.rangeposcolumns if m & (self.mask_dmin | self.mask_dmax | self.mask_d)]:
245 styledata.rangeposdeltacolumns[column][self.mask_value] = data, index
246 if column in ["d" + c + "min" for c, m in styledata.rangeposcolumns if m & self.mask_dmin]:
247 styledata.rangeposdeltacolumns[column[1:-3]][self.mask_dmin] = data, index
248 if column in ["d" + c + "max" for c, m in styledata.rangeposcolumns if m & self.mask_dmax]:
249 styledata.rangeposdeltacolumns[column[1:-3]][self.mask_dmax] = data, index
250 if column in ["d" + c for c, m in styledata.rangeposcolumns if m & self.mask_d]:
251 styledata.rangeposdeltacolumns[column[1:]][self.mask_d] = data, index
253 # delta handling: process rangeposdeltacolumns
254 for c, d in styledata.rangeposdeltacolumns.items():
255 if d.has_key(self.mask_value):
256 for k in d.keys():
257 if k != self.mask_value:
258 if k & (self.mask_dmin | self.mask_d):
259 graph.axes[c].adjustrange(d[self.mask_value][0], d[self.mask_value][1],
260 deltamindata=d[k][0], deltaminindex=d[k][1])
261 if k & (self.mask_dmax | self.mask_d):
262 graph.axes[c].adjustrange(d[self.mask_value][0], d[self.mask_value][1],
263 deltamaxdata=d[k][0], deltamaxindex=d[k][1])
264 del d[k]
266 def initdrawpoints(self, styledata, graph):
267 styledata.vrange = [[None for x in range(2)] for y in styledata.rangeposcolumns + styledata.vrangemissing]
268 styledata.rangepostmplist = [[column, mask, index, graph.axes[column]] # temporarily used by drawpoint only
269 for index, (column, mask) in enumerate(styledata.rangeposcolumns)]
270 for missing in styledata.vrangemissing:
271 for rangepostmp in styledata.rangepostmplist:
272 if rangepostmp[2] >= missing:
273 rangepostmp[2] += 1
275 def drawpoint(self, styledata, graph):
276 for column, mask, index, axis in styledata.rangepostmplist:
277 try:
278 if mask & self.mask_min:
279 styledata.vrange[index][0] = axis.convert(styledata.point[column + "min"])
280 if mask & self.mask_dmin:
281 styledata.vrange[index][0] = axis.convert(styledata.point[column] - styledata.point["d" + column + "min"])
282 if mask & self.mask_d:
283 styledata.vrange[index][0] = axis.convert(styledata.point[column] - styledata.point["d" + column])
284 except (ArithmeticError, ValueError, TypeError):
285 styledata.vrange[index][0] = None
286 try:
287 if mask & self.mask_max:
288 styledata.vrange[index][1] = axis.convert(styledata.point[column + "max"])
289 if mask & self.mask_dmax:
290 styledata.vrange[index][1] = axis.convert(styledata.point[column] + styledata.point["d" + column + "max"])
291 if mask & self.mask_d:
292 styledata.vrange[index][1] = axis.convert(styledata.point[column] + styledata.point["d" + column])
293 except (ArithmeticError, ValueError, TypeError):
294 styledata.vrange[index][1] = None
296 # some range checks for data consistency
297 if (styledata.vrange[index][0] is not None and styledata.vrange[index][1] is not None and
298 styledata.vrange[index][0] > styledata.vrange[index][1] + self.epsilon):
299 raise ValueError("negative errorbar range")
300 if (styledata.vrange[index][0] is not None and styledata.vpos[index] is not None and
301 styledata.vrange[index][0] > styledata.vpos[index] + self.epsilon):
302 raise ValueError("negative minimum errorbar")
303 if (styledata.vrange[index][1] is not None and styledata.vpos[index] is not None and
304 styledata.vrange[index][1] < styledata.vpos[index] - self.epsilon):
305 raise ValueError("negative maximum errorbar")
308 provider["vrange"] = provider["vrangemissing"] = _range()
311 def _crosssymbol(c, x_pt, y_pt, size_pt, attrs):
312 c.draw(path.path(path.moveto_pt(x_pt-0.5*size_pt, y_pt-0.5*size_pt),
313 path.lineto_pt(x_pt+0.5*size_pt, y_pt+0.5*size_pt),
314 path.moveto_pt(x_pt-0.5*size_pt, y_pt+0.5*size_pt),
315 path.lineto_pt(x_pt+0.5*size_pt, y_pt-0.5*size_pt)), attrs)
317 def _plussymbol(c, x_pt, y_pt, size_pt, attrs):
318 c.draw(path.path(path.moveto_pt(x_pt-0.707106781*size_pt, y_pt),
319 path.lineto_pt(x_pt+0.707106781*size_pt, y_pt),
320 path.moveto_pt(x_pt, y_pt-0.707106781*size_pt),
321 path.lineto_pt(x_pt, y_pt+0.707106781*size_pt)), attrs)
323 def _squaresymbol(c, x_pt, y_pt, size_pt, attrs):
324 c.draw(path.path(path.moveto_pt(x_pt-0.5*size_pt, y_pt-0.5*size_pt),
325 path.lineto_pt(x_pt+0.5*size_pt, y_pt-0.5*size_pt),
326 path.lineto_pt(x_pt+0.5*size_pt, y_pt+0.5*size_pt),
327 path.lineto_pt(x_pt-0.5*size_pt, y_pt+0.5*size_pt),
328 path.closepath()), attrs)
330 def _trianglesymbol(c, x_pt, y_pt, size_pt, attrs):
331 c.draw(path.path(path.moveto_pt(x_pt-0.759835685*size_pt, y_pt-0.438691337*size_pt),
332 path.lineto_pt(x_pt+0.759835685*size_pt, y_pt-0.438691337*size_pt),
333 path.lineto_pt(x_pt, y_pt+0.877382675*size_pt),
334 path.closepath()), attrs)
336 def _circlesymbol(c, x_pt, y_pt, size_pt, attrs):
337 c.draw(path.path(path.arc_pt(x_pt, y_pt, 0.564189583*size_pt, 0, 360),
338 path.closepath()), attrs)
340 def _diamondsymbol(c, x_pt, y_pt, size_pt, attrs):
341 c.draw(path.path(path.moveto_pt(x_pt-0.537284965*size_pt, y_pt),
342 path.lineto_pt(x_pt, y_pt-0.930604859*size_pt),
343 path.lineto_pt(x_pt+0.537284965*size_pt, y_pt),
344 path.lineto_pt(x_pt, y_pt+0.930604859*size_pt),
345 path.closepath()), attrs)
348 class _styleneedingpointpos(_style):
350 need = ["vposmissing"]
352 def columns(self, styledata, graph, columns):
353 if len(styledata.vposmissing):
354 raise ValueError("position columns incomplete")
355 return []
358 class symbol(_styleneedingpointpos):
360 need = ["vpos", "vposmissing", "vposvalid"]
362 # insert symbols like staticmethods
363 cross = _crosssymbol
364 plus = _plussymbol
365 square = _squaresymbol
366 triangle = _trianglesymbol
367 circle = _circlesymbol
368 diamond = _diamondsymbol
370 changecross = attr.changelist([cross, plus, square, triangle, circle, diamond])
371 changeplus = attr.changelist([plus, square, triangle, circle, diamond, cross])
372 changesquare = attr.changelist([square, triangle, circle, diamond, cross, plus])
373 changetriangle = attr.changelist([triangle, circle, diamond, cross, plus, square])
374 changecircle = attr.changelist([circle, diamond, cross, plus, square, triangle])
375 changediamond = attr.changelist([diamond, cross, plus, square, triangle, circle])
376 changesquaretwice = attr.changelist([square, square, triangle, triangle, circle, circle, diamond, diamond])
377 changetriangletwice = attr.changelist([triangle, triangle, circle, circle, diamond, diamond, square, square])
378 changecircletwice = attr.changelist([circle, circle, diamond, diamond, square, square, triangle, triangle])
379 changediamondtwice = attr.changelist([diamond, diamond, square, square, triangle, triangle, circle, circle])
381 changestrokedfilled = attr.changelist([deco.stroked, deco.filled])
382 changefilledstroked = attr.changelist([deco.filled, deco.stroked])
384 defaultsymbolattrs = [deco.stroked]
386 def __init__(self, symbol=changecross, size=0.2*unit.v_cm, symbolattrs=[]):
387 self.symbol = symbol
388 self.size = size
389 self.symbolattrs = symbolattrs
391 def selectstyle(self, styledata, graph, selectindex, selecttotal):
392 styledata.symbol = attr.selectattr(self.symbol, selectindex, selecttotal)
393 styledata.size_pt = unit.topt(attr.selectattr(self.size, selectindex, selecttotal))
394 if self.symbolattrs is not None:
395 styledata.symbolattrs = attr.selectattrs(self.defaultsymbolattrs + self.symbolattrs, selectindex, selecttotal)
396 else:
397 styledata.symbolattrs = None
399 def initdrawpoints(self, styledata, graph):
400 styledata.symbolcanvas = graph.insert(canvas.canvas())
402 def drawpoint(self, styledata, graph):
403 if styledata.vposvalid and styledata.symbolattrs is not None:
404 xpos, ypos = graph.vpos_pt(*styledata.vpos)
405 styledata.symbol(styledata.symbolcanvas, xpos, ypos, styledata.size_pt, styledata.symbolattrs)
407 def key_pt(self, styledata, graph, x_pt, y_pt, width_pt, height_pt):
408 if styledata.symbolattrs is not None:
409 styledata.symbol(graph, x_pt+0.5*width_pt, y_pt+0.5*height_pt, styledata.size_pt, styledata.symbolattrs)
412 class line(_styleneedingpointpos):
414 need = ["vpos", "vposmissing", "vposavailable", "vposvalid"]
416 changelinestyle = attr.changelist([style.linestyle.solid,
417 style.linestyle.dashed,
418 style.linestyle.dotted,
419 style.linestyle.dashdotted])
421 defaultlineattrs = [changelinestyle]
423 def __init__(self, lineattrs=[]):
424 self.lineattrs = lineattrs
426 def selectstyle(self, styledata, graph, selectindex, selecttotal):
427 styledata.lineattrs = attr.selectattrs(self.defaultlineattrs + self.lineattrs, selectindex, selecttotal)
429 def initdrawpoints(self, styledata, graph):
430 styledata.linecanvas = graph.insert(canvas.canvas())
431 if styledata.lineattrs is not None:
432 styledata.linecanvas.set(styledata.lineattrs)
433 styledata.path = path.path()
434 styledata.linebasepoints = []
435 styledata.lastvpos = None
437 def addpointstopath(self, styledata):
438 # add baselinepoints to styledata.path
439 if len(styledata.linebasepoints) > 1:
440 styledata.path.append(path.moveto_pt(*styledata.linebasepoints[0]))
441 if len(styledata.linebasepoints) > 2:
442 styledata.path.append(path.multilineto_pt(styledata.linebasepoints[1:]))
443 else:
444 styledata.path.append(path.lineto_pt(*styledata.linebasepoints[1]))
445 styledata.linebasepoints = []
447 def drawpoint(self, styledata, graph):
448 # append linebasepoints
449 if styledata.vposavailable:
450 if len(styledata.linebasepoints):
451 # the last point was inside the graph
452 if styledata.vposvalid: # shortcut for the common case
453 styledata.linebasepoints.append(graph.vpos_pt(*styledata.vpos))
454 else:
455 # cut end
456 cut = 1
457 for vstart, vend in zip(styledata.lastvpos, styledata.vpos):
458 newcut = None
459 if vend > 1:
460 # 1 = vstart + (vend - vstart) * cut
461 try:
462 newcut = (1 - vstart)/(vend - vstart)
463 except ArithmeticError:
464 break
465 if vend < 0:
466 # 0 = vstart + (vend - vstart) * cut
467 try:
468 newcut = - vstart/(vend - vstart)
469 except ArithmeticError:
470 break
471 if newcut is not None and newcut < cut:
472 cut = newcut
473 else:
474 cutvpos = []
475 for vstart, vend in zip(styledata.lastvpos, styledata.vpos):
476 cutvpos.append(vstart + (vend - vstart) * cut)
477 styledata.linebasepoints.append(styledata.graph.vpos_pt(*cutvpos))
478 self.addpointstopath(styledata)
479 else:
480 # the last point was outside the graph
481 if styledata.lastvpos is not None:
482 if styledata.vposvalid:
483 # cut beginning
484 cut = 0
485 for vstart, vend in zip(styledata.lastvpos, styledata.vpos):
486 newcut = None
487 if vstart > 1:
488 # 1 = vstart + (vend - vstart) * cut
489 try:
490 newcut = (1 - vstart)/(vend - vstart)
491 except ArithmeticError:
492 break
493 if vstart < 0:
494 # 0 = vstart + (vend - vstart) * cut
495 try:
496 newcut = - vstart/(vend - vstart)
497 except ArithmeticError:
498 break
499 if newcut is not None and newcut > cut:
500 cut = newcut
501 else:
502 cutvpos = []
503 for vstart, vend in zip(styledata.lastvpos, styledata.vpos):
504 cutvpos.append(vstart + (vend - vstart) * cut)
505 styledata.linebasepoints.append(graph.vpos_pt(*cutvpos))
506 styledata.linebasepoints.append(graph.vpos_pt(*styledata.vpos))
507 else:
508 # sometimes cut beginning and end
509 cutfrom = 0
510 cutto = 1
511 for vstart, vend in zip(styledata.lastvpos, styledata.vpos):
512 newcutfrom = None
513 if vstart > 1:
514 if vend > 1:
515 break
516 # 1 = vstart + (vend - vstart) * cutfrom
517 try:
518 newcutfrom = (1 - vstart)/(vend - vstart)
519 except ArithmeticError:
520 break
521 if vstart < 0:
522 if vend < 0:
523 break
524 # 0 = vstart + (vend - vstart) * cutfrom
525 try:
526 newcutfrom = - vstart/(vend - vstart)
527 except ArithmeticError:
528 break
529 if newcutfrom is not None and newcutfrom > cutfrom:
530 cutfrom = newcutfrom
531 newcutto = None
532 if vend > 1:
533 # 1 = vstart + (vend - vstart) * cutto
534 try:
535 newcutto = (1 - vstart)/(vend - vstart)
536 except ArithmeticError:
537 break
538 if vend < 0:
539 # 0 = vstart + (vend - vstart) * cutto
540 try:
541 newcutto = - vstart/(vend - vstart)
542 except ArithmeticError:
543 break
544 if newcutto is not None and newcutto < cutto:
545 cutto = newcutto
546 else:
547 if cutfrom < cutto:
548 cutfromvpos = []
549 cuttovpos = []
550 for vstart, vend in zip(styledata.lastvpos, styledata.vpos):
551 cutfromvpos.append(vstart + (vend - vstart) * cutfrom)
552 cuttovpos.append(vstart + (vend - vstart) * cutto)
553 styledata.linebasepoints.append(styledata.graph.vpos_pt(*cutfromvpos))
554 styledata.linebasepoints.append(styledata.graph.vpos_pt(*cuttovpos))
555 self.addpointstopath(styledata)
556 styledata.lastvpos = styledata.vpos[:]
557 else:
558 if len(styledata.linebasepoints) > 1:
559 self.addpointstopath(styledata)
560 styledata.lastvpos = None
562 def donedrawpoints(self, styledata, graph):
563 if len(styledata.linebasepoints) > 1:
564 self.addpointstopath(styledata)
565 if styledata.lineattrs is not None and len(styledata.path.path):
566 styledata.linecanvas.stroke(styledata.path)
568 def key_pt(self, styledata, graph, x_pt, y_pt, width_pt, height_pt):
569 if styledata.lineattrs is not None:
570 graph.stroke(path.line_pt(x_pt, y_pt+0.5*height_pt, x_pt+width_pt, y_pt+0.5*height_pt), styledata.lineattrs)
573 class errorbar(_style):
575 need = ["vpos", "vposmissing", "vposavailable", "vposvalid", "vrange", "vrangemissing"]
577 defaulterrorbarattrs = []
579 def __init__(self, size=0.1*unit.v_cm,
580 errorbarattrs=[],
581 epsilon=1e-10):
582 self.size = size
583 self.errorbarattrs = errorbarattrs
584 self.epsilon = epsilon
586 def columns(self, styledata, graph, columns):
587 for i in styledata.vposmissing:
588 if i in styledata.vrangemissing:
589 raise ValueError("position and range for a graph dimension missing")
590 return []
592 def selectstyle(self, styledata, graph, selectindex, selecttotal):
593 styledata.errorsize_pt = unit.topt(attr.selectattr(self.size, selectindex, selecttotal))
594 styledata.errorbarattrs = attr.selectattrs(self.defaulterrorbarattrs + self.errorbarattrs, selectindex, selecttotal)
596 def initdrawpoints(self, styledata, graph):
597 styledata.errorbarcanvas = graph.insert(canvas.canvas())
598 if styledata.errorbarattrs is not None:
599 styledata.errorbarcanvas.set(styledata.errorbarattrs)
600 styledata.dimensionlist = range(len(styledata.vpos))
602 def drawpoint(self, styledata, graph):
603 if styledata.errorbarattrs is None:
604 return
605 for i in styledata.dimensionlist:
606 for j in styledata.dimensionlist:
607 if (i != j and
608 (styledata.vpos[j] is None or
609 styledata.vpos[j] < -self.epsilon or
610 styledata.vpos[j] > 1+self.epsilon)):
611 break
612 else:
613 if ((styledata.vrange[i][0] is None and styledata.vpos[i] is None) or
614 (styledata.vrange[i][1] is None and styledata.vpos[i] is None) or
615 (styledata.vrange[i][0] is None and styledata.vrange[i][1] is None)):
616 continue
617 vminpos = styledata.vpos[:]
618 if styledata.vrange[i][0] is not None:
619 vminpos[i] = styledata.vrange[i][0]
620 mincap = 1
621 else:
622 mincap = 0
623 if vminpos[i] > 1+self.epsilon:
624 continue
625 if vminpos[i] < -self.epsilon:
626 vminpos[i] = 0
627 mincap = 0
628 vmaxpos = styledata.vpos[:]
629 if styledata.vrange[i][1] is not None:
630 vmaxpos[i] = styledata.vrange[i][1]
631 maxcap = 1
632 else:
633 maxcap = 0
634 if vmaxpos[i] < -self.epsilon:
635 continue
636 if vmaxpos[i] > 1+self.epsilon:
637 vmaxpos[i] = 1
638 maxcap = 0
639 styledata.errorbarcanvas.stroke(graph.vgeodesic(*(vminpos + vmaxpos)))
640 for j in styledata.dimensionlist:
641 if i != j:
642 if mincap:
643 styledata.errorbarcanvas.stroke(graph.vcap_pt(j, styledata.errorsize_pt, *vminpos))
644 if maxcap:
645 styledata.errorbarcanvas.stroke(graph.vcap_pt(j, styledata.errorsize_pt, *vmaxpos))
648 # not yet ported to the new style scheme
650 # class text(symbol):
652 # defaulttextattrs = [textmodule.halign.center, textmodule.vshift.mathaxis]
654 # def __init__(self, textdx=0*unit.v_cm, textdy=0.3*unit.v_cm, textattrs=[], **kwargs):
655 # self.textdx = textdx
656 # self.textdy = textdy
657 # self.textattrs = textattrs
658 # symbol.__init__(self, **kwargs)
660 # def setdata(self, graph, columns, styledata):
661 # columns = columns.copy()
662 # styledata.textindex = columns["text"]
663 # del columns["text"]
664 # return symbol.setdata(self, graph, columns, styledata)
666 # def selectstyle(self, selectindex, selecttotal, styledata):
667 # if self.textattrs is not None:
668 # styledata.textattrs = attr.selectattrs(self.defaulttextattrs + self.textattrs, selectindex, selecttotal)
669 # else:
670 # styledata.textattrs = None
671 # symbol.selectstyle(self, selectindex, selecttotal, styledata)
673 # def drawsymbol_pt(self, c, x, y, styledata, point=None):
674 # symbol.drawsymbol_pt(self, c, x, y, styledata, point)
675 # if None not in (x, y, point[styledata.textindex]) and styledata.textattrs is not None:
676 # c.text_pt(x + styledata.textdx_pt, y + styledata.textdy_pt, str(point[styledata.textindex]), styledata.textattrs)
678 # def drawpoints(self, points, graph, styledata):
679 # styledata.textdx_pt = unit.topt(self.textdx)
680 # styledata.textdy_pt = unit.topt(self.textdy)
681 # symbol.drawpoints(self, points, graph, styledata)
684 # class arrow(_style):
686 # defaultlineattrs = []
687 # defaultarrowattrs = []
689 # def __init__(self, linelength=0.25*unit.v_cm, arrowsize=0.15*unit.v_cm, lineattrs=[], arrowattrs=[], epsilon=1e-10):
690 # self.linelength = linelength
691 # self.arrowsize = arrowsize
692 # self.lineattrs = lineattrs
693 # self.arrowattrs = arrowattrs
694 # self.epsilon = epsilon
696 # def setdata(self, graph, columns, styledata):
697 # if len(graph.axisnames) != 2:
698 # raise TypeError("arrow style restricted on two-dimensional graphs")
699 # columns = columns.copy()
700 # styledata.xaxis, styledata.xindex = _style.setdatapattern(self, graph, columns, re.compile(r"(%s([2-9]|[1-9][0-9]+)?)$" % graph.axisnames[0]))
701 # styledata.yaxis, styledata.yindex = _style.setdatapattern(self, graph, columns, re.compile(r"(%s([2-9]|[1-9][0-9]+)?)$" % graph.axisnames[1]))
702 # styledata.sizeindex = columns["size"]
703 # del columns["size"]
704 # styledata.angleindex = columns["angle"]
705 # del columns["angle"]
706 # return columns
708 # def adjustaxes(self, points, columns, styledata):
709 # if styledata.xindex in columns:
710 # styledata.xaxis.adjustrange(points, styledata.xindex)
711 # if styledata.yindex in columns:
712 # styledata.yaxis.adjustrange(points, styledata.yindex)
714 # def selectstyle(self, selectindex, selecttotal, styledata):
715 # if self.lineattrs is not None:
716 # styledata.lineattrs = attr.selectattrs(self.defaultlineattrs + self.lineattrs, selectindex, selecttotal)
717 # else:
718 # styledata.lineattrs = None
719 # if self.arrowattrs is not None:
720 # styledata.arrowattrs = attr.selectattrs(self.defaultarrowattrs + self.arrowattrs, selectindex, selecttotal)
721 # else:
722 # styledata.arrowattrs = None
724 # def drawpoints(self, points, graph, styledata):
725 # if styledata.lineattrs is not None and styledata.arrowattrs is not None:
726 # linelength_pt = unit.topt(self.linelength)
727 # for point in points:
728 # xpos, ypos = graph.pos_pt(point[styledata.xindex], point[styledata.yindex], xaxis=styledata.xaxis, yaxis=styledata.yaxis)
729 # if point[styledata.sizeindex] > self.epsilon:
730 # dx = math.cos(point[styledata.angleindex]*math.pi/180)
731 # dy = math.sin(point[styledata.angleindex]*math.pi/180)
732 # x1 = xpos-0.5*dx*linelength_pt*point[styledata.sizeindex]
733 # y1 = ypos-0.5*dy*linelength_pt*point[styledata.sizeindex]
734 # x2 = xpos+0.5*dx*linelength_pt*point[styledata.sizeindex]
735 # y2 = ypos+0.5*dy*linelength_pt*point[styledata.sizeindex]
736 # graph.stroke(path.line_pt(x1, y1, x2, y2), styledata.lineattrs +
737 # [deco.earrow(styledata.arrowattrs, size=self.arrowsize*point[styledata.sizeindex])])
740 # class rect(_style):
742 # def __init__(self, palette=color.palette.Gray):
743 # self.palette = palette
745 # def setdata(self, graph, columns, styledata):
746 # if len(graph.axisnames) != 2:
747 # raise TypeError("arrow style restricted on two-dimensional graphs")
748 # columns = columns.copy()
749 # styledata.xaxis, styledata.xminindex = _style.setdatapattern(self, graph, columns, re.compile(r"(%s([2-9]|[1-9][0-9]+)?)min$" % graph.axisnames[0]))
750 # styledata.yaxis, styledata.yminindex = _style.setdatapattern(self, graph, columns, re.compile(r"(%s([2-9]|[1-9][0-9]+)?)min$" % graph.axisnames[1]))
751 # xaxis, styledata.xmaxindex = _style.setdatapattern(self, graph, columns, re.compile(r"(%s([2-9]|[1-9][0-9]+)?)max$" % graph.axisnames[0]))
752 # yaxis, styledata.ymaxindex = _style.setdatapattern(self, graph, columns, re.compile(r"(%s([2-9]|[1-9][0-9]+)?)max$" % graph.axisnames[1]))
753 # if xaxis != styledata.xaxis or yaxis != styledata.yaxis:
754 # raise ValueError("min/max values should use the same axes")
755 # styledata.colorindex = columns["color"]
756 # del columns["color"]
757 # return columns
759 # def selectstyle(self, selectindex, selecttotal, styledata):
760 # pass
762 # def adjustaxes(self, points, columns, styledata):
763 # if styledata.xminindex in columns:
764 # styledata.xaxis.adjustrange(points, styledata.xminindex)
765 # if styledata.xmaxindex in columns:
766 # styledata.xaxis.adjustrange(points, styledata.xmaxindex)
767 # if styledata.yminindex in columns:
768 # styledata.yaxis.adjustrange(points, styledata.yminindex)
769 # if styledata.ymaxindex in columns:
770 # styledata.yaxis.adjustrange(points, styledata.ymaxindex)
772 # def drawpoints(self, points, graph, styledata):
773 # # TODO: bbox shortcut
774 # c = graph.insert(canvas.canvas())
775 # lastcolorvalue = None
776 # for point in points:
777 # try:
778 # xvmin = styledata.xaxis.convert(point[styledata.xminindex])
779 # xvmax = styledata.xaxis.convert(point[styledata.xmaxindex])
780 # yvmin = styledata.yaxis.convert(point[styledata.yminindex])
781 # yvmax = styledata.yaxis.convert(point[styledata.ymaxindex])
782 # colorvalue = point[styledata.colorindex]
783 # if colorvalue != lastcolorvalue:
784 # color = self.palette.getcolor(point[styledata.colorindex])
785 # except:
786 # continue
787 # if ((xvmin < 0 and xvmax < 0) or (xvmin > 1 and xvmax > 1) or
788 # (yvmin < 0 and yvmax < 0) or (yvmin > 1 and yvmax > 1)):
789 # continue
790 # if xvmin < 0:
791 # xvmin = 0
792 # elif xvmin > 1:
793 # xvmin = 1
794 # if xvmax < 0:
795 # xvmax = 0
796 # elif xvmax > 1:
797 # xvmax = 1
798 # if yvmin < 0:
799 # yvmin = 0
800 # elif yvmin > 1:
801 # yvmin = 1
802 # if yvmax < 0:
803 # yvmax = 0
804 # elif yvmax > 1:
805 # yvmax = 1
806 # p = graph.vgeodesic(xvmin, yvmin, xvmax, yvmin)
807 # p.append(graph.vgeodesic_el(xvmax, yvmin, xvmax, yvmax))
808 # p.append(graph.vgeodesic_el(xvmax, yvmax, xvmin, yvmax))
809 # p.append(graph.vgeodesic_el(xvmin, yvmax, xvmin, yvmin))
810 # p.append(path.closepath())
811 # if colorvalue != lastcolorvalue:
812 # c.set([color])
813 # c.fill(p)
816 # class bar(_style):
818 # defaultfrompathattrs = []
819 # defaultbarattrs = [color.palette.Rainbow, deco.stroked([color.gray.black])]
821 # def __init__(self, fromvalue=None, frompathattrs=[], barattrs=[], subnames=None, epsilon=1e-10):
822 # self.fromvalue = fromvalue
823 # self.frompathattrs = frompathattrs
824 # self.barattrs = barattrs
825 # self.subnames = subnames
826 # self.epsilon = epsilon
828 # def setdata(self, graph, columns, styledata):
829 # # TODO: remove limitation to 2d graphs
830 # if len(graph.axisnames) != 2:
831 # raise TypeError("arrow style currently restricted on two-dimensional graphs")
832 # columns = columns.copy()
833 # xvalue = _style.setdatapattern(self, graph, columns, re.compile(r"(%s([2-9]|[1-9][0-9]+)?)$" % graph.axisnames[0]))
834 # yvalue = _style.setdatapattern(self, graph, columns, re.compile(r"(%s([2-9]|[1-9][0-9]+)?)$" % graph.axisnames[1]))
835 # if (xvalue is None and yvalue is None) or (xvalue is not None and yvalue is not None):
836 # raise TypeError("must specify exactly one value axis")
837 # if xvalue is not None:
838 # styledata.valuepos = 0
839 # styledata.nameaxis, styledata.nameindex = _style.setdatapattern(self, graph, columns, re.compile(r"(%s([2-9]|[1-9][0-9]+)?)name$" % graph.axisnames[1]))
840 # styledata.valueaxis = xvalue[0]
841 # styledata.valueindices = [xvalue[1]]
842 # else:
843 # styledata.valuepos = 1
844 # styledata.nameaxis, styledata.nameindex = _style.setdatapattern(self, graph, columns, re.compile(r"(%s([2-9]|[1-9][0-9]+)?)name$" % graph.axisnames[0]))
845 # styledata.valueaxis = yvalue[0]
846 # styledata.valueindices = [yvalue[1]]
847 # i = 1
848 # while 1:
849 # try:
850 # valueaxis, valueindex = _style.setdatapattern(self, graph, columns, re.compile(r"(%s([2-9]|[1-9][0-9]+)?)stack%i$" % (graph.axisnames[styledata.valuepos], i)))
851 # except:
852 # break
853 # if styledata.valueaxis != valueaxis:
854 # raise ValueError("different value axes for stacked bars")
855 # styledata.valueindices.append(valueindex)
856 # i += 1
857 # return columns
859 # def selectstyle(self, selectindex, selecttotal, styledata):
860 # if selectindex:
861 # styledata.frompathattrs = None
862 # else:
863 # styledata.frompathattrs = self.defaultfrompathattrs + self.frompathattrs
864 # if selecttotal > 1:
865 # if self.barattrs is not None:
866 # styledata.barattrs = attr.selectattrs(self.defaultbarattrs + self.barattrs, selectindex, selecttotal)
867 # else:
868 # styledata.barattrs = None
869 # else:
870 # styledata.barattrs = self.defaultbarattrs + self.barattrs
871 # styledata.selectindex = selectindex
872 # styledata.selecttotal = selecttotal
873 # if styledata.selecttotal != 1 and self.subnames is not None:
874 # raise ValueError("subnames not allowed when iterating over bars")
876 # def adjustaxes(self, points, columns, styledata):
877 # if styledata.nameindex in columns:
878 # if styledata.selecttotal == 1:
879 # styledata.nameaxis.adjustrange(points, styledata.nameindex, subnames=self.subnames)
880 # else:
881 # for i in range(styledata.selecttotal):
882 # styledata.nameaxis.adjustrange(points, styledata.nameindex, subnames=[i])
883 # for valueindex in styledata.valueindices:
884 # if valueindex in columns:
885 # styledata.valueaxis.adjustrange(points, valueindex)
887 # def drawpoints(self, points, graph, styledata):
888 # if self.fromvalue is not None:
889 # vfromvalue = styledata.valueaxis.convert(self.fromvalue)
890 # if vfromvalue < -self.epsilon:
891 # vfromvalue = 0
892 # if vfromvalue > 1 + self.epsilon:
893 # vfromvalue = 1
894 # if styledata.frompathattrs is not None and vfromvalue > self.epsilon and vfromvalue < 1 - self.epsilon:
895 # if styledata.valuepos:
896 # p = graph.vgeodesic(0, vfromvalue, 1, vfromvalue)
897 # else:
898 # p = graph.vgeodesic(vfromvalue, 0, vfromvalue, 1)
899 # graph.stroke(p, styledata.frompathattrs)
900 # else:
901 # vfromvalue = 0
902 # l = len(styledata.valueindices)
903 # if l > 1:
904 # barattrslist = []
905 # for i in range(l):
906 # barattrslist.append(attr.selectattrs(styledata.barattrs, i, l))
907 # else:
908 # barattrslist = [styledata.barattrs]
909 # for point in points:
910 # vvaluemax = vfromvalue
911 # for valueindex, barattrs in zip(styledata.valueindices, barattrslist):
912 # vvaluemin = vvaluemax
913 # try:
914 # vvaluemax = styledata.valueaxis.convert(point[valueindex])
915 # except:
916 # continue
918 # if styledata.selecttotal == 1:
919 # try:
920 # vnamemin = styledata.nameaxis.convert((point[styledata.nameindex], 0))
921 # except:
922 # continue
923 # try:
924 # vnamemax = styledata.nameaxis.convert((point[styledata.nameindex], 1))
925 # except:
926 # continue
927 # else:
928 # try:
929 # vnamemin = styledata.nameaxis.convert((point[styledata.nameindex], styledata.selectindex, 0))
930 # except:
931 # continue
932 # try:
933 # vnamemax = styledata.nameaxis.convert((point[styledata.nameindex], styledata.selectindex, 1))
934 # except:
935 # continue
937 # if styledata.valuepos:
938 # p = graph.vgeodesic(vnamemin, vvaluemin, vnamemin, vvaluemax)
939 # p.append(graph.vgeodesic_el(vnamemin, vvaluemax, vnamemax, vvaluemax))
940 # p.append(graph.vgeodesic_el(vnamemax, vvaluemax, vnamemax, vvaluemin))
941 # p.append(graph.vgeodesic_el(vnamemax, vvaluemin, vnamemin, vvaluemin))
942 # p.append(path.closepath())
943 # else:
944 # p = graph.vgeodesic(vvaluemin, vnamemin, vvaluemin, vnamemax)
945 # p.append(graph.vgeodesic_el(vvaluemin, vnamemax, vvaluemax, vnamemax))
946 # p.append(graph.vgeodesic_el(vvaluemax, vnamemax, vvaluemax, vnamemin))
947 # p.append(graph.vgeodesic_el(vvaluemax, vnamemin, vvaluemin, vnamemin))
948 # p.append(path.closepath())
949 # if barattrs is not None:
950 # graph.fill(p, barattrs)
952 # def key_pt(self, c, x_pt, y_pt, width_pt, height_pt, styledata):
953 # l = len(styledata.valueindices)
954 # if l > 1:
955 # for i in range(l):
956 # c.fill(path.rect_pt(x_pt+i*width_pt/l, y_pt, width_pt/l, height_pt), attr.selectattrs(styledata.barattrs, i, l))
957 # else:
958 # c.fill(path.rect_pt(x_pt, y_pt, width_pt, height_pt), styledata.barattrs)