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

« back to all changes in this revision

Viewing changes to pyx/graph/axis/rater.py

  • Committer: Bazaar Package Importer
  • Author(s): Graham Wilson
  • Date: 2004-12-25 06:42:57 UTC
  • Revision ID: james.westby@ubuntu.com-20041225064257-31469ij5uysqq302
Tags: upstream-0.7.1
Import upstream version 0.7.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
# -*- coding: ISO-8859-1 -*-
 
3
#
 
4
#
 
5
# Copyright (C) 2002-2004 J�rg Lehmann <joergl@users.sourceforge.net>
 
6
# Copyright (C) 2003-2004 Michael Schindler <m-schindler@users.sourceforge.net>
 
7
# Copyright (C) 2002-2004 Andr� Wobst <wobsta@users.sourceforge.net>
 
8
#
 
9
# This file is part of PyX (http://pyx.sourceforge.net/).
 
10
#
 
11
# PyX is free software; you can redistribute it and/or modify
 
12
# it under the terms of the GNU General Public License as published by
 
13
# the Free Software Foundation; either version 2 of the License, or
 
14
# (at your option) any later version.
 
15
#
 
16
# PyX is distributed in the hope that it will be useful,
 
17
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
18
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
19
# GNU General Public License for more details.
 
20
#
 
21
# You should have received a copy of the GNU General Public License
 
22
# 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
from pyx import unit, box
 
27
from pyx.graph.axis import tick
 
28
 
 
29
 
 
30
# rater
 
31
# conseptional remarks:
 
32
# - raters are used to calculate a rating for a realization of something
 
33
# - a rating means a positive floating point value
 
34
# - ratings are used to order those realizations by their suitability
 
35
#   (small ratings are better)
 
36
# - a rating of None means not suitable at all (those realizations should be
 
37
#   thrown out)
 
38
 
 
39
 
 
40
class cube:
 
41
    """a value rater
 
42
    - a cube rater has an optimal value, where the rate becomes zero
 
43
    - for a left (below the optimum) and a right value (above the optimum),
 
44
      the rating is value is set to 1 (modified by an overall weight factor
 
45
      for the rating)
 
46
    - the analytic form of the rating is cubic for both, the left and
 
47
      the right side of the rater, independently"""
 
48
 
 
49
    # __implements__ = sole implementation
 
50
 
 
51
    def __init__(self, opt, left=None, right=None, weight=1):
 
52
        """initializes the rater
 
53
        - by default, left is set to zero, right is set to 3*opt
 
54
        - left should be smaller than opt, right should be bigger than opt
 
55
        - weight should be positive and is a factor multiplicated to the rates"""
 
56
        if left is None:
 
57
            left = 0
 
58
        if right is None:
 
59
            right = 3*opt
 
60
        self.opt = opt
 
61
        self.left = left
 
62
        self.right = right
 
63
        self.weight = weight
 
64
 
 
65
    def rate(self, value, density):
 
66
        """returns a rating for a value
 
67
        - the density lineary rescales the rater (the optimum etc.),
 
68
          e.g. a value bigger than one increases the optimum (when it is
 
69
          positive) and a value lower than one decreases the optimum (when
 
70
          it is positive); the density itself should be positive"""
 
71
        opt = self.opt * density
 
72
        if value < opt:
 
73
            other = self.left * density
 
74
        elif value > opt:
 
75
            other = self.right * density
 
76
        else:
 
77
            return 0
 
78
        factor = (value - opt) / float(other - opt)
 
79
        return self.weight * (factor ** 3)
 
80
 
 
81
 
 
82
class distance:
 
83
    # TODO: update docstring
 
84
    """a distance rater (rates a list of distances)
 
85
    - the distance rater rates a list of distances by rating each independently
 
86
      and returning the average rate
 
87
    - there is an optimal value, where the rate becomes zero
 
88
    - the analytic form is linary for values above the optimal value
 
89
      (twice the optimal value has the rating one, three times the optimal
 
90
      value has the rating two, etc.)
 
91
    - the analytic form is reciprocal subtracting one for values below the
 
92
      optimal value (halve the optimal value has the rating one, one third of
 
93
      the optimal value has the rating two, etc.)"""
 
94
 
 
95
    # __implements__ = sole implementation
 
96
 
 
97
    def __init__(self, opt, weight=0.1):
 
98
        """inititializes the rater
 
99
        - opt is the optimal length (a visual PyX length)
 
100
        - weight should be positive and is a factor multiplicated to the rates"""
 
101
        self.opt = opt
 
102
        self.weight = weight
 
103
 
 
104
    def rate(self, distances, density):
 
105
        """rate distances
 
106
        - the distances are a list of positive floats in PostScript points
 
107
        - the density lineary rescales the rater (the optimum etc.),
 
108
          e.g. a value bigger than one increases the optimum (when it is
 
109
          positive) and a value lower than one decreases the optimum (when
 
110
          it is positive); the density itself should be positive"""
 
111
        if len(distances):
 
112
            opt = unit.topt(self.opt) / density
 
113
            rate = 0
 
114
            for distance in distances:
 
115
                if distance < opt:
 
116
                    rate += self.weight * (opt / distance - 1)
 
117
                else:
 
118
                    rate += self.weight * (distance / opt - 1)
 
119
            return rate / float(len(distances))
 
120
 
 
121
 
 
122
class rater:
 
123
    """a rater for ticks
 
124
    - the rating of axes is splited into two separate parts:
 
125
      - rating of the ticks in terms of the number of ticks, subticks,
 
126
        labels, etc.
 
127
      - rating of the label distances
 
128
    - in the end, a rate for ticks is the sum of these rates
 
129
    - it is useful to first just rate the number of ticks etc.
 
130
      and selecting those partitions, where this fits well -> as soon
 
131
      as an complete rate (the sum of both parts from the list above)
 
132
      of a first ticks is below a rate of just the number of ticks,
 
133
      subticks labels etc. of other ticks, those other ticks will never
 
134
      be better than the first one -> we gain speed by minimizing the
 
135
      number of ticks, where label distances have to be taken into account)
 
136
    - both parts of the rating are shifted into instances of raters
 
137
      defined above --- right now, there is not yet a strict interface
 
138
      for this delegation (should be done as soon as it is needed)"""
 
139
 
 
140
    # __implements__ = sole implementation
 
141
 
 
142
    def __init__(self, ticks, labels, range, distance):
 
143
        """initializes the axis rater
 
144
        - ticks and labels are lists of instances of a value rater
 
145
        - the first entry in ticks rate the number of ticks, the
 
146
          second the number of subticks, etc.; when there are no
 
147
          ticks of a level or there is not rater for a level, the
 
148
          level is just ignored
 
149
        - labels is analogous, but for labels
 
150
        - within the rating, all ticks with a higher level are
 
151
          considered as ticks for a given level
 
152
        - range is a value rater instance, which rates the covering
 
153
          of an axis range by the ticks (as a relative value of the
 
154
          tick range vs. the axis range), ticks might cover less or
 
155
          more than the axis range (for the standard automatic axis
 
156
          partition schemes an extention of the axis range is normal
 
157
          and should get some penalty)
 
158
        - distance is an distance rater instance"""
 
159
        self.ticks = ticks
 
160
        self.labels = labels
 
161
        self.range = range
 
162
        self.distance = distance
 
163
 
 
164
    def rateticks(self, axis, ticks, density):
 
165
        """rates ticks by the number of ticks, subticks, labels etc.
 
166
        - takes into account the number of ticks, subticks, labels
 
167
          etc. and the coverage of the axis range by the ticks
 
168
        - when there are no ticks of a level or there was not rater
 
169
          given in the constructor for a level, the level is just
 
170
          ignored
 
171
        - the method returns the sum of the rating results divided
 
172
          by the sum of the weights of the raters
 
173
        - within the rating, all ticks with a higher level are
 
174
          considered as ticks for a given level"""
 
175
        maxticklevel, maxlabellevel = tick.maxlevels(ticks)
 
176
        numticks = [0]*maxticklevel
 
177
        numlabels = [0]*maxlabellevel
 
178
        for t in ticks:
 
179
            if t.ticklevel is not None:
 
180
                for level in range(t.ticklevel, maxticklevel):
 
181
                    numticks[level] += 1
 
182
            if t.labellevel is not None:
 
183
                for level in range(t.labellevel, maxlabellevel):
 
184
                    numlabels[level] += 1
 
185
        rate = 0
 
186
        weight = 0
 
187
        for numtick, rater in zip(numticks, self.ticks):
 
188
            rate += rater.rate(numtick, density)
 
189
            weight += rater.weight
 
190
        for numlabel, rater in zip(numlabels, self.labels):
 
191
            rate += rater.rate(numlabel, density)
 
192
            weight += rater.weight
 
193
        return rate/weight
 
194
 
 
195
    def raterange(self, tickrange, datarange):
 
196
        """rate the range covered by the ticks compared to the range
 
197
        of the data
 
198
        - tickrange and datarange are the ranges covered by the ticks
 
199
          and the data in graph coordinates
 
200
        - usually, the datarange is 1 (ticks are calculated for a
 
201
          given datarange)
 
202
        - the ticks might cover less or more than the data range (for
 
203
          the standard automatic axis partition schemes an extention
 
204
          of the axis range is normal and should get some penalty)"""
 
205
        return self.range.rate(tickrange, datarange)
 
206
 
 
207
    def ratelayout(self, axiscanvas, density):
 
208
        """rate distances of the labels in an axis canvas
 
209
        - the distances should be collected as box distances of
 
210
          subsequent labels
 
211
        - the axiscanvas provides a labels attribute for easy
 
212
          access to the labels whose distances have to be taken
 
213
          into account
 
214
        - the density is used within the distancerate instance"""
 
215
        if len(axiscanvas.labels) > 1:
 
216
            try:
 
217
                distances = [axiscanvas.labels[i].boxdistance_pt(axiscanvas.labels[i+1])
 
218
                             for i in range(len(axiscanvas.labels) - 1)]
 
219
            except box.BoxCrossError:
 
220
                return None
 
221
            return self.distance.rate(distances, density)
 
222
        else:
 
223
            return None
 
224
 
 
225
 
 
226
class linear(rater):
 
227
    """a rater with predefined constructor arguments suitable for a linear axis"""
 
228
 
 
229
    def __init__(self, ticks=[cube(4), cube(10, weight=0.5)],
 
230
                       labels=[cube(4)],
 
231
                       range=cube(1, weight=2),
 
232
                       distance=distance(1*unit.v_cm)):
 
233
        rater.__init__(self, ticks, labels, range, distance)
 
234
 
 
235
lin = linear
 
236
 
 
237
 
 
238
class logarithmic(rater):
 
239
    """a rater with predefined constructor arguments suitable for a logarithmic axis"""
 
240
 
 
241
    def __init__(self, ticks=[cube(5, right=20), cube(20, right=100, weight=0.5)],
 
242
                       labels=[cube(5, right=20), cube(5, right=20, weight=0.5)],
 
243
                       range=cube(1, weight=2),
 
244
                       distance=distance(1*unit.v_cm)):
 
245
        rater.__init__(self, ticks, labels, range, distance)
 
246
 
 
247
log = logarithmic