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
27 from pyx
import attr
, deco
, style
, color
, unit
, canvas
, path
28 from pyx
import text
as textmodule
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."""
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."""
75 def adjustaxis(self
, styledata
, graph
, column
, data
, index
):
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."""
88 def initdrawpoints(self
, styledata
, graph
):
89 """Initialize drawing of data
91 This method might be used to initialize the drawing of data."""
94 def drawpoint(self
, styledata
, graph
):
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."""
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."""
109 def key_pt(self
, styledata
, graph
, x_pt
, y_pt
, width_pt
, height_pt
, dy_pt
):
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."""
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.
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
:
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
:
168 v
= axis
.convert(styledata
.point
[column
])
169 except (ArithmeticError, ValueError, TypeError):
170 styledata
.vposavailable
= styledata
.vposvalid
= 0
171 styledata
.vpos
[index
] = None
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"]
193 def __init__(self
, epsilon
=1e-10):
194 self
.epsilon
= epsilon
196 def columns(self
, styledata
, graph
, columns
):
197 def numberofbits(mask
):
201 return numberofbits(mask
>> 1) + 1
203 return numberofbits(mask
>> 1)
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
:
213 for column
in columns
:
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
:
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
)
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
)
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
):
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])
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
:
291 def drawpoint(self
, styledata
, graph
):
292 for column
, mask
, index
, axis
in styledata
.rangepostmplist
:
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
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")
374 class symbol(_styleneedingpointpos
):
376 need
= ["vpos", "vposmissing", "vposvalid"]
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
=[]):
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
)
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
)
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:]))
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
))
477 for vstart
, vend
in zip(styledata
.lastvpos
, styledata
.vpos
):
480 # 1 = vstart + (vend - vstart) * cut
482 newcut
= (1 - vstart
)/(vend
- vstart
)
483 except ArithmeticError:
486 # 0 = vstart + (vend - vstart) * cut
488 newcut
= - vstart
/(vend
- vstart
)
489 except ArithmeticError:
491 if newcut
is not None and newcut
< cut
:
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
)
500 # the last point was outside the graph
501 if styledata
.lastvpos
is not None:
502 if styledata
.vposvalid
:
505 for vstart
, vend
in zip(styledata
.lastvpos
, styledata
.vpos
):
508 # 1 = vstart + (vend - vstart) * cut
510 newcut
= (1 - vstart
)/(vend
- vstart
)
511 except ArithmeticError:
514 # 0 = vstart + (vend - vstart) * cut
516 newcut
= - vstart
/(vend
- vstart
)
517 except ArithmeticError:
519 if newcut
is not None and newcut
> cut
:
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
))
528 # sometimes cut beginning and end
531 for vstart
, vend
in zip(styledata
.lastvpos
, styledata
.vpos
):
536 # 1 = vstart + (vend - vstart) * cutfrom
538 newcutfrom
= (1 - vstart
)/(vend
- vstart
)
539 except ArithmeticError:
544 # 0 = vstart + (vend - vstart) * cutfrom
546 newcutfrom
= - vstart
/(vend
- vstart
)
547 except ArithmeticError:
549 if newcutfrom
is not None and newcutfrom
> cutfrom
:
553 # 1 = vstart + (vend - vstart) * cutto
555 newcutto
= (1 - vstart
)/(vend
- vstart
)
556 except ArithmeticError:
559 # 0 = vstart + (vend - vstart) * cutto
561 newcutto
= - vstart
/(vend
- vstart
)
562 except ArithmeticError:
564 if newcutto
is not None and newcutto
< cutto
:
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
[:]
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
)
594 class errorbar(_style
):
596 need
= ["vpos", "vposmissing", "vposavailable", "vposvalid", "vrange", "vrangemissing"]
598 defaulterrorbarattrs
= []
600 def __init__(self
, size
=0.1*unit
.v_cm
,
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")
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:
626 for i
in styledata
.dimensionlist
:
627 for j
in styledata
.dimensionlist
:
629 (styledata
.vpos
[j
] is None or
630 styledata
.vpos
[j
] < -self
.epsilon
or
631 styledata
.vpos
[j
] > 1+self
.epsilon
)):
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)):
638 vminpos
= styledata
.vpos
[:]
639 if styledata
.vrange
[i
][0] is not None:
640 vminpos
[i
] = styledata
.vrange
[i
][0]
644 if vminpos
[i
] > 1+self
.epsilon
:
646 if vminpos
[i
] < -self
.epsilon
:
649 vmaxpos
= styledata
.vpos
[:]
650 if styledata
.vrange
[i
][1] is not None:
651 vmaxpos
[i
] = styledata
.vrange
[i
][1]
655 if vmaxpos
[i
] < -self
.epsilon
:
657 if vmaxpos
[i
] > 1+self
.epsilon
:
660 styledata
.errorbarcanvas
.stroke(graph
.vgeodesic(*(vminpos
+ vmaxpos
)))
661 for j
in styledata
.dimensionlist
:
664 styledata
.errorbarcanvas
.stroke(graph
.vcap_pt(j
, styledata
.errorsize_pt
, *vminpos
))
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
):
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
)
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
)
699 text
= str(styledata
.point
["text"])
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
)
733 styledata
.lineattrs
= None
734 if self
.arrowattrs
is not None:
735 styledata
.arrowattrs
= attr
.selectattrs(self
.defaultarrowattrs
+ self
.arrowattrs
, selectindex
, selecttotal
)
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
)
747 angle
= styledata
.point
["angle"] + 0.0
748 size
= styledata
.point
["size"] + 0.0
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
):
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")
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):
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"]
810 if colorvalue
!= styledata
.lastcolorvalue
:
811 styledata
.rectcanvas
.set([self
.palette
.getcolor(colorvalue
)])
815 styledata
.rectcanvas
.fill(p
)
817 def key_pt(self
, styledata
, graph
, x_pt
, y_pt
, width_pt
, height_pt
, dy_pt
):
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
]
843 stackedvalue
= "%sstack%i" % (axisname
, len(valuecolumns
))
844 if stackedvalue
in columns
:
845 valuecolumns
.append(stackedvalue
)
848 styledata
.barcolumns
.append(valuecolumns
)
849 styledata
.barvalueindex
= dimension
853 for axisname
in axisnames
:
854 if (axisname
+ "name") in columns
:
856 raise ValueError("multiple names")
858 styledata
.barcolumns
.append(axisname
+ "name")
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
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
):
872 if self
.subnames
is not None:
873 raise ValueError("subnames set for single-bar data")
874 styledata
.barpossubname
= []
876 if self
.subnames
is not None:
877 styledata
.barpossubname
= [self
.subnames
[selectindex
]]
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)
888 i
= styledata
.barcolumns
.index(column
)
892 if i
== styledata
.barpossubindex
:
893 graph
.axes
[column
[:-4]].adjustrange(data
, index
, styledata
.barpossubname
)
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
)
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
]):
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]
925 if i
== styledata
.barpossubindex
:
926 styledata
.vbarpos
[i
][j
] = graph
.axes
[barname
[:-4]].convert(([styledata
.point
[barname
]] + styledata
.barpossubname
+ [j
]))
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
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()
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
):
959 styledata
.frompathattrs
= None
961 styledata
.frompathattrs
= self
.defaultfrompathattrs
+ self
.frompathattrs
963 if self
.barattrs
is not None:
964 styledata
.barattrs
= attr
.selectattrs(self
.defaultbarattrs
+ self
.barattrs
, selectindex
, selecttotal
)
966 styledata
.barattrs
= None
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
])
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
))
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
)
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
):
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
)
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
)