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-2005 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
27 from pyx
import attr
, deco
, style
, color
, unit
, canvas
, path
28 from pyx
import text
as textmodule
35 # fallback implementation for Python 2.2. and below
37 return zip(xrange(len(list)), list)
40 """Interface class for graph styles
42 Each graph style must support the methods described in this
43 class. However, since a graph style might not need to perform
44 actions on all the various events, it does not need to overwrite
45 all methods of this base class (e.g. this class is not an abstract
46 class in any respect).
48 A style should never store private data by istance variables
49 (i.e. accessing self), but it should use the sharedata and privatedata
50 instances instead. A style instance can be used multiple times with
51 different sharedata and privatedata instances at the very same time.
52 The sharedata and privatedata instances act as data containers and
53 sharedata allows for sharing information across several styles.
55 Every style contains two class variables, which are not to be
57 - providesdata is a list of variable names a style offers via
58 the sharedata instance. This list is used to determine whether
59 all needs of subsequent styles are fullfilled. Otherwise
60 getdefaultprovider should return a proper style to be used.
61 - needsdata is a list of variable names the style needs to access in the
65 providesdata
= [] # by default, we provide nothing
66 needsdata
= [] # and do not depend on anything
68 def columnnames(self
, privatedata
, sharedata
, graph
, columnnames
):
69 """Set column information
71 This method is used setup the column name information to be
72 accessible to the style later on. The style should analyse
73 the list of column names. The method should return a list of
74 column names which the style will make use of."""
77 def adjustaxis(self
, privatedata
, sharedata
, graph
, columnname
, data
):
80 This method is called in order to adjust the axis range to
81 the provided data. columnname is the column name (each style
82 is subsequently called for all column names)."""
85 def selectstyle(self
, privatedata
, sharedata
, graph
, selectindex
, selecttotal
):
86 """Select stroke/fill attributes
88 This method is called to allow for the selection of
89 changable attributes of a style."""
92 def initdrawpoints(self
, privatedata
, sharedata
, graph
):
93 """Initialize drawing of data
95 This method might be used to initialize the drawing of data."""
98 def drawpoint(self
, privatedata
, sharedata
, graph
, point
):
101 This method is called for each data point. The data is
102 available in the dictionary point. The dictionary
103 keys are the column names."""
106 def donedrawpoints(self
, privatedata
, sharedata
, graph
):
107 """Finalize drawing of data
109 This method is called after the last data point was
110 drawn using the drawpoint method above."""
113 def key_pt(self
, privatedata
, sharedata
, graph
, x_pt
, y_pt
, width_pt
, height_pt
):
117 # The following two methods are used to register and get a default provider
118 # for keys. A key is a variable name in sharedata. A provider is a style
119 # which creates variables in sharedata.
121 _defaultprovider
= {}
123 def registerdefaultprovider(style
, keys
):
124 """sets a style as a default creator for sharedata variables 'keys'"""
125 assert not len(style
.needsdata
), "currently we state, that a style should not depend on other sharedata variables"
127 assert key
in style
.providesdata
, "key not provided by style"
128 # we might allow for overwriting the defaults, i.e. the following is not checked:
129 # assert key in _defaultprovider.keys(), "default provider already registered for key"
130 _defaultprovider
[key
] = style
132 def getdefaultprovider(key
):
133 """returns a style, which acts as a default creator for the
134 sharedata variable 'key'"""
135 return _defaultprovider
[key
]
140 providesdata
= ["vpos", "vposmissing", "vposavailable", "vposvalid", "poscolumnnames"]
142 def __init__(self
, epsilon
=1e-10):
143 self
.epsilon
= epsilon
145 def columnnames(self
, privatedata
, sharedata
, graph
, columnnames
):
146 sharedata
.poscolumnnames
= []
147 sharedata
.vposmissing
= []
148 for count
, axisnames
in enumerate(graph
.axesnames
):
149 for axisname
in axisnames
:
150 for columnname
in columnnames
:
151 if axisname
== columnname
:
152 sharedata
.poscolumnnames
.append(columnname
)
153 if len(sharedata
.poscolumnnames
) > count
+1:
154 raise ValueError("multiple axes per graph dimension")
155 elif len(sharedata
.poscolumnnames
) < count
+1:
156 sharedata
.vposmissing
.append(count
)
157 sharedata
.poscolumnnames
.append(None)
158 return [columnname
for columnname
in sharedata
.poscolumnnames
if columnname
is not None]
160 def adjustaxis(self
, privatedata
, sharedata
, graph
, columnname
, data
):
161 if columnname
in sharedata
.poscolumnnames
:
162 graph
.axes
[columnname
].adjustaxis(data
)
164 def initdrawpoints(self
, privatedata
, sharedata
, graph
):
165 sharedata
.vpos
= [None]*(len(graph
.axesnames
))
166 privatedata
.pointpostmplist
= [[columnname
, index
, graph
.axes
[columnname
]] # temporarily used by drawpoint only
167 for index
, columnname
in enumerate([columnname
for columnname
in sharedata
.poscolumnnames
if columnname
is not None])]
168 for missing
in sharedata
.vposmissing
:
169 for pointpostmp
in privatedata
.pointpostmplist
:
170 if pointpostmp
[1] >= missing
:
173 def drawpoint(self
, privatedata
, sharedata
, graph
, point
):
174 sharedata
.vposavailable
= 1 # valid position (but might be outside of the graph)
175 sharedata
.vposvalid
= 1 # valid position inside the graph
176 for columnname
, index
, axis
in privatedata
.pointpostmplist
:
178 v
= axis
.convert(point
[columnname
])
179 except (ArithmeticError, ValueError, TypeError):
180 sharedata
.vposavailable
= sharedata
.vposvalid
= 0
181 sharedata
.vpos
[index
] = None
183 if v
< -self
.epsilon
or v
> 1+self
.epsilon
:
184 sharedata
.vposvalid
= 0
185 sharedata
.vpos
[index
] = v
188 registerdefaultprovider(pos(), pos
.providesdata
)
193 providesdata
= ["vrange", "vrangemissing", "vrangeminmissing", "vrangemaxmissing"]
203 def __init__(self
, usenames
={}, epsilon
=1e-10):
204 self
.usenames
= usenames
205 self
.epsilon
= epsilon
207 def _numberofbits(self
, mask
):
211 return self
._numberofbits
(mask
>> 1) + 1
213 return self
._numberofbits
(mask
>> 1)
215 def columnnames(self
, privatedata
, sharedata
, graph
, columnnames
):
217 privatedata
.rangeposcolumns
= []
218 sharedata
.vrangemissing
= []
219 sharedata
.vrangeminmissing
= []
220 sharedata
.vrangemaxmissing
= []
221 privatedata
.rangeposdeltacolumns
= {} # temporarily used by adjustaxis only
222 for count
, axisnames
in enumerate(graph
.axesnames
):
223 for axisname
in axisnames
:
225 usename
= self
.usenames
[axisname
]
229 for columnname
in columnnames
:
231 if usename
== columnname
:
232 mask
+= self
.mask_value
233 elif usename
+ "min" == columnname
:
234 mask
+= self
.mask_min
235 elif usename
+ "max" == columnname
:
236 mask
+= self
.mask_max
237 elif "d" + usename
+ "min" == columnname
:
238 mask
+= self
.mask_dmin
239 elif "d" + usename
+ "max" == columnname
:
240 mask
+= self
.mask_dmax
241 elif "d" + usename
== columnname
:
246 usecolumns
.append(columnname
)
247 if mask
& (self
.mask_min | self
.mask_max | self
.mask_dmin | self
.mask_dmax | self
.mask_d
):
248 if (self
._numberofbits
(mask
& (self
.mask_min | self
.mask_dmin | self
.mask_d
)) > 1 or
249 self
._numberofbits
(mask
& (self
.mask_max | self
.mask_dmax | self
.mask_d
)) > 1):
250 raise ValueError("multiple range definition")
251 if mask
& (self
.mask_dmin | self
.mask_dmax | self
.mask_d
):
252 if not (mask
& self
.mask_value
):
253 raise ValueError("missing value for delta")
254 privatedata
.rangeposdeltacolumns
[axisname
] = {}
255 privatedata
.rangeposcolumns
.append((axisname
, usename
, mask
))
256 elif mask
== self
.mask_value
:
257 usecolumns
= usecolumns
[:-1]
258 if len(privatedata
.rangeposcolumns
) + len(sharedata
.vrangemissing
) > count
+1:
259 raise ValueError("multiple axes per graph dimension")
260 elif len(privatedata
.rangeposcolumns
) + len(sharedata
.vrangemissing
) < count
+1:
261 sharedata
.vrangemissing
.append(count
)
262 sharedata
.vrangeminmissing
.append(count
)
263 sharedata
.vrangemaxmissing
.append(count
)
265 if not (privatedata
.rangeposcolumns
[-1][2] & (self
.mask_min | self
.mask_dmin | self
.mask_d
)):
266 sharedata
.vrangeminmissing
.append(count
)
267 if not (privatedata
.rangeposcolumns
[-1][2] & (self
.mask_max | self
.mask_dmax | self
.mask_d
)):
268 sharedata
.vrangemaxmissing
.append(count
)
271 def adjustaxis(self
, privatedata
, sharedata
, graph
, columnname
, data
):
272 if columnname
in [c
+ "min" for a
, c
, m
in privatedata
.rangeposcolumns
if m
& self
.mask_min
]:
273 graph
.axes
[columnname
[:-3]].adjustaxis(data
)
274 if columnname
in [c
+ "max" for a
, c
, m
in privatedata
.rangeposcolumns
if m
& self
.mask_max
]:
275 graph
.axes
[columnname
[:-3]].adjustaxis(data
)
277 # delta handling: fill rangeposdeltacolumns
278 for axisname
, usename
, mask
in privatedata
.rangeposcolumns
:
279 if columnname
== usename
and mask
& (self
.mask_dmin | self
.mask_dmax | self
.mask_d
):
280 privatedata
.rangeposdeltacolumns
[axisname
][self
.mask_value
] = data
281 if columnname
== "d" + usename
+ "min" and mask
& self
.mask_dmin
:
282 privatedata
.rangeposdeltacolumns
[axisname
][self
.mask_dmin
] = data
283 if columnname
== "d" + usename
+ "max" and mask
& self
.mask_dmax
:
284 privatedata
.rangeposdeltacolumns
[axisname
][self
.mask_dmax
] = data
285 if columnname
== "d" + usename
and mask
& self
.mask_d
:
286 privatedata
.rangeposdeltacolumns
[axisname
][self
.mask_d
] = data
288 # delta handling: process rangeposdeltacolumns
289 for a
, d
in privatedata
.rangeposdeltacolumns
.items():
290 if d
.has_key(self
.mask_value
):
292 if k
!= self
.mask_value
:
293 if k
& (self
.mask_dmin | self
.mask_d
):
296 mindata
.append(d
[self
.mask_value
] - d
[k
])
299 graph
.axes
[a
].adjustaxis(mindata
)
300 if k
& (self
.mask_dmax | self
.mask_d
):
303 maxdata
.append(d
[self
.mask_value
] + d
[k
])
306 graph
.axes
[a
].adjustaxis(maxdata
)
309 def initdrawpoints(self
, privatedata
, sharedata
, graph
):
310 sharedata
.vrange
= [[None for x
in xrange(2)] for y
in privatedata
.rangeposcolumns
+ sharedata
.vrangemissing
]
311 privatedata
.rangepostmplist
= [[usename
, mask
, index
, graph
.axes
[axisname
]] # temporarily used by drawpoint only
312 for index
, (axisname
, usename
, mask
) in enumerate(privatedata
.rangeposcolumns
)]
313 for missing
in sharedata
.vrangemissing
:
314 for rangepostmp
in privatedata
.rangepostmplist
:
315 if rangepostmp
[2] >= missing
:
318 def drawpoint(self
, privatedata
, sharedata
, graph
, point
):
319 for usename
, mask
, index
, axis
in privatedata
.rangepostmplist
:
321 if mask
& self
.mask_min
:
322 sharedata
.vrange
[index
][0] = axis
.convert(point
[usename
+ "min"])
323 if mask
& self
.mask_dmin
:
324 sharedata
.vrange
[index
][0] = axis
.convert(point
[usename
] - point
["d" + usename
+ "min"])
325 if mask
& self
.mask_d
:
326 sharedata
.vrange
[index
][0] = axis
.convert(point
[usename
] - point
["d" + usename
])
327 except (ArithmeticError, ValueError, TypeError):
328 sharedata
.vrange
[index
][0] = None
330 if mask
& self
.mask_max
:
331 sharedata
.vrange
[index
][1] = axis
.convert(point
[usename
+ "max"])
332 if mask
& self
.mask_dmax
:
333 sharedata
.vrange
[index
][1] = axis
.convert(point
[usename
] + point
["d" + usename
+ "max"])
334 if mask
& self
.mask_d
:
335 sharedata
.vrange
[index
][1] = axis
.convert(point
[usename
] + point
["d" + usename
])
336 except (ArithmeticError, ValueError, TypeError):
337 sharedata
.vrange
[index
][1] = None
339 # some range checks for data consistency
340 if (sharedata
.vrange
[index
][0] is not None and sharedata
.vrange
[index
][1] is not None and
341 sharedata
.vrange
[index
][0] > sharedata
.vrange
[index
][1] + self
.epsilon
):
342 raise ValueError("inverse range")
343 # disabled due to missing vpos access:
344 # if (sharedata.vrange[index][0] is not None and sharedata.vpos[index] is not None and
345 # sharedata.vrange[index][0] > sharedata.vpos[index] + self.epsilon):
346 # raise ValueError("negative minimum errorbar")
347 # if (sharedata.vrange[index][1] is not None and sharedata.vpos[index] is not None and
348 # sharedata.vrange[index][1] < sharedata.vpos[index] - self.epsilon):
349 # raise ValueError("negative maximum errorbar")
352 registerdefaultprovider(range(), range.providesdata
)
355 def _crosssymbol(c
, x_pt
, y_pt
, size_pt
, attrs
):
356 c
.draw(path
.path(path
.moveto_pt(x_pt
-0.5*size_pt
, y_pt
-0.5*size_pt
),
357 path
.lineto_pt(x_pt
+0.5*size_pt
, y_pt
+0.5*size_pt
),
358 path
.moveto_pt(x_pt
-0.5*size_pt
, y_pt
+0.5*size_pt
),
359 path
.lineto_pt(x_pt
+0.5*size_pt
, y_pt
-0.5*size_pt
)), attrs
)
361 def _plussymbol(c
, x_pt
, y_pt
, size_pt
, attrs
):
362 c
.draw(path
.path(path
.moveto_pt(x_pt
-0.707106781*size_pt
, y_pt
),
363 path
.lineto_pt(x_pt
+0.707106781*size_pt
, y_pt
),
364 path
.moveto_pt(x_pt
, y_pt
-0.707106781*size_pt
),
365 path
.lineto_pt(x_pt
, y_pt
+0.707106781*size_pt
)), attrs
)
367 def _squaresymbol(c
, x_pt
, y_pt
, size_pt
, attrs
):
368 c
.draw(path
.path(path
.moveto_pt(x_pt
-0.5*size_pt
, y_pt
-0.5*size_pt
),
369 path
.lineto_pt(x_pt
+0.5*size_pt
, y_pt
-0.5*size_pt
),
370 path
.lineto_pt(x_pt
+0.5*size_pt
, y_pt
+0.5*size_pt
),
371 path
.lineto_pt(x_pt
-0.5*size_pt
, y_pt
+0.5*size_pt
),
372 path
.closepath()), attrs
)
374 def _trianglesymbol(c
, x_pt
, y_pt
, size_pt
, attrs
):
375 c
.draw(path
.path(path
.moveto_pt(x_pt
-0.759835685*size_pt
, y_pt
-0.438691337*size_pt
),
376 path
.lineto_pt(x_pt
+0.759835685*size_pt
, y_pt
-0.438691337*size_pt
),
377 path
.lineto_pt(x_pt
, y_pt
+0.877382675*size_pt
),
378 path
.closepath()), attrs
)
380 def _circlesymbol(c
, x_pt
, y_pt
, size_pt
, attrs
):
381 c
.draw(path
.path(path
.arc_pt(x_pt
, y_pt
, 0.564189583*size_pt
, 0, 360),
382 path
.closepath()), attrs
)
384 def _diamondsymbol(c
, x_pt
, y_pt
, size_pt
, attrs
):
385 c
.draw(path
.path(path
.moveto_pt(x_pt
-0.537284965*size_pt
, y_pt
),
386 path
.lineto_pt(x_pt
, y_pt
-0.930604859*size_pt
),
387 path
.lineto_pt(x_pt
+0.537284965*size_pt
, y_pt
),
388 path
.lineto_pt(x_pt
, y_pt
+0.930604859*size_pt
),
389 path
.closepath()), attrs
)
392 class _styleneedingpointpos(_style
):
394 needsdata
= ["vposmissing"]
396 def columnnames(self
, privatedata
, sharedata
, graph
, columnnames
):
397 if len(sharedata
.vposmissing
):
398 raise ValueError("incomplete position information")
402 class symbol(_styleneedingpointpos
):
404 needsdata
= ["vpos", "vposmissing", "vposvalid"]
406 # "inject" the predefinied symbols into the class:
408 # Note, that statements like cross = _crosssymbol are
409 # invalid, since the would lead to unbound methods, but
410 # a single entry changeable list does the trick.
412 # Once we require Python 2.2+ we should use staticmethods
413 # to implement the default symbols inplace.
415 cross
= attr
.changelist([_crosssymbol
])
416 plus
= attr
.changelist([_plussymbol
])
417 square
= attr
.changelist([_squaresymbol
])
418 triangle
= attr
.changelist([_trianglesymbol
])
419 circle
= attr
.changelist([_circlesymbol
])
420 diamond
= attr
.changelist([_diamondsymbol
])
422 changecross
= attr
.changelist([_crosssymbol
, _plussymbol
, _squaresymbol
, _trianglesymbol
, _circlesymbol
, _diamondsymbol
])
423 changeplus
= attr
.changelist([_plussymbol
, _squaresymbol
, _trianglesymbol
, _circlesymbol
, _diamondsymbol
, _crosssymbol
])
424 changesquare
= attr
.changelist([_squaresymbol
, _trianglesymbol
, _circlesymbol
, _diamondsymbol
, _crosssymbol
, _plussymbol
])
425 changetriangle
= attr
.changelist([_trianglesymbol
, _circlesymbol
, _diamondsymbol
, _crosssymbol
, _plussymbol
, _squaresymbol
])
426 changecircle
= attr
.changelist([_circlesymbol
, _diamondsymbol
, _crosssymbol
, _plussymbol
, _squaresymbol
, _trianglesymbol
])
427 changediamond
= attr
.changelist([_diamondsymbol
, _crosssymbol
, _plussymbol
, _squaresymbol
, _trianglesymbol
, _circlesymbol
])
428 changesquaretwice
= attr
.changelist([_squaresymbol
, _squaresymbol
, _trianglesymbol
, _trianglesymbol
, _circlesymbol
, _circlesymbol
, _diamondsymbol
, _diamondsymbol
])
429 changetriangletwice
= attr
.changelist([_trianglesymbol
, _trianglesymbol
, _circlesymbol
, _circlesymbol
, _diamondsymbol
, _diamondsymbol
, _squaresymbol
, _squaresymbol
])
430 changecircletwice
= attr
.changelist([_circlesymbol
, _circlesymbol
, _diamondsymbol
, _diamondsymbol
, _squaresymbol
, _squaresymbol
, _trianglesymbol
, _trianglesymbol
])
431 changediamondtwice
= attr
.changelist([_diamondsymbol
, _diamondsymbol
, _squaresymbol
, _squaresymbol
, _trianglesymbol
, _trianglesymbol
, _circlesymbol
, _circlesymbol
])
433 changestrokedfilled
= attr
.changelist([deco
.stroked
, deco
.filled
])
434 changefilledstroked
= attr
.changelist([deco
.filled
, deco
.stroked
])
436 defaultsymbolattrs
= [deco
.stroked
]
438 def __init__(self
, symbol
=changecross
, size
=0.2*unit
.v_cm
, symbolattrs
=[]):
441 self
.symbolattrs
= symbolattrs
443 def selectstyle(self
, privatedata
, sharedata
, graph
, selectindex
, selecttotal
):
444 privatedata
.symbol
= attr
.selectattr(self
.symbol
, selectindex
, selecttotal
)
445 privatedata
.size_pt
= unit
.topt(attr
.selectattr(self
.size
, selectindex
, selecttotal
))
446 if self
.symbolattrs
is not None:
447 privatedata
.symbolattrs
= attr
.selectattrs(self
.defaultsymbolattrs
+ self
.symbolattrs
, selectindex
, selecttotal
)
449 privatedata
.symbolattrs
= None
451 def initdrawpoints(self
, privatedata
, sharedata
, graph
):
452 privatedata
.symbolcanvas
= canvas
.canvas()
454 def drawpoint(self
, privatedata
, sharedata
, graph
, point
):
455 if sharedata
.vposvalid
and privatedata
.symbolattrs
is not None:
456 x_pt
, y_pt
= graph
.vpos_pt(*sharedata
.vpos
)
457 privatedata
.symbol(privatedata
.symbolcanvas
, x_pt
, y_pt
, privatedata
.size_pt
, privatedata
.symbolattrs
)
459 def donedrawpoints(self
, privatedata
, sharedata
, graph
):
460 graph
.insert(privatedata
.symbolcanvas
)
462 def key_pt(self
, privatedata
, sharedata
, graph
, x_pt
, y_pt
, width_pt
, height_pt
):
463 if privatedata
.symbolattrs
is not None:
464 privatedata
.symbol(graph
, x_pt
+0.5*width_pt
, y_pt
+0.5*height_pt
, privatedata
.size_pt
, privatedata
.symbolattrs
)
467 class line(_styleneedingpointpos
):
469 needsdata
= ["vpos", "vposmissing", "vposavailable", "vposvalid"]
471 changelinestyle
= attr
.changelist([style
.linestyle
.solid
,
472 style
.linestyle
.dashed
,
473 style
.linestyle
.dotted
,
474 style
.linestyle
.dashdotted
])
476 defaultlineattrs
= [changelinestyle
]
478 def __init__(self
, lineattrs
=[]):
479 self
.lineattrs
= lineattrs
481 def selectstyle(self
, privatedata
, sharedata
, graph
, selectindex
, selecttotal
):
482 if self
.lineattrs
is not None:
483 privatedata
.lineattrs
= attr
.selectattrs(self
.defaultlineattrs
+ self
.lineattrs
, selectindex
, selecttotal
)
485 privatedata
.lineattrs
= None
487 def initdrawpoints(self
, privatedata
, sharedata
, graph
):
488 privatedata
.path
= path
.path()
489 privatedata
.linebasepoints
= []
490 privatedata
.lastvpos
= None
492 def addpointstopath(self
, privatedata
, sharedata
):
493 # add baselinepoints to privatedata.path
494 if len(privatedata
.linebasepoints
) > 1:
495 privatedata
.path
.append(path
.moveto_pt(*privatedata
.linebasepoints
[0]))
496 if len(privatedata
.linebasepoints
) > 2:
497 privatedata
.path
.append(path
.multilineto_pt(privatedata
.linebasepoints
[1:]))
499 privatedata
.path
.append(path
.lineto_pt(*privatedata
.linebasepoints
[1]))
500 privatedata
.linebasepoints
= []
502 def drawpoint(self
, privatedata
, sharedata
, graph
, point
):
503 # append linebasepoints
504 if sharedata
.vposavailable
:
505 if len(privatedata
.linebasepoints
):
506 # the last point was inside the graph
507 if sharedata
.vposvalid
: # shortcut for the common case
508 privatedata
.linebasepoints
.append(graph
.vpos_pt(*sharedata
.vpos
))
512 for vstart
, vend
in zip(privatedata
.lastvpos
, sharedata
.vpos
):
515 # 1 = vstart + (vend - vstart) * cut
517 newcut
= (1 - vstart
)/(vend
- vstart
)
518 except (ArithmeticError, TypeError):
521 # 0 = vstart + (vend - vstart) * cut
523 newcut
= - vstart
/(vend
- vstart
)
524 except (ArithmeticError, TypeError):
526 if newcut
is not None and newcut
< cut
:
530 for vstart
, vend
in zip(privatedata
.lastvpos
, sharedata
.vpos
):
531 cutvpos
.append(vstart
+ (vend
- vstart
) * cut
)
532 privatedata
.linebasepoints
.append(graph
.vpos_pt(*cutvpos
))
533 self
.addpointstopath(privatedata
, sharedata
)
535 # the last point was outside the graph
536 if privatedata
.lastvpos
is not None:
537 if sharedata
.vposvalid
:
540 for vstart
, vend
in zip(privatedata
.lastvpos
, sharedata
.vpos
):
543 # 1 = vstart + (vend - vstart) * cut
545 newcut
= (1 - vstart
)/(vend
- vstart
)
546 except (ArithmeticError, TypeError):
549 # 0 = vstart + (vend - vstart) * cut
551 newcut
= - vstart
/(vend
- vstart
)
552 except (ArithmeticError, TypeError):
554 if newcut
is not None and newcut
> cut
:
558 for vstart
, vend
in zip(privatedata
.lastvpos
, sharedata
.vpos
):
559 cutvpos
.append(vstart
+ (vend
- vstart
) * cut
)
560 privatedata
.linebasepoints
.append(graph
.vpos_pt(*cutvpos
))
561 privatedata
.linebasepoints
.append(graph
.vpos_pt(*sharedata
.vpos
))
563 # sometimes cut beginning and end
566 for vstart
, vend
in zip(privatedata
.lastvpos
, sharedata
.vpos
):
571 # 1 = vstart + (vend - vstart) * cutfrom
573 newcutfrom
= (1 - vstart
)/(vend
- vstart
)
574 except (ArithmeticError, TypeError):
579 # 0 = vstart + (vend - vstart) * cutfrom
581 newcutfrom
= - vstart
/(vend
- vstart
)
582 except (ArithmeticError, TypeError):
584 if newcutfrom
is not None and newcutfrom
> cutfrom
:
588 # 1 = vstart + (vend - vstart) * cutto
590 newcutto
= (1 - vstart
)/(vend
- vstart
)
591 except (ArithmeticError, TypeError):
594 # 0 = vstart + (vend - vstart) * cutto
596 newcutto
= - vstart
/(vend
- vstart
)
597 except (ArithmeticError, TypeError):
599 if newcutto
is not None and newcutto
< cutto
:
605 for vstart
, vend
in zip(privatedata
.lastvpos
, sharedata
.vpos
):
606 cutfromvpos
.append(vstart
+ (vend
- vstart
) * cutfrom
)
607 cuttovpos
.append(vstart
+ (vend
- vstart
) * cutto
)
608 privatedata
.linebasepoints
.append(graph
.vpos_pt(*cutfromvpos
))
609 privatedata
.linebasepoints
.append(graph
.vpos_pt(*cuttovpos
))
610 self
.addpointstopath(privatedata
, sharedata
)
611 privatedata
.lastvpos
= sharedata
.vpos
[:]
613 if len(privatedata
.linebasepoints
) > 1:
614 self
.addpointstopath(privatedata
, sharedata
)
615 privatedata
.lastvpos
= None
617 def donedrawpoints(self
, privatedata
, sharedata
, graph
):
618 if len(privatedata
.linebasepoints
) > 1:
619 self
.addpointstopath(privatedata
, sharedata
)
620 if privatedata
.lineattrs
is not None and len(privatedata
.path
):
621 graph
.stroke(privatedata
.path
, privatedata
.lineattrs
)
623 def key_pt(self
, privatedata
, sharedata
, graph
, x_pt
, y_pt
, width_pt
, height_pt
):
624 if privatedata
.lineattrs
is not None:
625 graph
.stroke(path
.line_pt(x_pt
, y_pt
+0.5*height_pt
, x_pt
+width_pt
, y_pt
+0.5*height_pt
), privatedata
.lineattrs
)
628 class errorbar(_style
):
630 needsdata
= ["vpos", "vposmissing", "vposavailable", "vposvalid", "vrange", "vrangeminmissing", "vrangemaxmissing"]
632 defaulterrorbarattrs
= []
634 def __init__(self
, size
=0.1*unit
.v_cm
,
638 self
.errorbarattrs
= errorbarattrs
639 self
.epsilon
= epsilon
641 def columnnames(self
, privatedata
, sharedata
, graph
, columnnames
):
642 for i
in sharedata
.vposmissing
:
643 if i
in sharedata
.vrangeminmissing
and i
in sharedata
.vrangemaxmissing
:
644 raise ValueError("position and range for a graph dimension missing")
647 def selectstyle(self
, privatedata
, sharedata
, graph
, selectindex
, selecttotal
):
648 privatedata
.errorsize_pt
= unit
.topt(attr
.selectattr(self
.size
, selectindex
, selecttotal
))
649 privatedata
.errorbarattrs
= attr
.selectattrs(self
.defaulterrorbarattrs
+ self
.errorbarattrs
, selectindex
, selecttotal
)
651 def initdrawpoints(self
, privatedata
, sharedata
, graph
):
652 if privatedata
.errorbarattrs
is not None:
653 privatedata
.errorbarcanvas
= canvas
.canvas(privatedata
.errorbarattrs
)
654 privatedata
.dimensionlist
= list(xrange(len(sharedata
.vpos
)))
656 def drawpoint(self
, privatedata
, sharedata
, graph
, point
):
657 if privatedata
.errorbarattrs
is not None:
658 for i
in privatedata
.dimensionlist
:
659 for j
in privatedata
.dimensionlist
:
661 (sharedata
.vpos
[j
] is None or
662 sharedata
.vpos
[j
] < -self
.epsilon
or
663 sharedata
.vpos
[j
] > 1+self
.epsilon
)):
666 if ((sharedata
.vrange
[i
][0] is None and sharedata
.vpos
[i
] is None) or
667 (sharedata
.vrange
[i
][1] is None and sharedata
.vpos
[i
] is None) or
668 (sharedata
.vrange
[i
][0] is None and sharedata
.vrange
[i
][1] is None)):
670 vminpos
= sharedata
.vpos
[:]
671 if sharedata
.vrange
[i
][0] is not None:
672 vminpos
[i
] = sharedata
.vrange
[i
][0]
676 if vminpos
[i
] > 1+self
.epsilon
:
678 if vminpos
[i
] < -self
.epsilon
:
681 vmaxpos
= sharedata
.vpos
[:]
682 if sharedata
.vrange
[i
][1] is not None:
683 vmaxpos
[i
] = sharedata
.vrange
[i
][1]
687 if vmaxpos
[i
] < -self
.epsilon
:
689 if vmaxpos
[i
] > 1+self
.epsilon
:
692 privatedata
.errorbarcanvas
.stroke(graph
.vgeodesic(*(vminpos
+ vmaxpos
)))
693 for j
in privatedata
.dimensionlist
:
696 privatedata
.errorbarcanvas
.stroke(graph
.vcap_pt(j
, privatedata
.errorsize_pt
, *vminpos
))
698 privatedata
.errorbarcanvas
.stroke(graph
.vcap_pt(j
, privatedata
.errorsize_pt
, *vmaxpos
))
700 def donedrawpoints(self
, privatedata
, sharedata
, graph
):
701 if privatedata
.errorbarattrs
is not None:
702 graph
.insert(privatedata
.errorbarcanvas
)
705 class text(_styleneedingpointpos
):
707 needsdata
= ["vpos", "vposmissing", "vposvalid"]
709 defaulttextattrs
= [textmodule
.halign
.center
, textmodule
.vshift
.mathaxis
]
711 def __init__(self
, textname
="text", textdx
=0*unit
.v_cm
, textdy
=0.3*unit
.v_cm
, textattrs
=[]):
712 self
.textname
= textname
715 self
.textattrs
= textattrs
717 def columnnames(self
, privatedata
, sharedata
, graph
, columnnames
):
718 if self
.textname
not in columnnames
:
719 raise ValueError("column '%s' missing" % self
.textname
)
720 return [self
.textname
] + _styleneedingpointpos
.columnnames(self
, privatedata
, sharedata
, graph
, columnnames
)
722 def selectstyle(self
, privatedata
, sharedata
, graph
, selectindex
, selecttotal
):
723 if self
.textattrs
is not None:
724 privatedata
.textattrs
= attr
.selectattrs(self
.defaulttextattrs
+ self
.textattrs
, selectindex
, selecttotal
)
726 privatedata
.textattrs
= None
728 def initdrawpoints(self
, privatedata
, sharedata
, grap
):
729 privatedata
.textdx_pt
= unit
.topt(self
.textdx
)
730 privatedata
.textdy_pt
= unit
.topt(self
.textdy
)
732 def drawpoint(self
, privatedata
, sharedata
, graph
, point
):
733 if privatedata
.textattrs
is not None and sharedata
.vposvalid
:
734 x_pt
, y_pt
= graph
.vpos_pt(*sharedata
.vpos
)
736 text
= str(point
[self
.textname
])
740 graph
.text_pt(x_pt
+ privatedata
.textdx_pt
, y_pt
+ privatedata
.textdy_pt
, text
, privatedata
.textattrs
)
742 def key_pt(self
, privatedata
, sharedata
, graph
, x_pt
, y_pt
, width_pt
, height_pt
):
743 raise RuntimeError("Style currently doesn't provide a graph key")
746 class arrow(_styleneedingpointpos
):
748 needsdata
= ["vpos", "vposmissing", "vposvalid"]
750 defaultlineattrs
= []
751 defaultarrowattrs
= []
753 def __init__(self
, linelength
=0.25*unit
.v_cm
, arrowsize
=0.15*unit
.v_cm
, lineattrs
=[], arrowattrs
=[], epsilon
=1e-5):
754 self
.linelength
= linelength
755 self
.arrowsize
= arrowsize
756 self
.lineattrs
= lineattrs
757 self
.arrowattrs
= arrowattrs
758 self
.epsilon
= epsilon
760 def columnnames(self
, privatedata
, sharedata
, graph
, columnnames
):
761 if len(graph
.axesnames
) != 2:
762 raise ValueError("arrow style restricted on two-dimensional graphs")
763 if "size" not in columnnames
:
764 raise ValueError("size missing")
765 if "angle" not in columnnames
:
766 raise ValueError("angle missing")
767 return ["size", "angle"] + _styleneedingpointpos
.columnnames(self
, privatedata
, sharedata
, graph
, columnnames
)
769 def selectstyle(self
, privatedata
, sharedata
, graph
, selectindex
, selecttotal
):
770 if self
.lineattrs
is not None:
771 privatedata
.lineattrs
= attr
.selectattrs(self
.defaultlineattrs
+ self
.lineattrs
, selectindex
, selecttotal
)
773 privatedata
.lineattrs
= None
774 if self
.arrowattrs
is not None:
775 privatedata
.arrowattrs
= attr
.selectattrs(self
.defaultarrowattrs
+ self
.arrowattrs
, selectindex
, selecttotal
)
777 privatedata
.arrowattrs
= None
779 def initdrawpoints(self
, privatedata
, sharedata
, graph
):
780 privatedata
.arrowcanvas
= canvas
.canvas()
782 def drawpoint(self
, privatedata
, sharedata
, graph
, point
):
783 if privatedata
.lineattrs
is not None and privatedata
.arrowattrs
is not None and sharedata
.vposvalid
:
784 linelength_pt
= unit
.topt(self
.linelength
)
785 x_pt
, y_pt
= graph
.vpos_pt(*sharedata
.vpos
)
787 angle
= point
["angle"] + 0.0
788 size
= point
["size"] + 0.0
792 if point
["size"] > self
.epsilon
:
793 dx
= math
.cos(angle
*math
.pi
/180)
794 dy
= math
.sin(angle
*math
.pi
/180)
795 x1
= x_pt
-0.5*dx
*linelength_pt
*size
796 y1
= y_pt
-0.5*dy
*linelength_pt
*size
797 x2
= x_pt
+0.5*dx
*linelength_pt
*size
798 y2
= y_pt
+0.5*dy
*linelength_pt
*size
799 privatedata
.arrowcanvas
.stroke(path
.line_pt(x1
, y1
, x2
, y2
), privatedata
.lineattrs
+
800 [deco
.earrow(privatedata
.arrowattrs
, size
=self
.arrowsize
*size
)])
802 def donedrawpoints(self
, privatedata
, sharedata
, graph
):
803 graph
.insert(privatedata
.arrowcanvas
)
805 def key_pt(self
, privatedata
, sharedata
, graph
, x_pt
, y_pt
, width_pt
, height_pt
):
806 raise RuntimeError("Style currently doesn't provide a graph key")
811 needsdata
= ["vrange", "vrangeminmissing", "vrangemaxmissing"]
813 def __init__(self
, palette
=color
.palette
.Grey
):
814 self
.palette
= palette
816 def columnnames(self
, privatedata
, sharedata
, graph
, columnnames
):
817 if len(graph
.axesnames
) != 2:
818 raise TypeError("arrow style restricted on two-dimensional graphs")
819 if "color" not in columnnames
:
820 raise ValueError("color missing")
821 if len(sharedata
.vrangeminmissing
) + len(sharedata
.vrangemaxmissing
):
822 raise ValueError("incomplete range")
825 def initdrawpoints(self
, privatedata
, sharedata
, graph
):
826 privatedata
.rectcanvas
= graph
.insert(canvas
.canvas())
828 def drawpoint(self
, privatedata
, sharedata
, graph
, point
):
829 xvmin
= sharedata
.vrange
[0][0]
830 xvmax
= sharedata
.vrange
[0][1]
831 yvmin
= sharedata
.vrange
[1][0]
832 yvmax
= sharedata
.vrange
[1][1]
833 if (xvmin
is not None and xvmin
< 1 and
834 xvmax
is not None and xvmax
> 0 and
835 yvmin
is not None and yvmin
< 1 and
836 yvmax
is not None and yvmax
> 0):
845 p
= graph
.vgeodesic(xvmin
, yvmin
, xvmax
, yvmin
)
846 p
.append(graph
.vgeodesic_el(xvmax
, yvmin
, xvmax
, yvmax
))
847 p
.append(graph
.vgeodesic_el(xvmax
, yvmax
, xvmin
, yvmax
))
848 p
.append(graph
.vgeodesic_el(xvmin
, yvmax
, xvmin
, yvmin
))
849 p
.append(path
.closepath())
850 privatedata
.rectcanvas
.fill(p
, [self
.palette
.getcolor(point
["color"])])
852 def key_pt(self
, privatedata
, sharedata
, graph
, x_pt
, y_pt
, width_pt
, height_pt
):
853 raise RuntimeError("Style currently doesn't provide a graph key")
856 class histogram(_style
):
858 needsdata
= ["vpos", "vposmissing", "vrange", "vrangeminmissing", "vrangemaxmissing"]
860 defaultlineattrs
= [deco
.stroked
]
861 defaultfrompathattrs
= []
863 def __init__(self
, lineattrs
=[], steps
=0, fromvalue
=0, frompathattrs
=[], fillable
=0,
864 autohistogramaxisindex
=0, autohistogrampointpos
=0.5, epsilon
=1e-10):
865 self
.lineattrs
= lineattrs
867 self
.fromvalue
= fromvalue
868 self
.frompathattrs
= frompathattrs
869 self
.fillable
= fillable
# TODO: fillable paths might not properly be closed by straight lines on curved graph geometries
870 self
.autohistogramaxisindex
= autohistogramaxisindex
871 self
.autohistogrampointpos
= autohistogrampointpos
872 self
.epsilon
= epsilon
874 def columnnames(self
, privatedata
, sharedata
, graph
, columnnames
):
875 if len(graph
.axesnames
) != 2:
876 raise TypeError("histogram style restricted on two-dimensional graphs")
877 privatedata
.rangeaxisindex
= None
878 for i
in builtinrange(len(graph
.axesnames
)):
879 if i
in sharedata
.vrangeminmissing
or i
in sharedata
.vrangemaxmissing
:
880 if i
in sharedata
.vposmissing
:
881 raise ValueError("pos and range missing")
883 if privatedata
.rangeaxisindex
is not None:
884 raise ValueError("multiple ranges")
885 privatedata
.rangeaxisindex
= i
886 if privatedata
.rangeaxisindex
is None:
887 privatedata
.rangeaxisindex
= self
.autohistogramaxisindex
888 privatedata
.autohistogram
= 1
890 privatedata
.autohistogram
= 0
893 def adjustaxis(self
, privatedata
, sharedata
, graph
, columnname
, data
):
894 if privatedata
.autohistogram
and columnname
== sharedata
.poscolumnnames
[privatedata
.rangeaxisindex
]:
896 raise ValueError("several data points needed for automatic histogram width calculation")
898 delta
= data
[1] - data
[0]
899 min = data
[0] - self
.autohistogrampointpos
* delta
900 max = data
[-1] + (1-self
.autohistogrampointpos
) * delta
901 graph
.axes
[columnname
].adjustaxis([min, max])
903 def selectstyle(self
, privatedata
, sharedata
, graph
, selectindex
, selecttotal
):
904 privatedata
.insertfrompath
= selectindex
== 0
905 if self
.lineattrs
is not None:
906 privatedata
.lineattrs
= attr
.selectattrs(self
.defaultlineattrs
+ self
.lineattrs
, selectindex
, selecttotal
)
908 privatedata
.lineattrs
= None
910 def vmoveto(self
, privatedata
, sharedata
, graph
, vpos
, vvalue
):
911 if -self
.epsilon
< vpos
< 1+self
.epsilon
and -self
.epsilon
< vvalue
< 1+self
.epsilon
:
912 if privatedata
.rangeaxisindex
:
913 privatedata
.path
.append(path
.moveto_pt(*graph
.vpos_pt(vvalue
, vpos
)))
915 privatedata
.path
.append(path
.moveto_pt(*graph
.vpos_pt(vpos
, vvalue
)))
917 def vposline(self
, privatedata
, sharedata
, graph
, vpos
, vvalue1
, vvalue2
):
918 if -self
.epsilon
< vpos
< 1+self
.epsilon
:
933 if abs(vvalue1cut
+ vvalue2cut
) <= 1:
934 if vvalue1cut
and not self
.fillable
:
935 if privatedata
.rangeaxisindex
:
936 privatedata
.path
.append(path
.moveto_pt(*graph
.vpos_pt(vvalue1
, vpos
)))
938 privatedata
.path
.append(path
.moveto_pt(*graph
.vpos_pt(vpos
, vvalue1
)))
939 if privatedata
.rangeaxisindex
:
940 privatedata
.path
.append(graph
.vgeodesic_el(vvalue1
, vpos
, vvalue2
, vpos
))
942 privatedata
.path
.append(graph
.vgeodesic_el(vpos
, vvalue1
, vpos
, vvalue2
))
944 def vvalueline(self
, privatedata
, sharedata
, graph
, vvalue
, vpos1
, vpos2
):
946 if vvalue
< -self
.epsilon
:
948 warnings
.warn("cut at graph boundary adds artificial lines to fillable step histogram path")
949 if vvalue
> 1+self
.epsilon
:
951 warnings
.warn("cut at graph boundary adds artificial lines to fillable step histogram path")
952 if self
.fillable
or (-self
.epsilon
< vvalue
< 1+self
.epsilon
):
967 if abs(vpos1cut
+ vpos2cut
) <= 1:
970 if privatedata
.rangeaxisindex
:
971 privatedata
.path
.append(path
.moveto_pt(*graph
.vpos_pt(privatedata
.vfromvalue
, vpos1
)))
972 privatedata
.path
.append(graph
.vgeodesic_el(privatedata
.vfromvalue
, vpos1
, vvalue
, vpos1
))
974 privatedata
.path
.append(path
.moveto_pt(*graph
.vpos_pt(vpos1
, privatedata
.vfromvalue
)))
975 privatedata
.path
.append(graph
.vgeodesic_el(vpos1
, privatedata
.vfromvalue
, vpos1
, vvalue
))
977 if privatedata
.rangeaxisindex
:
978 privatedata
.path
.append(path
.moveto_pt(*graph
.vpos_pt(vvalue
, vpos1
)))
980 privatedata
.path
.append(path
.moveto_pt(*graph
.vpos_pt(vpos1
, vvalue
)))
981 if privatedata
.rangeaxisindex
:
982 privatedata
.path
.append(graph
.vgeodesic_el(vvalue
, vpos1
, vvalue
, vpos2
))
984 privatedata
.path
.append(graph
.vgeodesic_el(vpos1
, vvalue
, vpos2
, vvalue
))
985 if self
.fillable
and vpos2cut
:
986 warnings
.warn("cut at graph boundary adds artificial lines to fillable step histogram path")
987 if privatedata
.rangeaxisindex
:
988 privatedata
.path
.append(graph
.vgeodesic_el(vvalue
, vpos2
, privatedata
.vfromvalue
, vpos2
))
990 privatedata
.path
.append(graph
.vgeodesic_el(vpos2
, vvalue
, vpos2
, privatedata
.vfromvalue
))
992 def drawvalue(self
, privatedata
, sharedata
, graph
, vmin
, vmax
, vvalue
):
993 currentvalid
= vmin
is not None and vmax
is not None and vvalue
is not None
994 if self
.fillable
and not self
.steps
:
998 if vmin
< -self
.epsilon
:
1001 elif vmin
> 1+self
.epsilon
:
1005 if vmax
< -self
.epsilon
:
1008 if vmax
> 1+self
.epsilon
:
1012 if vvalue
< -self
.epsilon
:
1015 if vvalue
> 1+self
.epsilon
:
1019 if abs(vmincut
) + abs(vmaxcut
) + abs(vvaluecut
) + abs(privatedata
.vfromvaluecut
) > 1:
1020 if abs(vmincut
+ vmaxcut
) > 1 or abs(vvaluecut
+privatedata
.vfromvaluecut
) > 1:
1023 warnings
.warn("multiple cuts at graph boundary add artificial lines to fillable rectangle histogram path")
1026 if privatedata
.rangeaxisindex
:
1027 privatedata
.path
.append(path
.moveto_pt(*graph
.vpos_pt(privatedata
.vfromvalue
, vmin
)))
1028 privatedata
.path
.append(graph
.vgeodesic_el(privatedata
.vfromvalue
, vmin
, privatedata
.vfromvalue
, vmax
))
1029 privatedata
.path
.append(graph
.vgeodesic_el(privatedata
.vfromvalue
, vmax
, vvalue
, vmax
))
1030 privatedata
.path
.append(graph
.vgeodesic_el(vvalue
, vmax
, vvalue
, vmin
))
1032 privatedata
.path
.append(path
.moveto_pt(*graph
.vpos_pt(vmin
, privatedata
.vfromvalue
)))
1033 privatedata
.path
.append(graph
.vgeodesic_el(vmin
, privatedata
.vfromvalue
, vmax
, privatedata
.vfromvalue
))
1034 privatedata
.path
.append(graph
.vgeodesic_el(vmax
, privatedata
.vfromvalue
, vmax
, vvalue
))
1035 privatedata
.path
.append(graph
.vgeodesic_el(vmax
, vvalue
, vmin
, vvalue
))
1038 if privatedata
.rangeaxisindex
:
1039 privatedata
.path
.append(path
.moveto_pt(*graph
.vpos_pt(vvalue
, vmax
)))
1040 privatedata
.path
.append(graph
.vgeodesic_el(vvalue
, vmax
, vvalue
, vmin
))
1041 privatedata
.path
.append(graph
.vgeodesic_el(vvalue
, vmin
, privatedata
.vfromvalue
, vmin
))
1042 privatedata
.path
.append(graph
.vgeodesic_el(privatedata
.vfromvalue
, vmin
, privatedata
.vfromvalue
, vmax
))
1044 privatedata
.path
.append(path
.moveto_pt(*graph
.vpos_pt(vmax
, vvalue
)))
1045 privatedata
.path
.append(graph
.vgeodesic_el(vmax
, vvalue
, vmin
, vvalue
))
1046 privatedata
.path
.append(graph
.vgeodesic_el(vmin
, vvalue
, vmin
, privatedata
.vfromvalue
))
1047 privatedata
.path
.append(graph
.vgeodesic_el(vmin
, privatedata
.vfromvalue
, vmax
, privatedata
.vfromvalue
))
1048 elif privatedata
.vfromvaluecut
:
1050 if privatedata
.rangeaxisindex
:
1051 privatedata
.path
.append(path
.moveto_pt(*graph
.vpos_pt(privatedata
.vfromvalue
, vmax
)))
1052 privatedata
.path
.append(graph
.vgeodesic_el(privatedata
.vfromvalue
, vmax
, vvalue
, vmax
))
1053 privatedata
.path
.append(graph
.vgeodesic_el(vvalue
, vmax
, vvalue
, vmin
))
1054 privatedata
.path
.append(graph
.vgeodesic_el(vvalue
, vmin
, privatedata
.vfromvalue
, vmin
))
1056 privatedata
.path
.append(path
.moveto_pt(*graph
.vpos_pt(vmax
, privatedata
.vfromvalue
)))
1057 privatedata
.path
.append(graph
.vgeodesic_el(vmax
, privatedata
.vfromvalue
, vmax
, vvalue
))
1058 privatedata
.path
.append(graph
.vgeodesic_el(vmax
, vvalue
, vmin
, vvalue
))
1059 privatedata
.path
.append(graph
.vgeodesic_el(vmin
, vvalue
, vmin
, privatedata
.vfromvalue
))
1062 if privatedata
.rangeaxisindex
:
1063 privatedata
.path
.append(path
.moveto_pt(*graph
.vpos_pt(vvalue
, vmin
)))
1064 privatedata
.path
.append(graph
.vgeodesic_el(vvalue
, vmin
, privatedata
.vfromvalue
, vmin
))
1065 privatedata
.path
.append(graph
.vgeodesic_el(privatedata
.vfromvalue
, vmin
, privatedata
.vfromvalue
, vmax
))
1066 privatedata
.path
.append(graph
.vgeodesic_el(privatedata
.vfromvalue
, vmax
, vvalue
, vmax
))
1068 privatedata
.path
.append(path
.moveto_pt(*graph
.vpos_pt(vmin
, vvalue
)))
1069 privatedata
.path
.append(graph
.vgeodesic_el(vmin
, vvalue
, vmin
, privatedata
.vfromvalue
))
1070 privatedata
.path
.append(graph
.vgeodesic_el(vmin
, privatedata
.vfromvalue
, vmax
, privatedata
.vfromvalue
))
1071 privatedata
.path
.append(graph
.vgeodesic_el(vmax
, privatedata
.vfromvalue
, vmax
, vvalue
))
1073 if privatedata
.rangeaxisindex
:
1074 privatedata
.path
.append(path
.moveto_pt(*graph
.vpos_pt(privatedata
.vfromvalue
, vmin
)))
1075 privatedata
.path
.append(graph
.vgeodesic_el(privatedata
.vfromvalue
, vmin
, privatedata
.vfromvalue
, vmax
))
1076 privatedata
.path
.append(graph
.vgeodesic_el(privatedata
.vfromvalue
, vmax
, vvalue
, vmax
))
1077 privatedata
.path
.append(graph
.vgeodesic_el(vvalue
, vmax
, vvalue
, vmin
))
1078 privatedata
.path
.append(graph
.vgeodesic_el(vvalue
, vmin
, privatedata
.vfromvalue
, vmin
))
1079 privatedata
.path
.append(path
.closepath())
1081 privatedata
.path
.append(path
.moveto_pt(*graph
.vpos_pt(vmin
, privatedata
.vfromvalue
)))
1082 privatedata
.path
.append(graph
.vgeodesic_el(vmin
, privatedata
.vfromvalue
, vmax
, privatedata
.vfromvalue
))
1083 privatedata
.path
.append(graph
.vgeodesic_el(vmax
, privatedata
.vfromvalue
, vmax
, vvalue
))
1084 privatedata
.path
.append(graph
.vgeodesic_el(vmax
, vvalue
, vmin
, vvalue
))
1085 privatedata
.path
.append(graph
.vgeodesic_el(vmin
, vvalue
, vmin
, privatedata
.vfromvalue
))
1086 privatedata
.path
.append(path
.closepath())
1089 gap
= abs(vmin
- privatedata
.lastvmax
) > self
.epsilon
1090 except (ArithmeticError, ValueError, TypeError):
1092 if (privatedata
.lastvvalue
is not None and
1096 (privatedata
.lastvvalue
-privatedata
.vfromvalue
)*(vvalue
-privatedata
.vfromvalue
) < 0)):
1097 self
.vposline(privatedata
, sharedata
, graph
,
1098 vmin
, privatedata
.lastvvalue
, vvalue
)
1100 if (privatedata
.lastvvalue
is not None and
1101 (not currentvalid
or
1102 abs(privatedata
.lastvvalue
-privatedata
.vfromvalue
) > abs(vvalue
-privatedata
.vfromvalue
) or
1104 self
.vposline(privatedata
, sharedata
, graph
,
1105 privatedata
.lastvmax
, privatedata
.lastvvalue
, privatedata
.vfromvalue
)
1107 self
.vmoveto(privatedata
, sharedata
, graph
,
1109 if (currentvalid
and
1110 (privatedata
.lastvvalue
is None or
1111 abs(privatedata
.lastvvalue
-privatedata
.vfromvalue
) < abs(vvalue
-privatedata
.vfromvalue
) or
1113 self
.vmoveto(privatedata
, sharedata
, graph
,
1114 vmin
, privatedata
.vfromvalue
)
1115 self
.vposline(privatedata
, sharedata
, graph
,
1116 vmin
, privatedata
.vfromvalue
, vvalue
)
1118 self
.vvalueline(privatedata
, sharedata
, graph
,
1120 privatedata
.lastvvalue
= vvalue
1121 privatedata
.lastvmax
= vmax
1123 privatedata
.lastvvalue
= privatedata
.lastvmax
= None
1125 def initdrawpoints(self
, privatedata
, sharedata
, graph
):
1126 privatedata
.path
= path
.path()
1127 privatedata
.lastvvalue
= privatedata
.lastvmax
= None
1128 privatedata
.vcurrentpoint
= None
1129 privatedata
.count
= 0
1130 if self
.fromvalue
is not None:
1131 valueaxisname
= sharedata
.poscolumnnames
[1-privatedata
.rangeaxisindex
]
1132 privatedata
.vfromvalue
= graph
.axes
[valueaxisname
].convert(self
.fromvalue
)
1133 privatedata
.vfromvaluecut
= 0
1134 if privatedata
.vfromvalue
< 0:
1135 privatedata
.vfromvalue
= 0
1136 privatedata
.vfromvaluecut
= -1
1137 if privatedata
.vfromvalue
> 1:
1138 privatedata
.vfromvalue
= 1
1139 privatedata
.vfromvaluecut
= 1
1140 if self
.frompathattrs
is not None and privatedata
.insertfrompath
:
1141 graph
.stroke(graph
.axes
[valueaxisname
].vgridpath(privatedata
.vfromvalue
),
1142 self
.defaultfrompathattrs
+ self
.frompathattrs
)
1144 privatedata
.vfromvalue
= 0
1146 def drawpoint(self
, privatedata
, sharedata
, graph
, point
):
1147 if privatedata
.autohistogram
:
1148 # automatic range handling
1149 privatedata
.count
+= 1
1150 if privatedata
.count
== 2:
1151 if privatedata
.rangeaxisindex
:
1152 privatedata
.vrange
= sharedata
.vpos
[1] - privatedata
.lastvpos
[1]
1153 self
.drawvalue(privatedata
, sharedata
, graph
,
1154 privatedata
.lastvpos
[1] - self
.autohistogrampointpos
*privatedata
.vrange
,
1155 privatedata
.lastvpos
[1] + (1-self
.autohistogrampointpos
)*privatedata
.vrange
,
1156 privatedata
.lastvpos
[0])
1158 privatedata
.vrange
= sharedata
.vpos
[0] - privatedata
.lastvpos
[0]
1159 self
.drawvalue(privatedata
, sharedata
, graph
,
1160 privatedata
.lastvpos
[0] - self
.autohistogrampointpos
*privatedata
.vrange
,
1161 privatedata
.lastvpos
[0] + (1-self
.autohistogrampointpos
)*privatedata
.vrange
,
1162 privatedata
.lastvpos
[1])
1163 elif privatedata
.count
> 2:
1164 if privatedata
.rangeaxisindex
:
1165 vrange
= sharedata
.vpos
[1] - privatedata
.lastvpos
[1]
1167 vrange
= sharedata
.vpos
[0] - privatedata
.lastvpos
[0]
1168 if abs(privatedata
.vrange
- vrange
) > self
.epsilon
:
1169 raise ValueError("equal steps (in graph coordinates) needed for automatic width calculation")
1170 if privatedata
.count
> 1:
1171 if privatedata
.rangeaxisindex
:
1172 self
.drawvalue(privatedata
, sharedata
, graph
,
1173 sharedata
.vpos
[1] - self
.autohistogrampointpos
*privatedata
.vrange
,
1174 sharedata
.vpos
[1] + (1-self
.autohistogrampointpos
)*privatedata
.vrange
,
1177 self
.drawvalue(privatedata
, sharedata
, graph
,
1178 sharedata
.vpos
[0] - self
.autohistogrampointpos
*privatedata
.vrange
,
1179 sharedata
.vpos
[0] + (1-self
.autohistogrampointpos
)*privatedata
.vrange
,
1181 privatedata
.lastvpos
= sharedata
.vpos
[:]
1183 if privatedata
.rangeaxisindex
:
1184 self
.drawvalue(privatedata
, sharedata
, graph
,
1185 sharedata
.vrange
[1][0], sharedata
.vrange
[1][1], sharedata
.vpos
[0])
1187 self
.drawvalue(privatedata
, sharedata
, graph
,
1188 sharedata
.vrange
[0][0], sharedata
.vrange
[0][1], sharedata
.vpos
[1])
1190 def donedrawpoints(self
, privatedata
, sharedata
, graph
):
1191 self
.drawvalue(privatedata
, sharedata
, graph
, None, None, None)
1192 if privatedata
.lineattrs
is not None and len(privatedata
.path
):
1193 graph
.draw(privatedata
.path
, privatedata
.lineattrs
)
1196 class barpos(_style
):
1198 providesdata
= ["vpos", "vposmissing", "vposavailable", "vposvalid", "vbarrange", "barposcolumnnames", "barvalueindex", "lastbarvalue", "stackedbar", "stackedbardraw"]
1200 defaultfrompathattrs
= []
1202 def __init__(self
, fromvalue
=None, frompathattrs
=[], epsilon
=1e-10):
1203 self
.fromvalue
= fromvalue
1204 self
.frompathattrs
= frompathattrs
1205 self
.epsilon
= epsilon
1207 def columnnames(self
, privatedata
, sharedata
, graph
, columnnames
):
1208 sharedata
.barposcolumnnames
= []
1209 sharedata
.barvalueindex
= None
1210 for dimension
, axisnames
in enumerate(graph
.axesnames
):
1212 for axisname
in axisnames
:
1213 if axisname
in columnnames
:
1214 if sharedata
.barvalueindex
is not None:
1215 raise ValueError("multiple values")
1216 sharedata
.barvalueindex
= dimension
1217 sharedata
.barposcolumnnames
.append(axisname
)
1219 if (axisname
+ "name") in columnnames
:
1220 sharedata
.barposcolumnnames
.append(axisname
+ "name")
1223 raise ValueError("multiple names and value")
1225 raise ValueError("value/name missing")
1226 if sharedata
.barvalueindex
is None:
1227 raise ValueError("missing value")
1228 sharedata
.vposmissing
= []
1229 return sharedata
.barposcolumnnames
1231 def addsubvalue(self
, value
, subvalue
):
1236 return value
[0], self
.addsubvalue(value
[1], subvalue
)
1238 return value
, subvalue
1240 return value
, subvalue
1242 def adjustaxis(self
, privatedata
, sharedata
, graph
, columnname
, data
):
1244 i
= sharedata
.barposcolumnnames
.index(columnname
)
1248 if i
== sharedata
.barvalueindex
:
1249 if self
.fromvalue
is not None:
1250 graph
.axes
[sharedata
.barposcolumnnames
[i
]].adjustaxis([self
.fromvalue
])
1251 graph
.axes
[sharedata
.barposcolumnnames
[i
]].adjustaxis(data
)
1253 graph
.axes
[sharedata
.barposcolumnnames
[i
][:-4]].adjustaxis([self
.addsubvalue(x
, 0) for x
in data
])
1254 graph
.axes
[sharedata
.barposcolumnnames
[i
][:-4]].adjustaxis([self
.addsubvalue(x
, 1) for x
in data
])
1256 def selectstyle(self
, privatedata
, sharedata
, graph
, selectindex
, selecttotal
):
1257 privatedata
.insertfrompath
= selectindex
== 0
1259 def initdrawpoints(self
, privatedata
, sharedata
, graph
):
1260 sharedata
.vpos
= [None]*(len(sharedata
.barposcolumnnames
))
1261 sharedata
.vbarrange
= [[None for i
in xrange(2)] for x
in sharedata
.barposcolumnnames
]
1262 sharedata
.stackedbar
= sharedata
.stackedbardraw
= 0
1264 if self
.fromvalue
is not None:
1265 privatedata
.vfromvalue
= graph
.axes
[sharedata
.barposcolumnnames
[sharedata
.barvalueindex
][0]].convert(self
.fromvalue
)
1266 if privatedata
.vfromvalue
< 0:
1267 privatedata
.vfromvalue
= 0
1268 if privatedata
.vfromvalue
> 1:
1269 privatedata
.vfromvalue
= 1
1270 if self
.frompathattrs
is not None and privatedata
.insertfrompath
:
1271 graph
.stroke(graph
.axes
[sharedata
.barposcolumnnames
[sharedata
.barvalueindex
][0]].vgridpath(privatedata
.vfromvalue
),
1272 self
.defaultfrompathattrs
+ self
.frompathattrs
)
1274 privatedata
.vfromvalue
= 0
1276 def drawpoint(self
, privatedata
, sharedata
, graph
, point
):
1277 sharedata
.vposavailable
= sharedata
.vposvalid
= 1
1278 for i
, barname
in enumerate(sharedata
.barposcolumnnames
):
1279 if i
== sharedata
.barvalueindex
:
1280 sharedata
.vbarrange
[i
][0] = privatedata
.vfromvalue
1281 sharedata
.lastbarvalue
= point
[barname
]
1283 sharedata
.vpos
[i
] = sharedata
.vbarrange
[i
][1] = graph
.axes
[barname
].convert(sharedata
.lastbarvalue
)
1284 except (ArithmeticError, ValueError, TypeError):
1285 sharedata
.vpos
[i
] = sharedata
.vbarrange
[i
][1] = None
1289 sharedata
.vbarrange
[i
][j
] = graph
.axes
[barname
[:-4]].convert(self
.addsubvalue(point
[barname
], j
))
1290 except (ArithmeticError, ValueError, TypeError):
1291 sharedata
.vbarrange
[i
][j
] = None
1293 sharedata
.vpos
[i
] = 0.5*(sharedata
.vbarrange
[i
][0]+sharedata
.vbarrange
[i
][1])
1294 except (ArithmeticError, ValueError, TypeError):
1295 sharedata
.vpos
[i
] = None
1296 if sharedata
.vpos
[i
] is None:
1297 sharedata
.vposavailable
= sharedata
.vposvalid
= 0
1298 elif sharedata
.vpos
[i
] < -self
.epsilon
or sharedata
.vpos
[i
] > 1+self
.epsilon
:
1299 sharedata
.vposvalid
= 0
1301 registerdefaultprovider(barpos(), ["vbarrange", "barposcolumnnames", "barvalueindex", "lastbarvalue", "stackedbar", "stackedbardraw"])
1304 class stackedbarpos(_style
):
1306 # provides no additional data, but needs some data (and modifies some of them)
1307 needsdata
= ["vbarrange", "barposcolumnnames", "barvalueindex", "lastbarvalue", "stackedbar", "stackedbardraw"]
1309 def __init__(self
, stackname
, addontop
=0, epsilon
=1e-10):
1310 self
.stackname
= stackname
1311 self
.epsilon
= epsilon
1312 self
.addontop
= addontop
1314 def columnnames(self
, privatedata
, sharedata
, graph
, columnnames
):
1315 if self
.stackname
not in columnnames
:
1316 raise ValueError("column '%s' missing" % self
.stackname
)
1317 return [self
.stackname
]
1319 def adjustaxis(self
, privatedata
, sharedata
, graph
, columnname
, data
):
1320 if columnname
== self
.stackname
:
1321 graph
.axes
[sharedata
.barposcolumnnames
[sharedata
.barvalueindex
]].adjustaxis(data
)
1323 def initdrawpoints(self
, privatedata
, sharedata
, graph
):
1324 if sharedata
.stackedbardraw
: # do not count the start bar when not gets painted
1325 sharedata
.stackedbar
+= 1
1327 def drawpoint(self
, privatedata
, sharedata
, graph
, point
):
1328 sharedata
.vbarrange
[sharedata
.barvalueindex
][0] = sharedata
.vbarrange
[sharedata
.barvalueindex
][1]
1331 sharedata
.lastbarvalue
+= point
[self
.stackname
]
1332 except (ArithmeticError, ValueError, TypeError):
1333 sharedata
.lastbarvalue
= None
1335 sharedata
.lastbarvalue
= point
[self
.stackname
]
1337 sharedata
.vpos
[sharedata
.barvalueindex
] = sharedata
.vbarrange
[sharedata
.barvalueindex
][1] = graph
.axes
[sharedata
.barposcolumnnames
[sharedata
.barvalueindex
]].convert(sharedata
.lastbarvalue
)
1338 except (ArithmeticError, ValueError, TypeError):
1339 sharedata
.vpos
[sharedata
.barvalueindex
] = sharedata
.vbarrange
[sharedata
.barvalueindex
][1] = None
1340 sharedata
.vposavailable
= sharedata
.vposvalid
= 0
1342 if not sharedata
.vposavailable
or not sharedata
.vposvalid
:
1343 sharedata
.vposavailable
= sharedata
.vposvalid
= 1
1344 for v
in sharedata
.vpos
:
1346 sharedata
.vposavailable
= sharedata
.vposvalid
= 0
1348 if v
< -self
.epsilon
or v
> 1+self
.epsilon
:
1349 sharedata
.vposvalid
= 0
1354 needsdata
= ["vbarrange"]
1356 defaultbarattrs
= [color
.palette
.Rainbow
, deco
.stroked([color
.grey
.black
])]
1358 def __init__(self
, barattrs
=[]):
1359 self
.barattrs
= barattrs
1361 def columnnames(self
, privatedata
, sharedata
, graph
, columnnames
):
1362 if len(graph
.axesnames
) != 2:
1363 raise TypeError("bar style restricted on two-dimensional graphs")
1366 def selectstyle(self
, privatedata
, sharedata
, graph
, selectindex
, selecttotal
):
1367 privatedata
.barattrs
= attr
.selectattrs(self
.defaultbarattrs
+ self
.barattrs
, selectindex
, selecttotal
)
1369 def initdrawpoints(self
, privatedata
, sharedata
, graph
):
1370 privatedata
.rectcanvas
= graph
.insert(canvas
.canvas())
1371 sharedata
.stackedbardraw
= 1
1372 privatedata
.stackedbar
= sharedata
.stackedbar
1374 def drawpointfill(self
, privatedata
, p
):
1376 privatedata
.rectcanvas
.fill(p
, privatedata
.barattrs
)
1378 def drawpoint(self
, privatedata
, sharedata
, graph
, point
):
1379 xvmin
= sharedata
.vbarrange
[0][0]
1380 xvmax
= sharedata
.vbarrange
[0][1]
1381 yvmin
= sharedata
.vbarrange
[1][0]
1382 yvmax
= sharedata
.vbarrange
[1][1]
1385 xvmin
, xvmax
= xvmax
, xvmin
1390 yvmin
, yvmax
= yvmax
, yvmin
1393 if (xvmin
is not None and xvmin
< 1 and
1394 xvmax
is not None and xvmax
> 0 and
1395 yvmin
is not None and yvmin
< 1 and
1396 yvmax
is not None and yvmax
> 0):
1405 p
= graph
.vgeodesic(xvmin
, yvmin
, xvmax
, yvmin
)
1406 p
.append(graph
.vgeodesic_el(xvmax
, yvmin
, xvmax
, yvmax
))
1407 p
.append(graph
.vgeodesic_el(xvmax
, yvmax
, xvmin
, yvmax
))
1408 p
.append(graph
.vgeodesic_el(xvmin
, yvmax
, xvmin
, yvmin
))
1409 p
.append(path
.closepath())
1410 self
.drawpointfill(privatedata
, p
)
1412 self
.drawpointfill(privatedata
, None)
1414 def key_pt(self
, privatedata
, sharedata
, graph
, x_pt
, y_pt
, width_pt
, height_pt
):
1415 selectindex
= privatedata
.stackedbar
1416 selecttotal
= sharedata
.stackedbar
+ 1
1417 graph
.fill(path
.rect_pt(x_pt
+ width_pt
*selectindex
/float(selecttotal
), y_pt
, width_pt
/float(selecttotal
), height_pt
), privatedata
.barattrs
)
1420 class changebar(bar
):
1422 def selectstyle(self
, privatedata
, sharedata
, graph
, selectindex
, selecttotal
):
1423 if selecttotal
!= 1:
1424 raise RuntimeError("Changebar can't change its appearance. Thus you can't use it to plot several bars side by side on a subaxis.")
1426 def initdrawpoints(self
, privatedata
, sharedata
, graph
):
1427 bar
.initdrawpoints(self
, privatedata
, sharedata
, graph
)
1428 privatedata
.bars
= []
1430 def drawpointfill(self
, privatedata
, p
):
1431 privatedata
.bars
.append(p
)
1433 def donedrawpoints(self
, privatedata
, sharedata
, graph
):
1434 selecttotal
= len(privatedata
.bars
)
1435 for selectindex
, p
in enumerate(privatedata
.bars
):
1437 barattrs
= attr
.selectattrs(self
.defaultbarattrs
+ self
.barattrs
, selectindex
, selecttotal
)
1438 privatedata
.rectcanvas
.fill(p
, barattrs
)
1440 def key_pt(self
, privatedata
, sharedata
, graph
, x_pt
, y_pt
, width_pt
, height_pt
):
1441 raise RuntimeError("Style currently doesn't provide a graph key")