remove errorbar range checks, as they fail on a reverse axis, which is correct (repor...
[PyX.git] / pyx / graph / style.py
blobba1f63d7215cfc57adc77723b922ed9a0c0ece32
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
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")
441 return []
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=[]):
481 self.symbol = symbol
482 self.size = size
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)
490 else:
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:]))
528 else:
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
534 if vposavailable:
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))
539 else:
540 # cut end
541 cut = 1
542 for vstart, vend in zip(privatedata.lastvpos, vpos):
543 if abs(vend - vstart) > self.epsilon:
544 newcut = None
545 if vend > 1:
546 # 1 = vstart + (vend - vstart) * cut
547 newcut = (1 - vstart)/(vend - vstart)
548 if vend < 0:
549 # 0 = vstart + (vend - vstart) * cut
550 newcut = - vstart/(vend - vstart)
551 if newcut is not None and newcut < cut:
552 cut = newcut
553 cutvpos = []
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)
558 else:
559 # the last point was outside the graph
560 if privatedata.lastvpos is not None:
561 if vposvalid:
562 # cut beginning
563 cut = 0
564 for vstart, vend in zip(privatedata.lastvpos, vpos):
565 if abs(vend - vstart) > self.epsilon:
566 newcut = None
567 if vstart > 1:
568 # 1 = vstart + (vend - vstart) * cut
569 newcut = (1 - vstart)/(vend - vstart)
570 if vstart < 0:
571 # 0 = vstart + (vend - vstart) * cut
572 newcut = - vstart/(vend - vstart)
573 if newcut is not None and newcut > cut:
574 cut = newcut
575 cutvpos = []
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))
580 else:
581 # sometimes cut beginning and end
582 cutfrom = 0
583 cutto = 1
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:
588 newcutfrom = None
589 if vstart > 1:
590 newcutfrom = (1 - vstart)/(vend - vstart)
591 if vstart < 0:
592 # 0 = vstart + (vend - vstart) * cutfrom
593 newcutfrom = - vstart/(vend - vstart)
594 if newcutfrom is not None and newcutfrom > cutfrom:
595 cutfrom = newcutfrom
596 newcutto = None
597 if vend > 1:
598 # 1 = vstart + (vend - vstart) * cutto
599 newcutto = (1 - vstart)/(vend - vstart)
600 if vend < 0:
601 # 0 = vstart + (vend - vstart) * cutto
602 newcutto = - vstart/(vend - vstart)
603 if newcutto is not None and newcutto < cutto:
604 cutto = newcutto
605 else:
606 if cutfrom < cutto:
607 cutfromvpos = []
608 cuttovpos = []
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[:]
616 else:
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
632 class line(_line):
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)
650 else:
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)
686 else:
687 privatedata.lineattrs = None
689 def adjustaxis(self, privatedata, sharedata, graph, plotitem, columnname, data):
690 if self.fromvalue is not None:
691 try:
692 i = sharedata.poscolumnnames.index(columnname)
693 except ValueError:
694 pass
695 else:
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)
712 else:
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,
736 errorbarattrs=[],
737 epsilon=1e-10):
738 self.size = size
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")
746 return []
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:
761 if (i != j and
762 (sharedata.vpos[j] is None or
763 sharedata.vpos[j] < -self.epsilon or
764 sharedata.vpos[j] > 1+self.epsilon)):
765 break
766 else:
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)):
770 continue
771 vminpos = sharedata.vpos[:]
772 if sharedata.vrange[i][0] is not None:
773 vminpos[i] = sharedata.vrange[i][0]
774 mincap = 1
775 else:
776 mincap = 0
777 if vminpos[i] > 1+self.epsilon:
778 continue
779 if vminpos[i] < -self.epsilon:
780 vminpos[i] = 0
781 mincap = 0
782 vmaxpos = sharedata.vpos[:]
783 if sharedata.vrange[i][1] is not None:
784 vmaxpos[i] = sharedata.vrange[i][1]
785 maxcap = 1
786 else:
787 maxcap = 0
788 if vmaxpos[i] < -self.epsilon:
789 continue
790 if vmaxpos[i] > 1+self.epsilon:
791 vmaxpos[i] = 1
792 maxcap = 0
793 privatedata.errorbarcanvas.stroke(graph.vgeodesic(*(vminpos + vmaxpos)))
794 for j in privatedata.dimensionlist:
795 if i != j:
796 if mincap:
797 privatedata.errorbarcanvas.stroke(graph.vcap_pt(j, privatedata.errorsize_pt, *vminpos))
798 if maxcap:
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
816 self.dxname = dxname
817 self.dyname = dyname
818 self.dxunit = dxunit
819 self.dyunit = dyunit
820 self.textdx = textdx
821 self.textdy = textdy
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)
841 else:
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)
847 else:
848 privatedata.dxunit_pt = unit.topt(self.dxunit)
849 if self.dyname is None:
850 privatedata.textdy_pt = unit.topt(self.textdy)
851 else:
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)
857 try:
858 text = str(point[self.textname])
859 except:
860 pass
861 else:
862 if self.dxname is None:
863 dx_pt = privatedata.textdx_pt
864 else:
865 dx_pt = float(point[self.dxname]) * privatedata.dxunit_pt
866 if self.dyname is None:
867 dy_pt = privatedata.textdy_pt
868 else:
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)
904 else:
905 privatedata.lineattrs = None
906 if self.arrowattrs is not None:
907 privatedata.arrowattrs = attr.selectattrs(self.defaultarrowattrs + self.arrowattrs, selectindex, selecttotal)
908 else:
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)
918 try:
919 angle = point["angle"] + 0.0
920 size = point["size"] + 0.0
921 except:
922 pass
923 else:
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
931 if self.decorator:
932 privatedata.arrowcanvas.stroke(path.line_pt(x1, y1, x2, y2),
933 privatedata.lineattrs+[self.decorator(privatedata.arrowattrs, size=self.arrowsize*size)])
934 else:
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):
967 if xvmin < 0:
968 xvmin = 0
969 elif xvmax > 1:
970 xvmax = 1
971 if yvmin < 0:
972 yvmin = 0
973 elif yvmax > 1:
974 yvmax = 1
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
993 self.steps = steps
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")
1010 else:
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
1017 else:
1018 privatedata.autohistogram = 0
1019 return []
1021 def adjustaxis(self, privatedata, sharedata, graph, plotitem, columnname, data):
1022 if privatedata.autohistogram and columnname == sharedata.poscolumnnames[privatedata.rangeaxisindex]:
1023 if len(data) == 1:
1024 raise ValueError("several data points needed for automatic histogram width calculation")
1025 if len(data) > 1:
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)
1037 else:
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)))
1044 else:
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:
1049 vvalue1cut = 0
1050 if vvalue1 < 0:
1051 vvalue1 = 0
1052 vvalue1cut = -1
1053 elif vvalue1 > 1:
1054 vvalue1 = 1
1055 vvalue1cut = 1
1056 vvalue2cut = 0
1057 if vvalue2 < 0:
1058 vvalue2 = 0
1059 vvalue2cut = -1
1060 elif vvalue2 > 1:
1061 vvalue2 = 1
1062 vvalue2cut = 1
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)))
1067 else:
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))
1071 else:
1072 privatedata.path.append(graph.vgeodesic_el(vpos, vvalue1, vpos, vvalue2))
1074 def vvalueline(self, privatedata, sharedata, graph, vvalue, vpos1, vpos2):
1075 if self.fillable:
1076 if vvalue < -self.epsilon:
1077 vvalue = 0
1078 logger.warning("cut at graph boundary adds artificial lines to fillable step histogram path")
1079 if vvalue > 1+self.epsilon:
1080 vvalue = 1
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):
1083 vpos1cut = 0
1084 if vpos1 < 0:
1085 vpos1 = 0
1086 vpos1cut = -1
1087 elif vpos1 > 1:
1088 vpos1 = 1
1089 vpos1cut = 1
1090 vpos2cut = 0
1091 if vpos2 < 0:
1092 vpos2 = 0
1093 vpos2cut = -1
1094 elif vpos2 > 1:
1095 vpos2 = 1
1096 vpos2cut = 1
1097 if abs(vpos1cut + vpos2cut) <= 1:
1098 if vpos1cut:
1099 if self.fillable:
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))
1103 else:
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))
1106 else:
1107 if privatedata.rangeaxisindex:
1108 privatedata.path.append(path.moveto_pt(*graph.vpos_pt(vvalue, vpos1)))
1109 else:
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))
1113 else:
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))
1119 else:
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:
1126 return
1127 vmincut = 0
1128 if vmin < -self.epsilon:
1129 vmin = 0
1130 vmincut = -1
1131 elif vmin > 1+self.epsilon:
1132 vmin = 1
1133 vmincut = 1
1134 vmaxcut = 0
1135 if vmax < -self.epsilon:
1136 vmax = 0
1137 vmaxcut = -1
1138 if vmax > 1+self.epsilon:
1139 vmax = 1
1140 vmaxcut = 1
1141 vvaluecut = 0
1142 if vvalue < -self.epsilon:
1143 vvalue = 0
1144 vvaluecut = -1
1145 if vvalue > 1+self.epsilon:
1146 vvalue = 1
1147 vvaluecut = 1
1148 done = 0
1149 if abs(vmincut) + abs(vmaxcut) + abs(vvaluecut) + abs(privatedata.vfromvaluecut) > 1:
1150 if abs(vmincut + vmaxcut) > 1 or abs(vvaluecut+privatedata.vfromvaluecut) > 1:
1151 done = 1
1152 else:
1153 logger.warning("multiple cuts at graph boundary add artificial lines to fillable rectangle histogram path")
1154 elif vmincut:
1155 done = 1
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))
1161 else:
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))
1166 elif vmaxcut:
1167 done = 1
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))
1173 else:
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:
1179 done = 1
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))
1185 else:
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))
1190 elif vvaluecut:
1191 done = 1
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))
1197 else:
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))
1202 if not done:
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())
1210 else:
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())
1217 else:
1218 try:
1219 gap = abs(vmin - privatedata.lastvmax) > self.epsilon
1220 except (ArithmeticError, ValueError, TypeError):
1221 gap = 1
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)
1226 else:
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)
1232 if currentvalid:
1233 self.vmoveto(privatedata, sharedata, graph,
1234 vmin, vvalue)
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)
1240 if currentvalid:
1241 self.vvalueline(privatedata, sharedata, graph,
1242 vvalue, vmin, vmax)
1243 privatedata.lastvvalue = vvalue
1244 privatedata.lastvmax = vmax
1245 else:
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)
1266 else:
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])
1280 else:
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]
1289 else:
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,
1298 sharedata.vpos[0])
1299 else:
1300 self.drawvalue(privatedata, sharedata, graph,
1301 sharedata.vpos[0] - self.autohistogrampointpos*privatedata.vrange,
1302 sharedata.vpos[0] + (1-self.autohistogrampointpos)*privatedata.vrange,
1303 sharedata.vpos[1])
1304 privatedata.lastvpos = sharedata.vpos[:]
1305 else:
1306 if privatedata.rangeaxisindex:
1307 self.drawvalue(privatedata, sharedata, graph,
1308 sharedata.vrange[1][0], sharedata.vrange[1][1], sharedata.vpos[0])
1309 else:
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:
1320 if self.rectkey:
1321 p = path.rect_pt(x_pt, y_pt, width_pt, height_pt)
1322 else:
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):
1342 found = 0
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)
1349 found += 1
1350 if (axisname + "name") in columnnames:
1351 sharedata.barposcolumnnames.append(axisname + "name")
1352 found += 1
1353 if found > 1:
1354 raise ValueError("multiple names and value")
1355 if not found:
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):
1363 try:
1364 value + ""
1365 except:
1366 try:
1367 return value[0], self.addsubvalue(value[1], subvalue)
1368 except:
1369 return value, subvalue
1370 else:
1371 return value, subvalue
1373 def adjustaxis(self, privatedata, sharedata, graph, plotitem, columnname, data):
1374 try:
1375 i = sharedata.barposcolumnnames.index(columnname)
1376 except ValueError:
1377 pass
1378 else:
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)
1383 else:
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)
1404 else:
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]
1413 try:
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
1417 else:
1418 for j in builtinrange(2):
1419 try:
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
1423 try:
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]
1460 if self.addontop:
1461 try:
1462 sharedata.lastbarvalue += point[self.stackname]
1463 except (ArithmeticError, ValueError, TypeError):
1464 sharedata.lastbarvalue = None
1465 else:
1466 sharedata.lastbarvalue = point[self.stackname]
1467 try:
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
1472 else:
1473 if not sharedata.vposavailable or not sharedata.vposvalid:
1474 sharedata.vposavailable = sharedata.vposvalid = 1
1475 for v in sharedata.vpos:
1476 if v is None:
1477 sharedata.vposavailable = sharedata.vposvalid = 0
1478 break
1479 if v < -self.epsilon or v > 1+self.epsilon:
1480 sharedata.vposvalid = 0
1483 class bar(_style):
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):
1498 return []
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):
1510 if p:
1511 privatedata.barcanvas.fill(p, privatedata.barattrs)
1513 def drawpoint(self, privatedata, sharedata, graph, point):
1514 vbarrange = []
1515 for vmin, vmax in sharedata.vbarrange:
1516 if vmin is None or vmax is None:
1517 self.drawpointfill(privatedata, None)
1518 return
1519 if vmin > vmax:
1520 vmin, vmax = vmax, vmin
1521 if vmin > 1 or vmax < 0:
1522 self.drawpointfill(privatedata, None)
1523 return
1524 if vmin < 0:
1525 vmin = 0
1526 if vmax > 1:
1527 vmax = 1
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:
1537 planes = []
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)
1572 if angle > 0:
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())
1578 if self.gradient:
1579 privatedata.todraw.append((-zindex, p, privatedata.barattrs + [self.lighting(angle, zindex)]))
1580 else:
1581 privatedata.todraw.append((-zindex, p, privatedata.barattrs))
1582 else:
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):
1614 if p:
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
1648 break
1649 else:
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
1655 break
1656 else:
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)
1665 class grid(_line):
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)
1680 else:
1681 privatedata.gridattrs = None
1683 def donedrawpoints(self, privatedata, sharedata, graph):
1684 values1 = list(sharedata.values1.keys())
1685 values1.sort()
1686 values2 = list(sharedata.values2.keys())
1687 values2.sort()
1688 if self.gridlines1:
1689 for value2 in values2:
1690 data1 = sharedata.data21[value2]
1691 self.initpointstopath(privatedata)
1692 for value1 in values1:
1693 try:
1694 data = data1[value1]
1695 except KeyError:
1696 self.addinvalid(privatedata)
1697 else:
1698 self.addpoint(privatedata, graph.vpos_pt, *data)
1699 p = self.donepointstopath(privatedata)
1700 if len(p):
1701 graph.layer("data").stroke(p, privatedata.gridattrs)
1702 if self.gridlines2:
1703 for value1 in values1:
1704 data2 = sharedata.data12[value1]
1705 self.initpointstopath(privatedata)
1706 for value2 in values2:
1707 try:
1708 data = data2[value2]
1709 except KeyError:
1710 self.addinvalid(privatedata)
1711 else:
1712 self.addpoint(privatedata, graph.vpos_pt, *data)
1713 p = self.donepointstopath(privatedata)
1714 if len(p):
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)
1751 return []
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())
1777 values1.sort()
1778 v1 = [0]*len(graph.axesnames)
1779 v2 = [0]*len(graph.axesnames)
1780 v1[sharedata.index1] = -1
1781 v2[sharedata.index1] = 1
1782 sign = 1
1783 if graph.vzindex(*v1) < graph.vzindex(*v2):
1784 values1.reverse()
1785 sign *= -1
1786 sortElements = [sortElements[3], sortElements[1], sortElements[2], sortElements[0]]
1788 values2 = list(sharedata.values2.keys())
1789 values2.sort()
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):
1795 values2.reverse()
1796 sign *= -1
1797 sortElements = [sortElements[0], sortElements[2], sortElements[1], sortElements[3]]
1799 sortElements = [(zindex, i) for i, zindex in enumerate(sortElements)]
1800 sortElements.sort()
1802 nodes = []
1803 elements = []
1804 for value1a, value1b in zip(values1[:-1], values1[1:]):
1805 for value2a, value2b in zip(values2[:-1], values2[1:]):
1806 try:
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]
1811 except KeyError:
1812 continue
1813 if not available1 or not available2 or not available3 or not available4:
1814 continue
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")
1817 continue
1818 def shrink(index, v1, v2, by):
1819 v1 = v1[:]
1820 v2 = v2[:]
1821 for i in builtinrange(3):
1822 if i != index:
1823 v1[i], v2[i] = v1[i] + by*(v2[i]-v1[i]), v2[i] + by*(v1[i]-v2[i])
1824 return v1, v2
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
1857 else:
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:
1864 if i == 0:
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))))
1875 elif i == 1:
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))))
1886 elif i == 2:
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))))
1897 elif i == 3:
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:
1932 if v1 is None:
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):
1943 l = len(values) - 1
1944 if l < 1:
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)
1952 needalpha = False
1953 for value2 in values2:
1954 for value1 in values1:
1955 try:
1956 available, valid, v = sharedata.data12[value1][value2]
1957 except KeyError:
1958 needalpha = True
1959 break
1960 if not available:
1961 needalpha = True
1962 continue
1963 else:
1964 continue
1965 break
1966 mode = {"/DeviceGray": "L",
1967 "/DeviceRGB": "RGB",
1968 "/DeviceCMYK": "CMYK"}[self.gradient.getcolor(0).colorspacestring()]
1969 if needalpha:
1970 mode = "A" + mode
1971 empty = b"\0"*len(mode)
1972 data = io.BytesIO()
1973 for value2 in values2:
1974 for value1 in values1:
1975 try:
1976 available, valid, v = sharedata.data12[value1][value2]
1977 except KeyError:
1978 data.write(empty)
1979 continue
1980 if not available:
1981 data.write(empty)
1982 continue
1983 c = privatedata.colors[value1][value2]
1984 c = self.color(privatedata, c)
1985 if needalpha:
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)
1991 v2enlargement = (values2[-1]-values2[0])*0.5/len(values2)
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)")
2010 p = path.path()
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)
2039 c.insert(b)
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):
2062 pass
2064 def initdrawpoints(self, privatedata, sharedata, graph):
2065 pass
2067 def drawpoint(self, privatedata, sharedata, graph, point):
2068 pass
2070 def donedrawpoints(self, privatedata, sharedata, graph):
2071 mode = {"/DeviceGray": "L",
2072 "/DeviceRGB": "RGB",
2073 "/DeviceCMYK": "CMYK"}[self.gradient.getcolor(0).colorspacestring()]
2074 data = io.BytesIO()
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
2086 else:
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)
2094 llx_pt -= add_pt
2095 urx_pt += add_pt
2096 else:
2097 add_pt = (ury_pt-lly_pt)*0.5/(self.resolution-1)
2098 lly_pt -= add_pt
2099 ury_pt += add_pt
2101 if graph.direction == "horizontal":
2102 t = trafo.trafo_pt(((urx_pt-llx_pt,0 ), (0, ury_pt-lly_pt)), (llx_pt, lly_pt))
2103 else:
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)
2107 c.insert(b)
2108 graph.layer("filldata").insert(c)