Search code examples
javascriptjqueryfor-loopcanvasdelay

Delay lineTo in for loop


I'm having a div (#option1) in the shape of a dot animating a graph using an array, Option1[], in which all the heights of the several 'dots' are included. I'm using a for loop to create space between two dots (left: '+=5px') and get the height for the next dot from the array, like so:

for (var i = 0; i < Option1.length; ++i) {
  $('#option1').animate({left: '+=5px',top: Option1[i]}, 200);
}

As you can see, the div takes 200ms to move to it's next coordinates. This all works fine, but it is just a moving dot. I actually want to make it look like the dot is drawing a line, so I tried to use a canvas and lineTo methods, like so:

var xaxis=5;
var a = document.getElementById("myCanvas1");
var atx = a.getContext("2d");
atx.beginPath();
atx.moveTo(5, 900);

for (var i = 0; i < Option1.length; ++i) {
  xaxis += 5;

  $('#option1').animate({left: '+=5px',top: Option1[i]}, 200);

  atx.lineTo(xaxis, Option1[i]);
  atx.stroke();
}

As you can see the line starts at coordinates 5,900, then moves 5px to the right and to the height of the next value of the Option1[] array. The line comes out correctly. The problem is that the total line shows instantaneously, and then the dot starts moving over the already drawn line. To make it so that a line-section only appears after the dot has past the new coordinates, I've tried the setTimeout function, setting the timeout at 200ms, just like the animate time:

$(document).ready(function() {
  var Option1 = [100, 150, 150, 130, 50, 100, 75, 125, 50, 100];

  var xaxis = 5;
  var a = document.getElementById("myCanvas1");
  var atx = a.getContext("2d");
  atx.beginPath();
  atx.moveTo(5, 400);
  atx.strokeStyle="green";

  for (var i = 0; i < Option1.length; ++i) {
    xaxis += 5;

    $('#option1').animate({
      left: '+=5px',
      top: Option1[i]
    }, 400);

    setTimeout(drawLines(), 400);

    function drawLines() {
      atx.lineTo(xaxis, Option1[i]);
      atx.stroke();
    };
  };
});
html,
body {
  width: 100%;
  height: 100%;
  background-color: black;
}

#maindiv {
  position: absolute;
  top: 5px;
  left: 5px;
  z-index: 5;
  cursor: pointer;
  Width: 500px;
  Height: 400px;
  background-color: black;
  border-radius: 1%;
  border-width: 0px;
  border-color: blue;
  border-style: solid;
  font-family: Verdana, Arial, Sans-Serif;
  color: white;
  box-shadow: 0 0 20px white;
}

canvas {
  position: absolute;
  z-index: 5;
  cursor: pointer;
  Width: 100%;
  Height: 100%;
  background-color: none;
  border-radius: 1%;
  border-width: 1px;
  border-color: blue;
  border-style: solid;
  font-family: Verdana, Arial, Sans-Serif;
  color: white;
  box-shadow: 0 0 20px white;
}

#option1 {
  position: absolute;
  top: 390px;
  left: 5px;
  z-index: 10;
  Width: 5px;
  Height: 5px;
  background-color: green;
  border-radius: 50%;
  border-width: 0px;
  border-color: blue;
  border-style: solid;
  font-family: Verdana, Arial, Sans-Serif;
  color: white;
  box-shadow: 0 0 20px white;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<html>
  <body>
    <div id="maindiv">
      <canvas id="myCanvas1" width="500" height="400"></canvas>
      <div id="option1"></div>
    </div>
  </body>
</html>

(This last hidden snippet is complete with html and css btw, so it's working). No success. The total line is there instantaneously. I've also tried to write the setTimeout in other ways using other posts on stackoverflow, but always the total line shows instantaneously.

Any help to get my line to be drawn live, would be much appreciated! Also solutions that use entirely different methods/functions are most welcome. Thanks!


Solution

  • Your first issue is that you are calling your drawLines function immediately and then passing the result of that function (undefined) to setTimeout.

    More on that here

    Once you fix that, you will find you have an issue of having a closure in a loop. There are a number of ways to fix that, though I have chosen to create a new function and pass the needed variables in to it and then have the timeout created inside that function.

    More info on closure inside a loop

    With both of those issues fixed, you end up with this:

    New function:

    function drawLine(atx, xaxis, y, delay){
        setTimeout(function(){
            atx.lineTo(xaxis, y);
            atx.stroke();
        }, delay);
    }
    

    Called as such in place of your existing setTimeout:

    drawLine(atx, xaxis, Option1[i], 400 * i);  
    

    You'll notice I have 400 * i for the timeout delay instead of just 400 as you had. If 400 is used, they will all draw at once after 400ms.

    Here is a working snippet:

    $(document).ready(function() {
      var Option1 = [100, 150, 150, 130, 50, 100, 75, 125, 50, 100];
    
      var xaxis = 5;
      var a = document.getElementById("myCanvas1");
      var atx = a.getContext("2d");
      atx.beginPath();
      atx.moveTo(5, 400);
      atx.strokeStyle="green";
    
      for (var i = 0; i < Option1.length; ++i) {
        xaxis += 5;
    
        $('#option1').animate({
          left: '+=5px',
          top: Option1[i]
        }, 400);
    
        drawLine(atx, xaxis, Option1[i], 400 * i);  
      };
    });
    
    function drawLine(atx, xaxis, y, delay){
        setTimeout(function(){
            atx.lineTo(xaxis, y);
            atx.stroke();
        }, delay);
    }
    html,
    body {
      width: 100%;
      height: 100%;
      background-color: black;
    }
    
    #maindiv {
      position: absolute;
      top: 5px;
      left: 5px;
      z-index: 5;
      cursor: pointer;
      Width: 500px;
      Height: 400px;
      background-color: black;
      border-radius: 1%;
      border-width: 0px;
      border-color: blue;
      border-style: solid;
      font-family: Verdana, Arial, Sans-Serif;
      color: white;
      box-shadow: 0 0 20px white;
    }
    
    canvas {
      position: absolute;
      z-index: 5;
      cursor: pointer;
      Width: 100%;
      Height: 100%;
      background-color: none;
      border-radius: 1%;
      border-width: 1px;
      border-color: blue;
      border-style: solid;
      font-family: Verdana, Arial, Sans-Serif;
      color: white;
      box-shadow: 0 0 20px white;
    }
    
    #option1 {
      position: absolute;
      top: 390px;
      left: 5px;
      z-index: 10;
      Width: 5px;
      Height: 5px;
      background-color: green;
      border-radius: 50%;
      border-width: 0px;
      border-color: blue;
      border-style: solid;
      font-family: Verdana, Arial, Sans-Serif;
      color: white;
      box-shadow: 0 0 20px white;
    }
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
    <html>
      <body>
        <div id="maindiv">
          <canvas id="myCanvas1" width="500" height="400"></canvas>
          <div id="option1"></div>
        </div>
      </body>
    </html>