40
# fallback implementation for Python 2.1 and below
41
# fallback implementation for Python 2.1
43
44
for key, value in items:
44
45
result[key] = value
48
"""Interface for graph data
50
Graph data consists in columns, where each column might
51
be identified by a string or an integer. Each row in the
52
resulting table refers to a data point.
54
All methods except for the constructor should consider
55
self to be readonly, since the data instance might be shared
56
between several graphs simultaniously. The plotitem instance
57
created by the graph is available as a container class."""
59
def getcolumnpointsindex(self, column):
62
This method returns data of a column by a tuple data, index.
63
column identifies the column. If index is not None, the data
64
of the column is found at position index for each element of
65
the list data. If index is None, the data is the list of
68
Some data might not be available by this function since it
69
is dynamic, i.e. it depends on plotitem. An example are
70
function data, which is available within a graph only. Thus
71
this method might raise an exception."""
72
raise NotImplementedError("call to an abstract method of %r" % self)
74
def getcolumn(self, column):
77
This method returns the data of a column in a list. column
78
has the same meaning as in getcolumnpointsindex. Note, that
79
this method typically has to create a new list, which needs
80
time and memory. While its easy to the user, internally
81
it should be avoided in favor of getcolumnpointsindex."""
82
raise NotImplementedError("call to an abstract method of %r" % self)
87
This method returns the number of points. All results by
88
getcolumnpointsindex and getcolumn will fit this number.
89
It might raise an exception as getcolumnpointsindex."""
90
raise NotImplementedError("call to an abstract method of %r" % self)
92
def getdefaultstyles(self):
93
"""Default styles for the data
95
Returns a list of default styles for the data. Note to
96
return the same instances when the graph should iterate
97
over the styles using selectstyles."""
98
raise NotImplementedError("call to an abstract method of %r" % self)
103
This method returns a title string for the data to be used
104
in graph keys and probably other locations. The method might
105
return None to indicate, that there is no title and the data
106
should be skiped in a graph key. Data titles does not need
108
raise NotImplementedError("call to an abstract method of %r" % self)
110
def initplotitem(self, plotitem, graph):
111
"""Initialize plotitem
113
This function is called within the plotitem initialization
114
procedure and allows to initialize the plotitem as a data
115
container. For static data the method might just do nothing."""
116
raise NotImplementedError("call to an abstract method of %r" % self)
118
def getcolumnpointsindex_plotitem(self, plotitem, column):
119
"""Data for a column with plotitem
121
Like getcolumnpointsindex but for use within a graph, i.e. with
122
a plotitem container class. For static data being defined within
123
the constructor already, the plotitem reference is not needed and
124
the method can be implemented by calling getcolumnpointsindex."""
125
raise NotImplementedError("call to an abstract method of %r" % self)
127
def getcolumnnames(self, plotitem):
128
"""Return list of column names of the data
130
This method returns a list of column names. It might
131
depend on the graph. (*YES*, it might depend on the graph
132
in case of a function, where function variables might be
133
axis names. Other variables, not available by axes, will
134
be taken from the context.)"""
135
raise NotImplementedError("call to an abstract method of %r" % self)
137
def adjustaxes(self, plotitem, graph, step):
138
"""Adjust axes ranges
140
This method should call adjustaxis for all styles.
141
On step == 0 axes with fixed data should be adjusted.
142
On step == 1 the current axes ranges might be used to
143
calculate further data (e.g. y data for a function y=f(x)
144
where the y range depends on the x range). On step == 2
145
axes ranges not previously set should be updated by data
146
accumulated by step 1."""
147
raise NotImplementedError("call to an abstract method of %r" % self)
149
def draw(self, plotitem, graph):
152
This method should draw the data. Its called by plotinfo,
153
since it can be implemented here more efficiently by avoiding
154
some additional function calls."""
155
raise NotImplementedError("call an abstract method of %r" % self)
159
"""Partly implements the _Idata interface"""
49
def splitatvalue(value, *splitpoints):
51
while section < len(splitpoints) and splitpoints[section] < value:
53
if len(splitpoints) > 1:
58
return (section, value)
61
_mathglobals = {"neg": lambda x: -x,
62
"abs": lambda x: x < 0 and -x or x,
63
"sgn": lambda x: x < 0 and -1 or 1,
73
"sind": lambda x: math.sin(math.pi/180*x),
74
"cosd": lambda x: math.cos(math.pi/180*x),
75
"tand": lambda x: math.tan(math.pi/180*x),
76
"asind": lambda x: 180/math.pi*math.asin(x),
77
"acosd": lambda x: 180/math.pi*math.acos(x),
78
"atand": lambda x: 180/math.pi*math.atan(x),
79
"norm": lambda x, y: math.hypot(x, y),
80
"splitatvalue": splitatvalue,
86
"""graph data interface
88
Graph data consists in columns, where each column might be identified by a
89
string or an integer. Each row in the resulting table refers to a data
92
All methods except for the constructor should consider self and its
93
attributes to be readonly, since the data instance might be shared between
94
several graphs simultaniously.
96
The instance variable columns is a dictionary mapping column names to the
97
data of the column (i.e. to a list). Only static columns (known at
98
construction time) are contained in that dictionary. For data with numbered
99
columns the column data is also available via the list columndata.
100
Otherwise the columndata list should be missing and an access to a column
103
The names of all columns (static and dynamic) must be fixed at the constructor
104
and stated in the columnnames dictionary.
106
The instance variable title and defaultstyles contain the data title and
107
the default styles (a list of styles), respectively.
110
def dynamiccolumns(self, graph):
111
"""create and return dynamic columns data
113
Returns dynamic data matching the given axes (the axes range and other
114
data might be used). The return value is a dictionary similar to the
115
columns instance variable.
121
"Graph data from a list of points"
161
123
defaultstyles = [style.symbol()]
163
def getcolumn(self, column):
164
data, index = self.getcolumnpointsindex(column)
168
return [point[index] for point in data]
170
def getdefaultstyles(self):
171
return self.defaultstyles
176
def initplotitem(self, plotitem, graph):
179
def draw(self, plotitem, graph):
180
columnpointsindex = []
182
for column in self.getcolumnnames(plotitem):
183
points, index = self.getcolumnpointsindex_plotitem(plotitem, column)
184
columnpointsindex.append((column, points, index))
189
raise ValueError("points len differs")
190
for privatedata, style in zip(plotitem.privatedatalist, plotitem.styles):
191
style.initdrawpoints(privatedata, plotitem.sharedata, graph)
192
if len(columnpointsindex):
193
plotitem.sharedata.point = {}
195
for column, points, index in columnpointsindex:
196
if index is not None:
197
plotitem.sharedata.point[column] = points[i][index]
199
plotitem.sharedata.point[column] = points[i]
200
for privatedata, style in zip(plotitem.privatedatalist, plotitem.styles):
201
style.drawpoint(privatedata, plotitem.sharedata, graph)
202
for privatedata, style in zip(plotitem.privatedatalist, plotitem.styles):
203
style.donedrawpoints(privatedata, plotitem.sharedata, graph)
206
class _staticdata(_data):
207
"""Partly implements the _Idata interface
209
This class partly implements the _Idata interface for static data
210
using self.columns and self.points to be initialized by the constructor."""
212
def getcolumnpointsindex(self, column):
213
return self.points, self.columns[column]
216
return len(self.points)
218
def getcolumnnames(self, plotitem):
219
return self.columns.keys()
221
def getcolumnpointsindex_plotitem(self, plotitem, column):
222
return self.getcolumnpointsindex(column)
224
def adjustaxes(self, plotitem, graph, step):
226
for column in self.getcolumnnames(plotitem):
227
points, index = self.getcolumnpointsindex_plotitem(plotitem, column)
228
for privatedata, style in zip(plotitem.privatedatalist, plotitem.styles):
229
style.adjustaxis(privatedata, plotitem.sharedata, graph, column, points, index)
232
class _dynamicdata(_data):
234
def getcolumnpointsindex(self, column):
235
raise RuntimeError("dynamic data not available outside a graph")
238
raise RuntimeError("dynamic data typically has no fixed number of points")
241
class list(_staticdata):
242
"Graph data from a list of points"
244
def getcolumnpointsindex(self, column):
246
if self.addlinenumbers:
247
index = self.columns[column]-1
249
index = self.columns[column]
252
if type(column) != type(column + 0):
253
raise ValueError("integer expected")
255
raise ValueError("integer expected")
256
if self.addlinenumbers:
262
return range(1, 1+len(self.points)), None
265
return self.points, index
267
125
def __init__(self, points, title="user provided list", addlinenumbers=1, **columns):
269
# be paranoid and check each row to have the same number of points
270
127
l = len(points[0])
128
self.columndata = [[x] for x in points[0]]
129
for point in points[1:]:
273
131
raise ValueError("different number of columns per point")
132
for i, x in enumerate(point):
133
self.columndata[i].append(x)
274
134
for v in columns.values():
275
135
if abs(v) > l or (not addlinenumbers and abs(v) == l):
276
136
raise ValueError("column number bigger than number of columns")
278
self.columns = columns
138
self.columndata = [range(1, len(points) + 1)] + self.columndata
139
self.columns = dict([(key, self.columndata[i]) for key, i in columns.items()])
141
self.columns = dict([(key, []) for key, i in columns])
142
self.columnnames = self.columns.keys()
279
143
self.title = title
280
self.addlinenumbers = addlinenumbers
283
##############################################################
284
# math tree enhanced by column number variables
285
##############################################################
287
class MathTreeFuncCol(mathtree.MathTreeFunc1):
289
def __init__(self, *args):
290
mathtree.MathTreeFunc1.__init__(self, "_column_", *args)
293
# we misuse VarList here:
294
# - instead of returning a string, we return this instance itself
295
# - before calculating the expression, you must call ColumnNameAndNumber
296
# once (when limiting the context to external defined variables,
297
# otherwise you have to call it each time)
300
def ColumnNameAndNumber(_hidden_self, **args):
301
number = int(_hidden_self.Args[0].Calc(**args))
302
_hidden_self.varname = "_column_%i" % number
303
return _hidden_self.varname, number
308
def Calc(_hidden_self, **args):
309
return args[_hidden_self.varname]
311
MathTreeFuncsWithCol = mathtree.DefaultMathTreeFuncs + [MathTreeFuncCol]
316
def __init__(self, tree):
318
self.Calc = tree.Calc
319
self.__str__ = tree.__str__
322
# returns a list of regular variables (strings) like the original mathtree
323
return [var for var in self.tree.VarList() if not isinstance(var, MathTreeFuncCol) and var[:8] != "_column_"]
325
def columndict(_hidden_self, **context):
326
# returns a dictionary of column names (keys) and column numbers (values)
328
for var in _hidden_self.tree.VarList():
329
if isinstance(var, MathTreeFuncCol):
330
name, number = var.ColumnNameAndNumber(**context)
331
columndict[name] = number
332
elif var[:8] == "_column_":
333
columndict[var] = int(var[8:])
337
class dataparser(mathtree.parser):
338
# mathtree parser enhanced by column handling
339
# parse returns a columntree instead of a regular tree
341
def __init__(self, MathTreeFuncs=MathTreeFuncsWithCol, **kwargs):
342
mathtree.parser.__init__(self, MathTreeFuncs=MathTreeFuncs, **kwargs)
344
def parse(self, expr):
345
return columntree(mathtree.parser.parse(self, expr.replace("$", "_column_")))
347
##############################################################
351
"""this is a helper class to mark, that no title was privided
352
(since a title equals None is a valid input, it needs to be
353
distinguished from providing no title when a title will be
354
created automatically)"""
357
class data(_staticdata):
149
_columnintref = re.compile(r"\$(-?\d+)", re.IGNORECASE)
358
152
"creates a new data set out of an existing data set"
360
def __init__(self, data, title=notitle, parser=dataparser(), context={}, **columns):
154
def __init__(self, data, title=_notitle, context={}, copy=1,
155
replacedollar=1, columncallback="__column__", **columns):
361
156
# build a nice title
157
if title is _notitle:
363
158
items = columns.items()
364
159
items.sort() # we want sorted items (otherwise they would be unpredictable scrambled)
365
self.title = "%s: %s" % (data.title,
160
self.title = "%s: %s" % (text.escapestring(data.title or "unkown source"),
366
161
", ".join(["%s=%s" % (text.escapestring(key),
367
text.escapestring(value))
162
text.escapestring(str(value)))
368
163
for key, value in items]))
370
165
self.title = title
372
167
self.orgdata = data
168
self.defaultstyles = self.orgdata.defaultstyles
374
170
# analyse the **columns argument
375
171
self.columns = {}
377
for column, value in columns.items():
172
for columnname, value in columns.items():
173
# search in the columns dictionary
379
# try if it is a valid column identifier
380
self.columns[column] = self.orgdata.getcolumnpointsindex(value)
381
except (KeyError, ValueError):
382
# take it as a mathematical expression
383
tree = parser.parse(value)
384
columndict = tree.columndict(**context)
386
for var, value in columndict.items():
387
# column data accessed via $<column number>
388
points, index = self.orgdata.getcolumnpointsindex(value)
389
varpointsindex.append((var, points, index))
390
for var in tree.VarList():
392
# column data accessed via the name of the column
393
points, index = self.orgdata.getcolumnpointsindex(var)
394
except (KeyError, ValueError):
395
# other data available in context
396
if var not in context.keys():
397
raise ValueError("undefined variable '%s'" % var)
175
self.columns[columnname] = self.orgdata.columns[value]
177
# search in the columndata list
179
self.columns[columnname] = self.orgdata.columndata[value]
180
except (AttributeError, TypeError):
181
# value was not an valid column identifier
182
# i.e. take it as a mathematical expression
184
m = _columnintref.search(value)
186
value = "%s%s(%s)%s" % (value[:m.start()], columncallback, m.groups()[0], value[m.end():])
187
m = _columnintref.search(value)
188
value = value.replace("$", columncallback)
189
expression = compile(value.strip(), __file__, "eval")
190
context = context.copy()
191
context[columncallback] = self.columncallback
192
if self.orgdata.columns:
193
key, columndata = self.orgdata.columns.items()[0]
194
count = len(columndata)
195
elif self.orgdata.columndata:
196
count = len(self.orgdata.columndata[0])
399
varpointsindex.append((var, points, index))
400
newdata = [None]*self.getcount()
401
vars = context.copy() # do not modify context, use a copy vars instead
402
for i in xrange(self.getcount()):
403
# insert column data as prepared in varpointsindex
404
for var, point, index in varpointsindex:
405
if index is not None:
406
vars[var] = points[i][index]
408
vars[var] = points[i]
409
# evaluate expression
411
newdata[i] = tree.Calc(**vars)
412
except (ArithmeticError, ValueError):
415
# point[newcolumnnumber] = eval(str(tree), vars)
417
# XXX: It might happen, that the evaluation of the expression
418
# seems to work, but the result is NaN/Inf/-Inf. This
419
# is highly plattform dependend.
421
self.columns[column] = newdata, None
423
def getcolumnpointsindex(self, column):
424
return self.columns[column]
427
return self.orgdata.getcount()
429
def getdefaultstyles(self):
430
return self.orgdata.getdefaultstyles()
200
for i in xrange(count):
201
self.columncallbackcount = i
202
for key, values in self.orgdata.columns.items():
203
context[key] = values[i]
205
newdata.append(eval(expression, _mathglobals, context))
206
except (ArithmeticError, ValueError):
208
self.columns[columnname] = newdata
211
# copy other, non-conflicting column names
212
for columnname, columndata in self.orgdata.columns.items():
213
if not self.columns.has_key(columnname):
214
self.columns[columnname] = columndata
216
self.columnnames = self.columns.keys()
218
def columncallback(self, value):
220
return self.orgdata.columndata[value][self.columncallbackcount]
222
return self.orgdata.columns[value][self.columncallbackcount]
586
382
data.__init__(self, readfile(filename, "user provided file-like object"), **kwargs)
589
class function(_dynamicdata):
591
defaultstyles = [style.line()]
593
def __init__(self, expression, title=notitle, min=None, max=None,
594
points=100, parser=mathtree.parser(), context={}):
389
defaultstyles = [style.line()]
391
def getcachekey(self, *args):
392
return ":".join([str(x) for x in args])
394
def __init__(self, filename, minrank=None, maxrank=None, **kwargs):
398
def __init__(self, file):
404
self.fill) = struct.unpack("<5i20s", file.read(40))
405
if self.magic != 0x20770002:
406
raise ValueError("bad magic number")
410
def __init__(self, file, i):
419
self.rank) = struct.unpack("<6i2h", file.read(28))
423
def __init__(self, file, sd):
424
file.seek(sd.absaddr)
429
self.dummy) = struct.unpack("<3i2h", file.read(16))
430
oln, olt = self.orgx, self.orgy
431
self.points = [(olt, oln)]
432
for i in range(self.nstrokes):
433
c1, c2 = struct.unpack("2c", file.read(2))
444
c3, c4, c5, c6, c7, c8 = struct.unpack("6c", file.read(6))
446
c2 = chr(ord(c2) | 0x40)
447
dx, dy = struct.unpack("<2i", c3+c4+c1+c2+c7+c8+c5+c6)
450
self.points.append((olt, oln))
451
sd.nstrokes = self.nstrokes
453
def readfile(file, title):
455
file.seek(h.dictaddr)
456
sds = [segdict(file, i+1) for i in range(h.segcount)]
457
sbs = [segment(file, sd) for sd in sds]
459
# remove jumps at long +/- 180
460
for sd, sb in zip(sds, sbs):
461
if sd.minlong < -150*3600 and sd.maxlong > 150*3600:
462
for i, (lat, long) in enumerate(sb.points):
464
sb.points[i] = lat, long + 360*3600
467
for sd, sb in zip(sds, sbs):
468
if ((minrank is None or sd.rank >= minrank) and
469
(maxrank is None or sd.rank <= maxrank)):
471
columndata.append((None, None))
472
columndata.extend([(long/3600.0, lat/3600.0)
473
for lat, long in sb.points])
475
result = list(columndata, title=title)
476
result.defaultstyles = self.defaultstyles
483
# not a file-like object -> open it
484
cachekey = self.getcachekey(filename, minrank, maxrank)
485
if not cbdfilecache.has_key(cachekey):
486
cbdfilecache[cachekey] = readfile(open(filename, "rb"), filename)
487
data.__init__(self, cbdfilecache[cachekey], **kwargs)
489
data.__init__(self, readfile(filename, "user provided file-like object"), **kwargs)
492
class function(_data):
494
defaultstyles = [style.line()]
496
assignmentpattern = re.compile(r"\s*([a-z_][a-z0-9_]*)\s*\(\s*([a-z_][a-z0-9_]*)\s*\)\s*=", re.IGNORECASE)
498
def __init__(self, expression, title=_notitle, min=None, max=None,
499
points=100, context={}):
501
if title is _notitle:
597
502
self.title = expression
599
504
self.title = title
602
507
self.numberofpoints = points
603
508
self.context = context.copy() # be save on late evaluations
604
self.yname, expression = [x.strip() for x in expression.split("=")]
605
self.mathtree = parser.parse(expression)
607
def getcolumnpointsindex_plotitem(self, plotitem, column):
608
return plotitem.points, plotitem.columns[column]
610
def initplotitem(self, plotitem, graph):
612
for xname in self.mathtree.VarList():
613
if xname in graph.axes.keys():
614
if self.xname is None:
617
raise ValueError("multiple variables found")
618
if self.xname is None:
619
raise ValueError("no variable found")
620
plotitem.columns = {self.xname: 0, self.yname: 1}
622
def getcolumnnames(self, plotitem):
623
return [self.xname, self.yname]
625
def adjustaxes(self, plotitem, graph, step):
628
if self.min is not None:
629
points.append(self.min)
630
if self.max is not None:
631
points.append(self.max)
632
for privatedata, style in zip(plotitem.privatedatalist, plotitem.styles):
633
style.adjustaxis(privatedata, plotitem.sharedata, graph, self.xname, points, None)
635
xaxis = graph.axes[self.xname]
636
min, max = xaxis.getrange()
637
if self.min is not None: min = self.min
638
if self.max is not None: max = self.max
639
vmin = xaxis.convert(min)
640
vmax = xaxis.convert(max)
642
for i in range(self.numberofpoints):
643
v = vmin + (vmax-vmin)*i / (self.numberofpoints-1.0)
645
self.context[self.xname] = x
647
y = self.mathtree.Calc(**self.context)
648
except (ArithmeticError, ValueError):
650
plotitem.points.append([x, y])
652
for privatedata, style in zip(plotitem.privatedatalist, plotitem.styles):
653
style.adjustaxis(privatedata, plotitem.sharedata, graph, self.yname, plotitem.points, 1)
656
class paramfunction(_staticdata):
509
m = self.assignmentpattern.match(expression)
511
self.yname, self.xname = m.groups()
512
expression = expression[m.end():]
514
raise ValueError("y(x)=... or similar expected")
515
if context.has_key(self.xname):
516
raise ValueError("xname in context")
517
self.expression = compile(expression.strip(), __file__, "eval")
519
self.columnnames = [self.xname, self.yname]
521
def dynamiccolumns(self, graph):
522
dynamiccolumns = {self.xname: [], self.yname: []}
524
xaxis = graph.axes[self.xname]
525
from pyx.graph.axis import logarithmic
526
logaxis = isinstance(xaxis.axis, logarithmic)
527
if self.min is not None:
531
if self.max is not None:
538
for i in range(self.numberofpoints):
539
x = min + (max-min)*i / (self.numberofpoints-1.0)
542
dynamiccolumns[self.xname].append(x)
543
self.context[self.xname] = x
545
y = eval(self.expression, _mathglobals, self.context)
546
except (ArithmeticError, ValueError):
548
dynamiccolumns[self.yname].append(y)
549
return dynamiccolumns
552
class functionxy(function):
554
def __init__(self, f, min=None, max=None, **kwargs):
555
function.__init__(self, "y(x)=f(x)", context={"f": f}, min=min, max=max, **kwargs)
558
class paramfunction(_data):
658
560
defaultstyles = [style.line()]
660
def __init__(self, varname, min, max, expression, title=notitle, points=100, parser=mathtree.parser(), context={}):
562
def __init__(self, varname, min, max, expression, title=_notitle, points=100, context={}):
563
if context.has_key(varname):
564
raise ValueError("varname in context")
565
if title is _notitle:
662
566
self.title = expression
664
568
self.title = title
665
varlist, expressionlist = expression.split("=")
666
keys = varlist.split(",")
667
mathtrees = parser.parse(expressionlist)
668
if len(keys) != len(mathtrees):
669
raise ValueError("unpack tuple of wrong size")
671
self.points = [None]*points
673
for index, key in enumerate(keys):
674
self.columns[key.strip()] = index
569
varlist, expression = expression.split("=")
570
expression = compile(expression.strip(), __file__, "eval")
571
keys = [key.strip() for key in varlist.split(",")]
572
self.columns = dict([(key, []) for key in keys])
573
context = context.copy()
675
574
for i in range(points):
676
575
param = min + (max-min)*i / (points-1.0)
677
576
context[varname] = param
678
self.points[i] = [None]*l
679
for index, mathtree in enumerate(mathtrees):
681
self.points[i][index] = mathtree.Calc(**context)
682
except (ArithmeticError, ValueError):
683
self.points[i][index] = None
577
values = eval(expression, _mathglobals, context)
578
for key, value in zip(keys, values):
579
self.columns[key].append(value)
580
if len(keys) != len(values):
581
raise ValueError("unpack tuple of wrong size")
582
self.columnnames = self.columns.keys()
585
class paramfunctionxy(paramfunction):
587
def __init__(self, f, min, max, **kwargs):
588
paramfunction.__init__(self, "t", min, max, "x, y = f(t)", context={"f": f}, **kwargs)