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
):
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."""
118 # Provider is a dictionary, which maps styledata variable names
119 # to default styles, which provide a default way to create the
120 # corresponding styledata variable. Entries in the provider
121 # dictionary should not depend on other styles, thus the need
122 # list should be empty.
129 provide
= ["vpos", "vposmissing", "vposavailable", "vposvalid"]
131 def __init__(self
, epsilon
=1e-10):
132 self
.epsilon
= epsilon
134 def columns(self
, styledata
, graph
, columns
):
135 styledata
.pointposcolumns
= []
136 styledata
.vposmissing
= []
137 for count
, axisnames
in enumerate(graph
.axesnames
):
138 for axisname
in axisnames
:
139 for column
in columns
:
140 if axisname
== column
:
141 styledata
.pointposcolumns
.append(column
)
142 if len(styledata
.pointposcolumns
) + len(styledata
.vposmissing
) > count
+1:
143 raise ValueError("multiple axes per graph dimension")
144 elif len(styledata
.pointposcolumns
) + len(styledata
.vposmissing
) < count
+1:
145 styledata
.vposmissing
.append(count
)
146 return styledata
.pointposcolumns
148 def adjustaxis(self
, styledata
, graph
, column
, data
, index
):
149 if column
in styledata
.pointposcolumns
:
150 graph
.axes
[column
].adjustrange(data
, index
)
152 def initdrawpoints(self
, styledata
, graph
):
153 styledata
.vpos
= [None]*(len(styledata
.pointposcolumns
) + len(styledata
.vposmissing
))
154 styledata
.pointpostmplist
= [[column
, index
, graph
.axes
[column
]] # temporarily used by drawpoint only
155 for index
, column
in enumerate(styledata
.pointposcolumns
)]
156 for missing
in styledata
.vposmissing
:
157 for pointpostmp
in styledata
.pointpostmplist
:
158 if pointpostmp
[1] >= missing
:
161 def drawpoint(self
, styledata
, graph
):
162 styledata
.vposavailable
= 1 # valid position (but might be outside of the graph)
163 styledata
.vposvalid
= 1 # valid position inside the graph
164 for column
, index
, axis
in styledata
.pointpostmplist
:
166 v
= axis
.convert(styledata
.point
[column
])
167 except (ArithmeticError, ValueError, TypeError):
168 styledata
.vposavailable
= styledata
.vposvalid
= 0
169 styledata
.vpos
[index
] = None
171 if v
< - self
.epsilon
or v
> 1 + self
.epsilon
:
172 styledata
.vposvalid
= 0
173 styledata
.vpos
[index
] = v
176 provider
["vpos"] = provider
["vposmissing"] = provider
["vposavailable"] = provider
["vposvalid"] = _pos()
179 class _range(_style
):
181 provide
= ["vrange", "vrangemissing"]
191 def __init__(self
, epsilon
=1e-10):
192 self
.epsilon
= epsilon
194 def columns(self
, styledata
, graph
, columns
):
195 def numberofbits(mask
):
199 return numberofbits(mask
>> 1) + 1
201 return numberofbits(mask
>> 1)
203 styledata
.rangeposcolumns
= []
204 styledata
.vrangemissing
= []
205 styledata
.rangeposdeltacolumns
= {} # temporarily used by adjustaxis only
206 for count
, axisnames
in enumerate(graph
.axesnames
):
207 for axisname
in axisnames
:
209 for column
in columns
:
211 if axisname
== column
:
212 mask
+= self
.mask_value
213 elif axisname
+ "min" == column
:
214 mask
+= self
.mask_min
215 elif axisname
+ "max" == column
:
216 mask
+= self
.mask_max
217 elif "d" + axisname
+ "min" == column
:
218 mask
+= self
.mask_dmin
219 elif "d" + axisname
+ "max" == column
:
220 mask
+= self
.mask_dmax
221 elif "d" + axisname
== column
:
226 usecolumns
.append(column
)
227 if mask
& (self
.mask_min | self
.mask_max | self
.mask_dmin | self
.mask_dmax | self
.mask_d
):
228 if (numberofbits(mask
& (self
.mask_min | self
.mask_dmin | self
.mask_d
)) > 1 or
229 numberofbits(mask
& (self
.mask_max | self
.mask_dmax | self
.mask_d
)) > 1):
230 raise ValueError("multiple errorbar definition")
231 if mask
& (self
.mask_dmin | self
.mask_dmax | self
.mask_d
):
232 if not (mask
& self
.mask_value
):
233 raise ValueError("missing value for delta")
234 styledata
.rangeposdeltacolumns
[axisname
] = {}
235 styledata
.rangeposcolumns
.append((axisname
, mask
))
236 elif mask
== self
.mask_value
:
237 usecolumns
= usecolumns
[:-1]
238 if len(styledata
.rangeposcolumns
) + len(styledata
.vrangemissing
) > count
+1:
239 raise ValueError("multiple axes per graph dimension")
240 elif len(styledata
.rangeposcolumns
) + len(styledata
.vrangemissing
) < count
+1:
241 styledata
.vrangemissing
.append(count
)
244 def adjustaxis(self
, styledata
, graph
, column
, data
, index
):
245 if column
in [c
+ "min" for c
, m
in styledata
.rangeposcolumns
if m
& self
.mask_min
]:
246 graph
.axes
[column
[:-3]].adjustrange(data
, index
)
247 if column
in [c
+ "max" for c
, m
in styledata
.rangeposcolumns
if m
& self
.mask_max
]:
248 graph
.axes
[column
[:-3]].adjustrange(data
, index
)
250 # delta handling: fill rangeposdeltacolumns
251 if column
in [c
for c
, m
in styledata
.rangeposcolumns
if m
& (self
.mask_dmin | self
.mask_dmax | self
.mask_d
)]:
252 styledata
.rangeposdeltacolumns
[column
][self
.mask_value
] = data
, index
253 if column
in ["d" + c
+ "min" for c
, m
in styledata
.rangeposcolumns
if m
& self
.mask_dmin
]:
254 styledata
.rangeposdeltacolumns
[column
[1:-3]][self
.mask_dmin
] = data
, index
255 if column
in ["d" + c
+ "max" for c
, m
in styledata
.rangeposcolumns
if m
& self
.mask_dmax
]:
256 styledata
.rangeposdeltacolumns
[column
[1:-3]][self
.mask_dmax
] = data
, index
257 if column
in ["d" + c
for c
, m
in styledata
.rangeposcolumns
if m
& self
.mask_d
]:
258 styledata
.rangeposdeltacolumns
[column
[1:]][self
.mask_d
] = data
, index
260 # delta handling: process rangeposdeltacolumns
261 for c
, d
in styledata
.rangeposdeltacolumns
.items():
262 if d
.has_key(self
.mask_value
):
264 if k
!= self
.mask_value
:
265 if k
& (self
.mask_dmin | self
.mask_d
):
266 graph
.axes
[c
].adjustrange(d
[self
.mask_value
][0], d
[self
.mask_value
][1],
267 deltamindata
=d
[k
][0], deltaminindex
=d
[k
][1])
268 if k
& (self
.mask_dmax | self
.mask_d
):
269 graph
.axes
[c
].adjustrange(d
[self
.mask_value
][0], d
[self
.mask_value
][1],
270 deltamaxdata
=d
[k
][0], deltamaxindex
=d
[k
][1])
273 def initdrawpoints(self
, styledata
, graph
):
274 styledata
.vrange
= [[None for x
in range(2)] for y
in styledata
.rangeposcolumns
+ styledata
.vrangemissing
]
275 styledata
.rangepostmplist
= [[column
, mask
, index
, graph
.axes
[column
]] # temporarily used by drawpoint only
276 for index
, (column
, mask
) in enumerate(styledata
.rangeposcolumns
)]
277 for missing
in styledata
.vrangemissing
:
278 for rangepostmp
in styledata
.rangepostmplist
:
279 if rangepostmp
[2] >= missing
:
282 def drawpoint(self
, styledata
, graph
):
283 for column
, mask
, index
, axis
in styledata
.rangepostmplist
:
285 if mask
& self
.mask_min
:
286 styledata
.vrange
[index
][0] = axis
.convert(styledata
.point
[column
+ "min"])
287 if mask
& self
.mask_dmin
:
288 styledata
.vrange
[index
][0] = axis
.convert(styledata
.point
[column
] - styledata
.point
["d" + column
+ "min"])
289 if mask
& self
.mask_d
:
290 styledata
.vrange
[index
][0] = axis
.convert(styledata
.point
[column
] - styledata
.point
["d" + column
])
291 except (ArithmeticError, ValueError, TypeError):
292 styledata
.vrange
[index
][0] = None
294 if mask
& self
.mask_max
:
295 styledata
.vrange
[index
][1] = axis
.convert(styledata
.point
[column
+ "max"])
296 if mask
& self
.mask_dmax
:
297 styledata
.vrange
[index
][1] = axis
.convert(styledata
.point
[column
] + styledata
.point
["d" + column
+ "max"])
298 if mask
& self
.mask_d
:
299 styledata
.vrange
[index
][1] = axis
.convert(styledata
.point
[column
] + styledata
.point
["d" + column
])
300 except (ArithmeticError, ValueError, TypeError):
301 styledata
.vrange
[index
][1] = None
303 # some range checks for data consistency
304 if (styledata
.vrange
[index
][0] is not None and styledata
.vrange
[index
][1] is not None and
305 styledata
.vrange
[index
][0] > styledata
.vrange
[index
][1] + self
.epsilon
):
306 raise ValueError("negative errorbar range")
307 if (styledata
.vrange
[index
][0] is not None and styledata
.vpos
[index
] is not None and
308 styledata
.vrange
[index
][0] > styledata
.vpos
[index
] + self
.epsilon
):
309 raise ValueError("negative minimum errorbar")
310 if (styledata
.vrange
[index
][1] is not None and styledata
.vpos
[index
] is not None and
311 styledata
.vrange
[index
][1] < styledata
.vpos
[index
] - self
.epsilon
):
312 raise ValueError("negative maximum errorbar")
315 provider
["vrange"] = provider
["vrangemissing"] = _range()
318 def _crosssymbol(c
, x_pt
, y_pt
, size_pt
, attrs
):
319 c
.draw(path
.path(path
.moveto_pt(x_pt
-0.5*size_pt
, y_pt
-0.5*size_pt
),
320 path
.lineto_pt(x_pt
+0.5*size_pt
, y_pt
+0.5*size_pt
),
321 path
.moveto_pt(x_pt
-0.5*size_pt
, y_pt
+0.5*size_pt
),
322 path
.lineto_pt(x_pt
+0.5*size_pt
, y_pt
-0.5*size_pt
)), attrs
)
324 def _plussymbol(c
, x_pt
, y_pt
, size_pt
, attrs
):
325 c
.draw(path
.path(path
.moveto_pt(x_pt
-0.707106781*size_pt
, y_pt
),
326 path
.lineto_pt(x_pt
+0.707106781*size_pt
, y_pt
),
327 path
.moveto_pt(x_pt
, y_pt
-0.707106781*size_pt
),
328 path
.lineto_pt(x_pt
, y_pt
+0.707106781*size_pt
)), attrs
)
330 def _squaresymbol(c
, x_pt
, y_pt
, size_pt
, attrs
):
331 c
.draw(path
.path(path
.moveto_pt(x_pt
-0.5*size_pt
, y_pt
-0.5*size_pt
),
332 path
.lineto_pt(x_pt
+0.5*size_pt
, y_pt
-0.5*size_pt
),
333 path
.lineto_pt(x_pt
+0.5*size_pt
, y_pt
+0.5*size_pt
),
334 path
.lineto_pt(x_pt
-0.5*size_pt
, y_pt
+0.5*size_pt
),
335 path
.closepath()), attrs
)
337 def _trianglesymbol(c
, x_pt
, y_pt
, size_pt
, attrs
):
338 c
.draw(path
.path(path
.moveto_pt(x_pt
-0.759835685*size_pt
, y_pt
-0.438691337*size_pt
),
339 path
.lineto_pt(x_pt
+0.759835685*size_pt
, y_pt
-0.438691337*size_pt
),
340 path
.lineto_pt(x_pt
, y_pt
+0.877382675*size_pt
),
341 path
.closepath()), attrs
)
343 def _circlesymbol(c
, x_pt
, y_pt
, size_pt
, attrs
):
344 c
.draw(path
.path(path
.arc_pt(x_pt
, y_pt
, 0.564189583*size_pt
, 0, 360),
345 path
.closepath()), attrs
)
347 def _diamondsymbol(c
, x_pt
, y_pt
, size_pt
, attrs
):
348 c
.draw(path
.path(path
.moveto_pt(x_pt
-0.537284965*size_pt
, y_pt
),
349 path
.lineto_pt(x_pt
, y_pt
-0.930604859*size_pt
),
350 path
.lineto_pt(x_pt
+0.537284965*size_pt
, y_pt
),
351 path
.lineto_pt(x_pt
, y_pt
+0.930604859*size_pt
),
352 path
.closepath()), attrs
)
355 class _styleneedingpointpos(_style
):
357 need
= ["vposmissing"]
359 def columns(self
, styledata
, graph
, columns
):
360 if len(styledata
.vposmissing
):
361 raise ValueError("position columns incomplete")
365 class symbol(_styleneedingpointpos
):
367 need
= ["vpos", "vposmissing", "vposvalid"]
369 # insert symbols like staticmethods
372 square
= _squaresymbol
373 triangle
= _trianglesymbol
374 circle
= _circlesymbol
375 diamond
= _diamondsymbol
377 changecross
= attr
.changelist([cross
, plus
, square
, triangle
, circle
, diamond
])
378 changeplus
= attr
.changelist([plus
, square
, triangle
, circle
, diamond
, cross
])
379 changesquare
= attr
.changelist([square
, triangle
, circle
, diamond
, cross
, plus
])
380 changetriangle
= attr
.changelist([triangle
, circle
, diamond
, cross
, plus
, square
])
381 changecircle
= attr
.changelist([circle
, diamond
, cross
, plus
, square
, triangle
])
382 changediamond
= attr
.changelist([diamond
, cross
, plus
, square
, triangle
, circle
])
383 changesquaretwice
= attr
.changelist([square
, square
, triangle
, triangle
, circle
, circle
, diamond
, diamond
])
384 changetriangletwice
= attr
.changelist([triangle
, triangle
, circle
, circle
, diamond
, diamond
, square
, square
])
385 changecircletwice
= attr
.changelist([circle
, circle
, diamond
, diamond
, square
, square
, triangle
, triangle
])
386 changediamondtwice
= attr
.changelist([diamond
, diamond
, square
, square
, triangle
, triangle
, circle
, circle
])
388 changestrokedfilled
= attr
.changelist([deco
.stroked
, deco
.filled
])
389 changefilledstroked
= attr
.changelist([deco
.filled
, deco
.stroked
])
391 defaultsymbolattrs
= [deco
.stroked
]
393 def __init__(self
, symbol
=changecross
, size
=0.2*unit
.v_cm
, symbolattrs
=[]):
396 self
.symbolattrs
= symbolattrs
398 def selectstyle(self
, styledata
, graph
, selectindex
, selecttotal
):
399 styledata
.symbol
= attr
.selectattr(self
.symbol
, selectindex
, selecttotal
)
400 styledata
.size_pt
= unit
.topt(attr
.selectattr(self
.size
, selectindex
, selecttotal
))
401 if self
.symbolattrs
is not None:
402 styledata
.symbolattrs
= attr
.selectattrs(self
.defaultsymbolattrs
+ self
.symbolattrs
, selectindex
, selecttotal
)
404 styledata
.symbolattrs
= None
406 def initdrawpoints(self
, styledata
, graph
):
407 styledata
.symbolcanvas
= graph
.insert(canvas
.canvas())
409 def drawpoint(self
, styledata
, graph
):
410 if styledata
.vposvalid
and styledata
.symbolattrs
is not None:
411 xpos
, ypos
= graph
.vpos_pt(*styledata
.vpos
)
412 styledata
.symbol(styledata
.symbolcanvas
, xpos
, ypos
, styledata
.size_pt
, styledata
.symbolattrs
)
414 def key_pt(self
, styledata
, graph
, x_pt
, y_pt
, width_pt
, height_pt
):
415 if styledata
.symbolattrs
is not None:
416 styledata
.symbol(graph
, x_pt
+0.5*width_pt
, y_pt
+0.5*height_pt
, styledata
.size_pt
, styledata
.symbolattrs
)
419 class line(_styleneedingpointpos
):
421 need
= ["vpos", "vposmissing", "vposavailable", "vposvalid"]
423 changelinestyle
= attr
.changelist([style
.linestyle
.solid
,
424 style
.linestyle
.dashed
,
425 style
.linestyle
.dotted
,
426 style
.linestyle
.dashdotted
])
428 defaultlineattrs
= [changelinestyle
]
430 def __init__(self
, lineattrs
=[]):
431 self
.lineattrs
= lineattrs
433 def selectstyle(self
, styledata
, graph
, selectindex
, selecttotal
):
434 styledata
.lineattrs
= attr
.selectattrs(self
.defaultlineattrs
+ self
.lineattrs
, selectindex
, selecttotal
)
436 def initdrawpoints(self
, styledata
, graph
):
437 styledata
.linecanvas
= graph
.insert(canvas
.canvas())
438 if styledata
.lineattrs
is not None:
439 styledata
.linecanvas
.set(styledata
.lineattrs
)
440 styledata
.path
= path
.path()
441 styledata
.linebasepoints
= []
442 styledata
.lastvpos
= None
444 def addpointstopath(self
, styledata
):
445 # add baselinepoints to styledata.path
446 if len(styledata
.linebasepoints
) > 1:
447 styledata
.path
.append(path
.moveto_pt(*styledata
.linebasepoints
[0]))
448 if len(styledata
.linebasepoints
) > 2:
449 styledata
.path
.append(path
.multilineto_pt(styledata
.linebasepoints
[1:]))
451 styledata
.path
.append(path
.lineto_pt(*styledata
.linebasepoints
[1]))
452 styledata
.linebasepoints
= []
454 def drawpoint(self
, styledata
, graph
):
455 # append linebasepoints
456 if styledata
.vposavailable
:
457 if len(styledata
.linebasepoints
):
458 # the last point was inside the graph
459 if styledata
.vposvalid
: # shortcut for the common case
460 styledata
.linebasepoints
.append(graph
.vpos_pt(*styledata
.vpos
))
464 for vstart
, vend
in zip(styledata
.lastvpos
, styledata
.vpos
):
467 # 1 = vstart + (vend - vstart) * cut
469 newcut
= (1 - vstart
)/(vend
- vstart
)
470 except ArithmeticError:
473 # 0 = vstart + (vend - vstart) * cut
475 newcut
= - vstart
/(vend
- vstart
)
476 except ArithmeticError:
478 if newcut
is not None and newcut
< cut
:
482 for vstart
, vend
in zip(styledata
.lastvpos
, styledata
.vpos
):
483 cutvpos
.append(vstart
+ (vend
- vstart
) * cut
)
484 styledata
.linebasepoints
.append(styledata
.graph
.vpos_pt(*cutvpos
))
485 self
.addpointstopath(styledata
)
487 # the last point was outside the graph
488 if styledata
.lastvpos
is not None:
489 if styledata
.vposvalid
:
492 for vstart
, vend
in zip(styledata
.lastvpos
, styledata
.vpos
):
495 # 1 = vstart + (vend - vstart) * cut
497 newcut
= (1 - vstart
)/(vend
- vstart
)
498 except ArithmeticError:
501 # 0 = vstart + (vend - vstart) * cut
503 newcut
= - vstart
/(vend
- vstart
)
504 except ArithmeticError:
506 if newcut
is not None and newcut
> cut
:
510 for vstart
, vend
in zip(styledata
.lastvpos
, styledata
.vpos
):
511 cutvpos
.append(vstart
+ (vend
- vstart
) * cut
)
512 styledata
.linebasepoints
.append(graph
.vpos_pt(*cutvpos
))
513 styledata
.linebasepoints
.append(graph
.vpos_pt(*styledata
.vpos
))
515 # sometimes cut beginning and end
518 for vstart
, vend
in zip(styledata
.lastvpos
, styledata
.vpos
):
523 # 1 = vstart + (vend - vstart) * cutfrom
525 newcutfrom
= (1 - vstart
)/(vend
- vstart
)
526 except ArithmeticError:
531 # 0 = vstart + (vend - vstart) * cutfrom
533 newcutfrom
= - vstart
/(vend
- vstart
)
534 except ArithmeticError:
536 if newcutfrom
is not None and newcutfrom
> cutfrom
:
540 # 1 = vstart + (vend - vstart) * cutto
542 newcutto
= (1 - vstart
)/(vend
- vstart
)
543 except ArithmeticError:
546 # 0 = vstart + (vend - vstart) * cutto
548 newcutto
= - vstart
/(vend
- vstart
)
549 except ArithmeticError:
551 if newcutto
is not None and newcutto
< cutto
:
557 for vstart
, vend
in zip(styledata
.lastvpos
, styledata
.vpos
):
558 cutfromvpos
.append(vstart
+ (vend
- vstart
) * cutfrom
)
559 cuttovpos
.append(vstart
+ (vend
- vstart
) * cutto
)
560 styledata
.linebasepoints
.append(styledata
.graph
.vpos_pt(*cutfromvpos
))
561 styledata
.linebasepoints
.append(styledata
.graph
.vpos_pt(*cuttovpos
))
562 self
.addpointstopath(styledata
)
563 styledata
.lastvpos
= styledata
.vpos
[:]
565 if len(styledata
.linebasepoints
) > 1:
566 self
.addpointstopath(styledata
)
567 styledata
.lastvpos
= None
569 def donedrawpoints(self
, styledata
, graph
):
570 if len(styledata
.linebasepoints
) > 1:
571 self
.addpointstopath(styledata
)
572 if styledata
.lineattrs
is not None and len(styledata
.path
.path
):
573 styledata
.linecanvas
.stroke(styledata
.path
)
575 def key_pt(self
, styledata
, graph
, x_pt
, y_pt
, width_pt
, height_pt
):
576 if styledata
.lineattrs
is not None:
577 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
)
580 class errorbar(_style
):
582 need
= ["vpos", "vposmissing", "vposavailable", "vposvalid", "vrange", "vrangemissing"]
584 defaulterrorbarattrs
= []
586 def __init__(self
, size
=0.1*unit
.v_cm
,
590 self
.errorbarattrs
= errorbarattrs
591 self
.epsilon
= epsilon
593 def columns(self
, styledata
, graph
, columns
):
594 for i
in styledata
.vposmissing
:
595 if i
in styledata
.vrangemissing
:
596 raise ValueError("position and range for a graph dimension missing")
599 def selectstyle(self
, styledata
, graph
, selectindex
, selecttotal
):
600 styledata
.errorsize_pt
= unit
.topt(attr
.selectattr(self
.size
, selectindex
, selecttotal
))
601 styledata
.errorbarattrs
= attr
.selectattrs(self
.defaulterrorbarattrs
+ self
.errorbarattrs
, selectindex
, selecttotal
)
603 def initdrawpoints(self
, styledata
, graph
):
604 styledata
.errorbarcanvas
= graph
.insert(canvas
.canvas())
605 if styledata
.errorbarattrs
is not None:
606 styledata
.errorbarcanvas
.set(styledata
.errorbarattrs
)
607 styledata
.dimensionlist
= range(len(styledata
.vpos
))
609 def drawpoint(self
, styledata
, graph
):
610 if styledata
.errorbarattrs
is None:
612 for i
in styledata
.dimensionlist
:
613 for j
in styledata
.dimensionlist
:
615 (styledata
.vpos
[j
] is None or
616 styledata
.vpos
[j
] < -self
.epsilon
or
617 styledata
.vpos
[j
] > 1+self
.epsilon
)):
620 if ((styledata
.vrange
[i
][0] is None and styledata
.vpos
[i
] is None) or
621 (styledata
.vrange
[i
][1] is None and styledata
.vpos
[i
] is None) or
622 (styledata
.vrange
[i
][0] is None and styledata
.vrange
[i
][1] is None)):
624 vminpos
= styledata
.vpos
[:]
625 if styledata
.vrange
[i
][0] is not None:
626 vminpos
[i
] = styledata
.vrange
[i
][0]
630 if vminpos
[i
] > 1+self
.epsilon
:
632 if vminpos
[i
] < -self
.epsilon
:
635 vmaxpos
= styledata
.vpos
[:]
636 if styledata
.vrange
[i
][1] is not None:
637 vmaxpos
[i
] = styledata
.vrange
[i
][1]
641 if vmaxpos
[i
] < -self
.epsilon
:
643 if vmaxpos
[i
] > 1+self
.epsilon
:
646 styledata
.errorbarcanvas
.stroke(graph
.vgeodesic(*(vminpos
+ vmaxpos
)))
647 for j
in styledata
.dimensionlist
:
650 styledata
.errorbarcanvas
.stroke(graph
.vcap_pt(j
, styledata
.errorsize_pt
, *vminpos
))
652 styledata
.errorbarcanvas
.stroke(graph
.vcap_pt(j
, styledata
.errorsize_pt
, *vmaxpos
))
655 # not yet ported to the new style scheme
657 # class text(symbol):
659 # defaulttextattrs = [textmodule.halign.center, textmodule.vshift.mathaxis]
661 # def __init__(self, textdx=0*unit.v_cm, textdy=0.3*unit.v_cm, textattrs=[], **kwargs):
662 # self.textdx = textdx
663 # self.textdy = textdy
664 # self.textattrs = textattrs
665 # symbol.__init__(self, **kwargs)
667 # def setdata(self, graph, columns, styledata):
668 # columns = columns.copy()
669 # styledata.textindex = columns["text"]
670 # del columns["text"]
671 # return symbol.setdata(self, graph, columns, styledata)
673 # def selectstyle(self, selectindex, selecttotal, styledata):
674 # if self.textattrs is not None:
675 # styledata.textattrs = attr.selectattrs(self.defaulttextattrs + self.textattrs, selectindex, selecttotal)
677 # styledata.textattrs = None
678 # symbol.selectstyle(self, selectindex, selecttotal, styledata)
680 # def drawsymbol_pt(self, c, x, y, styledata, point=None):
681 # symbol.drawsymbol_pt(self, c, x, y, styledata, point)
682 # if None not in (x, y, point[styledata.textindex]) and styledata.textattrs is not None:
683 # c.text_pt(x + styledata.textdx_pt, y + styledata.textdy_pt, str(point[styledata.textindex]), styledata.textattrs)
685 # def drawpoints(self, points, graph, styledata):
686 # styledata.textdx_pt = unit.topt(self.textdx)
687 # styledata.textdy_pt = unit.topt(self.textdy)
688 # symbol.drawpoints(self, points, graph, styledata)
691 # class arrow(_style):
693 # defaultlineattrs = []
694 # defaultarrowattrs = []
696 # def __init__(self, linelength=0.25*unit.v_cm, arrowsize=0.15*unit.v_cm, lineattrs=[], arrowattrs=[], epsilon=1e-10):
697 # self.linelength = linelength
698 # self.arrowsize = arrowsize
699 # self.lineattrs = lineattrs
700 # self.arrowattrs = arrowattrs
701 # self.epsilon = epsilon
703 # def setdata(self, graph, columns, styledata):
704 # if len(graph.axisnames) != 2:
705 # raise TypeError("arrow style restricted on two-dimensional graphs")
706 # columns = columns.copy()
707 # styledata.xaxis, styledata.xindex = _style.setdatapattern(self, graph, columns, re.compile(r"(%s([2-9]|[1-9][0-9]+)?)$" % graph.axisnames[0]))
708 # styledata.yaxis, styledata.yindex = _style.setdatapattern(self, graph, columns, re.compile(r"(%s([2-9]|[1-9][0-9]+)?)$" % graph.axisnames[1]))
709 # styledata.sizeindex = columns["size"]
710 # del columns["size"]
711 # styledata.angleindex = columns["angle"]
712 # del columns["angle"]
715 # def adjustaxes(self, points, columns, styledata):
716 # if styledata.xindex in columns:
717 # styledata.xaxis.adjustrange(points, styledata.xindex)
718 # if styledata.yindex in columns:
719 # styledata.yaxis.adjustrange(points, styledata.yindex)
721 # def selectstyle(self, selectindex, selecttotal, styledata):
722 # if self.lineattrs is not None:
723 # styledata.lineattrs = attr.selectattrs(self.defaultlineattrs + self.lineattrs, selectindex, selecttotal)
725 # styledata.lineattrs = None
726 # if self.arrowattrs is not None:
727 # styledata.arrowattrs = attr.selectattrs(self.defaultarrowattrs + self.arrowattrs, selectindex, selecttotal)
729 # styledata.arrowattrs = None
731 # def drawpoints(self, points, graph, styledata):
732 # if styledata.lineattrs is not None and styledata.arrowattrs is not None:
733 # linelength_pt = unit.topt(self.linelength)
734 # for point in points:
735 # xpos, ypos = graph.pos_pt(point[styledata.xindex], point[styledata.yindex], xaxis=styledata.xaxis, yaxis=styledata.yaxis)
736 # if point[styledata.sizeindex] > self.epsilon:
737 # dx = math.cos(point[styledata.angleindex]*math.pi/180)
738 # dy = math.sin(point[styledata.angleindex]*math.pi/180)
739 # x1 = xpos-0.5*dx*linelength_pt*point[styledata.sizeindex]
740 # y1 = ypos-0.5*dy*linelength_pt*point[styledata.sizeindex]
741 # x2 = xpos+0.5*dx*linelength_pt*point[styledata.sizeindex]
742 # y2 = ypos+0.5*dy*linelength_pt*point[styledata.sizeindex]
743 # graph.stroke(path.line_pt(x1, y1, x2, y2), styledata.lineattrs +
744 # [deco.earrow(styledata.arrowattrs, size=self.arrowsize*point[styledata.sizeindex])])
747 # class rect(_style):
749 # def __init__(self, palette=color.palette.Gray):
750 # self.palette = palette
752 # def setdata(self, graph, columns, styledata):
753 # if len(graph.axisnames) != 2:
754 # raise TypeError("arrow style restricted on two-dimensional graphs")
755 # columns = columns.copy()
756 # styledata.xaxis, styledata.xminindex = _style.setdatapattern(self, graph, columns, re.compile(r"(%s([2-9]|[1-9][0-9]+)?)min$" % graph.axisnames[0]))
757 # styledata.yaxis, styledata.yminindex = _style.setdatapattern(self, graph, columns, re.compile(r"(%s([2-9]|[1-9][0-9]+)?)min$" % graph.axisnames[1]))
758 # xaxis, styledata.xmaxindex = _style.setdatapattern(self, graph, columns, re.compile(r"(%s([2-9]|[1-9][0-9]+)?)max$" % graph.axisnames[0]))
759 # yaxis, styledata.ymaxindex = _style.setdatapattern(self, graph, columns, re.compile(r"(%s([2-9]|[1-9][0-9]+)?)max$" % graph.axisnames[1]))
760 # if xaxis != styledata.xaxis or yaxis != styledata.yaxis:
761 # raise ValueError("min/max values should use the same axes")
762 # styledata.colorindex = columns["color"]
763 # del columns["color"]
766 # def selectstyle(self, selectindex, selecttotal, styledata):
769 # def adjustaxes(self, points, columns, styledata):
770 # if styledata.xminindex in columns:
771 # styledata.xaxis.adjustrange(points, styledata.xminindex)
772 # if styledata.xmaxindex in columns:
773 # styledata.xaxis.adjustrange(points, styledata.xmaxindex)
774 # if styledata.yminindex in columns:
775 # styledata.yaxis.adjustrange(points, styledata.yminindex)
776 # if styledata.ymaxindex in columns:
777 # styledata.yaxis.adjustrange(points, styledata.ymaxindex)
779 # def drawpoints(self, points, graph, styledata):
780 # # TODO: bbox shortcut
781 # c = graph.insert(canvas.canvas())
782 # lastcolorvalue = None
783 # for point in points:
785 # xvmin = styledata.xaxis.convert(point[styledata.xminindex])
786 # xvmax = styledata.xaxis.convert(point[styledata.xmaxindex])
787 # yvmin = styledata.yaxis.convert(point[styledata.yminindex])
788 # yvmax = styledata.yaxis.convert(point[styledata.ymaxindex])
789 # colorvalue = point[styledata.colorindex]
790 # if colorvalue != lastcolorvalue:
791 # color = self.palette.getcolor(point[styledata.colorindex])
794 # if ((xvmin < 0 and xvmax < 0) or (xvmin > 1 and xvmax > 1) or
795 # (yvmin < 0 and yvmax < 0) or (yvmin > 1 and yvmax > 1)):
813 # p = graph.vgeodesic(xvmin, yvmin, xvmax, yvmin)
814 # p.append(graph.vgeodesic_el(xvmax, yvmin, xvmax, yvmax))
815 # p.append(graph.vgeodesic_el(xvmax, yvmax, xvmin, yvmax))
816 # p.append(graph.vgeodesic_el(xvmin, yvmax, xvmin, yvmin))
817 # p.append(path.closepath())
818 # if colorvalue != lastcolorvalue:
825 # defaultfrompathattrs = []
826 # defaultbarattrs = [color.palette.Rainbow, deco.stroked([color.gray.black])]
828 # def __init__(self, fromvalue=None, frompathattrs=[], barattrs=[], subnames=None, epsilon=1e-10):
829 # self.fromvalue = fromvalue
830 # self.frompathattrs = frompathattrs
831 # self.barattrs = barattrs
832 # self.subnames = subnames
833 # self.epsilon = epsilon
835 # def setdata(self, graph, columns, styledata):
836 # # TODO: remove limitation to 2d graphs
837 # if len(graph.axisnames) != 2:
838 # raise TypeError("arrow style currently restricted on two-dimensional graphs")
839 # columns = columns.copy()
840 # xvalue = _style.setdatapattern(self, graph, columns, re.compile(r"(%s([2-9]|[1-9][0-9]+)?)$" % graph.axisnames[0]))
841 # yvalue = _style.setdatapattern(self, graph, columns, re.compile(r"(%s([2-9]|[1-9][0-9]+)?)$" % graph.axisnames[1]))
842 # if (xvalue is None and yvalue is None) or (xvalue is not None and yvalue is not None):
843 # raise TypeError("must specify exactly one value axis")
844 # if xvalue is not None:
845 # styledata.valuepos = 0
846 # styledata.nameaxis, styledata.nameindex = _style.setdatapattern(self, graph, columns, re.compile(r"(%s([2-9]|[1-9][0-9]+)?)name$" % graph.axisnames[1]))
847 # styledata.valueaxis = xvalue[0]
848 # styledata.valueindices = [xvalue[1]]
850 # styledata.valuepos = 1
851 # styledata.nameaxis, styledata.nameindex = _style.setdatapattern(self, graph, columns, re.compile(r"(%s([2-9]|[1-9][0-9]+)?)name$" % graph.axisnames[0]))
852 # styledata.valueaxis = yvalue[0]
853 # styledata.valueindices = [yvalue[1]]
857 # valueaxis, valueindex = _style.setdatapattern(self, graph, columns, re.compile(r"(%s([2-9]|[1-9][0-9]+)?)stack%i$" % (graph.axisnames[styledata.valuepos], i)))
860 # if styledata.valueaxis != valueaxis:
861 # raise ValueError("different value axes for stacked bars")
862 # styledata.valueindices.append(valueindex)
866 # def selectstyle(self, selectindex, selecttotal, styledata):
868 # styledata.frompathattrs = None
870 # styledata.frompathattrs = self.defaultfrompathattrs + self.frompathattrs
871 # if selecttotal > 1:
872 # if self.barattrs is not None:
873 # styledata.barattrs = attr.selectattrs(self.defaultbarattrs + self.barattrs, selectindex, selecttotal)
875 # styledata.barattrs = None
877 # styledata.barattrs = self.defaultbarattrs + self.barattrs
878 # styledata.selectindex = selectindex
879 # styledata.selecttotal = selecttotal
880 # if styledata.selecttotal != 1 and self.subnames is not None:
881 # raise ValueError("subnames not allowed when iterating over bars")
883 # def adjustaxes(self, points, columns, styledata):
884 # if styledata.nameindex in columns:
885 # if styledata.selecttotal == 1:
886 # styledata.nameaxis.adjustrange(points, styledata.nameindex, subnames=self.subnames)
888 # for i in range(styledata.selecttotal):
889 # styledata.nameaxis.adjustrange(points, styledata.nameindex, subnames=[i])
890 # for valueindex in styledata.valueindices:
891 # if valueindex in columns:
892 # styledata.valueaxis.adjustrange(points, valueindex)
894 # def drawpoints(self, points, graph, styledata):
895 # if self.fromvalue is not None:
896 # vfromvalue = styledata.valueaxis.convert(self.fromvalue)
897 # if vfromvalue < -self.epsilon:
899 # if vfromvalue > 1 + self.epsilon:
901 # if styledata.frompathattrs is not None and vfromvalue > self.epsilon and vfromvalue < 1 - self.epsilon:
902 # if styledata.valuepos:
903 # p = graph.vgeodesic(0, vfromvalue, 1, vfromvalue)
905 # p = graph.vgeodesic(vfromvalue, 0, vfromvalue, 1)
906 # graph.stroke(p, styledata.frompathattrs)
909 # l = len(styledata.valueindices)
913 # barattrslist.append(attr.selectattrs(styledata.barattrs, i, l))
915 # barattrslist = [styledata.barattrs]
916 # for point in points:
917 # vvaluemax = vfromvalue
918 # for valueindex, barattrs in zip(styledata.valueindices, barattrslist):
919 # vvaluemin = vvaluemax
921 # vvaluemax = styledata.valueaxis.convert(point[valueindex])
925 # if styledata.selecttotal == 1:
927 # vnamemin = styledata.nameaxis.convert((point[styledata.nameindex], 0))
931 # vnamemax = styledata.nameaxis.convert((point[styledata.nameindex], 1))
936 # vnamemin = styledata.nameaxis.convert((point[styledata.nameindex], styledata.selectindex, 0))
940 # vnamemax = styledata.nameaxis.convert((point[styledata.nameindex], styledata.selectindex, 1))
944 # if styledata.valuepos:
945 # p = graph.vgeodesic(vnamemin, vvaluemin, vnamemin, vvaluemax)
946 # p.append(graph.vgeodesic_el(vnamemin, vvaluemax, vnamemax, vvaluemax))
947 # p.append(graph.vgeodesic_el(vnamemax, vvaluemax, vnamemax, vvaluemin))
948 # p.append(graph.vgeodesic_el(vnamemax, vvaluemin, vnamemin, vvaluemin))
949 # p.append(path.closepath())
951 # p = graph.vgeodesic(vvaluemin, vnamemin, vvaluemin, vnamemax)
952 # p.append(graph.vgeodesic_el(vvaluemin, vnamemax, vvaluemax, vnamemax))
953 # p.append(graph.vgeodesic_el(vvaluemax, vnamemax, vvaluemax, vnamemin))
954 # p.append(graph.vgeodesic_el(vvaluemax, vnamemin, vvaluemin, vnamemin))
955 # p.append(path.closepath())
956 # if barattrs is not None:
957 # graph.fill(p, barattrs)
959 # def key_pt(self, c, x_pt, y_pt, width_pt, height_pt, styledata):
960 # l = len(styledata.valueindices)
963 # c.fill(path.rect_pt(x_pt+i*width_pt/l, y_pt, width_pt/l, height_pt), attr.selectattrs(styledata.barattrs, i, l))
965 # c.fill(path.rect_pt(x_pt, y_pt, width_pt, height_pt), styledata.barattrs)