1 # -*- encoding: utf-8 -*-
4 # Copyright (C) 2002-2012 Jörg Lehmann <joergl@users.sourceforge.net>
5 # Copyright (C) 2003-2004 Michael Schindler <m-schindler@users.sourceforge.net>
6 # Copyright (C) 2002-2012 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
25 import io
, logging
, math
26 from pyx
import attr
, deco
, bitmap
, style
, color
, unit
, canvas
, path
, mesh
, trafo
27 from pyx
import text
as textmodule
28 from .graph
import registerdefaultprovider
, graphx
32 logger
= logging
.getLogger("pyx")
36 """Interface class for graph styles
38 Each graph style must support the methods described in this
39 class. However, since a graph style might not need to perform
40 actions on all the various events, it does not need to overwrite
41 all methods of this base class (e.g. this class is not an abstract
42 class in any respect).
44 A style should never store private data by instance variables
45 (i.e. accessing self), but it should use the sharedata and privatedata
46 instances instead. A style instance can be used multiple times with
47 different sharedata and privatedata instances at the very same time.
48 The sharedata and privatedata instances act as data containers and
49 sharedata allows for sharing information across several styles.
51 Every style contains two class variables, which are not to be
53 - providesdata is a list of variable names a style offers via
54 the sharedata instance. This list is used to determine whether
55 all needs of subsequent styles are fulfilled. Otherwise
56 getdefaultprovider should return a proper style to be used.
57 - needsdata is a list of variable names the style needs to access in the
61 providesdata
= [] # by default, we provide nothing
62 needsdata
= [] # and do not depend on anything
64 def columnnames(self
, privatedata
, sharedata
, graph
, columnnames
, dataaxisnames
):
65 """Set column information
67 This method is used setup the column name information to be
68 accessible to the style later on. The style should analyse
69 the list of column names. The method should return a list of
70 column names which the style will make use of. If a style
71 uses some column data to feed into an axis with a different
72 name, it should add an entry into the dataaxisnames dictionary
73 with key begin the column name and the value being the axis
77 def adjustaxis(self
, privatedata
, sharedata
, graph
, plotitem
, columnname
, data
):
80 This method is called in order to adjust the axis range to
81 the provided data. columnname is the column name (each style
82 is subsequently called for all column names)."""
85 def selectstyle(self
, privatedata
, sharedata
, graph
, selectindex
, selecttotal
):
86 """Select stroke/fill attributes
88 This method is called to allow for the selection of
89 changable attributes of a style."""
92 def initdrawpoints(self
, privatedata
, sharedata
, graph
):
93 """Initialize drawing of data
95 This method might be used to initialize the drawing of data."""
98 def drawpoint(self
, privatedata
, sharedata
, graph
, point
):
101 This method is called for each data point. The data is
102 available in the dictionary point. The dictionary
103 keys are the column names."""
106 def donedrawpoints(self
, privatedata
, sharedata
, graph
):
107 """Finalize drawing of data
109 This method is called after the last data point was
110 drawn using the drawpoint method above."""
113 def key_pt(self
, privatedata
, sharedata
, graph
, x_pt
, y_pt
, width_pt
, height_pt
):
119 class _autokeygraph
: pass
122 class _keygraphstyle(_style
):
124 autographkey
= _autokeygraph
126 def __init__(self
, colorname
="color", gradient
=color
.gradient
.Grey
, coloraxis
=None, keygraph
=_autokeygraph
):
127 self
.colorname
= colorname
128 self
.gradient
= gradient
129 self
.coloraxis
= coloraxis
130 self
.keygraph
= keygraph
132 def columnnames(self
, privatedata
, sharedata
, graph
, columnnames
, dataaxisnames
):
133 return [self
.colorname
]
135 def adjustaxis(self
, privatedata
, sharedata
, graph
, plotitem
, columnname
, data
):
136 if columnname
== self
.colorname
:
137 if self
.keygraph
is None:
138 # we always need a keygraph, but we might not show it
139 if self
.coloraxis
is None:
140 coloraxis
= axis
.lin()
142 coloraxis
= self
.coloraxis
143 privatedata
.keygraph
= graphx(length
=10, direction
="vertical", x
=coloraxis
)
144 elif self
.keygraph
is _autokeygraph
:
145 if self
.coloraxis
is None:
146 coloraxis
= axis
.lin(title
=plotitem
.title
)
147 plotitem
.title
= None # Huui!?
149 coloraxis
= self
.coloraxis
150 privatedata
.keygraph
= graphx(x
=coloraxis
, **graph
.autokeygraphattrs())
152 privatedata
.keygraph
= self
.keygraph
153 # TODO: we shouldn't have multiple plotitems
154 from . import data
as datamodule
155 privatedata
.keygraph
.plot(datamodule
.values(x
=data
), [gradient(gradient
=self
.gradient
)])
157 def color(self
, privatedata
, c
):
158 vc
= privatedata
.keygraph
.axes
["x"].convert(c
)
160 logger
.warning("gradient color range is exceeded (lower bound)")
163 logger
.warning("gradient color range is exceeded (upper bound)")
165 return self
.gradient
.getcolor(vc
)
167 def donedrawpoints(self
, privatedata
, sharedata
, graph
):
168 if self
.keygraph
is _autokeygraph
:
169 graph
.layer("key").insert(privatedata
.keygraph
, [graph
.autokeygraphtrafo(privatedata
.keygraph
)])
174 providesdata
= ["vpos", "vposmissing", "vposavailable", "vposvalid", "poscolumnnames"]
176 def __init__(self
, usenames
={}, epsilon
=1e-10):
177 self
.usenames
= usenames
178 self
.epsilon
= epsilon
180 def columnnames(self
, privatedata
, sharedata
, graph
, columnnames
, dataaxisnames
):
181 privatedata
.poscolumnnames
= []
182 privatedata
.vposmissing
= []
183 privatedata
.axisnames
= {}
184 for count
, axisnames
in enumerate(graph
.axesnames
):
185 for axisname
in axisnames
:
187 usename
= self
.usenames
[axisname
]
190 for columnname
in columnnames
:
191 if usename
== columnname
:
192 privatedata
.poscolumnnames
.append(columnname
)
193 privatedata
.axisnames
[columnname
] = axisname
194 if len(privatedata
.poscolumnnames
) > count
+1:
195 raise ValueError("multiple axes per graph dimension")
196 elif len(privatedata
.poscolumnnames
) < count
+1:
197 privatedata
.vposmissing
.append(count
)
198 privatedata
.poscolumnnames
.append(None)
199 # Make poscolumnnames and vposmissing available to the outside,
200 # but keep a private reference. A copy is not needed, because
201 # the data is not altered in place, but might be exchanged my a
202 # later, different pos style in the styles list (due to different
204 sharedata
.poscolumnnames
= privatedata
.poscolumnnames
205 sharedata
.vposmissing
= privatedata
.vposmissing
206 dataaxisnames
.update(privatedata
.axisnames
)
207 return [columnname
for columnname
in privatedata
.poscolumnnames
if columnname
is not None]
209 def adjustaxis(self
, privatedata
, sharedata
, graph
, plotitem
, columnname
, data
):
210 if columnname
in privatedata
.axisnames
:
211 graph
.axes
[privatedata
.axisnames
[columnname
]].adjustaxis(data
)
213 def initdrawpoints(self
, privatedata
, sharedata
, graph
):
214 sharedata
.vpos
= [None]*(len(graph
.axesnames
))
215 privatedata
.pointpostmplist
= [[columnname
, index
, graph
.axes
[privatedata
.axisnames
[columnname
]]] # temporarily used by drawpoint only
216 for index
, columnname
in enumerate([columnname
for columnname
in privatedata
.poscolumnnames
if columnname
is not None])]
217 for missing
in privatedata
.vposmissing
:
218 for pointpostmp
in privatedata
.pointpostmplist
:
219 if pointpostmp
[1] >= missing
:
222 def drawpoint(self
, privatedata
, sharedata
, graph
, point
):
223 sharedata
.vposavailable
= 1 # valid position (but might be outside of the graph)
224 sharedata
.vposvalid
= 1 # valid position inside the graph
225 for columnname
, index
, axis
in privatedata
.pointpostmplist
:
227 v
= axis
.convert(point
[columnname
])
228 except (ArithmeticError, ValueError, TypeError):
229 sharedata
.vposavailable
= sharedata
.vposvalid
= 0
230 sharedata
.vpos
[index
] = None
232 if v
< -self
.epsilon
or v
> 1+self
.epsilon
:
233 sharedata
.vposvalid
= 0
234 sharedata
.vpos
[index
] = v
237 registerdefaultprovider(pos(), pos
.providesdata
)
242 providesdata
= ["vrange", "vrangemissing", "vrangeminmissing", "vrangemaxmissing"]
252 def __init__(self
, usenames
={}, epsilon
=1e-10):
253 self
.usenames
= usenames
254 self
.epsilon
= epsilon
256 def _numberofbits(self
, mask
):
260 return self
._numberofbits
(mask
>> 1) + 1
262 return self
._numberofbits
(mask
>> 1)
264 def columnnames(self
, privatedata
, sharedata
, graph
, columnnames
, dataaxisnames
):
266 privatedata
.rangeposcolumns
= []
267 sharedata
.vrangemissing
= []
268 sharedata
.vrangeminmissing
= []
269 sharedata
.vrangemaxmissing
= []
270 privatedata
.rangeposdeltacolumns
= {} # temporarily used by adjustaxis only
271 for count
, axisnames
in enumerate(graph
.axesnames
):
272 for axisname
in axisnames
:
274 usename
= self
.usenames
[axisname
]
278 for columnname
in columnnames
:
280 if usename
== columnname
:
281 mask
+= self
.mask_value
282 elif usename
+ "min" == columnname
:
283 mask
+= self
.mask_min
284 elif usename
+ "max" == columnname
:
285 mask
+= self
.mask_max
286 elif "d" + usename
+ "min" == columnname
:
287 mask
+= self
.mask_dmin
288 elif "d" + usename
+ "max" == columnname
:
289 mask
+= self
.mask_dmax
290 elif "d" + usename
== columnname
:
295 usecolumns
.append(columnname
)
296 dataaxisnames
[columnname
] = axisname
297 if mask
& (self
.mask_min | self
.mask_max | self
.mask_dmin | self
.mask_dmax | self
.mask_d
):
298 if (self
._numberofbits
(mask
& (self
.mask_min | self
.mask_dmin | self
.mask_d
)) > 1 or
299 self
._numberofbits
(mask
& (self
.mask_max | self
.mask_dmax | self
.mask_d
)) > 1):
300 raise ValueError("multiple range definition")
301 if mask
& (self
.mask_dmin | self
.mask_dmax | self
.mask_d
):
302 if not (mask
& self
.mask_value
):
303 raise ValueError("missing value for delta")
304 privatedata
.rangeposdeltacolumns
[axisname
] = {}
305 privatedata
.rangeposcolumns
.append((axisname
, usename
, mask
))
306 elif mask
== self
.mask_value
:
307 usecolumns
= usecolumns
[:-1]
308 if len(privatedata
.rangeposcolumns
) + len(sharedata
.vrangemissing
) > count
+1:
309 raise ValueError("multiple axes per graph dimension")
310 elif len(privatedata
.rangeposcolumns
) + len(sharedata
.vrangemissing
) < count
+1:
311 sharedata
.vrangemissing
.append(count
)
312 sharedata
.vrangeminmissing
.append(count
)
313 sharedata
.vrangemaxmissing
.append(count
)
315 if not (privatedata
.rangeposcolumns
[-1][2] & (self
.mask_min | self
.mask_dmin | self
.mask_d
)):
316 sharedata
.vrangeminmissing
.append(count
)
317 if not (privatedata
.rangeposcolumns
[-1][2] & (self
.mask_max | self
.mask_dmax | self
.mask_d
)):
318 sharedata
.vrangemaxmissing
.append(count
)
321 def adjustaxis(self
, privatedata
, sharedata
, graph
, plotitem
, columnname
, data
):
322 for axisname
, usename
, mask
in privatedata
.rangeposcolumns
:
323 if columnname
== usename
+ "min" and mask
& self
.mask_min
:
324 graph
.axes
[axisname
].adjustaxis(data
)
325 if columnname
== usename
+ "max" and mask
& self
.mask_max
:
326 graph
.axes
[axisname
].adjustaxis(data
)
328 # delta handling: fill rangeposdeltacolumns
329 for axisname
, usename
, mask
in privatedata
.rangeposcolumns
:
330 if columnname
== usename
and mask
& (self
.mask_dmin | self
.mask_dmax | self
.mask_d
):
331 privatedata
.rangeposdeltacolumns
[axisname
][self
.mask_value
] = data
332 if columnname
== "d" + usename
+ "min" and mask
& self
.mask_dmin
:
333 privatedata
.rangeposdeltacolumns
[axisname
][self
.mask_dmin
] = data
334 if columnname
== "d" + usename
+ "max" and mask
& self
.mask_dmax
:
335 privatedata
.rangeposdeltacolumns
[axisname
][self
.mask_dmax
] = data
336 if columnname
== "d" + usename
and mask
& self
.mask_d
:
337 privatedata
.rangeposdeltacolumns
[axisname
][self
.mask_d
] = data
339 # delta handling: process rangeposdeltacolumns
340 for a
, d
in list(privatedata
.rangeposdeltacolumns
.items()):
341 if self
.mask_value
in d
:
342 for k
in list(d
.keys()):
343 if k
!= self
.mask_value
:
344 if k
& (self
.mask_dmin | self
.mask_d
):
346 for value
, delta
in zip(d
[self
.mask_value
], d
[k
]):
348 mindata
.append(value
-delta
)
351 graph
.axes
[a
].adjustaxis(mindata
)
352 if k
& (self
.mask_dmax | self
.mask_d
):
354 for value
, delta
in zip(d
[self
.mask_value
], d
[k
]):
356 maxdata
.append(value
+delta
)
359 graph
.axes
[a
].adjustaxis(maxdata
)
362 def initdrawpoints(self
, privatedata
, sharedata
, graph
):
363 sharedata
.vrange
= [[None for x
in builtinrange(2)] for y
in privatedata
.rangeposcolumns
+ sharedata
.vrangemissing
]
364 privatedata
.rangepostmplist
= [[usename
, mask
, index
, graph
.axes
[axisname
]] # temporarily used by drawpoint only
365 for index
, (axisname
, usename
, mask
) in enumerate(privatedata
.rangeposcolumns
)]
366 for missing
in sharedata
.vrangemissing
:
367 for rangepostmp
in privatedata
.rangepostmplist
:
368 if rangepostmp
[2] >= missing
:
371 def drawpoint(self
, privatedata
, sharedata
, graph
, point
):
372 for usename
, mask
, index
, axis
in privatedata
.rangepostmplist
:
374 if mask
& self
.mask_min
:
375 sharedata
.vrange
[index
][0] = axis
.convert(point
[usename
+ "min"])
376 if mask
& self
.mask_dmin
:
377 sharedata
.vrange
[index
][0] = axis
.convert(point
[usename
] - point
["d" + usename
+ "min"])
378 if mask
& self
.mask_d
:
379 sharedata
.vrange
[index
][0] = axis
.convert(point
[usename
] - point
["d" + usename
])
380 except (ArithmeticError, ValueError, TypeError):
381 sharedata
.vrange
[index
][0] = None
383 if mask
& self
.mask_max
:
384 sharedata
.vrange
[index
][1] = axis
.convert(point
[usename
+ "max"])
385 if mask
& self
.mask_dmax
:
386 sharedata
.vrange
[index
][1] = axis
.convert(point
[usename
] + point
["d" + usename
+ "max"])
387 if mask
& self
.mask_d
:
388 sharedata
.vrange
[index
][1] = axis
.convert(point
[usename
] + point
["d" + usename
])
389 except (ArithmeticError, ValueError, TypeError):
390 sharedata
.vrange
[index
][1] = None
394 registerdefaultprovider(range(), range.providesdata
)
397 def _crosssymbol(c
, x_pt
, y_pt
, size_pt
, attrs
):
398 c
.draw(path
.path(path
.moveto_pt(x_pt
-0.5*size_pt
, y_pt
-0.5*size_pt
),
399 path
.lineto_pt(x_pt
+0.5*size_pt
, y_pt
+0.5*size_pt
),
400 path
.moveto_pt(x_pt
-0.5*size_pt
, y_pt
+0.5*size_pt
),
401 path
.lineto_pt(x_pt
+0.5*size_pt
, y_pt
-0.5*size_pt
)), attrs
)
403 def _plussymbol(c
, x_pt
, y_pt
, size_pt
, attrs
):
404 c
.draw(path
.path(path
.moveto_pt(x_pt
-0.707106781*size_pt
, y_pt
),
405 path
.lineto_pt(x_pt
+0.707106781*size_pt
, y_pt
),
406 path
.moveto_pt(x_pt
, y_pt
-0.707106781*size_pt
),
407 path
.lineto_pt(x_pt
, y_pt
+0.707106781*size_pt
)), attrs
)
409 def _squaresymbol(c
, x_pt
, y_pt
, size_pt
, attrs
):
410 c
.draw(path
.path(path
.moveto_pt(x_pt
-0.5*size_pt
, y_pt
-0.5*size_pt
),
411 path
.lineto_pt(x_pt
+0.5*size_pt
, y_pt
-0.5*size_pt
),
412 path
.lineto_pt(x_pt
+0.5*size_pt
, y_pt
+0.5*size_pt
),
413 path
.lineto_pt(x_pt
-0.5*size_pt
, y_pt
+0.5*size_pt
),
414 path
.closepath()), attrs
)
416 def _trianglesymbol(c
, x_pt
, y_pt
, size_pt
, attrs
):
417 c
.draw(path
.path(path
.moveto_pt(x_pt
-0.759835685*size_pt
, y_pt
-0.438691337*size_pt
),
418 path
.lineto_pt(x_pt
+0.759835685*size_pt
, y_pt
-0.438691337*size_pt
),
419 path
.lineto_pt(x_pt
, y_pt
+0.877382675*size_pt
),
420 path
.closepath()), attrs
)
422 def _circlesymbol(c
, x_pt
, y_pt
, size_pt
, attrs
):
423 c
.draw(path
.path(path
.arc_pt(x_pt
, y_pt
, 0.564189583*size_pt
, 0, 360),
424 path
.closepath()), attrs
)
426 def _diamondsymbol(c
, x_pt
, y_pt
, size_pt
, attrs
):
427 c
.draw(path
.path(path
.moveto_pt(x_pt
-0.537284965*size_pt
, y_pt
),
428 path
.lineto_pt(x_pt
, y_pt
-0.930604859*size_pt
),
429 path
.lineto_pt(x_pt
+0.537284965*size_pt
, y_pt
),
430 path
.lineto_pt(x_pt
, y_pt
+0.930604859*size_pt
),
431 path
.closepath()), attrs
)
434 class _styleneedingpointpos(_style
):
436 needsdata
= ["vposmissing"]
438 def columnnames(self
, privatedata
, sharedata
, graph
, columnnames
, dataaxisnames
):
439 if len(sharedata
.vposmissing
):
440 raise ValueError("incomplete position information")
444 class symbol(_styleneedingpointpos
):
446 needsdata
= ["vpos", "vposmissing", "vposvalid"]
448 # "inject" the predefinied symbols into the class:
450 # Note, that statements like cross = _crosssymbol are
451 # invalid, since the would lead to unbound methods, but
452 # a single entry changeable list does the trick.
454 # Once we require Python 2.2+ we should use staticmethods
455 # to implement the default symbols inplace.
457 cross
= attr
.changelist([_crosssymbol
])
458 plus
= attr
.changelist([_plussymbol
])
459 square
= attr
.changelist([_squaresymbol
])
460 triangle
= attr
.changelist([_trianglesymbol
])
461 circle
= attr
.changelist([_circlesymbol
])
462 diamond
= attr
.changelist([_diamondsymbol
])
464 changecross
= attr
.changelist([_crosssymbol
, _plussymbol
, _squaresymbol
, _trianglesymbol
, _circlesymbol
, _diamondsymbol
])
465 changeplus
= attr
.changelist([_plussymbol
, _squaresymbol
, _trianglesymbol
, _circlesymbol
, _diamondsymbol
, _crosssymbol
])
466 changesquare
= attr
.changelist([_squaresymbol
, _trianglesymbol
, _circlesymbol
, _diamondsymbol
, _crosssymbol
, _plussymbol
])
467 changetriangle
= attr
.changelist([_trianglesymbol
, _circlesymbol
, _diamondsymbol
, _crosssymbol
, _plussymbol
, _squaresymbol
])
468 changecircle
= attr
.changelist([_circlesymbol
, _diamondsymbol
, _crosssymbol
, _plussymbol
, _squaresymbol
, _trianglesymbol
])
469 changediamond
= attr
.changelist([_diamondsymbol
, _crosssymbol
, _plussymbol
, _squaresymbol
, _trianglesymbol
, _circlesymbol
])
470 changesquaretwice
= attr
.changelist([_squaresymbol
, _squaresymbol
, _trianglesymbol
, _trianglesymbol
, _circlesymbol
, _circlesymbol
, _diamondsymbol
, _diamondsymbol
])
471 changetriangletwice
= attr
.changelist([_trianglesymbol
, _trianglesymbol
, _circlesymbol
, _circlesymbol
, _diamondsymbol
, _diamondsymbol
, _squaresymbol
, _squaresymbol
])
472 changecircletwice
= attr
.changelist([_circlesymbol
, _circlesymbol
, _diamondsymbol
, _diamondsymbol
, _squaresymbol
, _squaresymbol
, _trianglesymbol
, _trianglesymbol
])
473 changediamondtwice
= attr
.changelist([_diamondsymbol
, _diamondsymbol
, _squaresymbol
, _squaresymbol
, _trianglesymbol
, _trianglesymbol
, _circlesymbol
, _circlesymbol
])
475 changestrokedfilled
= attr
.changelist([deco
.stroked
, deco
.filled
])
476 changefilledstroked
= attr
.changelist([deco
.filled
, deco
.stroked
])
478 defaultsymbolattrs
= [deco
.stroked
]
480 def __init__(self
, symbol
=changecross
, size
=0.2*unit
.v_cm
, symbolattrs
=[]):
483 self
.symbolattrs
= symbolattrs
485 def selectstyle(self
, privatedata
, sharedata
, graph
, selectindex
, selecttotal
):
486 privatedata
.symbol
= attr
.selectattr(self
.symbol
, selectindex
, selecttotal
)
487 privatedata
.size_pt
= unit
.topt(attr
.selectattr(self
.size
, selectindex
, selecttotal
))
488 if self
.symbolattrs
is not None:
489 privatedata
.symbolattrs
= attr
.selectattrs(self
.defaultsymbolattrs
+ self
.symbolattrs
, selectindex
, selecttotal
)
491 privatedata
.symbolattrs
= None
493 def initdrawpoints(self
, privatedata
, sharedata
, graph
):
494 privatedata
.symbolcanvas
= canvas
.canvas()
496 def drawpoint(self
, privatedata
, sharedata
, graph
, point
):
497 if sharedata
.vposvalid
and privatedata
.symbolattrs
is not None:
498 x_pt
, y_pt
= graph
.vpos_pt(*sharedata
.vpos
)
499 privatedata
.symbol(privatedata
.symbolcanvas
, x_pt
, y_pt
, privatedata
.size_pt
, privatedata
.symbolattrs
)
501 def donedrawpoints(self
, privatedata
, sharedata
, graph
):
502 graph
.layer("data").insert(privatedata
.symbolcanvas
)
504 def key_pt(self
, privatedata
, sharedata
, graph
, x_pt
, y_pt
, width_pt
, height_pt
):
505 if privatedata
.symbolattrs
is not None:
506 privatedata
.symbol(graph
, x_pt
+0.5*width_pt
, y_pt
+0.5*height_pt
, privatedata
.size_pt
, privatedata
.symbolattrs
)
509 class _line(_styleneedingpointpos
):
511 # this style is not a complete style, but it provides the basic functionality to
512 # create a line, which is cut at the graph boundaries (or at otherwise invalid points)
514 def __init__(self
, epsilon
=1e-10):
515 self
.epsilon
= epsilon
517 def initpointstopath(self
, privatedata
):
518 privatedata
.path
= path
.path()
519 privatedata
.linebasepoints
= []
520 privatedata
.lastvpos
= None
522 def addpointstopath(self
, privatedata
):
523 # add baselinepoints to privatedata.path
524 if len(privatedata
.linebasepoints
) > 1:
525 privatedata
.path
.append(path
.moveto_pt(*privatedata
.linebasepoints
[0]))
526 if len(privatedata
.linebasepoints
) > 2:
527 privatedata
.path
.append(path
.multilineto_pt(privatedata
.linebasepoints
[1:]))
529 privatedata
.path
.append(path
.lineto_pt(*privatedata
.linebasepoints
[1]))
530 privatedata
.linebasepoints
= []
532 def addpoint(self
, privatedata
, graphvpos_pt
, vposavailable
, vposvalid
, vpos
):
533 # append linebasepoints
535 if len(privatedata
.linebasepoints
):
536 # the last point was inside the graph
537 if vposvalid
: # shortcut for the common case
538 privatedata
.linebasepoints
.append(graphvpos_pt(*vpos
))
542 for vstart
, vend
in zip(privatedata
.lastvpos
, vpos
):
543 if abs(vend
- vstart
) > self
.epsilon
:
546 # 1 = vstart + (vend - vstart) * cut
547 newcut
= (1 - vstart
)/(vend
- vstart
)
549 # 0 = vstart + (vend - vstart) * cut
550 newcut
= - vstart
/(vend
- vstart
)
551 if newcut
is not None and newcut
< cut
:
554 for vstart
, vend
in zip(privatedata
.lastvpos
, vpos
):
555 cutvpos
.append(vstart
+ (vend
- vstart
) * cut
)
556 privatedata
.linebasepoints
.append(graphvpos_pt(*cutvpos
))
557 self
.addpointstopath(privatedata
)
559 # the last point was outside the graph
560 if privatedata
.lastvpos
is not None:
564 for vstart
, vend
in zip(privatedata
.lastvpos
, vpos
):
565 if abs(vend
- vstart
) > self
.epsilon
:
568 # 1 = vstart + (vend - vstart) * cut
569 newcut
= (1 - vstart
)/(vend
- vstart
)
571 # 0 = vstart + (vend - vstart) * cut
572 newcut
= - vstart
/(vend
- vstart
)
573 if newcut
is not None and newcut
> cut
:
576 for vstart
, vend
in zip(privatedata
.lastvpos
, vpos
):
577 cutvpos
.append(vstart
+ (vend
- vstart
) * cut
)
578 privatedata
.linebasepoints
.append(graphvpos_pt(*cutvpos
))
579 privatedata
.linebasepoints
.append(graphvpos_pt(*vpos
))
581 # sometimes cut beginning and end
584 for vstart
, vend
in zip(privatedata
.lastvpos
, vpos
):
585 if vstart
> 1 and vend
> 1: break
586 if vstart
< 0 and vend
< 0: break
587 if abs(vend
- vstart
) > self
.epsilon
:
590 newcutfrom
= (1 - vstart
)/(vend
- vstart
)
592 # 0 = vstart + (vend - vstart) * cutfrom
593 newcutfrom
= - vstart
/(vend
- vstart
)
594 if newcutfrom
is not None and newcutfrom
> cutfrom
:
598 # 1 = vstart + (vend - vstart) * cutto
599 newcutto
= (1 - vstart
)/(vend
- vstart
)
601 # 0 = vstart + (vend - vstart) * cutto
602 newcutto
= - vstart
/(vend
- vstart
)
603 if newcutto
is not None and newcutto
< cutto
:
609 for vstart
, vend
in zip(privatedata
.lastvpos
, vpos
):
610 cutfromvpos
.append(vstart
+ (vend
- vstart
) * cutfrom
)
611 cuttovpos
.append(vstart
+ (vend
- vstart
) * cutto
)
612 privatedata
.linebasepoints
.append(graphvpos_pt(*cutfromvpos
))
613 privatedata
.linebasepoints
.append(graphvpos_pt(*cuttovpos
))
614 self
.addpointstopath(privatedata
)
615 privatedata
.lastvpos
= vpos
[:]
617 if len(privatedata
.linebasepoints
) > 1:
618 self
.addpointstopath(privatedata
)
619 privatedata
.lastvpos
= None
621 def addinvalid(self
, privatedata
):
622 if len(privatedata
.linebasepoints
) > 1:
623 self
.addpointstopath(privatedata
)
624 privatedata
.lastvpos
= None
626 def donepointstopath(self
, privatedata
):
627 if len(privatedata
.linebasepoints
) > 1:
628 self
.addpointstopath(privatedata
)
629 return privatedata
.path
634 needsdata
= ["vpos", "vposmissing", "vposavailable", "vposvalid"]
636 changelinestyle
= attr
.changelist([style
.linestyle
.solid
,
637 style
.linestyle
.dashed
,
638 style
.linestyle
.dotted
,
639 style
.linestyle
.dashdotted
])
641 defaultlineattrs
= [changelinestyle
]
643 def __init__(self
, lineattrs
=[], **kwargs
):
644 _line
.__init
__(self
, **kwargs
)
645 self
.lineattrs
= lineattrs
647 def selectstyle(self
, privatedata
, sharedata
, graph
, selectindex
, selecttotal
):
648 if self
.lineattrs
is not None:
649 privatedata
.lineattrs
= attr
.selectattrs(self
.defaultlineattrs
+ self
.lineattrs
, selectindex
, selecttotal
)
651 privatedata
.lineattrs
= None
653 def initdrawpoints(self
, privatedata
, sharedata
, graph
):
654 self
.initpointstopath(privatedata
)
656 def drawpoint(self
, privatedata
, sharedata
, graph
, point
):
657 self
.addpoint(privatedata
, graph
.vpos_pt
, sharedata
.vposavailable
, sharedata
.vposvalid
, sharedata
.vpos
)
659 def donedrawpoints(self
, privatedata
, sharedata
, graph
):
660 path
= self
.donepointstopath(privatedata
)
661 if privatedata
.lineattrs
is not None and len(path
):
662 graph
.layer("data").stroke(path
, privatedata
.lineattrs
)
664 def key_pt(self
, privatedata
, sharedata
, graph
, x_pt
, y_pt
, width_pt
, height_pt
):
665 if privatedata
.lineattrs
is not None:
666 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
)
669 class impulses(_styleneedingpointpos
):
671 needsdata
= ["vpos", "vposmissing", "vposavailable", "vposvalid", "poscolumnnames"]
673 defaultlineattrs
= [line
.changelinestyle
]
674 defaultfrompathattrs
= []
676 def __init__(self
, lineattrs
=[], fromvalue
=0, frompathattrs
=[], valueaxisindex
=1):
677 self
.lineattrs
= lineattrs
678 self
.fromvalue
= fromvalue
679 self
.frompathattrs
= frompathattrs
680 self
.valueaxisindex
= valueaxisindex
682 def selectstyle(self
, privatedata
, sharedata
, graph
, selectindex
, selecttotal
):
683 privatedata
.insertfrompath
= selectindex
== 0
684 if self
.lineattrs
is not None:
685 privatedata
.lineattrs
= attr
.selectattrs(self
.defaultlineattrs
+ self
.lineattrs
, selectindex
, selecttotal
)
687 privatedata
.lineattrs
= None
689 def adjustaxis(self
, privatedata
, sharedata
, graph
, plotitem
, columnname
, data
):
690 if self
.fromvalue
is not None:
692 i
= sharedata
.poscolumnnames
.index(columnname
)
696 if i
== self
.valueaxisindex
:
697 graph
.axes
[sharedata
.poscolumnnames
[i
]].adjustaxis([self
.fromvalue
])
699 def initdrawpoints(self
, privatedata
, sharedata
, graph
):
700 privatedata
.impulsescanvas
= canvas
.canvas()
701 if self
.fromvalue
is not None:
702 valueaxisname
= sharedata
.poscolumnnames
[self
.valueaxisindex
]
703 privatedata
.vfromvalue
= graph
.axes
[valueaxisname
].convert(self
.fromvalue
)
704 privatedata
.vfromvaluecut
= 0
705 if privatedata
.vfromvalue
< 0:
706 privatedata
.vfromvalue
= 0
707 if privatedata
.vfromvalue
> 1:
708 privatedata
.vfromvalue
= 1
709 if self
.frompathattrs
is not None and privatedata
.insertfrompath
:
710 graph
.layer("data").stroke(graph
.axes
[valueaxisname
].vgridpath(privatedata
.vfromvalue
),
711 self
.defaultfrompathattrs
+ self
.frompathattrs
)
713 privatedata
.vfromvalue
= 0
715 def drawpoint(self
, privatedata
, sharedata
, graph
, point
):
716 if sharedata
.vposvalid
and privatedata
.lineattrs
is not None:
717 vpos
= sharedata
.vpos
[:]
718 vpos
[self
.valueaxisindex
] = privatedata
.vfromvalue
719 privatedata
.impulsescanvas
.stroke(graph
.vgeodesic(*(vpos
+ sharedata
.vpos
)), privatedata
.lineattrs
)
721 def donedrawpoints(self
, privatedata
, sharedata
, graph
):
722 graph
.layer("data").insert(privatedata
.impulsescanvas
)
724 def key_pt(self
, privatedata
, sharedata
, graph
, x_pt
, y_pt
, width_pt
, height_pt
):
725 if privatedata
.lineattrs
is not None:
726 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
)
729 class errorbar(_style
):
731 needsdata
= ["vpos", "vposmissing", "vposavailable", "vposvalid", "vrange", "vrangeminmissing", "vrangemaxmissing"]
733 defaulterrorbarattrs
= []
735 def __init__(self
, size
=0.1*unit
.v_cm
,
739 self
.errorbarattrs
= errorbarattrs
740 self
.epsilon
= epsilon
742 def columnnames(self
, privatedata
, sharedata
, graph
, columnnames
, dataaxisnames
):
743 for i
in sharedata
.vposmissing
:
744 if i
in sharedata
.vrangeminmissing
and i
in sharedata
.vrangemaxmissing
:
745 raise ValueError("position and range for a graph dimension missing")
748 def selectstyle(self
, privatedata
, sharedata
, graph
, selectindex
, selecttotal
):
749 privatedata
.errorsize_pt
= unit
.topt(attr
.selectattr(self
.size
, selectindex
, selecttotal
))
750 privatedata
.errorbarattrs
= attr
.selectattrs(self
.defaulterrorbarattrs
+ self
.errorbarattrs
, selectindex
, selecttotal
)
752 def initdrawpoints(self
, privatedata
, sharedata
, graph
):
753 if privatedata
.errorbarattrs
is not None:
754 privatedata
.errorbarcanvas
= canvas
.canvas(privatedata
.errorbarattrs
)
755 privatedata
.dimensionlist
= list(builtinrange(len(sharedata
.vpos
)))
757 def drawpoint(self
, privatedata
, sharedata
, graph
, point
):
758 if privatedata
.errorbarattrs
is not None:
759 for i
in privatedata
.dimensionlist
:
760 for j
in privatedata
.dimensionlist
:
762 (sharedata
.vpos
[j
] is None or
763 sharedata
.vpos
[j
] < -self
.epsilon
or
764 sharedata
.vpos
[j
] > 1+self
.epsilon
)):
767 if ((sharedata
.vrange
[i
][0] is None and sharedata
.vpos
[i
] is None) or
768 (sharedata
.vrange
[i
][1] is None and sharedata
.vpos
[i
] is None) or
769 (sharedata
.vrange
[i
][0] is None and sharedata
.vrange
[i
][1] is None)):
771 vminpos
= sharedata
.vpos
[:]
772 if sharedata
.vrange
[i
][0] is not None:
773 vminpos
[i
] = sharedata
.vrange
[i
][0]
777 if vminpos
[i
] > 1+self
.epsilon
:
779 if vminpos
[i
] < -self
.epsilon
:
782 vmaxpos
= sharedata
.vpos
[:]
783 if sharedata
.vrange
[i
][1] is not None:
784 vmaxpos
[i
] = sharedata
.vrange
[i
][1]
788 if vmaxpos
[i
] < -self
.epsilon
:
790 if vmaxpos
[i
] > 1+self
.epsilon
:
793 privatedata
.errorbarcanvas
.stroke(graph
.vgeodesic(*(vminpos
+ vmaxpos
)))
794 for j
in privatedata
.dimensionlist
:
797 privatedata
.errorbarcanvas
.stroke(graph
.vcap_pt(j
, privatedata
.errorsize_pt
, *vminpos
))
799 privatedata
.errorbarcanvas
.stroke(graph
.vcap_pt(j
, privatedata
.errorsize_pt
, *vmaxpos
))
801 def donedrawpoints(self
, privatedata
, sharedata
, graph
):
802 if privatedata
.errorbarattrs
is not None:
803 graph
.layer("data").insert(privatedata
.errorbarcanvas
)
806 class text(_styleneedingpointpos
):
808 needsdata
= ["vpos", "vposmissing", "vposvalid"]
810 defaulttextattrs
= [textmodule
.halign
.center
, textmodule
.vshift
.mathaxis
]
812 def __init__(self
, textname
="text", dxname
=None, dyname
=None,
813 dxunit
=0.3*unit
.v_cm
, dyunit
=0.3*unit
.v_cm
,
814 textdx
=0*unit
.v_cm
, textdy
=0.3*unit
.v_cm
, textattrs
=[]):
815 self
.textname
= textname
822 self
.textattrs
= textattrs
824 def columnnames(self
, privatedata
, sharedata
, graph
, columnnames
, dataaxisnames
):
825 if self
.textname
not in columnnames
:
826 raise ValueError("column '%s' missing" % self
.textname
)
827 names
= [self
.textname
]
828 if self
.dxname
is not None:
829 if self
.dxname
not in columnnames
:
830 raise ValueError("column '%s' missing" % self
.dxname
)
831 names
.append(self
.dxname
)
832 if self
.dyname
is not None:
833 if self
.dyname
not in columnnames
:
834 raise ValueError("column '%s' missing" % self
.dyname
)
835 names
.append(self
.dyname
)
836 return names
+ _styleneedingpointpos
.columnnames(self
, privatedata
, sharedata
, graph
, columnnames
, dataaxisnames
)
838 def selectstyle(self
, privatedata
, sharedata
, graph
, selectindex
, selecttotal
):
839 if self
.textattrs
is not None:
840 privatedata
.textattrs
= attr
.selectattrs(self
.defaulttextattrs
+ self
.textattrs
, selectindex
, selecttotal
)
842 privatedata
.textattrs
= None
844 def initdrawpoints(self
, privatedata
, sharedata
, grap
):
845 if self
.dxname
is None:
846 privatedata
.textdx_pt
= unit
.topt(self
.textdx
)
848 privatedata
.dxunit_pt
= unit
.topt(self
.dxunit
)
849 if self
.dyname
is None:
850 privatedata
.textdy_pt
= unit
.topt(self
.textdy
)
852 privatedata
.dyunit_pt
= unit
.topt(self
.dyunit
)
854 def drawpoint(self
, privatedata
, sharedata
, graph
, point
):
855 if privatedata
.textattrs
is not None and sharedata
.vposvalid
:
856 x_pt
, y_pt
= graph
.vpos_pt(*sharedata
.vpos
)
858 text
= str(point
[self
.textname
])
862 if self
.dxname
is None:
863 dx_pt
= privatedata
.textdx_pt
865 dx_pt
= float(point
[self
.dxname
]) * privatedata
.dxunit_pt
866 if self
.dyname
is None:
867 dy_pt
= privatedata
.textdy_pt
869 dy_pt
= float(point
[self
.dyname
]) * privatedata
.dyunit_pt
870 graph
.layer("data").text_pt(x_pt
+ dx_pt
, y_pt
+ dy_pt
, text
, privatedata
.textattrs
)
872 def key_pt(self
, privatedata
, sharedata
, graph
, x_pt
, y_pt
, width_pt
, height_pt
):
873 raise RuntimeError("Style currently doesn't provide a graph key")
876 class arrow(_styleneedingpointpos
):
878 needsdata
= ["vpos", "vposmissing", "vposvalid"]
880 defaultlineattrs
= []
881 defaultarrowattrs
= []
883 def __init__(self
, linelength
=0.25*unit
.v_cm
, arrowsize
=0.15*unit
.v_cm
, lineattrs
=[], arrowattrs
=[], arrowpos
=0.5, epsilon
=1e-5, decorator
=deco
.earrow
):
884 self
.linelength
= linelength
885 self
.arrowsize
= arrowsize
886 self
.lineattrs
= lineattrs
887 self
.arrowattrs
= arrowattrs
888 self
.arrowpos
= arrowpos
889 self
.epsilon
= epsilon
890 self
.decorator
= decorator
892 def columnnames(self
, privatedata
, sharedata
, graph
, columnnames
, dataaxisnames
):
893 if len(graph
.axesnames
) != 2:
894 raise ValueError("arrow style restricted on two-dimensional graphs")
895 if "size" not in columnnames
:
896 raise ValueError("size missing")
897 if "angle" not in columnnames
:
898 raise ValueError("angle missing")
899 return ["size", "angle"] + _styleneedingpointpos
.columnnames(self
, privatedata
, sharedata
, graph
, columnnames
, dataaxisnames
)
901 def selectstyle(self
, privatedata
, sharedata
, graph
, selectindex
, selecttotal
):
902 if self
.lineattrs
is not None:
903 privatedata
.lineattrs
= attr
.selectattrs(self
.defaultlineattrs
+ self
.lineattrs
, selectindex
, selecttotal
)
905 privatedata
.lineattrs
= None
906 if self
.arrowattrs
is not None:
907 privatedata
.arrowattrs
= attr
.selectattrs(self
.defaultarrowattrs
+ self
.arrowattrs
, selectindex
, selecttotal
)
909 privatedata
.arrowattrs
= None
911 def initdrawpoints(self
, privatedata
, sharedata
, graph
):
912 privatedata
.arrowcanvas
= canvas
.canvas()
914 def drawpoint(self
, privatedata
, sharedata
, graph
, point
):
915 if privatedata
.lineattrs
is not None and privatedata
.arrowattrs
is not None and sharedata
.vposvalid
:
916 linelength_pt
= unit
.topt(self
.linelength
)
917 x_pt
, y_pt
= graph
.vpos_pt(*sharedata
.vpos
)
919 angle
= point
["angle"] + 0.0
920 size
= point
["size"] + 0.0
924 if point
["size"] > self
.epsilon
:
925 dx
= math
.cos(angle
*math
.pi
/180)
926 dy
= math
.sin(angle
*math
.pi
/180)
927 x1
= x_pt
-self
.arrowpos
*dx
*linelength_pt
*size
928 y1
= y_pt
-self
.arrowpos
*dy
*linelength_pt
*size
929 x2
= x_pt
+(1-self
.arrowpos
)*dx
*linelength_pt
*size
930 y2
= y_pt
+(1-self
.arrowpos
)*dy
*linelength_pt
*size
932 privatedata
.arrowcanvas
.stroke(path
.line_pt(x1
, y1
, x2
, y2
),
933 privatedata
.lineattrs
+[self
.decorator(privatedata
.arrowattrs
, size
=self
.arrowsize
*size
)])
935 privatedata
.arrowcanvas
.stroke(path
.line_pt(x1
, y1
, x2
, y2
), privatedata
.lineattrs
)
937 def donedrawpoints(self
, privatedata
, sharedata
, graph
):
938 graph
.layer("data").insert(privatedata
.arrowcanvas
)
940 def key_pt(self
, privatedata
, sharedata
, graph
, x_pt
, y_pt
, width_pt
, height_pt
):
941 raise RuntimeError("Style currently doesn't provide a graph key")
944 class rect(_keygraphstyle
):
946 needsdata
= ["vrange", "vrangeminmissing", "vrangemaxmissing"]
948 def columnnames(self
, privatedata
, sharedata
, graph
, columnnames
, dataaxisnames
):
949 if len(graph
.axesnames
) != 2:
950 raise TypeError("rect style restricted on two-dimensional graphs")
951 if len(sharedata
.vrangeminmissing
) + len(sharedata
.vrangemaxmissing
):
952 raise ValueError("incomplete range")
953 return _keygraphstyle
.columnnames(self
, privatedata
, sharedata
, graph
, columnnames
, dataaxisnames
)
955 def initdrawpoints(self
, privatedata
, sharedata
, graph
):
956 privatedata
.rectcanvas
= graph
.layer("filldata").insert(canvas
.canvas())
958 def drawpoint(self
, privatedata
, sharedata
, graph
, point
):
959 xvmin
= sharedata
.vrange
[0][0]
960 xvmax
= sharedata
.vrange
[0][1]
961 yvmin
= sharedata
.vrange
[1][0]
962 yvmax
= sharedata
.vrange
[1][1]
963 if (xvmin
is not None and xvmin
< 1 and
964 xvmax
is not None and xvmax
> 0 and
965 yvmin
is not None and yvmin
< 1 and
966 yvmax
is not None and yvmax
> 0):
975 p
= graph
.vgeodesic(xvmin
, yvmin
, xvmax
, yvmin
)
976 p
.append(graph
.vgeodesic_el(xvmax
, yvmin
, xvmax
, yvmax
))
977 p
.append(graph
.vgeodesic_el(xvmax
, yvmax
, xvmin
, yvmax
))
978 p
.append(graph
.vgeodesic_el(xvmin
, yvmax
, xvmin
, yvmin
))
979 p
.append(path
.closepath())
980 privatedata
.rectcanvas
.fill(p
, [self
.color(privatedata
, point
["color"])])
983 class histogram(_style
):
985 needsdata
= ["vpos", "vposmissing", "vrange", "vrangeminmissing", "vrangemaxmissing"]
987 defaultlineattrs
= [deco
.stroked
]
988 defaultfrompathattrs
= []
990 def __init__(self
, lineattrs
=[], steps
=0, fromvalue
=0, frompathattrs
=[], fillable
=0, rectkey
=0,
991 autohistogramaxisindex
=0, autohistogrampointpos
=0.5, epsilon
=1e-10):
992 self
.lineattrs
= lineattrs
994 self
.fromvalue
= fromvalue
995 self
.frompathattrs
= frompathattrs
996 self
.fillable
= fillable
# TODO: fillable paths might not properly be closed by straight lines on curved graph geometries
997 self
.rectkey
= rectkey
998 self
.autohistogramaxisindex
= autohistogramaxisindex
999 self
.autohistogrampointpos
= autohistogrampointpos
1000 self
.epsilon
= epsilon
1002 def columnnames(self
, privatedata
, sharedata
, graph
, columnnames
, dataaxisnames
):
1003 if len(graph
.axesnames
) != 2:
1004 raise TypeError("histogram style restricted on two-dimensional graphs")
1005 privatedata
.rangeaxisindex
= None
1006 for i
in builtinrange(len(graph
.axesnames
)):
1007 if i
in sharedata
.vrangeminmissing
or i
in sharedata
.vrangemaxmissing
:
1008 if i
in sharedata
.vposmissing
:
1009 raise ValueError("pos and range missing")
1011 if privatedata
.rangeaxisindex
is not None:
1012 raise ValueError("multiple ranges")
1013 privatedata
.rangeaxisindex
= i
1014 if privatedata
.rangeaxisindex
is None:
1015 privatedata
.rangeaxisindex
= self
.autohistogramaxisindex
1016 privatedata
.autohistogram
= 1
1018 privatedata
.autohistogram
= 0
1021 def adjustaxis(self
, privatedata
, sharedata
, graph
, plotitem
, columnname
, data
):
1022 if privatedata
.autohistogram
and columnname
== sharedata
.poscolumnnames
[privatedata
.rangeaxisindex
]:
1024 raise ValueError("several data points needed for automatic histogram width calculation")
1026 delta
= data
[1] - data
[0]
1027 min = data
[0] - self
.autohistogrampointpos
* delta
1028 max = data
[-1] + (1-self
.autohistogrampointpos
) * delta
1029 graph
.axes
[columnname
].adjustaxis([min, max])
1030 elif self
.fromvalue
is not None and columnname
== sharedata
.poscolumnnames
[1-privatedata
.rangeaxisindex
]:
1031 graph
.axes
[columnname
].adjustaxis([self
.fromvalue
])
1033 def selectstyle(self
, privatedata
, sharedata
, graph
, selectindex
, selecttotal
):
1034 privatedata
.insertfrompath
= selectindex
== 0
1035 if self
.lineattrs
is not None:
1036 privatedata
.lineattrs
= attr
.selectattrs(self
.defaultlineattrs
+ self
.lineattrs
, selectindex
, selecttotal
)
1038 privatedata
.lineattrs
= None
1040 def vmoveto(self
, privatedata
, sharedata
, graph
, vpos
, vvalue
):
1041 if -self
.epsilon
< vpos
< 1+self
.epsilon
and -self
.epsilon
< vvalue
< 1+self
.epsilon
:
1042 if privatedata
.rangeaxisindex
:
1043 privatedata
.path
.append(path
.moveto_pt(*graph
.vpos_pt(vvalue
, vpos
)))
1045 privatedata
.path
.append(path
.moveto_pt(*graph
.vpos_pt(vpos
, vvalue
)))
1047 def vposline(self
, privatedata
, sharedata
, graph
, vpos
, vvalue1
, vvalue2
):
1048 if -self
.epsilon
< vpos
< 1+self
.epsilon
:
1063 if abs(vvalue1cut
+ vvalue2cut
) <= 1:
1064 if vvalue1cut
and not self
.fillable
:
1065 if privatedata
.rangeaxisindex
:
1066 privatedata
.path
.append(path
.moveto_pt(*graph
.vpos_pt(vvalue1
, vpos
)))
1068 privatedata
.path
.append(path
.moveto_pt(*graph
.vpos_pt(vpos
, vvalue1
)))
1069 if privatedata
.rangeaxisindex
:
1070 privatedata
.path
.append(graph
.vgeodesic_el(vvalue1
, vpos
, vvalue2
, vpos
))
1072 privatedata
.path
.append(graph
.vgeodesic_el(vpos
, vvalue1
, vpos
, vvalue2
))
1074 def vvalueline(self
, privatedata
, sharedata
, graph
, vvalue
, vpos1
, vpos2
):
1076 if vvalue
< -self
.epsilon
:
1078 logger
.warning("cut at graph boundary adds artificial lines to fillable step histogram path")
1079 if vvalue
> 1+self
.epsilon
:
1081 logger
.warning("cut at graph boundary adds artificial lines to fillable step histogram path")
1082 if self
.fillable
or (-self
.epsilon
< vvalue
< 1+self
.epsilon
):
1097 if abs(vpos1cut
+ vpos2cut
) <= 1:
1100 if privatedata
.rangeaxisindex
:
1101 privatedata
.path
.append(path
.moveto_pt(*graph
.vpos_pt(privatedata
.vfromvalue
, vpos1
)))
1102 privatedata
.path
.append(graph
.vgeodesic_el(privatedata
.vfromvalue
, vpos1
, vvalue
, vpos1
))
1104 privatedata
.path
.append(path
.moveto_pt(*graph
.vpos_pt(vpos1
, privatedata
.vfromvalue
)))
1105 privatedata
.path
.append(graph
.vgeodesic_el(vpos1
, privatedata
.vfromvalue
, vpos1
, vvalue
))
1107 if privatedata
.rangeaxisindex
:
1108 privatedata
.path
.append(path
.moveto_pt(*graph
.vpos_pt(vvalue
, vpos1
)))
1110 privatedata
.path
.append(path
.moveto_pt(*graph
.vpos_pt(vpos1
, vvalue
)))
1111 if privatedata
.rangeaxisindex
:
1112 privatedata
.path
.append(graph
.vgeodesic_el(vvalue
, vpos1
, vvalue
, vpos2
))
1114 privatedata
.path
.append(graph
.vgeodesic_el(vpos1
, vvalue
, vpos2
, vvalue
))
1115 if self
.fillable
and vpos2cut
:
1116 logger
.warning("cut at graph boundary adds artificial lines to fillable step histogram path")
1117 if privatedata
.rangeaxisindex
:
1118 privatedata
.path
.append(graph
.vgeodesic_el(vvalue
, vpos2
, privatedata
.vfromvalue
, vpos2
))
1120 privatedata
.path
.append(graph
.vgeodesic_el(vpos2
, vvalue
, vpos2
, privatedata
.vfromvalue
))
1122 def drawvalue(self
, privatedata
, sharedata
, graph
, vmin
, vmax
, vvalue
):
1123 currentvalid
= vmin
is not None and vmax
is not None and vvalue
is not None
1124 if self
.fillable
and not self
.steps
:
1125 if not currentvalid
:
1128 if vmin
< -self
.epsilon
:
1131 elif vmin
> 1+self
.epsilon
:
1135 if vmax
< -self
.epsilon
:
1138 if vmax
> 1+self
.epsilon
:
1142 if vvalue
< -self
.epsilon
:
1145 if vvalue
> 1+self
.epsilon
:
1149 if abs(vmincut
) + abs(vmaxcut
) + abs(vvaluecut
) + abs(privatedata
.vfromvaluecut
) > 1:
1150 if abs(vmincut
+ vmaxcut
) > 1 or abs(vvaluecut
+privatedata
.vfromvaluecut
) > 1:
1153 logger
.warning("multiple cuts at graph boundary add artificial lines to fillable rectangle histogram path")
1156 if privatedata
.rangeaxisindex
:
1157 privatedata
.path
.append(path
.moveto_pt(*graph
.vpos_pt(privatedata
.vfromvalue
, vmin
)))
1158 privatedata
.path
.append(graph
.vgeodesic_el(privatedata
.vfromvalue
, vmin
, privatedata
.vfromvalue
, vmax
))
1159 privatedata
.path
.append(graph
.vgeodesic_el(privatedata
.vfromvalue
, vmax
, vvalue
, vmax
))
1160 privatedata
.path
.append(graph
.vgeodesic_el(vvalue
, vmax
, vvalue
, vmin
))
1162 privatedata
.path
.append(path
.moveto_pt(*graph
.vpos_pt(vmin
, privatedata
.vfromvalue
)))
1163 privatedata
.path
.append(graph
.vgeodesic_el(vmin
, privatedata
.vfromvalue
, vmax
, privatedata
.vfromvalue
))
1164 privatedata
.path
.append(graph
.vgeodesic_el(vmax
, privatedata
.vfromvalue
, vmax
, vvalue
))
1165 privatedata
.path
.append(graph
.vgeodesic_el(vmax
, vvalue
, vmin
, vvalue
))
1168 if privatedata
.rangeaxisindex
:
1169 privatedata
.path
.append(path
.moveto_pt(*graph
.vpos_pt(vvalue
, vmax
)))
1170 privatedata
.path
.append(graph
.vgeodesic_el(vvalue
, vmax
, vvalue
, vmin
))
1171 privatedata
.path
.append(graph
.vgeodesic_el(vvalue
, vmin
, privatedata
.vfromvalue
, vmin
))
1172 privatedata
.path
.append(graph
.vgeodesic_el(privatedata
.vfromvalue
, vmin
, privatedata
.vfromvalue
, vmax
))
1174 privatedata
.path
.append(path
.moveto_pt(*graph
.vpos_pt(vmax
, vvalue
)))
1175 privatedata
.path
.append(graph
.vgeodesic_el(vmax
, vvalue
, vmin
, vvalue
))
1176 privatedata
.path
.append(graph
.vgeodesic_el(vmin
, vvalue
, vmin
, privatedata
.vfromvalue
))
1177 privatedata
.path
.append(graph
.vgeodesic_el(vmin
, privatedata
.vfromvalue
, vmax
, privatedata
.vfromvalue
))
1178 elif privatedata
.vfromvaluecut
:
1180 if privatedata
.rangeaxisindex
:
1181 privatedata
.path
.append(path
.moveto_pt(*graph
.vpos_pt(privatedata
.vfromvalue
, vmax
)))
1182 privatedata
.path
.append(graph
.vgeodesic_el(privatedata
.vfromvalue
, vmax
, vvalue
, vmax
))
1183 privatedata
.path
.append(graph
.vgeodesic_el(vvalue
, vmax
, vvalue
, vmin
))
1184 privatedata
.path
.append(graph
.vgeodesic_el(vvalue
, vmin
, privatedata
.vfromvalue
, vmin
))
1186 privatedata
.path
.append(path
.moveto_pt(*graph
.vpos_pt(vmax
, privatedata
.vfromvalue
)))
1187 privatedata
.path
.append(graph
.vgeodesic_el(vmax
, privatedata
.vfromvalue
, vmax
, vvalue
))
1188 privatedata
.path
.append(graph
.vgeodesic_el(vmax
, vvalue
, vmin
, vvalue
))
1189 privatedata
.path
.append(graph
.vgeodesic_el(vmin
, vvalue
, vmin
, privatedata
.vfromvalue
))
1192 if privatedata
.rangeaxisindex
:
1193 privatedata
.path
.append(path
.moveto_pt(*graph
.vpos_pt(vvalue
, vmin
)))
1194 privatedata
.path
.append(graph
.vgeodesic_el(vvalue
, vmin
, privatedata
.vfromvalue
, vmin
))
1195 privatedata
.path
.append(graph
.vgeodesic_el(privatedata
.vfromvalue
, vmin
, privatedata
.vfromvalue
, vmax
))
1196 privatedata
.path
.append(graph
.vgeodesic_el(privatedata
.vfromvalue
, vmax
, vvalue
, vmax
))
1198 privatedata
.path
.append(path
.moveto_pt(*graph
.vpos_pt(vmin
, vvalue
)))
1199 privatedata
.path
.append(graph
.vgeodesic_el(vmin
, vvalue
, vmin
, privatedata
.vfromvalue
))
1200 privatedata
.path
.append(graph
.vgeodesic_el(vmin
, privatedata
.vfromvalue
, vmax
, privatedata
.vfromvalue
))
1201 privatedata
.path
.append(graph
.vgeodesic_el(vmax
, privatedata
.vfromvalue
, vmax
, vvalue
))
1203 if privatedata
.rangeaxisindex
:
1204 privatedata
.path
.append(path
.moveto_pt(*graph
.vpos_pt(privatedata
.vfromvalue
, vmin
)))
1205 privatedata
.path
.append(graph
.vgeodesic_el(privatedata
.vfromvalue
, vmin
, privatedata
.vfromvalue
, vmax
))
1206 privatedata
.path
.append(graph
.vgeodesic_el(privatedata
.vfromvalue
, vmax
, vvalue
, vmax
))
1207 privatedata
.path
.append(graph
.vgeodesic_el(vvalue
, vmax
, vvalue
, vmin
))
1208 privatedata
.path
.append(graph
.vgeodesic_el(vvalue
, vmin
, privatedata
.vfromvalue
, vmin
))
1209 privatedata
.path
.append(path
.closepath())
1211 privatedata
.path
.append(path
.moveto_pt(*graph
.vpos_pt(vmin
, privatedata
.vfromvalue
)))
1212 privatedata
.path
.append(graph
.vgeodesic_el(vmin
, privatedata
.vfromvalue
, vmax
, privatedata
.vfromvalue
))
1213 privatedata
.path
.append(graph
.vgeodesic_el(vmax
, privatedata
.vfromvalue
, vmax
, vvalue
))
1214 privatedata
.path
.append(graph
.vgeodesic_el(vmax
, vvalue
, vmin
, vvalue
))
1215 privatedata
.path
.append(graph
.vgeodesic_el(vmin
, vvalue
, vmin
, privatedata
.vfromvalue
))
1216 privatedata
.path
.append(path
.closepath())
1219 gap
= abs(vmin
- privatedata
.lastvmax
) > self
.epsilon
1220 except (ArithmeticError, ValueError, TypeError):
1222 if (privatedata
.lastvvalue
is not None and currentvalid
and not gap
and
1223 (self
.steps
or (privatedata
.lastvvalue
-privatedata
.vfromvalue
)*(vvalue
-privatedata
.vfromvalue
) < 0)):
1224 self
.vposline(privatedata
, sharedata
, graph
,
1225 vmin
, privatedata
.lastvvalue
, vvalue
)
1227 if privatedata
.lastvvalue
is not None and currentvalid
:
1228 currentbigger
= abs(privatedata
.lastvvalue
-privatedata
.vfromvalue
) < abs(vvalue
-privatedata
.vfromvalue
)
1229 if privatedata
.lastvvalue
is not None and (not currentvalid
or not currentbigger
or gap
):
1230 self
.vposline(privatedata
, sharedata
, graph
,
1231 privatedata
.lastvmax
, privatedata
.lastvvalue
, privatedata
.vfromvalue
)
1233 self
.vmoveto(privatedata
, sharedata
, graph
,
1235 if currentvalid
and (privatedata
.lastvvalue
is None or currentbigger
or gap
):
1236 self
.vmoveto(privatedata
, sharedata
, graph
,
1237 vmin
, privatedata
.vfromvalue
)
1238 self
.vposline(privatedata
, sharedata
, graph
,
1239 vmin
, privatedata
.vfromvalue
, vvalue
)
1241 self
.vvalueline(privatedata
, sharedata
, graph
,
1243 privatedata
.lastvvalue
= vvalue
1244 privatedata
.lastvmax
= vmax
1246 privatedata
.lastvvalue
= privatedata
.lastvmax
= None
1248 def initdrawpoints(self
, privatedata
, sharedata
, graph
):
1249 privatedata
.path
= path
.path()
1250 privatedata
.lastvvalue
= privatedata
.lastvmax
= None
1251 privatedata
.vcurrentpoint
= None
1252 privatedata
.count
= 0
1253 if self
.fromvalue
is not None:
1254 valueaxisname
= sharedata
.poscolumnnames
[1-privatedata
.rangeaxisindex
]
1255 privatedata
.vfromvalue
= graph
.axes
[valueaxisname
].convert(self
.fromvalue
)
1256 privatedata
.vfromvaluecut
= 0
1257 if privatedata
.vfromvalue
< 0:
1258 privatedata
.vfromvalue
= 0
1259 privatedata
.vfromvaluecut
= -1
1260 if privatedata
.vfromvalue
> 1:
1261 privatedata
.vfromvalue
= 1
1262 privatedata
.vfromvaluecut
= 1
1263 if self
.frompathattrs
is not None and privatedata
.insertfrompath
:
1264 graph
.layer("data").stroke(graph
.axes
[valueaxisname
].vgridpath(privatedata
.vfromvalue
),
1265 self
.defaultfrompathattrs
+ self
.frompathattrs
)
1267 privatedata
.vfromvalue
= 0
1269 def drawpoint(self
, privatedata
, sharedata
, graph
, point
):
1270 if privatedata
.autohistogram
:
1271 # automatic range handling
1272 privatedata
.count
+= 1
1273 if privatedata
.count
== 2:
1274 if privatedata
.rangeaxisindex
:
1275 privatedata
.vrange
= sharedata
.vpos
[1] - privatedata
.lastvpos
[1]
1276 self
.drawvalue(privatedata
, sharedata
, graph
,
1277 privatedata
.lastvpos
[1] - self
.autohistogrampointpos
*privatedata
.vrange
,
1278 privatedata
.lastvpos
[1] + (1-self
.autohistogrampointpos
)*privatedata
.vrange
,
1279 privatedata
.lastvpos
[0])
1281 privatedata
.vrange
= sharedata
.vpos
[0] - privatedata
.lastvpos
[0]
1282 self
.drawvalue(privatedata
, sharedata
, graph
,
1283 privatedata
.lastvpos
[0] - self
.autohistogrampointpos
*privatedata
.vrange
,
1284 privatedata
.lastvpos
[0] + (1-self
.autohistogrampointpos
)*privatedata
.vrange
,
1285 privatedata
.lastvpos
[1])
1286 elif privatedata
.count
> 2:
1287 if privatedata
.rangeaxisindex
:
1288 vrange
= sharedata
.vpos
[1] - privatedata
.lastvpos
[1]
1290 vrange
= sharedata
.vpos
[0] - privatedata
.lastvpos
[0]
1291 if abs(privatedata
.vrange
- vrange
) > self
.epsilon
:
1292 raise ValueError("equal steps (in graph coordinates) needed for automatic width calculation")
1293 if privatedata
.count
> 1:
1294 if privatedata
.rangeaxisindex
:
1295 self
.drawvalue(privatedata
, sharedata
, graph
,
1296 sharedata
.vpos
[1] - self
.autohistogrampointpos
*privatedata
.vrange
,
1297 sharedata
.vpos
[1] + (1-self
.autohistogrampointpos
)*privatedata
.vrange
,
1300 self
.drawvalue(privatedata
, sharedata
, graph
,
1301 sharedata
.vpos
[0] - self
.autohistogrampointpos
*privatedata
.vrange
,
1302 sharedata
.vpos
[0] + (1-self
.autohistogrampointpos
)*privatedata
.vrange
,
1304 privatedata
.lastvpos
= sharedata
.vpos
[:]
1306 if privatedata
.rangeaxisindex
:
1307 self
.drawvalue(privatedata
, sharedata
, graph
,
1308 sharedata
.vrange
[1][0], sharedata
.vrange
[1][1], sharedata
.vpos
[0])
1310 self
.drawvalue(privatedata
, sharedata
, graph
,
1311 sharedata
.vrange
[0][0], sharedata
.vrange
[0][1], sharedata
.vpos
[1])
1313 def donedrawpoints(self
, privatedata
, sharedata
, graph
):
1314 self
.drawvalue(privatedata
, sharedata
, graph
, None, None, None)
1315 if privatedata
.lineattrs
is not None and len(privatedata
.path
):
1316 graph
.layer("data").draw(privatedata
.path
, privatedata
.lineattrs
)
1318 def key_pt(self
, privatedata
, sharedata
, graph
, x_pt
, y_pt
, width_pt
, height_pt
):
1319 if privatedata
.lineattrs
is not None:
1321 p
= path
.rect_pt(x_pt
, y_pt
, width_pt
, height_pt
)
1323 p
= path
.line_pt(x_pt
, y_pt
+0.5*height_pt
, x_pt
+width_pt
, y_pt
+0.5*height_pt
)
1324 graph
.draw(p
, privatedata
.lineattrs
)
1327 class barpos(_style
):
1329 providesdata
= ["vpos", "vposmissing", "vposavailable", "vposvalid", "vbarrange", "barposcolumnnames", "barvalueindex", "lastbarvalue", "stackedbar", "stackedbardraw"]
1331 defaultfrompathattrs
= []
1333 def __init__(self
, fromvalue
=None, frompathattrs
=[], epsilon
=1e-10):
1334 self
.fromvalue
= fromvalue
1335 self
.frompathattrs
= frompathattrs
1336 self
.epsilon
= epsilon
1338 def columnnames(self
, privatedata
, sharedata
, graph
, columnnames
, dataaxisnames
):
1339 sharedata
.barposcolumnnames
= []
1340 sharedata
.barvalueindex
= None
1341 for dimension
, axisnames
in enumerate(graph
.axesnames
):
1343 for axisname
in axisnames
:
1344 if axisname
in columnnames
:
1345 if sharedata
.barvalueindex
is not None:
1346 raise ValueError("multiple values")
1347 sharedata
.barvalueindex
= dimension
1348 sharedata
.barposcolumnnames
.append(axisname
)
1350 if (axisname
+ "name") in columnnames
:
1351 sharedata
.barposcolumnnames
.append(axisname
+ "name")
1354 raise ValueError("multiple names and value")
1356 raise ValueError("value/name missing")
1357 if sharedata
.barvalueindex
is None:
1358 raise ValueError("missing value")
1359 sharedata
.vposmissing
= []
1360 return sharedata
.barposcolumnnames
1362 def addsubvalue(self
, value
, subvalue
):
1367 return value
[0], self
.addsubvalue(value
[1], subvalue
)
1369 return value
, subvalue
1371 return value
, subvalue
1373 def adjustaxis(self
, privatedata
, sharedata
, graph
, plotitem
, columnname
, data
):
1375 i
= sharedata
.barposcolumnnames
.index(columnname
)
1379 if i
== sharedata
.barvalueindex
:
1380 if self
.fromvalue
is not None:
1381 graph
.axes
[sharedata
.barposcolumnnames
[i
]].adjustaxis([self
.fromvalue
])
1382 graph
.axes
[sharedata
.barposcolumnnames
[i
]].adjustaxis(data
)
1384 graph
.axes
[sharedata
.barposcolumnnames
[i
][:-4]].adjustaxis([self
.addsubvalue(x
, 0) for x
in data
])
1385 graph
.axes
[sharedata
.barposcolumnnames
[i
][:-4]].adjustaxis([self
.addsubvalue(x
, 1) for x
in data
])
1387 def selectstyle(self
, privatedata
, sharedata
, graph
, selectindex
, selecttotal
):
1388 privatedata
.insertfrompath
= selectindex
== 0
1390 def initdrawpoints(self
, privatedata
, sharedata
, graph
):
1391 sharedata
.vpos
= [None]*(len(sharedata
.barposcolumnnames
))
1392 sharedata
.vbarrange
= [[None for i
in builtinrange(2)] for x
in sharedata
.barposcolumnnames
]
1393 sharedata
.stackedbar
= sharedata
.stackedbardraw
= 0
1395 if self
.fromvalue
is not None:
1396 privatedata
.vfromvalue
= graph
.axes
[sharedata
.barposcolumnnames
[sharedata
.barvalueindex
]].convert(self
.fromvalue
)
1397 if privatedata
.vfromvalue
< 0:
1398 privatedata
.vfromvalue
= 0
1399 if privatedata
.vfromvalue
> 1:
1400 privatedata
.vfromvalue
= 1
1401 if self
.frompathattrs
is not None and privatedata
.insertfrompath
:
1402 graph
.layer("data").stroke(graph
.axes
[sharedata
.barposcolumnnames
[sharedata
.barvalueindex
]].vgridpath(privatedata
.vfromvalue
),
1403 self
.defaultfrompathattrs
+ self
.frompathattrs
)
1405 privatedata
.vfromvalue
= 0
1407 def drawpoint(self
, privatedata
, sharedata
, graph
, point
):
1408 sharedata
.vposavailable
= sharedata
.vposvalid
= 1
1409 for i
, barname
in enumerate(sharedata
.barposcolumnnames
):
1410 if i
== sharedata
.barvalueindex
:
1411 sharedata
.vbarrange
[i
][0] = privatedata
.vfromvalue
1412 sharedata
.lastbarvalue
= point
[barname
]
1414 sharedata
.vpos
[i
] = sharedata
.vbarrange
[i
][1] = graph
.axes
[barname
].convert(sharedata
.lastbarvalue
)
1415 except (ArithmeticError, ValueError, TypeError):
1416 sharedata
.vpos
[i
] = sharedata
.vbarrange
[i
][1] = None
1418 for j
in builtinrange(2):
1420 sharedata
.vbarrange
[i
][j
] = graph
.axes
[barname
[:-4]].convert(self
.addsubvalue(point
[barname
], j
))
1421 except (ArithmeticError, ValueError, TypeError):
1422 sharedata
.vbarrange
[i
][j
] = None
1424 sharedata
.vpos
[i
] = 0.5*(sharedata
.vbarrange
[i
][0]+sharedata
.vbarrange
[i
][1])
1425 except (ArithmeticError, ValueError, TypeError):
1426 sharedata
.vpos
[i
] = None
1427 if sharedata
.vpos
[i
] is None:
1428 sharedata
.vposavailable
= sharedata
.vposvalid
= 0
1429 elif sharedata
.vpos
[i
] < -self
.epsilon
or sharedata
.vpos
[i
] > 1+self
.epsilon
:
1430 sharedata
.vposvalid
= 0
1432 registerdefaultprovider(barpos(), ["vbarrange", "barposcolumnnames", "barvalueindex", "lastbarvalue", "stackedbar", "stackedbardraw"])
1435 class stackedbarpos(_style
):
1437 # provides no additional data, but needs some data (and modifies some of them)
1438 needsdata
= ["vbarrange", "barposcolumnnames", "barvalueindex", "lastbarvalue", "stackedbar", "stackedbardraw"]
1440 def __init__(self
, stackname
, addontop
=0, epsilon
=1e-10):
1441 self
.stackname
= stackname
1442 self
.epsilon
= epsilon
1443 self
.addontop
= addontop
1445 def columnnames(self
, privatedata
, sharedata
, graph
, columnnames
, dataaxisnames
):
1446 if self
.stackname
not in columnnames
:
1447 raise ValueError("column '%s' missing" % self
.stackname
)
1448 return [self
.stackname
]
1450 def adjustaxis(self
, privatedata
, sharedata
, graph
, plotitem
, columnname
, data
):
1451 if columnname
== self
.stackname
:
1452 graph
.axes
[sharedata
.barposcolumnnames
[sharedata
.barvalueindex
]].adjustaxis(data
)
1454 def initdrawpoints(self
, privatedata
, sharedata
, graph
):
1455 if sharedata
.stackedbardraw
: # do not count the start bar when not gets painted
1456 sharedata
.stackedbar
+= 1
1458 def drawpoint(self
, privatedata
, sharedata
, graph
, point
):
1459 sharedata
.vbarrange
[sharedata
.barvalueindex
][0] = sharedata
.vbarrange
[sharedata
.barvalueindex
][1]
1462 sharedata
.lastbarvalue
+= point
[self
.stackname
]
1463 except (ArithmeticError, ValueError, TypeError):
1464 sharedata
.lastbarvalue
= None
1466 sharedata
.lastbarvalue
= point
[self
.stackname
]
1468 sharedata
.vpos
[sharedata
.barvalueindex
] = sharedata
.vbarrange
[sharedata
.barvalueindex
][1] = graph
.axes
[sharedata
.barposcolumnnames
[sharedata
.barvalueindex
]].convert(sharedata
.lastbarvalue
)
1469 except (ArithmeticError, ValueError, TypeError):
1470 sharedata
.vpos
[sharedata
.barvalueindex
] = sharedata
.vbarrange
[sharedata
.barvalueindex
][1] = None
1471 sharedata
.vposavailable
= sharedata
.vposvalid
= 0
1473 if not sharedata
.vposavailable
or not sharedata
.vposvalid
:
1474 sharedata
.vposavailable
= sharedata
.vposvalid
= 1
1475 for v
in sharedata
.vpos
:
1477 sharedata
.vposavailable
= sharedata
.vposvalid
= 0
1479 if v
< -self
.epsilon
or v
> 1+self
.epsilon
:
1480 sharedata
.vposvalid
= 0
1485 needsdata
= ["vbarrange"]
1487 defaultbarattrs
= [color
.gradient
.Rainbow
, deco
.stroked([color
.grey
.black
])]
1489 def __init__(self
, barattrs
=[], epsilon
=1e-10, gradient
=color
.gradient
.RedBlack
):
1490 self
.barattrs
= barattrs
1491 self
.epsilon
= epsilon
1492 self
.gradient
= gradient
1494 def lighting(self
, angle
, zindex
):
1495 return self
.gradient
.getcolor(0.7-0.4*abs(angle
)+0.1*zindex
)
1497 def columnnames(self
, privatedata
, sharedata
, graph
, columnnames
, dataaxisnames
):
1500 def selectstyle(self
, privatedata
, sharedata
, graph
, selectindex
, selecttotal
):
1501 privatedata
.barattrs
= attr
.selectattrs(self
.defaultbarattrs
+ self
.barattrs
, selectindex
, selecttotal
)
1503 def initdrawpoints(self
, privatedata
, sharedata
, graph
):
1504 privatedata
.barcanvas
= graph
.layer("filldata").insert(canvas
.canvas())
1505 sharedata
.stackedbardraw
= 1
1506 privatedata
.stackedbar
= sharedata
.stackedbar
1507 privatedata
.todraw
= []
1509 def drawpointfill(self
, privatedata
, p
):
1511 privatedata
.barcanvas
.fill(p
, privatedata
.barattrs
)
1513 def drawpoint(self
, privatedata
, sharedata
, graph
, point
):
1515 for vmin
, vmax
in sharedata
.vbarrange
:
1516 if vmin
is None or vmax
is None:
1517 self
.drawpointfill(privatedata
, None)
1520 vmin
, vmax
= vmax
, vmin
1521 if vmin
> 1 or vmax
< 0:
1522 self
.drawpointfill(privatedata
, None)
1528 vbarrange
.append((vmin
, vmax
))
1529 if len(vbarrange
) == 2:
1530 p
= graph
.vgeodesic(vbarrange
[0][0], vbarrange
[1][0], vbarrange
[0][1], vbarrange
[1][0])
1531 p
.append(graph
.vgeodesic_el(vbarrange
[0][1], vbarrange
[1][0], vbarrange
[0][1], vbarrange
[1][1]))
1532 p
.append(graph
.vgeodesic_el(vbarrange
[0][1], vbarrange
[1][1], vbarrange
[0][0], vbarrange
[1][1]))
1533 p
.append(graph
.vgeodesic_el(vbarrange
[0][0], vbarrange
[1][1], vbarrange
[0][0], vbarrange
[1][0]))
1534 p
.append(path
.closepath())
1535 self
.drawpointfill(privatedata
, p
)
1536 elif len(vbarrange
) == 3:
1538 if abs(vbarrange
[0][0] - vbarrange
[0][1]) > self
.epsilon
and abs(vbarrange
[1][0] - vbarrange
[1][1]):
1539 planes
.append((vbarrange
[0][0], vbarrange
[1][0], vbarrange
[2][0],
1540 vbarrange
[0][1], vbarrange
[1][0], vbarrange
[2][0],
1541 vbarrange
[0][1], vbarrange
[1][1], vbarrange
[2][0],
1542 vbarrange
[0][0], vbarrange
[1][1], vbarrange
[2][0]))
1543 planes
.append((vbarrange
[0][0], vbarrange
[1][0], vbarrange
[2][1],
1544 vbarrange
[0][0], vbarrange
[1][1], vbarrange
[2][1],
1545 vbarrange
[0][1], vbarrange
[1][1], vbarrange
[2][1],
1546 vbarrange
[0][1], vbarrange
[1][0], vbarrange
[2][1]))
1547 if abs(vbarrange
[0][0] - vbarrange
[0][1]) > self
.epsilon
and abs(vbarrange
[2][0] - vbarrange
[2][1]):
1548 planes
.append((vbarrange
[0][0], vbarrange
[1][0], vbarrange
[2][0],
1549 vbarrange
[0][0], vbarrange
[1][0], vbarrange
[2][1],
1550 vbarrange
[0][1], vbarrange
[1][0], vbarrange
[2][1],
1551 vbarrange
[0][1], vbarrange
[1][0], vbarrange
[2][0]))
1552 planes
.append((vbarrange
[0][0], vbarrange
[1][1], vbarrange
[2][0],
1553 vbarrange
[0][1], vbarrange
[1][1], vbarrange
[2][0],
1554 vbarrange
[0][1], vbarrange
[1][1], vbarrange
[2][1],
1555 vbarrange
[0][0], vbarrange
[1][1], vbarrange
[2][1]))
1556 if abs(vbarrange
[1][0] - vbarrange
[1][1]) > self
.epsilon
and abs(vbarrange
[2][0] - vbarrange
[2][1]):
1557 planes
.append((vbarrange
[0][0], vbarrange
[1][0], vbarrange
[2][0],
1558 vbarrange
[0][0], vbarrange
[1][1], vbarrange
[2][0],
1559 vbarrange
[0][0], vbarrange
[1][1], vbarrange
[2][1],
1560 vbarrange
[0][0], vbarrange
[1][0], vbarrange
[2][1]))
1561 planes
.append((vbarrange
[0][1], vbarrange
[1][0], vbarrange
[2][0],
1562 vbarrange
[0][1], vbarrange
[1][0], vbarrange
[2][1],
1563 vbarrange
[0][1], vbarrange
[1][1], vbarrange
[2][1],
1564 vbarrange
[0][1], vbarrange
[1][1], vbarrange
[2][0]))
1565 v
= [0.5 * (vbarrange
[0][0] + vbarrange
[0][1]),
1566 0.5 * (vbarrange
[1][0] + vbarrange
[1][1]),
1567 0.5 * (vbarrange
[2][0] + vbarrange
[2][1])]
1568 v
[sharedata
.barvalueindex
] = 0.5
1569 zindex
= graph
.vzindex(*v
)
1570 for v11
, v12
, v13
, v21
, v22
, v23
, v31
, v32
, v33
, v41
, v42
, v43
in planes
:
1571 angle
= graph
.vangle(v11
, v12
, v13
, v21
, v22
, v23
, v41
, v42
, v43
)
1573 p
= graph
.vgeodesic(v11
, v12
, v13
, v21
, v22
, v23
)
1574 p
.append(graph
.vgeodesic_el(v21
, v22
, v23
, v31
, v32
, v33
))
1575 p
.append(graph
.vgeodesic_el(v31
, v32
, v33
, v41
, v42
, v43
))
1576 p
.append(graph
.vgeodesic_el(v41
, v42
, v43
, v11
, v12
, v13
))
1577 p
.append(path
.closepath())
1579 privatedata
.todraw
.append((-zindex
, p
, privatedata
.barattrs
+ [self
.lighting(angle
, zindex
)]))
1581 privatedata
.todraw
.append((-zindex
, p
, privatedata
.barattrs
))
1583 raise TypeError("bar style restricted to two- and three dimensional graphs")
1585 def donedrawpoints(self
, privatedata
, sharedata
, graph
):
1586 privatedata
.todraw
.sort(key
=lambda x
: x
[0])
1587 for vzindex
, p
, a
in privatedata
.todraw
:
1588 privatedata
.barcanvas
.fill(p
, a
)
1590 def key_pt(self
, privatedata
, sharedata
, graph
, x_pt
, y_pt
, width_pt
, height_pt
):
1591 selectindex
= privatedata
.stackedbar
1592 selecttotal
= sharedata
.stackedbar
+ 1
1593 graph
.fill(path
.rect_pt(x_pt
+ width_pt
*selectindex
/float(selecttotal
), y_pt
, width_pt
/float(selecttotal
), height_pt
), privatedata
.barattrs
)
1596 class changebar(bar
):
1598 def selectstyle(self
, privatedata
, sharedata
, graph
, selectindex
, selecttotal
):
1599 if selecttotal
!= 1:
1600 raise RuntimeError("Changebar can't change its appearance. Thus you can't use it to plot several bars side by side on a subaxis.")
1602 def initdrawpoints(self
, privatedata
, sharedata
, graph
):
1603 if len(graph
.axesnames
) != 2:
1604 raise TypeError("changebar style restricted on two-dimensional graphs (at least for the moment)")
1605 bar
.initdrawpoints(self
, privatedata
, sharedata
, graph
)
1606 privatedata
.bars
= []
1608 def drawpointfill(self
, privatedata
, p
):
1609 privatedata
.bars
.append(p
)
1611 def donedrawpoints(self
, privatedata
, sharedata
, graph
):
1612 selecttotal
= len(privatedata
.bars
)
1613 for selectindex
, p
in enumerate(privatedata
.bars
):
1615 barattrs
= attr
.selectattrs(self
.defaultbarattrs
+ self
.barattrs
, selectindex
, selecttotal
)
1616 privatedata
.barcanvas
.fill(p
, barattrs
)
1618 def key_pt(self
, privatedata
, sharedata
, graph
, x_pt
, y_pt
, width_pt
, height_pt
):
1619 raise RuntimeError("Style currently doesn't provide a graph key")
1622 class gridpos(_style
):
1624 needsdata
= ["vpos", "vposmissing", "vposavailable", "vposvalid"]
1625 providesdata
= ["values1", "values2", "data12", "data21", "index1", "index2"]
1627 def __init__(self
, index1
=0, index2
=1, epsilon
=1e-10):
1628 self
.index1
= index1
1629 self
.index2
= index2
1630 self
.epsilon
= epsilon
1632 def initdrawpoints(self
, privatedata
, sharedata
, graph
):
1633 sharedata
.index1
= self
.index1
1634 sharedata
.index2
= self
.index2
1635 sharedata
.values1
= {}
1636 sharedata
.values2
= {}
1637 sharedata
.data12
= {}
1638 sharedata
.data21
= {}
1640 def drawpoint(self
, privatedata
, sharedata
, graph
, point
):
1641 if sharedata
.vposavailable
:
1642 sharedata
.value1
= sharedata
.vpos
[self
.index1
]
1643 sharedata
.value2
= sharedata
.vpos
[self
.index2
]
1644 if sharedata
.value1
not in sharedata
.values1
:
1645 for hasvalue
in list(sharedata
.values1
.keys()):
1646 if hasvalue
- self
.epsilon
<= sharedata
.value1
<= hasvalue
+ self
.epsilon
:
1647 sharedata
.value1
= hasvalue
1650 sharedata
.values1
[sharedata
.value1
] = 1
1651 if sharedata
.value2
not in sharedata
.values2
:
1652 for hasvalue
in list(sharedata
.values2
.keys()):
1653 if hasvalue
- self
.epsilon
<= sharedata
.value2
<= hasvalue
+ self
.epsilon
:
1654 sharedata
.value2
= hasvalue
1657 sharedata
.values2
[sharedata
.value2
] = 1
1658 data
= sharedata
.vposavailable
, sharedata
.vposvalid
, sharedata
.vpos
[:]
1659 sharedata
.data12
.setdefault(sharedata
.value1
, {})[sharedata
.value2
] = data
1660 sharedata
.data21
.setdefault(sharedata
.value2
, {})[sharedata
.value1
] = data
1662 registerdefaultprovider(gridpos(), gridpos
.providesdata
)
1667 needsdata
= ["values1", "values2", "data12", "data21"]
1669 defaultgridattrs
= [line
.changelinestyle
]
1671 def __init__(self
, gridlines1
=1, gridlines2
=1, gridattrs
=[], **kwargs
):
1672 _line
.__init
__(self
, **kwargs
)
1673 self
.gridlines1
= gridlines1
1674 self
.gridlines2
= gridlines2
1675 self
.gridattrs
= gridattrs
1677 def selectstyle(self
, privatedata
, sharedata
, graph
, selectindex
, selecttotal
):
1678 if self
.gridattrs
is not None:
1679 privatedata
.gridattrs
= attr
.selectattrs(self
.defaultgridattrs
+ self
.gridattrs
, selectindex
, selecttotal
)
1681 privatedata
.gridattrs
= None
1683 def donedrawpoints(self
, privatedata
, sharedata
, graph
):
1684 values1
= list(sharedata
.values1
.keys())
1686 values2
= list(sharedata
.values2
.keys())
1689 for value2
in values2
:
1690 data1
= sharedata
.data21
[value2
]
1691 self
.initpointstopath(privatedata
)
1692 for value1
in values1
:
1694 data
= data1
[value1
]
1696 self
.addinvalid(privatedata
)
1698 self
.addpoint(privatedata
, graph
.vpos_pt
, *data
)
1699 p
= self
.donepointstopath(privatedata
)
1701 graph
.layer("data").stroke(p
, privatedata
.gridattrs
)
1703 for value1
in values1
:
1704 data2
= sharedata
.data12
[value1
]
1705 self
.initpointstopath(privatedata
)
1706 for value2
in values2
:
1708 data
= data2
[value2
]
1710 self
.addinvalid(privatedata
)
1712 self
.addpoint(privatedata
, graph
.vpos_pt
, *data
)
1713 p
= self
.donepointstopath(privatedata
)
1715 graph
.layer("data").stroke(p
, privatedata
.gridattrs
)
1718 class surface(_keygraphstyle
):
1720 needsdata
= ["values1", "values2", "data12", "data21"]
1722 def __init__(self
, gridlines1
=0.05, gridlines2
=0.05, gridcolor
=None,
1723 backcolor
=color
.gray
.black
, **kwargs
):
1724 _keygraphstyle
.__init
__(self
, **kwargs
)
1725 self
.gridlines1
= gridlines1
1726 self
.gridlines2
= gridlines2
1727 self
.gridcolor
= gridcolor
1728 self
.backcolor
= backcolor
1730 colorspacestring
= self
.gradient
.getcolor(0).colorspacestring()
1731 if self
.gridcolor
is not None and self
.gridcolor
.colorspacestring() != colorspacestring
:
1732 raise RuntimeError("colorspace mismatch (gradient/grid)")
1733 if self
.backcolor
is not None and self
.backcolor
.colorspacestring() != colorspacestring
:
1734 raise RuntimeError("colorspace mismatch (gradient/back)")
1736 def midvalue(self
, v1
, v2
, v3
, v4
):
1737 return [0.25*sum(values
) for values
in zip(v1
, v2
, v3
, v4
)]
1739 def midcolor(self
, c1
, c2
, c3
, c4
):
1740 return 0.25*(c1
+c2
+c3
+c4
)
1742 def lighting(self
, angle
, zindex
):
1743 if angle
< 0 and self
.backcolor
is not None:
1744 return self
.backcolor
1745 return self
.gradient
.getcolor(0.7-0.4*abs(angle
)+0.1*zindex
)
1747 def columnnames(self
, privatedata
, sharedata
, graph
, columnnames
, dataaxisnames
):
1748 privatedata
.colorize
= self
.colorname
in columnnames
1749 if privatedata
.colorize
:
1750 return _keygraphstyle
.columnnames(self
, privatedata
, sharedata
, graph
, columnnames
, dataaxisnames
)
1753 def initdrawpoints(self
, privatedata
, sharedata
, graph
):
1754 privatedata
.colors
= {}
1756 def drawpoint(self
, privatedata
, sharedata
, graph
, point
):
1757 if privatedata
.colorize
:
1758 privatedata
.colors
.setdefault(sharedata
.value1
, {})[sharedata
.value2
] = point
[self
.colorname
]
1760 def donedrawpoints(self
, privatedata
, sharedata
, graph
):
1761 v1
= [0]*len(graph
.axesnames
)
1762 v2
= [0]*len(graph
.axesnames
)
1763 v3
= [0]*len(graph
.axesnames
)
1764 v4
= [0]*len(graph
.axesnames
)
1765 v1
[sharedata
.index2
] = 0.5
1766 v2
[sharedata
.index1
] = 0.5
1767 v3
[sharedata
.index1
] = 0.5
1768 v3
[sharedata
.index2
] = 1
1769 v4
[sharedata
.index1
] = 1
1770 v4
[sharedata
.index2
] = 0.5
1771 sortElements
= [-graph
.vzindex(*v1
),
1772 -graph
.vzindex(*v2
),
1773 -graph
.vzindex(*v3
),
1774 -graph
.vzindex(*v4
)]
1776 values1
= list(sharedata
.values1
.keys())
1778 v1
= [0]*len(graph
.axesnames
)
1779 v2
= [0]*len(graph
.axesnames
)
1780 v1
[sharedata
.index1
] = -1
1781 v2
[sharedata
.index1
] = 1
1783 if graph
.vzindex(*v1
) < graph
.vzindex(*v2
):
1786 sortElements
= [sortElements
[3], sortElements
[1], sortElements
[2], sortElements
[0]]
1788 values2
= list(sharedata
.values2
.keys())
1790 v1
= [0]*len(graph
.axesnames
)
1791 v2
= [0]*len(graph
.axesnames
)
1792 v1
[sharedata
.index2
] = -1
1793 v2
[sharedata
.index2
] = 1
1794 if graph
.vzindex(*v1
) < graph
.vzindex(*v2
):
1797 sortElements
= [sortElements
[0], sortElements
[2], sortElements
[1], sortElements
[3]]
1799 sortElements
= [(zindex
, i
) for i
, zindex
in enumerate(sortElements
)]
1804 for value1a
, value1b
in zip(values1
[:-1], values1
[1:]):
1805 for value2a
, value2b
in zip(values2
[:-1], values2
[1:]):
1807 available1
, valid1
, v1
= sharedata
.data12
[value1a
][value2a
]
1808 available2
, valid2
, v2
= sharedata
.data12
[value1a
][value2b
]
1809 available3
, valid3
, v3
= sharedata
.data12
[value1b
][value2a
]
1810 available4
, valid4
, v4
= sharedata
.data12
[value1b
][value2b
]
1813 if not available1
or not available2
or not available3
or not available4
:
1815 if not valid1
or not valid2
or not valid3
or not valid4
:
1816 logger
.warning("surface elements partially outside of the graph are (currently) skipped completely")
1818 def shrink(index
, v1
, v2
, by
):
1821 for i
in builtinrange(3):
1823 v1
[i
], v2
[i
] = v1
[i
] + by
*(v2
[i
]-v1
[i
]), v2
[i
] + by
*(v1
[i
]-v2
[i
])
1825 v1f
, v2f
, v3f
, v4f
= v1
, v2
, v3
, v4
1826 if self
.gridcolor
is not None and self
.gridlines1
:
1827 v1
, v2
= shrink(sharedata
.index1
, v1
, v2
, self
.gridlines1
)
1828 v3
, v4
= shrink(sharedata
.index1
, v3
, v4
, self
.gridlines1
)
1829 if self
.gridcolor
is not None and self
.gridlines2
:
1830 v1
, v3
= shrink(sharedata
.index2
, v1
, v3
, self
.gridlines2
)
1831 v2
, v4
= shrink(sharedata
.index2
, v2
, v4
, self
.gridlines2
)
1832 v5
= self
.midvalue(v1
, v2
, v3
, v4
)
1833 x1_pt
, y1_pt
= graph
.vpos_pt(*v1
)
1834 x2_pt
, y2_pt
= graph
.vpos_pt(*v2
)
1835 x3_pt
, y3_pt
= graph
.vpos_pt(*v3
)
1836 x4_pt
, y4_pt
= graph
.vpos_pt(*v4
)
1837 x5_pt
, y5_pt
= graph
.vpos_pt(*v5
)
1838 if privatedata
.colorize
:
1839 c1
= privatedata
.colors
[value1a
][value2a
]
1840 c2
= privatedata
.colors
[value1a
][value2b
]
1841 c3
= privatedata
.colors
[value1b
][value2a
]
1842 c4
= privatedata
.colors
[value1b
][value2b
]
1843 c5
= self
.midcolor(c1
, c2
, c3
, c4
)
1844 c1a
= c1b
= self
.color(privatedata
, c1
)
1845 c2a
= c2c
= self
.color(privatedata
, c2
)
1846 c3b
= c3d
= self
.color(privatedata
, c3
)
1847 c4c
= c4d
= self
.color(privatedata
, c4
)
1848 c5a
= c5b
= c5c
= c5d
= self
.color(privatedata
, c5
)
1849 if self
.backcolor
is not None and sign
*graph
.vangle(*(v1
+v2
+v5
)) < 0:
1850 c1a
= c2a
= c5a
= self
.backcolor
1851 if self
.backcolor
is not None and sign
*graph
.vangle(*(v3
+v1
+v5
)) < 0:
1852 c3b
= c1b
= c5b
= self
.backcolor
1853 if self
.backcolor
is not None and sign
*graph
.vangle(*(v2
+v4
+v5
)) < 0:
1854 c2c
= c4c
= c5c
= self
.backcolor
1855 if self
.backcolor
is not None and sign
*graph
.vangle(*(v4
+v3
+v5
)) < 0:
1856 c4d
= c3d
= c5d
= self
.backcolor
1858 zindex
= graph
.vzindex(*v5
)
1859 c1a
= c2a
= c5a
= self
.lighting(sign
*graph
.vangle(*(v1
+v2
+v5
)), zindex
)
1860 c3b
= c1b
= c5b
= self
.lighting(sign
*graph
.vangle(*(v3
+v1
+v5
)), zindex
)
1861 c2c
= c4c
= c5c
= self
.lighting(sign
*graph
.vangle(*(v2
+v4
+v5
)), zindex
)
1862 c4d
= c3d
= c5d
= self
.lighting(sign
*graph
.vangle(*(v4
+v3
+v5
)), zindex
)
1863 for zindex
, i
in sortElements
:
1865 elements
.append(mesh
.element((mesh
.node_pt((x1_pt
, y1_pt
), c1a
),
1866 mesh
.node_pt((x2_pt
, y2_pt
), c2a
),
1867 mesh
.node_pt((x5_pt
, y5_pt
), c5a
))))
1868 if self
.gridcolor
is not None and self
.gridlines2
:
1869 elements
.append(mesh
.element((mesh
.node_pt(graph
.vpos_pt(*v1f
), self
.gridcolor
),
1870 mesh
.node_pt(graph
.vpos_pt(*v2
), self
.gridcolor
),
1871 mesh
.node_pt(graph
.vpos_pt(*v1
), self
.gridcolor
))))
1872 elements
.append(mesh
.element((mesh
.node_pt(graph
.vpos_pt(*v1f
), self
.gridcolor
),
1873 mesh
.node_pt(graph
.vpos_pt(*v2
), self
.gridcolor
),
1874 mesh
.node_pt(graph
.vpos_pt(*v2f
), self
.gridcolor
))))
1876 elements
.append(mesh
.element((mesh
.node_pt((x3_pt
, y3_pt
), c3b
),
1877 mesh
.node_pt((x1_pt
, y1_pt
), c1b
),
1878 mesh
.node_pt((x5_pt
, y5_pt
), c5b
))))
1879 if self
.gridcolor
is not None and self
.gridlines1
:
1880 elements
.append(mesh
.element((mesh
.node_pt(graph
.vpos_pt(*v1f
), self
.gridcolor
),
1881 mesh
.node_pt(graph
.vpos_pt(*v3
), self
.gridcolor
),
1882 mesh
.node_pt(graph
.vpos_pt(*v1
), self
.gridcolor
))))
1883 elements
.append(mesh
.element((mesh
.node_pt(graph
.vpos_pt(*v1f
), self
.gridcolor
),
1884 mesh
.node_pt(graph
.vpos_pt(*v3
), self
.gridcolor
),
1885 mesh
.node_pt(graph
.vpos_pt(*v3f
), self
.gridcolor
))))
1887 elements
.append(mesh
.element((mesh
.node_pt((x2_pt
, y2_pt
), c2c
),
1888 mesh
.node_pt((x4_pt
, y4_pt
), c4c
),
1889 mesh
.node_pt((x5_pt
, y5_pt
), c5c
))))
1890 if self
.gridcolor
is not None and self
.gridlines1
:
1891 elements
.append(mesh
.element((mesh
.node_pt(graph
.vpos_pt(*v2f
), self
.gridcolor
),
1892 mesh
.node_pt(graph
.vpos_pt(*v4
), self
.gridcolor
),
1893 mesh
.node_pt(graph
.vpos_pt(*v2
), self
.gridcolor
))))
1894 elements
.append(mesh
.element((mesh
.node_pt(graph
.vpos_pt(*v2f
), self
.gridcolor
),
1895 mesh
.node_pt(graph
.vpos_pt(*v4
), self
.gridcolor
),
1896 mesh
.node_pt(graph
.vpos_pt(*v4f
), self
.gridcolor
))))
1898 elements
.append(mesh
.element((mesh
.node_pt((x4_pt
, y4_pt
), c4d
),
1899 mesh
.node_pt((x3_pt
, y3_pt
), c3d
),
1900 mesh
.node_pt((x5_pt
, y5_pt
), c5d
))))
1901 if self
.gridcolor
is not None and self
.gridlines2
:
1902 elements
.append(mesh
.element((mesh
.node_pt(graph
.vpos_pt(*v3f
), self
.gridcolor
),
1903 mesh
.node_pt(graph
.vpos_pt(*v4
), self
.gridcolor
),
1904 mesh
.node_pt(graph
.vpos_pt(*v3
), self
.gridcolor
))))
1905 elements
.append(mesh
.element((mesh
.node_pt(graph
.vpos_pt(*v3f
), self
.gridcolor
),
1906 mesh
.node_pt(graph
.vpos_pt(*v4
), self
.gridcolor
),
1907 mesh
.node_pt(graph
.vpos_pt(*v4f
), self
.gridcolor
))))
1908 m
= mesh
.mesh(elements
, check
=0)
1909 graph
.layer("filldata").insert(m
)
1911 if privatedata
.colorize
:
1912 _keygraphstyle
.donedrawpoints(self
, privatedata
, sharedata
, graph
)
1915 class density(_keygraphstyle
):
1917 needsdata
= ["values1", "values2", "data12", "data21"]
1919 def __init__(self
, epsilon
=1e-10, **kwargs
):
1920 _keygraphstyle
.__init
__(self
, **kwargs
)
1921 self
.epsilon
= epsilon
1923 def initdrawpoints(self
, privatedata
, sharedata
, graph
):
1924 privatedata
.colors
= {}
1925 privatedata
.vfixed
= [None]*len(graph
.axesnames
)
1927 def drawpoint(self
, privatedata
, sharedata
, graph
, point
):
1928 privatedata
.colors
.setdefault(sharedata
.value1
, {})[sharedata
.value2
] = point
[self
.colorname
]
1929 if len(privatedata
.vfixed
) > 2 and sharedata
.vposavailable
:
1930 for i
, (v1
, v2
) in enumerate(list(zip(privatedata
.vfixed
, sharedata
.vpos
))):
1931 if i
!= sharedata
.index1
and i
!= sharedata
.index2
:
1933 privatedata
.vfixed
[i
] = v2
1934 elif abs(v1
-v2
) > self
.epsilon
:
1935 raise ValueError("data must be in a plane for the density style")
1937 def donedrawpoints(self
, privatedata
, sharedata
, graph
):
1938 privatedata
.keygraph
.doaxes()
1940 values1
= sorted(list(sharedata
.values1
.keys()))
1941 values2
= sorted(list(sharedata
.values2
.keys()))
1942 def equidistant(values
):
1945 raise ValueError("several data points required by the density style in each dimension")
1946 range = values
[-1] - values
[0]
1947 for i
, value
in enumerate(values
):
1948 if abs(value
- values
[0] - i
* range / l
) > self
.epsilon
:
1949 raise ValueError("data must be equidistant for the density style")
1950 equidistant(values1
)
1951 equidistant(values2
)
1953 for value2
in values2
:
1954 for value1
in values1
:
1956 available
, valid
, v
= sharedata
.data12
[value1
][value2
]
1966 mode
= {"/DeviceGray": "L",
1967 "/DeviceRGB": "RGB",
1968 "/DeviceCMYK": "CMYK"}[self
.gradient
.getcolor(0).colorspacestring()]
1971 empty
= b
"\0"*len(mode
)
1973 for value2
in values2
:
1974 for value1
in values1
:
1976 available
, valid
, v
= sharedata
.data12
[value1
][value2
]
1983 c
= privatedata
.colors
[value1
][value2
]
1984 c
= self
.color(privatedata
, c
)
1986 data
.write(bytes((255,)))
1987 data
.write(c
.to8bitbytes())
1988 i
= bitmap
.image(len(values1
), len(values2
), mode
, data
.getvalue())
1990 v1enlargement
= (values1
[-1]-values1
[0])*0.5/(len(values1
)-1)
1991 v2enlargement
= (values2
[-1]-values2
[0])*0.5/(len(values2
)-1)
1993 privatedata
.vfixed
[sharedata
.index1
] = values1
[0]-v1enlargement
1994 privatedata
.vfixed
[sharedata
.index2
] = values2
[-1]+v2enlargement
1995 x1_pt
, y1_pt
= graph
.vpos_pt(*privatedata
.vfixed
)
1996 privatedata
.vfixed
[sharedata
.index1
] = values1
[-1]+v1enlargement
1997 privatedata
.vfixed
[sharedata
.index2
] = values2
[-1]+v2enlargement
1998 x2_pt
, y2_pt
= graph
.vpos_pt(*privatedata
.vfixed
)
1999 privatedata
.vfixed
[sharedata
.index1
] = values1
[0]-v1enlargement
2000 privatedata
.vfixed
[sharedata
.index2
] = values2
[0]-v2enlargement
2001 x3_pt
, y3_pt
= graph
.vpos_pt(*privatedata
.vfixed
)
2002 t
= trafo
.trafo_pt(((x2_pt
-x1_pt
, x3_pt
-x1_pt
), (y2_pt
-y1_pt
, y3_pt
-y1_pt
)), (x1_pt
, y1_pt
))
2004 privatedata
.vfixed
[sharedata
.index1
] = values1
[-1]+v1enlargement
2005 privatedata
.vfixed
[sharedata
.index2
] = values2
[0]-v2enlargement
2006 vx4
, vy4
= t
.inverse().apply_pt(*graph
.vpos_pt(*privatedata
.vfixed
))
2007 if abs(vx4
- 1) > self
.epsilon
or abs(vy4
- 1) > self
.epsilon
:
2008 raise ValueError("invalid graph layout for density style (bitmap positioning by affine transformation failed)")
2011 privatedata
.vfixed
[sharedata
.index1
] = 0
2012 privatedata
.vfixed
[sharedata
.index2
] = 0
2013 p
.append(path
.moveto_pt(*graph
.vpos_pt(*privatedata
.vfixed
)))
2014 vfixed2
= privatedata
.vfixed
+ privatedata
.vfixed
2015 vfixed2
[sharedata
.index1
] = 0
2016 vfixed2
[sharedata
.index2
] = 0
2017 vfixed2
[sharedata
.index1
+ len(graph
.axesnames
)] = 1
2018 vfixed2
[sharedata
.index2
+ len(graph
.axesnames
)] = 0
2019 p
.append(graph
.vgeodesic_el(*vfixed2
))
2020 vfixed2
[sharedata
.index1
] = 1
2021 vfixed2
[sharedata
.index2
] = 0
2022 vfixed2
[sharedata
.index1
+ len(graph
.axesnames
)] = 1
2023 vfixed2
[sharedata
.index2
+ len(graph
.axesnames
)] = 1
2024 p
.append(graph
.vgeodesic_el(*vfixed2
))
2025 vfixed2
[sharedata
.index1
] = 1
2026 vfixed2
[sharedata
.index2
] = 1
2027 vfixed2
[sharedata
.index1
+ len(graph
.axesnames
)] = 0
2028 vfixed2
[sharedata
.index2
+ len(graph
.axesnames
)] = 1
2029 p
.append(graph
.vgeodesic_el(*vfixed2
))
2030 vfixed2
[sharedata
.index1
] = 0
2031 vfixed2
[sharedata
.index2
] = 1
2032 vfixed2
[sharedata
.index1
+ len(graph
.axesnames
)] = 0
2033 vfixed2
[sharedata
.index2
+ len(graph
.axesnames
)] = 0
2034 p
.append(graph
.vgeodesic_el(*vfixed2
))
2035 p
.append(path
.closepath())
2037 c
= canvas
.canvas([canvas
.clip(p
)])
2038 b
= bitmap
.bitmap_trafo(t
, i
)
2040 graph
.layer("filldata").insert(c
)
2042 _keygraphstyle
.donedrawpoints(self
, privatedata
, sharedata
, graph
)
2046 class gradient(_style
):
2048 defaultbarattrs
= [color
.gradient
.Rainbow
, deco
.stroked([color
.grey
.black
])]
2050 def __init__(self
, gradient
=color
.gradient
.Gray
, resolution
=100, columnname
="x"):
2051 self
.gradient
= gradient
2052 self
.resolution
= resolution
2053 self
.columnname
= columnname
2055 def columnnames(self
, privatedata
, sharedata
, graph
, columnnames
, dataaxisnames
):
2056 return [self
.columnname
]
2058 def adjustaxis(self
, privatedata
, sharedata
, graph
, plotitem
, columnname
, data
):
2059 graph
.axes
[self
.columnname
].adjustaxis(data
)
2061 def selectstyle(self
, privatedata
, sharedata
, graph
, selectindex
, selecttotal
):
2064 def initdrawpoints(self
, privatedata
, sharedata
, graph
):
2067 def drawpoint(self
, privatedata
, sharedata
, graph
, point
):
2070 def donedrawpoints(self
, privatedata
, sharedata
, graph
):
2071 mode
= {"/DeviceGray": "L",
2072 "/DeviceRGB": "RGB",
2073 "/DeviceCMYK": "CMYK"}[self
.gradient
.getcolor(0).colorspacestring()]
2075 for i
in builtinrange(self
.resolution
):
2076 c
= self
.gradient
.getcolor(i
*1.0/self
.resolution
)
2077 data
.write(c
.to8bitbytes())
2078 i
= bitmap
.image(self
.resolution
, 1, mode
, data
.getvalue())
2080 llx_pt
, lly_pt
= graph
.vpos_pt(0)
2081 urx_pt
, ury_pt
= graph
.vpos_pt(1)
2083 if graph
.direction
== "horizontal":
2084 lly_pt
-= 0.5*graph
.height_pt
2085 ury_pt
+= 0.5*graph
.height_pt
2087 llx_pt
-= 0.5*graph
.width_pt
2088 urx_pt
+= 0.5*graph
.width_pt
2090 c
= canvas
.canvas([canvas
.clip(path
.rect_pt(llx_pt
, lly_pt
, urx_pt
-llx_pt
, ury_pt
-lly_pt
))])
2092 if graph
.direction
== "horizontal":
2093 add_pt
= (urx_pt
-llx_pt
)*0.5/(self
.resolution
-1)
2097 add_pt
= (ury_pt
-lly_pt
)*0.5/(self
.resolution
-1)
2101 if graph
.direction
== "horizontal":
2102 t
= trafo
.trafo_pt(((urx_pt
-llx_pt
,0 ), (0, ury_pt
-lly_pt
)), (llx_pt
, lly_pt
))
2104 t
= trafo
.trafo_pt(((0, urx_pt
-llx_pt
), (ury_pt
-lly_pt
, 0)), (llx_pt
, lly_pt
))
2106 b
= bitmap
.bitmap_trafo(t
, i
)
2108 graph
.layer("filldata").insert(c
)