remove warnings module in favor of logging
[PyX.git] / pyx / graph / style.py
blob7a0d2f59b01b9f06d3a37a4967e7b74d8b78a313
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
29 from . import axis
31 builtinrange = range
32 logger = logging.getLogger("pyx")
35 class _style:
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
52 modified:
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
58 sharedata instance.
59 """
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
74 name."""
75 return []
77 def adjustaxis(self, privatedata, sharedata, graph, plotitem, columnname, data):
78 """Adjust axis range
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)."""
83 pass
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."""
90 pass
92 def initdrawpoints(self, privatedata, sharedata, graph):
93 """Initialize drawing of data
95 This method might be used to initialize the drawing of data."""
96 pass
98 def drawpoint(self, privatedata, sharedata, graph, point):
99 """Draw data
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."""
104 pass
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."""
111 pass
113 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
114 """Draw graph key"""
115 pass
118 class _marker: pass
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()
141 else:
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!?
148 else:
149 coloraxis = self.coloraxis
150 privatedata.keygraph = graphx(x=coloraxis, **graph.autokeygraphattrs())
151 else:
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)
159 if vc < 0:
160 logger.warning("gradient color range is exceeded (lower bound)")
161 vc = 0
162 if vc > 1:
163 logger.warning("gradient color range is exceeded (upper bound)")
164 vc = 1
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)])
172 class pos(_style):
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:
186 try:
187 usename = self.usenames[axisname]
188 except KeyError:
189 usename = 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
203 # usenames).
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:
220 pointpostmp[1] += 1
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:
226 try:
227 v = axis.convert(point[columnname])
228 except (ArithmeticError, ValueError, TypeError):
229 sharedata.vposavailable = sharedata.vposvalid = 0
230 sharedata.vpos[index] = None
231 else:
232 if v < -self.epsilon or v > 1+self.epsilon:
233 sharedata.vposvalid = 0
234 sharedata.vpos[index] = v
237 registerdefaultprovider(pos(), pos.providesdata)
240 class range(_style):
242 providesdata = ["vrange", "vrangemissing", "vrangeminmissing", "vrangemaxmissing"]
244 # internal bit masks
245 mask_value = 1
246 mask_min = 2
247 mask_max = 4
248 mask_dmin = 8
249 mask_dmax = 16
250 mask_d = 32
252 def __init__(self, usenames={}, epsilon=1e-10):
253 self.usenames = usenames
254 self.epsilon = epsilon
256 def _numberofbits(self, mask):
257 if not mask:
258 return 0
259 if mask & 1:
260 return self._numberofbits(mask >> 1) + 1
261 else:
262 return self._numberofbits(mask >> 1)
264 def columnnames(self, privatedata, sharedata, graph, columnnames, dataaxisnames):
265 usecolumns = []
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:
273 try:
274 usename = self.usenames[axisname]
275 except KeyError:
276 usename = axisname
277 mask = 0
278 for columnname in columnnames:
279 addusecolumns = 1
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:
291 mask += self.mask_d
292 else:
293 addusecolumns = 0
294 if addusecolumns:
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)
314 else:
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)
319 return usecolumns
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):
345 mindata = []
346 for value, delta in zip(d[self.mask_value], d[k]):
347 try:
348 mindata.append(value-delta)
349 except:
350 pass
351 graph.axes[a].adjustaxis(mindata)
352 if k & (self.mask_dmax | self.mask_d):
353 maxdata = []
354 for value, delta in zip(d[self.mask_value], d[k]):
355 try:
356 maxdata.append(value+delta)
357 except:
358 pass
359 graph.axes[a].adjustaxis(maxdata)
360 del d[k]
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:
369 rangepostmp[2] += 1
371 def drawpoint(self, privatedata, sharedata, graph, point):
372 for usename, mask, index, axis in privatedata.rangepostmplist:
373 try:
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
382 try:
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
392 # some range checks for data consistency
393 if (sharedata.vrange[index][0] is not None and sharedata.vrange[index][1] is not None and
394 sharedata.vrange[index][0] > sharedata.vrange[index][1] + self.epsilon):
395 raise ValueError("inverse range")
396 # disabled due to missing vpos access:
397 # if (sharedata.vrange[index][0] is not None and sharedata.vpos[index] is not None and
398 # sharedata.vrange[index][0] > sharedata.vpos[index] + self.epsilon):
399 # raise ValueError("negative minimum errorbar")
400 # if (sharedata.vrange[index][1] is not None and sharedata.vpos[index] is not None and
401 # sharedata.vrange[index][1] < sharedata.vpos[index] - self.epsilon):
402 # raise ValueError("negative maximum errorbar")
405 registerdefaultprovider(range(), range.providesdata)
408 def _crosssymbol(c, x_pt, y_pt, size_pt, attrs):
409 c.draw(path.path(path.moveto_pt(x_pt-0.5*size_pt, y_pt-0.5*size_pt),
410 path.lineto_pt(x_pt+0.5*size_pt, y_pt+0.5*size_pt),
411 path.moveto_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)), attrs)
414 def _plussymbol(c, x_pt, y_pt, size_pt, attrs):
415 c.draw(path.path(path.moveto_pt(x_pt-0.707106781*size_pt, y_pt),
416 path.lineto_pt(x_pt+0.707106781*size_pt, y_pt),
417 path.moveto_pt(x_pt, y_pt-0.707106781*size_pt),
418 path.lineto_pt(x_pt, y_pt+0.707106781*size_pt)), attrs)
420 def _squaresymbol(c, x_pt, y_pt, size_pt, attrs):
421 c.draw(path.path(path.moveto_pt(x_pt-0.5*size_pt, y_pt-0.5*size_pt),
422 path.lineto_pt(x_pt+0.5*size_pt, y_pt-0.5*size_pt),
423 path.lineto_pt(x_pt+0.5*size_pt, y_pt+0.5*size_pt),
424 path.lineto_pt(x_pt-0.5*size_pt, y_pt+0.5*size_pt),
425 path.closepath()), attrs)
427 def _trianglesymbol(c, x_pt, y_pt, size_pt, attrs):
428 c.draw(path.path(path.moveto_pt(x_pt-0.759835685*size_pt, y_pt-0.438691337*size_pt),
429 path.lineto_pt(x_pt+0.759835685*size_pt, y_pt-0.438691337*size_pt),
430 path.lineto_pt(x_pt, y_pt+0.877382675*size_pt),
431 path.closepath()), attrs)
433 def _circlesymbol(c, x_pt, y_pt, size_pt, attrs):
434 c.draw(path.path(path.arc_pt(x_pt, y_pt, 0.564189583*size_pt, 0, 360),
435 path.closepath()), attrs)
437 def _diamondsymbol(c, x_pt, y_pt, size_pt, attrs):
438 c.draw(path.path(path.moveto_pt(x_pt-0.537284965*size_pt, y_pt),
439 path.lineto_pt(x_pt, y_pt-0.930604859*size_pt),
440 path.lineto_pt(x_pt+0.537284965*size_pt, y_pt),
441 path.lineto_pt(x_pt, y_pt+0.930604859*size_pt),
442 path.closepath()), attrs)
445 class _styleneedingpointpos(_style):
447 needsdata = ["vposmissing"]
449 def columnnames(self, privatedata, sharedata, graph, columnnames, dataaxisnames):
450 if len(sharedata.vposmissing):
451 raise ValueError("incomplete position information")
452 return []
455 class symbol(_styleneedingpointpos):
457 needsdata = ["vpos", "vposmissing", "vposvalid"]
459 # "inject" the predefinied symbols into the class:
461 # Note, that statements like cross = _crosssymbol are
462 # invalid, since the would lead to unbound methods, but
463 # a single entry changeable list does the trick.
465 # Once we require Python 2.2+ we should use staticmethods
466 # to implement the default symbols inplace.
468 cross = attr.changelist([_crosssymbol])
469 plus = attr.changelist([_plussymbol])
470 square = attr.changelist([_squaresymbol])
471 triangle = attr.changelist([_trianglesymbol])
472 circle = attr.changelist([_circlesymbol])
473 diamond = attr.changelist([_diamondsymbol])
475 changecross = attr.changelist([_crosssymbol, _plussymbol, _squaresymbol, _trianglesymbol, _circlesymbol, _diamondsymbol])
476 changeplus = attr.changelist([_plussymbol, _squaresymbol, _trianglesymbol, _circlesymbol, _diamondsymbol, _crosssymbol])
477 changesquare = attr.changelist([_squaresymbol, _trianglesymbol, _circlesymbol, _diamondsymbol, _crosssymbol, _plussymbol])
478 changetriangle = attr.changelist([_trianglesymbol, _circlesymbol, _diamondsymbol, _crosssymbol, _plussymbol, _squaresymbol])
479 changecircle = attr.changelist([_circlesymbol, _diamondsymbol, _crosssymbol, _plussymbol, _squaresymbol, _trianglesymbol])
480 changediamond = attr.changelist([_diamondsymbol, _crosssymbol, _plussymbol, _squaresymbol, _trianglesymbol, _circlesymbol])
481 changesquaretwice = attr.changelist([_squaresymbol, _squaresymbol, _trianglesymbol, _trianglesymbol, _circlesymbol, _circlesymbol, _diamondsymbol, _diamondsymbol])
482 changetriangletwice = attr.changelist([_trianglesymbol, _trianglesymbol, _circlesymbol, _circlesymbol, _diamondsymbol, _diamondsymbol, _squaresymbol, _squaresymbol])
483 changecircletwice = attr.changelist([_circlesymbol, _circlesymbol, _diamondsymbol, _diamondsymbol, _squaresymbol, _squaresymbol, _trianglesymbol, _trianglesymbol])
484 changediamondtwice = attr.changelist([_diamondsymbol, _diamondsymbol, _squaresymbol, _squaresymbol, _trianglesymbol, _trianglesymbol, _circlesymbol, _circlesymbol])
486 changestrokedfilled = attr.changelist([deco.stroked, deco.filled])
487 changefilledstroked = attr.changelist([deco.filled, deco.stroked])
489 defaultsymbolattrs = [deco.stroked]
491 def __init__(self, symbol=changecross, size=0.2*unit.v_cm, symbolattrs=[]):
492 self.symbol = symbol
493 self.size = size
494 self.symbolattrs = symbolattrs
496 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
497 privatedata.symbol = attr.selectattr(self.symbol, selectindex, selecttotal)
498 privatedata.size_pt = unit.topt(attr.selectattr(self.size, selectindex, selecttotal))
499 if self.symbolattrs is not None:
500 privatedata.symbolattrs = attr.selectattrs(self.defaultsymbolattrs + self.symbolattrs, selectindex, selecttotal)
501 else:
502 privatedata.symbolattrs = None
504 def initdrawpoints(self, privatedata, sharedata, graph):
505 privatedata.symbolcanvas = canvas.canvas()
507 def drawpoint(self, privatedata, sharedata, graph, point):
508 if sharedata.vposvalid and privatedata.symbolattrs is not None:
509 x_pt, y_pt = graph.vpos_pt(*sharedata.vpos)
510 privatedata.symbol(privatedata.symbolcanvas, x_pt, y_pt, privatedata.size_pt, privatedata.symbolattrs)
512 def donedrawpoints(self, privatedata, sharedata, graph):
513 graph.layer("data").insert(privatedata.symbolcanvas)
515 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
516 if privatedata.symbolattrs is not None:
517 privatedata.symbol(graph, x_pt+0.5*width_pt, y_pt+0.5*height_pt, privatedata.size_pt, privatedata.symbolattrs)
520 class _line(_styleneedingpointpos):
522 # this style is not a complete style, but it provides the basic functionality to
523 # create a line, which is cut at the graph boundaries (or at otherwise invalid points)
525 def __init__(self, epsilon=1e-10):
526 self.epsilon = epsilon
528 def initpointstopath(self, privatedata):
529 privatedata.path = path.path()
530 privatedata.linebasepoints = []
531 privatedata.lastvpos = None
533 def addpointstopath(self, privatedata):
534 # add baselinepoints to privatedata.path
535 if len(privatedata.linebasepoints) > 1:
536 privatedata.path.append(path.moveto_pt(*privatedata.linebasepoints[0]))
537 if len(privatedata.linebasepoints) > 2:
538 privatedata.path.append(path.multilineto_pt(privatedata.linebasepoints[1:]))
539 else:
540 privatedata.path.append(path.lineto_pt(*privatedata.linebasepoints[1]))
541 privatedata.linebasepoints = []
543 def addpoint(self, privatedata, graphvpos_pt, vposavailable, vposvalid, vpos):
544 # append linebasepoints
545 if vposavailable:
546 if len(privatedata.linebasepoints):
547 # the last point was inside the graph
548 if vposvalid: # shortcut for the common case
549 privatedata.linebasepoints.append(graphvpos_pt(*vpos))
550 else:
551 # cut end
552 cut = 1
553 for vstart, vend in zip(privatedata.lastvpos, vpos):
554 if abs(vend - vstart) > self.epsilon:
555 newcut = None
556 if vend > 1:
557 # 1 = vstart + (vend - vstart) * cut
558 newcut = (1 - vstart)/(vend - vstart)
559 if vend < 0:
560 # 0 = vstart + (vend - vstart) * cut
561 newcut = - vstart/(vend - vstart)
562 if newcut is not None and newcut < cut:
563 cut = newcut
564 cutvpos = []
565 for vstart, vend in zip(privatedata.lastvpos, vpos):
566 cutvpos.append(vstart + (vend - vstart) * cut)
567 privatedata.linebasepoints.append(graphvpos_pt(*cutvpos))
568 self.addpointstopath(privatedata)
569 else:
570 # the last point was outside the graph
571 if privatedata.lastvpos is not None:
572 if vposvalid:
573 # cut beginning
574 cut = 0
575 for vstart, vend in zip(privatedata.lastvpos, vpos):
576 if abs(vend - vstart) > self.epsilon:
577 newcut = None
578 if vstart > 1:
579 # 1 = vstart + (vend - vstart) * cut
580 newcut = (1 - vstart)/(vend - vstart)
581 if vstart < 0:
582 # 0 = vstart + (vend - vstart) * cut
583 newcut = - vstart/(vend - vstart)
584 if newcut is not None and newcut > cut:
585 cut = newcut
586 cutvpos = []
587 for vstart, vend in zip(privatedata.lastvpos, vpos):
588 cutvpos.append(vstart + (vend - vstart) * cut)
589 privatedata.linebasepoints.append(graphvpos_pt(*cutvpos))
590 privatedata.linebasepoints.append(graphvpos_pt(*vpos))
591 else:
592 # sometimes cut beginning and end
593 cutfrom = 0
594 cutto = 1
595 for vstart, vend in zip(privatedata.lastvpos, vpos):
596 if vstart > 1 and vend > 1: break
597 if vstart < 0 and vend < 0: break
598 if abs(vend - vstart) > self.epsilon:
599 newcutfrom = None
600 if vstart > 1:
601 newcutfrom = (1 - vstart)/(vend - vstart)
602 if vstart < 0:
603 # 0 = vstart + (vend - vstart) * cutfrom
604 newcutfrom = - vstart/(vend - vstart)
605 if newcutfrom is not None and newcutfrom > cutfrom:
606 cutfrom = newcutfrom
607 newcutto = None
608 if vend > 1:
609 # 1 = vstart + (vend - vstart) * cutto
610 newcutto = (1 - vstart)/(vend - vstart)
611 if vend < 0:
612 # 0 = vstart + (vend - vstart) * cutto
613 newcutto = - vstart/(vend - vstart)
614 if newcutto is not None and newcutto < cutto:
615 cutto = newcutto
616 else:
617 if cutfrom < cutto:
618 cutfromvpos = []
619 cuttovpos = []
620 for vstart, vend in zip(privatedata.lastvpos, vpos):
621 cutfromvpos.append(vstart + (vend - vstart) * cutfrom)
622 cuttovpos.append(vstart + (vend - vstart) * cutto)
623 privatedata.linebasepoints.append(graphvpos_pt(*cutfromvpos))
624 privatedata.linebasepoints.append(graphvpos_pt(*cuttovpos))
625 self.addpointstopath(privatedata)
626 privatedata.lastvpos = vpos[:]
627 else:
628 if len(privatedata.linebasepoints) > 1:
629 self.addpointstopath(privatedata)
630 privatedata.lastvpos = None
632 def addinvalid(self, privatedata):
633 if len(privatedata.linebasepoints) > 1:
634 self.addpointstopath(privatedata)
635 privatedata.lastvpos = None
637 def donepointstopath(self, privatedata):
638 if len(privatedata.linebasepoints) > 1:
639 self.addpointstopath(privatedata)
640 return privatedata.path
643 class line(_line):
645 needsdata = ["vpos", "vposmissing", "vposavailable", "vposvalid"]
647 changelinestyle = attr.changelist([style.linestyle.solid,
648 style.linestyle.dashed,
649 style.linestyle.dotted,
650 style.linestyle.dashdotted])
652 defaultlineattrs = [changelinestyle]
654 def __init__(self, lineattrs=[], **kwargs):
655 _line.__init__(self, **kwargs)
656 self.lineattrs = lineattrs
658 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
659 if self.lineattrs is not None:
660 privatedata.lineattrs = attr.selectattrs(self.defaultlineattrs + self.lineattrs, selectindex, selecttotal)
661 else:
662 privatedata.lineattrs = None
664 def initdrawpoints(self, privatedata, sharedata, graph):
665 self.initpointstopath(privatedata)
667 def drawpoint(self, privatedata, sharedata, graph, point):
668 self.addpoint(privatedata, graph.vpos_pt, sharedata.vposavailable, sharedata.vposvalid, sharedata.vpos)
670 def donedrawpoints(self, privatedata, sharedata, graph):
671 path = self.donepointstopath(privatedata)
672 if privatedata.lineattrs is not None and len(path):
673 graph.layer("data").stroke(path, privatedata.lineattrs)
675 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
676 if privatedata.lineattrs is not None:
677 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)
680 class impulses(_styleneedingpointpos):
682 needsdata = ["vpos", "vposmissing", "vposavailable", "vposvalid", "poscolumnnames"]
684 defaultlineattrs = [line.changelinestyle]
685 defaultfrompathattrs = []
687 def __init__(self, lineattrs=[], fromvalue=0, frompathattrs=[], valueaxisindex=1):
688 self.lineattrs = lineattrs
689 self.fromvalue = fromvalue
690 self.frompathattrs = frompathattrs
691 self.valueaxisindex = valueaxisindex
693 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
694 privatedata.insertfrompath = selectindex == 0
695 if self.lineattrs is not None:
696 privatedata.lineattrs = attr.selectattrs(self.defaultlineattrs + self.lineattrs, selectindex, selecttotal)
697 else:
698 privatedata.lineattrs = None
700 def adjustaxis(self, privatedata, sharedata, graph, plotitem, columnname, data):
701 if self.fromvalue is not None:
702 try:
703 i = sharedata.poscolumnnames.index(columnname)
704 except ValueError:
705 pass
706 else:
707 if i == self.valueaxisindex:
708 graph.axes[sharedata.poscolumnnames[i]].adjustaxis([self.fromvalue])
710 def initdrawpoints(self, privatedata, sharedata, graph):
711 privatedata.impulsescanvas = canvas.canvas()
712 if self.fromvalue is not None:
713 valueaxisname = sharedata.poscolumnnames[self.valueaxisindex]
714 privatedata.vfromvalue = graph.axes[valueaxisname].convert(self.fromvalue)
715 privatedata.vfromvaluecut = 0
716 if privatedata.vfromvalue < 0:
717 privatedata.vfromvalue = 0
718 if privatedata.vfromvalue > 1:
719 privatedata.vfromvalue = 1
720 if self.frompathattrs is not None and privatedata.insertfrompath:
721 graph.layer("data").stroke(graph.axes[valueaxisname].vgridpath(privatedata.vfromvalue),
722 self.defaultfrompathattrs + self.frompathattrs)
723 else:
724 privatedata.vfromvalue = 0
726 def drawpoint(self, privatedata, sharedata, graph, point):
727 if sharedata.vposvalid and privatedata.lineattrs is not None:
728 vpos = sharedata.vpos[:]
729 vpos[self.valueaxisindex] = privatedata.vfromvalue
730 privatedata.impulsescanvas.stroke(graph.vgeodesic(*(vpos + sharedata.vpos)), privatedata.lineattrs)
732 def donedrawpoints(self, privatedata, sharedata, graph):
733 graph.layer("data").insert(privatedata.impulsescanvas)
735 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
736 if privatedata.lineattrs is not None:
737 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)
740 class errorbar(_style):
742 needsdata = ["vpos", "vposmissing", "vposavailable", "vposvalid", "vrange", "vrangeminmissing", "vrangemaxmissing"]
744 defaulterrorbarattrs = []
746 def __init__(self, size=0.1*unit.v_cm,
747 errorbarattrs=[],
748 epsilon=1e-10):
749 self.size = size
750 self.errorbarattrs = errorbarattrs
751 self.epsilon = epsilon
753 def columnnames(self, privatedata, sharedata, graph, columnnames, dataaxisnames):
754 for i in sharedata.vposmissing:
755 if i in sharedata.vrangeminmissing and i in sharedata.vrangemaxmissing:
756 raise ValueError("position and range for a graph dimension missing")
757 return []
759 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
760 privatedata.errorsize_pt = unit.topt(attr.selectattr(self.size, selectindex, selecttotal))
761 privatedata.errorbarattrs = attr.selectattrs(self.defaulterrorbarattrs + self.errorbarattrs, selectindex, selecttotal)
763 def initdrawpoints(self, privatedata, sharedata, graph):
764 if privatedata.errorbarattrs is not None:
765 privatedata.errorbarcanvas = canvas.canvas(privatedata.errorbarattrs)
766 privatedata.dimensionlist = list(builtinrange(len(sharedata.vpos)))
768 def drawpoint(self, privatedata, sharedata, graph, point):
769 if privatedata.errorbarattrs is not None:
770 for i in privatedata.dimensionlist:
771 for j in privatedata.dimensionlist:
772 if (i != j and
773 (sharedata.vpos[j] is None or
774 sharedata.vpos[j] < -self.epsilon or
775 sharedata.vpos[j] > 1+self.epsilon)):
776 break
777 else:
778 if ((sharedata.vrange[i][0] is None and sharedata.vpos[i] is None) or
779 (sharedata.vrange[i][1] is None and sharedata.vpos[i] is None) or
780 (sharedata.vrange[i][0] is None and sharedata.vrange[i][1] is None)):
781 continue
782 vminpos = sharedata.vpos[:]
783 if sharedata.vrange[i][0] is not None:
784 vminpos[i] = sharedata.vrange[i][0]
785 mincap = 1
786 else:
787 mincap = 0
788 if vminpos[i] > 1+self.epsilon:
789 continue
790 if vminpos[i] < -self.epsilon:
791 vminpos[i] = 0
792 mincap = 0
793 vmaxpos = sharedata.vpos[:]
794 if sharedata.vrange[i][1] is not None:
795 vmaxpos[i] = sharedata.vrange[i][1]
796 maxcap = 1
797 else:
798 maxcap = 0
799 if vmaxpos[i] < -self.epsilon:
800 continue
801 if vmaxpos[i] > 1+self.epsilon:
802 vmaxpos[i] = 1
803 maxcap = 0
804 privatedata.errorbarcanvas.stroke(graph.vgeodesic(*(vminpos + vmaxpos)))
805 for j in privatedata.dimensionlist:
806 if i != j:
807 if mincap:
808 privatedata.errorbarcanvas.stroke(graph.vcap_pt(j, privatedata.errorsize_pt, *vminpos))
809 if maxcap:
810 privatedata.errorbarcanvas.stroke(graph.vcap_pt(j, privatedata.errorsize_pt, *vmaxpos))
812 def donedrawpoints(self, privatedata, sharedata, graph):
813 if privatedata.errorbarattrs is not None:
814 graph.layer("data").insert(privatedata.errorbarcanvas)
817 class text(_styleneedingpointpos):
819 needsdata = ["vpos", "vposmissing", "vposvalid"]
821 defaulttextattrs = [textmodule.halign.center, textmodule.vshift.mathaxis]
823 def __init__(self, textname="text", dxname=None, dyname=None,
824 dxunit=0.3*unit.v_cm, dyunit=0.3*unit.v_cm,
825 textdx=0*unit.v_cm, textdy=0.3*unit.v_cm, textattrs=[]):
826 self.textname = textname
827 self.dxname = dxname
828 self.dyname = dyname
829 self.dxunit = dxunit
830 self.dyunit = dyunit
831 self.textdx = textdx
832 self.textdy = textdy
833 self.textattrs = textattrs
835 def columnnames(self, privatedata, sharedata, graph, columnnames, dataaxisnames):
836 if self.textname not in columnnames:
837 raise ValueError("column '%s' missing" % self.textname)
838 names = [self.textname]
839 if self.dxname is not None:
840 if self.dxname not in columnnames:
841 raise ValueError("column '%s' missing" % self.dxname)
842 names.append(self.dxname)
843 if self.dyname is not None:
844 if self.dyname not in columnnames:
845 raise ValueError("column '%s' missing" % self.dyname)
846 names.append(self.dyname)
847 return names + _styleneedingpointpos.columnnames(self, privatedata, sharedata, graph, columnnames, dataaxisnames)
849 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
850 if self.textattrs is not None:
851 privatedata.textattrs = attr.selectattrs(self.defaulttextattrs + self.textattrs, selectindex, selecttotal)
852 else:
853 privatedata.textattrs = None
855 def initdrawpoints(self, privatedata, sharedata, grap):
856 if self.dxname is None:
857 privatedata.textdx_pt = unit.topt(self.textdx)
858 else:
859 privatedata.dxunit_pt = unit.topt(self.dxunit)
860 if self.dyname is None:
861 privatedata.textdy_pt = unit.topt(self.textdy)
862 else:
863 privatedata.dyunit_pt = unit.topt(self.dyunit)
865 def drawpoint(self, privatedata, sharedata, graph, point):
866 if privatedata.textattrs is not None and sharedata.vposvalid:
867 x_pt, y_pt = graph.vpos_pt(*sharedata.vpos)
868 try:
869 text = str(point[self.textname])
870 except:
871 pass
872 else:
873 if self.dxname is None:
874 dx_pt = privatedata.textdx_pt
875 else:
876 dx_pt = float(point[self.dxname]) * privatedata.dxunit_pt
877 if self.dyname is None:
878 dy_pt = privatedata.textdy_pt
879 else:
880 dy_pt = float(point[self.dyname]) * privatedata.dyunit_pt
881 graph.layer("data").text_pt(x_pt + dx_pt, y_pt + dy_pt, text, privatedata.textattrs)
883 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
884 raise RuntimeError("Style currently doesn't provide a graph key")
887 class arrow(_styleneedingpointpos):
889 needsdata = ["vpos", "vposmissing", "vposvalid"]
891 defaultlineattrs = []
892 defaultarrowattrs = []
894 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):
895 self.linelength = linelength
896 self.arrowsize = arrowsize
897 self.lineattrs = lineattrs
898 self.arrowattrs = arrowattrs
899 self.arrowpos = arrowpos
900 self.epsilon = epsilon
901 self.decorator = decorator
903 def columnnames(self, privatedata, sharedata, graph, columnnames, dataaxisnames):
904 if len(graph.axesnames) != 2:
905 raise ValueError("arrow style restricted on two-dimensional graphs")
906 if "size" not in columnnames:
907 raise ValueError("size missing")
908 if "angle" not in columnnames:
909 raise ValueError("angle missing")
910 return ["size", "angle"] + _styleneedingpointpos.columnnames(self, privatedata, sharedata, graph, columnnames, dataaxisnames)
912 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
913 if self.lineattrs is not None:
914 privatedata.lineattrs = attr.selectattrs(self.defaultlineattrs + self.lineattrs, selectindex, selecttotal)
915 else:
916 privatedata.lineattrs = None
917 if self.arrowattrs is not None:
918 privatedata.arrowattrs = attr.selectattrs(self.defaultarrowattrs + self.arrowattrs, selectindex, selecttotal)
919 else:
920 privatedata.arrowattrs = None
922 def initdrawpoints(self, privatedata, sharedata, graph):
923 privatedata.arrowcanvas = canvas.canvas()
925 def drawpoint(self, privatedata, sharedata, graph, point):
926 if privatedata.lineattrs is not None and privatedata.arrowattrs is not None and sharedata.vposvalid:
927 linelength_pt = unit.topt(self.linelength)
928 x_pt, y_pt = graph.vpos_pt(*sharedata.vpos)
929 try:
930 angle = point["angle"] + 0.0
931 size = point["size"] + 0.0
932 except:
933 pass
934 else:
935 if point["size"] > self.epsilon:
936 dx = math.cos(angle*math.pi/180)
937 dy = math.sin(angle*math.pi/180)
938 x1 = x_pt-self.arrowpos*dx*linelength_pt*size
939 y1 = y_pt-self.arrowpos*dy*linelength_pt*size
940 x2 = x_pt+(1-self.arrowpos)*dx*linelength_pt*size
941 y2 = y_pt+(1-self.arrowpos)*dy*linelength_pt*size
942 if self.decorator:
943 privatedata.arrowcanvas.stroke(path.line_pt(x1, y1, x2, y2),
944 privatedata.lineattrs+[self.decorator(privatedata.arrowattrs, size=self.arrowsize*size)])
945 else:
946 privatedata.arrowcanvas.stroke(path.line_pt(x1, y1, x2, y2), privatedata.lineattrs)
948 def donedrawpoints(self, privatedata, sharedata, graph):
949 graph.layer("data").insert(privatedata.arrowcanvas)
951 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
952 raise RuntimeError("Style currently doesn't provide a graph key")
955 class rect(_keygraphstyle):
957 needsdata = ["vrange", "vrangeminmissing", "vrangemaxmissing"]
959 def columnnames(self, privatedata, sharedata, graph, columnnames, dataaxisnames):
960 if len(graph.axesnames) != 2:
961 raise TypeError("rect style restricted on two-dimensional graphs")
962 if len(sharedata.vrangeminmissing) + len(sharedata.vrangemaxmissing):
963 raise ValueError("incomplete range")
964 return _keygraphstyle.columnnames(self, privatedata, sharedata, graph, columnnames, dataaxisnames)
966 def initdrawpoints(self, privatedata, sharedata, graph):
967 privatedata.rectcanvas = graph.layer("filldata").insert(canvas.canvas())
969 def drawpoint(self, privatedata, sharedata, graph, point):
970 xvmin = sharedata.vrange[0][0]
971 xvmax = sharedata.vrange[0][1]
972 yvmin = sharedata.vrange[1][0]
973 yvmax = sharedata.vrange[1][1]
974 if (xvmin is not None and xvmin < 1 and
975 xvmax is not None and xvmax > 0 and
976 yvmin is not None and yvmin < 1 and
977 yvmax is not None and yvmax > 0):
978 if xvmin < 0:
979 xvmin = 0
980 elif xvmax > 1:
981 xvmax = 1
982 if yvmin < 0:
983 yvmin = 0
984 elif yvmax > 1:
985 yvmax = 1
986 p = graph.vgeodesic(xvmin, yvmin, xvmax, yvmin)
987 p.append(graph.vgeodesic_el(xvmax, yvmin, xvmax, yvmax))
988 p.append(graph.vgeodesic_el(xvmax, yvmax, xvmin, yvmax))
989 p.append(graph.vgeodesic_el(xvmin, yvmax, xvmin, yvmin))
990 p.append(path.closepath())
991 privatedata.rectcanvas.fill(p, [self.color(privatedata, point["color"])])
994 class histogram(_style):
996 needsdata = ["vpos", "vposmissing", "vrange", "vrangeminmissing", "vrangemaxmissing"]
998 defaultlineattrs = [deco.stroked]
999 defaultfrompathattrs = []
1001 def __init__(self, lineattrs=[], steps=0, fromvalue=0, frompathattrs=[], fillable=0, rectkey=0,
1002 autohistogramaxisindex=0, autohistogrampointpos=0.5, epsilon=1e-10):
1003 self.lineattrs = lineattrs
1004 self.steps = steps
1005 self.fromvalue = fromvalue
1006 self.frompathattrs = frompathattrs
1007 self.fillable = fillable # TODO: fillable paths might not properly be closed by straight lines on curved graph geometries
1008 self.rectkey = rectkey
1009 self.autohistogramaxisindex = autohistogramaxisindex
1010 self.autohistogrampointpos = autohistogrampointpos
1011 self.epsilon = epsilon
1013 def columnnames(self, privatedata, sharedata, graph, columnnames, dataaxisnames):
1014 if len(graph.axesnames) != 2:
1015 raise TypeError("histogram style restricted on two-dimensional graphs")
1016 privatedata.rangeaxisindex = None
1017 for i in builtinrange(len(graph.axesnames)):
1018 if i in sharedata.vrangeminmissing or i in sharedata.vrangemaxmissing:
1019 if i in sharedata.vposmissing:
1020 raise ValueError("pos and range missing")
1021 else:
1022 if privatedata.rangeaxisindex is not None:
1023 raise ValueError("multiple ranges")
1024 privatedata.rangeaxisindex = i
1025 if privatedata.rangeaxisindex is None:
1026 privatedata.rangeaxisindex = self.autohistogramaxisindex
1027 privatedata.autohistogram = 1
1028 else:
1029 privatedata.autohistogram = 0
1030 return []
1032 def adjustaxis(self, privatedata, sharedata, graph, plotitem, columnname, data):
1033 if privatedata.autohistogram and columnname == sharedata.poscolumnnames[privatedata.rangeaxisindex]:
1034 if len(data) == 1:
1035 raise ValueError("several data points needed for automatic histogram width calculation")
1036 if len(data) > 1:
1037 delta = data[1] - data[0]
1038 min = data[0] - self.autohistogrampointpos * delta
1039 max = data[-1] + (1-self.autohistogrampointpos) * delta
1040 graph.axes[columnname].adjustaxis([min, max])
1041 elif self.fromvalue is not None and columnname == sharedata.poscolumnnames[1-privatedata.rangeaxisindex]:
1042 graph.axes[columnname].adjustaxis([self.fromvalue])
1044 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1045 privatedata.insertfrompath = selectindex == 0
1046 if self.lineattrs is not None:
1047 privatedata.lineattrs = attr.selectattrs(self.defaultlineattrs + self.lineattrs, selectindex, selecttotal)
1048 else:
1049 privatedata.lineattrs = None
1051 def vmoveto(self, privatedata, sharedata, graph, vpos, vvalue):
1052 if -self.epsilon < vpos < 1+self.epsilon and -self.epsilon < vvalue < 1+self.epsilon:
1053 if privatedata.rangeaxisindex:
1054 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue, vpos)))
1055 else:
1056 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vpos, vvalue)))
1058 def vposline(self, privatedata, sharedata, graph, vpos, vvalue1, vvalue2):
1059 if -self.epsilon < vpos < 1+self.epsilon:
1060 vvalue1cut = 0
1061 if vvalue1 < 0:
1062 vvalue1 = 0
1063 vvalue1cut = -1
1064 elif vvalue1 > 1:
1065 vvalue1 = 1
1066 vvalue1cut = 1
1067 vvalue2cut = 0
1068 if vvalue2 < 0:
1069 vvalue2 = 0
1070 vvalue2cut = -1
1071 elif vvalue2 > 1:
1072 vvalue2 = 1
1073 vvalue2cut = 1
1074 if abs(vvalue1cut + vvalue2cut) <= 1:
1075 if vvalue1cut and not self.fillable:
1076 if privatedata.rangeaxisindex:
1077 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue1, vpos)))
1078 else:
1079 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vpos, vvalue1)))
1080 if privatedata.rangeaxisindex:
1081 privatedata.path.append(graph.vgeodesic_el(vvalue1, vpos, vvalue2, vpos))
1082 else:
1083 privatedata.path.append(graph.vgeodesic_el(vpos, vvalue1, vpos, vvalue2))
1085 def vvalueline(self, privatedata, sharedata, graph, vvalue, vpos1, vpos2):
1086 if self.fillable:
1087 if vvalue < -self.epsilon:
1088 vvalue = 0
1089 logger.warning("cut at graph boundary adds artificial lines to fillable step histogram path")
1090 if vvalue > 1+self.epsilon:
1091 vvalue = 1
1092 logger.warning("cut at graph boundary adds artificial lines to fillable step histogram path")
1093 if self.fillable or (-self.epsilon < vvalue < 1+self.epsilon):
1094 vpos1cut = 0
1095 if vpos1 < 0:
1096 vpos1 = 0
1097 vpos1cut = -1
1098 elif vpos1 > 1:
1099 vpos1 = 1
1100 vpos1cut = 1
1101 vpos2cut = 0
1102 if vpos2 < 0:
1103 vpos2 = 0
1104 vpos2cut = -1
1105 elif vpos2 > 1:
1106 vpos2 = 1
1107 vpos2cut = 1
1108 if abs(vpos1cut + vpos2cut) <= 1:
1109 if vpos1cut:
1110 if self.fillable:
1111 if privatedata.rangeaxisindex:
1112 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(privatedata.vfromvalue, vpos1)))
1113 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vpos1, vvalue, vpos1))
1114 else:
1115 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vpos1, privatedata.vfromvalue)))
1116 privatedata.path.append(graph.vgeodesic_el(vpos1, privatedata.vfromvalue, vpos1, vvalue))
1117 else:
1118 if privatedata.rangeaxisindex:
1119 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue, vpos1)))
1120 else:
1121 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vpos1, vvalue)))
1122 if privatedata.rangeaxisindex:
1123 privatedata.path.append(graph.vgeodesic_el(vvalue, vpos1, vvalue, vpos2))
1124 else:
1125 privatedata.path.append(graph.vgeodesic_el(vpos1, vvalue, vpos2, vvalue))
1126 if self.fillable and vpos2cut:
1127 logger.warning("cut at graph boundary adds artificial lines to fillable step histogram path")
1128 if privatedata.rangeaxisindex:
1129 privatedata.path.append(graph.vgeodesic_el(vvalue, vpos2, privatedata.vfromvalue, vpos2))
1130 else:
1131 privatedata.path.append(graph.vgeodesic_el(vpos2, vvalue, vpos2, privatedata.vfromvalue))
1133 def drawvalue(self, privatedata, sharedata, graph, vmin, vmax, vvalue):
1134 currentvalid = vmin is not None and vmax is not None and vvalue is not None
1135 if self.fillable and not self.steps:
1136 if not currentvalid:
1137 return
1138 vmincut = 0
1139 if vmin < -self.epsilon:
1140 vmin = 0
1141 vmincut = -1
1142 elif vmin > 1+self.epsilon:
1143 vmin = 1
1144 vmincut = 1
1145 vmaxcut = 0
1146 if vmax < -self.epsilon:
1147 vmax = 0
1148 vmaxcut = -1
1149 if vmax > 1+self.epsilon:
1150 vmax = 1
1151 vmaxcut = 1
1152 vvaluecut = 0
1153 if vvalue < -self.epsilon:
1154 vvalue = 0
1155 vvaluecut = -1
1156 if vvalue > 1+self.epsilon:
1157 vvalue = 1
1158 vvaluecut = 1
1159 done = 0
1160 if abs(vmincut) + abs(vmaxcut) + abs(vvaluecut) + abs(privatedata.vfromvaluecut) > 1:
1161 if abs(vmincut + vmaxcut) > 1 or abs(vvaluecut+privatedata.vfromvaluecut) > 1:
1162 done = 1
1163 else:
1164 logger.warning("multiple cuts at graph boundary add artificial lines to fillable rectangle histogram path")
1165 elif vmincut:
1166 done = 1
1167 if privatedata.rangeaxisindex:
1168 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(privatedata.vfromvalue, vmin)))
1169 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmin, privatedata.vfromvalue, vmax))
1170 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmax, vvalue, vmax))
1171 privatedata.path.append(graph.vgeodesic_el(vvalue, vmax, vvalue, vmin))
1172 else:
1173 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmin, privatedata.vfromvalue)))
1174 privatedata.path.append(graph.vgeodesic_el(vmin, privatedata.vfromvalue, vmax, privatedata.vfromvalue))
1175 privatedata.path.append(graph.vgeodesic_el(vmax, privatedata.vfromvalue, vmax, vvalue))
1176 privatedata.path.append(graph.vgeodesic_el(vmax, vvalue, vmin, vvalue))
1177 elif vmaxcut:
1178 done = 1
1179 if privatedata.rangeaxisindex:
1180 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue, vmax)))
1181 privatedata.path.append(graph.vgeodesic_el(vvalue, vmax, vvalue, vmin))
1182 privatedata.path.append(graph.vgeodesic_el(vvalue, vmin, privatedata.vfromvalue, vmin))
1183 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmin, privatedata.vfromvalue, vmax))
1184 else:
1185 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmax, vvalue)))
1186 privatedata.path.append(graph.vgeodesic_el(vmax, vvalue, vmin, vvalue))
1187 privatedata.path.append(graph.vgeodesic_el(vmin, vvalue, vmin, privatedata.vfromvalue))
1188 privatedata.path.append(graph.vgeodesic_el(vmin, privatedata.vfromvalue, vmax, privatedata.vfromvalue))
1189 elif privatedata.vfromvaluecut:
1190 done = 1
1191 if privatedata.rangeaxisindex:
1192 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(privatedata.vfromvalue, vmax)))
1193 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmax, vvalue, vmax))
1194 privatedata.path.append(graph.vgeodesic_el(vvalue, vmax, vvalue, vmin))
1195 privatedata.path.append(graph.vgeodesic_el(vvalue, vmin, privatedata.vfromvalue, vmin))
1196 else:
1197 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmax, privatedata.vfromvalue)))
1198 privatedata.path.append(graph.vgeodesic_el(vmax, privatedata.vfromvalue, vmax, vvalue))
1199 privatedata.path.append(graph.vgeodesic_el(vmax, vvalue, vmin, vvalue))
1200 privatedata.path.append(graph.vgeodesic_el(vmin, vvalue, vmin, privatedata.vfromvalue))
1201 elif vvaluecut:
1202 done = 1
1203 if privatedata.rangeaxisindex:
1204 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue, vmin)))
1205 privatedata.path.append(graph.vgeodesic_el(vvalue, vmin, privatedata.vfromvalue, vmin))
1206 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmin, privatedata.vfromvalue, vmax))
1207 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmax, vvalue, vmax))
1208 else:
1209 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmin, vvalue)))
1210 privatedata.path.append(graph.vgeodesic_el(vmin, vvalue, vmin, privatedata.vfromvalue))
1211 privatedata.path.append(graph.vgeodesic_el(vmin, privatedata.vfromvalue, vmax, privatedata.vfromvalue))
1212 privatedata.path.append(graph.vgeodesic_el(vmax, privatedata.vfromvalue, vmax, vvalue))
1213 if not done:
1214 if privatedata.rangeaxisindex:
1215 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(privatedata.vfromvalue, vmin)))
1216 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmin, privatedata.vfromvalue, vmax))
1217 privatedata.path.append(graph.vgeodesic_el(privatedata.vfromvalue, vmax, vvalue, vmax))
1218 privatedata.path.append(graph.vgeodesic_el(vvalue, vmax, vvalue, vmin))
1219 privatedata.path.append(graph.vgeodesic_el(vvalue, vmin, privatedata.vfromvalue, vmin))
1220 privatedata.path.append(path.closepath())
1221 else:
1222 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vmin, privatedata.vfromvalue)))
1223 privatedata.path.append(graph.vgeodesic_el(vmin, privatedata.vfromvalue, vmax, privatedata.vfromvalue))
1224 privatedata.path.append(graph.vgeodesic_el(vmax, privatedata.vfromvalue, vmax, vvalue))
1225 privatedata.path.append(graph.vgeodesic_el(vmax, vvalue, vmin, vvalue))
1226 privatedata.path.append(graph.vgeodesic_el(vmin, vvalue, vmin, privatedata.vfromvalue))
1227 privatedata.path.append(path.closepath())
1228 else:
1229 try:
1230 gap = abs(vmin - privatedata.lastvmax) > self.epsilon
1231 except (ArithmeticError, ValueError, TypeError):
1232 gap = 1
1233 if (privatedata.lastvvalue is not None and currentvalid and not gap and
1234 (self.steps or (privatedata.lastvvalue-privatedata.vfromvalue)*(vvalue-privatedata.vfromvalue) < 0)):
1235 self.vposline(privatedata, sharedata, graph,
1236 vmin, privatedata.lastvvalue, vvalue)
1237 else:
1238 if privatedata.lastvvalue is not None and currentvalid:
1239 currentbigger = abs(privatedata.lastvvalue-privatedata.vfromvalue) < abs(vvalue-privatedata.vfromvalue)
1240 if privatedata.lastvvalue is not None and (not currentvalid or not currentbigger or gap):
1241 self.vposline(privatedata, sharedata, graph,
1242 privatedata.lastvmax, privatedata.lastvvalue, privatedata.vfromvalue)
1243 if currentvalid:
1244 self.vmoveto(privatedata, sharedata, graph,
1245 vmin, vvalue)
1246 if currentvalid and (privatedata.lastvvalue is None or currentbigger or gap):
1247 self.vmoveto(privatedata, sharedata, graph,
1248 vmin, privatedata.vfromvalue)
1249 self.vposline(privatedata, sharedata, graph,
1250 vmin, privatedata.vfromvalue, vvalue)
1251 if currentvalid:
1252 self.vvalueline(privatedata, sharedata, graph,
1253 vvalue, vmin, vmax)
1254 privatedata.lastvvalue = vvalue
1255 privatedata.lastvmax = vmax
1256 else:
1257 privatedata.lastvvalue = privatedata.lastvmax = None
1259 def initdrawpoints(self, privatedata, sharedata, graph):
1260 privatedata.path = path.path()
1261 privatedata.lastvvalue = privatedata.lastvmax = None
1262 privatedata.vcurrentpoint = None
1263 privatedata.count = 0
1264 if self.fromvalue is not None:
1265 valueaxisname = sharedata.poscolumnnames[1-privatedata.rangeaxisindex]
1266 privatedata.vfromvalue = graph.axes[valueaxisname].convert(self.fromvalue)
1267 privatedata.vfromvaluecut = 0
1268 if privatedata.vfromvalue < 0:
1269 privatedata.vfromvalue = 0
1270 privatedata.vfromvaluecut = -1
1271 if privatedata.vfromvalue > 1:
1272 privatedata.vfromvalue = 1
1273 privatedata.vfromvaluecut = 1
1274 if self.frompathattrs is not None and privatedata.insertfrompath:
1275 graph.layer("data").stroke(graph.axes[valueaxisname].vgridpath(privatedata.vfromvalue),
1276 self.defaultfrompathattrs + self.frompathattrs)
1277 else:
1278 privatedata.vfromvalue = 0
1280 def drawpoint(self, privatedata, sharedata, graph, point):
1281 if privatedata.autohistogram:
1282 # automatic range handling
1283 privatedata.count += 1
1284 if privatedata.count == 2:
1285 if privatedata.rangeaxisindex:
1286 privatedata.vrange = sharedata.vpos[1] - privatedata.lastvpos[1]
1287 self.drawvalue(privatedata, sharedata, graph,
1288 privatedata.lastvpos[1] - self.autohistogrampointpos*privatedata.vrange,
1289 privatedata.lastvpos[1] + (1-self.autohistogrampointpos)*privatedata.vrange,
1290 privatedata.lastvpos[0])
1291 else:
1292 privatedata.vrange = sharedata.vpos[0] - privatedata.lastvpos[0]
1293 self.drawvalue(privatedata, sharedata, graph,
1294 privatedata.lastvpos[0] - self.autohistogrampointpos*privatedata.vrange,
1295 privatedata.lastvpos[0] + (1-self.autohistogrampointpos)*privatedata.vrange,
1296 privatedata.lastvpos[1])
1297 elif privatedata.count > 2:
1298 if privatedata.rangeaxisindex:
1299 vrange = sharedata.vpos[1] - privatedata.lastvpos[1]
1300 else:
1301 vrange = sharedata.vpos[0] - privatedata.lastvpos[0]
1302 if abs(privatedata.vrange - vrange) > self.epsilon:
1303 raise ValueError("equal steps (in graph coordinates) needed for automatic width calculation")
1304 if privatedata.count > 1:
1305 if privatedata.rangeaxisindex:
1306 self.drawvalue(privatedata, sharedata, graph,
1307 sharedata.vpos[1] - self.autohistogrampointpos*privatedata.vrange,
1308 sharedata.vpos[1] + (1-self.autohistogrampointpos)*privatedata.vrange,
1309 sharedata.vpos[0])
1310 else:
1311 self.drawvalue(privatedata, sharedata, graph,
1312 sharedata.vpos[0] - self.autohistogrampointpos*privatedata.vrange,
1313 sharedata.vpos[0] + (1-self.autohistogrampointpos)*privatedata.vrange,
1314 sharedata.vpos[1])
1315 privatedata.lastvpos = sharedata.vpos[:]
1316 else:
1317 if privatedata.rangeaxisindex:
1318 self.drawvalue(privatedata, sharedata, graph,
1319 sharedata.vrange[1][0], sharedata.vrange[1][1], sharedata.vpos[0])
1320 else:
1321 self.drawvalue(privatedata, sharedata, graph,
1322 sharedata.vrange[0][0], sharedata.vrange[0][1], sharedata.vpos[1])
1324 def donedrawpoints(self, privatedata, sharedata, graph):
1325 self.drawvalue(privatedata, sharedata, graph, None, None, None)
1326 if privatedata.lineattrs is not None and len(privatedata.path):
1327 graph.layer("data").draw(privatedata.path, privatedata.lineattrs)
1329 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
1330 if privatedata.lineattrs is not None:
1331 if self.rectkey:
1332 p = path.rect_pt(x_pt, y_pt, width_pt, height_pt)
1333 else:
1334 p = path.line_pt(x_pt, y_pt+0.5*height_pt, x_pt+width_pt, y_pt+0.5*height_pt)
1335 graph.draw(p, privatedata.lineattrs)
1338 class barpos(_style):
1340 providesdata = ["vpos", "vposmissing", "vposavailable", "vposvalid", "vbarrange", "barposcolumnnames", "barvalueindex", "lastbarvalue", "stackedbar", "stackedbardraw"]
1342 defaultfrompathattrs = []
1344 def __init__(self, fromvalue=None, frompathattrs=[], epsilon=1e-10):
1345 self.fromvalue = fromvalue
1346 self.frompathattrs = frompathattrs
1347 self.epsilon = epsilon
1349 def columnnames(self, privatedata, sharedata, graph, columnnames, dataaxisnames):
1350 sharedata.barposcolumnnames = []
1351 sharedata.barvalueindex = None
1352 for dimension, axisnames in enumerate(graph.axesnames):
1353 found = 0
1354 for axisname in axisnames:
1355 if axisname in columnnames:
1356 if sharedata.barvalueindex is not None:
1357 raise ValueError("multiple values")
1358 sharedata.barvalueindex = dimension
1359 sharedata.barposcolumnnames.append(axisname)
1360 found += 1
1361 if (axisname + "name") in columnnames:
1362 sharedata.barposcolumnnames.append(axisname + "name")
1363 found += 1
1364 if found > 1:
1365 raise ValueError("multiple names and value")
1366 if not found:
1367 raise ValueError("value/name missing")
1368 if sharedata.barvalueindex is None:
1369 raise ValueError("missing value")
1370 sharedata.vposmissing = []
1371 return sharedata.barposcolumnnames
1373 def addsubvalue(self, value, subvalue):
1374 try:
1375 value + ""
1376 except:
1377 try:
1378 return value[0], self.addsubvalue(value[1], subvalue)
1379 except:
1380 return value, subvalue
1381 else:
1382 return value, subvalue
1384 def adjustaxis(self, privatedata, sharedata, graph, plotitem, columnname, data):
1385 try:
1386 i = sharedata.barposcolumnnames.index(columnname)
1387 except ValueError:
1388 pass
1389 else:
1390 if i == sharedata.barvalueindex:
1391 if self.fromvalue is not None:
1392 graph.axes[sharedata.barposcolumnnames[i]].adjustaxis([self.fromvalue])
1393 graph.axes[sharedata.barposcolumnnames[i]].adjustaxis(data)
1394 else:
1395 graph.axes[sharedata.barposcolumnnames[i][:-4]].adjustaxis([self.addsubvalue(x, 0) for x in data])
1396 graph.axes[sharedata.barposcolumnnames[i][:-4]].adjustaxis([self.addsubvalue(x, 1) for x in data])
1398 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1399 privatedata.insertfrompath = selectindex == 0
1401 def initdrawpoints(self, privatedata, sharedata, graph):
1402 sharedata.vpos = [None]*(len(sharedata.barposcolumnnames))
1403 sharedata.vbarrange = [[None for i in builtinrange(2)] for x in sharedata.barposcolumnnames]
1404 sharedata.stackedbar = sharedata.stackedbardraw = 0
1406 if self.fromvalue is not None:
1407 privatedata.vfromvalue = graph.axes[sharedata.barposcolumnnames[sharedata.barvalueindex]].convert(self.fromvalue)
1408 if privatedata.vfromvalue < 0:
1409 privatedata.vfromvalue = 0
1410 if privatedata.vfromvalue > 1:
1411 privatedata.vfromvalue = 1
1412 if self.frompathattrs is not None and privatedata.insertfrompath:
1413 graph.layer("data").stroke(graph.axes[sharedata.barposcolumnnames[sharedata.barvalueindex]].vgridpath(privatedata.vfromvalue),
1414 self.defaultfrompathattrs + self.frompathattrs)
1415 else:
1416 privatedata.vfromvalue = 0
1418 def drawpoint(self, privatedata, sharedata, graph, point):
1419 sharedata.vposavailable = sharedata.vposvalid = 1
1420 for i, barname in enumerate(sharedata.barposcolumnnames):
1421 if i == sharedata.barvalueindex:
1422 sharedata.vbarrange[i][0] = privatedata.vfromvalue
1423 sharedata.lastbarvalue = point[barname]
1424 try:
1425 sharedata.vpos[i] = sharedata.vbarrange[i][1] = graph.axes[barname].convert(sharedata.lastbarvalue)
1426 except (ArithmeticError, ValueError, TypeError):
1427 sharedata.vpos[i] = sharedata.vbarrange[i][1] = None
1428 else:
1429 for j in builtinrange(2):
1430 try:
1431 sharedata.vbarrange[i][j] = graph.axes[barname[:-4]].convert(self.addsubvalue(point[barname], j))
1432 except (ArithmeticError, ValueError, TypeError):
1433 sharedata.vbarrange[i][j] = None
1434 try:
1435 sharedata.vpos[i] = 0.5*(sharedata.vbarrange[i][0]+sharedata.vbarrange[i][1])
1436 except (ArithmeticError, ValueError, TypeError):
1437 sharedata.vpos[i] = None
1438 if sharedata.vpos[i] is None:
1439 sharedata.vposavailable = sharedata.vposvalid = 0
1440 elif sharedata.vpos[i] < -self.epsilon or sharedata.vpos[i] > 1+self.epsilon:
1441 sharedata.vposvalid = 0
1443 registerdefaultprovider(barpos(), ["vbarrange", "barposcolumnnames", "barvalueindex", "lastbarvalue", "stackedbar", "stackedbardraw"])
1446 class stackedbarpos(_style):
1448 # provides no additional data, but needs some data (and modifies some of them)
1449 needsdata = ["vbarrange", "barposcolumnnames", "barvalueindex", "lastbarvalue", "stackedbar", "stackedbardraw"]
1451 def __init__(self, stackname, addontop=0, epsilon=1e-10):
1452 self.stackname = stackname
1453 self.epsilon = epsilon
1454 self.addontop = addontop
1456 def columnnames(self, privatedata, sharedata, graph, columnnames, dataaxisnames):
1457 if self.stackname not in columnnames:
1458 raise ValueError("column '%s' missing" % self.stackname)
1459 return [self.stackname]
1461 def adjustaxis(self, privatedata, sharedata, graph, plotitem, columnname, data):
1462 if columnname == self.stackname:
1463 graph.axes[sharedata.barposcolumnnames[sharedata.barvalueindex]].adjustaxis(data)
1465 def initdrawpoints(self, privatedata, sharedata, graph):
1466 if sharedata.stackedbardraw: # do not count the start bar when not gets painted
1467 sharedata.stackedbar += 1
1469 def drawpoint(self, privatedata, sharedata, graph, point):
1470 sharedata.vbarrange[sharedata.barvalueindex][0] = sharedata.vbarrange[sharedata.barvalueindex][1]
1471 if self.addontop:
1472 try:
1473 sharedata.lastbarvalue += point[self.stackname]
1474 except (ArithmeticError, ValueError, TypeError):
1475 sharedata.lastbarvalue = None
1476 else:
1477 sharedata.lastbarvalue = point[self.stackname]
1478 try:
1479 sharedata.vpos[sharedata.barvalueindex] = sharedata.vbarrange[sharedata.barvalueindex][1] = graph.axes[sharedata.barposcolumnnames[sharedata.barvalueindex]].convert(sharedata.lastbarvalue)
1480 except (ArithmeticError, ValueError, TypeError):
1481 sharedata.vpos[sharedata.barvalueindex] = sharedata.vbarrange[sharedata.barvalueindex][1] = None
1482 sharedata.vposavailable = sharedata.vposvalid = 0
1483 else:
1484 if not sharedata.vposavailable or not sharedata.vposvalid:
1485 sharedata.vposavailable = sharedata.vposvalid = 1
1486 for v in sharedata.vpos:
1487 if v is None:
1488 sharedata.vposavailable = sharedata.vposvalid = 0
1489 break
1490 if v < -self.epsilon or v > 1+self.epsilon:
1491 sharedata.vposvalid = 0
1494 class bar(_style):
1496 needsdata = ["vbarrange"]
1498 defaultbarattrs = [color.gradient.Rainbow, deco.stroked([color.grey.black])]
1500 def __init__(self, barattrs=[], epsilon=1e-10, gradient=color.gradient.RedBlack):
1501 self.barattrs = barattrs
1502 self.epsilon = epsilon
1503 self.gradient = gradient
1505 def lighting(self, angle, zindex):
1506 return self.gradient.getcolor(0.7-0.4*abs(angle)+0.1*zindex)
1508 def columnnames(self, privatedata, sharedata, graph, columnnames, dataaxisnames):
1509 return []
1511 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1512 privatedata.barattrs = attr.selectattrs(self.defaultbarattrs + self.barattrs, selectindex, selecttotal)
1514 def initdrawpoints(self, privatedata, sharedata, graph):
1515 privatedata.barcanvas = graph.layer("filldata").insert(canvas.canvas())
1516 sharedata.stackedbardraw = 1
1517 privatedata.stackedbar = sharedata.stackedbar
1518 privatedata.todraw = []
1520 def drawpointfill(self, privatedata, p):
1521 if p:
1522 privatedata.barcanvas.fill(p, privatedata.barattrs)
1524 def drawpoint(self, privatedata, sharedata, graph, point):
1525 vbarrange = []
1526 for vmin, vmax in sharedata.vbarrange:
1527 if vmin is None or vmax is None:
1528 self.drawpointfill(privatedata, None)
1529 return
1530 if vmin > vmax:
1531 vmin, vmax = vmax, vmin
1532 if vmin > 1 or vmax < 0:
1533 self.drawpointfill(privatedata, None)
1534 return
1535 if vmin < 0:
1536 vmin = 0
1537 if vmax > 1:
1538 vmax = 1
1539 vbarrange.append((vmin, vmax))
1540 if len(vbarrange) == 2:
1541 p = graph.vgeodesic(vbarrange[0][0], vbarrange[1][0], vbarrange[0][1], vbarrange[1][0])
1542 p.append(graph.vgeodesic_el(vbarrange[0][1], vbarrange[1][0], vbarrange[0][1], vbarrange[1][1]))
1543 p.append(graph.vgeodesic_el(vbarrange[0][1], vbarrange[1][1], vbarrange[0][0], vbarrange[1][1]))
1544 p.append(graph.vgeodesic_el(vbarrange[0][0], vbarrange[1][1], vbarrange[0][0], vbarrange[1][0]))
1545 p.append(path.closepath())
1546 self.drawpointfill(privatedata, p)
1547 elif len(vbarrange) == 3:
1548 planes = []
1549 if abs(vbarrange[0][0] - vbarrange[0][1]) > self.epsilon and abs(vbarrange[1][0] - vbarrange[1][1]):
1550 planes.append((vbarrange[0][0], vbarrange[1][0], vbarrange[2][0],
1551 vbarrange[0][1], vbarrange[1][0], vbarrange[2][0],
1552 vbarrange[0][1], vbarrange[1][1], vbarrange[2][0],
1553 vbarrange[0][0], vbarrange[1][1], vbarrange[2][0]))
1554 planes.append((vbarrange[0][0], vbarrange[1][0], vbarrange[2][1],
1555 vbarrange[0][0], vbarrange[1][1], vbarrange[2][1],
1556 vbarrange[0][1], vbarrange[1][1], vbarrange[2][1],
1557 vbarrange[0][1], vbarrange[1][0], vbarrange[2][1]))
1558 if abs(vbarrange[0][0] - vbarrange[0][1]) > self.epsilon and abs(vbarrange[2][0] - vbarrange[2][1]):
1559 planes.append((vbarrange[0][0], vbarrange[1][0], vbarrange[2][0],
1560 vbarrange[0][0], vbarrange[1][0], vbarrange[2][1],
1561 vbarrange[0][1], vbarrange[1][0], vbarrange[2][1],
1562 vbarrange[0][1], vbarrange[1][0], vbarrange[2][0]))
1563 planes.append((vbarrange[0][0], vbarrange[1][1], vbarrange[2][0],
1564 vbarrange[0][1], vbarrange[1][1], vbarrange[2][0],
1565 vbarrange[0][1], vbarrange[1][1], vbarrange[2][1],
1566 vbarrange[0][0], vbarrange[1][1], vbarrange[2][1]))
1567 if abs(vbarrange[1][0] - vbarrange[1][1]) > self.epsilon and abs(vbarrange[2][0] - vbarrange[2][1]):
1568 planes.append((vbarrange[0][0], vbarrange[1][0], vbarrange[2][0],
1569 vbarrange[0][0], vbarrange[1][1], vbarrange[2][0],
1570 vbarrange[0][0], vbarrange[1][1], vbarrange[2][1],
1571 vbarrange[0][0], vbarrange[1][0], vbarrange[2][1]))
1572 planes.append((vbarrange[0][1], vbarrange[1][0], vbarrange[2][0],
1573 vbarrange[0][1], vbarrange[1][0], vbarrange[2][1],
1574 vbarrange[0][1], vbarrange[1][1], vbarrange[2][1],
1575 vbarrange[0][1], vbarrange[1][1], vbarrange[2][0]))
1576 v = [0.5 * (vbarrange[0][0] + vbarrange[0][1]),
1577 0.5 * (vbarrange[1][0] + vbarrange[1][1]),
1578 0.5 * (vbarrange[2][0] + vbarrange[2][1])]
1579 v[sharedata.barvalueindex] = 0.5
1580 zindex = graph.vzindex(*v)
1581 for v11, v12, v13, v21, v22, v23, v31, v32, v33, v41, v42, v43 in planes:
1582 angle = graph.vangle(v11, v12, v13, v21, v22, v23, v41, v42, v43)
1583 if angle > 0:
1584 p = graph.vgeodesic(v11, v12, v13, v21, v22, v23)
1585 p.append(graph.vgeodesic_el(v21, v22, v23, v31, v32, v33))
1586 p.append(graph.vgeodesic_el(v31, v32, v33, v41, v42, v43))
1587 p.append(graph.vgeodesic_el(v41, v42, v43, v11, v12, v13))
1588 p.append(path.closepath())
1589 if self.gradient:
1590 privatedata.todraw.append((-zindex, p, privatedata.barattrs + [self.lighting(angle, zindex)]))
1591 else:
1592 privatedata.todraw.append((-zindex, p, privatedata.barattrs))
1593 else:
1594 raise TypeError("bar style restricted to two- and three dimensional graphs")
1596 def donedrawpoints(self, privatedata, sharedata, graph):
1597 privatedata.todraw.sort(key=lambda x: x[0])
1598 for vzindex, p, a in privatedata.todraw:
1599 privatedata.barcanvas.fill(p, a)
1601 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
1602 selectindex = privatedata.stackedbar
1603 selecttotal = sharedata.stackedbar + 1
1604 graph.fill(path.rect_pt(x_pt + width_pt*selectindex/float(selecttotal), y_pt, width_pt/float(selecttotal), height_pt), privatedata.barattrs)
1607 class changebar(bar):
1609 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1610 if selecttotal != 1:
1611 raise RuntimeError("Changebar can't change its appearance. Thus you can't use it to plot several bars side by side on a subaxis.")
1613 def initdrawpoints(self, privatedata, sharedata, graph):
1614 if len(graph.axesnames) != 2:
1615 raise TypeError("changebar style restricted on two-dimensional graphs (at least for the moment)")
1616 bar.initdrawpoints(self, privatedata, sharedata, graph)
1617 privatedata.bars = []
1619 def drawpointfill(self, privatedata, p):
1620 privatedata.bars.append(p)
1622 def donedrawpoints(self, privatedata, sharedata, graph):
1623 selecttotal = len(privatedata.bars)
1624 for selectindex, p in enumerate(privatedata.bars):
1625 if p:
1626 barattrs = attr.selectattrs(self.defaultbarattrs + self.barattrs, selectindex, selecttotal)
1627 privatedata.barcanvas.fill(p, barattrs)
1629 def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt):
1630 raise RuntimeError("Style currently doesn't provide a graph key")
1633 class gridpos(_style):
1635 needsdata = ["vpos", "vposmissing", "vposavailable", "vposvalid"]
1636 providesdata = ["values1", "values2", "data12", "data21", "index1", "index2"]
1638 def __init__(self, index1=0, index2=1, epsilon=1e-10):
1639 self.index1 = index1
1640 self.index2 = index2
1641 self.epsilon = epsilon
1643 def initdrawpoints(self, privatedata, sharedata, graph):
1644 sharedata.index1 = self.index1
1645 sharedata.index2 = self.index2
1646 sharedata.values1 = {}
1647 sharedata.values2 = {}
1648 sharedata.data12 = {}
1649 sharedata.data21 = {}
1651 def drawpoint(self, privatedata, sharedata, graph, point):
1652 if sharedata.vposavailable:
1653 sharedata.value1 = sharedata.vpos[self.index1]
1654 sharedata.value2 = sharedata.vpos[self.index2]
1655 if sharedata.value1 not in sharedata.values1:
1656 for hasvalue in list(sharedata.values1.keys()):
1657 if hasvalue - self.epsilon <= sharedata.value1 <= hasvalue + self.epsilon:
1658 sharedata.value1 = hasvalue
1659 break
1660 else:
1661 sharedata.values1[sharedata.value1] = 1
1662 if sharedata.value2 not in sharedata.values2:
1663 for hasvalue in list(sharedata.values2.keys()):
1664 if hasvalue - self.epsilon <= sharedata.value2 <= hasvalue + self.epsilon:
1665 sharedata.value2 = hasvalue
1666 break
1667 else:
1668 sharedata.values2[sharedata.value2] = 1
1669 data = sharedata.vposavailable, sharedata.vposvalid, sharedata.vpos[:]
1670 sharedata.data12.setdefault(sharedata.value1, {})[sharedata.value2] = data
1671 sharedata.data21.setdefault(sharedata.value2, {})[sharedata.value1] = data
1673 registerdefaultprovider(gridpos(), gridpos.providesdata)
1676 class grid(_line):
1678 needsdata = ["values1", "values2", "data12", "data21"]
1680 defaultgridattrs = [line.changelinestyle]
1682 def __init__(self, gridlines1=1, gridlines2=1, gridattrs=[], **kwargs):
1683 _line.__init__(self, **kwargs)
1684 self.gridlines1 = gridlines1
1685 self.gridlines2 = gridlines2
1686 self.gridattrs = gridattrs
1688 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
1689 if self.gridattrs is not None:
1690 privatedata.gridattrs = attr.selectattrs(self.defaultgridattrs + self.gridattrs, selectindex, selecttotal)
1691 else:
1692 privatedata.gridattrs = None
1694 def donedrawpoints(self, privatedata, sharedata, graph):
1695 values1 = list(sharedata.values1.keys())
1696 values1.sort()
1697 values2 = list(sharedata.values2.keys())
1698 values2.sort()
1699 if self.gridlines1:
1700 for value2 in values2:
1701 data1 = sharedata.data21[value2]
1702 self.initpointstopath(privatedata)
1703 for value1 in values1:
1704 try:
1705 data = data1[value1]
1706 except KeyError:
1707 self.addinvalid(privatedata)
1708 else:
1709 self.addpoint(privatedata, graph.vpos_pt, *data)
1710 p = self.donepointstopath(privatedata)
1711 if len(p):
1712 graph.layer("data").stroke(p, privatedata.gridattrs)
1713 if self.gridlines2:
1714 for value1 in values1:
1715 data2 = sharedata.data12[value1]
1716 self.initpointstopath(privatedata)
1717 for value2 in values2:
1718 try:
1719 data = data2[value2]
1720 except KeyError:
1721 self.addinvalid(privatedata)
1722 else:
1723 self.addpoint(privatedata, graph.vpos_pt, *data)
1724 p = self.donepointstopath(privatedata)
1725 if len(p):
1726 graph.layer("data").stroke(p, privatedata.gridattrs)
1729 class surface(_keygraphstyle):
1731 needsdata = ["values1", "values2", "data12", "data21"]
1733 def __init__(self, gridlines1=0.05, gridlines2=0.05, gridcolor=None,
1734 backcolor=color.gray.black, **kwargs):
1735 _keygraphstyle.__init__(self, **kwargs)
1736 self.gridlines1 = gridlines1
1737 self.gridlines2 = gridlines2
1738 self.gridcolor = gridcolor
1739 self.backcolor = backcolor
1741 colorspacestring = self.gradient.getcolor(0).colorspacestring()
1742 if self.gridcolor is not None and self.gridcolor.colorspacestring() != colorspacestring:
1743 raise RuntimeError("colorspace mismatch (gradient/grid)")
1744 if self.backcolor is not None and self.backcolor.colorspacestring() != colorspacestring:
1745 raise RuntimeError("colorspace mismatch (gradient/back)")
1747 def midvalue(self, v1, v2, v3, v4):
1748 return [0.25*sum(values) for values in zip(v1, v2, v3, v4)]
1750 def midcolor(self, c1, c2, c3, c4):
1751 return 0.25*(c1+c2+c3+c4)
1753 def lighting(self, angle, zindex):
1754 if angle < 0 and self.backcolor is not None:
1755 return self.backcolor
1756 return self.gradient.getcolor(0.7-0.4*abs(angle)+0.1*zindex)
1758 def columnnames(self, privatedata, sharedata, graph, columnnames, dataaxisnames):
1759 privatedata.colorize = self.colorname in columnnames
1760 if privatedata.colorize:
1761 return _keygraphstyle.columnnames(self, privatedata, sharedata, graph, columnnames, dataaxisnames)
1762 return []
1764 def initdrawpoints(self, privatedata, sharedata, graph):
1765 privatedata.colors = {}
1767 def drawpoint(self, privatedata, sharedata, graph, point):
1768 if privatedata.colorize:
1769 privatedata.colors.setdefault(sharedata.value1, {})[sharedata.value2] = point[self.colorname]
1771 def donedrawpoints(self, privatedata, sharedata, graph):
1772 v1 = [0]*len(graph.axesnames)
1773 v2 = [0]*len(graph.axesnames)
1774 v3 = [0]*len(graph.axesnames)
1775 v4 = [0]*len(graph.axesnames)
1776 v1[sharedata.index2] = 0.5
1777 v2[sharedata.index1] = 0.5
1778 v3[sharedata.index1] = 0.5
1779 v3[sharedata.index2] = 1
1780 v4[sharedata.index1] = 1
1781 v4[sharedata.index2] = 0.5
1782 sortElements = [-graph.vzindex(*v1),
1783 -graph.vzindex(*v2),
1784 -graph.vzindex(*v3),
1785 -graph.vzindex(*v4)]
1787 values1 = list(sharedata.values1.keys())
1788 values1.sort()
1789 v1 = [0]*len(graph.axesnames)
1790 v2 = [0]*len(graph.axesnames)
1791 v1[sharedata.index1] = -1
1792 v2[sharedata.index1] = 1
1793 sign = 1
1794 if graph.vzindex(*v1) < graph.vzindex(*v2):
1795 values1.reverse()
1796 sign *= -1
1797 sortElements = [sortElements[3], sortElements[1], sortElements[2], sortElements[0]]
1799 values2 = list(sharedata.values2.keys())
1800 values2.sort()
1801 v1 = [0]*len(graph.axesnames)
1802 v2 = [0]*len(graph.axesnames)
1803 v1[sharedata.index2] = -1
1804 v2[sharedata.index2] = 1
1805 if graph.vzindex(*v1) < graph.vzindex(*v2):
1806 values2.reverse()
1807 sign *= -1
1808 sortElements = [sortElements[0], sortElements[2], sortElements[1], sortElements[3]]
1810 sortElements = [(zindex, i) for i, zindex in enumerate(sortElements)]
1811 sortElements.sort()
1813 nodes = []
1814 elements = []
1815 for value1a, value1b in zip(values1[:-1], values1[1:]):
1816 for value2a, value2b in zip(values2[:-1], values2[1:]):
1817 try:
1818 available1, valid1, v1 = sharedata.data12[value1a][value2a]
1819 available2, valid2, v2 = sharedata.data12[value1a][value2b]
1820 available3, valid3, v3 = sharedata.data12[value1b][value2a]
1821 available4, valid4, v4 = sharedata.data12[value1b][value2b]
1822 except KeyError:
1823 continue
1824 if not available1 or not available2 or not available3 or not available4:
1825 continue
1826 if not valid1 or not valid2 or not valid3 or not valid4:
1827 logger.warning("surface elements partially outside of the graph are (currently) skipped completely")
1828 continue
1829 def shrink(index, v1, v2, by):
1830 v1 = v1[:]
1831 v2 = v2[:]
1832 for i in builtinrange(3):
1833 if i != index:
1834 v1[i], v2[i] = v1[i] + by*(v2[i]-v1[i]), v2[i] + by*(v1[i]-v2[i])
1835 return v1, v2
1836 v1f, v2f, v3f, v4f = v1, v2, v3, v4
1837 if self.gridcolor is not None and self.gridlines1:
1838 v1, v2 = shrink(sharedata.index1, v1, v2, self.gridlines1)
1839 v3, v4 = shrink(sharedata.index1, v3, v4, self.gridlines1)
1840 if self.gridcolor is not None and self.gridlines2:
1841 v1, v3 = shrink(sharedata.index2, v1, v3, self.gridlines2)
1842 v2, v4 = shrink(sharedata.index2, v2, v4, self.gridlines2)
1843 v5 = self.midvalue(v1, v2, v3, v4)
1844 x1_pt, y1_pt = graph.vpos_pt(*v1)
1845 x2_pt, y2_pt = graph.vpos_pt(*v2)
1846 x3_pt, y3_pt = graph.vpos_pt(*v3)
1847 x4_pt, y4_pt = graph.vpos_pt(*v4)
1848 x5_pt, y5_pt = graph.vpos_pt(*v5)
1849 if privatedata.colorize:
1850 c1 = privatedata.colors[value1a][value2a]
1851 c2 = privatedata.colors[value1a][value2b]
1852 c3 = privatedata.colors[value1b][value2a]
1853 c4 = privatedata.colors[value1b][value2b]
1854 c5 = self.midcolor(c1, c2, c3, c4)
1855 c1a = c1b = self.color(privatedata, c1)
1856 c2a = c2c = self.color(privatedata, c2)
1857 c3b = c3d = self.color(privatedata, c3)
1858 c4c = c4d = self.color(privatedata, c4)
1859 c5a = c5b = c5c = c5d = self.color(privatedata, c5)
1860 if self.backcolor is not None and sign*graph.vangle(*(v1+v2+v5)) < 0:
1861 c1a = c2a = c5a = self.backcolor
1862 if self.backcolor is not None and sign*graph.vangle(*(v3+v1+v5)) < 0:
1863 c3b = c1b = c5b = self.backcolor
1864 if self.backcolor is not None and sign*graph.vangle(*(v2+v4+v5)) < 0:
1865 c2c = c4c = c5c = self.backcolor
1866 if self.backcolor is not None and sign*graph.vangle(*(v4+v3+v5)) < 0:
1867 c4d = c3d = c5d = self.backcolor
1868 else:
1869 zindex = graph.vzindex(*v5)
1870 c1a = c2a = c5a = self.lighting(sign*graph.vangle(*(v1+v2+v5)), zindex)
1871 c3b = c1b = c5b = self.lighting(sign*graph.vangle(*(v3+v1+v5)), zindex)
1872 c2c = c4c = c5c = self.lighting(sign*graph.vangle(*(v2+v4+v5)), zindex)
1873 c4d = c3d = c5d = self.lighting(sign*graph.vangle(*(v4+v3+v5)), zindex)
1874 for zindex, i in sortElements:
1875 if i == 0:
1876 elements.append(mesh.element((mesh.node_pt((x1_pt, y1_pt), c1a),
1877 mesh.node_pt((x2_pt, y2_pt), c2a),
1878 mesh.node_pt((x5_pt, y5_pt), c5a))))
1879 if self.gridcolor is not None and self.gridlines2:
1880 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v1f), self.gridcolor),
1881 mesh.node_pt(graph.vpos_pt(*v2), 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(*v2), self.gridcolor),
1885 mesh.node_pt(graph.vpos_pt(*v2f), self.gridcolor))))
1886 elif i == 1:
1887 elements.append(mesh.element((mesh.node_pt((x3_pt, y3_pt), c3b),
1888 mesh.node_pt((x1_pt, y1_pt), c1b),
1889 mesh.node_pt((x5_pt, y5_pt), c5b))))
1890 if self.gridcolor is not None and self.gridlines1:
1891 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v1f), self.gridcolor),
1892 mesh.node_pt(graph.vpos_pt(*v3), self.gridcolor),
1893 mesh.node_pt(graph.vpos_pt(*v1), self.gridcolor))))
1894 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v1f), self.gridcolor),
1895 mesh.node_pt(graph.vpos_pt(*v3), self.gridcolor),
1896 mesh.node_pt(graph.vpos_pt(*v3f), self.gridcolor))))
1897 elif i == 2:
1898 elements.append(mesh.element((mesh.node_pt((x2_pt, y2_pt), c2c),
1899 mesh.node_pt((x4_pt, y4_pt), c4c),
1900 mesh.node_pt((x5_pt, y5_pt), c5c))))
1901 if self.gridcolor is not None and self.gridlines1:
1902 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v2f), self.gridcolor),
1903 mesh.node_pt(graph.vpos_pt(*v4), self.gridcolor),
1904 mesh.node_pt(graph.vpos_pt(*v2), self.gridcolor))))
1905 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v2f), self.gridcolor),
1906 mesh.node_pt(graph.vpos_pt(*v4), self.gridcolor),
1907 mesh.node_pt(graph.vpos_pt(*v4f), self.gridcolor))))
1908 elif i == 3:
1909 elements.append(mesh.element((mesh.node_pt((x4_pt, y4_pt), c4d),
1910 mesh.node_pt((x3_pt, y3_pt), c3d),
1911 mesh.node_pt((x5_pt, y5_pt), c5d))))
1912 if self.gridcolor is not None and self.gridlines2:
1913 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v3f), self.gridcolor),
1914 mesh.node_pt(graph.vpos_pt(*v4), self.gridcolor),
1915 mesh.node_pt(graph.vpos_pt(*v3), self.gridcolor))))
1916 elements.append(mesh.element((mesh.node_pt(graph.vpos_pt(*v3f), self.gridcolor),
1917 mesh.node_pt(graph.vpos_pt(*v4), self.gridcolor),
1918 mesh.node_pt(graph.vpos_pt(*v4f), self.gridcolor))))
1919 m = mesh.mesh(elements, check=0)
1920 graph.layer("filldata").insert(m)
1922 if privatedata.colorize:
1923 _keygraphstyle.donedrawpoints(self, privatedata, sharedata, graph)
1926 class density(_keygraphstyle):
1928 needsdata = ["values1", "values2", "data12", "data21"]
1930 def __init__(self, epsilon=1e-10, **kwargs):
1931 _keygraphstyle.__init__(self, **kwargs)
1932 self.epsilon = epsilon
1934 def initdrawpoints(self, privatedata, sharedata, graph):
1935 privatedata.colors = {}
1936 privatedata.vfixed = [None]*len(graph.axesnames)
1938 def drawpoint(self, privatedata, sharedata, graph, point):
1939 privatedata.colors.setdefault(sharedata.value1, {})[sharedata.value2] = point[self.colorname]
1940 if len(privatedata.vfixed) > 2 and sharedata.vposavailable:
1941 for i, (v1, v2) in enumerate(list(zip(privatedata.vfixed, sharedata.vpos))):
1942 if i != sharedata.index1 and i != sharedata.index2:
1943 if v1 is None:
1944 privatedata.vfixed[i] = v2
1945 elif abs(v1-v2) > self.epsilon:
1946 raise ValueError("data must be in a plane for the density style")
1948 def donedrawpoints(self, privatedata, sharedata, graph):
1949 privatedata.keygraph.doaxes()
1951 values1 = sorted(list(sharedata.values1.keys()))
1952 values2 = sorted(list(sharedata.values2.keys()))
1953 def equidistant(values):
1954 l = len(values) - 1
1955 if l < 1:
1956 raise ValueError("several data points required by the density style in each dimension")
1957 range = values[-1] - values[0]
1958 for i, value in enumerate(values):
1959 if abs(value - values[0] - i * range / l) > self.epsilon:
1960 raise ValueError("data must be equidistant for the density style")
1961 equidistant(values1)
1962 equidistant(values2)
1963 needalpha = False
1964 for value2 in values2:
1965 for value1 in values1:
1966 try:
1967 available, valid, v = sharedata.data12[value1][value2]
1968 except KeyError:
1969 needalpha = True
1970 break
1971 if not available:
1972 needalpha = True
1973 continue
1974 else:
1975 continue
1976 break
1977 mode = {"/DeviceGray": "L",
1978 "/DeviceRGB": "RGB",
1979 "/DeviceCMYK": "CMYK"}[self.gradient.getcolor(0).colorspacestring()]
1980 if needalpha:
1981 mode = "A" + mode
1982 empty = b"\0"*len(mode)
1983 data = io.BytesIO()
1984 for value2 in values2:
1985 for value1 in values1:
1986 try:
1987 available, valid, v = sharedata.data12[value1][value2]
1988 except KeyError:
1989 data.write(empty)
1990 continue
1991 if not available:
1992 data.write(empty)
1993 continue
1994 c = privatedata.colors[value1][value2]
1995 c = self.color(privatedata, c)
1996 if needalpha:
1997 data.write(bytes((255,)))
1998 data.write(c.to8bitbytes())
1999 i = bitmap.image(len(values1), len(values2), mode, data.getvalue())
2001 v1enlargement = (values1[-1]-values1[0])*0.5/len(values1)
2002 v2enlargement = (values2[-1]-values2[0])*0.5/len(values2)
2004 privatedata.vfixed[sharedata.index1] = values1[0]-v1enlargement
2005 privatedata.vfixed[sharedata.index2] = values2[-1]+v2enlargement
2006 x1_pt, y1_pt = graph.vpos_pt(*privatedata.vfixed)
2007 privatedata.vfixed[sharedata.index1] = values1[-1]+v1enlargement
2008 privatedata.vfixed[sharedata.index2] = values2[-1]+v2enlargement
2009 x2_pt, y2_pt = graph.vpos_pt(*privatedata.vfixed)
2010 privatedata.vfixed[sharedata.index1] = values1[0]-v1enlargement
2011 privatedata.vfixed[sharedata.index2] = values2[0]-v2enlargement
2012 x3_pt, y3_pt = graph.vpos_pt(*privatedata.vfixed)
2013 t = trafo.trafo_pt(((x2_pt-x1_pt, x3_pt-x1_pt), (y2_pt-y1_pt, y3_pt-y1_pt)), (x1_pt, y1_pt))
2015 privatedata.vfixed[sharedata.index1] = values1[-1]+v1enlargement
2016 privatedata.vfixed[sharedata.index2] = values2[0]-v2enlargement
2017 vx4, vy4 = t.inverse().apply_pt(*graph.vpos_pt(*privatedata.vfixed))
2018 if abs(vx4 - 1) > self.epsilon or abs(vy4 - 1) > self.epsilon:
2019 raise ValueError("invalid graph layout for density style (bitmap positioning by affine transformation failed)")
2021 p = path.path()
2022 privatedata.vfixed[sharedata.index1] = 0
2023 privatedata.vfixed[sharedata.index2] = 0
2024 p.append(path.moveto_pt(*graph.vpos_pt(*privatedata.vfixed)))
2025 vfixed2 = privatedata.vfixed + privatedata.vfixed
2026 vfixed2[sharedata.index1] = 0
2027 vfixed2[sharedata.index2] = 0
2028 vfixed2[sharedata.index1 + len(graph.axesnames)] = 1
2029 vfixed2[sharedata.index2 + len(graph.axesnames)] = 0
2030 p.append(graph.vgeodesic_el(*vfixed2))
2031 vfixed2[sharedata.index1] = 1
2032 vfixed2[sharedata.index2] = 0
2033 vfixed2[sharedata.index1 + len(graph.axesnames)] = 1
2034 vfixed2[sharedata.index2 + len(graph.axesnames)] = 1
2035 p.append(graph.vgeodesic_el(*vfixed2))
2036 vfixed2[sharedata.index1] = 1
2037 vfixed2[sharedata.index2] = 1
2038 vfixed2[sharedata.index1 + len(graph.axesnames)] = 0
2039 vfixed2[sharedata.index2 + len(graph.axesnames)] = 1
2040 p.append(graph.vgeodesic_el(*vfixed2))
2041 vfixed2[sharedata.index1] = 0
2042 vfixed2[sharedata.index2] = 1
2043 vfixed2[sharedata.index1 + len(graph.axesnames)] = 0
2044 vfixed2[sharedata.index2 + len(graph.axesnames)] = 0
2045 p.append(graph.vgeodesic_el(*vfixed2))
2046 p.append(path.closepath())
2048 c = canvas.canvas([canvas.clip(p)])
2049 b = bitmap.bitmap_trafo(t, i)
2050 c.insert(b)
2051 graph.layer("filldata").insert(c)
2053 _keygraphstyle.donedrawpoints(self, privatedata, sharedata, graph)
2057 class gradient(_style):
2059 defaultbarattrs = [color.gradient.Rainbow, deco.stroked([color.grey.black])]
2061 def __init__(self, gradient=color.gradient.Gray, resolution=100, columnname="x"):
2062 self.gradient = gradient
2063 self.resolution = resolution
2064 self.columnname = columnname
2066 def columnnames(self, privatedata, sharedata, graph, columnnames, dataaxisnames):
2067 return [self.columnname]
2069 def adjustaxis(self, privatedata, sharedata, graph, plotitem, columnname, data):
2070 graph.axes[self.columnname].adjustaxis(data)
2072 def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal):
2073 pass
2075 def initdrawpoints(self, privatedata, sharedata, graph):
2076 pass
2078 def drawpoint(self, privatedata, sharedata, graph, point):
2079 pass
2081 def donedrawpoints(self, privatedata, sharedata, graph):
2082 mode = {"/DeviceGray": "L",
2083 "/DeviceRGB": "RGB",
2084 "/DeviceCMYK": "CMYK"}[self.gradient.getcolor(0).colorspacestring()]
2085 data = io.BytesIO()
2086 for i in builtinrange(self.resolution):
2087 c = self.gradient.getcolor(i*1.0/self.resolution)
2088 data.write(c.to8bitbytes())
2089 i = bitmap.image(self.resolution, 1, mode, data.getvalue())
2091 llx_pt, lly_pt = graph.vpos_pt(0)
2092 urx_pt, ury_pt = graph.vpos_pt(1)
2094 if graph.direction == "horizontal":
2095 lly_pt -= 0.5*graph.height_pt
2096 ury_pt += 0.5*graph.height_pt
2097 else:
2098 llx_pt -= 0.5*graph.width_pt
2099 urx_pt += 0.5*graph.width_pt
2101 c = canvas.canvas([canvas.clip(path.rect_pt(llx_pt, lly_pt, urx_pt-llx_pt, ury_pt-lly_pt))])
2103 if graph.direction == "horizontal":
2104 add_pt = (urx_pt-llx_pt)*0.5/(self.resolution-1)
2105 llx_pt -= add_pt
2106 urx_pt += add_pt
2107 else:
2108 add_pt = (ury_pt-lly_pt)*0.5/(self.resolution-1)
2109 lly_pt -= add_pt
2110 ury_pt += add_pt
2112 if graph.direction == "horizontal":
2113 t = trafo.trafo_pt(((urx_pt-llx_pt,0 ), (0, ury_pt-lly_pt)), (llx_pt, lly_pt))
2114 else:
2115 t = trafo.trafo_pt(((0, urx_pt-llx_pt), (ury_pt-lly_pt, 0)), (llx_pt, lly_pt))
2117 b = bitmap.bitmap_trafo(t, i)
2118 c.insert(b)
2119 graph.layer("filldata").insert(c)