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), data is
81 a list of points and index is the index of the column within
85 def initdrawpoints(self
, styledata
, graph
):
86 """Initialize drawing of data
88 This method might be used to initialize the drawing of data."""
91 def drawpoint(self
, styledata
, graph
):
94 This method is called for each data point. The data is
95 available in the dictionary styledata.data. The dictionary
96 keys are the column names."""
99 def donedrawpoints(self
, styledata
, graph
):
100 """Finalize drawing of data
102 This method is called after the last data point was
103 drawn using the drawpoint method above."""
106 def key_pt(self
, styledata
, graph
, x_pt
, y_pt
, width_pt
, height_pt
):
109 Draw a graph key to graph."""
113 # provider is a dictionary, which maps styledata variable names
114 # to default styles, which provide a default way to create the
115 # corresponding styledata variable.
122 provide
= ["vpos", "vposmissing", "vposavailable", "vposvalid"]
124 def __init__(self
, epsilon
=1e-10):
125 self
.epsilon
= epsilon
127 def columns(self
, styledata
, graph
, columns
):
128 styledata
.pointposcolumns
= []
129 styledata
.vposmissing
= []
130 for count
, axisnames
in enumerate(graph
.axesnames
):
131 for axisname
in axisnames
:
132 for column
in columns
:
133 if axisname
== column
:
134 styledata
.pointposcolumns
.append(column
)
135 if len(styledata
.pointposcolumns
) + len(styledata
.vposmissing
) > count
+1:
136 raise ValueError("multiple axes per graph dimension")
137 elif len(styledata
.pointposcolumns
) + len(styledata
.vposmissing
) < count
+1:
138 styledata
.vposmissing
.append(count
)
139 return styledata
.pointposcolumns
141 def adjustaxis(self
, styledata
, graph
, column
, data
, index
):
142 if column
in styledata
.pointposcolumns
:
143 graph
.axes
[column
].adjustrange(data
, index
)
145 def initdrawpoints(self
, styledata
, graph
):
146 styledata
.vpos
= [None]*(len(styledata
.pointposcolumns
) + len(styledata
.vposmissing
))
147 styledata
.pointpostmplist
= [[column
, index
, graph
.axes
[column
]] # temporarily used by drawpoint only
148 for index
, column
in enumerate(styledata
.pointposcolumns
)]
149 for missing
in styledata
.vposmissing
:
150 for pointpostmp
in styledata
.pointpostmplist
:
151 if pointpostmp
[1] >= missing
:
154 def drawpoint(self
, styledata
, graph
):
155 styledata
.vposavailable
= 1 # valid position (but might be outside of the graph)
156 styledata
.vposvalid
= 1 # valid position inside the graph
157 for column
, index
, axis
in styledata
.pointpostmplist
:
159 v
= axis
.convert(styledata
.point
[column
])
160 except (ArithmeticError, ValueError, TypeError):
161 styledata
.vposavailable
= styledata
.vposvalid
= 0
162 styledata
.vpos
[index
] = None
164 if v
< - self
.epsilon
or v
> 1 + self
.epsilon
:
165 styledata
.vposvalid
= 0
166 styledata
.vpos
[index
] = v
169 provider
["vpos"] = provider
["vposmissing"] = provider
["vposavailable"] = provider
["vposvalid"] = _pos()
172 class _range(_style
):
174 provide
= ["vrange", "vrangemissing"]
184 def __init__(self
, epsilon
=1e-10):
185 self
.epsilon
= epsilon
187 def columns(self
, styledata
, graph
, columns
):
188 def numberofbits(mask
):
192 return numberofbits(mask
>> 1) + 1
194 return numberofbits(mask
>> 1)
196 styledata
.rangeposcolumns
= []
197 styledata
.vrangemissing
= []
198 styledata
.rangeposdeltacolumns
= {} # temporarily used by adjustaxis only
199 for count
, axisnames
in enumerate(graph
.axesnames
):
200 for axisname
in axisnames
:
202 for column
in columns
:
204 if axisname
== column
:
205 mask
+= self
.mask_value
206 elif axisname
+ "min" == column
:
207 mask
+= self
.mask_min
208 elif axisname
+ "max" == column
:
209 mask
+= self
.mask_max
210 elif "d" + axisname
+ "min" == column
:
211 mask
+= self
.mask_dmin
212 elif "d" + axisname
+ "max" == column
:
213 mask
+= self
.mask_dmax
214 elif "d" + axisname
== column
:
219 usecolumns
.append(column
)
220 if mask
& (self
.mask_min | self
.mask_max | self
.mask_dmin | self
.mask_dmax | self
.mask_d
):
221 if (numberofbits(mask
& (self
.mask_min | self
.mask_dmin | self
.mask_d
)) > 1 or
222 numberofbits(mask
& (self
.mask_max | self
.mask_dmax | self
.mask_d
)) > 1):
223 raise ValueError("multiple errorbar definition")
224 if mask
& (self
.mask_dmin | self
.mask_dmax | self
.mask_d
):
225 if not (mask
& self
.mask_value
):
226 raise ValueError("missing value for delta")
227 styledata
.rangeposdeltacolumns
[axisname
] = {}
228 styledata
.rangeposcolumns
.append((axisname
, mask
))
229 elif mask
== self
.mask_value
:
230 usecolumns
= usecolumns
[:-1]
231 if len(styledata
.rangeposcolumns
) + len(styledata
.vrangemissing
) > count
+1:
232 raise ValueError("multiple axes per graph dimension")
233 elif len(styledata
.rangeposcolumns
) + len(styledata
.vrangemissing
) < count
+1:
234 styledata
.vrangemissing
.append(count
)
237 def adjustaxis(self
, styledata
, graph
, column
, data
, index
):
238 if column
in [c
+ "min" for c
, m
in styledata
.rangeposcolumns
if m
& self
.mask_min
]:
239 graph
.axes
[column
[:-3]].adjustrange(data
, index
)
240 if column
in [c
+ "max" for c
, m
in styledata
.rangeposcolumns
if m
& self
.mask_max
]:
241 graph
.axes
[column
[:-3]].adjustrange(data
, index
)
243 # delta handling: fill rangeposdeltacolumns
244 if column
in [c
for c
, m
in styledata
.rangeposcolumns
if m
& (self
.mask_dmin | self
.mask_dmax | self
.mask_d
)]:
245 styledata
.rangeposdeltacolumns
[column
][self
.mask_value
] = data
, index
246 if column
in ["d" + c
+ "min" for c
, m
in styledata
.rangeposcolumns
if m
& self
.mask_dmin
]:
247 styledata
.rangeposdeltacolumns
[column
[1:-3]][self
.mask_dmin
] = data
, index
248 if column
in ["d" + c
+ "max" for c
, m
in styledata
.rangeposcolumns
if m
& self
.mask_dmax
]:
249 styledata
.rangeposdeltacolumns
[column
[1:-3]][self
.mask_dmax
] = data
, index
250 if column
in ["d" + c
for c
, m
in styledata
.rangeposcolumns
if m
& self
.mask_d
]:
251 styledata
.rangeposdeltacolumns
[column
[1:]][self
.mask_d
] = data
, index
253 # delta handling: process rangeposdeltacolumns
254 for c
, d
in styledata
.rangeposdeltacolumns
.items():
255 if d
.has_key(self
.mask_value
):
257 if k
!= self
.mask_value
:
258 if k
& (self
.mask_dmin | self
.mask_d
):
259 graph
.axes
[c
].adjustrange(d
[self
.mask_value
][0], d
[self
.mask_value
][1],
260 deltamindata
=d
[k
][0], deltaminindex
=d
[k
][1])
261 if k
& (self
.mask_dmax | self
.mask_d
):
262 graph
.axes
[c
].adjustrange(d
[self
.mask_value
][0], d
[self
.mask_value
][1],
263 deltamaxdata
=d
[k
][0], deltamaxindex
=d
[k
][1])
266 def initdrawpoints(self
, styledata
, graph
):
267 styledata
.vrange
= [[None for x
in range(2)] for y
in styledata
.rangeposcolumns
+ styledata
.vrangemissing
]
268 styledata
.rangepostmplist
= [[column
, mask
, index
, graph
.axes
[column
]] # temporarily used by drawpoint only
269 for index
, (column
, mask
) in enumerate(styledata
.rangeposcolumns
)]
270 for missing
in styledata
.vrangemissing
:
271 for rangepostmp
in styledata
.rangepostmplist
:
272 if rangepostmp
[2] >= missing
:
275 def drawpoint(self
, styledata
, graph
):
276 for column
, mask
, index
, axis
in styledata
.rangepostmplist
:
278 if mask
& self
.mask_min
:
279 styledata
.vrange
[index
][0] = axis
.convert(styledata
.point
[column
+ "min"])
280 if mask
& self
.mask_dmin
:
281 styledata
.vrange
[index
][0] = axis
.convert(styledata
.point
[column
] - styledata
.point
["d" + column
+ "min"])
282 if mask
& self
.mask_d
:
283 styledata
.vrange
[index
][0] = axis
.convert(styledata
.point
[column
] - styledata
.point
["d" + column
])
284 except (ArithmeticError, ValueError, TypeError):
285 styledata
.vrange
[index
][0] = None
287 if mask
& self
.mask_max
:
288 styledata
.vrange
[index
][1] = axis
.convert(styledata
.point
[column
+ "max"])
289 if mask
& self
.mask_dmax
:
290 styledata
.vrange
[index
][1] = axis
.convert(styledata
.point
[column
] + styledata
.point
["d" + column
+ "max"])
291 if mask
& self
.mask_d
:
292 styledata
.vrange
[index
][1] = axis
.convert(styledata
.point
[column
] + styledata
.point
["d" + column
])
293 except (ArithmeticError, ValueError, TypeError):
294 styledata
.vrange
[index
][1] = None
296 # some range checks for data consistency
297 if (styledata
.vrange
[index
][0] is not None and styledata
.vrange
[index
][1] is not None and
298 styledata
.vrange
[index
][0] > styledata
.vrange
[index
][1] + self
.epsilon
):
299 raise ValueError("negative errorbar range")
300 if (styledata
.vrange
[index
][0] is not None and styledata
.vpos
[index
] is not None and
301 styledata
.vrange
[index
][0] > styledata
.vpos
[index
] + self
.epsilon
):
302 raise ValueError("negative minimum errorbar")
303 if (styledata
.vrange
[index
][1] is not None and styledata
.vpos
[index
] is not None and
304 styledata
.vrange
[index
][1] < styledata
.vpos
[index
] - self
.epsilon
):
305 raise ValueError("negative maximum errorbar")
308 provider
["vrange"] = provider
["vrangemissing"] = _range()
311 def _crosssymbol(c
, x_pt
, y_pt
, size_pt
, attrs
):
312 c
.draw(path
.path(path
.moveto_pt(x_pt
-0.5*size_pt
, y_pt
-0.5*size_pt
),
313 path
.lineto_pt(x_pt
+0.5*size_pt
, y_pt
+0.5*size_pt
),
314 path
.moveto_pt(x_pt
-0.5*size_pt
, y_pt
+0.5*size_pt
),
315 path
.lineto_pt(x_pt
+0.5*size_pt
, y_pt
-0.5*size_pt
)), attrs
)
317 def _plussymbol(c
, x_pt
, y_pt
, size_pt
, attrs
):
318 c
.draw(path
.path(path
.moveto_pt(x_pt
-0.707106781*size_pt
, y_pt
),
319 path
.lineto_pt(x_pt
+0.707106781*size_pt
, y_pt
),
320 path
.moveto_pt(x_pt
, y_pt
-0.707106781*size_pt
),
321 path
.lineto_pt(x_pt
, y_pt
+0.707106781*size_pt
)), attrs
)
323 def _squaresymbol(c
, x_pt
, y_pt
, size_pt
, attrs
):
324 c
.draw(path
.path(path
.moveto_pt(x_pt
-0.5*size_pt
, y_pt
-0.5*size_pt
),
325 path
.lineto_pt(x_pt
+0.5*size_pt
, y_pt
-0.5*size_pt
),
326 path
.lineto_pt(x_pt
+0.5*size_pt
, y_pt
+0.5*size_pt
),
327 path
.lineto_pt(x_pt
-0.5*size_pt
, y_pt
+0.5*size_pt
),
328 path
.closepath()), attrs
)
330 def _trianglesymbol(c
, x_pt
, y_pt
, size_pt
, attrs
):
331 c
.draw(path
.path(path
.moveto_pt(x_pt
-0.759835685*size_pt
, y_pt
-0.438691337*size_pt
),
332 path
.lineto_pt(x_pt
+0.759835685*size_pt
, y_pt
-0.438691337*size_pt
),
333 path
.lineto_pt(x_pt
, y_pt
+0.877382675*size_pt
),
334 path
.closepath()), attrs
)
336 def _circlesymbol(c
, x_pt
, y_pt
, size_pt
, attrs
):
337 c
.draw(path
.path(path
.arc_pt(x_pt
, y_pt
, 0.564189583*size_pt
, 0, 360),
338 path
.closepath()), attrs
)
340 def _diamondsymbol(c
, x_pt
, y_pt
, size_pt
, attrs
):
341 c
.draw(path
.path(path
.moveto_pt(x_pt
-0.537284965*size_pt
, y_pt
),
342 path
.lineto_pt(x_pt
, y_pt
-0.930604859*size_pt
),
343 path
.lineto_pt(x_pt
+0.537284965*size_pt
, y_pt
),
344 path
.lineto_pt(x_pt
, y_pt
+0.930604859*size_pt
),
345 path
.closepath()), attrs
)
348 class _styleneedingpointpos(_style
):
350 need
= ["vposmissing"]
352 def columns(self
, styledata
, graph
, columns
):
353 if len(styledata
.vposmissing
):
354 raise ValueError("position columns incomplete")
358 class symbol(_styleneedingpointpos
):
360 need
= ["vpos", "vposmissing", "vposvalid"]
362 # insert symbols like staticmethods
365 square
= _squaresymbol
366 triangle
= _trianglesymbol
367 circle
= _circlesymbol
368 diamond
= _diamondsymbol
370 changecross
= attr
.changelist([cross
, plus
, square
, triangle
, circle
, diamond
])
371 changeplus
= attr
.changelist([plus
, square
, triangle
, circle
, diamond
, cross
])
372 changesquare
= attr
.changelist([square
, triangle
, circle
, diamond
, cross
, plus
])
373 changetriangle
= attr
.changelist([triangle
, circle
, diamond
, cross
, plus
, square
])
374 changecircle
= attr
.changelist([circle
, diamond
, cross
, plus
, square
, triangle
])
375 changediamond
= attr
.changelist([diamond
, cross
, plus
, square
, triangle
, circle
])
376 changesquaretwice
= attr
.changelist([square
, square
, triangle
, triangle
, circle
, circle
, diamond
, diamond
])
377 changetriangletwice
= attr
.changelist([triangle
, triangle
, circle
, circle
, diamond
, diamond
, square
, square
])
378 changecircletwice
= attr
.changelist([circle
, circle
, diamond
, diamond
, square
, square
, triangle
, triangle
])
379 changediamondtwice
= attr
.changelist([diamond
, diamond
, square
, square
, triangle
, triangle
, circle
, circle
])
381 changestrokedfilled
= attr
.changelist([deco
.stroked
, deco
.filled
])
382 changefilledstroked
= attr
.changelist([deco
.filled
, deco
.stroked
])
384 defaultsymbolattrs
= [deco
.stroked
]
386 def __init__(self
, symbol
=changecross
, size
=0.2*unit
.v_cm
, symbolattrs
=[]):
389 self
.symbolattrs
= symbolattrs
391 def selectstyle(self
, styledata
, graph
, selectindex
, selecttotal
):
392 styledata
.symbol
= attr
.selectattr(self
.symbol
, selectindex
, selecttotal
)
393 styledata
.size_pt
= unit
.topt(attr
.selectattr(self
.size
, selectindex
, selecttotal
))
394 if self
.symbolattrs
is not None:
395 styledata
.symbolattrs
= attr
.selectattrs(self
.defaultsymbolattrs
+ self
.symbolattrs
, selectindex
, selecttotal
)
397 styledata
.symbolattrs
= None
399 def initdrawpoints(self
, styledata
, graph
):
400 styledata
.symbolcanvas
= graph
.insert(canvas
.canvas())
402 def drawpoint(self
, styledata
, graph
):
403 if styledata
.vposvalid
and styledata
.symbolattrs
is not None:
404 xpos
, ypos
= graph
.vpos_pt(*styledata
.vpos
)
405 styledata
.symbol(styledata
.symbolcanvas
, xpos
, ypos
, styledata
.size_pt
, styledata
.symbolattrs
)
407 def key_pt(self
, styledata
, graph
, x_pt
, y_pt
, width_pt
, height_pt
):
408 if styledata
.symbolattrs
is not None:
409 styledata
.symbol(graph
, x_pt
+0.5*width_pt
, y_pt
+0.5*height_pt
, styledata
.size_pt
, styledata
.symbolattrs
)
412 class line(_styleneedingpointpos
):
414 need
= ["vpos", "vposmissing", "vposavailable", "vposvalid"]
416 changelinestyle
= attr
.changelist([style
.linestyle
.solid
,
417 style
.linestyle
.dashed
,
418 style
.linestyle
.dotted
,
419 style
.linestyle
.dashdotted
])
421 defaultlineattrs
= [changelinestyle
]
423 def __init__(self
, lineattrs
=[]):
424 self
.lineattrs
= lineattrs
426 def selectstyle(self
, styledata
, graph
, selectindex
, selecttotal
):
427 styledata
.lineattrs
= attr
.selectattrs(self
.defaultlineattrs
+ self
.lineattrs
, selectindex
, selecttotal
)
429 def initdrawpoints(self
, styledata
, graph
):
430 styledata
.linecanvas
= graph
.insert(canvas
.canvas())
431 if styledata
.lineattrs
is not None:
432 styledata
.linecanvas
.set(styledata
.lineattrs
)
433 styledata
.path
= path
.path()
434 styledata
.linebasepoints
= []
435 styledata
.lastvpos
= None
437 def addpointstopath(self
, styledata
):
438 # add baselinepoints to styledata.path
439 if len(styledata
.linebasepoints
) > 1:
440 styledata
.path
.append(path
.moveto_pt(*styledata
.linebasepoints
[0]))
441 if len(styledata
.linebasepoints
) > 2:
442 styledata
.path
.append(path
.multilineto_pt(styledata
.linebasepoints
[1:]))
444 styledata
.path
.append(path
.lineto_pt(*styledata
.linebasepoints
[1]))
445 styledata
.linebasepoints
= []
447 def drawpoint(self
, styledata
, graph
):
448 # append linebasepoints
449 if styledata
.vposavailable
:
450 if len(styledata
.linebasepoints
):
451 # the last point was inside the graph
452 if styledata
.vposvalid
: # shortcut for the common case
453 styledata
.linebasepoints
.append(graph
.vpos_pt(*styledata
.vpos
))
457 for vstart
, vend
in zip(styledata
.lastvpos
, styledata
.vpos
):
460 # 1 = vstart + (vend - vstart) * cut
462 newcut
= (1 - vstart
)/(vend
- vstart
)
463 except ArithmeticError:
466 # 0 = vstart + (vend - vstart) * cut
468 newcut
= - vstart
/(vend
- vstart
)
469 except ArithmeticError:
471 if newcut
is not None and newcut
< cut
:
475 for vstart
, vend
in zip(styledata
.lastvpos
, styledata
.vpos
):
476 cutvpos
.append(vstart
+ (vend
- vstart
) * cut
)
477 styledata
.linebasepoints
.append(styledata
.graph
.vpos_pt(*cutvpos
))
478 self
.addpointstopath(styledata
)
480 # the last point was outside the graph
481 if styledata
.lastvpos
is not None:
482 if styledata
.vposvalid
:
485 for vstart
, vend
in zip(styledata
.lastvpos
, styledata
.vpos
):
488 # 1 = vstart + (vend - vstart) * cut
490 newcut
= (1 - vstart
)/(vend
- vstart
)
491 except ArithmeticError:
494 # 0 = vstart + (vend - vstart) * cut
496 newcut
= - vstart
/(vend
- vstart
)
497 except ArithmeticError:
499 if newcut
is not None and newcut
> cut
:
503 for vstart
, vend
in zip(styledata
.lastvpos
, styledata
.vpos
):
504 cutvpos
.append(vstart
+ (vend
- vstart
) * cut
)
505 styledata
.linebasepoints
.append(graph
.vpos_pt(*cutvpos
))
506 styledata
.linebasepoints
.append(graph
.vpos_pt(*styledata
.vpos
))
508 # sometimes cut beginning and end
511 for vstart
, vend
in zip(styledata
.lastvpos
, styledata
.vpos
):
516 # 1 = vstart + (vend - vstart) * cutfrom
518 newcutfrom
= (1 - vstart
)/(vend
- vstart
)
519 except ArithmeticError:
524 # 0 = vstart + (vend - vstart) * cutfrom
526 newcutfrom
= - vstart
/(vend
- vstart
)
527 except ArithmeticError:
529 if newcutfrom
is not None and newcutfrom
> cutfrom
:
533 # 1 = vstart + (vend - vstart) * cutto
535 newcutto
= (1 - vstart
)/(vend
- vstart
)
536 except ArithmeticError:
539 # 0 = vstart + (vend - vstart) * cutto
541 newcutto
= - vstart
/(vend
- vstart
)
542 except ArithmeticError:
544 if newcutto
is not None and newcutto
< cutto
:
550 for vstart
, vend
in zip(styledata
.lastvpos
, styledata
.vpos
):
551 cutfromvpos
.append(vstart
+ (vend
- vstart
) * cutfrom
)
552 cuttovpos
.append(vstart
+ (vend
- vstart
) * cutto
)
553 styledata
.linebasepoints
.append(styledata
.graph
.vpos_pt(*cutfromvpos
))
554 styledata
.linebasepoints
.append(styledata
.graph
.vpos_pt(*cuttovpos
))
555 self
.addpointstopath(styledata
)
556 styledata
.lastvpos
= styledata
.vpos
[:]
558 if len(styledata
.linebasepoints
) > 1:
559 self
.addpointstopath(styledata
)
560 styledata
.lastvpos
= None
562 def donedrawpoints(self
, styledata
, graph
):
563 if len(styledata
.linebasepoints
) > 1:
564 self
.addpointstopath(styledata
)
565 if styledata
.lineattrs
is not None and len(styledata
.path
.path
):
566 styledata
.linecanvas
.stroke(styledata
.path
)
568 def key_pt(self
, styledata
, graph
, x_pt
, y_pt
, width_pt
, height_pt
):
569 if styledata
.lineattrs
is not None:
570 graph
.stroke(path
.line_pt(x_pt
, y_pt
+0.5*height_pt
, x_pt
+width_pt
, y_pt
+0.5*height_pt
), styledata
.lineattrs
)
573 class errorbar(_style
):
575 need
= ["vpos", "vposmissing", "vposavailable", "vposvalid", "vrange", "vrangemissing"]
577 defaulterrorbarattrs
= []
579 def __init__(self
, size
=0.1*unit
.v_cm
,
583 self
.errorbarattrs
= errorbarattrs
584 self
.epsilon
= epsilon
586 def columns(self
, styledata
, graph
, columns
):
587 for i
in styledata
.vposmissing
:
588 if i
in styledata
.vrangemissing
:
589 raise ValueError("position and range for a graph dimension missing")
592 def selectstyle(self
, styledata
, graph
, selectindex
, selecttotal
):
593 styledata
.errorsize_pt
= unit
.topt(attr
.selectattr(self
.size
, selectindex
, selecttotal
))
594 styledata
.errorbarattrs
= attr
.selectattrs(self
.defaulterrorbarattrs
+ self
.errorbarattrs
, selectindex
, selecttotal
)
596 def initdrawpoints(self
, styledata
, graph
):
597 styledata
.errorbarcanvas
= graph
.insert(canvas
.canvas())
598 if styledata
.errorbarattrs
is not None:
599 styledata
.errorbarcanvas
.set(styledata
.errorbarattrs
)
600 styledata
.dimensionlist
= range(len(styledata
.vpos
))
602 def drawpoint(self
, styledata
, graph
):
603 if styledata
.errorbarattrs
is None:
605 for i
in styledata
.dimensionlist
:
606 for j
in styledata
.dimensionlist
:
608 (styledata
.vpos
[j
] is None or
609 styledata
.vpos
[j
] < -self
.epsilon
or
610 styledata
.vpos
[j
] > 1+self
.epsilon
)):
613 if ((styledata
.vrange
[i
][0] is None and styledata
.vpos
[i
] is None) or
614 (styledata
.vrange
[i
][1] is None and styledata
.vpos
[i
] is None) or
615 (styledata
.vrange
[i
][0] is None and styledata
.vrange
[i
][1] is None)):
617 vminpos
= styledata
.vpos
[:]
618 if styledata
.vrange
[i
][0] is not None:
619 vminpos
[i
] = styledata
.vrange
[i
][0]
623 if vminpos
[i
] > 1+self
.epsilon
:
625 if vminpos
[i
] < -self
.epsilon
:
628 vmaxpos
= styledata
.vpos
[:]
629 if styledata
.vrange
[i
][1] is not None:
630 vmaxpos
[i
] = styledata
.vrange
[i
][1]
634 if vmaxpos
[i
] < -self
.epsilon
:
636 if vmaxpos
[i
] > 1+self
.epsilon
:
639 styledata
.errorbarcanvas
.stroke(graph
.vgeodesic(*(vminpos
+ vmaxpos
)))
640 for j
in styledata
.dimensionlist
:
643 styledata
.errorbarcanvas
.stroke(graph
.vcap_pt(j
, styledata
.errorsize_pt
, *vminpos
))
645 styledata
.errorbarcanvas
.stroke(graph
.vcap_pt(j
, styledata
.errorsize_pt
, *vmaxpos
))
648 # not yet ported to the new style scheme
650 # class text(symbol):
652 # defaulttextattrs = [textmodule.halign.center, textmodule.vshift.mathaxis]
654 # def __init__(self, textdx=0*unit.v_cm, textdy=0.3*unit.v_cm, textattrs=[], **kwargs):
655 # self.textdx = textdx
656 # self.textdy = textdy
657 # self.textattrs = textattrs
658 # symbol.__init__(self, **kwargs)
660 # def setdata(self, graph, columns, styledata):
661 # columns = columns.copy()
662 # styledata.textindex = columns["text"]
663 # del columns["text"]
664 # return symbol.setdata(self, graph, columns, styledata)
666 # def selectstyle(self, selectindex, selecttotal, styledata):
667 # if self.textattrs is not None:
668 # styledata.textattrs = attr.selectattrs(self.defaulttextattrs + self.textattrs, selectindex, selecttotal)
670 # styledata.textattrs = None
671 # symbol.selectstyle(self, selectindex, selecttotal, styledata)
673 # def drawsymbol_pt(self, c, x, y, styledata, point=None):
674 # symbol.drawsymbol_pt(self, c, x, y, styledata, point)
675 # if None not in (x, y, point[styledata.textindex]) and styledata.textattrs is not None:
676 # c.text_pt(x + styledata.textdx_pt, y + styledata.textdy_pt, str(point[styledata.textindex]), styledata.textattrs)
678 # def drawpoints(self, points, graph, styledata):
679 # styledata.textdx_pt = unit.topt(self.textdx)
680 # styledata.textdy_pt = unit.topt(self.textdy)
681 # symbol.drawpoints(self, points, graph, styledata)
684 # class arrow(_style):
686 # defaultlineattrs = []
687 # defaultarrowattrs = []
689 # def __init__(self, linelength=0.25*unit.v_cm, arrowsize=0.15*unit.v_cm, lineattrs=[], arrowattrs=[], epsilon=1e-10):
690 # self.linelength = linelength
691 # self.arrowsize = arrowsize
692 # self.lineattrs = lineattrs
693 # self.arrowattrs = arrowattrs
694 # self.epsilon = epsilon
696 # def setdata(self, graph, columns, styledata):
697 # if len(graph.axisnames) != 2:
698 # raise TypeError("arrow style restricted on two-dimensional graphs")
699 # columns = columns.copy()
700 # styledata.xaxis, styledata.xindex = _style.setdatapattern(self, graph, columns, re.compile(r"(%s([2-9]|[1-9][0-9]+)?)$" % graph.axisnames[0]))
701 # styledata.yaxis, styledata.yindex = _style.setdatapattern(self, graph, columns, re.compile(r"(%s([2-9]|[1-9][0-9]+)?)$" % graph.axisnames[1]))
702 # styledata.sizeindex = columns["size"]
703 # del columns["size"]
704 # styledata.angleindex = columns["angle"]
705 # del columns["angle"]
708 # def adjustaxes(self, points, columns, styledata):
709 # if styledata.xindex in columns:
710 # styledata.xaxis.adjustrange(points, styledata.xindex)
711 # if styledata.yindex in columns:
712 # styledata.yaxis.adjustrange(points, styledata.yindex)
714 # def selectstyle(self, selectindex, selecttotal, styledata):
715 # if self.lineattrs is not None:
716 # styledata.lineattrs = attr.selectattrs(self.defaultlineattrs + self.lineattrs, selectindex, selecttotal)
718 # styledata.lineattrs = None
719 # if self.arrowattrs is not None:
720 # styledata.arrowattrs = attr.selectattrs(self.defaultarrowattrs + self.arrowattrs, selectindex, selecttotal)
722 # styledata.arrowattrs = None
724 # def drawpoints(self, points, graph, styledata):
725 # if styledata.lineattrs is not None and styledata.arrowattrs is not None:
726 # linelength_pt = unit.topt(self.linelength)
727 # for point in points:
728 # xpos, ypos = graph.pos_pt(point[styledata.xindex], point[styledata.yindex], xaxis=styledata.xaxis, yaxis=styledata.yaxis)
729 # if point[styledata.sizeindex] > self.epsilon:
730 # dx = math.cos(point[styledata.angleindex]*math.pi/180)
731 # dy = math.sin(point[styledata.angleindex]*math.pi/180)
732 # x1 = xpos-0.5*dx*linelength_pt*point[styledata.sizeindex]
733 # y1 = ypos-0.5*dy*linelength_pt*point[styledata.sizeindex]
734 # x2 = xpos+0.5*dx*linelength_pt*point[styledata.sizeindex]
735 # y2 = ypos+0.5*dy*linelength_pt*point[styledata.sizeindex]
736 # graph.stroke(path.line_pt(x1, y1, x2, y2), styledata.lineattrs +
737 # [deco.earrow(styledata.arrowattrs, size=self.arrowsize*point[styledata.sizeindex])])
740 # class rect(_style):
742 # def __init__(self, palette=color.palette.Gray):
743 # self.palette = palette
745 # def setdata(self, graph, columns, styledata):
746 # if len(graph.axisnames) != 2:
747 # raise TypeError("arrow style restricted on two-dimensional graphs")
748 # columns = columns.copy()
749 # styledata.xaxis, styledata.xminindex = _style.setdatapattern(self, graph, columns, re.compile(r"(%s([2-9]|[1-9][0-9]+)?)min$" % graph.axisnames[0]))
750 # styledata.yaxis, styledata.yminindex = _style.setdatapattern(self, graph, columns, re.compile(r"(%s([2-9]|[1-9][0-9]+)?)min$" % graph.axisnames[1]))
751 # xaxis, styledata.xmaxindex = _style.setdatapattern(self, graph, columns, re.compile(r"(%s([2-9]|[1-9][0-9]+)?)max$" % graph.axisnames[0]))
752 # yaxis, styledata.ymaxindex = _style.setdatapattern(self, graph, columns, re.compile(r"(%s([2-9]|[1-9][0-9]+)?)max$" % graph.axisnames[1]))
753 # if xaxis != styledata.xaxis or yaxis != styledata.yaxis:
754 # raise ValueError("min/max values should use the same axes")
755 # styledata.colorindex = columns["color"]
756 # del columns["color"]
759 # def selectstyle(self, selectindex, selecttotal, styledata):
762 # def adjustaxes(self, points, columns, styledata):
763 # if styledata.xminindex in columns:
764 # styledata.xaxis.adjustrange(points, styledata.xminindex)
765 # if styledata.xmaxindex in columns:
766 # styledata.xaxis.adjustrange(points, styledata.xmaxindex)
767 # if styledata.yminindex in columns:
768 # styledata.yaxis.adjustrange(points, styledata.yminindex)
769 # if styledata.ymaxindex in columns:
770 # styledata.yaxis.adjustrange(points, styledata.ymaxindex)
772 # def drawpoints(self, points, graph, styledata):
773 # # TODO: bbox shortcut
774 # c = graph.insert(canvas.canvas())
775 # lastcolorvalue = None
776 # for point in points:
778 # xvmin = styledata.xaxis.convert(point[styledata.xminindex])
779 # xvmax = styledata.xaxis.convert(point[styledata.xmaxindex])
780 # yvmin = styledata.yaxis.convert(point[styledata.yminindex])
781 # yvmax = styledata.yaxis.convert(point[styledata.ymaxindex])
782 # colorvalue = point[styledata.colorindex]
783 # if colorvalue != lastcolorvalue:
784 # color = self.palette.getcolor(point[styledata.colorindex])
787 # if ((xvmin < 0 and xvmax < 0) or (xvmin > 1 and xvmax > 1) or
788 # (yvmin < 0 and yvmax < 0) or (yvmin > 1 and yvmax > 1)):
806 # p = graph.vgeodesic(xvmin, yvmin, xvmax, yvmin)
807 # p.append(graph.vgeodesic_el(xvmax, yvmin, xvmax, yvmax))
808 # p.append(graph.vgeodesic_el(xvmax, yvmax, xvmin, yvmax))
809 # p.append(graph.vgeodesic_el(xvmin, yvmax, xvmin, yvmin))
810 # p.append(path.closepath())
811 # if colorvalue != lastcolorvalue:
818 # defaultfrompathattrs = []
819 # defaultbarattrs = [color.palette.Rainbow, deco.stroked([color.gray.black])]
821 # def __init__(self, fromvalue=None, frompathattrs=[], barattrs=[], subnames=None, epsilon=1e-10):
822 # self.fromvalue = fromvalue
823 # self.frompathattrs = frompathattrs
824 # self.barattrs = barattrs
825 # self.subnames = subnames
826 # self.epsilon = epsilon
828 # def setdata(self, graph, columns, styledata):
829 # # TODO: remove limitation to 2d graphs
830 # if len(graph.axisnames) != 2:
831 # raise TypeError("arrow style currently restricted on two-dimensional graphs")
832 # columns = columns.copy()
833 # xvalue = _style.setdatapattern(self, graph, columns, re.compile(r"(%s([2-9]|[1-9][0-9]+)?)$" % graph.axisnames[0]))
834 # yvalue = _style.setdatapattern(self, graph, columns, re.compile(r"(%s([2-9]|[1-9][0-9]+)?)$" % graph.axisnames[1]))
835 # if (xvalue is None and yvalue is None) or (xvalue is not None and yvalue is not None):
836 # raise TypeError("must specify exactly one value axis")
837 # if xvalue is not None:
838 # styledata.valuepos = 0
839 # styledata.nameaxis, styledata.nameindex = _style.setdatapattern(self, graph, columns, re.compile(r"(%s([2-9]|[1-9][0-9]+)?)name$" % graph.axisnames[1]))
840 # styledata.valueaxis = xvalue[0]
841 # styledata.valueindices = [xvalue[1]]
843 # styledata.valuepos = 1
844 # styledata.nameaxis, styledata.nameindex = _style.setdatapattern(self, graph, columns, re.compile(r"(%s([2-9]|[1-9][0-9]+)?)name$" % graph.axisnames[0]))
845 # styledata.valueaxis = yvalue[0]
846 # styledata.valueindices = [yvalue[1]]
850 # valueaxis, valueindex = _style.setdatapattern(self, graph, columns, re.compile(r"(%s([2-9]|[1-9][0-9]+)?)stack%i$" % (graph.axisnames[styledata.valuepos], i)))
853 # if styledata.valueaxis != valueaxis:
854 # raise ValueError("different value axes for stacked bars")
855 # styledata.valueindices.append(valueindex)
859 # def selectstyle(self, selectindex, selecttotal, styledata):
861 # styledata.frompathattrs = None
863 # styledata.frompathattrs = self.defaultfrompathattrs + self.frompathattrs
864 # if selecttotal > 1:
865 # if self.barattrs is not None:
866 # styledata.barattrs = attr.selectattrs(self.defaultbarattrs + self.barattrs, selectindex, selecttotal)
868 # styledata.barattrs = None
870 # styledata.barattrs = self.defaultbarattrs + self.barattrs
871 # styledata.selectindex = selectindex
872 # styledata.selecttotal = selecttotal
873 # if styledata.selecttotal != 1 and self.subnames is not None:
874 # raise ValueError("subnames not allowed when iterating over bars")
876 # def adjustaxes(self, points, columns, styledata):
877 # if styledata.nameindex in columns:
878 # if styledata.selecttotal == 1:
879 # styledata.nameaxis.adjustrange(points, styledata.nameindex, subnames=self.subnames)
881 # for i in range(styledata.selecttotal):
882 # styledata.nameaxis.adjustrange(points, styledata.nameindex, subnames=[i])
883 # for valueindex in styledata.valueindices:
884 # if valueindex in columns:
885 # styledata.valueaxis.adjustrange(points, valueindex)
887 # def drawpoints(self, points, graph, styledata):
888 # if self.fromvalue is not None:
889 # vfromvalue = styledata.valueaxis.convert(self.fromvalue)
890 # if vfromvalue < -self.epsilon:
892 # if vfromvalue > 1 + self.epsilon:
894 # if styledata.frompathattrs is not None and vfromvalue > self.epsilon and vfromvalue < 1 - self.epsilon:
895 # if styledata.valuepos:
896 # p = graph.vgeodesic(0, vfromvalue, 1, vfromvalue)
898 # p = graph.vgeodesic(vfromvalue, 0, vfromvalue, 1)
899 # graph.stroke(p, styledata.frompathattrs)
902 # l = len(styledata.valueindices)
906 # barattrslist.append(attr.selectattrs(styledata.barattrs, i, l))
908 # barattrslist = [styledata.barattrs]
909 # for point in points:
910 # vvaluemax = vfromvalue
911 # for valueindex, barattrs in zip(styledata.valueindices, barattrslist):
912 # vvaluemin = vvaluemax
914 # vvaluemax = styledata.valueaxis.convert(point[valueindex])
918 # if styledata.selecttotal == 1:
920 # vnamemin = styledata.nameaxis.convert((point[styledata.nameindex], 0))
924 # vnamemax = styledata.nameaxis.convert((point[styledata.nameindex], 1))
929 # vnamemin = styledata.nameaxis.convert((point[styledata.nameindex], styledata.selectindex, 0))
933 # vnamemax = styledata.nameaxis.convert((point[styledata.nameindex], styledata.selectindex, 1))
937 # if styledata.valuepos:
938 # p = graph.vgeodesic(vnamemin, vvaluemin, vnamemin, vvaluemax)
939 # p.append(graph.vgeodesic_el(vnamemin, vvaluemax, vnamemax, vvaluemax))
940 # p.append(graph.vgeodesic_el(vnamemax, vvaluemax, vnamemax, vvaluemin))
941 # p.append(graph.vgeodesic_el(vnamemax, vvaluemin, vnamemin, vvaluemin))
942 # p.append(path.closepath())
944 # p = graph.vgeodesic(vvaluemin, vnamemin, vvaluemin, vnamemax)
945 # p.append(graph.vgeodesic_el(vvaluemin, vnamemax, vvaluemax, vnamemax))
946 # p.append(graph.vgeodesic_el(vvaluemax, vnamemax, vvaluemax, vnamemin))
947 # p.append(graph.vgeodesic_el(vvaluemax, vnamemin, vvaluemin, vnamemin))
948 # p.append(path.closepath())
949 # if barattrs is not None:
950 # graph.fill(p, barattrs)
952 # def key_pt(self, c, x_pt, y_pt, width_pt, height_pt, styledata):
953 # l = len(styledata.valueindices)
956 # c.fill(path.rect_pt(x_pt+i*width_pt/l, y_pt, width_pt/l, height_pt), attr.selectattrs(styledata.barattrs, i, l))
958 # c.fill(path.rect_pt(x_pt, y_pt, width_pt, height_pt), styledata.barattrs)