~ubuntu-branches/ubuntu/trusty/pyx/trusty

« back to all changes in this revision

Viewing changes to pyx/graph/data.py

  • Committer: Bazaar Package Importer
  • Author(s): Thomas Viehmann
  • Date: 2006-11-26 14:04:53 UTC
  • mfrom: (2.1.3 edgy)
  • Revision ID: james.westby@ubuntu.com-20061126140453-1dq3cycpspmlik2t
Tags: 0.9-3
* New maintainer. Thank you for more than three years of
  maintenance,  Graham! Closes: #400087
* Don't hard-code python 2.3 in manual/Makefile.
  Thanks to Matthias Klose for the bug report and patch.
  Closes: #392634
* Remove obsolete dh_python call from debian/rules.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#!/usr/bin/env python
2
1
# -*- coding: ISO-8859-1 -*-
3
2
#
4
3
#
5
4
# Copyright (C) 2002-2004 J�rg Lehmann <joergl@users.sourceforge.net>
6
5
# Copyright (C) 2003-2004 Michael Schindler <m-schindler@users.sourceforge.net>
7
 
# Copyright (C) 2002-2004 Andr� Wobst <wobsta@users.sourceforge.net>
 
6
# Copyright (C) 2002-2006 Andr� Wobst <wobsta@users.sourceforge.net>
8
7
#
9
8
# This file is part of PyX (http://pyx.sourceforge.net/).
10
9
#
20
19
#
21
20
# You should have received a copy of the GNU General Public License
22
21
# along with PyX; if not, write to the Free Software
23
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
24
 
 
25
 
 
26
 
import re, ConfigParser
27
 
from pyx import mathtree, text
 
22
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 
23
 
 
24
from __future__ import nested_scopes
 
25
 
 
26
import math, re, ConfigParser, struct, warnings
 
27
from pyx import text
 
28
from pyx.style import linestyle
28
29
from pyx.graph import style
29
30
 
30
31
try:
37
38
try:
38
39
    dict()
39
40
except NameError:
40
 
    # fallback implementation for Python 2.1 and below
 
41
    # fallback implementation for Python 2.1
41
42
    def dict(items):
42
43
        result = {}
43
44
        for key, value in items:
44
45
            result[key] = value
45
46
        return result
46
47
 
47
 
class _Idata:
48
 
    """Interface for graph data
49
 
 
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.
53
 
 
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."""
58
 
 
59
 
    def getcolumnpointsindex(self, column):
60
 
        """Data for a column
61
 
 
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
66
 
        data.
67
 
 
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)
73
 
 
74
 
    def getcolumn(self, column):
75
 
        """Data for a column
76
 
 
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)
83
 
 
84
 
    def getcount(self):
85
 
        """Number of points
86
 
 
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)
91
 
 
92
 
    def getdefaultstyles(self):
93
 
        """Default styles for the data
94
 
 
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)
99
 
 
100
 
    def gettitle(self):
101
 
        """Title of the data
102
 
 
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
107
 
        to be unique."""
108
 
        raise NotImplementedError("call to an abstract method of %r" % self)
109
 
 
110
 
    def initplotitem(self, plotitem, graph):
111
 
        """Initialize plotitem
112
 
 
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)
117
 
 
118
 
    def getcolumnpointsindex_plotitem(self, plotitem, column):
119
 
        """Data for a column with plotitem
120
 
 
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)
126
 
 
127
 
    def getcolumnnames(self, plotitem):
128
 
        """Return list of column names of the data
129
 
 
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)
136
 
 
137
 
    def adjustaxes(self, plotitem, graph, step):
138
 
        """Adjust axes ranges
139
 
 
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)
148
 
 
149
 
    def draw(self, plotitem, graph):
150
 
        """Draw data
151
 
 
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)
156
 
 
157
 
 
158
 
class _data(_Idata):
159
 
    """Partly implements the _Idata interface"""
 
48
 
 
49
def splitatvalue(value, *splitpoints):
 
50
    section = 0
 
51
    while section < len(splitpoints) and splitpoints[section] < value:
 
52
        section += 1
 
53
    if len(splitpoints) > 1:
 
54
        if section % 2:
 
55
            section = None
 
56
        else:
 
57
            section >>= 1
 
58
    return (section, value)
 
59
 
 
60
 
 
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,
 
64
                "sqrt": math.sqrt,
 
65
                "exp": math.exp,
 
66
                "log": math.log,
 
67
                "sin": math.sin,
 
68
                "cos": math.cos,
 
69
                "tan": math.tan,
 
70
                "asin": math.asin,
 
71
                "acos": math.acos,
 
72
                "atan": math.atan,
 
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,
 
81
                "pi": math.pi,
 
82
                "e": math.e}
 
83
 
 
84
 
 
85
class _data:
 
86
    """graph data interface
 
87
 
 
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
 
90
    point.
 
91
 
 
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.
 
95
 
 
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
 
101
    number will fail.
 
102
 
 
103
    The names of all columns (static and dynamic) must be fixed at the constructor
 
104
    and stated in the columnnames dictionary.
 
105
 
 
106
    The instance variable title and defaultstyles contain the data title and
 
107
    the default styles (a list of styles), respectively.
 
108
    """
 
109
 
 
110
    def dynamiccolumns(self, graph):
 
111
        """create and return dynamic columns data
 
112
 
 
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.
 
116
        """
 
117
        return {}
 
118
 
 
119
 
 
120
class list(_data):
 
121
    "Graph data from a list of points"
160
122
 
161
123
    defaultstyles = [style.symbol()]
162
124
 
163
 
    def getcolumn(self, column):
164
 
        data, index = self.getcolumnpointsindex(column)
165
 
        if index is None:
166
 
            return data
167
 
        else:
168
 
            return [point[index] for point in data]
169
 
 
170
 
    def getdefaultstyles(self):
171
 
        return self.defaultstyles
172
 
 
173
 
    def gettitle(self):
174
 
        return self.title
175
 
 
176
 
    def initplotitem(self, plotitem, graph):
177
 
        pass
178
 
 
179
 
    def draw(self, plotitem, graph):
180
 
        columnpointsindex = []
181
 
        l = None
182
 
        for column in self.getcolumnnames(plotitem):
183
 
            points, index = self.getcolumnpointsindex_plotitem(plotitem, column)
184
 
            columnpointsindex.append((column, points, index))
185
 
            if l is None:
186
 
                l = len(points)
187
 
            else:
188
 
                if l != len(points):
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 = {}
194
 
            for i in xrange(l):
195
 
                for column, points, index in columnpointsindex:
196
 
                    if index is not None:
197
 
                        plotitem.sharedata.point[column] = points[i][index]
198
 
                    else:
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)
204
 
 
205
 
 
206
 
class _staticdata(_data):
207
 
    """Partly implements the _Idata interface
208
 
 
209
 
    This class partly implements the _Idata interface for static data
210
 
    using self.columns and self.points to be initialized by the constructor."""
211
 
 
212
 
    def getcolumnpointsindex(self, column):
213
 
        return self.points, self.columns[column]
214
 
 
215
 
    def getcount(self):
216
 
        return len(self.points)
217
 
 
218
 
    def getcolumnnames(self, plotitem):
219
 
        return self.columns.keys()
220
 
 
221
 
    def getcolumnpointsindex_plotitem(self, plotitem, column):
222
 
        return self.getcolumnpointsindex(column)
223
 
 
224
 
    def adjustaxes(self, plotitem, graph, step):
225
 
        if step == 0:
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)
230
 
 
231
 
 
232
 
class _dynamicdata(_data):
233
 
 
234
 
    def getcolumnpointsindex(self, column):
235
 
        raise RuntimeError("dynamic data not available outside a graph")
236
 
 
237
 
    def getcount(self):
238
 
        raise RuntimeError("dynamic data typically has no fixed number of points")
239
 
 
240
 
 
241
 
class list(_staticdata):
242
 
    "Graph data from a list of points"
243
 
 
244
 
    def getcolumnpointsindex(self, column):
245
 
        try:
246
 
            if self.addlinenumbers:
247
 
                index = self.columns[column]-1
248
 
            else:
249
 
                index = self.columns[column]
250
 
        except KeyError:
251
 
            try:
252
 
                if type(column) != type(column + 0):
253
 
                    raise ValueError("integer expected")
254
 
            except:
255
 
                raise ValueError("integer expected")
256
 
            if self.addlinenumbers:
257
 
                if column > 0:
258
 
                    index = column-1
259
 
                elif column < 0:
260
 
                    index = column
261
 
                else:
262
 
                    return range(1, 1+len(self.points)), None
263
 
            else:
264
 
                index = column
265
 
        return self.points, index
266
 
 
267
125
    def __init__(self, points, title="user provided list", addlinenumbers=1, **columns):
268
126
        if len(points):
269
 
            # be paranoid and check each row to have the same number of points
270
127
            l = len(points[0])
271
 
            for p in points[1:]:
272
 
                if l != len(p):
 
128
            self.columndata = [[x] for x in points[0]]
 
129
            for point in points[1:]:
 
130
                if l != len(point):
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")
277
 
        self.points = points
278
 
        self.columns = columns
 
137
            if addlinenumbers:
 
138
                self.columndata = [range(1, len(points) + 1)] + self.columndata
 
139
            self.columns = dict([(key, self.columndata[i]) for key, i in columns.items()])
 
140
        else:
 
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
281
 
 
282
 
 
283
 
##############################################################
284
 
# math tree enhanced by column number variables
285
 
##############################################################
286
 
 
287
 
class MathTreeFuncCol(mathtree.MathTreeFunc1):
288
 
 
289
 
    def __init__(self, *args):
290
 
        mathtree.MathTreeFunc1.__init__(self, "_column_", *args)
291
 
 
292
 
    def VarList(self):
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)
298
 
        return [self]
299
 
 
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
304
 
 
305
 
    def __str__(self):
306
 
        return self.varname
307
 
 
308
 
    def Calc(_hidden_self, **args):
309
 
        return args[_hidden_self.varname]
310
 
 
311
 
MathTreeFuncsWithCol = mathtree.DefaultMathTreeFuncs + [MathTreeFuncCol]
312
 
 
313
 
 
314
 
class columntree:
315
 
 
316
 
    def __init__(self, tree):
317
 
        self.tree = tree
318
 
        self.Calc = tree.Calc
319
 
        self.__str__ = tree.__str__
320
 
 
321
 
    def VarList(self):
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_"]
324
 
 
325
 
    def columndict(_hidden_self, **context):
326
 
        # returns a dictionary of column names (keys) and column numbers (values)
327
 
        columndict = {}
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:])
334
 
        return columndict
335
 
 
336
 
 
337
 
class dataparser(mathtree.parser):
338
 
    # mathtree parser enhanced by column handling
339
 
    # parse returns a columntree instead of a regular tree
340
 
 
341
 
    def __init__(self, MathTreeFuncs=MathTreeFuncsWithCol, **kwargs):
342
 
        mathtree.parser.__init__(self, MathTreeFuncs=MathTreeFuncs, **kwargs)
343
 
 
344
 
    def parse(self, expr):
345
 
        return columntree(mathtree.parser.parse(self, expr.replace("$", "_column_")))
346
 
 
347
 
##############################################################
348
 
 
349
 
 
350
 
class notitle:
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)"""
 
144
 
 
145
 
 
146
class _notitle:
355
147
    pass
356
148
 
357
 
class data(_staticdata):
 
149
_columnintref = re.compile(r"\$(-?\d+)", re.IGNORECASE)
 
150
 
 
151
class data(_data):
358
152
    "creates a new data set out of an existing data set"
359
153
 
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
362
 
        if title is notitle:
 
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]))
369
164
        else:
370
165
            self.title = title
371
166
 
372
167
        self.orgdata = data
 
168
        self.defaultstyles = self.orgdata.defaultstyles
373
169
 
374
170
        # analyse the **columns argument
375
171
        self.columns = {}
376
 
        newcolumns = {}
377
 
        for column, value in columns.items():
 
172
        for columnname, value in columns.items():
 
173
            # search in the columns dictionary
378
174
            try:
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)
385
 
                varpointsindex = []
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():
391
 
                    try:
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]
 
176
            except KeyError:
 
177
                # search in the columndata list
 
178
                try:
 
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
 
183
                    if replacedollar:
 
184
                        m = _columnintref.search(value)
 
185
                        while m:
 
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])
398
197
                    else:
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]
407
 
                        else:
408
 
                            vars[var] = points[i]
409
 
                    # evaluate expression
410
 
                    try:
411
 
                        newdata[i] = tree.Calc(**vars)
412
 
                    except (ArithmeticError, ValueError):
413
 
                        newdata[i] = None
414
 
                    # we could also do:
415
 
                    # point[newcolumnnumber] = eval(str(tree), vars)
416
 
 
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.
420
 
 
421
 
                self.columns[column] = newdata, None
422
 
 
423
 
    def getcolumnpointsindex(self, column):
424
 
        return self.columns[column]
425
 
 
426
 
    def getcount(self):
427
 
        return self.orgdata.getcount()
428
 
 
429
 
    def getdefaultstyles(self):
430
 
        return self.orgdata.getdefaultstyles()
 
198
                        count = 0
 
199
                    newdata = []
 
200
                    for i in xrange(count):
 
201
                        self.columncallbackcount = i
 
202
                        for key, values in self.orgdata.columns.items():
 
203
                            context[key] = values[i]
 
204
                        try:
 
205
                            newdata.append(eval(expression, _mathglobals, context))
 
206
                        except (ArithmeticError, ValueError):
 
207
                            newdata.append(None)
 
208
                    self.columns[columnname] = newdata
 
209
 
 
210
        if copy:
 
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
 
215
 
 
216
        self.columnnames = self.columns.keys()
 
217
 
 
218
    def columncallback(self, value):
 
219
        try:
 
220
            return self.orgdata.columndata[value][self.columncallbackcount]
 
221
        except:
 
222
            return self.orgdata.columns[value][self.columncallbackcount]
431
223
 
432
224
 
433
225
filecache = {}
492
284
 
493
285
        def readfile(file, title, self=self, commentpattern=commentpattern, stringpattern=stringpattern, columnpattern=columnpattern, skiphead=skiphead, skiptail=skiptail, every=every):
494
286
            columns = []
495
 
            points = []
 
287
            columndata = []
496
288
            linenumber = 0
497
289
            maxcolumns = 0
498
290
            for line in file.readlines():
499
291
                line = line.strip()
500
292
                match = commentpattern.match(line)
501
293
                if match:
502
 
                    if not len(points):
 
294
                    if not len(columndata):
503
295
                        columns = self.splitline(line[match.end():], stringpattern, columnpattern, tofloat=0)
504
296
                else:
505
297
                    linedata = []
510
302
                            linedata = [linenumber + 1] + linedata
511
303
                            if len(linedata) > maxcolumns:
512
304
                                maxcolumns = len(linedata)
513
 
                            points.append(linedata)
 
305
                            columndata.append(linedata)
514
306
                        linenumber += 1
515
307
            if skiptail >= every:
516
308
                skip, x = divmod(skiptail, every)
517
 
                del points[-skip:]
518
 
            for i in xrange(len(points)):
519
 
                if len(points[i]) != maxcolumns:
520
 
                    points[i].extend([None]*(maxcolumns-len(points[i])))
521
 
            return list(points, title=title, addlinenumbers=0,
 
309
                del columndata[-skip:]
 
310
            for i in xrange(len(columndata)):
 
311
                if len(columndata[i]) != maxcolumns:
 
312
                    columndata[i].extend([None]*(maxcolumns-len(columndata[i])))
 
313
            return list(columndata, title=title, addlinenumbers=0,
522
314
                        **dict([(column, i+1) for i, column in enumerate(columns[:maxcolumns-1])]))
523
315
 
524
316
        try:
553
345
            config.readfp(file)
554
346
            sections = config.sections()
555
347
            sections.sort()
556
 
            points = [None]*len(sections)
 
348
            columndata = [None]*len(sections)
557
349
            maxcolumns = 1
558
350
            columns = {}
559
351
            for i in xrange(len(sections)):
572
364
                        maxcolumns += 1
573
365
                    else:
574
366
                        point[index] = value
575
 
                points[i] = point
576
 
            return list(points, title=title, addlinenumbers=0, **columns)
 
367
                columndata[i] = point
 
368
            # wrap result into a data instance to remove column numbers
 
369
            result = data(list(columndata, addlinenumbers=0, **columns), title=title)
 
370
            # ... but reinsert sections as linenumbers
 
371
            result.columndata = [[x[0] for x in columndata]]
 
372
            return result
577
373
 
578
374
        try:
579
375
            filename.readlines
586
382
            data.__init__(self, readfile(filename, "user provided file-like object"), **kwargs)
587
383
 
588
384
 
589
 
class function(_dynamicdata):
590
 
 
591
 
    defaultstyles = [style.line()]
592
 
 
593
 
    def __init__(self, expression, title=notitle, min=None, max=None,
594
 
                 points=100, parser=mathtree.parser(), context={}):
595
 
 
596
 
        if title is notitle:
 
385
cbdfilecache = {}
 
386
 
 
387
class cbdfile(data):
 
388
 
 
389
    defaultstyles = [style.line()]
 
390
 
 
391
    def getcachekey(self, *args):
 
392
        return ":".join([str(x) for x in args])
 
393
 
 
394
    def __init__(self, filename, minrank=None, maxrank=None, **kwargs):
 
395
 
 
396
        class cbdhead:
 
397
 
 
398
            def __init__(self, file):
 
399
                (self.magic,
 
400
                 self.dictaddr,
 
401
                 self.segcount,
 
402
                 self.segsize,
 
403
                 self.segmax,
 
404
                 self.fill) = struct.unpack("<5i20s", file.read(40))
 
405
                if self.magic != 0x20770002:
 
406
                    raise ValueError("bad magic number")
 
407
 
 
408
        class segdict:
 
409
 
 
410
            def __init__(self, file, i):
 
411
                self.index = i
 
412
                (self.segid,
 
413
                 self.maxlat,
 
414
                 self.minlat,
 
415
                 self.maxlong,
 
416
                 self.minlong,
 
417
                 self.absaddr,
 
418
                 self.nbytes,
 
419
                 self.rank) = struct.unpack("<6i2h", file.read(28))
 
420
 
 
421
        class segment:
 
422
 
 
423
            def __init__(self, file, sd):
 
424
                file.seek(sd.absaddr)
 
425
                (self.orgx,
 
426
                 self.orgy,
 
427
                 self.id,
 
428
                 self.nstrokes,
 
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))
 
434
                    if ord(c2) & 0x40:
 
435
                        if c1 > "\177":
 
436
                            dy = ord(c1) - 256
 
437
                        else:
 
438
                            dy = ord(c1)
 
439
                        if c2 > "\177":
 
440
                            dx = ord(c2) - 256
 
441
                        else:
 
442
                            dx = ord(c2) - 64
 
443
                    else:
 
444
                        c3, c4, c5, c6, c7, c8 = struct.unpack("6c", file.read(6))
 
445
                        if c2 > "\177":
 
446
                            c2 = chr(ord(c2) | 0x40)
 
447
                        dx, dy = struct.unpack("<2i", c3+c4+c1+c2+c7+c8+c5+c6)
 
448
                    oln += dx
 
449
                    olt += dy
 
450
                    self.points.append((olt, oln))
 
451
                sd.nstrokes = self.nstrokes
 
452
 
 
453
        def readfile(file, title):
 
454
            h = cbdhead(file)
 
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]
 
458
 
 
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):
 
463
                         if long < 0:
 
464
                             sb.points[i] = lat, long + 360*3600
 
465
 
 
466
            columndata = []
 
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)):
 
470
                    if columndata:
 
471
                        columndata.append((None, None))
 
472
                    columndata.extend([(long/3600.0, lat/3600.0)
 
473
                                       for lat, long in sb.points])
 
474
 
 
475
            result = list(columndata, title=title)
 
476
            result.defaultstyles = self.defaultstyles
 
477
            return result
 
478
 
 
479
 
 
480
        try:
 
481
            filename.readlines
 
482
        except:
 
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)
 
488
        else:
 
489
            data.__init__(self, readfile(filename, "user provided file-like object"), **kwargs)
 
490
 
 
491
 
 
492
class function(_data):
 
493
 
 
494
    defaultstyles = [style.line()]
 
495
 
 
496
    assignmentpattern = re.compile(r"\s*([a-z_][a-z0-9_]*)\s*\(\s*([a-z_][a-z0-9_]*)\s*\)\s*=", re.IGNORECASE)
 
497
 
 
498
    def __init__(self, expression, title=_notitle, min=None, max=None,
 
499
                 points=100, context={}):
 
500
 
 
501
        if title is _notitle:
597
502
            self.title = expression
598
503
        else:
599
504
            self.title = title
601
506
        self.max = max
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)
606
 
 
607
 
    def getcolumnpointsindex_plotitem(self, plotitem, column):
608
 
        return plotitem.points, plotitem.columns[column]
609
 
 
610
 
    def initplotitem(self, plotitem, graph):
611
 
        self.xname = None
612
 
        for xname in self.mathtree.VarList():
613
 
            if xname in graph.axes.keys():
614
 
                if self.xname is None:
615
 
                    self.xname = xname
616
 
                else:
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}
621
 
 
622
 
    def getcolumnnames(self, plotitem):
623
 
        return [self.xname, self.yname]
624
 
 
625
 
    def adjustaxes(self, plotitem, graph, step):
626
 
        if step == 0:
627
 
            points = []
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)
634
 
        elif step == 1:
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)
641
 
            plotitem.points = []
642
 
            for i in range(self.numberofpoints):
643
 
                v = vmin + (vmax-vmin)*i / (self.numberofpoints-1.0)
644
 
                x = xaxis.invert(v)
645
 
                self.context[self.xname] = x
646
 
                try:
647
 
                    y = self.mathtree.Calc(**self.context)
648
 
                except (ArithmeticError, ValueError):
649
 
                    y = None
650
 
                plotitem.points.append([x, y])
651
 
        elif step == 2:
652
 
            for privatedata, style in zip(plotitem.privatedatalist, plotitem.styles):
653
 
                style.adjustaxis(privatedata, plotitem.sharedata, graph, self.yname, plotitem.points, 1)
654
 
 
655
 
 
656
 
class paramfunction(_staticdata):
 
509
        m = self.assignmentpattern.match(expression)
 
510
        if m:
 
511
            self.yname, self.xname = m.groups()
 
512
            expression = expression[m.end():]
 
513
        else:
 
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")
 
518
        self.columns = {}
 
519
        self.columnnames = [self.xname, self.yname]
 
520
 
 
521
    def dynamiccolumns(self, graph):
 
522
        dynamiccolumns = {self.xname: [], self.yname: []}
 
523
 
 
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:
 
528
            min = self.min
 
529
        else:
 
530
            min = xaxis.data.min
 
531
        if self.max is not None:
 
532
            max = self.max
 
533
        else:
 
534
            max = xaxis.data.max
 
535
        if logaxis:
 
536
            min = math.log(min)
 
537
            max = math.log(max)
 
538
        for i in range(self.numberofpoints):
 
539
            x = min + (max-min)*i / (self.numberofpoints-1.0)
 
540
            if logaxis:
 
541
                x = math.exp(x)
 
542
            dynamiccolumns[self.xname].append(x)
 
543
            self.context[self.xname] = x
 
544
            try:
 
545
                y = eval(self.expression, _mathglobals, self.context)
 
546
            except (ArithmeticError, ValueError):
 
547
                y = None
 
548
            dynamiccolumns[self.yname].append(y)
 
549
        return dynamiccolumns
 
550
 
 
551
 
 
552
class functionxy(function):
 
553
 
 
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)
 
556
 
 
557
 
 
558
class paramfunction(_data):
657
559
 
658
560
    defaultstyles = [style.line()]
659
561
 
660
 
    def __init__(self, varname, min, max, expression, title=notitle, points=100, parser=mathtree.parser(), context={}):
661
 
        if title is notitle:
 
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
663
567
        else:
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")
670
 
        l = len(keys)
671
 
        self.points = [None]*points
672
 
        self.columns = {}
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):
680
 
                try:
681
 
                    self.points[i][index] = mathtree.Calc(**context)
682
 
                except (ArithmeticError, ValueError):
683
 
                    self.points[i][index] = None
684
 
 
 
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()
 
583
 
 
584
 
 
585
class paramfunctionxy(paramfunction):
 
586
 
 
587
    def __init__(self, f, min, max, **kwargs):
 
588
        paramfunction.__init__(self, "t", min, max, "x, y = f(t)", context={"f": f}, **kwargs)