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"]
370 # note, that statements like cross = _crosssymbol are
371 # invalid, since the would lead to unbound methods, but
372 # a single entry changeable list does the trick
373 cross
= attr
.changelist([_crosssymbol
])
374 plus
= attr
.changelist([_plussymbol
])
375 square
= attr
.changelist([_squaresymbol
])
376 triangle
= attr
.changelist([_trianglesymbol
])
377 circle
= attr
.changelist([_circlesymbol
])
378 diamond
= attr
.changelist([_diamondsymbol
])
380 changecross
= attr
.changelist([_crosssymbol
, _plussymbol
, _squaresymbol
, _trianglesymbol
, _circlesymbol
, _diamondsymbol
])
381 changeplus
= attr
.changelist([_plussymbol
, _squaresymbol
, _trianglesymbol
, _circlesymbol
, _diamondsymbol
, cross
])
382 changesquare
= attr
.changelist([_squaresymbol
, _trianglesymbol
, _circlesymbol
, _diamondsymbol
, cross
, _plussymbol
])
383 changetriangle
= attr
.changelist([_trianglesymbol
, _circlesymbol
, _diamondsymbol
, cross
, _plussymbol
, _squaresymbol
])
384 changecircle
= attr
.changelist([_circlesymbol
, _diamondsymbol
, cross
, _plussymbol
, _squaresymbol
, _trianglesymbol
])
385 changediamond
= attr
.changelist([_diamondsymbol
, cross
, _plussymbol
, _squaresymbol
, _trianglesymbol
, _circlesymbol
])
386 changesquaretwice
= attr
.changelist([_squaresymbol
, _squaresymbol
, _trianglesymbol
, _trianglesymbol
, _circlesymbol
, _circlesymbol
, _diamondsymbol
, _diamondsymbol
])
387 changetriangletwice
= attr
.changelist([_trianglesymbol
, _trianglesymbol
, _circlesymbol
, _circlesymbol
, _diamondsymbol
, _diamondsymbol
, _squaresymbol
, _squaresymbol
])
388 changecircletwice
= attr
.changelist([_circlesymbol
, _circlesymbol
, _diamondsymbol
, _diamondsymbol
, _squaresymbol
, _squaresymbol
, _trianglesymbol
, _trianglesymbol
])
389 changediamondtwice
= attr
.changelist([_diamondsymbol
, _diamondsymbol
, _squaresymbol
, _squaresymbol
, _trianglesymbol
, _trianglesymbol
, _circlesymbol
, _circlesymbol
])
391 changestrokedfilled
= attr
.changelist([deco
.stroked
, deco
.filled
])
392 changefilledstroked
= attr
.changelist([deco
.filled
, deco
.stroked
])
394 defaultsymbolattrs
= [deco
.stroked
]
396 def __init__(self
, symbol
=changecross
, size
=0.2*unit
.v_cm
, symbolattrs
=[]):
399 self
.symbolattrs
= symbolattrs
401 def selectstyle(self
, styledata
, graph
, selectindex
, selecttotal
):
402 styledata
.symbol
= attr
.selectattr(self
.symbol
, selectindex
, selecttotal
)
403 styledata
.size_pt
= unit
.topt(attr
.selectattr(self
.size
, selectindex
, selecttotal
))
404 if self
.symbolattrs
is not None:
405 styledata
.symbolattrs
= attr
.selectattrs(self
.defaultsymbolattrs
+ self
.symbolattrs
, selectindex
, selecttotal
)
407 styledata
.symbolattrs
= None
409 def initdrawpoints(self
, styledata
, graph
):
410 styledata
.symbolcanvas
= graph
.insert(canvas
.canvas())
412 def drawpoint(self
, styledata
, graph
):
413 if styledata
.vposvalid
and styledata
.symbolattrs
is not None:
414 xpos
, ypos
= graph
.vpos_pt(*styledata
.vpos
)
415 styledata
.symbol(styledata
.symbolcanvas
, xpos
, ypos
, styledata
.size_pt
, styledata
.symbolattrs
)
417 def key_pt(self
, styledata
, graph
, x_pt
, y_pt
, width_pt
, height_pt
):
418 if styledata
.symbolattrs
is not None:
419 styledata
.symbol(graph
, x_pt
+0.5*width_pt
, y_pt
+0.5*height_pt
, styledata
.size_pt
, styledata
.symbolattrs
)
422 class line(_styleneedingpointpos
):
424 need
= ["vpos", "vposmissing", "vposavailable", "vposvalid"]
426 changelinestyle
= attr
.changelist([style
.linestyle
.solid
,
427 style
.linestyle
.dashed
,
428 style
.linestyle
.dotted
,
429 style
.linestyle
.dashdotted
])
431 defaultlineattrs
= [changelinestyle
]
433 def __init__(self
, lineattrs
=[]):
434 self
.lineattrs
= lineattrs
436 def selectstyle(self
, styledata
, graph
, selectindex
, selecttotal
):
437 styledata
.lineattrs
= attr
.selectattrs(self
.defaultlineattrs
+ self
.lineattrs
, selectindex
, selecttotal
)
439 def initdrawpoints(self
, styledata
, graph
):
440 styledata
.linecanvas
= graph
.insert(canvas
.canvas())
441 if styledata
.lineattrs
is not None:
442 styledata
.linecanvas
.set(styledata
.lineattrs
)
443 styledata
.path
= path
.path()
444 styledata
.linebasepoints
= []
445 styledata
.lastvpos
= None
447 def addpointstopath(self
, styledata
):
448 # add baselinepoints to styledata.path
449 if len(styledata
.linebasepoints
) > 1:
450 styledata
.path
.append(path
.moveto_pt(*styledata
.linebasepoints
[0]))
451 if len(styledata
.linebasepoints
) > 2:
452 styledata
.path
.append(path
.multilineto_pt(styledata
.linebasepoints
[1:]))
454 styledata
.path
.append(path
.lineto_pt(*styledata
.linebasepoints
[1]))
455 styledata
.linebasepoints
= []
457 def drawpoint(self
, styledata
, graph
):
458 # append linebasepoints
459 if styledata
.vposavailable
:
460 if len(styledata
.linebasepoints
):
461 # the last point was inside the graph
462 if styledata
.vposvalid
: # shortcut for the common case
463 styledata
.linebasepoints
.append(graph
.vpos_pt(*styledata
.vpos
))
467 for vstart
, vend
in zip(styledata
.lastvpos
, styledata
.vpos
):
470 # 1 = vstart + (vend - vstart) * cut
472 newcut
= (1 - vstart
)/(vend
- vstart
)
473 except ArithmeticError:
476 # 0 = vstart + (vend - vstart) * cut
478 newcut
= - vstart
/(vend
- vstart
)
479 except ArithmeticError:
481 if newcut
is not None and newcut
< cut
:
485 for vstart
, vend
in zip(styledata
.lastvpos
, styledata
.vpos
):
486 cutvpos
.append(vstart
+ (vend
- vstart
) * cut
)
487 styledata
.linebasepoints
.append(graph
.vpos_pt(*cutvpos
))
488 self
.addpointstopath(styledata
)
490 # the last point was outside the graph
491 if styledata
.lastvpos
is not None:
492 if styledata
.vposvalid
:
495 for vstart
, vend
in zip(styledata
.lastvpos
, styledata
.vpos
):
498 # 1 = vstart + (vend - vstart) * cut
500 newcut
= (1 - vstart
)/(vend
- vstart
)
501 except ArithmeticError:
504 # 0 = vstart + (vend - vstart) * cut
506 newcut
= - vstart
/(vend
- vstart
)
507 except ArithmeticError:
509 if newcut
is not None and newcut
> cut
:
513 for vstart
, vend
in zip(styledata
.lastvpos
, styledata
.vpos
):
514 cutvpos
.append(vstart
+ (vend
- vstart
) * cut
)
515 styledata
.linebasepoints
.append(graph
.vpos_pt(*cutvpos
))
516 styledata
.linebasepoints
.append(graph
.vpos_pt(*styledata
.vpos
))
518 # sometimes cut beginning and end
521 for vstart
, vend
in zip(styledata
.lastvpos
, styledata
.vpos
):
526 # 1 = vstart + (vend - vstart) * cutfrom
528 newcutfrom
= (1 - vstart
)/(vend
- vstart
)
529 except ArithmeticError:
534 # 0 = vstart + (vend - vstart) * cutfrom
536 newcutfrom
= - vstart
/(vend
- vstart
)
537 except ArithmeticError:
539 if newcutfrom
is not None and newcutfrom
> cutfrom
:
543 # 1 = vstart + (vend - vstart) * cutto
545 newcutto
= (1 - vstart
)/(vend
- vstart
)
546 except ArithmeticError:
549 # 0 = vstart + (vend - vstart) * cutto
551 newcutto
= - vstart
/(vend
- vstart
)
552 except ArithmeticError:
554 if newcutto
is not None and newcutto
< cutto
:
560 for vstart
, vend
in zip(styledata
.lastvpos
, styledata
.vpos
):
561 cutfromvpos
.append(vstart
+ (vend
- vstart
) * cutfrom
)
562 cuttovpos
.append(vstart
+ (vend
- vstart
) * cutto
)
563 styledata
.linebasepoints
.append(graph
.vpos_pt(*cutfromvpos
))
564 styledata
.linebasepoints
.append(graph
.vpos_pt(*cuttovpos
))
565 self
.addpointstopath(styledata
)
566 styledata
.lastvpos
= styledata
.vpos
[:]
568 if len(styledata
.linebasepoints
) > 1:
569 self
.addpointstopath(styledata
)
570 styledata
.lastvpos
= None
572 def donedrawpoints(self
, styledata
, graph
):
573 if len(styledata
.linebasepoints
) > 1:
574 self
.addpointstopath(styledata
)
575 if styledata
.lineattrs
is not None and len(styledata
.path
.path
):
576 styledata
.linecanvas
.stroke(styledata
.path
)
578 def key_pt(self
, styledata
, graph
, x_pt
, y_pt
, width_pt
, height_pt
):
579 if styledata
.lineattrs
is not None:
580 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
)
583 class errorbar(_style
):
585 need
= ["vpos", "vposmissing", "vposavailable", "vposvalid", "vrange", "vrangemissing"]
587 defaulterrorbarattrs
= []
589 def __init__(self
, size
=0.1*unit
.v_cm
,
593 self
.errorbarattrs
= errorbarattrs
594 self
.epsilon
= epsilon
596 def columns(self
, styledata
, graph
, columns
):
597 for i
in styledata
.vposmissing
:
598 if i
in styledata
.vrangemissing
:
599 raise ValueError("position and range for a graph dimension missing")
602 def selectstyle(self
, styledata
, graph
, selectindex
, selecttotal
):
603 styledata
.errorsize_pt
= unit
.topt(attr
.selectattr(self
.size
, selectindex
, selecttotal
))
604 styledata
.errorbarattrs
= attr
.selectattrs(self
.defaulterrorbarattrs
+ self
.errorbarattrs
, selectindex
, selecttotal
)
606 def initdrawpoints(self
, styledata
, graph
):
607 styledata
.errorbarcanvas
= graph
.insert(canvas
.canvas())
608 if styledata
.errorbarattrs
is not None:
609 styledata
.errorbarcanvas
.set(styledata
.errorbarattrs
)
610 styledata
.dimensionlist
= range(len(styledata
.vpos
))
612 def drawpoint(self
, styledata
, graph
):
613 if styledata
.errorbarattrs
is None:
615 for i
in styledata
.dimensionlist
:
616 for j
in styledata
.dimensionlist
:
618 (styledata
.vpos
[j
] is None or
619 styledata
.vpos
[j
] < -self
.epsilon
or
620 styledata
.vpos
[j
] > 1+self
.epsilon
)):
623 if ((styledata
.vrange
[i
][0] is None and styledata
.vpos
[i
] is None) or
624 (styledata
.vrange
[i
][1] is None and styledata
.vpos
[i
] is None) or
625 (styledata
.vrange
[i
][0] is None and styledata
.vrange
[i
][1] is None)):
627 vminpos
= styledata
.vpos
[:]
628 if styledata
.vrange
[i
][0] is not None:
629 vminpos
[i
] = styledata
.vrange
[i
][0]
633 if vminpos
[i
] > 1+self
.epsilon
:
635 if vminpos
[i
] < -self
.epsilon
:
638 vmaxpos
= styledata
.vpos
[:]
639 if styledata
.vrange
[i
][1] is not None:
640 vmaxpos
[i
] = styledata
.vrange
[i
][1]
644 if vmaxpos
[i
] < -self
.epsilon
:
646 if vmaxpos
[i
] > 1+self
.epsilon
:
649 styledata
.errorbarcanvas
.stroke(graph
.vgeodesic(*(vminpos
+ vmaxpos
)))
650 for j
in styledata
.dimensionlist
:
653 styledata
.errorbarcanvas
.stroke(graph
.vcap_pt(j
, styledata
.errorsize_pt
, *vminpos
))
655 styledata
.errorbarcanvas
.stroke(graph
.vcap_pt(j
, styledata
.errorsize_pt
, *vmaxpos
))
658 # class text(symbol):
660 # defaulttextattrs = [textmodule.halign.center, textmodule.vshift.mathaxis]
662 # def __init__(self, textdx=0*unit.v_cm, textdy=0.3*unit.v_cm, textattrs=[], **kwargs):
663 # self.textdx = textdx
664 # self.textdy = textdy
665 # self.textattrs = textattrs
666 # symbol.__init__(self, **kwargs)
668 # def setdata(self, graph, columns, styledata):
669 # columns = columns.copy()
670 # styledata.textindex = columns["text"]
671 # del columns["text"]
672 # return symbol.setdata(self, graph, columns, styledata)
674 # def selectstyle(self, selectindex, selecttotal, styledata):
675 # if self.textattrs is not None:
676 # styledata.textattrs = attr.selectattrs(self.defaulttextattrs + self.textattrs, selectindex, selecttotal)
678 # styledata.textattrs = None
679 # symbol.selectstyle(self, selectindex, selecttotal, styledata)
681 # def drawsymbol_pt(self, c, x, y, styledata, point=None):
682 # symbol.drawsymbol_pt(self, c, x, y, styledata, point)
683 # if None not in (x, y, point[styledata.textindex]) and styledata.textattrs is not None:
684 # c.text_pt(x + styledata.textdx_pt, y + styledata.textdy_pt, str(point[styledata.textindex]), styledata.textattrs)
686 # def drawpoints(self, points, graph, styledata):
687 # styledata.textdx_pt = unit.topt(self.textdx)
688 # styledata.textdy_pt = unit.topt(self.textdy)
689 # symbol.drawpoints(self, points, graph, styledata)
692 class arrow(_styleneedingpointpos
):
694 need
= ["vpos", "vposmissing", "vposvalid"]
696 defaultlineattrs
= []
697 defaultarrowattrs
= []
699 def __init__(self
, linelength
=0.25*unit
.v_cm
, arrowsize
=0.15*unit
.v_cm
, lineattrs
=[], arrowattrs
=[], epsilon
=1e-10):
700 self
.linelength
= linelength
701 self
.arrowsize
= arrowsize
702 self
.lineattrs
= lineattrs
703 self
.arrowattrs
= arrowattrs
704 self
.epsilon
= epsilon
706 def columns(self
, styledata
, graph
, columns
):
707 if len(graph
.axesnames
) != 2:
708 raise ValueError("arrow style restricted on two-dimensional graphs")
709 if "size" not in columns
:
710 raise ValueError("size missing")
711 if "angle" not in columns
:
712 raise ValueError("angle missing")
713 return ["size", "angle"] + _styleneedingpointpos
.columns(self
, styledata
, graph
, columns
)
715 def selectstyle(self
, styledata
, graph
, selectindex
, selecttotal
):
716 if self
.lineattrs
is not None:
717 styledata
.lineattrs
= attr
.selectattrs(self
.defaultlineattrs
+ self
.lineattrs
, selectindex
, selecttotal
)
719 styledata
.lineattrs
= None
720 if self
.arrowattrs
is not None:
721 styledata
.arrowattrs
= attr
.selectattrs(self
.defaultarrowattrs
+ self
.arrowattrs
, selectindex
, selecttotal
)
723 styledata
.arrowattrs
= None
725 def drawpoint(self
, styledata
, graph
):
726 if styledata
.lineattrs
is not None and styledata
.arrowattrs
is not None and styledata
.vposvalid
:
727 linelength_pt
= unit
.topt(self
.linelength
)
728 xpos
, ypos
= graph
.vpos_pt(*styledata
.vpos
)
730 angle
= styledata
.point
["angle"] + 0.0
731 size
= styledata
.point
["size"] + 0.0
734 if styledata
.point
["size"] > self
.epsilon
:
735 dx
= math
.cos(angle
*math
.pi
/180)
736 dy
= math
.sin(angle
*math
.pi
/180)
737 x1
= xpos
-0.5*dx
*linelength_pt
*size
738 y1
= ypos
-0.5*dy
*linelength_pt
*size
739 x2
= xpos
+0.5*dx
*linelength_pt
*size
740 y2
= ypos
+0.5*dy
*linelength_pt
*size
741 graph
.stroke(path
.line_pt(x1
, y1
, x2
, y2
), styledata
.lineattrs
+
742 [deco
.earrow(styledata
.arrowattrs
, size
=self
.arrowsize
*size
)])
744 def key_pt(self
, styledata
, graph
, x_pt
, y_pt
, width_pt
, height_pt
):
748 # class rect(_style):
750 # def __init__(self, palette=color.palette.Gray):
751 # self.palette = palette
753 # def setdata(self, graph, columns, styledata):
754 # if len(graph.axisnames) != 2:
755 # raise TypeError("arrow style restricted on two-dimensional graphs")
756 # columns = columns.copy()
757 # styledata.xaxis, styledata.xminindex = _style.setdatapattern(self, graph, columns, re.compile(r"(%s([2-9]|[1-9][0-9]+)?)min$" % graph.axisnames[0]))
758 # styledata.yaxis, styledata.yminindex = _style.setdatapattern(self, graph, columns, re.compile(r"(%s([2-9]|[1-9][0-9]+)?)min$" % graph.axisnames[1]))
759 # xaxis, styledata.xmaxindex = _style.setdatapattern(self, graph, columns, re.compile(r"(%s([2-9]|[1-9][0-9]+)?)max$" % graph.axisnames[0]))
760 # yaxis, styledata.ymaxindex = _style.setdatapattern(self, graph, columns, re.compile(r"(%s([2-9]|[1-9][0-9]+)?)max$" % graph.axisnames[1]))
761 # if xaxis != styledata.xaxis or yaxis != styledata.yaxis:
762 # raise ValueError("min/max values should use the same axes")
763 # styledata.colorindex = columns["color"]
764 # del columns["color"]
767 # def selectstyle(self, selectindex, selecttotal, styledata):
770 # def adjustaxes(self, points, columns, styledata):
771 # if styledata.xminindex in columns:
772 # styledata.xaxis.adjustrange(points, styledata.xminindex)
773 # if styledata.xmaxindex in columns:
774 # styledata.xaxis.adjustrange(points, styledata.xmaxindex)
775 # if styledata.yminindex in columns:
776 # styledata.yaxis.adjustrange(points, styledata.yminindex)
777 # if styledata.ymaxindex in columns:
778 # styledata.yaxis.adjustrange(points, styledata.ymaxindex)
780 # def drawpoints(self, points, graph, styledata):
781 # # TODO: bbox shortcut
782 # c = graph.insert(canvas.canvas())
783 # lastcolorvalue = None
784 # for point in points:
786 # xvmin = styledata.xaxis.convert(point[styledata.xminindex])
787 # xvmax = styledata.xaxis.convert(point[styledata.xmaxindex])
788 # yvmin = styledata.yaxis.convert(point[styledata.yminindex])
789 # yvmax = styledata.yaxis.convert(point[styledata.ymaxindex])
790 # colorvalue = point[styledata.colorindex]
791 # if colorvalue != lastcolorvalue:
792 # color = self.palette.getcolor(point[styledata.colorindex])
795 # if ((xvmin < 0 and xvmax < 0) or (xvmin > 1 and xvmax > 1) or
796 # (yvmin < 0 and yvmax < 0) or (yvmin > 1 and yvmax > 1)):
814 # p = graph.vgeodesic(xvmin, yvmin, xvmax, yvmin)
815 # p.append(graph.vgeodesic_el(xvmax, yvmin, xvmax, yvmax))
816 # p.append(graph.vgeodesic_el(xvmax, yvmax, xvmin, yvmax))
817 # p.append(graph.vgeodesic_el(xvmin, yvmax, xvmin, yvmin))
818 # p.append(path.closepath())
819 # if colorvalue != lastcolorvalue:
826 # defaultfrompathattrs = []
827 # defaultbarattrs = [color.palette.Rainbow, deco.stroked([color.gray.black])]
829 # def __init__(self, fromvalue=None, frompathattrs=[], barattrs=[], subnames=None, epsilon=1e-10):
830 # self.fromvalue = fromvalue
831 # self.frompathattrs = frompathattrs
832 # self.barattrs = barattrs
833 # self.subnames = subnames
834 # self.epsilon = epsilon
836 # def setdata(self, graph, columns, styledata):
837 # # TODO: remove limitation to 2d graphs
838 # if len(graph.axisnames) != 2:
839 # raise TypeError("arrow style currently restricted on two-dimensional graphs")
840 # columns = columns.copy()
841 # xvalue = _style.setdatapattern(self, graph, columns, re.compile(r"(%s([2-9]|[1-9][0-9]+)?)$" % graph.axisnames[0]))
842 # yvalue = _style.setdatapattern(self, graph, columns, re.compile(r"(%s([2-9]|[1-9][0-9]+)?)$" % graph.axisnames[1]))
843 # if (xvalue is None and yvalue is None) or (xvalue is not None and yvalue is not None):
844 # raise TypeError("must specify exactly one value axis")
845 # if xvalue is not None:
846 # styledata.valuepos = 0
847 # styledata.nameaxis, styledata.nameindex = _style.setdatapattern(self, graph, columns, re.compile(r"(%s([2-9]|[1-9][0-9]+)?)name$" % graph.axisnames[1]))
848 # styledata.valueaxis = xvalue[0]
849 # styledata.valueindices = [xvalue[1]]
851 # styledata.valuepos = 1
852 # styledata.nameaxis, styledata.nameindex = _style.setdatapattern(self, graph, columns, re.compile(r"(%s([2-9]|[1-9][0-9]+)?)name$" % graph.axisnames[0]))
853 # styledata.valueaxis = yvalue[0]
854 # styledata.valueindices = [yvalue[1]]
858 # valueaxis, valueindex = _style.setdatapattern(self, graph, columns, re.compile(r"(%s([2-9]|[1-9][0-9]+)?)stack%i$" % (graph.axisnames[styledata.valuepos], i)))
861 # if styledata.valueaxis != valueaxis:
862 # raise ValueError("different value axes for stacked bars")
863 # styledata.valueindices.append(valueindex)
867 # def selectstyle(self, selectindex, selecttotal, styledata):
869 # styledata.frompathattrs = None
871 # styledata.frompathattrs = self.defaultfrompathattrs + self.frompathattrs
872 # if selecttotal > 1:
873 # if self.barattrs is not None:
874 # styledata.barattrs = attr.selectattrs(self.defaultbarattrs + self.barattrs, selectindex, selecttotal)
876 # styledata.barattrs = None
878 # styledata.barattrs = self.defaultbarattrs + self.barattrs
879 # styledata.selectindex = selectindex
880 # styledata.selecttotal = selecttotal
881 # if styledata.selecttotal != 1 and self.subnames is not None:
882 # raise ValueError("subnames not allowed when iterating over bars")
884 # def adjustaxes(self, points, columns, styledata):
885 # if styledata.nameindex in columns:
886 # if styledata.selecttotal == 1:
887 # styledata.nameaxis.adjustrange(points, styledata.nameindex, subnames=self.subnames)
889 # for i in range(styledata.selecttotal):
890 # styledata.nameaxis.adjustrange(points, styledata.nameindex, subnames=[i])
891 # for valueindex in styledata.valueindices:
892 # if valueindex in columns:
893 # styledata.valueaxis.adjustrange(points, valueindex)
895 # def drawpoints(self, points, graph, styledata):
896 # if self.fromvalue is not None:
897 # vfromvalue = styledata.valueaxis.convert(self.fromvalue)
898 # if vfromvalue < -self.epsilon:
900 # if vfromvalue > 1 + self.epsilon:
902 # if styledata.frompathattrs is not None and vfromvalue > self.epsilon and vfromvalue < 1 - self.epsilon:
903 # if styledata.valuepos:
904 # p = graph.vgeodesic(0, vfromvalue, 1, vfromvalue)
906 # p = graph.vgeodesic(vfromvalue, 0, vfromvalue, 1)
907 # graph.stroke(p, styledata.frompathattrs)
910 # l = len(styledata.valueindices)
914 # barattrslist.append(attr.selectattrs(styledata.barattrs, i, l))
916 # barattrslist = [styledata.barattrs]
917 # for point in points:
918 # vvaluemax = vfromvalue
919 # for valueindex, barattrs in zip(styledata.valueindices, barattrslist):
920 # vvaluemin = vvaluemax
922 # vvaluemax = styledata.valueaxis.convert(point[valueindex])
926 # if styledata.selecttotal == 1:
928 # vnamemin = styledata.nameaxis.convert((point[styledata.nameindex], 0))
932 # vnamemax = styledata.nameaxis.convert((point[styledata.nameindex], 1))
937 # vnamemin = styledata.nameaxis.convert((point[styledata.nameindex], styledata.selectindex, 0))
941 # vnamemax = styledata.nameaxis.convert((point[styledata.nameindex], styledata.selectindex, 1))
945 # if styledata.valuepos:
946 # p = graph.vgeodesic(vnamemin, vvaluemin, vnamemin, vvaluemax)
947 # p.append(graph.vgeodesic_el(vnamemin, vvaluemax, vnamemax, vvaluemax))
948 # p.append(graph.vgeodesic_el(vnamemax, vvaluemax, vnamemax, vvaluemin))
949 # p.append(graph.vgeodesic_el(vnamemax, vvaluemin, vnamemin, vvaluemin))
950 # p.append(path.closepath())
952 # p = graph.vgeodesic(vvaluemin, vnamemin, vvaluemin, vnamemax)
953 # p.append(graph.vgeodesic_el(vvaluemin, vnamemax, vvaluemax, vnamemax))
954 # p.append(graph.vgeodesic_el(vvaluemax, vnamemax, vvaluemax, vnamemin))
955 # p.append(graph.vgeodesic_el(vvaluemax, vnamemin, vvaluemin, vnamemin))
956 # p.append(path.closepath())
957 # if barattrs is not None:
958 # graph.fill(p, barattrs)
960 # def key_pt(self, c, x_pt, y_pt, width_pt, height_pt, styledata):
961 # l = len(styledata.valueindices)
964 # c.fill(path.rect_pt(x_pt+i*width_pt/l, y_pt, width_pt/l, height_pt), attr.selectattrs(styledata.barattrs, i, l))
966 # c.fill(path.rect_pt(x_pt, y_pt, width_pt, height_pt), styledata.barattrs)