Here's my solution, using _why's wonderful little shoes toolkit. Run like
shoes graph.rb "x*x; -5; 5; 0; 25"
The args are "fn; xmin; xmax; ymin; ymax"
I tried to make the code as straightforward as possible, to recapture
the feel of the old Basic days. A few inevitable complications, due to
shoes being a work in progress: the Grapher class is to work around
scoping quirks, and the range check is due to a bug that doesn't let
the exception handler catch an overflowing Bignum -> long conversion
in the canvas code (it would've been nice to simply draw to a point
off-canvas and let the drawing engine cope). Also shoes's argv
handling code puts the filename in ARGV[0] and doesn't like negative
numbers as plain command line args (they are treated as shoes args),
so a single quoted string was the simplest thing that worked.
#------------------------------------------------------------------------------------------------------
# ARGV[0] is the filename, since we launch via shoes graph.rb args
ARGV.shift
args = ARGV[0].split(";").map {|i| i.chomp}
F = args.shift
Xmin, Xmax, Ymin, Ymax = args.map {|i| i.to_f}
X = Y = 800
XScale = X * 1.0/(Xmax - Xmin)
YScale = Y * 1.0/(Ymax - Ymin)
class Grapher
def at(x,y)
[((x -Xmin)* XScale).to_i, Y - ((y - Ymin) * YScale).to_i] rescue nil
end
def bounded?(x,y)
x && y && 0 <= x && x <= X && 0 <= y && y <= Y
end
def fn(x)
y = eval(F)
end
end
Shoes.app :height => Y, :width => X do
g = Grapher.new
background rgb(255, 255, 255)
fill white
stroke black
strokewidth 1
u, v = nil
Xmin.step(Xmax, (Xmax - Xmin)/(X*1.0)) {|i|
begin
x0, y0 = g.at(u,v)
u, v = i, g.fn(i)
x, y = g.at(u,v)
if g.bounded?(x,y) and g.bounded?(x0,y0)
line(x0, y0, x, y)
end
rescue
end
}
end