Hi list,
I grew frustrated with the coarseness of control of graph.data.function. You
can specify the number of points, but it's quite suboptimal if the gradient of
your function varies significantly, especially if that variation in gradient is
only confined to a small region.
So, instead of finishing my thesis like I *really* ought to be doing, I wrote
the attached extension, the adaptivefunction class. This works just like
graph.data.function, except that after generating all the linearly separated
points, it will do a second pass and adaptively add in-between points. It
chooses to do this based on how much the angle differs between two adjacent
lines (three adjacent points). If the angle differs by more than the angle
epsilon (a new constructor parameter) then it will split the two lines into
four lines by adding two new points.
What this means is that adaptivefunction uses points more efficiently than
function does for the same number of points, and it should result in better
looking function plots with smaller file sizes.
adaptivefunction tries to take into account the scale and shape of the
rendered plot when determining the angles; it adapts to optimise the visual
result. One caveat with this is that I'm not sure how to handle the case when
the y axis range is not specified or cannot be inferred from other data. (Are
the min and max values of the axis class None in that case? That seems to be
the case.)
I've played around with it a bit. It works fine on both linear and log plots (I
should know, as it took me several hours to figure out how to work around
precision issues with the log plots).
It's fun giving it 3 starting points and a small epsilon, and seeing it
produce a lovely smoothed plot.
Thought I'd share. Enjoy. :-)
Peace,
Brendon
# -*- coding: utf-8 -*-
# Brendon L. Higgins 25-08-2010
# Based on the function class from data.py in PyX (http://pyx.sourceforge.net)
# GNU GPL 2
# adaptivefunction samples the given number of points of the given function.
# It then adaptively samples extra points to smooth out rapid changes.
#
# adaptivefunction is used very similarly to function.
# New constructor parameter: epsilon
# epsilon is the maximum angular deviation allowed between two adjacent lines
# before a pair of intermediate points will be calculated
# E.g., epsilon=2.0 will allow lines to differ by up to 2 degrees
# Smaller numbers mean greater precision
from pyx.graph.data import function
from pyx.graph.data import _notitle
from pyx.graph.data import _mathglobals
import math
class adaptivefunction(function):
def __init__(self, expression, title=_notitle, min=None, max=None,
points=50, epsilon=2.0, context={}):
function.__init__(self, expression, title, min, max, points, context)
self.epsilon = epsilon
def dynamiccolumns(self, graph):
dynamiccolumns = {self.xname: [], self.yname: []}
xaxis = graph.axes[self.xname]
yaxis = graph.axes[self.yname]
from pyx.graph.axis import logarithmic
logxaxis = isinstance(xaxis.axis, logarithmic)
logyaxis = isinstance(yaxis.axis, logarithmic)
if self.min is not None:
min = self.min
else:
min = xaxis.data.min
if self.max is not None:
max = self.max
else:
max = xaxis.data.max
miny = yaxis.data.min
maxy = yaxis.data.max
if logxaxis:
min = math.log(min)
max = math.log(max)
if logyaxis:
miny = math.log(miny)
maxy = math.log(maxy)
for i in range(self.numberofpoints):
x = min + (max-min)*i / (self.numberofpoints-1.0)
if logxaxis:
x = math.exp(x)
dynamiccolumns[self.xname].append(x)
self.context[self.xname] = x
try:
y = eval(self.expression, _mathglobals, self.context)
except (ArithmeticError, ValueError):
y = None
dynamiccolumns[self.yname].append(y)
if len(dynamiccolumns[self.xname]) < 3:
return dynamiccolumns
x0 = y0 = x1 = y1 = x2 = y2 = None
e = self.epsilon/180.0*math.pi
r = float((max-min)/graph.width)/float((maxy-miny)/graph.height)
def nearenough():
if not logxaxis:
(dx0, dx1) = (x1-x0, x2-x1)
else:
(dx0, dx1) = (math.log(x1/x0), math.log(x2/x1))
if not logyaxis:
(dy0, dy1) = (y1-y0, y2-y1)
else:
(dy0, dy1) = (math.log(y1/y0), math.log(y2/y1))
t0 = math.atan(r*dy0/dx0)
t1 = math.atan(r*dy1/dx1)
return abs(t0 - t1) <= e
i = -1
while True:
while y0 is None or y1 is None or y2 is None or nearenough():
i += 1
if i == len(dynamiccolumns[self.yname]):
break
(x0, y0) = (x1, y1)
(x1, y1) = (x2, y2)
x2 = dynamiccolumns[self.xname][i]
y2 = dynamiccolumns[self.yname][i]
if i == len(dynamiccolumns[self.yname]):
break
if not logxaxis:
(nx1, nx2) = ((x0+x1)/2.0, (x1+x2)/2.0)
else:
(nx1, nx2) = (math.sqrt(x0*x1), math.sqrt(x1*x2))
dynamiccolumns[self.xname].insert(i, nx2)
self.context[self.xname] = nx2
try:
ny2 = eval(self.expression, _mathglobals, self.context)
except (ArithmeticError, ValueError):
ny2 = None
dynamiccolumns[self.yname].insert(i, ny2)
dynamiccolumns[self.xname].insert(i-1, nx1)
self.context[self.xname] = nx1
try:
ny1 = eval(self.expression, _mathglobals, self.context)
except (ArithmeticError, ValueError):
ny1 = None
dynamiccolumns[self.yname].insert(i-1, ny1)
(x2, y2) = (x1, y1)
(x1, y1) = (nx1, ny1)
return dynamiccolumns
------------------------------------------------------------------------------
Sell apps to millions through the Intel(R) Atom(Tm) Developer Program
Be part of this innovative community and reach millions of netbook users
worldwide. Take advantage of special opportunities to increase revenue and
speed time-to-market. Join now, and jumpstart your future.
http://p.sf.net/sfu/intel-atom-d2d
_______________________________________________
PyX-user mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/pyx-user