graph style + data reorganization completed
[PyX/mjg.git] / pyx / graph / style.py
blob3d73ef817516f8c33222cbcf843c47a171c79097
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
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). If index
81 is not None, data is a list of points and index is the index
82 of the column within a point. Otherwise data is already the
83 axis data. Note, that data might be different for different
84 columns, e.i. data might come from various places and is
85 combined without copying but keeping references."""
86 pass
88 def initdrawpoints(self, styledata, graph):
89 """Initialize drawing of data
91 This method might be used to initialize the drawing of data."""
92 pass
94 def drawpoint(self, styledata, graph):
95 """Draw data
97 This method is called for each data point. The data is
98 available in the dictionary styledata.data. The dictionary
99 keys are the column names."""
100 pass
102 def donedrawpoints(self, styledata, graph):
103 """Finalize drawing of data
105 This method is called after the last data point was
106 drawn using the drawpoint method above."""
107 pass
109 def key_pt(self, styledata, graph, x_pt, y_pt, width_pt, height_pt, dy_pt):
110 """Draw graph key
112 This method draws a key for the style to graph at the given
113 position x_pt, y_pt indicating the lower left corner of the
114 given area width_pt, height_pt. The style might draw several
115 key entries shifted vertically by dy_pt. The method returns
116 the number of key entries or None, when nothing was drawn."""
117 return None
120 # Provider is a dictionary, which maps styledata variable names
121 # to default styles, which provide a default way to create the
122 # corresponding styledata variable. Entries in the provider
123 # dictionary should not depend on other styles, thus the need
124 # list should be empty.
126 provider = {}
129 class _pos(_style):
131 provide = ["vpos", "vposmissing", "vposavailable", "vposvalid"]
133 def __init__(self, epsilon=1e-10):
134 self.epsilon = epsilon
136 def columns(self, styledata, graph, columns):
137 styledata.pointposcolumns = []
138 styledata.vposmissing = []
139 for count, axisnames in enumerate(graph.axesnames):
140 for axisname in axisnames:
141 for column in columns:
142 if axisname == column:
143 styledata.pointposcolumns.append(column)
144 if len(styledata.pointposcolumns) + len(styledata.vposmissing) > count+1:
145 raise ValueError("multiple axes per graph dimension")
146 elif len(styledata.pointposcolumns) + len(styledata.vposmissing) < count+1:
147 styledata.vposmissing.append(count)
148 return styledata.pointposcolumns
150 def adjustaxis(self, styledata, graph, column, data, index):
151 if column in styledata.pointposcolumns:
152 graph.axes[column].adjustrange(data, index)
154 def initdrawpoints(self, styledata, graph):
155 styledata.vpos = [None]*(len(styledata.pointposcolumns) + len(styledata.vposmissing))
156 styledata.pointpostmplist = [[column, index, graph.axes[column]] # temporarily used by drawpoint only
157 for index, column in enumerate(styledata.pointposcolumns)]
158 for missing in styledata.vposmissing:
159 for pointpostmp in styledata.pointpostmplist:
160 if pointpostmp[1] >= missing:
161 pointpostmp[1] += 1
163 def drawpoint(self, styledata, graph):
164 styledata.vposavailable = 1 # valid position (but might be outside of the graph)
165 styledata.vposvalid = 1 # valid position inside the graph
166 for column, index, axis in styledata.pointpostmplist:
167 try:
168 v = axis.convert(styledata.point[column])
169 except (ArithmeticError, ValueError, TypeError):
170 styledata.vposavailable = styledata.vposvalid = 0
171 styledata.vpos[index] = None
172 else:
173 if v < -self.epsilon or v > 1+self.epsilon:
174 styledata.vposvalid = 0
175 styledata.vpos[index] = v
178 provider["vpos"] = provider["vposmissing"] = provider["vposavailable"] = provider["vposvalid"] = _pos()
181 class _range(_style):
183 provide = ["vrange", "vrangemissing", "vrangeminmissing", "vrangemaxmissing"]
185 # internal bit masks
186 mask_value = 1
187 mask_min = 2
188 mask_max = 4
189 mask_dmin = 8
190 mask_dmax = 16
191 mask_d = 32
193 def __init__(self, epsilon=1e-10):
194 self.epsilon = epsilon
196 def columns(self, styledata, graph, columns):
197 def numberofbits(mask):
198 if not mask:
199 return 0
200 if mask & 1:
201 return numberofbits(mask >> 1) + 1
202 else:
203 return numberofbits(mask >> 1)
204 usecolumns = []
205 styledata.rangeposcolumns = []
206 styledata.vrangemissing = []
207 styledata.vrangeminmissing = []
208 styledata.vrangemaxmissing = []
209 styledata.rangeposdeltacolumns = {} # temporarily used by adjustaxis only
210 for count, axisnames in enumerate(graph.axesnames):
211 for axisname in axisnames:
212 mask = 0
213 for column in columns:
214 addusecolumns = 1
215 if axisname == column:
216 mask += self.mask_value
217 elif axisname + "min" == column:
218 mask += self.mask_min
219 elif axisname + "max" == column:
220 mask += self.mask_max
221 elif "d" + axisname + "min" == column:
222 mask += self.mask_dmin
223 elif "d" + axisname + "max" == column:
224 mask += self.mask_dmax
225 elif "d" + axisname == column:
226 mask += self.mask_d
227 else:
228 addusecolumns = 0
229 if addusecolumns:
230 usecolumns.append(column)
231 if mask & (self.mask_min | self.mask_max | self.mask_dmin | self.mask_dmax | self.mask_d):
232 if (numberofbits(mask & (self.mask_min | self.mask_dmin | self.mask_d)) > 1 or
233 numberofbits(mask & (self.mask_max | self.mask_dmax | self.mask_d)) > 1):
234 raise ValueError("multiple errorbar definition")
235 if mask & (self.mask_dmin | self.mask_dmax | self.mask_d):
236 if not (mask & self.mask_value):
237 raise ValueError("missing value for delta")
238 styledata.rangeposdeltacolumns[axisname] = {}
239 styledata.rangeposcolumns.append((axisname, mask))
240 elif mask == self.mask_value:
241 usecolumns = usecolumns[:-1]
242 if len(styledata.rangeposcolumns) + len(styledata.vrangemissing) > count+1:
243 raise ValueError("multiple axes per graph dimension")
244 elif len(styledata.rangeposcolumns) + len(styledata.vrangemissing) < count+1:
245 styledata.vrangemissing.append(count)
246 else:
247 if not (styledata.rangeposcolumns[-1][1] & (self.mask_min | self.mask_dmin | self.mask_d)):
248 styledata.vrangeminmissing.append(count)
249 if not (styledata.rangeposcolumns[-1][1] & (self.mask_max | self.mask_dmax | self.mask_d)):
250 styledata.vrangemaxmissing.append(count)
251 return usecolumns
253 def adjustaxis(self, styledata, graph, column, data, index):
254 if column in [c + "min" for c, m in styledata.rangeposcolumns if m & self.mask_min]:
255 graph.axes[column[:-3]].adjustrange(data, index)
256 if column in [c + "max" for c, m in styledata.rangeposcolumns if m & self.mask_max]:
257 graph.axes[column[:-3]].adjustrange(data, index)
259 # delta handling: fill rangeposdeltacolumns
260 if column in [c for c, m in styledata.rangeposcolumns if m & (self.mask_dmin | self.mask_dmax | self.mask_d)]:
261 styledata.rangeposdeltacolumns[column][self.mask_value] = data, index
262 if column in ["d" + c + "min" for c, m in styledata.rangeposcolumns if m & self.mask_dmin]:
263 styledata.rangeposdeltacolumns[column[1:-3]][self.mask_dmin] = data, index
264 if column in ["d" + c + "max" for c, m in styledata.rangeposcolumns if m & self.mask_dmax]:
265 styledata.rangeposdeltacolumns[column[1:-3]][self.mask_dmax] = data, index
266 if column in ["d" + c for c, m in styledata.rangeposcolumns if m & self.mask_d]:
267 styledata.rangeposdeltacolumns[column[1:]][self.mask_d] = data, index
269 # delta handling: process rangeposdeltacolumns
270 for c, d in styledata.rangeposdeltacolumns.items():
271 if d.has_key(self.mask_value):
272 for k in d.keys():
273 if k != self.mask_value:
274 if k & (self.mask_dmin | self.mask_d):
275 graph.axes[c].adjustrange(d[self.mask_value][0], d[self.mask_value][1],
276 deltamindata=d[k][0], deltaminindex=d[k][1])
277 if k & (self.mask_dmax | self.mask_d):
278 graph.axes[c].adjustrange(d[self.mask_value][0], d[self.mask_value][1],
279 deltamaxdata=d[k][0], deltamaxindex=d[k][1])
280 del d[k]
282 def initdrawpoints(self, styledata, graph):
283 styledata.vrange = [[None for x in range(2)] for y in styledata.rangeposcolumns + styledata.vrangemissing]
284 styledata.rangepostmplist = [[column, mask, index, graph.axes[column]] # temporarily used by drawpoint only
285 for index, (column, mask) in enumerate(styledata.rangeposcolumns)]
286 for missing in styledata.vrangemissing:
287 for rangepostmp in styledata.rangepostmplist:
288 if rangepostmp[2] >= missing:
289 rangepostmp[2] += 1
291 def drawpoint(self, styledata, graph):
292 for column, mask, index, axis in styledata.rangepostmplist:
293 try:
294 if mask & self.mask_min:
295 styledata.vrange[index][0] = axis.convert(styledata.point[column + "min"])
296 if mask & self.mask_dmin:
297 styledata.vrange[index][0] = axis.convert(styledata.point[column] - styledata.point["d" + column + "min"])
298 if mask & self.mask_d:
299 styledata.vrange[index][0] = axis.convert(styledata.point[column] - styledata.point["d" + column])
300 except (ArithmeticError, ValueError, TypeError):
301 styledata.vrange[index][0] = None
302 try:
303 if mask & self.mask_max:
304 styledata.vrange[index][1] = axis.convert(styledata.point[column + "max"])
305 if mask & self.mask_dmax:
306 styledata.vrange[index][1] = axis.convert(styledata.point[column] + styledata.point["d" + column + "max"])
307 if mask & self.mask_d:
308 styledata.vrange[index][1] = axis.convert(styledata.point[column] + styledata.point["d" + column])
309 except (ArithmeticError, ValueError, TypeError):
310 styledata.vrange[index][1] = None
312 # some range checks for data consistency
313 if (styledata.vrange[index][0] is not None and styledata.vrange[index][1] is not None and
314 styledata.vrange[index][0] > styledata.vrange[index][1] + self.epsilon):
315 raise ValueError("negative errorbar range")
316 #if (styledata.vrange[index][0] is not None and styledata.vpos[index] is not None and
317 # styledata.vrange[index][0] > styledata.vpos[index] + self.epsilon):
318 # raise ValueError("negative minimum errorbar")
319 #if (styledata.vrange[index][1] is not None and styledata.vpos[index] is not None and
320 # styledata.vrange[index][1] < styledata.vpos[index] - self.epsilon):
321 # raise ValueError("negative maximum errorbar")
324 provider["vrange"] = provider["vrangemissing"] = _range()
327 def _crosssymbol(c, x_pt, y_pt, size_pt, attrs):
328 c.draw(path.path(path.moveto_pt(x_pt-0.5*size_pt, y_pt-0.5*size_pt),
329 path.lineto_pt(x_pt+0.5*size_pt, y_pt+0.5*size_pt),
330 path.moveto_pt(x_pt-0.5*size_pt, y_pt+0.5*size_pt),
331 path.lineto_pt(x_pt+0.5*size_pt, y_pt-0.5*size_pt)), attrs)
333 def _plussymbol(c, x_pt, y_pt, size_pt, attrs):
334 c.draw(path.path(path.moveto_pt(x_pt-0.707106781*size_pt, y_pt),
335 path.lineto_pt(x_pt+0.707106781*size_pt, y_pt),
336 path.moveto_pt(x_pt, y_pt-0.707106781*size_pt),
337 path.lineto_pt(x_pt, y_pt+0.707106781*size_pt)), attrs)
339 def _squaresymbol(c, x_pt, y_pt, size_pt, attrs):
340 c.draw(path.path(path.moveto_pt(x_pt-0.5*size_pt, y_pt-0.5*size_pt),
341 path.lineto_pt(x_pt+0.5*size_pt, y_pt-0.5*size_pt),
342 path.lineto_pt(x_pt+0.5*size_pt, y_pt+0.5*size_pt),
343 path.lineto_pt(x_pt-0.5*size_pt, y_pt+0.5*size_pt),
344 path.closepath()), attrs)
346 def _trianglesymbol(c, x_pt, y_pt, size_pt, attrs):
347 c.draw(path.path(path.moveto_pt(x_pt-0.759835685*size_pt, y_pt-0.438691337*size_pt),
348 path.lineto_pt(x_pt+0.759835685*size_pt, y_pt-0.438691337*size_pt),
349 path.lineto_pt(x_pt, y_pt+0.877382675*size_pt),
350 path.closepath()), attrs)
352 def _circlesymbol(c, x_pt, y_pt, size_pt, attrs):
353 c.draw(path.path(path.arc_pt(x_pt, y_pt, 0.564189583*size_pt, 0, 360),
354 path.closepath()), attrs)
356 def _diamondsymbol(c, x_pt, y_pt, size_pt, attrs):
357 c.draw(path.path(path.moveto_pt(x_pt-0.537284965*size_pt, y_pt),
358 path.lineto_pt(x_pt, y_pt-0.930604859*size_pt),
359 path.lineto_pt(x_pt+0.537284965*size_pt, y_pt),
360 path.lineto_pt(x_pt, y_pt+0.930604859*size_pt),
361 path.closepath()), attrs)
364 class _styleneedingpointpos(_style):
366 need = ["vposmissing"]
368 def columns(self, styledata, graph, columns):
369 if len(styledata.vposmissing):
370 raise ValueError("position columns incomplete")
371 return []
374 class symbol(_styleneedingpointpos):
376 need = ["vpos", "vposmissing", "vposvalid"]
378 # insert symbols
379 # note, that statements like cross = _crosssymbol are
380 # invalid, since the would lead to unbound methods, but
381 # a single entry changeable list does the trick
382 cross = attr.changelist([_crosssymbol])
383 plus = attr.changelist([_plussymbol])
384 square = attr.changelist([_squaresymbol])
385 triangle = attr.changelist([_trianglesymbol])
386 circle = attr.changelist([_circlesymbol])
387 diamond = attr.changelist([_diamondsymbol])
389 changecross = attr.changelist([_crosssymbol, _plussymbol, _squaresymbol, _trianglesymbol, _circlesymbol, _diamondsymbol])
390 changeplus = attr.changelist([_plussymbol, _squaresymbol, _trianglesymbol, _circlesymbol, _diamondsymbol, cross])
391 changesquare = attr.changelist([_squaresymbol, _trianglesymbol, _circlesymbol, _diamondsymbol, cross, _plussymbol])
392 changetriangle = attr.changelist([_trianglesymbol, _circlesymbol, _diamondsymbol, cross, _plussymbol, _squaresymbol])
393 changecircle = attr.changelist([_circlesymbol, _diamondsymbol, cross, _plussymbol, _squaresymbol, _trianglesymbol])
394 changediamond = attr.changelist([_diamondsymbol, cross, _plussymbol, _squaresymbol, _trianglesymbol, _circlesymbol])
395 changesquaretwice = attr.changelist([_squaresymbol, _squaresymbol, _trianglesymbol, _trianglesymbol, _circlesymbol, _circlesymbol, _diamondsymbol, _diamondsymbol])
396 changetriangletwice = attr.changelist([_trianglesymbol, _trianglesymbol, _circlesymbol, _circlesymbol, _diamondsymbol, _diamondsymbol, _squaresymbol, _squaresymbol])
397 changecircletwice = attr.changelist([_circlesymbol, _circlesymbol, _diamondsymbol, _diamondsymbol, _squaresymbol, _squaresymbol, _trianglesymbol, _trianglesymbol])
398 changediamondtwice = attr.changelist([_diamondsymbol, _diamondsymbol, _squaresymbol, _squaresymbol, _trianglesymbol, _trianglesymbol, _circlesymbol, _circlesymbol])
400 changestrokedfilled = attr.changelist([deco.stroked, deco.filled])
401 changefilledstroked = attr.changelist([deco.filled, deco.stroked])
403 defaultsymbolattrs = [deco.stroked]
405 def __init__(self, symbol=changecross, size=0.2*unit.v_cm, symbolattrs=[]):
406 self.symbol = symbol
407 self.size = size
408 self.symbolattrs = symbolattrs
410 def selectstyle(self, styledata, graph, selectindex, selecttotal):
411 styledata.symbol = attr.selectattr(self.symbol, selectindex, selecttotal)
412 styledata.size_pt = unit.topt(attr.selectattr(self.size, selectindex, selecttotal))
413 if self.symbolattrs is not None:
414 styledata.symbolattrs = attr.selectattrs(self.defaultsymbolattrs + self.symbolattrs, selectindex, selecttotal)
415 else:
416 styledata.symbolattrs = None
418 def initdrawpoints(self, styledata, graph):
419 styledata.symbolcanvas = graph.insert(canvas.canvas())
421 def drawpoint(self, styledata, graph):
422 if styledata.vposvalid and styledata.symbolattrs is not None:
423 xpos, ypos = graph.vpos_pt(*styledata.vpos)
424 styledata.symbol(styledata.symbolcanvas, xpos, ypos, styledata.size_pt, styledata.symbolattrs)
426 def key_pt(self, styledata, graph, x_pt, y_pt, width_pt, height_pt, dy_pt):
427 if styledata.symbolattrs is not None:
428 styledata.symbol(graph, x_pt+0.5*width_pt, y_pt+0.5*height_pt, styledata.size_pt, styledata.symbolattrs)
429 return 1
432 class line(_styleneedingpointpos):
434 need = ["vpos", "vposmissing", "vposavailable", "vposvalid"]
436 changelinestyle = attr.changelist([style.linestyle.solid,
437 style.linestyle.dashed,
438 style.linestyle.dotted,
439 style.linestyle.dashdotted])
441 defaultlineattrs = [changelinestyle]
443 def __init__(self, lineattrs=[]):
444 self.lineattrs = lineattrs
446 def selectstyle(self, styledata, graph, selectindex, selecttotal):
447 styledata.lineattrs = attr.selectattrs(self.defaultlineattrs + self.lineattrs, selectindex, selecttotal)
449 def initdrawpoints(self, styledata, graph):
450 if styledata.lineattrs is not None:
451 styledata.linecanvas = graph.insert(canvas.canvas())
452 styledata.linecanvas.set(styledata.lineattrs)
453 styledata.path = path.path()
454 styledata.linebasepoints = []
455 styledata.lastvpos = None
457 def addpointstopath(self, styledata):
458 # add baselinepoints to styledata.path
459 if len(styledata.linebasepoints) > 1:
460 styledata.path.append(path.moveto_pt(*styledata.linebasepoints[0]))
461 if len(styledata.linebasepoints) > 2:
462 styledata.path.append(path.multilineto_pt(styledata.linebasepoints[1:]))
463 else:
464 styledata.path.append(path.lineto_pt(*styledata.linebasepoints[1]))
465 styledata.linebasepoints = []
467 def drawpoint(self, styledata, graph):
468 # append linebasepoints
469 if styledata.vposavailable:
470 if len(styledata.linebasepoints):
471 # the last point was inside the graph
472 if styledata.vposvalid: # shortcut for the common case
473 styledata.linebasepoints.append(graph.vpos_pt(*styledata.vpos))
474 else:
475 # cut end
476 cut = 1
477 for vstart, vend in zip(styledata.lastvpos, styledata.vpos):
478 newcut = None
479 if vend > 1:
480 # 1 = vstart + (vend - vstart) * cut
481 try:
482 newcut = (1 - vstart)/(vend - vstart)
483 except ArithmeticError:
484 break
485 if vend < 0:
486 # 0 = vstart + (vend - vstart) * cut
487 try:
488 newcut = - vstart/(vend - vstart)
489 except ArithmeticError:
490 break
491 if newcut is not None and newcut < cut:
492 cut = newcut
493 else:
494 cutvpos = []
495 for vstart, vend in zip(styledata.lastvpos, styledata.vpos):
496 cutvpos.append(vstart + (vend - vstart) * cut)
497 styledata.linebasepoints.append(graph.vpos_pt(*cutvpos))
498 self.addpointstopath(styledata)
499 else:
500 # the last point was outside the graph
501 if styledata.lastvpos is not None:
502 if styledata.vposvalid:
503 # cut beginning
504 cut = 0
505 for vstart, vend in zip(styledata.lastvpos, styledata.vpos):
506 newcut = None
507 if vstart > 1:
508 # 1 = vstart + (vend - vstart) * cut
509 try:
510 newcut = (1 - vstart)/(vend - vstart)
511 except ArithmeticError:
512 break
513 if vstart < 0:
514 # 0 = vstart + (vend - vstart) * cut
515 try:
516 newcut = - vstart/(vend - vstart)
517 except ArithmeticError:
518 break
519 if newcut is not None and newcut > cut:
520 cut = newcut
521 else:
522 cutvpos = []
523 for vstart, vend in zip(styledata.lastvpos, styledata.vpos):
524 cutvpos.append(vstart + (vend - vstart) * cut)
525 styledata.linebasepoints.append(graph.vpos_pt(*cutvpos))
526 styledata.linebasepoints.append(graph.vpos_pt(*styledata.vpos))
527 else:
528 # sometimes cut beginning and end
529 cutfrom = 0
530 cutto = 1
531 for vstart, vend in zip(styledata.lastvpos, styledata.vpos):
532 newcutfrom = None
533 if vstart > 1:
534 if vend > 1:
535 break
536 # 1 = vstart + (vend - vstart) * cutfrom
537 try:
538 newcutfrom = (1 - vstart)/(vend - vstart)
539 except ArithmeticError:
540 break
541 if vstart < 0:
542 if vend < 0:
543 break
544 # 0 = vstart + (vend - vstart) * cutfrom
545 try:
546 newcutfrom = - vstart/(vend - vstart)
547 except ArithmeticError:
548 break
549 if newcutfrom is not None and newcutfrom > cutfrom:
550 cutfrom = newcutfrom
551 newcutto = None
552 if vend > 1:
553 # 1 = vstart + (vend - vstart) * cutto
554 try:
555 newcutto = (1 - vstart)/(vend - vstart)
556 except ArithmeticError:
557 break
558 if vend < 0:
559 # 0 = vstart + (vend - vstart) * cutto
560 try:
561 newcutto = - vstart/(vend - vstart)
562 except ArithmeticError:
563 break
564 if newcutto is not None and newcutto < cutto:
565 cutto = newcutto
566 else:
567 if cutfrom < cutto:
568 cutfromvpos = []
569 cuttovpos = []
570 for vstart, vend in zip(styledata.lastvpos, styledata.vpos):
571 cutfromvpos.append(vstart + (vend - vstart) * cutfrom)
572 cuttovpos.append(vstart + (vend - vstart) * cutto)
573 styledata.linebasepoints.append(graph.vpos_pt(*cutfromvpos))
574 styledata.linebasepoints.append(graph.vpos_pt(*cuttovpos))
575 self.addpointstopath(styledata)
576 styledata.lastvpos = styledata.vpos[:]
577 else:
578 if len(styledata.linebasepoints) > 1:
579 self.addpointstopath(styledata)
580 styledata.lastvpos = None
582 def donedrawpoints(self, styledata, graph):
583 if len(styledata.linebasepoints) > 1:
584 self.addpointstopath(styledata)
585 if styledata.lineattrs is not None and len(styledata.path.path):
586 styledata.linecanvas.stroke(styledata.path)
588 def key_pt(self, styledata, graph, x_pt, y_pt, width_pt, height_pt, dy_pt):
589 if styledata.lineattrs is not None:
590 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)
591 return 1
594 class errorbar(_style):
596 need = ["vpos", "vposmissing", "vposavailable", "vposvalid", "vrange", "vrangemissing"]
598 defaulterrorbarattrs = []
600 def __init__(self, size=0.1*unit.v_cm,
601 errorbarattrs=[],
602 epsilon=1e-10):
603 self.size = size
604 self.errorbarattrs = errorbarattrs
605 self.epsilon = epsilon
607 def columns(self, styledata, graph, columns):
608 for i in styledata.vposmissing:
609 if i in styledata.vrangemissing:
610 raise ValueError("position and range for a graph dimension missing")
611 return []
613 def selectstyle(self, styledata, graph, selectindex, selecttotal):
614 styledata.errorsize_pt = unit.topt(attr.selectattr(self.size, selectindex, selecttotal))
615 styledata.errorbarattrs = attr.selectattrs(self.defaulterrorbarattrs + self.errorbarattrs, selectindex, selecttotal)
617 def initdrawpoints(self, styledata, graph):
618 if styledata.errorbarattrs is not None:
619 styledata.errorbarcanvas = graph.insert(canvas.canvas())
620 styledata.errorbarcanvas.set(styledata.errorbarattrs)
621 styledata.dimensionlist = range(len(styledata.vpos))
623 def drawpoint(self, styledata, graph):
624 if styledata.errorbarattrs is None:
625 return
626 for i in styledata.dimensionlist:
627 for j in styledata.dimensionlist:
628 if (i != j and
629 (styledata.vpos[j] is None or
630 styledata.vpos[j] < -self.epsilon or
631 styledata.vpos[j] > 1+self.epsilon)):
632 break
633 else:
634 if ((styledata.vrange[i][0] is None and styledata.vpos[i] is None) or
635 (styledata.vrange[i][1] is None and styledata.vpos[i] is None) or
636 (styledata.vrange[i][0] is None and styledata.vrange[i][1] is None)):
637 continue
638 vminpos = styledata.vpos[:]
639 if styledata.vrange[i][0] is not None:
640 vminpos[i] = styledata.vrange[i][0]
641 mincap = 1
642 else:
643 mincap = 0
644 if vminpos[i] > 1+self.epsilon:
645 continue
646 if vminpos[i] < -self.epsilon:
647 vminpos[i] = 0
648 mincap = 0
649 vmaxpos = styledata.vpos[:]
650 if styledata.vrange[i][1] is not None:
651 vmaxpos[i] = styledata.vrange[i][1]
652 maxcap = 1
653 else:
654 maxcap = 0
655 if vmaxpos[i] < -self.epsilon:
656 continue
657 if vmaxpos[i] > 1+self.epsilon:
658 vmaxpos[i] = 1
659 maxcap = 0
660 styledata.errorbarcanvas.stroke(graph.vgeodesic(*(vminpos + vmaxpos)))
661 for j in styledata.dimensionlist:
662 if i != j:
663 if mincap:
664 styledata.errorbarcanvas.stroke(graph.vcap_pt(j, styledata.errorsize_pt, *vminpos))
665 if maxcap:
666 styledata.errorbarcanvas.stroke(graph.vcap_pt(j, styledata.errorsize_pt, *vmaxpos))
669 class text(_styleneedingpointpos):
671 need = ["vpos", "vposmissing", "vposvalid"]
673 defaulttextattrs = [textmodule.halign.center, textmodule.vshift.mathaxis]
675 def __init__(self, textdx=0*unit.v_cm, textdy=0.3*unit.v_cm, textattrs=[], **kwargs):
676 self.textdx = textdx
677 self.textdy = textdy
678 self.textattrs = textattrs
680 def columns(self, styledata, graph, columns):
681 if "text" not in columns:
682 raise ValueError("text missing")
683 return ["text"] + _styleneedingpointpos.columns(self, styledata, graph, columns)
685 def selectstyle(self, styledata, graph, selectindex, selecttotal):
686 if self.textattrs is not None:
687 styledata.textattrs = attr.selectattrs(self.defaulttextattrs + self.textattrs, selectindex, selecttotal)
688 else:
689 styledata.textattrs = None
691 def initdrawpoints(self, styledata, grap):
692 styledata.textdx_pt = unit.topt(self.textdx)
693 styledata.textdy_pt = unit.topt(self.textdy)
695 def drawpoint(self, styledata, graph):
696 if styledata.textattrs is not None and styledata.vposvalid:
697 x_pt, y_pt = graph.vpos_pt(*styledata.vpos)
698 try:
699 text = str(styledata.point["text"])
700 except:
701 pass
702 else:
703 graph.text_pt(x_pt + styledata.textdx_pt, y_pt + styledata.textdy_pt, text, styledata.textattrs)
706 class arrow(_styleneedingpointpos):
708 need = ["vpos", "vposmissing", "vposvalid"]
710 defaultlineattrs = []
711 defaultarrowattrs = []
713 def __init__(self, linelength=0.25*unit.v_cm, arrowsize=0.15*unit.v_cm, lineattrs=[], arrowattrs=[], epsilon=1e-10):
714 self.linelength = linelength
715 self.arrowsize = arrowsize
716 self.lineattrs = lineattrs
717 self.arrowattrs = arrowattrs
718 self.epsilon = epsilon
720 def columns(self, styledata, graph, columns):
721 if len(graph.axesnames) != 2:
722 raise ValueError("arrow style restricted on two-dimensional graphs")
723 if "size" not in columns:
724 raise ValueError("size missing")
725 if "angle" not in columns:
726 raise ValueError("angle missing")
727 return ["size", "angle"] + _styleneedingpointpos.columns(self, styledata, graph, columns)
729 def selectstyle(self, styledata, graph, selectindex, selecttotal):
730 if self.lineattrs is not None:
731 styledata.lineattrs = attr.selectattrs(self.defaultlineattrs + self.lineattrs, selectindex, selecttotal)
732 else:
733 styledata.lineattrs = None
734 if self.arrowattrs is not None:
735 styledata.arrowattrs = attr.selectattrs(self.defaultarrowattrs + self.arrowattrs, selectindex, selecttotal)
736 else:
737 styledata.arrowattrs = None
739 def initdrawpoints(self, styledata, graph):
740 styledata.arrowcanvas = graph.insert(canvas.canvas())
742 def drawpoint(self, styledata, graph):
743 if styledata.lineattrs is not None and styledata.arrowattrs is not None and styledata.vposvalid:
744 linelength_pt = unit.topt(self.linelength)
745 x_pt, y_pt = graph.vpos_pt(*styledata.vpos)
746 try:
747 angle = styledata.point["angle"] + 0.0
748 size = styledata.point["size"] + 0.0
749 except:
750 pass
751 else:
752 if styledata.point["size"] > self.epsilon:
753 dx = math.cos(angle*math.pi/180)
754 dy = math.sin(angle*math.pi/180)
755 x1 = x_pt-0.5*dx*linelength_pt*size
756 y1 = y_pt-0.5*dy*linelength_pt*size
757 x2 = x_pt+0.5*dx*linelength_pt*size
758 y2 = y_pt+0.5*dy*linelength_pt*size
759 styledata.arrowcanvas.stroke(path.line_pt(x1, y1, x2, y2), styledata.lineattrs +
760 [deco.earrow(styledata.arrowattrs, size=self.arrowsize*size)])
762 def key_pt(self, styledata, graph, x_pt, y_pt, width_pt, height_pt, dy_pt):
763 raise "TODO"
766 class rect(_style):
768 need = ["vrange", "vrangeminmissing", "vrangemaxmissing"]
770 def __init__(self, palette=color.palette.Gray):
771 self.palette = palette
773 def columns(self, styledata, graph, columns):
774 if len(graph.axesnames) != 2:
775 raise TypeError("arrow style restricted on two-dimensional graphs")
776 if "color" not in columns:
777 raise ValueError("color missing")
778 if len(styledata.vrangeminmissing) + len(styledata.vrangemaxmissing):
779 raise ValueError("range columns incomplete")
780 return ["color"]
782 def initdrawpoints(self, styledata, graph):
783 styledata.rectcanvas = graph.insert(canvas.canvas())
784 styledata.lastcolorvalue = None
786 def drawpoint(self, styledata, graph):
787 xvmin = styledata.vrange[0][0]
788 xvmax = styledata.vrange[0][1]
789 yvmin = styledata.vrange[1][0]
790 yvmax = styledata.vrange[1][1]
791 if (xvmin is not None and xvmin < 1 and
792 xvmax is not None and xvmax > 0 and
793 yvmin is not None and yvmin < 1 and
794 yvmax is not None and yvmax > 0):
795 if xvmin < 0:
796 xvmin = 0
797 elif xvmax > 1:
798 xvmax = 1
799 if yvmin < 0:
800 yvmin = 0
801 elif yvmax > 1:
802 yvmax = 1
803 p = graph.vgeodesic(xvmin, yvmin, xvmax, yvmin)
804 p.append(graph.vgeodesic_el(xvmax, yvmin, xvmax, yvmax))
805 p.append(graph.vgeodesic_el(xvmax, yvmax, xvmin, yvmax))
806 p.append(graph.vgeodesic_el(xvmin, yvmax, xvmin, yvmin))
807 p.append(path.closepath())
808 colorvalue = styledata.point["color"]
809 try:
810 if colorvalue != styledata.lastcolorvalue:
811 styledata.rectcanvas.set([self.palette.getcolor(colorvalue)])
812 except:
813 pass
814 else:
815 styledata.rectcanvas.fill(p)
817 def key_pt(self, styledata, graph, x_pt, y_pt, width_pt, height_pt, dy_pt):
818 raise "TODO"
821 class barpos(_style):
823 provide = ["vpos", "vposmissing", "vposavailable", "vposvalid", "barcolumns", "barvalueindex", "vbarpos"]
825 def __init__(self, fromvalue=None, subindex=0, subnames=None, epsilon=1e-10):
826 # TODO: vpos configuration ...
827 self.fromvalue = fromvalue
828 self.subnames = subnames
829 self.subindex = subindex
830 self.epsilon = epsilon
832 def columns(self, styledata, graph, columns):
833 # TODO: we might check whether barcolumns/barvalueindex is already available
834 styledata.barcolumns = []
835 styledata.barvalueindex = None
836 for dimension, axisnames in enumerate(graph.axesnames):
837 for axisname in axisnames:
838 if axisname in columns:
839 if styledata.barvalueindex is not None:
840 raise ValueError("multiple values")
841 valuecolumns = [axisname]
842 while 1:
843 stackedvalue = "%sstack%i" % (axisname, len(valuecolumns))
844 if stackedvalue in columns:
845 valuecolumns.append(stackedvalue)
846 else:
847 break
848 styledata.barcolumns.append(valuecolumns)
849 styledata.barvalueindex = dimension
850 break
851 else:
852 found = 0
853 for axisname in axisnames:
854 if (axisname + "name") in columns:
855 if found > 1:
856 raise ValueError("multiple names")
857 found = 1
858 styledata.barcolumns.append(axisname + "name")
859 if not found:
860 raise ValueError("value/name missing")
861 if styledata.barvalueindex is None:
862 raise ValueError("missing value")
863 if self.subindex >= styledata.barvalueindex:
864 styledata.barpossubindex = self.subindex + 1
865 else:
866 styledata.barpossubindex = self.subindex
867 styledata.vposmissing = []
868 return styledata.barcolumns[styledata.barvalueindex] + [styledata.barcolumns[i] for i in range(len(styledata.barcolumns)) if i != styledata.barvalueindex]
870 def selectstyle(self, styledata, graph, selectindex, selecttotal):
871 if selecttotal == 1:
872 if self.subnames is not None:
873 raise ValueError("subnames set for single-bar data")
874 styledata.barpossubname = []
875 else:
876 if self.subnames is not None:
877 styledata.barpossubname = [self.subnames[selectindex]]
878 else:
879 styledata.barpossubname = [selectindex]
881 def adjustaxis(self, styledata, graph, column, data, index):
882 if column in styledata.barcolumns[styledata.barvalueindex]:
883 graph.axes[styledata.barcolumns[styledata.barvalueindex][0]].adjustrange(data, index)
884 if self.fromvalue is not None and column == styledata.barcolumns[styledata.barvalueindex][0]:
885 graph.axes[styledata.barcolumns[styledata.barvalueindex][0]].adjustrange([self.fromvalue], None)
886 else:
887 try:
888 i = styledata.barcolumns.index(column)
889 except ValueError:
890 pass
891 else:
892 if i == styledata.barpossubindex:
893 graph.axes[column[:-4]].adjustrange(data, index, styledata.barpossubname)
894 else:
895 graph.axes[column[:-4]].adjustrange(data, index)
897 def initdrawpoints(self, styledata, graph):
898 styledata.vpos = [None]*(len(styledata.barcolumns))
899 styledata.vbarpos = [[None for i in range(2)] for x in styledata.barcolumns]
901 if self.fromvalue is not None:
902 vfromvalue = graph.axes[styledata.barcolumns[styledata.barvalueindex][0]].convert(self.fromvalue)
903 if vfromvalue < 0:
904 vfromvalue = 0
905 if vfromvalue > 1:
906 vfromvalue = 1
907 else:
908 vfromvalue = 0
910 styledata.vbarpos[styledata.barvalueindex] = [vfromvalue] + [None]*len(styledata.barcolumns[styledata.barvalueindex])
912 def drawpoint(self, styledata, graph):
913 styledata.vposavailable = styledata.vposvalid = 1
914 for i, barname in enumerate(styledata.barcolumns):
915 if i == styledata.barvalueindex:
916 for j, valuename in enumerate(styledata.barcolumns[styledata.barvalueindex]):
917 try:
918 styledata.vbarpos[i][j+1] = graph.axes[styledata.barcolumns[i][0]].convert(styledata.point[valuename])
919 except (ArithmeticError, ValueError, TypeError):
920 styledata.vbarpos[i][j+1] = None
921 styledata.vpos[i] = styledata.vbarpos[i][-1]
922 else:
923 for j in range(2):
924 try:
925 if i == styledata.barpossubindex:
926 styledata.vbarpos[i][j] = graph.axes[barname[:-4]].convert(([styledata.point[barname]] + styledata.barpossubname + [j]))
927 else:
928 styledata.vbarpos[i][j] = graph.axes[barname[:-4]].convert((styledata.point[barname], j))
929 except (ArithmeticError, ValueError, TypeError):
930 styledata.vbarpos[i][j] = None
931 try:
932 styledata.vpos[i] = 0.5*(styledata.vbarpos[i][0]+styledata.vbarpos[i][1])
933 except (ArithmeticError, ValueError, TypeError):
934 styledata.vpos[i] = None
935 if styledata.vpos[i] is None:
936 styledata.vposavailable = styledata.vposvalid = 0
937 elif styledata.vpos[i] < -self.epsilon or styledata.vpos[i] > 1+self.epsilon:
938 styledata.vposvalid = 0
940 provider["barcolumns"] = provider["barvalueindex"] = provider["barpos"] = barpos()
943 class bar(_style):
945 need = ["barvalueindex", "vbarpos"]
947 defaultfrompathattrs = []
948 defaultbarattrs = [color.palette.Rainbow, deco.stroked([color.gray.black])]
950 def __init__(self, frompathattrs=[], barattrs=[], subnames=None, multikey=0, epsilon=1e-10):
951 self.frompathattrs = frompathattrs
952 self.barattrs = barattrs
953 self.subnames = subnames
954 self.multikey = multikey
955 self.epsilon = epsilon
957 def selectstyle(self, styledata, graph, selectindex, selecttotal):
958 if selectindex:
959 styledata.frompathattrs = None
960 else:
961 styledata.frompathattrs = self.defaultfrompathattrs + self.frompathattrs
962 if selecttotal > 1:
963 if self.barattrs is not None:
964 styledata.barattrs = attr.selectattrs(self.defaultbarattrs + self.barattrs, selectindex, selecttotal)
965 else:
966 styledata.barattrs = None
967 else:
968 styledata.barattrs = self.defaultbarattrs + self.barattrs
969 styledata.barselectindex = selectindex
970 styledata.barselecttotal = selecttotal
971 if styledata.barselecttotal != 1 and self.subnames is not None:
972 raise ValueError("subnames not allowed when iterating over bars")
974 def initdrawpoints(self, styledata, graph):
975 styledata.bartmpvpos = [None]*4
976 l = len(styledata.vbarpos[styledata.barvalueindex])
977 if l > 1:
978 styledata.bartmplist = []
979 for i in xrange(1, l):
980 barattrs = attr.selectattrs(styledata.barattrs, i-1, l)
981 if barattrs is not None:
982 styledata.bartmplist.append((i, barattrs))
983 else:
984 styledata.bartmplist = [(1, styledata.barattrs)]
985 if styledata.frompathattrs is not None:
986 vfromvalue = styledata.vbarpos[styledata.barvalueindex][0]
987 if vfromvalue > self.epsilon and vfromvalue < 1 - self.epsilon:
988 if styledata.barvalueindex:
989 p = graph.vgeodesic(0, vfromvalue, 1, vfromvalue)
990 else:
991 p = graph.vgeodesic(vfromvalue, 0, vfromvalue, 1)
992 graph.stroke(p, styledata.frompathattrs)
993 styledata.barcanvas = graph.insert(canvas.canvas())
995 def drawpoint(self, styledata, graph):
996 if styledata.barattrs is not None:
997 for i, barattrs in styledata.bartmplist:
998 if None not in styledata.vbarpos[1-styledata.barvalueindex]+styledata.vbarpos[styledata.barvalueindex][i-1:i+1]:
999 styledata.bartmpvpos[1-styledata.barvalueindex] = styledata.vbarpos[1-styledata.barvalueindex][0]
1000 styledata.bartmpvpos[ styledata.barvalueindex] = styledata.vbarpos[styledata.barvalueindex][i-1]
1001 styledata.bartmpvpos[3-styledata.barvalueindex] = styledata.vbarpos[1-styledata.barvalueindex][0]
1002 styledata.bartmpvpos[2+styledata.barvalueindex] = styledata.vbarpos[styledata.barvalueindex][i]
1003 p = graph.vgeodesic(*styledata.bartmpvpos)
1004 styledata.bartmpvpos[1-styledata.barvalueindex] = styledata.vbarpos[1-styledata.barvalueindex][0]
1005 styledata.bartmpvpos[ styledata.barvalueindex] = styledata.vbarpos[styledata.barvalueindex][i]
1006 styledata.bartmpvpos[3-styledata.barvalueindex] = styledata.vbarpos[1-styledata.barvalueindex][1]
1007 styledata.bartmpvpos[2+styledata.barvalueindex] = styledata.vbarpos[styledata.barvalueindex][i]
1008 p.append(graph.vgeodesic_el(*styledata.bartmpvpos))
1009 styledata.bartmpvpos[1-styledata.barvalueindex] = styledata.vbarpos[1-styledata.barvalueindex][1]
1010 styledata.bartmpvpos[ styledata.barvalueindex] = styledata.vbarpos[styledata.barvalueindex][i]
1011 styledata.bartmpvpos[3-styledata.barvalueindex] = styledata.vbarpos[1-styledata.barvalueindex][1]
1012 styledata.bartmpvpos[2+styledata.barvalueindex] = styledata.vbarpos[styledata.barvalueindex][i-1]
1013 p.append(graph.vgeodesic_el(*styledata.bartmpvpos))
1014 styledata.bartmpvpos[1-styledata.barvalueindex] = styledata.vbarpos[1-styledata.barvalueindex][1]
1015 styledata.bartmpvpos[ styledata.barvalueindex] = styledata.vbarpos[styledata.barvalueindex][i-1]
1016 styledata.bartmpvpos[3-styledata.barvalueindex] = styledata.vbarpos[1-styledata.barvalueindex][0]
1017 styledata.bartmpvpos[2+styledata.barvalueindex] = styledata.vbarpos[styledata.barvalueindex][i-1]
1018 p.append(graph.vgeodesic_el(*styledata.bartmpvpos))
1019 p.append(path.closepath())
1020 styledata.barcanvas.fill(p, barattrs)
1022 def key_pt(self, styledata, c, x_pt, y_pt, width_pt, height_pt, dy_pt):
1023 if self.multikey:
1024 l = 0
1025 for i, barattrs in styledata.bartmplist:
1026 c.fill(path.rect_pt(x_pt, y_pt-l*dy_pt, width_pt, height_pt), barattrs)
1027 l += 1
1028 return l
1029 else:
1030 for i, barattrs in styledata.bartmplist:
1031 c.fill(path.rect_pt(x_pt+(i-1)*width_pt/styledata.bartmplist[-1][0], y_pt,
1032 width_pt/styledata.bartmplist[-1][0], height_pt), barattrs)
1033 return 1