1

I am trying to animate a line two lines along a path, one then the other. Basically it will look like one line being drawn, stopping at a point, then another line being drawn somewhere else. So far I have come across promises and callbacks to achieve this, but being a javascript newbie this is confusing

Current animate function:

/*
*   Animation function draws a line between every point
*/              
var animate = function(p){
    return new Promise(function(resolve) {
        t = 1;
        var runAnimation = function(){
            if(t<p.length-1){
                context.beginPath();
                context.moveTo(p[t-1].x,p[t-1].y);
                context.lineTo(p[t].x,p[t].y);
                context.stroke();
                t++;
                requestAnimationFrame(function(){runAnimation()});
            } else {
                resolve()
            }
        };
        runAnimation();
    });
}

Current call to animate function:

animate(points).then(animate(secondary_points));

The points are similar to:

var points = [{x:100, y:200}];

And the paths the lines need to follow are just the multiple coordinates inside points and secondary_points

Ive tried many solutions on SO that were similar, but small differences cause me to either mess up or not understand the solution. The biggest issue I seem to have is calling the SAME animate function, with that animate function needing to be run on different parameters.

Without this solution, using

animate(points);
animate(secondary_points);

the lines are drawn somewhat at the same time, but the result is actually just randomly placed dots along the path instead of smooth lines, I assume because both are running at the same time.

How would I go about fixing this so that one line is drawn along path1 and then the second line is drawn along path2?

It is probably a simple solution, but Ive worked with JS for 3 days and my head is still spinning from getting used to some of the syntax of the old code Ive had to fix

Thank you

EDIT:

The full flow of the animation is as follows:

I have a php file that contains 2 canvases, each containing an image of a map. The php file has a couple <script/> tags, one of which calls the js script I am writing the animation on via drawPath(source,destination,true) or drawPath(source,destination,false)

The drawPath function uses the boolean to determine which canvas to get the context for, and then draw on the path from point A to point B via finding the path and creating the points mentioned above, then drawing using animate(). There are a couple breaks in the maps that require separate lines, which prompted my original question. I was able to fix that thanks to suggestions, but now I am having a larger issue.

If I need to go from point A on map A to point B on map B, ie

drawPath(source, end_point_of_map_A, true); is called then drawPath(start_point_of_map_B, destination, false);, the lines are drawn only on one map, and they are similar to before where they are 1. random and 2. incomplete/only dots

I am assuming this is due to the animation again, because it worked when just drawing the lines statically, and each animation works when going from point A to B on a single map

Any help is appreciated!

Edit:

DrawPath()

function drawPath(source, desti, flag) {
    /*
    * Define context
    */
    //lower
    if(!flag){
        var c = document.getElementById("myCanvas");
        context = c.getContext("2d");
    //upper
    } else {
        var cUpr = document.getElementById("myCanvasUpr");
        context = cUpr.getContext("2d");
    }
    /*
    * Clear the variables
    */
    points = [];
    secondary_points = [];
    vertices = [];
    secondary_vertices = [];
    t = 1;
    done = false;




    //check for invalid locations
    if (source != "" && desti != "") {
        context.lineCap = 'round';
        context.beginPath();

        /*
        * Get the coordinates from source and destination strings
        */
        var src = dict[source];
        var dst = dict[desti];

        /*
        * Get the point number of the point on the path that the source and destination connect to
        */

        var begin = point_num[source];
        var finish = point_num[desti];

        /*
        * Draw the green and red starting/ending circles (green is start, red is end)
        */
        context.beginPath();
        context.arc(src[0], src[1], 8, 0, 2 * Math.PI);
        context.fillStyle = 'green';
        context.fill();

        context.beginPath();
        context.arc(dst[0], dst[1], 6, 0, 2 * Math.PI);
        context.fillStyle = 'red';
        context.fill();

        /*
        * Call the function that draws the entire path
        */
        draw_segments(begin, finish, src, dst, flag);
        //window.alert(JSON.stringify(vertices, null, 4))
        /*
        * Edit what the line looks like
        */
        context.lineWidth = 5;
        context.strokeStyle = "#ff0000";
        context.stroke();

    }
}
ChrisM
  • 125
  • 14
  • I don't know if you stumbled upon [this question/answer](https://stackoverflow.com/questions/29904445/draw-continuous-lines-on-html-canvas-one-after-the-other) but it seems to answer yours. I applied the answer to this [jsFiddle](http://jsfiddle.net/0uhoyz72/16/) based on your question. – Wild Beard Jul 19 '18 at 21:33
  • 2
    This `animate(points).then(animate(secondary_points));`, probably needs to be: `animate(points).then(() => animate(secondary_points));`. You have to pass a function reference to `.then()` so it can be executed later. You were executing it immediately. – jfriend00 Jul 19 '18 at 21:35
  • @jfriend00 This solution worked thank you! But, now I discovered a new issue. I am updating my question to explain as it is similar – ChrisM Jul 19 '18 at 22:02
  • @jfriend00 Apologies, took a while to edit – ChrisM Jul 19 '18 at 22:11
  • First off, this edit seems like a new and different question than what you first asked about. Second off, I don't think anyone can help with that without see all the relevant code and without a more complete description of what is supposed to be happening and what you observe happening. Perhaps that belongs in another question. – jfriend00 Jul 19 '18 at 22:19
  • @jfriend00 I believe is boils down to the same. It works when I dont have any async code running, but doesnt when I do. Basically, I believe that the animate function is somehow getting called again before finishing and exiting back to the php file, or the second drawPath() is getting called while the first drawPath() is still executing. If you think its worth a new question, Ill create it though – ChrisM Jul 19 '18 at 22:24
  • Your talking about `drawPath()` and how it gets called, yet there is NO code included in your question that has anything to do with that. There's NOTHING we can do when we can't see the code you're talking about. Your code is probably wrong, but we can't even have a chance to fix it without seeing it. – jfriend00 Jul 19 '18 at 22:29
  • @jfriend00 well nothing has been changed in `drawPath()` or the php file. The only difference is I added animate within the js file and instead of manually doing `context.moveTo()` and `context.lineTo()` I called animate(). But, I will add `drawPath()` to clarify some of it – ChrisM Jul 19 '18 at 22:32
  • OK, I'll bow out then. I have no idea what you're asking. I see `drawPath()` contains references to a bunch of non-locally declared variables that looks like possibly a problem, but honestly I have no understanding of the problem you're describing or how that relates to the code you've shown. – jfriend00 Jul 19 '18 at 22:47
  • @jfriend00 I appreciate your help thus far. To clarify for other readers, the variables are global in the js file, and as mentioned everything worked before using animate(). The flow is php->drawPath()->draw_segments->animate->return. would it be helpful to also include the draw_segments? – ChrisM Jul 19 '18 at 22:54
  • So `drawSegments()` calls `animate()?` Since `animate()` is asynchronous, that means **you're asynchronously trying to use some global variables**. That is often a recipe for bugs as it means that multiple things can be trying to use the same set of global variables before they are finished. This probably needs to be entirely rewritten to avoid globals. Pass the data down to your functions as either arguments or as properties of an object (a great use of object oriented design) and make sure your asynchronous operations aren't relying on data that someone else might change. – jfriend00 Jul 19 '18 at 23:10
  • You're still hiding code from us. We have no way of seeing the whole flow from beginning to end (we have no code that actually calls `animate()`) so there's just no way for us to comprehend what's really happening. As such, all we can do is make guesses. And, that's a sign of an incomplete question here on stack overflow. You shouldn't be asking us to guess how to help you. – jfriend00 Jul 19 '18 at 23:11
  • I will create a followup question with more details then, thank you – ChrisM Jul 19 '18 at 23:22

1 Answers1

2

A nice way to handle this is to put your lines into a an array where each element is a set of points of the line. Then you can call reduce() on that triggering each promise in turn. reduce() takes a little getting used to if you're new to javascript, but it basically takes each element of the array c in this case, does something and that something becomes the next a. You start the whole thing off with a resolve promise which will be the initial a. The promise chain will be returned by reduce to you can tack on a final then to know when the whole thing is finished.

For example:

let canvas = document.getElementById('canvas')
let context = canvas.getContext('2d');
var animate = function(p){

    return new Promise(function(resolve) {
        t = 1;
        var runAnimation = function(){
            if(t<p.length-1){
                context.beginPath();
                context.moveTo(p[t-1].x,p[t-1].y);
                context.lineTo(p[t].x,p[t].y);
                context.stroke();
                t++;
                requestAnimationFrame(function(){runAnimation()});
            } else {
                resolve()
            }
        };
        runAnimation();
    });
}

// make some points:

let points = Array.from({length: 200}, (_,i) => ({x:i+1, y:i+2}))
let points2 = Array.from({length: 200}, (_,i) => ({x:300-i, y:i+2}))
let points3 = Array.from({length: 200}, (_,i) => ({x:i*2, y:100+100*Math.sin(i/10)}))

// create an array holding each set

let sets = [points, points2, points3]

// use reduce to call each in sequence returning the promise each time
sets.reduce((a, c) => a.then(() => animate(c)), Promise.resolve())
.then(() => console.log("done"))
<canvas id="canvas" height="300" width="500"></canvas>
Mark
  • 90,562
  • 7
  • 108
  • 148
  • So if I am understanding this right, get all points for each path, combine them into an array such as `all_lines = [path1, path2, path3..]` where `path1 = [{x:n , y:m}]` then use `reduce()` which essentially runs animate on paths 1-3? In your example, do you mind explaining in a little more detail about the line `sets.reduce((a, c) => a.then(() => animate(c)), Promise.resolve())`? Still hard to follow some of the syntax there – ChrisM Jul 19 '18 at 22:16
  • 1
    @ChrisM, you are understanding correctly. `reduce` is confusing at first. It takes a list (`sets`) and for each item calls a function with two args (called the `a` for accumulator & `c` for current here). The return value of that function becomes the `a` for the next call. For the first call we manually give it the initial `a` which is the `Promise.resolve()`. The first pass calls `Promise.resolve().then(() => animate(c))` where 'c' is the first element in the list. This produces a new promise, which becomes the next `a`. It essentially make s a big chain of `then().then()`, etc, – Mark Jul 19 '18 at 22:26
  • So while `a` becomes the return of the last call, `c` becomes the next element in the list? – ChrisM Jul 19 '18 at 22:30
  • 1
    @ChrisM yep. Probably easier to understand with an example that doesn't include promises. The classic use of reduce is summing an array of numbers. – Mark Jul 19 '18 at 22:33
  • in the summing example Im assuming `a` is the current sum and `c` is added to it until the end is reached and the final `a` is returned? – ChrisM Jul 19 '18 at 22:35
  • 1
    @ChrisM exactly. `[1, 2, 3].reduce((a, c) => a + c, 0)` – Mark Jul 19 '18 at 22:36
  • 1
    the => denotes an arrow function right? so the equivalent would be `function(a,c){return a+c}` with the second parameter passed to `reduce(...,0)` denoting that the initial `a = 0`? – ChrisM Jul 19 '18 at 22:39
  • 1
    @ChrisM yes, I think you have it. – Mark Jul 19 '18 at 22:40