2

We have an array of points through which we would like to draw a quadratic curve on an HTML5 canvas, but we would like to draw the curve slowly, instead of all at once. Kind of like replaying the drawing of the curve.

Original curve: http://jsfiddle.net/NWBV4/12/

Curve replay: http://jsfiddle.net/NWBV4/15/

In the curve replay, if we change SEGMENT_PER_POINTS to something very large (e.g., 1000), it draws perfectly in one shot.

But as you can tell, with smaller numbers, there are gaps in the replay of the curve.

Any clues on how we can fix this? We're pretty stuck!

Crashalot
  • 33,605
  • 61
  • 269
  • 439
  • *"We also tried stroking every point, but that didn't work, either."* Why didn't it work? – ninjagecko Apr 02 '12 at 01:20
  • Just added a jsfiddle to show the current curve. Stroking every point distorted the curve, at least the way we did it. Any suggestions? – Crashalot Apr 02 '12 at 01:26
  • what does it normally look like? – ninjagecko Apr 02 '12 at 01:34
  • Here's the JSFiddle for it: http://jsfiddle.net/NWBV4/12/ – Crashalot Apr 02 '12 at 01:36
  • that's the same one you linked in the question; what is the curve supposed to look like? Also keep in mind that points are not magically interpolated; the first two arguments are the x-y coordinates of the control point of the spline. See http://stackoverflow.com/questions/7054272/how-to-draw-smooth-curve-through-n-points-using-javascript-html5-canvas – ninjagecko Apr 02 '12 at 02:01
  • Thanks for the SO post, that's the foundation of our algorithm. The curve is just an example. We would like to see how we could apply some "replay" functionality to an array of points just like the array in the JSFiddle. – Crashalot Apr 02 '12 at 02:04
  • Thanks for looking at this, BTW. – Crashalot Apr 02 '12 at 02:04
  • on a sidenote, I would suggest you to have a look at this canvas library : http://raphaeljs.com/ – DhruvPathak Apr 02 '12 at 05:21

1 Answers1

1

You should add a sleep primitive in your drawing code. However in javascript, rather than with sleep or wait primitives, this is accomplished in an event-directed manner with setInterval or setTimeout. As demonstrated:

var sec = 1000; // milliseconds

var totalDrawingTime = 5*sec;
var numPointsToDraw = [calculate this];
var waitTimePerPoint = totalDrawingTime/numPointsToDraw;

function slowlyDrawCurve(...) {
    var x = ...;

    function drawNextPointAndWait() {
        x += ...;
        if (x < MAX_CANVAS_SIZE) {
            y = f(x);
            point = [x,y];
            dispatch_to_canvas_function(point);
            setTimeout(helper, waitTimePerPoint);
        }
    }
    drawNextPointAndWait();
}

edit

Demonstration here: http://jsfiddle.net/eJVnU/4/

This was actually a little more interesting. Namely, if you are drawing at intervals on order of a few milliseconds (1000 points means 1 millisecond per update!), you need to be careful how you deal with javascript's timers. The javascript events scheduled with setTimeout may not trigger for a few milliseconds or more, making them unreliable! Therefore what I did was figure out when each segment should be completed by. If we were running ahead of schedule by more than a few milliseconds, we did a setTimeout, BUT, if we were otherwise running behind schedule, we directly performed a recursive call to the segment-drawing routine, shortcutting the event-handling system. This also ensures that the drawing is done smoothly to the human eye, as long as the segments are of roughly equal length.

(If you wanted it done even more smoothly, you could calculate the length of the segment drawn, keep the sum total of arclength drawn, and divide that by some fixed constant rate arclength_per_second to figure out how long things should have taken.)

ninjagecko
  • 88,546
  • 24
  • 137
  • 145
  • Wow, thanks for this! Our needs are even simpler. We only need to draw a fixed number of points then "sleep" for 50 ms or 100 ms then repeat until all points are drawn. We'll try to adapt your code, but hopefully it won't be too tough. – Crashalot Apr 02 '12 at 04:21
  • We just compared the output of the original curve to the "replayed" curve in your jsFiddle ... there are appear to be some minor differences. We're hoping to find something that is an exact replica, just drawn out over time. These differences show up for you as well, right? – Crashalot Apr 02 '12 at 04:23
  • Here's our attempt, but it fails so far: http://jsfiddle.net/NWBV4/15/ ... any clues why there are gaps? Of course, if we change SEGMENT_PER_POINTS to something very large (e.g., 1000), it draws perfectly in one shot. – Crashalot Apr 02 '12 at 05:09
  • @Crashalot [regarding "We just compared"]: yes, that is because I was playing around with other ways to calculate the control point. You can modify it to use your original way (just change where `controlPoint` is defined). Here's an example which calculates control points in a way which avoid jitter most of the time http://jsfiddle.net/eJVnU/5/ Ideally you would do something like a cubic spline interpolation of 4 points and calculate the control points from that (maybe use `bezierCurveTo`). – ninjagecko Apr 02 '12 at 05:44
  • Thanks, @ninjagecko! You're very kind to help out. Our problem seems actually different from what I originally articulated. It's more a question of how to close the gaps when we draw segments every 100 MS. Example http://jsfiddle.net/NWBV4/15/. How can we get the final output of this jsFiddle to match the original here: http://jsfiddle.net/NWBV4/12/? Any ideas? We're stumped. – Crashalot Apr 02 '12 at 08:07
  • @Crashalot: this seems nothing like what I proposed. You seem to be removing 10 elements from the `points` array at a time, and attempting to draw all the remaining points (see how `[].splice` works... it is not the same as `[].slice` in case you have them confused). Furthermore this method will draw a weird point at the end if the number of points is equal to `10*N+1` for some number N. I've already given you a method which works. =) – ninjagecko Apr 02 '12 at 16:36
  • Thanks, @ninjagecko. We're drawing 10 points at a time since splice returns the removed elements while also removing the same points from the array. Yes, your method works, but we need the replay not tied to a fixed time limit but rather X points drawn every 100 MS. Sorry for not specifying this in the original question. This new attempt is more like what we need. Any ideas? If not, thanks anyway for helping. – Crashalot Apr 02 '12 at 18:24
  • @Crashalot: Sorry, I'm not sure what bug you are running into in your new version, but you could just modify the code I gave to only call setTimeout every 100ms, by changing the parameter of the setTimeout function to 100 (also removing the `if`). – ninjagecko Apr 02 '12 at 18:32
  • OK, we'll try adapting your solution, but there are some elements we don't understand. Could we chat about this in SO chat if you don't mind? – Crashalot Apr 02 '12 at 18:41
  • @Crashalot: I will pass, sorry. However here is my solution with all fluff taken out; it does exactly what you want, and you can change the control points as you please: http://jsfiddle.net/ApPtY/2/ – ninjagecko Apr 02 '12 at 18:58