1 # -*- coding: ISO-8859-1 -*-
4 # Copyright (C) 2002-2004 Jörg Lehmann <joergl@users.sourceforge.net>
5 # Copyright (C) 2003-2004 Michael Schindler <m-schindler@users.sourceforge.net>
6 # Copyright (C) 2002-2006 André Wobst <wobsta@users.sourceforge.net>
8 # This file is part of PyX (http://pyx.sourceforge.net/).
10 # PyX is free software; you can redistribute it and/or modify
11 # it under the terms of the GNU General Public License as published by
12 # the Free Software Foundation; either version 2 of the License, or
13 # (at your option) any later version.
15 # PyX is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 # GNU General Public License for more details.
20 # You should have received a copy of the GNU General Public License
21 # along with PyX; if not, write to the Free Software
22 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
26 from pyx
import attr
, deco
, style
, color
, unit
, canvas
, path
27 from pyx
import text
as textmodule
34 # fallback implementation for Python 2.2. and below
36 return zip(xrange(len(list)), list)
39 """Interface class for graph styles
41 Each graph style must support the methods described in this
42 class. However, since a graph style might not need to perform
43 actions on all the various events, it does not need to overwrite
44 all methods of this base class (e.g. this class is not an abstract
45 class in any respect).
47 A style should never store private data by istance variables
48 (i.e. accessing self), but it should use the sharedata and privatedata
49 instances instead. A style instance can be used multiple times with
50 different sharedata and privatedata instances at the very same time.
51 The sharedata and privatedata instances act as data containers and
52 sharedata allows for sharing information across several styles.
54 Every style contains two class variables, which are not to be
56 - providesdata is a list of variable names a style offers via
57 the sharedata instance. This list is used to determine whether
58 all needs of subsequent styles are fullfilled. Otherwise
59 getdefaultprovider should return a proper style to be used.
60 - needsdata is a list of variable names the style needs to access in the
64 providesdata
= [] # by default, we provide nothing
65 needsdata
= [] # and do not depend on anything
67 def columnnames(self
, privatedata
, sharedata
, graph
, columnnames
):
68 """Set column information
70 This method is used setup the column name information to be
71 accessible to the style later on. The style should analyse
72 the list of column names. The method should return a list of
73 column names which the style will make use of."""
76 def adjustaxis(self
, privatedata
, sharedata
, graph
, columnname
, data
):
79 This method is called in order to adjust the axis range to
80 the provided data. columnname is the column name (each style
81 is subsequently called for all column names)."""
84 def selectstyle(self
, privatedata
, sharedata
, graph
, selectindex
, selecttotal
):
85 """Select stroke/fill attributes
87 This method is called to allow for the selection of
88 changable attributes of a style."""
91 def initdrawpoints(self
, privatedata
, sharedata
, graph
):
92 """Initialize drawing of data
94 This method might be used to initialize the drawing of data."""
97 def drawpoint(self
, privatedata
, sharedata
, graph
, point
):
100 This method is called for each data point. The data is
101 available in the dictionary point. The dictionary
102 keys are the column names."""
105 def donedrawpoints(self
, privatedata
, sharedata
, graph
):
106 """Finalize drawing of data
108 This method is called after the last data point was
109 drawn using the drawpoint method above."""
112 def key_pt(self
, privatedata
, sharedata
, graph
, x_pt
, y_pt
, width_pt
, height_pt
):
116 # The following two methods are used to register and get a default provider
117 # for keys. A key is a variable name in sharedata. A provider is a style
118 # which creates variables in sharedata.
120 _defaultprovider
= {}
122 def registerdefaultprovider(style
, keys
):
123 """sets a style as a default creator for sharedata variables 'keys'"""
124 assert not len(style
.needsdata
), "currently we state, that a style should not depend on other sharedata variables"
126 assert key
in style
.providesdata
, "key not provided by style"
127 # we might allow for overwriting the defaults, i.e. the following is not checked:
128 # assert key in _defaultprovider.keys(), "default provider already registered for key"
129 _defaultprovider
[key
] = style
131 def getdefaultprovider(key
):
132 """returns a style, which acts as a default creator for the
133 sharedata variable 'key'"""
134 return _defaultprovider
[key
]
139 providesdata
= ["vpos", "vposmissing", "vposavailable", "vposvalid", "poscolumnnames"]
141 def __init__(self
, epsilon
=1e-10):
142 self
.epsilon
= epsilon
144 def columnnames(self
, privatedata
, sharedata
, graph
, columnnames
):
145 sharedata
.poscolumnnames
= []
146 sharedata
.vposmissing
= []
147 for count
, axisnames
in enumerate(graph
.axesnames
):
148 for axisname
in axisnames
:
149 for columnname
in columnnames
:
150 if axisname
== columnname
:
151 sharedata
.poscolumnnames
.append(columnname
)
152 if len(sharedata
.poscolumnnames
) > count
+1:
153 raise ValueError("multiple axes per graph dimension")
154 elif len(sharedata
.poscolumnnames
) < count
+1:
155 sharedata
.vposmissing
.append(count
)
156 sharedata
.poscolumnnames
.append(None)
157 return [columnname
for columnname
in sharedata
.poscolumnnames
if columnname
is not None]
159 def adjustaxis(self
, privatedata
, sharedata
, graph
, columnname
, data
):
160 if columnname
in sharedata
.poscolumnnames
:
161 graph
.axes
[columnname
].adjustaxis(data
)
163 def initdrawpoints(self
, privatedata
, sharedata
, graph
):
164 sharedata
.vpos
= [None]*(len(graph
.axesnames
))
165 privatedata
.pointpostmplist
= [[columnname
, index
, graph
.axes
[columnname
]] # temporarily used by drawpoint only
166 for index
, columnname
in enumerate([columnname
for columnname
in sharedata
.poscolumnnames
if columnname
is not None])]
167 for missing
in sharedata
.vposmissing
:
168 for pointpostmp
in privatedata
.pointpostmplist
:
169 if pointpostmp
[1] >= missing
:
172 def drawpoint(self
, privatedata
, sharedata
, graph
, point
):
173 sharedata
.vposavailable
= 1 # valid position (but might be outside of the graph)
174 sharedata
.vposvalid
= 1 # valid position inside the graph
175 for columnname
, index
, axis
in privatedata
.pointpostmplist
:
177 v
= axis
.convert(point
[columnname
])
178 except (ArithmeticError, ValueError, TypeError):
179 sharedata
.vposavailable
= sharedata
.vposvalid
= 0
180 sharedata
.vpos
[index
] = None
182 if v
< -self
.epsilon
or v
> 1+self
.epsilon
:
183 sharedata
.vposvalid
= 0
184 sharedata
.vpos
[index
] = v
187 registerdefaultprovider(pos(), pos
.providesdata
)
192 providesdata
= ["vrange", "vrangemissing", "vrangeminmissing", "vrangemaxmissing"]
202 def __init__(self
, usenames
={}, epsilon
=1e-10):
203 self
.usenames
= usenames
204 self
.epsilon
= epsilon
206 def _numberofbits(self
, mask
):
210 return self
._numberofbits
(mask
>> 1) + 1
212 return self
._numberofbits
(mask
>> 1)
214 def columnnames(self
, privatedata
, sharedata
, graph
, columnnames
):
216 privatedata
.rangeposcolumns
= []
217 sharedata
.vrangemissing
= []
218 sharedata
.vrangeminmissing
= []
219 sharedata
.vrangemaxmissing
= []
220 privatedata
.rangeposdeltacolumns
= {} # temporarily used by adjustaxis only
221 for count
, axisnames
in enumerate(graph
.axesnames
):
222 for axisname
in axisnames
:
224 usename
= self
.usenames
[axisname
]
228 for columnname
in columnnames
:
230 if usename
== columnname
:
231 mask
+= self
.mask_value
232 elif usename
+ "min" == columnname
:
233 mask
+= self
.mask_min
234 elif usename
+ "max" == columnname
:
235 mask
+= self
.mask_max
236 elif "d" + usename
+ "min" == columnname
:
237 mask
+= self
.mask_dmin
238 elif "d" + usename
+ "max" == columnname
:
239 mask
+= self
.mask_dmax
240 elif "d" + usename
== columnname
:
245 usecolumns
.append(columnname
)
246 if mask
& (self
.mask_min | self
.mask_max | self
.mask_dmin | self
.mask_dmax | self
.mask_d
):
247 if (self
._numberofbits
(mask
& (self
.mask_min | self
.mask_dmin | self
.mask_d
)) > 1 or
248 self
._numberofbits
(mask
& (self
.mask_max | self
.mask_dmax | self
.mask_d
)) > 1):
249 raise ValueError("multiple range definition")
250 if mask
& (self
.mask_dmin | self
.mask_dmax | self
.mask_d
):
251 if not (mask
& self
.mask_value
):
252 raise ValueError("missing value for delta")
253 privatedata
.rangeposdeltacolumns
[axisname
] = {}
254 privatedata
.rangeposcolumns
.append((axisname
, usename
, mask
))
255 elif mask
== self
.mask_value
:
256 usecolumns
= usecolumns
[:-1]
257 if len(privatedata
.rangeposcolumns
) + len(sharedata
.vrangemissing
) > count
+1:
258 raise ValueError("multiple axes per graph dimension")
259 elif len(privatedata
.rangeposcolumns
) + len(sharedata
.vrangemissing
) < count
+1:
260 sharedata
.vrangemissing
.append(count
)
261 sharedata
.vrangeminmissing
.append(count
)
262 sharedata
.vrangemaxmissing
.append(count
)
264 if not (privatedata
.rangeposcolumns
[-1][2] & (self
.mask_min | self
.mask_dmin | self
.mask_d
)):
265 sharedata
.vrangeminmissing
.append(count
)
266 if not (privatedata
.rangeposcolumns
[-1][2] & (self
.mask_max | self
.mask_dmax | self
.mask_d
)):
267 sharedata
.vrangemaxmissing
.append(count
)
270 def adjustaxis(self
, privatedata
, sharedata
, graph
, columnname
, data
):
271 if columnname
in [c
+ "min" for a
, c
, m
in privatedata
.rangeposcolumns
if m
& self
.mask_min
]:
272 graph
.axes
[columnname
[:-3]].adjustaxis(data
)
273 if columnname
in [c
+ "max" for a
, c
, m
in privatedata
.rangeposcolumns
if m
& self
.mask_max
]:
274 graph
.axes
[columnname
[:-3]].adjustaxis(data
)
276 # delta handling: fill rangeposdeltacolumns
277 for axisname
, usename
, mask
in privatedata
.rangeposcolumns
:
278 if columnname
== usename
and mask
& (self
.mask_dmin | self
.mask_dmax | self
.mask_d
):
279 privatedata
.rangeposdeltacolumns
[axisname
][self
.mask_value
] = data
280 if columnname
== "d" + usename
+ "min" and mask
& self
.mask_dmin
:
281 privatedata
.rangeposdeltacolumns
[axisname
][self
.mask_dmin
] = data
282 if columnname
== "d" + usename
+ "max" and mask
& self
.mask_dmax
:
283 privatedata
.rangeposdeltacolumns
[axisname
][self
.mask_dmax
] = data
284 if columnname
== "d" + usename
and mask
& self
.mask_d
:
285 privatedata
.rangeposdeltacolumns
[axisname
][self
.mask_d
] = data
287 # delta handling: process rangeposdeltacolumns
288 for a
, d
in privatedata
.rangeposdeltacolumns
.items():
289 if d
.has_key(self
.mask_value
):
291 if k
!= self
.mask_value
:
292 if k
& (self
.mask_dmin | self
.mask_d
):
295 mindata
.append(d
[self
.mask_value
] - d
[k
])
298 graph
.axes
[a
].adjustaxis(mindata
)
299 if k
& (self
.mask_dmax | self
.mask_d
):
302 maxdata
.append(d
[self
.mask_value
] + d
[k
])
305 graph
.axes
[a
].adjustaxis(maxdata
)
308 def initdrawpoints(self
, privatedata
, sharedata
, graph
):
309 sharedata
.vrange
= [[None for x
in xrange(2)] for y
in privatedata
.rangeposcolumns
+ sharedata
.vrangemissing
]
310 privatedata
.rangepostmplist
= [[usename
, mask
, index
, graph
.axes
[axisname
]] # temporarily used by drawpoint only
311 for index
, (axisname
, usename
, mask
) in enumerate(privatedata
.rangeposcolumns
)]
312 for missing
in sharedata
.vrangemissing
:
313 for rangepostmp
in privatedata
.rangepostmplist
:
314 if rangepostmp
[2] >= missing
:
317 def drawpoint(self
, privatedata
, sharedata
, graph
, point
):
318 for usename
, mask
, index
, axis
in privatedata
.rangepostmplist
:
320 if mask
& self
.mask_min
:
321 sharedata
.vrange
[index
][0] = axis
.convert(point
[usename
+ "min"])
322 if mask
& self
.mask_dmin
:
323 sharedata
.vrange
[index
][0] = axis
.convert(point
[usename
] - point
["d" + usename
+ "min"])
324 if mask
& self
.mask_d
:
325 sharedata
.vrange
[index
][0] = axis
.convert(point
[usename
] - point
["d" + usename
])
326 except (ArithmeticError, ValueError, TypeError):
327 sharedata
.vrange
[index
][0] = None
329 if mask
& self
.mask_max
:
330 sharedata
.vrange
[index
][1] = axis
.convert(point
[usename
+ "max"])
331 if mask
& self
.mask_dmax
:
332 sharedata
.vrange
[index
][1] = axis
.convert(point
[usename
] + point
["d" + usename
+ "max"])
333 if mask
& self
.mask_d
:
334 sharedata
.vrange
[index
][1] = axis
.convert(point
[usename
] + point
["d" + usename
])
335 except (ArithmeticError, ValueError, TypeError):
336 sharedata
.vrange
[index
][1] = None
338 # some range checks for data consistency
339 if (sharedata
.vrange
[index
][0] is not None and sharedata
.vrange
[index
][1] is not None and
340 sharedata
.vrange
[index
][0] > sharedata
.vrange
[index
][1] + self
.epsilon
):
341 raise ValueError("inverse range")
342 # disabled due to missing vpos access:
343 # if (sharedata.vrange[index][0] is not None and sharedata.vpos[index] is not None and
344 # sharedata.vrange[index][0] > sharedata.vpos[index] + self.epsilon):
345 # raise ValueError("negative minimum errorbar")
346 # if (sharedata.vrange[index][1] is not None and sharedata.vpos[index] is not None and
347 # sharedata.vrange[index][1] < sharedata.vpos[index] - self.epsilon):
348 # raise ValueError("negative maximum errorbar")
351 registerdefaultprovider(range(), range.providesdata
)
354 def _crosssymbol(c
, x_pt
, y_pt
, size_pt
, attrs
):
355 c
.draw(path
.path(path
.moveto_pt(x_pt
-0.5*size_pt
, y_pt
-0.5*size_pt
),
356 path
.lineto_pt(x_pt
+0.5*size_pt
, y_pt
+0.5*size_pt
),
357 path
.moveto_pt(x_pt
-0.5*size_pt
, y_pt
+0.5*size_pt
),
358 path
.lineto_pt(x_pt
+0.5*size_pt
, y_pt
-0.5*size_pt
)), attrs
)
360 def _plussymbol(c
, x_pt
, y_pt
, size_pt
, attrs
):
361 c
.draw(path
.path(path
.moveto_pt(x_pt
-0.707106781*size_pt
, y_pt
),
362 path
.lineto_pt(x_pt
+0.707106781*size_pt
, y_pt
),
363 path
.moveto_pt(x_pt
, y_pt
-0.707106781*size_pt
),
364 path
.lineto_pt(x_pt
, y_pt
+0.707106781*size_pt
)), attrs
)
366 def _squaresymbol(c
, x_pt
, y_pt
, size_pt
, attrs
):
367 c
.draw(path
.path(path
.moveto_pt(x_pt
-0.5*size_pt
, y_pt
-0.5*size_pt
),
368 path
.lineto_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
.closepath()), attrs
)
373 def _trianglesymbol(c
, x_pt
, y_pt
, size_pt
, attrs
):
374 c
.draw(path
.path(path
.moveto_pt(x_pt
-0.759835685*size_pt
, y_pt
-0.438691337*size_pt
),
375 path
.lineto_pt(x_pt
+0.759835685*size_pt
, y_pt
-0.438691337*size_pt
),
376 path
.lineto_pt(x_pt
, y_pt
+0.877382675*size_pt
),
377 path
.closepath()), attrs
)
379 def _circlesymbol(c
, x_pt
, y_pt
, size_pt
, attrs
):
380 c
.draw(path
.path(path
.arc_pt(x_pt
, y_pt
, 0.564189583*size_pt
, 0, 360),
381 path
.closepath()), attrs
)
383 def _diamondsymbol(c
, x_pt
, y_pt
, size_pt
, attrs
):
384 c
.draw(path
.path(path
.moveto_pt(x_pt
-0.537284965*size_pt
, y_pt
),
385 path
.lineto_pt(x_pt
, y_pt
-0.930604859*size_pt
),
386 path
.lineto_pt(x_pt
+0.537284965*size_pt
, y_pt
),
387 path
.lineto_pt(x_pt
, y_pt
+0.930604859*size_pt
),
388 path
.closepath()), attrs
)
391 class _styleneedingpointpos(_style
):
393 needsdata
= ["vposmissing"]
395 def columnnames(self
, privatedata
, sharedata
, graph
, columnnames
):
396 if len(sharedata
.vposmissing
):
397 raise ValueError("incomplete position information")
401 class symbol(_styleneedingpointpos
):
403 needsdata
= ["vpos", "vposmissing", "vposvalid"]
405 # "inject" the predefinied symbols into the class:
407 # Note, that statements like cross = _crosssymbol are
408 # invalid, since the would lead to unbound methods, but
409 # a single entry changeable list does the trick.
411 # Once we require Python 2.2+ we should use staticmethods
412 # to implement the default symbols inplace.
414 cross
= attr
.changelist([_crosssymbol
])
415 plus
= attr
.changelist([_plussymbol
])
416 square
= attr
.changelist([_squaresymbol
])
417 triangle
= attr
.changelist([_trianglesymbol
])
418 circle
= attr
.changelist([_circlesymbol
])
419 diamond
= attr
.changelist([_diamondsymbol
])
421 changecross
= attr
.changelist([_crosssymbol
, _plussymbol
, _squaresymbol
, _trianglesymbol
, _circlesymbol
, _diamondsymbol
])
422 changeplus
= attr
.changelist([_plussymbol
, _squaresymbol
, _trianglesymbol
, _circlesymbol
, _diamondsymbol
, _crosssymbol
])
423 changesquare
= attr
.changelist([_squaresymbol
, _trianglesymbol
, _circlesymbol
, _diamondsymbol
, _crosssymbol
, _plussymbol
])
424 changetriangle
= attr
.changelist([_trianglesymbol
, _circlesymbol
, _diamondsymbol
, _crosssymbol
, _plussymbol
, _squaresymbol
])
425 changecircle
= attr
.changelist([_circlesymbol
, _diamondsymbol
, _crosssymbol
, _plussymbol
, _squaresymbol
, _trianglesymbol
])
426 changediamond
= attr
.changelist([_diamondsymbol
, _crosssymbol
, _plussymbol
, _squaresymbol
, _trianglesymbol
, _circlesymbol
])
427 changesquaretwice
= attr
.changelist([_squaresymbol
, _squaresymbol
, _trianglesymbol
, _trianglesymbol
, _circlesymbol
, _circlesymbol
, _diamondsymbol
, _diamondsymbol
])
428 changetriangletwice
= attr
.changelist([_trianglesymbol
, _trianglesymbol
, _circlesymbol
, _circlesymbol
, _diamondsymbol
, _diamondsymbol
, _squaresymbol
, _squaresymbol
])
429 changecircletwice
= attr
.changelist([_circlesymbol
, _circlesymbol
, _diamondsymbol
, _diamondsymbol
, _squaresymbol
, _squaresymbol
, _trianglesymbol
, _trianglesymbol
])
430 changediamondtwice
= attr
.changelist([_diamondsymbol
, _diamondsymbol
, _squaresymbol
, _squaresymbol
, _trianglesymbol
, _trianglesymbol
, _circlesymbol
, _circlesymbol
])
432 changestrokedfilled
= attr
.changelist([deco
.stroked
, deco
.filled
])
433 changefilledstroked
= attr
.changelist([deco
.filled
, deco
.stroked
])
435 defaultsymbolattrs
= [deco
.stroked
]
437 def __init__(self
, symbol
=changecross
, size
=0.2*unit
.v_cm
, symbolattrs
=[]):
440 self
.symbolattrs
= symbolattrs
442 def selectstyle(self
, privatedata
, sharedata
, graph
, selectindex
, selecttotal
):
443 privatedata
.symbol
= attr
.selectattr(self
.symbol
, selectindex
, selecttotal
)
444 privatedata
.size_pt
= unit
.topt(attr
.selectattr(self
.size
, selectindex
, selecttotal
))
445 if self
.symbolattrs
is not None:
446 privatedata
.symbolattrs
= attr
.selectattrs(self
.defaultsymbolattrs
+ self
.symbolattrs
, selectindex
, selecttotal
)
448 privatedata
.symbolattrs
= None
450 def initdrawpoints(self
, privatedata
, sharedata
, graph
):
451 privatedata
.symbolcanvas
= canvas
.canvas()
453 def drawpoint(self
, privatedata
, sharedata
, graph
, point
):
454 if sharedata
.vposvalid
and privatedata
.symbolattrs
is not None:
455 x_pt
, y_pt
= graph
.vpos_pt(*sharedata
.vpos
)
456 privatedata
.symbol(privatedata
.symbolcanvas
, x_pt
, y_pt
, privatedata
.size_pt
, privatedata
.symbolattrs
)
458 def donedrawpoints(self
, privatedata
, sharedata
, graph
):
459 graph
.insert(privatedata
.symbolcanvas
)
461 def key_pt(self
, privatedata
, sharedata
, graph
, x_pt
, y_pt
, width_pt
, height_pt
):
462 if privatedata
.symbolattrs
is not None:
463 privatedata
.symbol(graph
, x_pt
+0.5*width_pt
, y_pt
+0.5*height_pt
, privatedata
.size_pt
, privatedata
.symbolattrs
)
466 class line(_styleneedingpointpos
):
468 needsdata
= ["vpos", "vposmissing", "vposavailable", "vposvalid"]
470 changelinestyle
= attr
.changelist([style
.linestyle
.solid
,
471 style
.linestyle
.dashed
,
472 style
.linestyle
.dotted
,
473 style
.linestyle
.dashdotted
])
475 defaultlineattrs
= [changelinestyle
]
477 def __init__(self
, lineattrs
=[]):
478 self
.lineattrs
= lineattrs
480 def selectstyle(self
, privatedata
, sharedata
, graph
, selectindex
, selecttotal
):
481 if self
.lineattrs
is not None:
482 privatedata
.lineattrs
= attr
.selectattrs(self
.defaultlineattrs
+ self
.lineattrs
, selectindex
, selecttotal
)
484 privatedata
.lineattrs
= None
486 def initdrawpoints(self
, privatedata
, sharedata
, graph
):
487 privatedata
.path
= path
.path()
488 privatedata
.linebasepoints
= []
489 privatedata
.lastvpos
= None
491 def addpointstopath(self
, privatedata
, sharedata
):
492 # add baselinepoints to privatedata.path
493 if len(privatedata
.linebasepoints
) > 1:
494 privatedata
.path
.append(path
.moveto_pt(*privatedata
.linebasepoints
[0]))
495 if len(privatedata
.linebasepoints
) > 2:
496 privatedata
.path
.append(path
.multilineto_pt(privatedata
.linebasepoints
[1:]))
498 privatedata
.path
.append(path
.lineto_pt(*privatedata
.linebasepoints
[1]))
499 privatedata
.linebasepoints
= []
501 def drawpoint(self
, privatedata
, sharedata
, graph
, point
):
502 # append linebasepoints
503 if sharedata
.vposavailable
:
504 if len(privatedata
.linebasepoints
):
505 # the last point was inside the graph
506 if sharedata
.vposvalid
: # shortcut for the common case
507 privatedata
.linebasepoints
.append(graph
.vpos_pt(*sharedata
.vpos
))
511 for vstart
, vend
in zip(privatedata
.lastvpos
, sharedata
.vpos
):
514 # 1 = vstart + (vend - vstart) * cut
516 newcut
= (1 - vstart
)/(vend
- vstart
)
517 except (ArithmeticError, TypeError):
520 # 0 = vstart + (vend - vstart) * cut
522 newcut
= - vstart
/(vend
- vstart
)
523 except (ArithmeticError, TypeError):
525 if newcut
is not None and newcut
< cut
:
529 for vstart
, vend
in zip(privatedata
.lastvpos
, sharedata
.vpos
):
530 cutvpos
.append(vstart
+ (vend
- vstart
) * cut
)
531 privatedata
.linebasepoints
.append(graph
.vpos_pt(*cutvpos
))
532 self
.addpointstopath(privatedata
, sharedata
)
534 # the last point was outside the graph
535 if privatedata
.lastvpos
is not None:
536 if sharedata
.vposvalid
:
539 for vstart
, vend
in zip(privatedata
.lastvpos
, sharedata
.vpos
):
542 # 1 = vstart + (vend - vstart) * cut
544 newcut
= (1 - vstart
)/(vend
- vstart
)
545 except (ArithmeticError, TypeError):
548 # 0 = vstart + (vend - vstart) * cut
550 newcut
= - vstart
/(vend
- vstart
)
551 except (ArithmeticError, TypeError):
553 if newcut
is not None and newcut
> cut
:
557 for vstart
, vend
in zip(privatedata
.lastvpos
, sharedata
.vpos
):
558 cutvpos
.append(vstart
+ (vend
- vstart
) * cut
)
559 privatedata
.linebasepoints
.append(graph
.vpos_pt(*cutvpos
))
560 privatedata
.linebasepoints
.append(graph
.vpos_pt(*sharedata
.vpos
))
562 # sometimes cut beginning and end
565 for vstart
, vend
in zip(privatedata
.lastvpos
, sharedata
.vpos
):
570 # 1 = vstart + (vend - vstart) * cutfrom
572 newcutfrom
= (1 - vstart
)/(vend
- vstart
)
573 except (ArithmeticError, TypeError):
578 # 0 = vstart + (vend - vstart) * cutfrom
580 newcutfrom
= - vstart
/(vend
- vstart
)
581 except (ArithmeticError, TypeError):
583 if newcutfrom
is not None and newcutfrom
> cutfrom
:
587 # 1 = vstart + (vend - vstart) * cutto
589 newcutto
= (1 - vstart
)/(vend
- vstart
)
590 except (ArithmeticError, TypeError):
593 # 0 = vstart + (vend - vstart) * cutto
595 newcutto
= - vstart
/(vend
- vstart
)
596 except (ArithmeticError, TypeError):
598 if newcutto
is not None and newcutto
< cutto
:
604 for vstart
, vend
in zip(privatedata
.lastvpos
, sharedata
.vpos
):
605 cutfromvpos
.append(vstart
+ (vend
- vstart
) * cutfrom
)
606 cuttovpos
.append(vstart
+ (vend
- vstart
) * cutto
)
607 privatedata
.linebasepoints
.append(graph
.vpos_pt(*cutfromvpos
))
608 privatedata
.linebasepoints
.append(graph
.vpos_pt(*cuttovpos
))
609 self
.addpointstopath(privatedata
, sharedata
)
610 privatedata
.lastvpos
= sharedata
.vpos
[:]
612 if len(privatedata
.linebasepoints
) > 1:
613 self
.addpointstopath(privatedata
, sharedata
)
614 privatedata
.lastvpos
= None
616 def donedrawpoints(self
, privatedata
, sharedata
, graph
):
617 if len(privatedata
.linebasepoints
) > 1:
618 self
.addpointstopath(privatedata
, sharedata
)
619 if privatedata
.lineattrs
is not None and len(privatedata
.path
):
620 graph
.stroke(privatedata
.path
, privatedata
.lineattrs
)
622 def key_pt(self
, privatedata
, sharedata
, graph
, x_pt
, y_pt
, width_pt
, height_pt
):
623 if privatedata
.lineattrs
is not None:
624 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
)
627 class errorbar(_style
):
629 needsdata
= ["vpos", "vposmissing", "vposavailable", "vposvalid", "vrange", "vrangeminmissing", "vrangemaxmissing"]
631 defaulterrorbarattrs
= []
633 def __init__(self
, size
=0.1*unit
.v_cm
,
637 self
.errorbarattrs
= errorbarattrs
638 self
.epsilon
= epsilon
640 def columnnames(self
, privatedata
, sharedata
, graph
, columnnames
):
641 for i
in sharedata
.vposmissing
:
642 if i
in sharedata
.vrangeminmissing
and i
in sharedata
.vrangemaxmissing
:
643 raise ValueError("position and range for a graph dimension missing")
646 def selectstyle(self
, privatedata
, sharedata
, graph
, selectindex
, selecttotal
):
647 privatedata
.errorsize_pt
= unit
.topt(attr
.selectattr(self
.size
, selectindex
, selecttotal
))
648 privatedata
.errorbarattrs
= attr
.selectattrs(self
.defaulterrorbarattrs
+ self
.errorbarattrs
, selectindex
, selecttotal
)
650 def initdrawpoints(self
, privatedata
, sharedata
, graph
):
651 if privatedata
.errorbarattrs
is not None:
652 privatedata
.errorbarcanvas
= canvas
.canvas(privatedata
.errorbarattrs
)
653 privatedata
.dimensionlist
= list(xrange(len(sharedata
.vpos
)))
655 def drawpoint(self
, privatedata
, sharedata
, graph
, point
):
656 if privatedata
.errorbarattrs
is not None:
657 for i
in privatedata
.dimensionlist
:
658 for j
in privatedata
.dimensionlist
:
660 (sharedata
.vpos
[j
] is None or
661 sharedata
.vpos
[j
] < -self
.epsilon
or
662 sharedata
.vpos
[j
] > 1+self
.epsilon
)):
665 if ((sharedata
.vrange
[i
][0] is None and sharedata
.vpos
[i
] is None) or
666 (sharedata
.vrange
[i
][1] is None and sharedata
.vpos
[i
] is None) or
667 (sharedata
.vrange
[i
][0] is None and sharedata
.vrange
[i
][1] is None)):
669 vminpos
= sharedata
.vpos
[:]
670 if sharedata
.vrange
[i
][0] is not None:
671 vminpos
[i
] = sharedata
.vrange
[i
][0]
675 if vminpos
[i
] > 1+self
.epsilon
:
677 if vminpos
[i
] < -self
.epsilon
:
680 vmaxpos
= sharedata
.vpos
[:]
681 if sharedata
.vrange
[i
][1] is not None:
682 vmaxpos
[i
] = sharedata
.vrange
[i
][1]
686 if vmaxpos
[i
] < -self
.epsilon
:
688 if vmaxpos
[i
] > 1+self
.epsilon
:
691 privatedata
.errorbarcanvas
.stroke(graph
.vgeodesic(*(vminpos
+ vmaxpos
)))
692 for j
in privatedata
.dimensionlist
:
695 privatedata
.errorbarcanvas
.stroke(graph
.vcap_pt(j
, privatedata
.errorsize_pt
, *vminpos
))
697 privatedata
.errorbarcanvas
.stroke(graph
.vcap_pt(j
, privatedata
.errorsize_pt
, *vmaxpos
))
699 def donedrawpoints(self
, privatedata
, sharedata
, graph
):
700 if privatedata
.errorbarattrs
is not None:
701 graph
.insert(privatedata
.errorbarcanvas
)
704 class text(_styleneedingpointpos
):
706 needsdata
= ["vpos", "vposmissing", "vposvalid"]
708 defaulttextattrs
= [textmodule
.halign
.center
, textmodule
.vshift
.mathaxis
]
710 def __init__(self
, textname
="text", textdx
=0*unit
.v_cm
, textdy
=0.3*unit
.v_cm
, textattrs
=[]):
711 self
.textname
= textname
714 self
.textattrs
= textattrs
716 def columnnames(self
, privatedata
, sharedata
, graph
, columnnames
):
717 if self
.textname
not in columnnames
:
718 raise ValueError("column '%s' missing" % self
.textname
)
719 return [self
.textname
] + _styleneedingpointpos
.columnnames(self
, privatedata
, sharedata
, graph
, columnnames
)
721 def selectstyle(self
, privatedata
, sharedata
, graph
, selectindex
, selecttotal
):
722 if self
.textattrs
is not None:
723 privatedata
.textattrs
= attr
.selectattrs(self
.defaulttextattrs
+ self
.textattrs
, selectindex
, selecttotal
)
725 privatedata
.textattrs
= None
727 def initdrawpoints(self
, privatedata
, sharedata
, grap
):
728 privatedata
.textdx_pt
= unit
.topt(self
.textdx
)
729 privatedata
.textdy_pt
= unit
.topt(self
.textdy
)
731 def drawpoint(self
, privatedata
, sharedata
, graph
, point
):
732 if privatedata
.textattrs
is not None and sharedata
.vposvalid
:
733 x_pt
, y_pt
= graph
.vpos_pt(*sharedata
.vpos
)
735 text
= str(point
[self
.textname
])
739 graph
.text_pt(x_pt
+ privatedata
.textdx_pt
, y_pt
+ privatedata
.textdy_pt
, text
, privatedata
.textattrs
)
741 def key_pt(self
, privatedata
, sharedata
, graph
, x_pt
, y_pt
, width_pt
, height_pt
):
742 raise RuntimeError("Style currently doesn't provide a graph key")
745 class arrow(_styleneedingpointpos
):
747 needsdata
= ["vpos", "vposmissing", "vposvalid"]
749 defaultlineattrs
= []
750 defaultarrowattrs
= []
752 def __init__(self
, linelength
=0.25*unit
.v_cm
, arrowsize
=0.15*unit
.v_cm
, lineattrs
=[], arrowattrs
=[], epsilon
=1e-5):
753 self
.linelength
= linelength
754 self
.arrowsize
= arrowsize
755 self
.lineattrs
= lineattrs
756 self
.arrowattrs
= arrowattrs
757 self
.epsilon
= epsilon
759 def columnnames(self
, privatedata
, sharedata
, graph
, columnnames
):
760 if len(graph
.axesnames
) != 2:
761 raise ValueError("arrow style restricted on two-dimensional graphs")
762 if "size" not in columnnames
:
763 raise ValueError("size missing")
764 if "angle" not in columnnames
:
765 raise ValueError("angle missing")
766 return ["size", "angle"] + _styleneedingpointpos
.columnnames(self
, privatedata
, sharedata
, graph
, columnnames
)
768 def selectstyle(self
, privatedata
, sharedata
, graph
, selectindex
, selecttotal
):
769 if self
.lineattrs
is not None:
770 privatedata
.lineattrs
= attr
.selectattrs(self
.defaultlineattrs
+ self
.lineattrs
, selectindex
, selecttotal
)
772 privatedata
.lineattrs
= None
773 if self
.arrowattrs
is not None:
774 privatedata
.arrowattrs
= attr
.selectattrs(self
.defaultarrowattrs
+ self
.arrowattrs
, selectindex
, selecttotal
)
776 privatedata
.arrowattrs
= None
778 def initdrawpoints(self
, privatedata
, sharedata
, graph
):
779 privatedata
.arrowcanvas
= canvas
.canvas()
781 def drawpoint(self
, privatedata
, sharedata
, graph
, point
):
782 if privatedata
.lineattrs
is not None and privatedata
.arrowattrs
is not None and sharedata
.vposvalid
:
783 linelength_pt
= unit
.topt(self
.linelength
)
784 x_pt
, y_pt
= graph
.vpos_pt(*sharedata
.vpos
)
786 angle
= point
["angle"] + 0.0
787 size
= point
["size"] + 0.0
791 if point
["size"] > self
.epsilon
:
792 dx
= math
.cos(angle
*math
.pi
/180)
793 dy
= math
.sin(angle
*math
.pi
/180)
794 x1
= x_pt
-0.5*dx
*linelength_pt
*size
795 y1
= y_pt
-0.5*dy
*linelength_pt
*size
796 x2
= x_pt
+0.5*dx
*linelength_pt
*size
797 y2
= y_pt
+0.5*dy
*linelength_pt
*size
798 privatedata
.arrowcanvas
.stroke(path
.line_pt(x1
, y1
, x2
, y2
), privatedata
.lineattrs
+
799 [deco
.earrow(privatedata
.arrowattrs
, size
=self
.arrowsize
*size
)])
801 def donedrawpoints(self
, privatedata
, sharedata
, graph
):
802 graph
.insert(privatedata
.arrowcanvas
)
804 def key_pt(self
, privatedata
, sharedata
, graph
, x_pt
, y_pt
, width_pt
, height_pt
):
805 raise RuntimeError("Style currently doesn't provide a graph key")
810 needsdata
= ["vrange", "vrangeminmissing", "vrangemaxmissing"]
812 def __init__(self
, palette
=color
.palette
.Grey
):
813 self
.palette
= palette
815 def columnnames(self
, privatedata
, sharedata
, graph
, columnnames
):
816 if len(graph
.axesnames
) != 2:
817 raise TypeError("arrow style restricted on two-dimensional graphs")
818 if "color" not in columnnames
:
819 raise ValueError("color missing")
820 if len(sharedata
.vrangeminmissing
) + len(sharedata
.vrangemaxmissing
):
821 raise ValueError("incomplete range")
824 def initdrawpoints(self
, privatedata
, sharedata
, graph
):
825 privatedata
.rectcanvas
= graph
.insert(canvas
.canvas())
827 def drawpoint(self
, privatedata
, sharedata
, graph
, point
):
828 xvmin
= sharedata
.vrange
[0][0]
829 xvmax
= sharedata
.vrange
[0][1]
830 yvmin
= sharedata
.vrange
[1][0]
831 yvmax
= sharedata
.vrange
[1][1]
832 if (xvmin
is not None and xvmin
< 1 and
833 xvmax
is not None and xvmax
> 0 and
834 yvmin
is not None and yvmin
< 1 and
835 yvmax
is not None and yvmax
> 0):
844 p
= graph
.vgeodesic(xvmin
, yvmin
, xvmax
, yvmin
)
845 p
.append(graph
.vgeodesic_el(xvmax
, yvmin
, xvmax
, yvmax
))
846 p
.append(graph
.vgeodesic_el(xvmax
, yvmax
, xvmin
, yvmax
))
847 p
.append(graph
.vgeodesic_el(xvmin
, yvmax
, xvmin
, yvmin
))
848 p
.append(path
.closepath())
849 privatedata
.rectcanvas
.fill(p
, [self
.palette
.getcolor(point
["color"])])
851 def key_pt(self
, privatedata
, sharedata
, graph
, x_pt
, y_pt
, width_pt
, height_pt
):
852 raise RuntimeError("Style currently doesn't provide a graph key")
855 class histogram(_style
):
857 needsdata
= ["vpos", "vposmissing", "vrange", "vrangeminmissing", "vrangemaxmissing"]
859 defaultlineattrs
= [deco
.stroked
]
860 defaultfrompathattrs
= []
862 def __init__(self
, lineattrs
=[], steps
=0, fromvalue
=0, frompathattrs
=[], fillable
=0,
863 autohistogramaxisindex
=0, autohistogrampointpos
=0.5, epsilon
=1e-10):
864 self
.lineattrs
= lineattrs
866 self
.fromvalue
= fromvalue
867 self
.frompathattrs
= frompathattrs
868 self
.fillable
= fillable
# TODO: fillable paths might not properly be closed by straight lines on curved graph geometries
869 self
.autohistogramaxisindex
= autohistogramaxisindex
870 self
.autohistogrampointpos
= autohistogrampointpos
871 self
.epsilon
= epsilon
873 def columnnames(self
, privatedata
, sharedata
, graph
, columnnames
):
874 if len(graph
.axesnames
) != 2:
875 raise TypeError("histogram style restricted on two-dimensional graphs")
876 privatedata
.rangeaxisindex
= None
877 for i
in builtinrange(len(graph
.axesnames
)):
878 if i
in sharedata
.vrangeminmissing
or i
in sharedata
.vrangemaxmissing
:
879 if i
in sharedata
.vposmissing
:
880 raise ValueError("pos and range missing")
882 if privatedata
.rangeaxisindex
is not None:
883 raise ValueError("multiple ranges")
884 privatedata
.rangeaxisindex
= i
885 if privatedata
.rangeaxisindex
is None:
886 privatedata
.rangeaxisindex
= self
.autohistogramaxisindex
887 privatedata
.autohistogram
= 1
889 privatedata
.autohistogram
= 0
892 def adjustaxis(self
, privatedata
, sharedata
, graph
, columnname
, data
):
893 if privatedata
.autohistogram
and columnname
== sharedata
.poscolumnnames
[privatedata
.rangeaxisindex
]:
895 raise ValueError("several data points needed for automatic histogram width calculation")
897 delta
= data
[1] - data
[0]
898 min = data
[0] - self
.autohistogrampointpos
* delta
899 max = data
[-1] + (1-self
.autohistogrampointpos
) * delta
900 graph
.axes
[columnname
].adjustaxis([min, max])
902 def selectstyle(self
, privatedata
, sharedata
, graph
, selectindex
, selecttotal
):
903 privatedata
.insertfrompath
= selectindex
== 0
904 if self
.lineattrs
is not None:
905 privatedata
.lineattrs
= attr
.selectattrs(self
.defaultlineattrs
+ self
.lineattrs
, selectindex
, selecttotal
)
907 privatedata
.lineattrs
= None
909 def vmoveto(self
, privatedata
, sharedata
, graph
, vpos
, vvalue
):
910 if -self
.epsilon
< vpos
< 1+self
.epsilon
and -self
.epsilon
< vvalue
< 1+self
.epsilon
:
911 if privatedata
.rangeaxisindex
:
912 privatedata
.path
.append(path
.moveto_pt(*graph
.vpos_pt(vvalue
, vpos
)))
914 privatedata
.path
.append(path
.moveto_pt(*graph
.vpos_pt(vpos
, vvalue
)))
916 def vposline(self
, privatedata
, sharedata
, graph
, vpos
, vvalue1
, vvalue2
):
917 if -self
.epsilon
< vpos
< 1+self
.epsilon
:
932 if abs(vvalue1cut
+ vvalue2cut
) <= 1:
933 if vvalue1cut
and not self
.fillable
:
934 if privatedata
.rangeaxisindex
:
935 privatedata
.path
.append(path
.moveto_pt(*graph
.vpos_pt(vvalue1
, vpos
)))
937 privatedata
.path
.append(path
.moveto_pt(*graph
.vpos_pt(vpos
, vvalue1
)))
938 if privatedata
.rangeaxisindex
:
939 privatedata
.path
.append(graph
.vgeodesic_el(vvalue1
, vpos
, vvalue2
, vpos
))
941 privatedata
.path
.append(graph
.vgeodesic_el(vpos
, vvalue1
, vpos
, vvalue2
))
943 def vvalueline(self
, privatedata
, sharedata
, graph
, vvalue
, vpos1
, vpos2
):
945 if vvalue
< -self
.epsilon
:
947 warnings
.warn("cut at graph boundary adds artificial lines to fillable step histogram path")
948 if vvalue
> 1+self
.epsilon
:
950 warnings
.warn("cut at graph boundary adds artificial lines to fillable step histogram path")
951 if self
.fillable
or (-self
.epsilon
< vvalue
< 1+self
.epsilon
):
966 if abs(vpos1cut
+ vpos2cut
) <= 1:
969 if privatedata
.rangeaxisindex
:
970 privatedata
.path
.append(path
.moveto_pt(*graph
.vpos_pt(privatedata
.vfromvalue
, vpos1
)))
971 privatedata
.path
.append(graph
.vgeodesic_el(privatedata
.vfromvalue
, vpos1
, vvalue
, vpos1
))
973 privatedata
.path
.append(path
.moveto_pt(*graph
.vpos_pt(vpos1
, privatedata
.vfromvalue
)))
974 privatedata
.path
.append(graph
.vgeodesic_el(vpos1
, privatedata
.vfromvalue
, vpos1
, vvalue
))
976 if privatedata
.rangeaxisindex
:
977 privatedata
.path
.append(path
.moveto_pt(*graph
.vpos_pt(vvalue
, vpos1
)))
979 privatedata
.path
.append(path
.moveto_pt(*graph
.vpos_pt(vpos1
, vvalue
)))
980 if privatedata
.rangeaxisindex
:
981 privatedata
.path
.append(graph
.vgeodesic_el(vvalue
, vpos1
, vvalue
, vpos2
))
983 privatedata
.path
.append(graph
.vgeodesic_el(vpos1
, vvalue
, vpos2
, vvalue
))
984 if self
.fillable
and vpos2cut
:
985 warnings
.warn("cut at graph boundary adds artificial lines to fillable step histogram path")
986 if privatedata
.rangeaxisindex
:
987 privatedata
.path
.append(graph
.vgeodesic_el(vvalue
, vpos2
, privatedata
.vfromvalue
, vpos2
))
989 privatedata
.path
.append(graph
.vgeodesic_el(vpos2
, vvalue
, vpos2
, privatedata
.vfromvalue
))
991 def drawvalue(self
, privatedata
, sharedata
, graph
, vmin
, vmax
, vvalue
):
992 currentvalid
= vmin
is not None and vmax
is not None and vvalue
is not None
993 if self
.fillable
and not self
.steps
:
997 if vmin
< -self
.epsilon
:
1000 elif vmin
> 1+self
.epsilon
:
1004 if vmax
< -self
.epsilon
:
1007 if vmax
> 1+self
.epsilon
:
1011 if vvalue
< -self
.epsilon
:
1014 if vvalue
> 1+self
.epsilon
:
1018 if abs(vmincut
) + abs(vmaxcut
) + abs(vvaluecut
) + abs(privatedata
.vfromvaluecut
) > 1:
1019 if abs(vmincut
+ vmaxcut
) > 1 or abs(vvaluecut
+privatedata
.vfromvaluecut
) > 1:
1022 warnings
.warn("multiple cuts at graph boundary add artificial lines to fillable rectangle histogram path")
1025 if privatedata
.rangeaxisindex
:
1026 privatedata
.path
.append(path
.moveto_pt(*graph
.vpos_pt(privatedata
.vfromvalue
, vmin
)))
1027 privatedata
.path
.append(graph
.vgeodesic_el(privatedata
.vfromvalue
, vmin
, privatedata
.vfromvalue
, vmax
))
1028 privatedata
.path
.append(graph
.vgeodesic_el(privatedata
.vfromvalue
, vmax
, vvalue
, vmax
))
1029 privatedata
.path
.append(graph
.vgeodesic_el(vvalue
, vmax
, vvalue
, vmin
))
1031 privatedata
.path
.append(path
.moveto_pt(*graph
.vpos_pt(vmin
, privatedata
.vfromvalue
)))
1032 privatedata
.path
.append(graph
.vgeodesic_el(vmin
, privatedata
.vfromvalue
, vmax
, privatedata
.vfromvalue
))
1033 privatedata
.path
.append(graph
.vgeodesic_el(vmax
, privatedata
.vfromvalue
, vmax
, vvalue
))
1034 privatedata
.path
.append(graph
.vgeodesic_el(vmax
, vvalue
, vmin
, vvalue
))
1037 if privatedata
.rangeaxisindex
:
1038 privatedata
.path
.append(path
.moveto_pt(*graph
.vpos_pt(vvalue
, vmax
)))
1039 privatedata
.path
.append(graph
.vgeodesic_el(vvalue
, vmax
, vvalue
, vmin
))
1040 privatedata
.path
.append(graph
.vgeodesic_el(vvalue
, vmin
, privatedata
.vfromvalue
, vmin
))
1041 privatedata
.path
.append(graph
.vgeodesic_el(privatedata
.vfromvalue
, vmin
, privatedata
.vfromvalue
, vmax
))
1043 privatedata
.path
.append(path
.moveto_pt(*graph
.vpos_pt(vmax
, vvalue
)))
1044 privatedata
.path
.append(graph
.vgeodesic_el(vmax
, vvalue
, vmin
, vvalue
))
1045 privatedata
.path
.append(graph
.vgeodesic_el(vmin
, vvalue
, vmin
, privatedata
.vfromvalue
))
1046 privatedata
.path
.append(graph
.vgeodesic_el(vmin
, privatedata
.vfromvalue
, vmax
, privatedata
.vfromvalue
))
1047 elif privatedata
.vfromvaluecut
:
1049 if privatedata
.rangeaxisindex
:
1050 privatedata
.path
.append(path
.moveto_pt(*graph
.vpos_pt(privatedata
.vfromvalue
, vmax
)))
1051 privatedata
.path
.append(graph
.vgeodesic_el(privatedata
.vfromvalue
, vmax
, vvalue
, vmax
))
1052 privatedata
.path
.append(graph
.vgeodesic_el(vvalue
, vmax
, vvalue
, vmin
))
1053 privatedata
.path
.append(graph
.vgeodesic_el(vvalue
, vmin
, privatedata
.vfromvalue
, vmin
))
1055 privatedata
.path
.append(path
.moveto_pt(*graph
.vpos_pt(vmax
, privatedata
.vfromvalue
)))
1056 privatedata
.path
.append(graph
.vgeodesic_el(vmax
, privatedata
.vfromvalue
, vmax
, vvalue
))
1057 privatedata
.path
.append(graph
.vgeodesic_el(vmax
, vvalue
, vmin
, vvalue
))
1058 privatedata
.path
.append(graph
.vgeodesic_el(vmin
, vvalue
, vmin
, privatedata
.vfromvalue
))
1061 if privatedata
.rangeaxisindex
:
1062 privatedata
.path
.append(path
.moveto_pt(*graph
.vpos_pt(vvalue
, vmin
)))
1063 privatedata
.path
.append(graph
.vgeodesic_el(vvalue
, vmin
, privatedata
.vfromvalue
, vmin
))
1064 privatedata
.path
.append(graph
.vgeodesic_el(privatedata
.vfromvalue
, vmin
, privatedata
.vfromvalue
, vmax
))
1065 privatedata
.path
.append(graph
.vgeodesic_el(privatedata
.vfromvalue
, vmax
, vvalue
, vmax
))
1067 privatedata
.path
.append(path
.moveto_pt(*graph
.vpos_pt(vmin
, vvalue
)))
1068 privatedata
.path
.append(graph
.vgeodesic_el(vmin
, vvalue
, vmin
, privatedata
.vfromvalue
))
1069 privatedata
.path
.append(graph
.vgeodesic_el(vmin
, privatedata
.vfromvalue
, vmax
, privatedata
.vfromvalue
))
1070 privatedata
.path
.append(graph
.vgeodesic_el(vmax
, privatedata
.vfromvalue
, vmax
, vvalue
))
1072 if privatedata
.rangeaxisindex
:
1073 privatedata
.path
.append(path
.moveto_pt(*graph
.vpos_pt(privatedata
.vfromvalue
, vmin
)))
1074 privatedata
.path
.append(graph
.vgeodesic_el(privatedata
.vfromvalue
, vmin
, privatedata
.vfromvalue
, vmax
))
1075 privatedata
.path
.append(graph
.vgeodesic_el(privatedata
.vfromvalue
, vmax
, vvalue
, vmax
))
1076 privatedata
.path
.append(graph
.vgeodesic_el(vvalue
, vmax
, vvalue
, vmin
))
1077 privatedata
.path
.append(graph
.vgeodesic_el(vvalue
, vmin
, privatedata
.vfromvalue
, vmin
))
1078 privatedata
.path
.append(path
.closepath())
1080 privatedata
.path
.append(path
.moveto_pt(*graph
.vpos_pt(vmin
, privatedata
.vfromvalue
)))
1081 privatedata
.path
.append(graph
.vgeodesic_el(vmin
, privatedata
.vfromvalue
, vmax
, privatedata
.vfromvalue
))
1082 privatedata
.path
.append(graph
.vgeodesic_el(vmax
, privatedata
.vfromvalue
, vmax
, vvalue
))
1083 privatedata
.path
.append(graph
.vgeodesic_el(vmax
, vvalue
, vmin
, vvalue
))
1084 privatedata
.path
.append(graph
.vgeodesic_el(vmin
, vvalue
, vmin
, privatedata
.vfromvalue
))
1085 privatedata
.path
.append(path
.closepath())
1088 gap
= abs(vmin
- privatedata
.lastvmax
) > self
.epsilon
1089 except (ArithmeticError, ValueError, TypeError):
1091 if (privatedata
.lastvvalue
is not None and currentvalid
and not gap
and
1092 (self
.steps
or (privatedata
.lastvvalue
-privatedata
.vfromvalue
)*(vvalue
-privatedata
.vfromvalue
) < 0)):
1093 self
.vposline(privatedata
, sharedata
, graph
,
1094 vmin
, privatedata
.lastvvalue
, vvalue
)
1096 if privatedata
.lastvvalue
is not None and currentvalid
:
1097 currentbigger
= abs(privatedata
.lastvvalue
-privatedata
.vfromvalue
) < abs(vvalue
-privatedata
.vfromvalue
)
1098 if privatedata
.lastvvalue
is not None and (not currentvalid
or not currentbigger
or gap
):
1099 self
.vposline(privatedata
, sharedata
, graph
,
1100 privatedata
.lastvmax
, privatedata
.lastvvalue
, privatedata
.vfromvalue
)
1102 self
.vmoveto(privatedata
, sharedata
, graph
,
1104 if currentvalid
and (privatedata
.lastvvalue
is None or currentbigger
or gap
):
1105 self
.vmoveto(privatedata
, sharedata
, graph
,
1106 vmin
, privatedata
.vfromvalue
)
1107 self
.vposline(privatedata
, sharedata
, graph
,
1108 vmin
, privatedata
.vfromvalue
, vvalue
)
1110 self
.vvalueline(privatedata
, sharedata
, graph
,
1112 privatedata
.lastvvalue
= vvalue
1113 privatedata
.lastvmax
= vmax
1115 privatedata
.lastvvalue
= privatedata
.lastvmax
= None
1117 def initdrawpoints(self
, privatedata
, sharedata
, graph
):
1118 privatedata
.path
= path
.path()
1119 privatedata
.lastvvalue
= privatedata
.lastvmax
= None
1120 privatedata
.vcurrentpoint
= None
1121 privatedata
.count
= 0
1122 if self
.fromvalue
is not None:
1123 valueaxisname
= sharedata
.poscolumnnames
[1-privatedata
.rangeaxisindex
]
1124 privatedata
.vfromvalue
= graph
.axes
[valueaxisname
].convert(self
.fromvalue
)
1125 privatedata
.vfromvaluecut
= 0
1126 if privatedata
.vfromvalue
< 0:
1127 privatedata
.vfromvalue
= 0
1128 privatedata
.vfromvaluecut
= -1
1129 if privatedata
.vfromvalue
> 1:
1130 privatedata
.vfromvalue
= 1
1131 privatedata
.vfromvaluecut
= 1
1132 if self
.frompathattrs
is not None and privatedata
.insertfrompath
:
1133 graph
.stroke(graph
.axes
[valueaxisname
].vgridpath(privatedata
.vfromvalue
),
1134 self
.defaultfrompathattrs
+ self
.frompathattrs
)
1136 privatedata
.vfromvalue
= 0
1138 def drawpoint(self
, privatedata
, sharedata
, graph
, point
):
1139 if privatedata
.autohistogram
:
1140 # automatic range handling
1141 privatedata
.count
+= 1
1142 if privatedata
.count
== 2:
1143 if privatedata
.rangeaxisindex
:
1144 privatedata
.vrange
= sharedata
.vpos
[1] - privatedata
.lastvpos
[1]
1145 self
.drawvalue(privatedata
, sharedata
, graph
,
1146 privatedata
.lastvpos
[1] - self
.autohistogrampointpos
*privatedata
.vrange
,
1147 privatedata
.lastvpos
[1] + (1-self
.autohistogrampointpos
)*privatedata
.vrange
,
1148 privatedata
.lastvpos
[0])
1150 privatedata
.vrange
= sharedata
.vpos
[0] - privatedata
.lastvpos
[0]
1151 self
.drawvalue(privatedata
, sharedata
, graph
,
1152 privatedata
.lastvpos
[0] - self
.autohistogrampointpos
*privatedata
.vrange
,
1153 privatedata
.lastvpos
[0] + (1-self
.autohistogrampointpos
)*privatedata
.vrange
,
1154 privatedata
.lastvpos
[1])
1155 elif privatedata
.count
> 2:
1156 if privatedata
.rangeaxisindex
:
1157 vrange
= sharedata
.vpos
[1] - privatedata
.lastvpos
[1]
1159 vrange
= sharedata
.vpos
[0] - privatedata
.lastvpos
[0]
1160 if abs(privatedata
.vrange
- vrange
) > self
.epsilon
:
1161 raise ValueError("equal steps (in graph coordinates) needed for automatic width calculation")
1162 if privatedata
.count
> 1:
1163 if privatedata
.rangeaxisindex
:
1164 self
.drawvalue(privatedata
, sharedata
, graph
,
1165 sharedata
.vpos
[1] - self
.autohistogrampointpos
*privatedata
.vrange
,
1166 sharedata
.vpos
[1] + (1-self
.autohistogrampointpos
)*privatedata
.vrange
,
1169 self
.drawvalue(privatedata
, sharedata
, graph
,
1170 sharedata
.vpos
[0] - self
.autohistogrampointpos
*privatedata
.vrange
,
1171 sharedata
.vpos
[0] + (1-self
.autohistogrampointpos
)*privatedata
.vrange
,
1173 privatedata
.lastvpos
= sharedata
.vpos
[:]
1175 if privatedata
.rangeaxisindex
:
1176 self
.drawvalue(privatedata
, sharedata
, graph
,
1177 sharedata
.vrange
[1][0], sharedata
.vrange
[1][1], sharedata
.vpos
[0])
1179 self
.drawvalue(privatedata
, sharedata
, graph
,
1180 sharedata
.vrange
[0][0], sharedata
.vrange
[0][1], sharedata
.vpos
[1])
1182 def donedrawpoints(self
, privatedata
, sharedata
, graph
):
1183 self
.drawvalue(privatedata
, sharedata
, graph
, None, None, None)
1184 if privatedata
.lineattrs
is not None and len(privatedata
.path
):
1185 graph
.draw(privatedata
.path
, privatedata
.lineattrs
)
1187 def key_pt(self
, privatedata
, sharedata
, graph
, x_pt
, y_pt
, width_pt
, height_pt
):
1188 if privatedata
.lineattrs
is not None:
1189 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
)
1192 class barpos(_style
):
1194 providesdata
= ["vpos", "vposmissing", "vposavailable", "vposvalid", "vbarrange", "barposcolumnnames", "barvalueindex", "lastbarvalue", "stackedbar", "stackedbardraw"]
1196 defaultfrompathattrs
= []
1198 def __init__(self
, fromvalue
=None, frompathattrs
=[], epsilon
=1e-10):
1199 self
.fromvalue
= fromvalue
1200 self
.frompathattrs
= frompathattrs
1201 self
.epsilon
= epsilon
1203 def columnnames(self
, privatedata
, sharedata
, graph
, columnnames
):
1204 sharedata
.barposcolumnnames
= []
1205 sharedata
.barvalueindex
= None
1206 for dimension
, axisnames
in enumerate(graph
.axesnames
):
1208 for axisname
in axisnames
:
1209 if axisname
in columnnames
:
1210 if sharedata
.barvalueindex
is not None:
1211 raise ValueError("multiple values")
1212 sharedata
.barvalueindex
= dimension
1213 sharedata
.barposcolumnnames
.append(axisname
)
1215 if (axisname
+ "name") in columnnames
:
1216 sharedata
.barposcolumnnames
.append(axisname
+ "name")
1219 raise ValueError("multiple names and value")
1221 raise ValueError("value/name missing")
1222 if sharedata
.barvalueindex
is None:
1223 raise ValueError("missing value")
1224 sharedata
.vposmissing
= []
1225 return sharedata
.barposcolumnnames
1227 def addsubvalue(self
, value
, subvalue
):
1232 return value
[0], self
.addsubvalue(value
[1], subvalue
)
1234 return value
, subvalue
1236 return value
, subvalue
1238 def adjustaxis(self
, privatedata
, sharedata
, graph
, columnname
, data
):
1240 i
= sharedata
.barposcolumnnames
.index(columnname
)
1244 if i
== sharedata
.barvalueindex
:
1245 if self
.fromvalue
is not None:
1246 graph
.axes
[sharedata
.barposcolumnnames
[i
]].adjustaxis([self
.fromvalue
])
1247 graph
.axes
[sharedata
.barposcolumnnames
[i
]].adjustaxis(data
)
1249 graph
.axes
[sharedata
.barposcolumnnames
[i
][:-4]].adjustaxis([self
.addsubvalue(x
, 0) for x
in data
])
1250 graph
.axes
[sharedata
.barposcolumnnames
[i
][:-4]].adjustaxis([self
.addsubvalue(x
, 1) for x
in data
])
1252 def selectstyle(self
, privatedata
, sharedata
, graph
, selectindex
, selecttotal
):
1253 privatedata
.insertfrompath
= selectindex
== 0
1255 def initdrawpoints(self
, privatedata
, sharedata
, graph
):
1256 sharedata
.vpos
= [None]*(len(sharedata
.barposcolumnnames
))
1257 sharedata
.vbarrange
= [[None for i
in xrange(2)] for x
in sharedata
.barposcolumnnames
]
1258 sharedata
.stackedbar
= sharedata
.stackedbardraw
= 0
1260 if self
.fromvalue
is not None:
1261 privatedata
.vfromvalue
= graph
.axes
[sharedata
.barposcolumnnames
[sharedata
.barvalueindex
][0]].convert(self
.fromvalue
)
1262 if privatedata
.vfromvalue
< 0:
1263 privatedata
.vfromvalue
= 0
1264 if privatedata
.vfromvalue
> 1:
1265 privatedata
.vfromvalue
= 1
1266 if self
.frompathattrs
is not None and privatedata
.insertfrompath
:
1267 graph
.stroke(graph
.axes
[sharedata
.barposcolumnnames
[sharedata
.barvalueindex
][0]].vgridpath(privatedata
.vfromvalue
),
1268 self
.defaultfrompathattrs
+ self
.frompathattrs
)
1270 privatedata
.vfromvalue
= 0
1272 def drawpoint(self
, privatedata
, sharedata
, graph
, point
):
1273 sharedata
.vposavailable
= sharedata
.vposvalid
= 1
1274 for i
, barname
in enumerate(sharedata
.barposcolumnnames
):
1275 if i
== sharedata
.barvalueindex
:
1276 sharedata
.vbarrange
[i
][0] = privatedata
.vfromvalue
1277 sharedata
.lastbarvalue
= point
[barname
]
1279 sharedata
.vpos
[i
] = sharedata
.vbarrange
[i
][1] = graph
.axes
[barname
].convert(sharedata
.lastbarvalue
)
1280 except (ArithmeticError, ValueError, TypeError):
1281 sharedata
.vpos
[i
] = sharedata
.vbarrange
[i
][1] = None
1285 sharedata
.vbarrange
[i
][j
] = graph
.axes
[barname
[:-4]].convert(self
.addsubvalue(point
[barname
], j
))
1286 except (ArithmeticError, ValueError, TypeError):
1287 sharedata
.vbarrange
[i
][j
] = None
1289 sharedata
.vpos
[i
] = 0.5*(sharedata
.vbarrange
[i
][0]+sharedata
.vbarrange
[i
][1])
1290 except (ArithmeticError, ValueError, TypeError):
1291 sharedata
.vpos
[i
] = None
1292 if sharedata
.vpos
[i
] is None:
1293 sharedata
.vposavailable
= sharedata
.vposvalid
= 0
1294 elif sharedata
.vpos
[i
] < -self
.epsilon
or sharedata
.vpos
[i
] > 1+self
.epsilon
:
1295 sharedata
.vposvalid
= 0
1297 registerdefaultprovider(barpos(), ["vbarrange", "barposcolumnnames", "barvalueindex", "lastbarvalue", "stackedbar", "stackedbardraw"])
1300 class stackedbarpos(_style
):
1302 # provides no additional data, but needs some data (and modifies some of them)
1303 needsdata
= ["vbarrange", "barposcolumnnames", "barvalueindex", "lastbarvalue", "stackedbar", "stackedbardraw"]
1305 def __init__(self
, stackname
, addontop
=0, epsilon
=1e-10):
1306 self
.stackname
= stackname
1307 self
.epsilon
= epsilon
1308 self
.addontop
= addontop
1310 def columnnames(self
, privatedata
, sharedata
, graph
, columnnames
):
1311 if self
.stackname
not in columnnames
:
1312 raise ValueError("column '%s' missing" % self
.stackname
)
1313 return [self
.stackname
]
1315 def adjustaxis(self
, privatedata
, sharedata
, graph
, columnname
, data
):
1316 if columnname
== self
.stackname
:
1317 graph
.axes
[sharedata
.barposcolumnnames
[sharedata
.barvalueindex
]].adjustaxis(data
)
1319 def initdrawpoints(self
, privatedata
, sharedata
, graph
):
1320 if sharedata
.stackedbardraw
: # do not count the start bar when not gets painted
1321 sharedata
.stackedbar
+= 1
1323 def drawpoint(self
, privatedata
, sharedata
, graph
, point
):
1324 sharedata
.vbarrange
[sharedata
.barvalueindex
][0] = sharedata
.vbarrange
[sharedata
.barvalueindex
][1]
1327 sharedata
.lastbarvalue
+= point
[self
.stackname
]
1328 except (ArithmeticError, ValueError, TypeError):
1329 sharedata
.lastbarvalue
= None
1331 sharedata
.lastbarvalue
= point
[self
.stackname
]
1333 sharedata
.vpos
[sharedata
.barvalueindex
] = sharedata
.vbarrange
[sharedata
.barvalueindex
][1] = graph
.axes
[sharedata
.barposcolumnnames
[sharedata
.barvalueindex
]].convert(sharedata
.lastbarvalue
)
1334 except (ArithmeticError, ValueError, TypeError):
1335 sharedata
.vpos
[sharedata
.barvalueindex
] = sharedata
.vbarrange
[sharedata
.barvalueindex
][1] = None
1336 sharedata
.vposavailable
= sharedata
.vposvalid
= 0
1338 if not sharedata
.vposavailable
or not sharedata
.vposvalid
:
1339 sharedata
.vposavailable
= sharedata
.vposvalid
= 1
1340 for v
in sharedata
.vpos
:
1342 sharedata
.vposavailable
= sharedata
.vposvalid
= 0
1344 if v
< -self
.epsilon
or v
> 1+self
.epsilon
:
1345 sharedata
.vposvalid
= 0
1350 needsdata
= ["vbarrange"]
1352 defaultbarattrs
= [color
.palette
.Rainbow
, deco
.stroked([color
.grey
.black
])]
1354 def __init__(self
, barattrs
=[]):
1355 self
.barattrs
= barattrs
1357 def columnnames(self
, privatedata
, sharedata
, graph
, columnnames
):
1358 if len(graph
.axesnames
) != 2:
1359 raise TypeError("bar style restricted on two-dimensional graphs")
1362 def selectstyle(self
, privatedata
, sharedata
, graph
, selectindex
, selecttotal
):
1363 privatedata
.barattrs
= attr
.selectattrs(self
.defaultbarattrs
+ self
.barattrs
, selectindex
, selecttotal
)
1365 def initdrawpoints(self
, privatedata
, sharedata
, graph
):
1366 privatedata
.rectcanvas
= graph
.insert(canvas
.canvas())
1367 sharedata
.stackedbardraw
= 1
1368 privatedata
.stackedbar
= sharedata
.stackedbar
1370 def drawpointfill(self
, privatedata
, p
):
1372 privatedata
.rectcanvas
.fill(p
, privatedata
.barattrs
)
1374 def drawpoint(self
, privatedata
, sharedata
, graph
, point
):
1375 xvmin
= sharedata
.vbarrange
[0][0]
1376 xvmax
= sharedata
.vbarrange
[0][1]
1377 yvmin
= sharedata
.vbarrange
[1][0]
1378 yvmax
= sharedata
.vbarrange
[1][1]
1381 xvmin
, xvmax
= xvmax
, xvmin
1386 yvmin
, yvmax
= yvmax
, yvmin
1389 if (xvmin
is not None and xvmin
< 1 and
1390 xvmax
is not None and xvmax
> 0 and
1391 yvmin
is not None and yvmin
< 1 and
1392 yvmax
is not None and yvmax
> 0):
1401 p
= graph
.vgeodesic(xvmin
, yvmin
, xvmax
, yvmin
)
1402 p
.append(graph
.vgeodesic_el(xvmax
, yvmin
, xvmax
, yvmax
))
1403 p
.append(graph
.vgeodesic_el(xvmax
, yvmax
, xvmin
, yvmax
))
1404 p
.append(graph
.vgeodesic_el(xvmin
, yvmax
, xvmin
, yvmin
))
1405 p
.append(path
.closepath())
1406 self
.drawpointfill(privatedata
, p
)
1408 self
.drawpointfill(privatedata
, None)
1410 def key_pt(self
, privatedata
, sharedata
, graph
, x_pt
, y_pt
, width_pt
, height_pt
):
1411 selectindex
= privatedata
.stackedbar
1412 selecttotal
= sharedata
.stackedbar
+ 1
1413 graph
.fill(path
.rect_pt(x_pt
+ width_pt
*selectindex
/float(selecttotal
), y_pt
, width_pt
/float(selecttotal
), height_pt
), privatedata
.barattrs
)
1416 class changebar(bar
):
1418 def selectstyle(self
, privatedata
, sharedata
, graph
, selectindex
, selecttotal
):
1419 if selecttotal
!= 1:
1420 raise RuntimeError("Changebar can't change its appearance. Thus you can't use it to plot several bars side by side on a subaxis.")
1422 def initdrawpoints(self
, privatedata
, sharedata
, graph
):
1423 bar
.initdrawpoints(self
, privatedata
, sharedata
, graph
)
1424 privatedata
.bars
= []
1426 def drawpointfill(self
, privatedata
, p
):
1427 privatedata
.bars
.append(p
)
1429 def donedrawpoints(self
, privatedata
, sharedata
, graph
):
1430 selecttotal
= len(privatedata
.bars
)
1431 for selectindex
, p
in enumerate(privatedata
.bars
):
1433 barattrs
= attr
.selectattrs(self
.defaultbarattrs
+ self
.barattrs
, selectindex
, selecttotal
)
1434 privatedata
.rectcanvas
.fill(p
, barattrs
)
1436 def key_pt(self
, privatedata
, sharedata
, graph
, x_pt
, y_pt
, width_pt
, height_pt
):
1437 raise RuntimeError("Style currently doesn't provide a graph key")