Vertices with indexArray doesn't connect triangles properly - javascript

I'm trying to create a sort of "polygon drawer" in p5.js.
My intented behaviour is that a random triangle out of three vertices gets ctreated at start and the user can use his mouse to add triangles, which automatically connect to the two closest vertices from the mouseCursor.
This allw orks fine, but sometimes when I start adding the new vertice by clicking (the current vector at the mouse get's pushed to the array of vertices and the triangle / index array get' updated), a triangle get's drawn which uses a different vertice, not the closest, second closest and the mouse cursor vector.
I have two arrays for the vertices and triangles (the later functions as an index array):
let vertices = [];
let triangles = [];
Globally I track the closest and second closest index of the vertices array:
let closest;
let secondClosest;
I start by creating a random triangle. I push each vertex' index to the triangle array and push the first one once again to complete the triangle:
function createRandomTriangle(){
for(i = 0; i < 3; i++){
let vert = createVector(random(0, width),random(0, height));
vertices.push(vert);
triangles.push(i);
}
triangles.push(0);
}
In my draw function I first draw every triangle by going through the triangles array and drawing lines from their vector equivalents in the vertices array.
I then calculate which two vertices are closest to the mouse cursor and update my globals.
As the last step I draw the trianlge from the mouse cursor as some sort of "guide".
function draw() {
background(255);
for(i = 0; i < triangles.length-1; i++){
line(vertices[triangles[i]].x, vertices[triangles[i]].y,vertices[triangles[i+1]].x, vertices[triangles[i+1]].y);
}
let mouseVector = createVector(mouseX, mouseY);
let distances = [];
for(i = 0; i < vertices.length; i++){
distances.push(mouseVector.dist(vertices[i]));
}
closest = distances.indexOf(Math.min( ...distances ));
distances[closest] = width + height;
secondClosest = distances.indexOf(Math.min( ...distances ));
line(mouseVector.x, mouseVector.y, vertices[closest].x, vertices[closest].y);
line(mouseVector.x, mouseVector.y, vertices[secondClosest].x, vertices[secondClosest].y);
line(vertices[closest].x, vertices[closest].y, vertices[secondClosest].x, vertices[secondClosest].y);
}
Now this function is probably the one causing harm, but I can't figure out why.
Once the user clicks, the mouse cursor vector get's pushed to the array of vertices, his index is pushed first to the triangles array, then the closest index, then the second closest index, then the mouse cursor index again.
function mouseClicked() {
let latestIndex = vertices.push(createVector(mouseX, mouseY)) - 1;
triangles.push(latestIndex);
triangles.push(closest);
triangles.push(secondClosest);
triangles.push(latestIndex);
}
Sometimes this method works fine and sometimes a new line suddenly appears.
I can't comprehend exactly why.
You can test thje p5.js sketch here: https://editor.p5js.org/etlam/sketches/4SAhIydAC

Here is a very basic example of how you might go about drawing a polygon. I really recommend creating a StateMaker, so you can keep track of everything, but that's more than I'm willing to go into right now.
//<![CDATA[
/* js/external.js */
let doc, htm, bod, nav, M, I, mobile, S, Q;
addEventListener('load', ()=>{
doc = document; htm = doc.documentElement; bod = doc.body; nav = navigator; M = tag=>doc.createElement(tag); I = id=>doc.getElementById(id);
mobile = nav.userAgent.match(/Mobi/i) ? true : false;
S = (selector, within)=>{
let w = within || doc;
return w.querySelector(selector);
}
Q = (selector, within)=>{
let w = within || doc;
return w.querySelectorAll(selector);
}
rand = (min, max)=>{
let mn = min, mx = max;
if(mx === undefined){
mx = mn; mn = 0;
}
return mn+Math.floor(Math.random()*(mx-mn+1));
}
// tiny library above magic below - can put on another page using a load Event (besides // end load line)
const can = I('can'), canB = can.getBoundingClientRect();
const canW = can.width = canB.width, canH = can.height = canB.height;
const ctx = can.getContext('2d'), points = [];
ctx.lineWidth = '1px'; ctx.lineStyle = '#000'; ctx.fillStyle = '#700';
function polyFun(e){
let b = can.getBoundingClientRect();
ctx.clearRect(0, 0, canW, canH);
points.push([e.clientX-b.left, e.clientY-b.top]);
ctx.beginPath(); ctx.moveTo(...points[0]);
for(let i=1,l=points.length; i<l; i++){
ctx.lineTo(...points[i]);
}
ctx.fill(); ctx.stroke(); ctx.closePath();
}
if(mobile){
ontouchstart = e=>{
polyFun(e.touches[0]);
}
}
else{
onmousedown = polyFun;
}
}); // end load
//]]>
/* css/external.css */
*{
box-sizing:border-box; font-size:0; padding:0; margin:0;
}
html,body,#can{
width:100%; height:100%;
}
#can{
cursor:pointer;
}
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8' /><meta name='viewport' content='width=device-width, height=device-height, initial-scale:1, user-scalable=no' />
<title>Title Here</title>
<link type='text/css' rel='stylesheet' href='css/external.css' />
<script src='js/external.js'></script>
</head>
<body>
<canvas id='can'></canvas>
</body>
</html>

Related

HTML5 canvas context does not update in Safari

UPDATE: This has been submitted as a WebKit bug report: https://bugs.webkit.org/show_bug.cgi?id=246498
UPDATE: I added a code snippet with contributions by #Kaiido, the bug is more apparent on his example fiddle.
I've been experiencing a very weird HTML5 2D Canvas issue in Safari that is not present in Chrome or Firefox. This has started affecting a website that has been in production for 5 years.
I have canvas drawing code like this, which produces a flicker in some of the lines where color is not correctly applied:
const canvasWidth = 600;
const gapBetweenLines = 5;
const nbrLines = canvasWidth / gapBetweenLines;
const canvasHeight = 100;
const canvas = document.getElementById('map');
canvas.width = canvasWidth;
canvas.height = canvasHeight;
// create an array of line objects, each with a with random color
let lines = [];
for (let i = 0; i < nbrLines; i++) {
lines.push({
index: i,
x: i * gapBetweenLines,
color: '#' + Math.floor(Math.random() * 16777215).toString(16)
// force always 6 length
.padStart(6, "0")
});
}
// function to shuffle the given array in place
function shuffle(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
}
// draw lines on the canvas at specific intervals with the random colors
function drawLines() {
const shuffledLines = [...lines];
shuffle(shuffledLines);
let ctx = canvas.getContext('2d');
for (let i = 0; i < nbrLines; i++) {
const line = shuffledLines[i];
ctx.strokeStyle = line.color;
ctx.beginPath();
ctx.moveTo(line.x, 0);
ctx.lineTo(line.x, canvasHeight);
ctx.stroke();
}
}
// call the drawLines function every 100ms
setInterval(drawLines, 200);
<!DOCTYPE html>
<html>
<body>
<h1>Flickering Lines</h1>
<canvas id="map"></canvas>
<div id="lineinfo"></div>
</body>
</html>
In Safari, the stroke colors do not consistently change to their proper strokeStyle value. It behaves as if some of the strokeStyle assignments are being ignored. All the lines draw, but many of them retain the color from prior loop iterations.
This seems like a fairly basic use case for stroke(), so I cannot figure out what I may be doing wrong here; any suggestions would be greatly appreciated.

Why is my sprite graphing algorithm sometimes leaving gaps while graphing?

My friends and I are working on a game. However, I've run into difficulty with the star sprite management. Sometimes, there are gaps (areas where sprites should be graphed but instead are not). I have included the entire program as a code snippet but below are also code blocks which are likely directly responsible for the problem.
There are coordinate pairs for each sprite. The coordinate map is presented below:
[{x:xCoordinate,y:yCoordinate},{x:xCoordinate,y:yCoordinate},...];
This code spawns and removes sprites...
for(var i = 0; i < stars.positions.length; i++){
//store coordinate positions...
var x = stars.positions[i].x;
var y = stars.positions[i].y;
//delete sprites no longer visible within the viewport...
if(x > window.innerWidth || x < -spriteWidth || y < -spriteHeight || y > window.innerHeight){
//sprite is no longer visible within viewport; remove it...
stars.positions.splice(i,1);
}
//find necessary comparative coordinates...
var lowestXCoordinatePair = stars.meta.lowestXCoordinatePair;
var highestXCoordinatePair = stars.meta.highestXCoordinatePair;
var lowestYCoordinatePair = stars.meta.lowestYCoordinatePair;
var highestYCoordinatePair = stars.meta.highestYCoordinatePair;
//gather star sprite meta data...
var spriteWidth = stars.meta.spriteWidth;
var spriteHeight = stars.meta.spriteHeight;
if(lowestXCoordinatePair.x > 0){
//Gap on the left side. New sprites necessary to fill the gap on left row...
//console.log('adding sprites left row...')
for(var i = 0; i < stars.meta.imagesYRequired; i++){
stars.positions.push({
x:lowestXCoordinatePair.x-spriteWidth,
y:lowestXCoordinatePair.y+i*spriteHeight
});
}
}
if(highestXCoordinatePair.x < window.innerWidth-spriteWidth){
//Gap on the right side. New sprites necessary to fill the gap on the right row...
//console.log('adding sprites right row...')
for(var i = 0; i < stars.meta.imagesYRequired; i++){
stars.positions.push({
x:highestXCoordinatePair.x+spriteWidth,
y:highestXCoordinatePair.y+i*spriteHeight
});
}
}
if(lowestYCoordinatePair.y > 0){
//Gap on the top side. New sprites necessary to fill the gap on the top row...
//console.log('adding sprites top row...')
for(var i = 0; i < stars.meta.imagesXRequired; i++){
stars.positions.push({
x:lowestYCoordinatePair.x+i*spriteWidth,
y:lowestYCoordinatePair.y-spriteHeight
});
}
}
if(highestYCoordinatePair.y < window.innerHeight-spriteHeight){
//Gap on the bottom side. New sprites necessary to fill the gap on the bottom row...
console.log('adding sprites bottom row...')
for(var i = 0; i < stars.meta.imagesXRequired; i++){
stars.positions.push({
x:highestYCoordinatePair.x+i*spriteWidth,
y:highestYCoordinatePair.y+spriteHeight
});
}
}
'use strict';
//global variables
var canvas, c;
//viewport variables
var viewportPosition = {
x:false,
y:false
};
//game matrix settings
var gameMatrixConfig = {
width:20000,
height:20000
};
//cursor position
var cursorPosition = {
x:false,
y:false
};
//spaceship position
var spaceship = {
x:false,
y:false,
rotation:0,
gameMatrixPositionX:false,
gameMatrixPositionY:false
};
//fps monitor (for monitoring frame rate for development purposes)...
var fps = {
lastFrameTime:undefined,
timeSinceLastFrame:undefined,
startDisplayTimeInterval:function(){
setInterval(function(){
document.getElementById('fpsLabel').innerHTML = Math.floor(1000/getTimeSinceLastFrame());
},500);
}
};
function getTimeSinceLastFrame(){
return fps.timeSinceLastFrame;
}
//resize throttle timer global variable holder
var resizeTimer = false;
//the drawing frame:
var renderFrame = false;
//global events
window.addEventListener('load',function(){
initialize('load');
});
window.addEventListener('resize',function(){
clearTimeout(resizeTimer);
resizeTimer = setTimeout(function(){
initialize('resize');
},100);
});
window.addEventListener('mousemove',function(e){
cursorPosition.x = e.clientX;
cursorPosition.y = e.clientY;
});
//global functions
function initialize(type){
if(type == 'load'){
preLoadSprites();
}
initializeCanvas();
}
function initializeCanvas(){
canvas = document.getElementById('canvas');
c = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
c.width = canvas.width;
c.height = canvas.height;
}
/*a class with a filePath argument.
This class is used to create new sprite images
from an instance of the class.*/
function sprite(filePath){
//create a new image preload
this.sprite = new Image();
this.sprite.src = filePath;
//load status
this.loaded = false;
/*
holds all current positions of the sprite.
default start coordinates are 0,0
*/
this.positions = [];
//original associative array data structure...
//this.positions = {};
//the image has been preloaded
this.sprite.addEventListener('load',function(){
this.loaded = true;
/*
bind the "this" reference of the constructor to avoid referencing
the load event function
*/
}.bind(this));
}
function preLoadSprites(){
//create new objects for all the sprites to be used in the game
var sprites = {
stars:new sprite('https://drive.google.com/uc?export=download&id=0B821h2dKD0r_bERZb0RHeVRTQnM')
};
/*
check the load status of the sprites
loop through all properties of sprites object twice per second
check for load status. Load flag default is true. Set flag to false
when an unloaded image is discovered.
*/
var interval = setInterval(function(){
var x, loaded = true;
for(x in sprites){
if(!sprites[x].loaded){
loaded = false;
}
}
if(loaded){
clearInterval(interval);
//complete other tasks such as hiding a load spinner...
//ready sprites and context for graphing and provide access to sprites....
initializeGraphing(sprites);
}
},50);
}
function initializeGraphing(sprites){
//set initial viewport position in game matrix....
viewportPosition.x = gameMatrixConfig.width/2;
viewportPosition.y = gameMatrixConfig.height/2;
//start graph animation loop; provide access to sprites....
graph(sprites);
}
/*research how to inherit values from another object with
local variables.*/
function graph(sprites){
updateSpritePositions(sprites);
//testing frame rate...
setInterval(function(){draw()},16.67);
//60fps interval code. Uncomment when testing is complete...
//setInterval(function(){draw()},16.67);
//calculate sprite requirements for viewport and configure a sprite matrix...
initializeStars(sprites.stars);
fps.startDisplayTimeInterval();
//render the graphic frame
function draw(){
//console.log(fps.timeSinceLastFrame)
fps.timeSinceLastFrame = Date.now()-fps.lastFrameTime;
//fps.displayTimeInterval();
//fps.lastFrameTime = Date.now();
//clear the canvas
c.clearRect(0,0,window.innerWidth,window.innerHeight);
//graph the stars...
graphStars(sprites.stars);
updateSpritePositions(sprites);
fps.lastFrameTime = Date.now();
}
}
function initializeStars(stars){
/*
calculate sprite requirements for viewport and configure a sprite matrix
this only needs to happen once unless the viewport is resized
*/
/*
meta data used for various calculations throughout the script...
*/
stars.meta = {
//required sprites to fill the viewport width (based on sprite width)...
imagesXRequired:Math.ceil(window.innerWidth/(stars.sprite.width))+2,
//required sprites to fill the viewport height (based on sprite height)...
imagesYRequired:Math.ceil(window.innerHeight/(stars.sprite.height))+2,
//required sprites to fill the entire viewport...
get requiredSprites(){
return this.imagesXRequired*this.imagesYRequired;
},
//the sprite width...
spriteWidth:stars.sprite.width,
//the sprite height...
spriteHeight:stars.sprite.height,
//the lowest x value in stars.positions...
get lowestXCoordinatePair(){
var xCoordinates = [];
var yCoordinates = [];
for(var i = 0; i < stars.positions.length; i++){
xCoordinates.push(stars.positions[i].x);
yCoordinates.push(stars.positions[i].y);
}
var x = Math.min.apply(Math, xCoordinates);
var index = xCoordinates.indexOf(x);
var y = yCoordinates[index];
return {
x:x,
y:y
};
},
//the highest x value in stars.positions...
get highestXCoordinatePair(){
var xCoordinates = [];
var yCoordinates = [];
for(var i = 0; i < stars.positions.length; i++){
xCoordinates.push(stars.positions[i].x);
yCoordinates.push(stars.positions[i].y);
}
var x = Math.max.apply(Math, xCoordinates);
var index = xCoordinates.indexOf(x);
var y = yCoordinates[index];
return {
x:x,
y:y
};
},
//the lowest y value in stars.positions...
get lowestYCoordinatePair(){
var xCoordinates = [];
var yCoordinates = [];
for(var i = 0; i < stars.positions.length; i++){
xCoordinates.push(stars.positions[i].x);
yCoordinates.push(stars.positions[i].y);
}
var y = Math.min.apply(Math, yCoordinates);
var index = yCoordinates.indexOf(y);
var x = xCoordinates[index];
return {
x:x,
y:y
};
},
//the highest y value in stars.positions...
get highestYCoordinatePair(){
var xCoordinates = [];
var yCoordinates = [];
for(var i = 0; i < stars.positions.length; i++){
xCoordinates.push(stars.positions[i].x);
yCoordinates.push(stars.positions[i].y);
}
var y = Math.max.apply(Math, yCoordinates);
var index = yCoordinates.indexOf(y);
var x = xCoordinates[index];
return {
x:x,
y:y
};
}
};
//the y coordinate in a scaled matrix system for sprites...
var y = 0;
//the x coordinate in a scaled matrix system for sprites...
var x = 0;
//loop through the number of required sprites and graph...
for(var i = 0; i < stars.meta.requiredSprites; i++){
//calculate when a new row is necessary
if((i)%stars.meta.imagesXRequired == 0){
x = 0;
y++;
}
//set actual starting viewport matrix coordinate positions....
stars.positions[i] = {
x:x*stars.meta.spriteWidth,
y:y*stars.meta.spriteWidth
};
x++;
}
}
function graphStars(stars){
/*
prior to graphing, determine if a sprite is no longer within
the viewport matrix and remove it...
if new sprites are necessary, add new coordinates accordingly...
*/
/*
==============IMPORTANT NOTE==================
*/
for(var i = 0; i < stars.positions.length; i++){
//store coordinate positions...
var x = stars.positions[i].x;
var y = stars.positions[i].y;
//delete sprites no longer visible within the viewport...
if(x > window.innerWidth || x < -spriteWidth || y < -spriteHeight || y > window.innerHeight){
//sprite is no longer visible within viewport; remove it...
stars.positions.splice(i,1);
}
//find necessary comparative coordinates...
var lowestXCoordinatePair = stars.meta.lowestXCoordinatePair;
var highestXCoordinatePair = stars.meta.highestXCoordinatePair;
var lowestYCoordinatePair = stars.meta.lowestYCoordinatePair;
var highestYCoordinatePair = stars.meta.highestYCoordinatePair;
//gather star sprite meta data...
var spriteWidth = stars.meta.spriteWidth;
var spriteHeight = stars.meta.spriteHeight;
if(lowestXCoordinatePair.x > 0){
//Gap on the left side. New sprites necessary to fill the gap on left row...
//console.log('adding sprites left row...')
for(var i = 0; i < stars.meta.imagesYRequired; i++){
stars.positions.push({
x:lowestXCoordinatePair.x-spriteWidth,
y:lowestXCoordinatePair.y+i*spriteHeight
});
}
}
if(highestXCoordinatePair.x < window.innerWidth-spriteWidth){
//Gap on the right side. New sprites necessary to fill the gap on the right row...
//console.log('adding sprites right row...')
for(var i = 0; i < stars.meta.imagesYRequired; i++){
stars.positions.push({
x:highestXCoordinatePair.x+spriteWidth,
y:highestXCoordinatePair.y+i*spriteHeight
});
}
}
if(lowestYCoordinatePair.y > 0){
//Gap on the top side. New sprites necessary to fill the gap on the top row...
//console.log('adding sprites top row...')
for(var i = 0; i < stars.meta.imagesXRequired; i++){
stars.positions.push({
x:lowestYCoordinatePair.x+i*spriteWidth,
y:lowestYCoordinatePair.y-spriteHeight
});
}
}
if(highestYCoordinatePair.y < window.innerHeight-spriteHeight){
//Gap on the bottom side. New sprites necessary to fill the gap on the bottom row...
console.log('adding sprites bottom row...')
for(var i = 0; i < stars.meta.imagesXRequired; i++){
stars.positions.push({
x:highestYCoordinatePair.x+i*spriteWidth,
y:highestYCoordinatePair.y+spriteHeight
});
}
}
c.drawImage(stars.sprite,x,y);
}
}
function updateViewportPosition(){
}
function updateSpritePositions(sprites){
/*gather information from the cursor to influence the next frame data
cursor is a local object variable template for use within this function only
cursor stores information about the cursor's position
*/
var cursor = {
distance:{
//the cursor's distances on the planes from the origin for X and Y
cursorXDistance:Math.abs(window.innerWidth/2-cursorPosition.x),
cursorYDistance:Math.abs(window.innerHeight/2-cursorPosition.y)
},
quadrant:function(){
//method returns the appropriate quadrant number for the unit circle
if(cursorPosition.x > window.innerWidth/2 && cursorPosition.y < window.innerHeight/2){
//first quadrant
return 1;
}
if(cursorPosition.x > window.innerWidth/2 && cursorPosition.y > window.innerHeight/2){
//fourth quadrant
return 4;
}
if(cursorPosition.x < window.innerWidth/2 && cursorPosition.y < window.innerHeight/2){
//second quadrant
return 2;
}
if(cursorPosition.x < window.innerWidth/2 && cursorPosition.y > window.innerHeight/2){
//third quadrant
return 3;
}
}
};
//calculate the velocity (the number of pixels to move for the next frame)...
function velocity(){
/*
To calculate velocity ratio, divide the hypotenuse of the cursor's position
by the viewport hypotenuse.
*/
return Math.sqrt(Math.pow(cursor.distance.cursorXDistance,2) + Math.pow(cursor.distance.cursorYDistance,2))/100;
}
//calculate the movement ratio: the number of x pixels per y pixels
function movementRatio(){
var xRatio = cursor.distance.cursorXDistance/(cursor.distance.cursorYDistance+cursor.distance.cursorXDistance);
var yRatio = cursor.distance.cursorYDistance/(cursor.distance.cursorYDistance+cursor.distance.cursorXDistance);
return {xRatio,yRatio};
}
//update positions of sprites...
//retrieve the current movement ratio object...
var coordinateChange = movementRatio();
//retrieve the current quadrant of the unit circle for the cursor's position...
var quadrant = cursor.quadrant();
//retrieve velocity coefficient...
var velocity = velocity();
//update viewport position based on quadrant position...
var i;
for(i in sprites.stars.positions){
if(quadrant == 1){
//update star sprite position
sprites.stars.positions[i].x -= coordinateChange.xRatio*velocity;
sprites.stars.positions[i].y += coordinateChange.yRatio*velocity;
}
if(quadrant == 2){
//update star sprite position
sprites.stars.positions[i].x += coordinateChange.xRatio*velocity;
sprites.stars.positions[i].y += coordinateChange.yRatio*velocity;
}
if(quadrant == 3){
//update the star sprite position
sprites.stars.positions[i].x += coordinateChange.xRatio*velocity;
sprites.stars.positions[i].y -= coordinateChange.yRatio*velocity;
}
if(quadrant == 4){
//update star sprite position
sprites.stars.positions[i].x -= coordinateChange.xRatio*velocity;
sprites.stars.positions[i].y -= coordinateChange.yRatio*velocity;
}
}
}
html,body{
width:100%;
height:100%;
margin:0;
padding:0;
}
canvas{
width:100%;
height:100%;
margin-bottom:-8px;
background:#434343;
}
#gameInterface{
width:100%;
height:100%;
}
.hidden{
width:1px;
height:1px;
position:fixed;
top:0;
left:0;
z-index:-100;
}
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>Canvas Game v0.35</title>
<!--<link rel="stylesheet" href="css/index.css" type="text/css">-->
<!--<script type="text/javascript" src="js/index.js"></script>-->
</head>
<body>
<div id="fpsLabel" style="position:fixed; top:0; left:0; display:table; background:#fff;">
</div>
<div id="intro">
</div>
<div id="gameInterface">
<canvas id="canvas"></canvas>
</div>
</body>
</html>
I've brainstormed a list of possible causes for the problem:
Gaps are caused by sprites being removed too soon.
Gaps are caused by sprites not being spawned.
(sub item) Spawning conditional is correct but the loop to create a new row is flawed.
(sub item) Spawning conditional is flawed.
Could someone help me understand why there are occasionally gaps and help me understand how to fix it? Thank you.

How to make javascript canvas draw faster?

I have the following code to display an ECG. I use the canvas to draw the graph background (each grid of 2.5 mm dimension). Later I'm taking the y coordinates from an array array_1 (x coordinates are calculated within the program). The problem with this approach is it will take around 40 seconds to plot the entire graph since there are 1250 values within array array_1. What I could do is I could do the plotting part within a loop in which case, the entire graph is plotted as soon as the page is loaded. But, I need the plotting to happen over the course of 5 seconds. Not more. Not less. How would I alter the code to do this? Please help.
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<canvas id="canvas" width="1350" height="1300" style="background-color: white;"></canvas>
<script type='text/javascript'>
var canvas = document.getElementById("canvas");
var ctxt = canvas.getContext("2d");
var n1 = 1;
var n1_x=49; //Graph x coordinate starting pixel.
var n1_y=72;//Graph y coordinate starting pixel.
var array_1 = []// array from which y coordinates are taken. Has 1250 elements
var ctx = canvas.getContext("2d");
var x=0;
var y=0;
var Line_position=-1;
while(x<=1350)//graph width
{
ctxt.lineWidth = "0.5";
Line_position=Line_position+1;
if(Line_position%5==0)
{
ctxt.lineWidth = "1.5";
}
ctxt.strokeStyle = "black";
ctxt.beginPath();
ctxt.moveTo(x, 0);
ctxt.lineTo(x, 1300);
ctxt.stroke();
x=x+9.43;
}
Line_position=-1;
while(y<=1300)//graph height
{
ctxt.lineWidth = "0.5";
Line_position=Line_position+1;
if(Line_position%5==0)
{
ctxt.lineWidth = "1.5";
}
ctxt.strokeStyle = "black";
ctxt.beginPath();
ctxt.moveTo(0, y);
ctxt.lineTo(1350,y);
ctxt.stroke();
y=y+9.43;
}
drawWave();
function drawWave()
{
requestAnimationFrame(drawWave);
ctx.lineWidth = "1";
ctx.strokeStyle = 'blue';
ctx.beginPath();
ctx.moveTo(n1_x- 1, n1_y+array_1[n1-1]);//move to the pixel position
ctx.lineTo(n1_x, n1_y+array_1[n1]);//Draw to the pixel position
ctx.stroke();
n1_x=n1_x+0.374;//Incrementing pixel so as to traverse x axis.
n1++;
}
</script>
</body>
</html>
Here is the array:
array_1 = [69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,72,72,72,72,72,72,72,73,73,74,74,74,74,74,74,74,73,73,73,73,74,74,74,74,73,73,73,73,73,73,73,73,73,73,73,73,73,74,74,74,73,73,73,72,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,72,72,72,71,72,72,72,73,73,73,72,72,72,73,73,73,74,73,73,72,72,72,72,72,73,73,73,73,73,72,72,72,72,73,73,73,72,72,72,71,101,71,70,70,70,69,68,68,67,67,66,66,67,67,69,70,72,72,72,73,73,74,73,73,73,73,73,73,73,73,73,74,76,77,76,70,57,40,22,11,11,22,40,57,69,73,73,71,71,71,72,72,73,73,74,74,74,73,72,72,72,72,72,72,72,72,72,72,72,72,71,71,70,70,71,71,71,71,70,70,69,69,69,69,69,69,69,68,68,68,67,67,66,66,65,65,64,63,63,62,62,62,62,62,62,62,62,63,63,64,65,66,67,68,68,69,70,71,72,72,72,73,73,73,73,72,72,72,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,72,72,72,73,73,73,73,72,73,73,73,73,73,73,73,73,73,73,72,72,72,72,72,72,73,73,74,74,74,74,74,74,73,73,72,73,73,73,74,73,73,72,72,72,73,73,73,72,72,73,73,74,74,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,72,72,71,70,70,70,70,70,69,69,68,67,67,67,67,68,69,71,72,72,73,73,73,73,74,74,74,74,74,73,73,73,73,75,77,78,76,67,53,35,18,8,10,23,41,58,69,73,72,71,70,71,72,73,73,73,73,73,73,73,73,72,72,73,73,73,73,72,71,71,70,70,71,71,71,71,71,71,71,71,70,70,69,69,69,69,68,68,67,67,67,67,67,66,65,65,65,64,63,62,61,61,61,60,60,60,59,60,60,60,61,62,63,65,66,66,67,68,69,70,71,72,72,72,72,73,73,73,72,72,72,72,72,72,72,73,73,73,73,73,73,72,72,73,73,73,73,73,73,73,73,73,73,73,73,73,72,72,71,71,72,72,73,73,73,72,72,72,72,72,72,73,73,73,73,73,73,73,73,73,72,73,73,73,73,73,73,72,73,73,73,73,73,73,73,72,72,72,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,72,71,71,70,70,69,69,69,68,67,67,66,65,66,66,68,69,70,71,72,72,73,73,73,73,73,73,74,74,74,74,74,74,76,78,78,74,64,48,29,13,5,10,26,45,62,71,73,72,71,71,72,73,73,73,73,73,74,74,74,73,72,72,72,73,73,73,73,73,73,73,72,72,72,72,71,71,71,71,71,71,71,71,71,70,70,69,69,69,69,68,67,66,66,66,66,65,65,64,63,62,62,61,61,60,60,60,60,61,62,62,63,64,65,66,67,68,70,71,72,72,72,72,72,72,73,73,73,73,73,73,73,74,74,75,75,74,74,74,73,73,73,74,73,73,73,73,73,74,74,74,74,74,73,73,73,73,73,73,73,73,73,73,73,73,73,73,72,72,73,73,74,74,74,73,73,73,73,73,73,73,73,73,73,72,72,72,72,73,73,72,72,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,72,72,73,73,72,72,71,70,70,70,69,69,68,68,67,67,66,67,67,68,69,70,71,72,73,73,74,74,73,73,73,74,75,75,74,73,73,74,76,78,75,67,52,32,15,5,8,22,41,59,69,73,72,71,70,71,72,72,73,73,73,73,73,73,73,73,73,72,72,72,72,72,72,72,72,72,72,71,71,71,70,70,70,70,70,70,70,69,69,69,69,68,68,68,68,67,67,66,65,65,64,64,64,63,62,61,60,60,60,60,60,61,61,62,62,63,64,65,65,66,67,68,69,70,71,71,71,71,71,71,72,72,73,73,73,72,72,73,73,73,73,73,73,73,73,73,73,73,73,73,72,72,72,72,72,72,72,71,71,71,71,71,71,71,72,72,72,72,72,72,72,72,72,71,71,71,72,72,73,73,72,72,72,72,72,73,73,73,73,73,72,72,72,72,72,73,73,73,73,73,72,72,72,73,73,74,73,73,73,73,73,73,73,73,73,73,73,73,72,72,72,72,71,71,71,70,70,70,70,69,69,68,67,67,68,69,71,72,73,73,73,73,73,73,73,73,74,75,75,75,74,74,74,75,77,77,75,67,52,34,18,10,12,26,45,62,71,74,73,72,72,72,73,74,74,74,75,75,74,74,74,74,74,74,74,74,74,73,73,73,73,74,74,73,73,73,73,73,73,73,72,72,71,71,71,71,71,70,70,70,69,69,69,68,68,68,68,67,66,65,64,63,63,62,62,62,63,63,63,63,64,65,66,67,69,69,70,71,72,72,73,73,74,74,74,74,75,75,76,76,74,72,70,70,69,69 ];
I'd probably go about the task something like this. As mentioned in a comment, we need to draw a number of the data-points per-frame. How many we draw depends on the speed that the browser is able to supply an animation frame.
I've hard-coded the value to 4, since that seems to work on my machine, but with not much more work you can probably make the code time itself and adjust this value on the fly so that your animation runs for as close as possible to the target time. I had a quick go, but the results were awful, I'll leave that as an exercise in research or thought for the reader.
By keeping track of how many frames we've already drawn for the current 'refresh-cycle', we know how far to index into the array for the first point to be drawn for each frame.
I've tried to parameterize the code as much as possible, but it's late and I'm tired, I may have overlooked something somewhere.
<!doctype html>
<html>
<head>
<script>
function byId(id,parent){return (parent == undefined ? document : parent).getElementById(id);}
window.addEventListener('load', onDocLoaded, false);
function onDocLoaded(evt)
{
drawBkg(byId('canvas'), 9.43, 5, "0.5", "1.5", "black");
drawCurFrame();
}
var dataSamples = [69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,72,72,72,72,72,72,72,73,73,74,74,74,74,74,74,74,73,73,73,73,74,74,74,74,73,73,73,73,73,73,73,73,73,73,73,73,73,74,74,74,73,73,73,72,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,72,72,72,71,72,72,72,73,73,73,72,72,72,73,73,73,74,73,73,72,72,72,72,72,73,73,73,73,73,72,72,72,72,73,73,73,72,72,72,71,101,71,70,70,70,69,68,68,67,67,66,66,67,67,69,70,72,72,72,73,73,74,73,73,73,73,73,73,73,73,73,74,76,77,76,70,57,40,22,11,11,22,40,57,69,73,73,71,71,71,72,72,73,73,74,74,74,73,72,72,72,72,72,72,72,72,72,72,72,72,71,71,70,70,71,71,71,71,70,70,69,69,69,69,69,69,69,68,68,68,67,67,66,66,65,65,64,63,63,62,62,62,62,62,62,62,62,63,63,64,65,66,67,68,68,69,70,71,72,72,72,73,73,73,73,72,72,72,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,72,72,72,73,73,73,73,72,73,73,73,73,73,73,73,73,73,73,72,72,72,72,72,72,73,73,74,74,74,74,74,74,73,73,72,73,73,73,74,73,73,72,72,72,73,73,73,72,72,73,73,74,74,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,72,72,71,70,70,70,70,70,69,69,68,67,67,67,67,68,69,71,72,72,73,73,73,73,74,74,74,74,74,73,73,73,73,75,77,78,76,67,53,35,18,8,10,23,41,58,69,73,72,71,70,71,72,73,73,73,73,73,73,73,73,72,72,73,73,73,73,72,71,71,70,70,71,71,71,71,71,71,71,71,70,70,69,69,69,69,68,68,67,67,67,67,67,66,65,65,65,64,63,62,61,61,61,60,60,60,59,60,60,60,61,62,63,65,66,66,67,68,69,70,71,72,72,72,72,73,73,73,72,72,72,72,72,72,72,73,73,73,73,73,73,72,72,73,73,73,73,73,73,73,73,73,73,73,73,73,72,72,71,71,72,72,73,73,73,72,72,72,72,72,72,73,73,73,73,73,73,73,73,73,72,73,73,73,73,73,73,72,73,73,73,73,73,73,73,72,72,72,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,72,71,71,70,70,69,69,69,68,67,67,66,65,66,66,68,69,70,71,72,72,73,73,73,73,73,73,74,74,74,74,74,74,76,78,78,74,64,48,29,13,5,10,26,45,62,71,73,72,71,71,72,73,73,73,73,73,74,74,74,73,72,72,72,73,73,73,73,73,73,73,72,72,72,72,71,71,71,71,71,71,71,71,71,70,70,69,69,69,69,68,67,66,66,66,66,65,65,64,63,62,62,61,61,60,60,60,60,61,62,62,63,64,65,66,67,68,70,71,72,72,72,72,72,72,73,73,73,73,73,73,73,74,74,75,75,74,74,74,73,73,73,74,73,73,73,73,73,74,74,74,74,74,73,73,73,73,73,73,73,73,73,73,73,73,73,73,72,72,73,73,74,74,74,73,73,73,73,73,73,73,73,73,73,72,72,72,72,73,73,72,72,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,73,72,72,73,73,72,72,71,70,70,70,69,69,68,68,67,67,66,67,67,68,69,70,71,72,73,73,74,74,73,73,73,74,75,75,74,73,73,74,76,78,75,67,52,32,15,5,8,22,41,59,69,73,72,71,70,71,72,72,73,73,73,73,73,73,73,73,73,72,72,72,72,72,72,72,72,72,72,71,71,71,70,70,70,70,70,70,70,69,69,69,69,68,68,68,68,67,67,66,65,65,64,64,64,63,62,61,60,60,60,60,60,61,61,62,62,63,64,65,65,66,67,68,69,70,71,71,71,71,71,71,72,72,73,73,73,72,72,73,73,73,73,73,73,73,73,73,73,73,73,73,72,72,72,72,72,72,72,71,71,71,71,71,71,71,72,72,72,72,72,72,72,72,72,71,71,71,72,72,73,73,72,72,72,72,72,73,73,73,73,73,72,72,72,72,72,73,73,73,73,73,72,72,72,73,73,74,73,73,73,73,73,73,73,73,73,73,73,73,72,72,72,72,71,71,71,70,70,70,70,69,69,68,67,67,68,69,71,72,73,73,73,73,73,73,73,73,74,75,75,75,74,74,74,75,77,77,75,67,52,34,18,10,12,26,45,62,71,74,73,72,72,72,73,74,74,74,75,75,74,74,74,74,74,74,74,74,74,73,73,73,73,74,74,73,73,73,73,73,73,73,72,72,71,71,71,71,71,70,70,70,69,69,69,68,68,68,68,67,66,65,64,63,63,62,62,62,63,63,63,63,64,65,66,67,69,69,70,71,72,72,73,73,74,74,74,74,75,75,76,76,74,72,70,70,69,69 ];
function drawBkg(canvasElem, squareSize, numSquaresPerBlock, minorLineWidthStr, majorLineWidthStr, lineColStr)
{
var nLinesDone = 0;
var i, curX, curY;
var ctx = canvasElem.getContext('2d');
ctx.clearRect(0,0,canvasElem.width,canvasElem.height);
// draw the vertical lines
curX=0;
ctx.strokeStyle = lineColStr;
while (curX < canvasElem.width)
{
if (nLinesDone % numSquaresPerBlock == 0)
ctx.lineWidth = majorLineWidthStr;
else
ctx.lineWidth = minorLineWidthStr;
ctx.beginPath();
ctx.moveTo(curX, 0);
ctx.lineTo(curX, canvasElem.height);
ctx.stroke();
curX += squareSize;
nLinesDone++;
}
// draw the horizontal lines
curY=0;
nLinesDone = 0;
while (curY < canvasElem.height)
{
if (nLinesDone % numSquaresPerBlock == 0)
ctx.lineWidth = majorLineWidthStr;
else
ctx.lineWidth = minorLineWidthStr;
ctx.beginPath();
ctx.moveTo(0, curY);
ctx.lineTo(canvasElem.width, curY);
ctx.stroke();
curY += squareSize;
nLinesDone++;
}
}
// position that will be treated as 0,0 when drawing our points.
var originX=49;
var originY=72;
function drawSamples(nSamplesToDraw, firstSample, lineWidthStr, lineColourStr)
{
var can = byId('canvas');
var ctx = can.getContext('2d');
ctx.strokeStyle = lineColourStr;
ctx.lineWidth = lineWidthStr;
console.log(firstSample);
ctx.beginPath();
ctx.moveTo( originX+firstSample-1, dataSamples[firstSample-1]+originY );
for (var i=0; i<nSamplesToDraw; i++)
{
var curSample = dataSamples[i + firstSample];
ctx.lineTo( originX+firstSample+i, curSample+originY );
}
ctx.stroke();
}
var curFrame=0;
var nPointsPerFrame = 4;
function drawCurFrame()
{
if ((dataSamples.length - (nPointsPerFrame * curFrame)) < nPointsPerFrame) // will we over-run the end of the array of datapoints?
{
curFrame = 0; // if so, reset
drawBkg(byId('canvas'), 9.43, 5, "0.5", "1.5", "black");
}
drawSamples(nPointsPerFrame, nPointsPerFrame*curFrame, "1", "blue");
curFrame++;
requestAnimationFrame( drawCurFrame );
}
</script>
<style>
#canvas
{
border: solid 1px black;
background-color: #FFFFFF;
}
</style>
</head>
<body>
<div id='txt'></div>
<canvas id="canvas" width="1350" height="1300"></canvas>
</body>
</html>
Update
Now that I see you have provided some more info I get what you want.
The problem is you need to draw a fixed number of line segments within time t.
As you do not know how long each frame could take you can not rely on a fixed frame rate. The alternative it to just use the current time and save the end time.
Get the start time and then each frame draw all the should be drawn until the current time. As the line segments being drawn will not be displayed until the next screen refresh the time you get will be approx 16ms behind so will need to adjust for that.
What I have done is keep track of the average frame time and used half that time to estimate when the new canvas update will be displayed.
Its a bit pedantic but might as well show how to get a required time as close as possible. If you dont care its a few ms out then just remove the average frame time stuff. You will be at most 30ms off on a slow machine.
var canvas; // canvas
var ctx;
function getCanvas () {
// to do
// get canvas and context
}
function drawGrid () {
// to do
// draw the grid
}
function drawTimedLine(){
if(canvas === undefined){ // if the canvas not available get it
getCanvas();
}
// clear the canvas is repeating animation
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawGrid();
var array_1 = ; // your data
// All stuff needed for timed animation.
// The frame you render will not be displayed untill the next
// vertical refresh which is unknown, Assume it is one frame.
var startDelay = 1000; // if Needed time in milliseconds to delay start
var endTime; // hold the time that the animation has to end
var lastDataPoint; // holds the last point drawn to
var timeToDraw = 5 * 1000; // how long the animation should last
var repeatAfter = 1 *1000; // if you want to repeat the animatoin
var frameCount = 0; // count the frames to get average frame time
var startTime; //the start time;
var numberPoints = array_1.length; // number of points;
var startX = 49; // line starts at
var yOffset = 72; // line Y offset
var endX = 512; // line ends at.
var width = endX - startX; // width
var xStep = width / numberPoints; // X step per point
var pointsPerMS = numberPoints / timeToDraw; // get how many points per ms should be drawn
// function to draw
function drawWave() {
// variable needed
var averageframeTime, timeLeft, i, currentTime;
currentTime = new Date().valueOf(); // gets the time in millisecond;
if (startTime === undefined) { // Is this the first frame
startTime = currentTime; // save the start time;
endTime = currentTime + timeToDraw; // workout when the end time is;
lastDataPoint = 0; // set the data position to the start;
averageframeTime = 0; // no frames counted so frame time is zero
} else {
frameCount += 1; // count the frames
// get the average frame time
averageframeTime = (currentTime - startTime) / frameCount;
}
// get the time this frame
// will most likely be displayed
// then calculate how long
// till the end
timeLeft = endTime - Math.min(endTime, currentTime + averageframeTime / 2);
// now get where we should
// be when the frame is presented
pointPos = Math.floor(pointsPerMS * (timeToDraw - timeLeft));
// now draw the points from where we last left of
// till the new pos;
ctx.lineWidth = 4;
ctx.strokeStyle = 'blue';
ctx.beginPath();
ctx.moveTo( // move to first point
lastDataPoint * xStep + startX,
array_1[lastDataPoint] + yOffset
);
// draw each line from the last drawn to the new position
for (i = lastDataPoint + 1; i <= pointPos && i < numberPoints; i++) {
// Add the line segment
ctx.lineTo(
i * xStep + startX,
array_1[i] + yOffset
);
}
ctx.stroke(); // execute the render commands
lastDataPoint = pointPos; // update the last point
if (pointPos < numberPoints) { // are we there yet???
requestAnimationFrame(drawWave); // no so request another frame
}else{
// if you want to repeat the animation
setTimeout(drawTimedLine , repeatAfter );
}
}
// start the line animation with delay if needed
setTimeout(drawWave,startDelay);
}
// use this if you want it to start as soon as page is ready.
document.addEventListener("DOMContentLoaded",drawTimedLine);
// or use if you want it to start when page has images loaded and is ready
// document.addEventListener("load",drawTimedLine);
I have also added the ability to repeat the animation. If not needed just remove that code
My original answer
Dont know what the problem is with speed as it runs quite well on my machine.
To set up a better start use
function startFunction(){
// your code
}
document.addEventListener("DOMContentLoaded",startFunction);
This will wait until the page has loaded and parsed the page. Images and other media may not have loaded but the page is ready to be manipulated.
Not sure what you mean with 5 seconds. Assuming you may want the thing to sart in 5 seconds.
The following will do that.
document.addEventListener("DOMContentLoaded",function() {setTimeout(startFunction,5000);});
I would ask why plot the graph one entry at a time with requestAnimationFrame 1250 is not that many lines to draw. If you add ctx.beginPath() ctx.moveTo(/*first point*/) then loop all points with ctx.moveTo(/*points*/) then ctx.stroke() will run realtime on but the slowest of devices.
BTW ctx.lineWidth is a Number not a string. Also you have two context? Use the one context for the canvas. Remove ctxt and just use ctx and finally you don't need to add type='text/javascript' to the script tag as Javascript is the default.
1) It cannot take that long to draw 1000 lines, even 100000 lines won't take more than 10 ms on any decent Browser. Look else where the time is lost.
2) The core issue of your code is that it lacks modularity. Split your code into a few clear functions, group the parameters into a few objects only, name and indent things properly.
Below an (incomplete but working) example of how this might look.
var cv, ctx;
var data = null;
var debug = true;
// ---------------------------------------
// define here all graphic related parameters
var gfxParams = {
canvasWidth: 600,
canvasHeight: 600,
gridColor: '#A66',
gridSpacing: 10,
gridLineWidth: 0.5,
gridStrongLinesEvery: 5,
lineColor: '#AEB',
lastLineColor: '#8A9' // , ...
};
// define here all animation related parameters
var animationParams = {
duration: 5,
startTime: -1
}
// ---------------------------------------
// main
// ---------------------------------------
window.onload = function() {
data = getData();
setupCanvas(data);
launchAnimation();
}
// ---------------------------------------
//
function setupCanvas(data) {
cv = document.getElementById('cv');
cv.width = gfxParams.canvasWidth;
cv.height = gfxParams.canvasHeight;
ctx = cv.getContext('2d');
// here you should translate and scale the context
// so that it shows your data.
}
function drawGrid(ctx) {
var i = 0,
pos = 0,
lw = gfxParams.gridLineWidth;
ctx.fillStyle = gfxParams.gridColor;
var vLineCount = gfxParams.canvasWidth / gfxParams.gridSpacing;
for (i = 0; i < vLineCount; i++) {
pos = i * gfxParams.gridSpacing;
ctx.fillRect(pos, 0, lw, gfxParams.canvasHeight);
}
var hLineCount = gfxParams.canvasHeight / gfxParams.gridSpacing;
for (i = 0; i < hLineCount; i++) {
pos = i * gfxParams.gridSpacing;
ctx.fillRect(0, pos, gfxParams.canvasWidth, lw);
}
}
function animate() {
requestAnimationFrame(animate);
var now = Date.now();
// erase screen
ctx.clearRect(0, 0, gfxParams.canvasWidth, gfxParams.canvasHeight);
// draw grid
drawGrid(ctx);
// draw lines
var lastIndex = getLastDrawnIndex(data, now - animationParams.startTime);
drawLines(ctx, data, lastIndex);
if (debug) {
ctx.save();
ctx.fillStyle = '#000';
ctx.fillText(lastIndex + ' lines drawn. Time elapsed : ' + (now - animationParams.startTime), 10, 10);
ctx.restore();
}
}
// comment
function launchAnimation() {
requestAnimationFrame(animate);
animationParams.startTime = Date.now();
}
// comment
function getData() {
var newData = [];
for (var i = 0; i < 500; i++) {
newData.push([Math.random() * 600, Math.random() * 600]);
}
return newData;
}
// comment
function getLastDrawnIndex(data, timeElapsed_ms) {
var timeElapsed = timeElapsed_ms / 1000;
if (timeElapsed >= animationParams.duration) return data.length - 1;
return Math.floor(data.length * timeElapsed / animationParams.duration);
}
function drawLines(ctx, data, lastIndex) {
ctx.strokeStyle = gfxParams.lineColor;
// other ctx setup here.
for (var i = 0; i < lastIndex - 1; i++) {
drawLine(ctx, data[i], data[i + 1]);
}
ctx.strokeStyle = gfxParams.lastLineColor;
drawLine(ctx, data[lastIndex - 1], data[lastIndex]);
}
function drawLine(ctx, p1, p2) {
ctx.beginPath();
ctx.moveTo(p1[0], p1[1]);
ctx.lineTo(p2[0], p2[1]);
ctx.stroke();
}
<canvas id='cv'></canvas>

Drawing in canvas becomes slower and slower when it draws more stuff during loop

I am trying to incrementally draw 3 lines which are 120 degrees from each other from a point using html5 canvas. The vertex of each lines will become another 3 new center point and spawns another 3 lines at each center and it repeats this..
My problem is, the incremental speed becomes slower and slower (or the drawing becomes slower) as more items are drawn. (maybe something happens in my code but I am not quite familiar how canvas exactly works...). You can copy the code and run it in your local browser to see what I means.
Please see my code (it is very easy to understand) and tell me what causes this.
<!DOCTYPE HTML>
<html>
<head>
<style>
body {
margin: 0px;
padding: 0px;
}
</style>
</head>
<body>
<canvas id="canvas" ></canvas>
<script>
window.requestAnimFrame = (function(callback) {
return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000 / 60);
};
})();
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
canvas.width= window.innerWidth;
canvas.height= window.innerHeight;
// some staring test values
var centerPt={x:canvas.width/2,y:canvas.height/2};
var radius=100;
var angle=0;
// calculate the 3 endpoints at 120 degree separations
var endPt000=anglePoint(centerPt,90);
var endPt120=anglePoint(centerPt,210);
var endPt240=anglePoint(centerPt,330);
var length = 0;
var maxLength = 100;
var centreSet = new Array();
centreSet = getCentres();
var counter = 0;
var end = centreSet.length;
init();
function init() {
start(centreSet[0].x, centreSet[0].y);
}
function start(myX, myY) {
centerPt.x = myX;
centerPt.y = myY;
animate(centerPt, length);
}
function animate(centerPt,length) {
// update
// clear
ctx.clearRect(0, 0, canvas.width, canvas.height);
// draw stuff
draw(centerPt,length);
length = length + 1;
// request new frame
if(length < maxLength){
requestAnimFrame(function() {
animate(centerPt,length);
});
}
else{
if(counter < end){
counter = counter + 1;
centerPt.x = centreSet[counter].x;
centerPt.y = centreSet[counter].y;
endPt000=anglePoint(centerPt,90);
endPt120=anglePoint(centerPt,210);
endPt240=anglePoint(centerPt,330);
length = 0;
setTimeout(function(){animate(centerPt, length);},600);
}
}
}
// draw a red center dot
// draw 3 blue endpoint dots
// draw 3 lines from center going slider% of the way to the endpoints
function draw(centerPt,sliderValue){
var pct=sliderValue;
ctx.clearRect(0,0,canvas.width,canvas.height);
line(centerPt,pointAtPercent(centerPt,endPt000,pct),"green");
line(centerPt,pointAtPercent(centerPt,endPt120,pct),"green");
line(centerPt,pointAtPercent(centerPt,endPt240,pct),"green");
}
// calc XY at the specified angle off the centerpoint
function anglePoint(centerPt,degrees){
var x=centerPt.x-radius*Math.cos( degrees*Math.PI/180 );
var y=centerPt.y-radius*Math.sin( degrees*Math.PI/180 );
return({x:x,y:y});
}
// just draw a line from point1 to point2
function line(pt1,pt2,color){
// ctx.beginPath();
ctx.moveTo(pt1.x,pt1.y);
ctx.lineTo(pt2.x,pt2.y);
ctx.strokeStyle=color;
ctx.lineWidth=2;
ctx.stroke();
}
// calc XY which is a specified percent distance from pt1 to pt2
function pointAtPercent(pt1,pt2,sliderValue) {
// calculate XY at slider% towards pt2
var x = pt1.x + (pt2.x-pt1.x) * sliderValue/100;
var y = pt1.y + (pt2.y-pt1.y) * sliderValue/100;
return({x:x,y:y});
}
//the following are used to get all the center points...
function getCentres() {
var x = window.innerWidth/2;
var y = window.innerHeight/2;
centreSet[0] = centerPt;
var ref = 0;
var end = 0;
var b = true;
var tempCenter = centerPt;
for(var j = 0; j < 5; j++){
tempCenter = centreSet[ref];
end = end + 1;
centreSet[end] = anglePoint(tempCenter,90);
end = end + 1;
centreSet[end] = anglePoint(tempCenter,210);
end = end + 1;
centreSet[end] = anglePoint(tempCenter,330);
ref = ref+1;
}
return centreSet;
}
</script>
</body>
</html>
The problem is you are appending and appending the path. This means that each time you call stroke() the new line together with all the old lines are stroked. You won't see this clearly as the old lines are drawn on top in the same location. And as more and more lines are added the more time it takes to stroke them..
To prevent this you need to break the path. Do this with beginPath().
If you activate your out-commented line it should work fine:
function line(pt1,pt2,color){
ctx.beginPath(); //<-- activate this
ctx.moveTo(pt1.x,pt1.y);
ctx.lineTo(pt2.x,pt2.y);
ctx.strokeStyle=color;
ctx.lineWidth=2;
ctx.stroke();
}

HTML5 canvas continuously stroking lines

I want to draw some continuously growing lines in HTML5 and Javascript. Here is what I want to do:
A point located at the center of my screen will have 3 lines growing (120 degree to each other) to a certain length, say 50 pix, then each of this 3 vertex will become a new center and have another 3 lines.
(I couldnt post images due to low reputation I have, hopefully you know what I mean abt the image here...)
I already written the function to have a array of all the points I need as the centers, starting from the center of my screen. I am thinking to write a loop over this array to draw the lines. I DO NOT want to directly use the stroke so that the line just appears on the screen. I want to have something like the the lines are drawn bit by bit (bad english here, please excuse my english) until it reaches the pre-defined length. However my code dont work quite well here, it only displays all the center points and only the last center point has the movement to have the 3 lines to grow...
I need to know the correct way to do this... many thanks in advance!
(please ignore the variable time or startTime in my code... )
<script>
window.requestAnimFrame = (function(callback) {
return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000 / 60);
};
})();
var canvas = document.getElementById('myCanvas');
canvas.width= window.innerWidth;
canvas.height= window.innerHeight;
var context = canvas.getContext('2d');
var totalLength = 50;
var centreSet = new Array();
var counter = 0;
var centre = {
x: canvas.width / 2,
y: canvas.height / 2,
};
var myLine = {
length : 0,
color : 'grey',
lineWidth : 0.5,
};
function drawLine(centre, context, mylength) {
context.beginPath();
context.moveTo(centre.x, centre.y);
context.lineTo(centre.x, centre.y - mylength);
context.moveTo(centre.x, centre.y);
context.lineTo(centre.x - 0.866 * mylength, centre.y + mylength/2);
context.moveTo(centre.x, centre.y);
context.lineTo(centre.x + 0.866 * mylength, centre.y + mylength/2);
context.lineWidth = myLine.lineWidth;
context.strokeStyle = myLine.color;
context.closePath();
context.stroke();
}
function startAnimate(centre, canvas, context, startTime, mylength) {
// update
var time = (new Date()).getTime() - startTime;
var linearSpeed = 5;
// pixels / second
var newX = linearSpeed / 10;
if(mylength < totalLength) {
mylength = mylength + newX;
// clear
//context.clearRect(0, 0, canvas.width, canvas.height);
drawLine(centre, context, mylength);
// request new frame
requestAnimFrame(function() {
startAnimate(centre, canvas, context, startTime, mylength);
});
}
}
function animate(centre, canvas, context, startTime){
//create array to have all the center points
centreSet = getCentres();
for (var i = 0; i < centreSet.length; i++){
//pass the x and y values in a object for each center we have in the array
centre.x = str2x(centreSet[i]);
centre.y = str2y(centreSet[i]);
startAnimate(centre, canvas, context, startTime, 0);
}
}
setTimeout(function() {
var startTime = (new Date()).getTime();
animate(centre, canvas, context, startTime);
}, 1000);
I just edited your code, I added the following part:
var length = 0;
for(var i = 0; i < 380; i++){
window.setTimeout(function() {drawFrame(length);},16.67);
length = length + 0.25;
}
I expect the screen appears to draw the incremental lines bit by bit until it reaches the length I want. However, it seems like the whole incremental process is not shown and it only shows the finished drawing.
Can anyone tell me why?
Regarding your followup question about why your animation loop fails
By putting your setTimeout in a for-loop, each new setTimeout is cancelling the previous setTimeout.
So you’re just left with the very last setTimeout running to completion.
In an animation loop, you typically do 3 things during each "frame":
Change some data to reflect how the new frame is different from the previous frame.
Draw the frame.
Test if the animation is complete. If not, do another frame (go to #1).
The setTimeout function is used to do the last part of #3 (do another frame)
So setTimeout is really acting as your animation loop. --- Your for-loop is not needed.
This is how you would restructure your code to follow this pattern:
var length=0;
var maxLength=50;
function draw(){
// make the line .25 longer
length=length+.25;
// draw
drawFrame(length);
// test if the line is fully extended
// if not, call setTimeout again
// setTimeout(draw,100) will call this same draw() function in 100ms
if(length<maxLength){
setTimeout(draw,100);
}
}
[Edited: to include spawning of child objects after lines reach terminal distance]
In your code you were not spawning new center points when the lines reached their maximum extension.
I would suggest that each of your centre objects have at least this much information in order to spawn a new set of centre objects when their lines reach terminal length:
var newCentrePoint={
x:x,
y:y,
maxLength:newMaxLength,
growLength:growLength,
currentLength:0,
isActive:true
}
The x,y are the centerpoint’s coordinates.
maxLength is the maximum extension of the 3 lines before they are terminated.
growLength is the amount by which each line will grow in each new frame.
currentLength is the current length of the line.
isActive is a flag indicating if this point is growing lines (true) or if it’s terminated (false)
Then when each line reaches terminal length you can spawn a new set of lines like this:
// spawns 3 new centre points – default values are for testing
function spawn(point,newMaxLength,newColor,growLength,newLineWidth){
var max=newMaxLength||point.maxLength/2;
var color=newColor|| (colors[++colorIndex%(colors.length)]);
var grow=growLength||point.growLength/2;
var lw=newLineWidth||point.lineWidth-1;
// new center points are spawned at the termination points of the 3 current lines
newPoint((point.x),(point.y-point.maxLength),max,color,grow,lw);
newPoint((point.x-0.866*point.maxLength),(point.y+point.maxLength/2),max,color,grow,lw);
newPoint((point.x+0.866*point.maxLength),(point.y+point.maxLength/2),max,color,grow,lw);
}
// creates a new point object and puts in the centreSet array for processing
function newPoint(x,y,newMaxLength,newColor,growLength,newLineWidth){
var newPt={
x:x,
y:y,
maxLength:newMaxLength,
color:newColor,
lineWidth:newLineWidth,
growLength:growLength,
currentLength:0,
isActive:true
}
centreSet.push(newPt);
}
Here is code and a Fiddle: http://jsfiddle.net/m1erickson/Vc8Gf/
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
body{ background-color: ivory; }
canvas{border:1px solid red;}
</style>
<script>
$(function(){
var canvas=document.getElementById("canvas");
var context = canvas.getContext('2d');
// colors
var colors=["red","blue","gold","purple","green"];
var colorIndex=0;
//
var centreSet=[]
var generations=1;
// seed point
newPoint(canvas.width/2,canvas.height/2,100,"red",15);
// start
draw();
//
function draw(){
//
context.clearRect(0,0,canvas.width,canvas.height);
//
for(var i=0;i<centreSet.length;i++){
//
var centre=centreSet[i];
//
if(centre.isActive){
//
centre.currentLength+=centre.growLength;
//
if(centre.currentLength>=centre.maxLength){
centre.isActive=false;
centre.currentLength=centre.maxLength;
spawn(centre);
}
}
//
drawLines(centre);
}
//
if(generations<120){
setTimeout(draw,500);
}else{
context.font="18pt Verdana";
context.fillText("Finished 120 generations",40,350);
}
}
function spawn(point,newMaxLength,newColor,growLength,newLineWidth){
var max=newMaxLength||point.maxLength/2;
var color=newColor|| (colors[++colorIndex%(colors.length)]);
var grow=growLength||point.growLength/2;
var lw=newLineWidth||point.lineWidth-1;
newPoint((point.x),(point.y-point.maxLength),max,color,grow,lw);
newPoint((point.x-0.866*point.maxLength),(point.y+point.maxLength/2),max,color,grow,lw);
newPoint((point.x+0.866*point.maxLength),(point.y+point.maxLength/2),max,color,grow,lw);
generations++;
}
function newPoint(x,y,newMaxLength,newColor,growLength,newLineWidth){
var newPt={
x:x,
y:y,
maxLength:newMaxLength,
color:newColor,
lineWidth:newLineWidth,
growLength:growLength,
currentLength:0,
isActive:true
}
centreSet.push(newPt);
}
function drawLines(centre) {
var length=centre.currentLength;
//
context.beginPath();
context.moveTo(centre.x, centre.y);
context.lineTo(centre.x, centre.y - length);
//
context.moveTo(centre.x, centre.y);
context.lineTo(centre.x - 0.866 * length, centre.y + length/2);
//
context.moveTo(centre.x, centre.y);
context.lineTo(centre.x + 0.866 * length, centre.y + length/2);
//
context.strokeStyle=centre.color;
context.lineWidth = centre.lineWidth;
context.stroke();
}
}); // end $(function(){});
</script>
</head>
<body>
<canvas id="canvas" width=400 height=400></canvas>
</body>
</html>

Categories