2 # -*- coding: ISO-8859-1 -*-
5 # Copyright (C) 2002-2004 Jörg Lehmann <joergl@users.sourceforge.net>
6 # Copyright (C) 2003-2004 Michael Schindler <m-schindler@users.sourceforge.net>
7 # Copyright (C) 2002-2004 André Wobst <wobsta@users.sourceforge.net>
9 # This file is part of PyX (http://pyx.sourceforge.net/).
11 # PyX is free software; you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation; either version 2 of the License, or
14 # (at your option) any later version.
16 # PyX is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # GNU General Public License for more details.
21 # You should have received a copy of the GNU General Public License
22 # along with PyX; if not, write to the Free Software
23 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
27 from pyx
import attr
, deco
, style
, color
, unit
, canvas
, path
28 from pyx
import text
as textmodule
33 # fallback implementation for Python 2.2. and below
35 return zip(xrange(len(list)), list)
38 """Interface class for graph styles
40 Each graph style must support the methods described in this
41 class. However, since a graph style might not need to perform
42 actions on all the various events, it does not need to overwrite
43 all methods of this base class (e.g. this class is not an abstract
44 class in any respect).
46 A style should never store private data by istance variables
47 (i.e. accessing self), but it should use the sharedata and privatedata
48 instances instead. A style instance can be used multiple times with
49 different sharedata and privatedata instances at the very same time.
50 The sharedata and privatedata instances act as data containers and
51 sharedata allows for sharing information across several styles.
53 Every style contains two class variables, which are not to be
55 - providesdata is a list of variable names a style offers via
56 the sharedata instance. This list is used to determine whether
57 all needs of subsequent styles are fullfilled. Otherwise
58 getdefaultprovider should return a proper style to be used.
59 - needsdata is a list of variable names the style needs to access in the
63 providesdata
= [] # by default, we provide nothing
64 needsdata
= [] # and do not depend on anything
66 def columns(self
, privatedata
, sharedata
, graph
, columns
):
67 """Set column information
69 This method is used setup the column information to be
70 accessible to the style later on. The style should analyse
71 the list of strings columns, which contain the column names
72 of the data. The method should return a list of column names
73 which the style will make use of."""
76 def selectstyle(self
, privatedata
, sharedata
, graph
, selectindex
, selecttotal
):
77 """Select stroke/fill attributes
79 This method is called to allow for the selection of
80 changable attributes of a style."""
83 def adjustaxis(self
, privatedata
, sharedata
, graph
, column
, data
, index
):
86 This method is called in order to adjust the axis range to
87 the provided data. Column is the name of the column (each
88 style is subsequently called for all column names). If index
89 is not None, data is a list of points and index is the index
90 of the column within a point. Otherwise data is already the
91 axis data. Note, that data might be different for different
92 columns, e.i. data might come from various places and is
93 combined without copying but keeping references."""
96 def initdrawpoints(self
, privatedata
, sharedata
, graph
):
97 """Initialize drawing of data
99 This method might be used to initialize the drawing of data."""
102 def drawpoint(self
, privatedata
, sharedata
, graph
):
105 This method is called for each data point. The data is
106 available in the dictionary sharedata.point. The dictionary
107 keys are the column names."""
110 def donedrawpoints(self
, privatedata
, sharedata
, graph
):
111 """Finalize drawing of data
113 This method is called after the last data point was
114 drawn using the drawpoint method above."""
117 def key_pt(self
, privatedata
, sharedata
, graph
, x_pt
, y_pt
, width_pt
, height_pt
, dy_pt
, selectindex
, selecttotal
):
120 This method draws a key for the style to graph at the given
121 position x_pt, y_pt indicating the lower left corner of the
122 given area width_pt, height_pt. The style might draw several
123 key entries shifted vertically by dy_pt. The method returns
124 the number of key entries or None, when nothing was drawn."""
128 # The following two methods are used to register and get a default provider
129 # for keys. A key is a variable name in sharedata. A provider is a style
130 # which creates variables in sharedata.
132 _defaultprovider
= {}
134 def registerdefaultprovider(style
, keys
):
135 """sets a style as a default creator for sharedata variables 'keys'"""
136 assert not len(style
.needsdata
), "currently we state, that a style should not depend on other sharedata variables"
138 assert key
in style
.providesdata
, "key not provided by style"
139 # we might allow for overwriting the defaults, i.e. the following is not checked:
140 # assert key in _defaultprovider.keys(), "default provider already registered for key"
141 _defaultprovider
[key
] = style
143 def getdefaultprovider(key
):
144 """returns a style, which acts as a default creator for the
145 sharedata variable 'key'"""
146 return _defaultprovider
[key
]
151 providesdata
= ["vpos", "vposmissing", "vposavailable", "vposvalid"]
153 def __init__(self
, epsilon
=1e-10):
154 self
.epsilon
= epsilon
156 def columns(self
, privatedata
, sharedata
, graph
, columns
):
157 privatedata
.pointposcolumns
= []
158 sharedata
.vposmissing
= []
159 for count
, axisnames
in enumerate(graph
.axesnames
):
160 for axisname
in axisnames
:
161 for column
in columns
:
162 if axisname
== column
:
163 privatedata
.pointposcolumns
.append(column
)
164 if len(privatedata
.pointposcolumns
) + len(sharedata
.vposmissing
) > count
+1:
165 raise ValueError("multiple axes per graph dimension")
166 elif len(privatedata
.pointposcolumns
) + len(sharedata
.vposmissing
) < count
+1:
167 sharedata
.vposmissing
.append(count
)
168 return privatedata
.pointposcolumns
170 def adjustaxis(self
, privatedata
, sharedata
, graph
, column
, data
, index
):
171 if column
in privatedata
.pointposcolumns
:
172 graph
.axes
[column
].adjustrange(data
, index
)
174 def initdrawpoints(self
, privatedata
, sharedata
, graph
):
175 sharedata
.vpos
= [None]*(len(privatedata
.pointposcolumns
) + len(sharedata
.vposmissing
))
176 privatedata
.pointpostmplist
= [[column
, index
, graph
.axes
[column
]] # temporarily used by drawpoint only
177 for index
, column
in enumerate(privatedata
.pointposcolumns
)]
178 for missing
in sharedata
.vposmissing
:
179 for pointpostmp
in privatedata
.pointpostmplist
:
180 if pointpostmp
[1] >= missing
:
183 def drawpoint(self
, privatedata
, sharedata
, graph
):
184 sharedata
.vposavailable
= 1 # valid position (but might be outside of the graph)
185 sharedata
.vposvalid
= 1 # valid position inside the graph
186 for column
, index
, axis
in privatedata
.pointpostmplist
:
188 v
= axis
.convert(sharedata
.point
[column
])
189 except (ArithmeticError, ValueError, TypeError):
190 sharedata
.vposavailable
= sharedata
.vposvalid
= 0
191 sharedata
.vpos
[index
] = None
193 if v
< -self
.epsilon
or v
> 1+self
.epsilon
:
194 sharedata
.vposvalid
= 0
195 sharedata
.vpos
[index
] = v
198 registerdefaultprovider(_pos(), _pos
.providesdata
)
201 class _range(_style
):
203 providesdata
= ["vrange", "vrangemissing", "vrangeminmissing", "vrangemaxmissing"]
213 def __init__(self
, epsilon
=1e-10):
214 self
.epsilon
= epsilon
216 def columns(self
, privatedata
, sharedata
, graph
, columns
):
217 def numberofbits(mask
):
221 return numberofbits(mask
>> 1) + 1
223 return numberofbits(mask
>> 1)
225 privatedata
.rangeposcolumns
= []
226 sharedata
.vrangemissing
= []
227 sharedata
.vrangeminmissing
= []
228 sharedata
.vrangemaxmissing
= []
229 privatedata
.rangeposdeltacolumns
= {} # temporarily used by adjustaxis only
230 for count
, axisnames
in enumerate(graph
.axesnames
):
231 for axisname
in axisnames
:
233 for column
in columns
:
235 if axisname
== column
:
236 mask
+= self
.mask_value
237 elif axisname
+ "min" == column
:
238 mask
+= self
.mask_min
239 elif axisname
+ "max" == column
:
240 mask
+= self
.mask_max
241 elif "d" + axisname
+ "min" == column
:
242 mask
+= self
.mask_dmin
243 elif "d" + axisname
+ "max" == column
:
244 mask
+= self
.mask_dmax
245 elif "d" + axisname
== column
:
250 usecolumns
.append(column
)
251 if mask
& (self
.mask_min | self
.mask_max | self
.mask_dmin | self
.mask_dmax | self
.mask_d
):
252 if (numberofbits(mask
& (self
.mask_min | self
.mask_dmin | self
.mask_d
)) > 1 or
253 numberofbits(mask
& (self
.mask_max | self
.mask_dmax | self
.mask_d
)) > 1):
254 raise ValueError("multiple range definition")
255 if mask
& (self
.mask_dmin | self
.mask_dmax | self
.mask_d
):
256 if not (mask
& self
.mask_value
):
257 raise ValueError("missing value for delta")
258 privatedata
.rangeposdeltacolumns
[axisname
] = {}
259 privatedata
.rangeposcolumns
.append((axisname
, mask
))
260 elif mask
== self
.mask_value
:
261 usecolumns
= usecolumns
[:-1]
262 if len(privatedata
.rangeposcolumns
) + len(sharedata
.vrangemissing
) > count
+1:
263 raise ValueError("multiple axes per graph dimension")
264 elif len(privatedata
.rangeposcolumns
) + len(sharedata
.vrangemissing
) < count
+1:
265 sharedata
.vrangemissing
.append(count
)
267 if not (privatedata
.rangeposcolumns
[-1][1] & (self
.mask_min | self
.mask_dmin | self
.mask_d
)):
268 sharedata
.vrangeminmissing
.append(count
)
269 if not (privatedata
.rangeposcolumns
[-1][1] & (self
.mask_max | self
.mask_dmax | self
.mask_d
)):
270 sharedata
.vrangemaxmissing
.append(count
)
273 def adjustaxis(self
, privatedata
, sharedata
, graph
, column
, data
, index
):
274 if column
in [c
+ "min" for c
, m
in privatedata
.rangeposcolumns
if m
& self
.mask_min
]:
275 graph
.axes
[column
[:-3]].adjustrange(data
, index
)
276 if column
in [c
+ "max" for c
, m
in privatedata
.rangeposcolumns
if m
& self
.mask_max
]:
277 graph
.axes
[column
[:-3]].adjustrange(data
, index
)
279 # delta handling: fill rangeposdeltacolumns
280 if column
in [c
for c
, m
in privatedata
.rangeposcolumns
if m
& (self
.mask_dmin | self
.mask_dmax | self
.mask_d
)]:
281 privatedata
.rangeposdeltacolumns
[column
][self
.mask_value
] = data
, index
282 if column
in ["d" + c
+ "min" for c
, m
in privatedata
.rangeposcolumns
if m
& self
.mask_dmin
]:
283 privatedata
.rangeposdeltacolumns
[column
[1:-3]][self
.mask_dmin
] = data
, index
284 if column
in ["d" + c
+ "max" for c
, m
in privatedata
.rangeposcolumns
if m
& self
.mask_dmax
]:
285 privatedata
.rangeposdeltacolumns
[column
[1:-3]][self
.mask_dmax
] = data
, index
286 if column
in ["d" + c
for c
, m
in privatedata
.rangeposcolumns
if m
& self
.mask_d
]:
287 privatedata
.rangeposdeltacolumns
[column
[1:]][self
.mask_d
] = data
, index
289 # delta handling: process rangeposdeltacolumns
290 for c
, d
in privatedata
.rangeposdeltacolumns
.items():
291 if d
.has_key(self
.mask_value
):
293 if k
!= self
.mask_value
:
294 if k
& (self
.mask_dmin | self
.mask_d
):
295 graph
.axes
[c
].adjustrange(d
[self
.mask_value
][0], d
[self
.mask_value
][1],
296 deltamindata
=d
[k
][0], deltaminindex
=d
[k
][1])
297 if k
& (self
.mask_dmax | self
.mask_d
):
298 graph
.axes
[c
].adjustrange(d
[self
.mask_value
][0], d
[self
.mask_value
][1],
299 deltamaxdata
=d
[k
][0], deltamaxindex
=d
[k
][1])
302 def initdrawpoints(self
, privatedata
, sharedata
, graph
):
303 sharedata
.vrange
= [[None for x
in range(2)] for y
in privatedata
.rangeposcolumns
+ sharedata
.vrangemissing
]
304 privatedata
.rangepostmplist
= [[column
, mask
, index
, graph
.axes
[column
]] # temporarily used by drawpoint only
305 for index
, (column
, mask
) in enumerate(privatedata
.rangeposcolumns
)]
306 for missing
in sharedata
.vrangemissing
:
307 for rangepostmp
in privatedata
.rangepostmplist
:
308 if rangepostmp
[2] >= missing
:
311 def drawpoint(self
, privatedata
, sharedata
, graph
):
312 for column
, mask
, index
, axis
in privatedata
.rangepostmplist
:
314 if mask
& self
.mask_min
:
315 sharedata
.vrange
[index
][0] = axis
.convert(sharedata
.point
[column
+ "min"])
316 if mask
& self
.mask_dmin
:
317 sharedata
.vrange
[index
][0] = axis
.convert(sharedata
.point
[column
] - sharedata
.point
["d" + column
+ "min"])
318 if mask
& self
.mask_d
:
319 sharedata
.vrange
[index
][0] = axis
.convert(sharedata
.point
[column
] - sharedata
.point
["d" + column
])
320 except (ArithmeticError, ValueError, TypeError):
321 sharedata
.vrange
[index
][0] = None
323 if mask
& self
.mask_max
:
324 sharedata
.vrange
[index
][1] = axis
.convert(sharedata
.point
[column
+ "max"])
325 if mask
& self
.mask_dmax
:
326 sharedata
.vrange
[index
][1] = axis
.convert(sharedata
.point
[column
] + sharedata
.point
["d" + column
+ "max"])
327 if mask
& self
.mask_d
:
328 sharedata
.vrange
[index
][1] = axis
.convert(sharedata
.point
[column
] + sharedata
.point
["d" + column
])
329 except (ArithmeticError, ValueError, TypeError):
330 sharedata
.vrange
[index
][1] = None
332 # some range checks for data consistency
333 if (sharedata
.vrange
[index
][0] is not None and sharedata
.vrange
[index
][1] is not None and
334 sharedata
.vrange
[index
][0] > sharedata
.vrange
[index
][1] + self
.epsilon
):
335 raise ValueError("inverse range")
336 #if (sharedata.vrange[index][0] is not None and sharedata.vpos[index] is not None and
337 # sharedata.vrange[index][0] > sharedata.vpos[index] + self.epsilon):
338 # raise ValueError("negative minimum errorbar")
339 #if (sharedata.vrange[index][1] is not None and sharedata.vpos[index] is not None and
340 # sharedata.vrange[index][1] < sharedata.vpos[index] - self.epsilon):
341 # raise ValueError("negative maximum errorbar")
344 registerdefaultprovider(_range(), _range
.providesdata
)
347 def _crosssymbol(c
, x_pt
, y_pt
, size_pt
, attrs
):
348 c
.draw(path
.path(path
.moveto_pt(x_pt
-0.5*size_pt
, y_pt
-0.5*size_pt
),
349 path
.lineto_pt(x_pt
+0.5*size_pt
, y_pt
+0.5*size_pt
),
350 path
.moveto_pt(x_pt
-0.5*size_pt
, y_pt
+0.5*size_pt
),
351 path
.lineto_pt(x_pt
+0.5*size_pt
, y_pt
-0.5*size_pt
)), attrs
)
353 def _plussymbol(c
, x_pt
, y_pt
, size_pt
, attrs
):
354 c
.draw(path
.path(path
.moveto_pt(x_pt
-0.707106781*size_pt
, y_pt
),
355 path
.lineto_pt(x_pt
+0.707106781*size_pt
, y_pt
),
356 path
.moveto_pt(x_pt
, y_pt
-0.707106781*size_pt
),
357 path
.lineto_pt(x_pt
, y_pt
+0.707106781*size_pt
)), attrs
)
359 def _squaresymbol(c
, x_pt
, y_pt
, size_pt
, attrs
):
360 c
.draw(path
.path(path
.moveto_pt(x_pt
-0.5*size_pt
, y_pt
-0.5*size_pt
),
361 path
.lineto_pt(x_pt
+0.5*size_pt
, y_pt
-0.5*size_pt
),
362 path
.lineto_pt(x_pt
+0.5*size_pt
, y_pt
+0.5*size_pt
),
363 path
.lineto_pt(x_pt
-0.5*size_pt
, y_pt
+0.5*size_pt
),
364 path
.closepath()), attrs
)
366 def _trianglesymbol(c
, x_pt
, y_pt
, size_pt
, attrs
):
367 c
.draw(path
.path(path
.moveto_pt(x_pt
-0.759835685*size_pt
, y_pt
-0.438691337*size_pt
),
368 path
.lineto_pt(x_pt
+0.759835685*size_pt
, y_pt
-0.438691337*size_pt
),
369 path
.lineto_pt(x_pt
, y_pt
+0.877382675*size_pt
),
370 path
.closepath()), attrs
)
372 def _circlesymbol(c
, x_pt
, y_pt
, size_pt
, attrs
):
373 c
.draw(path
.path(path
.arc_pt(x_pt
, y_pt
, 0.564189583*size_pt
, 0, 360),
374 path
.closepath()), attrs
)
376 def _diamondsymbol(c
, x_pt
, y_pt
, size_pt
, attrs
):
377 c
.draw(path
.path(path
.moveto_pt(x_pt
-0.537284965*size_pt
, y_pt
),
378 path
.lineto_pt(x_pt
, y_pt
-0.930604859*size_pt
),
379 path
.lineto_pt(x_pt
+0.537284965*size_pt
, y_pt
),
380 path
.lineto_pt(x_pt
, y_pt
+0.930604859*size_pt
),
381 path
.closepath()), attrs
)
384 class _styleneedingpointpos(_style
):
386 needsdata
= ["vposmissing"]
388 def columns(self
, privatedata
, sharedata
, graph
, columns
):
389 if len(sharedata
.vposmissing
):
390 raise ValueError("position columns incomplete")
394 class symbol(_styleneedingpointpos
):
396 needsdata
= ["vpos", "vposmissing", "vposvalid"]
399 # note, that statements like cross = _crosssymbol are
400 # invalid, since the would lead to unbound methods, but
401 # a single entry changeable list does the trick
402 cross
= attr
.changelist([_crosssymbol
])
403 plus
= attr
.changelist([_plussymbol
])
404 square
= attr
.changelist([_squaresymbol
])
405 triangle
= attr
.changelist([_trianglesymbol
])
406 circle
= attr
.changelist([_circlesymbol
])
407 diamond
= attr
.changelist([_diamondsymbol
])
409 changecross
= attr
.changelist([_crosssymbol
, _plussymbol
, _squaresymbol
, _trianglesymbol
, _circlesymbol
, _diamondsymbol
])
410 changeplus
= attr
.changelist([_plussymbol
, _squaresymbol
, _trianglesymbol
, _circlesymbol
, _diamondsymbol
, cross
])
411 changesquare
= attr
.changelist([_squaresymbol
, _trianglesymbol
, _circlesymbol
, _diamondsymbol
, cross
, _plussymbol
])
412 changetriangle
= attr
.changelist([_trianglesymbol
, _circlesymbol
, _diamondsymbol
, cross
, _plussymbol
, _squaresymbol
])
413 changecircle
= attr
.changelist([_circlesymbol
, _diamondsymbol
, cross
, _plussymbol
, _squaresymbol
, _trianglesymbol
])
414 changediamond
= attr
.changelist([_diamondsymbol
, cross
, _plussymbol
, _squaresymbol
, _trianglesymbol
, _circlesymbol
])
415 changesquaretwice
= attr
.changelist([_squaresymbol
, _squaresymbol
, _trianglesymbol
, _trianglesymbol
, _circlesymbol
, _circlesymbol
, _diamondsymbol
, _diamondsymbol
])
416 changetriangletwice
= attr
.changelist([_trianglesymbol
, _trianglesymbol
, _circlesymbol
, _circlesymbol
, _diamondsymbol
, _diamondsymbol
, _squaresymbol
, _squaresymbol
])
417 changecircletwice
= attr
.changelist([_circlesymbol
, _circlesymbol
, _diamondsymbol
, _diamondsymbol
, _squaresymbol
, _squaresymbol
, _trianglesymbol
, _trianglesymbol
])
418 changediamondtwice
= attr
.changelist([_diamondsymbol
, _diamondsymbol
, _squaresymbol
, _squaresymbol
, _trianglesymbol
, _trianglesymbol
, _circlesymbol
, _circlesymbol
])
420 changestrokedfilled
= attr
.changelist([deco
.stroked
, deco
.filled
])
421 changefilledstroked
= attr
.changelist([deco
.filled
, deco
.stroked
])
423 defaultsymbolattrs
= [deco
.stroked
]
425 def __init__(self
, symbol
=changecross
, size
=0.2*unit
.v_cm
, symbolattrs
=[]):
428 self
.symbolattrs
= symbolattrs
430 def selectstyle(self
, privatedata
, sharedata
, graph
, selectindex
, selecttotal
):
431 privatedata
.symbol
= attr
.selectattr(self
.symbol
, selectindex
, selecttotal
)
432 privatedata
.size_pt
= unit
.topt(attr
.selectattr(self
.size
, selectindex
, selecttotal
))
433 if self
.symbolattrs
is not None:
434 privatedata
.symbolattrs
= attr
.selectattrs(self
.defaultsymbolattrs
+ self
.symbolattrs
, selectindex
, selecttotal
)
436 privatedata
.symbolattrs
= None
438 def initdrawpoints(self
, privatedata
, sharedata
, graph
):
439 privatedata
.symbolcanvas
= graph
.insert(canvas
.canvas())
441 def drawpoint(self
, privatedata
, sharedata
, graph
):
442 if sharedata
.vposvalid
and privatedata
.symbolattrs
is not None:
443 xpos
, ypos
= graph
.vpos_pt(*sharedata
.vpos
)
444 privatedata
.symbol(privatedata
.symbolcanvas
, xpos
, ypos
, privatedata
.size_pt
, privatedata
.symbolattrs
)
446 def key_pt(self
, privatedata
, sharedata
, graph
, x_pt
, y_pt
, width_pt
, height_pt
, dy_pt
, selectindex
, selecttotal
):
447 if privatedata
.symbolattrs
is not None:
448 privatedata
.symbol(graph
, x_pt
+0.5*width_pt
, y_pt
+0.5*height_pt
, privatedata
.size_pt
, privatedata
.symbolattrs
)
452 class line(_styleneedingpointpos
):
454 needsdata
= ["vpos", "vposmissing", "vposavailable", "vposvalid"]
456 changelinestyle
= attr
.changelist([style
.linestyle
.solid
,
457 style
.linestyle
.dashed
,
458 style
.linestyle
.dotted
,
459 style
.linestyle
.dashdotted
])
461 defaultlineattrs
= [changelinestyle
]
463 def __init__(self
, lineattrs
=[]):
464 self
.lineattrs
= lineattrs
466 def selectstyle(self
, privatedata
, sharedata
, graph
, selectindex
, selecttotal
):
467 privatedata
.lineattrs
= attr
.selectattrs(self
.defaultlineattrs
+ self
.lineattrs
, selectindex
, selecttotal
)
469 def initdrawpoints(self
, privatedata
, sharedata
, graph
):
470 if privatedata
.lineattrs
is not None:
471 privatedata
.linecanvas
= graph
.insert(canvas
.canvas())
472 privatedata
.linecanvas
.set(privatedata
.lineattrs
)
473 privatedata
.path
= path
.path()
474 privatedata
.linebasepoints
= []
475 privatedata
.lastvpos
= None
477 def addpointstopath(self
, privatedata
, sharedata
):
478 # add baselinepoints to privatedata.path
479 if len(privatedata
.linebasepoints
) > 1:
480 privatedata
.path
.append(path
.moveto_pt(*privatedata
.linebasepoints
[0]))
481 if len(privatedata
.linebasepoints
) > 2:
482 privatedata
.path
.append(path
.multilineto_pt(privatedata
.linebasepoints
[1:]))
484 privatedata
.path
.append(path
.lineto_pt(*privatedata
.linebasepoints
[1]))
485 privatedata
.linebasepoints
= []
487 def drawpoint(self
, privatedata
, sharedata
, graph
):
488 # append linebasepoints
489 if sharedata
.vposavailable
:
490 if len(privatedata
.linebasepoints
):
491 # the last point was inside the graph
492 if sharedata
.vposvalid
: # shortcut for the common case
493 privatedata
.linebasepoints
.append(graph
.vpos_pt(*sharedata
.vpos
))
497 for vstart
, vend
in zip(privatedata
.lastvpos
, sharedata
.vpos
):
500 # 1 = vstart + (vend - vstart) * cut
502 newcut
= (1 - vstart
)/(vend
- vstart
)
503 except ArithmeticError:
506 # 0 = vstart + (vend - vstart) * cut
508 newcut
= - vstart
/(vend
- vstart
)
509 except ArithmeticError:
511 if newcut
is not None and newcut
< cut
:
515 for vstart
, vend
in zip(privatedata
.lastvpos
, sharedata
.vpos
):
516 cutvpos
.append(vstart
+ (vend
- vstart
) * cut
)
517 privatedata
.linebasepoints
.append(graph
.vpos_pt(*cutvpos
))
518 self
.addpointstopath(privatedata
, sharedata
)
520 # the last point was outside the graph
521 if privatedata
.lastvpos
is not None:
522 if sharedata
.vposvalid
:
525 for vstart
, vend
in zip(privatedata
.lastvpos
, sharedata
.vpos
):
528 # 1 = vstart + (vend - vstart) * cut
530 newcut
= (1 - vstart
)/(vend
- vstart
)
531 except ArithmeticError:
534 # 0 = vstart + (vend - vstart) * cut
536 newcut
= - vstart
/(vend
- vstart
)
537 except ArithmeticError:
539 if newcut
is not None and newcut
> cut
:
543 for vstart
, vend
in zip(privatedata
.lastvpos
, sharedata
.vpos
):
544 cutvpos
.append(vstart
+ (vend
- vstart
) * cut
)
545 privatedata
.linebasepoints
.append(graph
.vpos_pt(*cutvpos
))
546 privatedata
.linebasepoints
.append(graph
.vpos_pt(*sharedata
.vpos
))
548 # sometimes cut beginning and end
551 for vstart
, vend
in zip(privatedata
.lastvpos
, sharedata
.vpos
):
556 # 1 = vstart + (vend - vstart) * cutfrom
558 newcutfrom
= (1 - vstart
)/(vend
- vstart
)
559 except ArithmeticError:
564 # 0 = vstart + (vend - vstart) * cutfrom
566 newcutfrom
= - vstart
/(vend
- vstart
)
567 except ArithmeticError:
569 if newcutfrom
is not None and newcutfrom
> cutfrom
:
573 # 1 = vstart + (vend - vstart) * cutto
575 newcutto
= (1 - vstart
)/(vend
- vstart
)
576 except ArithmeticError:
579 # 0 = vstart + (vend - vstart) * cutto
581 newcutto
= - vstart
/(vend
- vstart
)
582 except ArithmeticError:
584 if newcutto
is not None and newcutto
< cutto
:
590 for vstart
, vend
in zip(privatedata
.lastvpos
, sharedata
.vpos
):
591 cutfromvpos
.append(vstart
+ (vend
- vstart
) * cutfrom
)
592 cuttovpos
.append(vstart
+ (vend
- vstart
) * cutto
)
593 privatedata
.linebasepoints
.append(graph
.vpos_pt(*cutfromvpos
))
594 privatedata
.linebasepoints
.append(graph
.vpos_pt(*cuttovpos
))
595 self
.addpointstopath(privatedata
, sharedata
)
596 privatedata
.lastvpos
= sharedata
.vpos
[:]
598 if len(privatedata
.linebasepoints
) > 1:
599 self
.addpointstopath(privatedata
, sharedata
)
600 privatedata
.lastvpos
= None
602 def donedrawpoints(self
, privatedata
, sharedata
, graph
):
603 if len(privatedata
.linebasepoints
) > 1:
604 self
.addpointstopath(privatedata
, sharedata
)
605 if privatedata
.lineattrs
is not None and len(privatedata
.path
.path
):
606 privatedata
.linecanvas
.stroke(privatedata
.path
)
608 def key_pt(self
, privatedata
, sharedata
, graph
, x_pt
, y_pt
, width_pt
, height_pt
, dy_pt
, selectindex
, selecttotal
):
609 if privatedata
.lineattrs
is not None:
610 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
)
614 class errorbar(_style
):
616 needsdata
= ["vpos", "vposmissing", "vposavailable", "vposvalid", "vrange", "vrangemissing"]
618 defaulterrorbarattrs
= []
620 def __init__(self
, size
=0.1*unit
.v_cm
,
624 self
.errorbarattrs
= errorbarattrs
625 self
.epsilon
= epsilon
627 def columns(self
, privatedata
, sharedata
, graph
, columns
):
628 for i
in sharedata
.vposmissing
:
629 if i
in sharedata
.vrangemissing
:
630 raise ValueError("position and range for a graph dimension missing")
633 def selectstyle(self
, privatedata
, sharedata
, graph
, selectindex
, selecttotal
):
634 privatedata
.errorsize_pt
= unit
.topt(attr
.selectattr(self
.size
, selectindex
, selecttotal
))
635 privatedata
.errorbarattrs
= attr
.selectattrs(self
.defaulterrorbarattrs
+ self
.errorbarattrs
, selectindex
, selecttotal
)
637 def initdrawpoints(self
, privatedata
, sharedata
, graph
):
638 if privatedata
.errorbarattrs
is not None:
639 privatedata
.errorbarcanvas
= graph
.insert(canvas
.canvas())
640 privatedata
.errorbarcanvas
.set(privatedata
.errorbarattrs
)
641 privatedata
.dimensionlist
= range(len(sharedata
.vpos
))
643 def drawpoint(self
, privatedata
, sharedata
, graph
):
644 if privatedata
.errorbarattrs
is not None:
645 for i
in privatedata
.dimensionlist
:
646 for j
in privatedata
.dimensionlist
:
648 (sharedata
.vpos
[j
] is None or
649 sharedata
.vpos
[j
] < -self
.epsilon
or
650 sharedata
.vpos
[j
] > 1+self
.epsilon
)):
653 if ((sharedata
.vrange
[i
][0] is None and sharedata
.vpos
[i
] is None) or
654 (sharedata
.vrange
[i
][1] is None and sharedata
.vpos
[i
] is None) or
655 (sharedata
.vrange
[i
][0] is None and sharedata
.vrange
[i
][1] is None)):
657 vminpos
= sharedata
.vpos
[:]
658 if sharedata
.vrange
[i
][0] is not None:
659 vminpos
[i
] = sharedata
.vrange
[i
][0]
663 if vminpos
[i
] > 1+self
.epsilon
:
665 if vminpos
[i
] < -self
.epsilon
:
668 vmaxpos
= sharedata
.vpos
[:]
669 if sharedata
.vrange
[i
][1] is not None:
670 vmaxpos
[i
] = sharedata
.vrange
[i
][1]
674 if vmaxpos
[i
] < -self
.epsilon
:
676 if vmaxpos
[i
] > 1+self
.epsilon
:
679 privatedata
.errorbarcanvas
.stroke(graph
.vgeodesic(*(vminpos
+ vmaxpos
)))
680 for j
in privatedata
.dimensionlist
:
683 privatedata
.errorbarcanvas
.stroke(graph
.vcap_pt(j
, privatedata
.errorsize_pt
, *vminpos
))
685 privatedata
.errorbarcanvas
.stroke(graph
.vcap_pt(j
, privatedata
.errorsize_pt
, *vmaxpos
))
688 class text(_styleneedingpointpos
):
690 needsdata
= ["vpos", "vposmissing", "vposvalid"]
692 defaulttextattrs
= [textmodule
.halign
.center
, textmodule
.vshift
.mathaxis
]
694 def __init__(self
, textdx
=0*unit
.v_cm
, textdy
=0.3*unit
.v_cm
, textattrs
=[], **kwargs
):
697 self
.textattrs
= textattrs
699 def columns(self
, privatedata
, sharedata
, graph
, columns
):
700 if "text" not in columns
:
701 raise ValueError("text missing")
702 return ["text"] + _styleneedingpointpos
.columns(self
, privatedata
, sharedata
, graph
, columns
)
704 def selectstyle(self
, privatedata
, sharedata
, graph
, selectindex
, selecttotal
):
705 if self
.textattrs
is not None:
706 privatedata
.textattrs
= attr
.selectattrs(self
.defaulttextattrs
+ self
.textattrs
, selectindex
, selecttotal
)
708 privatedata
.textattrs
= None
710 def initdrawpoints(self
, privatedata
, sharedata
, grap
):
711 privatedata
.textdx_pt
= unit
.topt(self
.textdx
)
712 privatedata
.textdy_pt
= unit
.topt(self
.textdy
)
714 def drawpoint(self
, privatedata
, sharedata
, graph
):
715 if privatedata
.textattrs
is not None and sharedata
.vposvalid
:
716 x_pt
, y_pt
= graph
.vpos_pt(*sharedata
.vpos
)
718 text
= str(sharedata
.point
["text"])
722 graph
.text_pt(x_pt
+ privatedata
.textdx_pt
, y_pt
+ privatedata
.textdy_pt
, text
, privatedata
.textattrs
)
725 class arrow(_styleneedingpointpos
):
727 needsdata
= ["vpos", "vposmissing", "vposvalid"]
729 defaultlineattrs
= []
730 defaultarrowattrs
= []
732 def __init__(self
, linelength
=0.25*unit
.v_cm
, arrowsize
=0.15*unit
.v_cm
, lineattrs
=[], arrowattrs
=[], epsilon
=1e-10):
733 self
.linelength
= linelength
734 self
.arrowsize
= arrowsize
735 self
.lineattrs
= lineattrs
736 self
.arrowattrs
= arrowattrs
737 self
.epsilon
= epsilon
739 def columns(self
, privatedata
, sharedata
, graph
, columns
):
740 if len(graph
.axesnames
) != 2:
741 raise ValueError("arrow style restricted on two-dimensional graphs")
742 if "size" not in columns
:
743 raise ValueError("size missing")
744 if "angle" not in columns
:
745 raise ValueError("angle missing")
746 return ["size", "angle"] + _styleneedingpointpos
.columns(self
, privatedata
, sharedata
, graph
, columns
)
748 def selectstyle(self
, privatedata
, sharedata
, graph
, selectindex
, selecttotal
):
749 if self
.lineattrs
is not None:
750 privatedata
.lineattrs
= attr
.selectattrs(self
.defaultlineattrs
+ self
.lineattrs
, selectindex
, selecttotal
)
752 privatedata
.lineattrs
= None
753 if self
.arrowattrs
is not None:
754 privatedata
.arrowattrs
= attr
.selectattrs(self
.defaultarrowattrs
+ self
.arrowattrs
, selectindex
, selecttotal
)
756 privatedata
.arrowattrs
= None
758 def initdrawpoints(self
, privatedata
, sharedata
, graph
):
759 privatedata
.arrowcanvas
= graph
.insert(canvas
.canvas())
761 def drawpoint(self
, privatedata
, sharedata
, graph
):
762 if privatedata
.lineattrs
is not None and privatedata
.arrowattrs
is not None and sharedata
.vposvalid
:
763 linelength_pt
= unit
.topt(self
.linelength
)
764 x_pt
, y_pt
= graph
.vpos_pt(*sharedata
.vpos
)
766 angle
= sharedata
.point
["angle"] + 0.0
767 size
= sharedata
.point
["size"] + 0.0
771 if sharedata
.point
["size"] > self
.epsilon
:
772 dx
= math
.cos(angle
*math
.pi
/180)
773 dy
= math
.sin(angle
*math
.pi
/180)
774 x1
= x_pt
-0.5*dx
*linelength_pt
*size
775 y1
= y_pt
-0.5*dy
*linelength_pt
*size
776 x2
= x_pt
+0.5*dx
*linelength_pt
*size
777 y2
= y_pt
+0.5*dy
*linelength_pt
*size
778 privatedata
.arrowcanvas
.stroke(path
.line_pt(x1
, y1
, x2
, y2
), privatedata
.lineattrs
+
779 [deco
.earrow(privatedata
.arrowattrs
, size
=self
.arrowsize
*size
)])
781 def key_pt(self
, privatedata
, sharedata
, graph
, x_pt
, y_pt
, width_pt
, height_pt
, dy_pt
, selectindex
, selecttotal
):
787 needsdata
= ["vrange", "vrangeminmissing", "vrangemaxmissing"]
789 def __init__(self
, palette
=color
.palette
.Gray
):
790 self
.palette
= palette
792 def columns(self
, privatedata
, sharedata
, graph
, columns
):
793 if len(graph
.axesnames
) != 2:
794 raise TypeError("arrow style restricted on two-dimensional graphs")
795 if "color" not in columns
:
796 raise ValueError("color missing")
797 if len(sharedata
.vrangeminmissing
) + len(sharedata
.vrangemaxmissing
):
798 raise ValueError("range columns incomplete")
801 def initdrawpoints(self
, privatedata
, sharedata
, graph
):
802 privatedata
.rectcanvas
= graph
.insert(canvas
.canvas())
803 privatedata
.lastcolorvalue
= None
805 def drawpoint(self
, privatedata
, sharedata
, graph
):
806 xvmin
= sharedata
.vrange
[0][0]
807 xvmax
= sharedata
.vrange
[0][1]
808 yvmin
= sharedata
.vrange
[1][0]
809 yvmax
= sharedata
.vrange
[1][1]
810 if (xvmin
is not None and xvmin
< 1 and
811 xvmax
is not None and xvmax
> 0 and
812 yvmin
is not None and yvmin
< 1 and
813 yvmax
is not None and yvmax
> 0):
822 p
= graph
.vgeodesic(xvmin
, yvmin
, xvmax
, yvmin
)
823 p
.append(graph
.vgeodesic_el(xvmax
, yvmin
, xvmax
, yvmax
))
824 p
.append(graph
.vgeodesic_el(xvmax
, yvmax
, xvmin
, yvmax
))
825 p
.append(graph
.vgeodesic_el(xvmin
, yvmax
, xvmin
, yvmin
))
826 p
.append(path
.closepath())
827 colorvalue
= sharedata
.point
["color"]
829 if colorvalue
!= privatedata
.lastcolorvalue
:
830 privatedata
.rectcanvas
.set([self
.palette
.getcolor(colorvalue
)])
834 privatedata
.rectcanvas
.fill(p
)
836 def key_pt(self
, privatedata
, sharedata
, graph
, x_pt
, y_pt
, width_pt
, height_pt
, dy_pt
, selectindex
, selecttotal
):
840 class barpos(_style
):
842 providesdata
= ["vpos", "vposmissing", "vposavailable", "vposvalid", "vbarrange", "barcolumns", "barvalueindex", "stackedbar"]
844 defaultfrompathattrs
= []
846 def __init__(self
, fromvalue
=None, frompathattrs
=[], subindex
=0, subnames
=None, epsilon
=1e-10):
847 # NOTE subindex is a perspective for higher dimensional plots
848 # (just ignore it for the moment -- we don't even need to document about it)
849 self
.fromvalue
= fromvalue
850 self
.frompathattrs
= frompathattrs
851 self
.subindex
= subindex
852 self
.subnames
= subnames
853 self
.epsilon
= epsilon
855 def columns(self
, privatedata
, sharedata
, graph
, columns
):
856 sharedata
.barcolumns
= []
857 sharedata
.barvalueindex
= None
858 for dimension
, axisnames
in enumerate(graph
.axesnames
):
860 for axisname
in axisnames
:
861 if axisname
in columns
:
862 if sharedata
.barvalueindex
is not None:
863 raise ValueError("multiple values")
864 sharedata
.barvalueindex
= dimension
865 sharedata
.barcolumns
.append(axisname
)
867 if (axisname
+ "name") in columns
:
868 sharedata
.barcolumns
.append(axisname
+ "name")
871 raise ValueError("multiple names")
873 raise ValueError("value/name missing")
874 if sharedata
.barvalueindex
is None:
875 raise ValueError("missing value")
876 if self
.subindex
>= sharedata
.barvalueindex
:
877 privatedata
.barpossubindex
= self
.subindex
+ 1
879 privatedata
.barpossubindex
= self
.subindex
880 sharedata
.vposmissing
= []
881 return sharedata
.barcolumns
883 def selectstyle(self
, privatedata
, sharedata
, graph
, selectindex
, selecttotal
):
885 if self
.subnames
is not None:
886 raise ValueError("subnames set for single-bar data")
887 privatedata
.barpossubname
= []
889 if self
.subnames
is not None:
890 privatedata
.barpossubname
= [self
.subnames
[selectindex
]]
892 privatedata
.barpossubname
= [selectindex
]
894 def adjustaxis(self
, privatedata
, sharedata
, graph
, column
, data
, index
):
896 i
= sharedata
.barcolumns
.index(column
)
900 if i
== sharedata
.barvalueindex
:
901 if self
.fromvalue
is not None:
902 graph
.axes
[sharedata
.barcolumns
[i
]].adjustrange([self
.fromvalue
], None)
903 graph
.axes
[sharedata
.barcolumns
[i
]].adjustrange(data
, index
)
905 if i
== privatedata
.barpossubindex
:
906 graph
.axes
[sharedata
.barcolumns
[i
][:-4]].adjustrange(data
, index
, privatedata
.barpossubname
)
908 graph
.axes
[sharedata
.barcolumns
[i
][:-4]].adjustrange(data
, index
)
910 def initdrawpoints(self
, privatedata
, sharedata
, graph
):
911 sharedata
.vpos
= [None]*(len(sharedata
.barcolumns
))
912 sharedata
.vbarrange
= [[None for i
in range(2)] for x
in sharedata
.barcolumns
]
913 sharedata
.stackedbar
= 0
915 if self
.fromvalue
is not None:
916 privatedata
.vfromvalue
= graph
.axes
[sharedata
.barcolumns
[sharedata
.barvalueindex
][0]].convert(self
.fromvalue
)
917 if privatedata
.vfromvalue
< 0:
918 privatedata
.vfromvalue
= 0
919 if privatedata
.vfromvalue
> 1:
920 privatedata
.vfromvalue
= 1
921 if self
.frompathattrs
is not None:
923 graph
.stroke(graph
.axespos
[sharedata
.barcolumns
[sharedata
.barvalueindex
][0]].vgridpath(privatedata
.vfromvalue
),
924 self
.defaultfrompathattrs
+ self
.frompathattrs
)
926 privatedata
.vfromvalue
= 0
928 def drawpoint(self
, privatedata
, sharedata
, graph
):
929 sharedata
.vposavailable
= sharedata
.vposvalid
= 1
930 for i
, barname
in enumerate(sharedata
.barcolumns
):
931 if i
== sharedata
.barvalueindex
:
932 sharedata
.vbarrange
[i
][0] = privatedata
.vfromvalue
934 sharedata
.vpos
[i
] = sharedata
.vbarrange
[i
][1] = graph
.axes
[barname
].convert(sharedata
.point
[barname
])
935 except (ArithmeticError, ValueError, TypeError):
936 sharedata
.vpos
[i
] = sharedata
.vbarrange
[i
][1] = None
940 if i
== privatedata
.barpossubindex
:
941 sharedata
.vbarrange
[i
][j
] = graph
.axes
[barname
[:-4]].convert(([sharedata
.point
[barname
]] + privatedata
.barpossubname
+ [j
]))
943 sharedata
.vbarrange
[i
][j
] = graph
.axes
[barname
[:-4]].convert((sharedata
.point
[barname
], j
))
944 except (ArithmeticError, ValueError, TypeError):
945 sharedata
.vbarrange
[i
][j
] = None
947 sharedata
.vpos
[i
] = 0.5*(sharedata
.vbarrange
[i
][0]+sharedata
.vbarrange
[i
][1])
948 except (ArithmeticError, ValueError, TypeError):
949 sharedata
.vpos
[i
] = None
950 if sharedata
.vpos
[i
] is None:
951 sharedata
.vposavailable
= sharedata
.vposvalid
= 0
952 elif sharedata
.vpos
[i
] < -self
.epsilon
or sharedata
.vpos
[i
] > 1+self
.epsilon
:
953 sharedata
.vposvalid
= 0
955 registerdefaultprovider(barpos(), ["vbarrange", "barcolumns", "barvalueindex"])
958 class stackedbarpos(_style
):
960 # provides no additional data, but needs some data (and modifies some of them)
961 needsdata
= ["vbarrange", "barcolumns", "barvalueindex"]
963 def __init__(self
, stackname
, epsilon
=1e-10):
964 self
.stackname
= stackname
965 self
.epsilon
= epsilon
967 def columns(self
, privatedata
, sharedata
, graph
, columns
):
968 if self
.stackname
not in columns
:
969 raise ValueError("stackname column missing")
970 return [self
.stackname
]
972 def adjustaxis(self
, privatedata
, sharedata
, graph
, column
, data
, index
):
973 if column
== self
.stackname
:
974 graph
.axes
[sharedata
.barcolumns
[sharedata
.barvalueindex
]].adjustrange(data
, index
)
976 def drawpoint(self
, privatedata
, sharedata
, graph
):
977 sharedata
.vbarrange
[sharedata
.barvalueindex
][0] = sharedata
.vbarrange
[sharedata
.barvalueindex
][1]
979 sharedata
.vpos
[sharedata
.barvalueindex
] = sharedata
.vbarrange
[sharedata
.barvalueindex
][1] = graph
.axes
[sharedata
.barcolumns
[sharedata
.barvalueindex
]].convert(sharedata
.point
[self
.stackname
])
980 except (ArithmeticError, ValueError, TypeError):
981 sharedata
.vpos
[sharedata
.barvalueindex
] = sharedata
.vbarrange
[sharedata
.barvalueindex
][1] = None
982 sharedata
.vposavailable
= sharedata
.vposvalid
= 0
984 if not sharedata
.vposavailable
or not sharedata
.vposvalid
:
985 sharedata
.vposavailable
= sharedata
.vposvalid
= 1
986 for v
in sharedata
.vpos
:
988 sharedata
.vposavailable
= sharedata
.vposvalid
= 0
990 if v
< -self
.epsilon
or v
> 1+self
.epsilon
:
991 sharedata
.vposvalid
= 0
996 needsdata
= ["vbarrange"]
998 defaultbarattrs
= [color
.palette
.Rainbow
, deco
.stroked([color
.gray
.black
])]
1000 def __init__(self
, barattrs
=[]):
1001 self
.barattrs
= barattrs
1003 def columns(self
, privatedata
, sharedata
, graph
, columns
):
1004 if len(graph
.axesnames
) != 2:
1005 raise TypeError("bar style restricted on two-dimensional graphs")
1008 def selectstyle(self
, privatedata
, sharedata
, graph
, selectindex
, selecttotal
):
1010 privatedata
.barattrs
= attr
.selectattrs(self
.defaultbarattrs
+ self
.barattrs
, selectindex
, selecttotal
)
1012 privatedata
.barattrs
= self
.defaultbarattrs
+ self
.barattrs
1014 def initdrawpoints(self
, privatedata
, sharedata
, graph
):
1015 privatedata
.rectcanvas
= graph
.insert(canvas
.canvas())
1017 def drawpoint(self
, privatedata
, sharedata
, graph
):
1018 xvmin
= sharedata
.vbarrange
[0][0]
1019 xvmax
= sharedata
.vbarrange
[0][1]
1020 yvmin
= sharedata
.vbarrange
[1][0]
1021 yvmax
= sharedata
.vbarrange
[1][1]
1022 if None not in [xvmin
, xvmax
, yvmin
, yvmax
]:
1024 p
= graph
.vgeodesic(xvmin
, yvmin
, xvmax
, yvmin
)
1025 p
.append(graph
.vgeodesic_el(xvmax
, yvmin
, xvmax
, yvmax
))
1026 p
.append(graph
.vgeodesic_el(xvmax
, yvmax
, xvmin
, yvmax
))
1027 p
.append(graph
.vgeodesic_el(xvmin
, yvmax
, xvmin
, yvmin
))
1028 p
.append(path
.closepath())
1029 privatedata
.rectcanvas
.fill(p
, privatedata
.barattrs
)
1031 def key_pt(self
, privatedata
, sharedata
, graph
, x_pt
, y_pt
, width_pt
, height_pt
, dy_pt
, selectindex
, selecttotal
):