Graphics Equations
I'm making a grapher which graphs equations (currently) by starting as some x, and going by some increment, figuring out the y and drawing a line to the new point. Now in the interest of speed I need to use as few control points (x increments) as possible. How could I intuitivley use more control points where needed?
I'm thinking something with checking the derivative of the function and if its high, lower the increment, but I can't quite get a good algorithm out of it.
A complete answer to that would get you a Ph.D. For decent heuristics, take a look at the literature on numerical integration, since that also requires a higher sampling density in less smooth parts of the function.
I may be a little unclear exactly what you want to do (in my experience using a simple bresenham algorithm where you sample every x should be extremely fast). But how about just calculating the change in y between your two points that you want to draw the line (which is approximately the derivative? - its been a while since my last math class ). Depending upon its size, select more points in between.
sorry, what I meant in my last post was calculate the change in y over change in x between your two points (slope - approx derivative)...
http://forum.java.sun.com/thread.jspa?forumID=20&threadID=639762Reply 4 from DrLaszloJamf contains this link to a sample algorithm: http://forum.java.sun.com/thread.jspa?forumID=20&threadID=516322
I know very little about this field but a simple way would be as
follows.
The idea is based on using polynomial splines of degree 2.
Let f(x) be your function that should be plotted between x0 and x1,
and let d be the maximum and initial distance between your x points.
1. Compute y(i) = f(x(i)) for x(i) = x0+d*i where i = 0,..,(x1-x0)/d
and save these values in the lists X and Y.
2. Compute the the second derivative for the approximating spline which is something like
dd(i) = -2*(x(i)*(y(i+1)-y(i+2))-x(i+1)*(y(i)-y(i+2))+x(i+2)*(y(i)-y(i+1)))/((x(i)-x(i+1))*(x(i)-x(i+2))*(x(i+1)-x(i+2)))
and save it in the list DD.
3. For every dd(i) in DD where |dd(i)| is above a chosen constant,
add a point x = (x(i)+x(i+1))/2 and update the lists X, Y and DD.
4. Resume with 3 until no more points are added.
When computing dd(i) you can reuse a lot of the computations in order
to make it faster.
parza at 2007-7-16 21:27:27 >

Correction.
3. For every i such that |(x(i)-x(i+1))*dd(i)|>K*(x1-x0), add a
point x = (x(i)+x(i+1))/2 and update the lists X, Y and DD.
K is just a chosen constant.
The second derivative can be seen as a measurement on how hard
the function is curving, but this only make since if we compare
it to how big part of our domain we are looking at. That is the
justification for this adjustment.
parza at 2007-7-16 21:27:27 >

Thanks for response! So after some thought I decided to go with the quad curve idea, and it worked great. Check it out at http://people.cornell.edu/pages/mm473/ . I have blue dots representing points on the equation, and red dots representing quad control points. It gets kinda squirrely when things get cramped, but I'm looking into how to solve that.
When you write quad curve idea, do you mean quad curves foundin Java's 2D Graphics?Using this will only smoothen the curve, not provide extra resolution where needed.
parza at 2007-7-16 21:27:27 >

> java.lang.UnsupportedClassVersionError: msketch2/AppletMain> (Unsupported major.minor version 49.0)Error occurs when trying to visit your site.
What I mean by quad curves is I followed the link above and used that method. It works by going in even increments across the screen. For each pair of increment points, its creates a "quadTo" curve with the quadratic control point at the intersection of the tangent lines of the two points. There is also a limit of too small a change in derivative between two increment points, in which case it does a simple "lineTo." This is useful too because in my program I apply alot of AffineTransforms to the resulting path, and since quad control points are transformed as well, the equation retains its shape very nicely. The code boils down to:
for (double t1=safeBounds.getY(), t2=t1, dx1=dx(t1), dx2, x1=x(t1), x2;
t2<(safeBounds.getY()+safeBounds.getHeight());
t1=t2, dx1=dx2, x1=x2){
t2=t1+(safeBounds.getHeight()/STEPS);
x2=x(t2);
dx2=dx(t2);
double desc = dx1-dx2;
if ((Math.abs(desc)<.2f) || (Math.abs(desc)>3)){
path.lineTo(x2,t2);
}
else{
double ctrlx = (x2*dx1 - x1*dx2 + dx1*dx2*t1 - dx1*dx2*t2)/desc;
double ctrly = (x2 - x1 + dx1*t1 - dx2*t2)/desc;
path.quadTo(ctrlx, ctrly, x2, t2);
}
}
The interesting problem is that if function is very zoomed out, increment points might fall on unrelated parts of the funciton. Like imagine the function Sin(x) and having one point at 0,0 and another at the bottom of the next trough. Their tangent lines intersect somewhere far into the thrid quadrant, and the quadratic control points makes the function go in a huge loop in that direction. Basically by playing around with different values of min/max change in derivative, I got it to be acceptable, but if anyone has a better workaround, let me know.
As for the applet not working, I have no idea except maybe download latest Java, but I've never really made an applet before so I dunno. Works for me.
> java.lang.UnsupportedClassVersionError: msketch2/AppletMain> (Unsupported major.minor version 49.0)I think that's the class version number for 1.4, so check your plugin.
> The interesting problem is that if function is very zoomed out...
Is this not due to lack of resolution. If you would have more points
where the cure is bending hard, this would not happen.
Another function where your approach will run into problems is
f(x) = e^(-k*x^2) because when we increase the constant k the spice
will not be well represented when it becomes narrow compared to to
the even increment. If you use following suggestion I think it will
solve it.
Here is a improved suggestion of my previous reply 5,6. It adjusts
the increment dependent on how hard the previous step curves.
Let f(x) be your function that should be plotted between x0 and x1,
and let d initial increment.
1. Let x(o)=x0, x(1)=x0+d and x(2)=x0+2*d and compute y(i)=f(x(i))
for i=0,1,2. Let i=2.
2. Compute
dd(i) = -2*(x(i-2)*(y(i-1)-y(i))-x(i-1)*(y(i-2)-y(i))+x(i)*(y(i)-y(i-1)))/((x(i-2)-x(i-1))*(x(i-2)-x(i))*(x(i-1)-x(i)))
3. If |(x(i-1)-x(i))*dd(i)|>K1*(x1-x0) then d = d/2.
4. If |(x(i-1)-x(i))*dd(i)|<K2*(x1-x0) then d = d*2.
5. Let i = i+1
6. Let x(i) = x(i-1)+d and compute y(i) = f(x(i)). If x(i)><x1
resume at 2, else we are done.
The initial increment should be really short to catch any heavy
bending. If f(x) does not bend heavy, the increment will be adjusted
really fast since d=d/2 and d=d*2.>
parza at 2007-7-16 21:27:27 >

After computing the points quads can be used to make the curvemore Smooth.
parza at 2007-7-16 21:27:27 >

This topic is really fascinating to me for some reason....
I'm currenty satisfied with what I got but, some things for the method you described, parz, nonetheless. The method of comparing to dd to the size of the viewing area is very useful, I actually will use that. Also, I know my function dd directly so I don't have to calculate it. As for incrementing "d", one thing I tried was just having d be inversely related to dd(x). Basically my for loop looked like for(x=xmin;x<xmax; x+=Constant / dd(x)); It worked alright except for when dd(x) got very small it would just set a huge increment. Actually now that I think of if I did x+=Min(Constant/dd(x),maxXIncrement), that might come out nicely. Then the constant would just depent on the screen size... hmm...>
Alright I think I got it. There is now a min/max increment in pixels (5 and 15), and in between the increment varies with .2 / dd(x). It adds a quad point when the dd(x) > .2. Curves look great even under big transformations. Its updated on the website if anyone wants to check it out.
I look at your link and looks good. Letting the increment depend
on the inverse of dd(x) seams to be a good idea, but I think it
needs some adjustments. I found this interesting too, so I looked
into it some more and following suggests that increment depends
on the square rooted inverse.
Let x be the current location, d be the next increment, and e(x,d)
be the maximum error between the true curve and the line y going
through points [x, f(x)] and where f and y are parallel in point x.
That is e(x,d) = max_{w=[0,d]} |y(x+w) - f(x+w)|
= max_{w=[0,d]} |f'(x)*w + f(x) - f(x+w)|
where f'(x) is the first derivative of f(x). If we can find the
largest d>0 such that e(x,d) <= C, we can draw the line y between
[x, x+d] and know that the error is at most C. Now, we use Taylor
series ( http://mathworld.wolfram.com/TaylorSeries.html ) to
expand f(x+w) = f(x) + f'(x)*w + f''(x)*w^2/2! + ... Then we get
e(x,d) = max_{w=[0,d]} |f''(x)*w^2/2! + f'''(x)*w^3/3! + ...|
=< max_{w=[0,d]} |f''(x)|*w^2/2! + |f'''(x)|*w^3/3! + ...
=< |f''(x)|*d^2/2! + |f'''(x)|*d^3/3! + ...
We know that for continues function f^n(x)*d^n/n! --> 0
when n --> inf and for most functions this happens really fast.
Let us assume that we only look at functions where the first
term is significant. Then we know that
e(x,d) <= |f''(x)|*d^2/2 <= C==>
d <= sqrt(2*C/|f''(x)|)
where C = [max function error]*[error in pixels]/[window hight in pixels]
How do you know the second derivative of any incoming function?
parza at 2007-7-20 19:25:42 >

Sorry, C should be C = [y-axis domain length]*[error in pixels]/[y-axis in pixels]
parza at 2007-7-20 19:25:42 >

I couldn't follow your math to well, (what is the max(0,d) mean? ) but I tried having the increment vary with the inverse root and the choice of increments seems a little better so I'm gonna stick with it. The problem with this method is the same as any of the others so far, though. If I choose a control point that happens to fall where the second derivative is close to 0, then the next increment will be disproportionately large and it will miss part of the curve. I can pretty much get around this with max/min increment but thats a workaround not a solution.
> what is the max(0,d) mean?
max_{w=[0,d]) f(w) is the maximum value f(x) where x can be any
value between 0 and d. More strict it is
There exists an x=[0,d] such that f(x) = max_{w=[0,d]) f(w) and
and for all w=[0,d], f(w) <= f(x).
> I can pretty much get around this with max/min increment but thats
> a workaround not a solution.
In theory you could use more terms from the Taylor series, but then
you would have to solve a polynomial and have access to more
derivatives.
[code]
f''(x)*d^2/2! + f'''(x)*d^3/3! - C = 0
[code]
But in some cases this will also give you infinite d's, so the only real
solution I can see is the one you are using.
parza at 2007-7-20 19:25:42 >

Reposting it in better format.
> what is the max(0,d) mean?
max_{w=[0,d]) f(w) is the maximum value f(x) where x can be any
value between 0 and d. More strict it is
There exists an x=[0,d] such that f(x) = max_{w=[0,d]) f(w) and
and for all w=[0,d], f(w) <= f(x).
> I can pretty much get around this with max/min increment but thats
> a workaround not a solution.
In theory you could use more terms from the Taylor series, but then
you would have to solve a polynomial and have access to more
derivatives.
f''(x)*d^2/2! + f'''(x)*d^3/3! - C = 0
parza at 2007-7-20 19:25:42 >
