html5 draw gaussian function using bezierCurveTo - javascript

I have been trying to draw gaussin-like function using bezierCurveTo
find the code below
<canvas id="thisCan" width="0px" height="0px" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.
</canvas>
<script>
(function() {
var
// Obtain a reference to the canvas element
// using its id.
htmlCanvas = document.getElementById('thisCan'),
// Obtain a graphics context on the
// canvas element for drawing.
ctx = htmlCanvas.getContext('2d');
var width = 0;
var height = 0;
// Start listening to resize events and
// draw canvas.
initialize();
function initialize()
{
// Register an event listener to
// call the resizeCanvas() function each time
// the window is resized.
window.addEventListener('resize', resizeCanvas, false);
// Draw canvas border for the first time.
resizeCanvas();
}
// Display custom canvas.
// In this case it's a blue, 5 pixel border that
// resizes along with the browser window.
function redraw()
{
ctx.beginPath();
ctx.moveTo(width/2, 0);
ctx.bezierCurveTo(150, 119, 186, 121, 66, 185);
ctx.moveTo(width/2 + width * 0.08 , 0);
ctx.bezierCurveTo(344, 119, 344, 121, 504, 185);
ctx.stroke();
}
// Runs each time the DOM window resize event fires.
// Resets the canvas dimensions to match window,
// then draws the new borders accordingly.
function resizeCanvas()
{
var contentElement = document.getElementsByClassName("content-box post")[0]
width = htmlCanvas.width = (contentElement.clientWidth * 0.75)
height = htmlCanvas.height = (contentElement.offsetWidth*0.75 * 0.5);
redraw();
}
})();
</script>
I am planning to draw many curves in between as well. But how do I make it parametric, based on width and height variables?
I need to specify the control points using width and height parameters, so that it becomes window-size invariant.
Is there a way?

Don't use Bezier curves if you want an exponent curve, they're different functions. Instead, just plot the function you actually need, with something like http://jsbin.com/nubutodosu/edit?js,output, where you define a Gaussian object:
// Gaussian distribution generator
var Gaussian = function(mean, std) {
this.mean = mean;
this.std = std;
this.a = 1/Math.sqrt(2*Math.PI);
};
Gaussian.prototype = {
addStd: function(v) {
this.std += v;
},
get: function(x) {
var f = this.a / this.std;
var p = -1/2;
var c = (x-this.mean)/this.std;
c *= c;
p *= c;
return f * Math.pow(Math.E, p);
},
generateValues: function(start, end) {
var LUT = [];
var step = (Math.abs(start)+Math.abs(end)) / 100;
for(var i=start; i<end; i+=step) {
LUT.push(this.get(i));
}
return LUT;
}
};
And then you can give that a draw routine so that it can plot itself over the interval that you need:
...
draw: function(ctx) {
var points = this.generateValues(-10,10);
var len = points.length;
ctx.strokeStyle = "black";
ctx.beginPath();
var p0 = points[0];
ctx.moveTo(0, height - (height*p0));
points.forEach(function(p,i) {
if(i===0) {
return;
}
ctx.lineTo(width * i/len, height - (height*p));
p0 = p;
});
ctx.stroke();
}
...
So you build your array of values over the interval, then draw them on the canvas by "connecting the dots".

I managed to resolve it.
To be specific, I was looking out for half-gaussian curve.
I managed to figure out that guassian function has a very special property with respect to bezier curves. It might be elementary, but I could not find it on google. So this might be informative finding.
If each control points of the cubic bezier curve reside on a "line parallel to X axis and passing through start point/end point", the resulting curve will be of a half guassian shape.
e.g.
On top of this finding, I have written the following code:
<canvas id="thisCan" width="0px" height="0px">
Your browser does not support the HTML5 canvas tag.
</canvas>
<script>
(function() {
var
// Obtain a reference to the canvas element
// using its id.
htmlCanvas = document.getElementById('thisCan'),
// Obtain a graphics context on the
// canvas element for drawing.
ctx = htmlCanvas.getContext('2d');
// Start listening to resize events and
// draw canvas.
initialize();
function initialize()
{
// Register an event listener to
// call the resizeCanvas() function each time
// the window is resized.
window.addEventListener('resize', resizeCanvas, false);
// Draw canvas border for the first time.
resizeCanvas();
}
// Display custom canvas.
// In this case it's a blue, 5 pixel border that
// resizes along with the browser window.
function redraw(width, height)
{
var start = width/2;
var margin = width * 0.01;
var height = (width / 3) - (margin * 4);
var end_step = width/4
ctx.beginPath();
ctx.moveTo(start - margin, 0);
ctx.bezierCurveTo((start - height/3), 0 , (start - height/3), height , end_step*1, height);
ctx.moveTo(start + margin, 0);
ctx.bezierCurveTo((start + height/3), 0 , (start + height/3), height , end_step*3, height);
ctx.moveTo(start - margin, 0);
ctx.bezierCurveTo((start - height*0.33), 0 , (start - height*0.16), height , end_step*1.5, height);
ctx.moveTo(start + margin, 0);
ctx.bezierCurveTo((start + height*0.33), 0 , (start + height*0.16), height , end_step*2.5, height);
ctx.moveTo(start, 0);
ctx.bezierCurveTo((start ), 0 , (start ), height , end_step*2, height);
ctx.stroke();
}
// Runs each time the DOM window resize event fires.
// Resets the canvas dimensions to match window,
// then draws the new borders accordingly.
function resizeCanvas()
{
var width = 0;
var height = 0;
var contentElement = document.getElementsByClassName("content-box post")[0]
width = htmlCanvas.width = (contentElement.clientWidth * 0.85)
height = htmlCanvas.height = (contentElement.offsetWidth*0.85 * 0.33);
redraw(width, height);
}
})();
</script>
and the output:

Related

TypeError: Cannot Read Property of Undefined Even Though I Defined It

I'm a beginner to JavaScript. I've looked through a few questions on here regarding "TypeError: ... undefined even though the property is defined" but their examples are either too dense for me to grasp or they realize they've incorrectly assigned something downstream. Here I think my example is quite simple, and I don't think I've incorrectly assigned anything.
I've defined position in my class MoveableObjects and when I call player = new MoveableObject(position, other params ...) I don't get any errors.
When I change this to player = new Sprite(position, other params ...) where Sprite extends MoveableObjects and then try to call a function inside Sprite it tells me position.x is undefined. I put console.log(player) immediately after the declaration and it does in fact show position as undefined, but I don't understand why it does since I've clearly defined it.
Here is the constructor of MoveableObjects
class MoveableObject {
constructor({ colorRGB, width, height, position, velocity}) {
this.width = width;
this.height = height;
this.position = position;
this.velocity = velocity;
this.colorRGB = colorRGB;
// If the side of the object is touching or beyond the side of the canvas it is flagged as bounded.
this.inUpLimit = false;
this.inDownLimit = false;
this.inLeftLimit = false;
this.inRightLimit = false;
// Translate the velocity into directional words
this.movingLeft = false;
this.movingRight = false;
this.movingUp = false;
this.movingDown = false;
}
Here is the constructor of Sprite and the draw() function it uses.
// Sprite represents moveable objects with an animated image.
class Sprite extends MoveableObject {
constructor({ imageSrc, numRows = 1, numCols = 1, spacing = 0, margin = 0, animationSpeed = 10, position, velocity}) {
super(position, velocity)
this.image = new Image() // Image can be a single image or a spritesheet.
this.image.src = imageSrc
this.numRows = numRows // # animation sequences in the spritesheet
this.numCols = numCols // # frames in an animation sequence
this.spacing = spacing // # pixels between each frame
this.margin = margin // # pixels between image border and sprite border. Vertical and horizontal margins should be equal.
this.animation = new Object()
this.animation.speed = animationSpeed // # times draw function is called before the next frame in the animation sequence is called.
this.animation.counter = 0 // # times the draw function has been called
this.animation.enabled = false // Whether or not the animation sequence is currently running
this.animation.row = 1; // The row of the current frame being drawn
this.animation.col = 1; // The column of the current frame being drawn
this.animation.firstRow = 1; // The row of the frame to initialize the animation loop on
this.animation.firstCol = 1; // The column of the frame to initialize the animation loop on
this.animation.lastRow = 1; // The row of the frame to restart the animation loop on
this.animation.lastCol = 1; // The column of the frame to restart the animation loop on
this.frame = new Object();
this.frame.width = 0; // Init image.onload
this.frame.height = 0; // Init image.onload
this.image.onload = () => { // Calculates width and height of animation frame based on numRows, numColumns, spacing, and margin
let imageWidth = this.image.width;
let spriteSheetWidth = imageWidth - (2 * this.margin);
let widthNoSpacing = spriteSheetWidth - this.spacing * (this.numCols - 1);
let frameWidth = widthNoSpacing / this.numCols;
let imageHeight = this.image.height;
let spriteSheetHeight = imageHeight - (2 * this.margin);
let heightNoSpacing = spriteSheetHeight - this.spacing * (this.numRows - 1);
let frameHeight = heightNoSpacing / numRows;
this.frame.width = frameWidth;
this.frame.height = frameHeight;
}
}
draw() {
// Draw the frame at the current row and column.
context.drawImage(
this.image, // the entire image being loaded
this.animation.col * this.frame.width, // sx, x coordinate to begin crop
this.animation.row * this.frame.height, // sy, y coordinate to begin crop
this.frame.width, // swidth, width of cropped image
this.frame.height, // sheight, height of cropped image
this.position.x, // x coordinate where to place image on canvas
this.position.y, // y coordinate where to place image on canvas
this.frame.width, // the width to stretch/shrink it to
this.frame.height // the height to stretch/shrink it to
)
Here I create a new MoveableObject with position defined and then console.log() it. It shows position to be what I set it to. I copy/paste that declaration and change "new MoveableObject" to "new Sprite" (with a new variable name of course) and print that, and it shows position is undefined:
// Initializers for main()
const gravity = 0.8;
const player = new Sprite({
imageSrc: './lib/img/template.png',
numRows: 21,
numCols: 4,
spacing: 0,
margin: 0,
position: {
x: 0,
y: 0
},
velocity: {
x: 0,
y: 0
}
})
console.log(player)
const enemy = new MoveableObject({
colorRGB: 'blue',
width: 50,
height: 150,
position: {
x: 150,
y: 50
},
velocity: {
x: 0,
y: 0
}
})
console.log(enemy)
const moveableObjects = [player, enemy];
// The main function of the script updates the game state every frame.
function main() {
// When the game starts the canvas, then player, then enemy are rendered.
context.fillStyle = 'black';
context.fillRect(0, 0, canvas.width, canvas.height);
player.draw();
enemy.draw();
The error is raised on player.draw() and reads "Uncaught TypeError: Cannot read properties of undefined (reading 'x')"
Does anyone understand why this is the case? It seems to me like the error is maybe produced by not properly extending my class or using super() incorrectly to pass attributes, but I haven't found an example that proves that.

Add highlight or marker function to html canvas w/javascript

I'm working on making a nuclear physics webgame. I have created a nuclide chart on a canvas. I manually created every isotope and inputted their corresponding isotope name. A sample is below, as there are over 500 isotopes. I had to do this manually because the "grid" has to be in the form of the normal nuclide chart. The thing I need to do next is create some sort of function that will either highlight an isotope when its clicked on, or put a "marker" on the isotope when clicked on. And unhighlight or move the marker when a different isotope is clicked on. I've been at this for quite some time, but I can't figure it out. Does anyone know how I can achieve this?
// hydrogen
ctx.fillRect(21, 960, 25, 25);
ctx.fillRect(46, 960, 25, 25);
ctx.strokeRect(71, 960, 25, 25);
ctx.strokeRect(96, 960, 25, 25);
ctx.strokeRect(121, 960, 25, 25);
ctx.strokeRect(146, 960, 25, 25);
ctx.strokeRect(171, 960, 25, 25);
//
ctx.fillStyle = 'white';
ctx.fillText("1H", 23, 980, 15, 15);
ctx.fillStyle = 'white';
ctx.fillText("2H", 48, 980, 15, 15);
ctx.fillStyle = 'black';
ctx.fillText("3H", 73, 980, 15, 15);
ctx.fillStyle = 'black';
ctx.fillText("4H", 98, 980, 15, 15);
ctx.fillStyle = 'black';
ctx.fillText("5H", 123, 980, 15, 15);
ctx.fillStyle = 'black';
ctx.fillText("6H", 148, 980, 15, 15);
ctx.fillStyle = 'black';
ctx.fillText("7H", 173, 980, 15, 15);
Down here I've created a demo of how you would do such a thing like creating a hitbox. The hitbox will be represented by a square that has a random position on the canvas when the script runs.
In the script we use the mousedown and mouseup event listeners to determine when the user presses and releases the mouse. On mousedown the x and y coordinates of the mouse are calculated relative to the size of the canvas. By calculating it this way you can get the exact pixel on the canvas that has been clicked upon. It then stores this value in a global variable.
You'll need to know the x, y, width and height of the hitbox because you'll want to determine if the x and y of the click is within the square that is the hitbox.
In the demo below the view is re-rendered whenever you click and release the click. The mouseData state changes en will show that in the new render. The render then evaluates for the hitbox if the mouseData.position coordinates are inside of the box and changes the color based on that evaluation.
Then releasing the mouse triggers another re-render changing the clicked state to false and showing the original state of the hitbox.
So this is basically what it is, a square of coordinates which detects if the clicked pixel is inside that square and does something if it is.
const canvas = document.querySelector('#canvas');
const context = canvas.getContext('2d');
const squareSideSize = 50;
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;
// Stores data on the mouseclick.
let mouseData = {
clicked: false,
position: [] // Here we will store the x and y of click as [ x, y ]
}
/**
* Generates random number between min and max value.
* Just for the purpose of drawing the hitbox.
*/
const getRandomInt = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;
/**
* Returns an array with x and y coordinates
* based on the click event.
*/
const getMousePositionOnCanvas = ({ clientX, clientY }) => {
const canvasRect = canvas.getBoundingClientRect();
const x = clientX - canvasRect.left;
const y = clientY - canvasRect.top;
return [ x, y ];
};
/**
* Determines if the x and y from the click is within
* the x, y, width and height of the square.
* Returns true or false.
*/
const isInHitbox = ([ x, y ], square) => (
(x >= square.x && x <= square.x + square.width) &&
(y >= square.y && y <= square.y + square.height)
);
/**
* Create random square.
* This square has an x and y position as well
* as a width and height. The x and y are created
* to enchance the demonstration.
*/
const square = {
x: getRandomInt(0, canvas.width - squareSideSize),
y: getRandomInt(0, canvas.height - squareSideSize),
width: squareSideSize,
height: squareSideSize
};
// Listen for mousedown and update the click position.
canvas.addEventListener('mousedown', event => {
mouseData.clicked = true; // Set clicked state to true.
mouseData.position = getMousePositionOnCanvas(event); // Get the click position relative to the canvas.
render(); // Render with clicked state.
});
// Listen for mousemove and update the position.
canvas.addEventListener('mousemove', event => {
if (mouseData.clicked === true) { // Only act if clicked.
mouseData.position = getMousePositionOnCanvas(event); // Update mouse position.
render(); // Render with updated mouse position.
}
})
// Listen for mouseup and clear the click position.
canvas.addEventListener('mouseup', event => {
mouseData.clicked = false; // Set clicked state to false.
mouseData.position.length = 0; // Reset the positions.
render(); // Render unclicked state.
});
/**
* Render function which clears and draws on the canvas.
* Here we determine what to draw according the data we
* collection from the mouse.
*/
function render() {
requestAnimationFrame(() => {
const { x, y, width, height } = square; // Get all dimensions of the square.
const { clicked, position } = mouseData; // Get data of the mouse.
context.clearRect(0, 0, canvas.width, canvas.height); // Clear the canvas for a clean re-render.
// Default white color.
context.fillStyle = 'white';
// Check if the mouse has clicked and if the
// position is inside the hitbox. Change the color
// if a it is true.
if (clicked === true && isInHitbox(position, square)) {
context.fillStyle = 'green';
}
// Draw the hitbox.
context.fillRect(x, y, width, height);
});
}
// Initial render.
render();
html,
body {
width: 100%;
height: 100%;
margin: 0;
box-sizing: border-box;
}
body {
padding: 10px;
}
canvas {
width: 100%;
height: 100%;
background: #000000;
}
<canvas id="canvas"></canvas>
I created these functions to draw a circle onto my canvas and be able to track mouse position, test for if it's on the circle, and then listen for click down and up to be able to drag it. If there are other draw elements in the project, in this instance for me it's all 520 of my isotopes, you just need to put that code inside of your redraw function, or else everything else disappears after moving the cirlce
var cw=canvas.width;
var ch=canvas.height;
document.body.appendChild(canvas);
// used to calc canvas position relative to window
function reOffset(){
var BB=canvas.getBoundingClientRect();
offsetX=BB.left;
offsetY=BB.top;
}
var offsetX,offsetY;
reOffset();
window.onscroll=function(e){ reOffset(); }
window.onresize=function(e){ reOffset(); }
canvas.onresize=function(e){ reOffset(); }
// save relevant information about shapes drawn on the canvas
var shapes=[];
// define one circle and save it in the shapes[] array
shapes.push( {x:300, y:275, radius:10} );
var isDragging=false;
var startX,startY;
// hold the index of the shape being dragged (if any)
var selectedShapeIndex;
// draw the shapes on the canvas
drawAll();
// listen for mouse events
canvas.onmousedown=handleMouseDown;
canvas.onmousemove=handleMouseMove;
canvas.onmouseup=handleMouseUp;
canvas.onmouseout=handleMouseOut;
// given mouse X & Y (mx & my) and shape object
// return true/false whether mouse is inside the shape
function isMouseInShape(mx,my,shape){
if(shape.radius){
// this is a circle
var dx=mx-shape.x;
var dy=my-shape.y;
// math test to see if mouse is inside circle
if(dx*dx+dy*dy<shape.radius*shape.radius){
// yes, mouse is inside this circle
return(true);
}
}
// the mouse isn't in any of the shapes
return(false);
}
function handleMouseDown(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
// calculate the current mouse position
startX=parseInt(e.clientX-offsetX);
startY=parseInt(e.clientY-offsetY);
// test mouse position against all shapes
// post result if mouse is in a shape
for(var i=0;i<shapes.length;i++){
if(isMouseInShape(startX,startY,shapes[i])){
// the mouse is inside this shape
// select this shape
selectedShapeIndex=i;
// set the isDragging flag
isDragging=true;
// and return (==stop looking for
// further shapes under the mouse)
return;
}
}
}
function handleMouseUp(e){
// return if we're not dragging
if(!isDragging){return;}
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
// the drag is over -- clear the isDragging flag
isDragging=false;
}
function handleMouseOut(e){
// return if we're not dragging
if(!isDragging){return;}
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
// the drag is over -- clear the isDragging flag
isDragging=false;
}
function handleMouseMove(e){
// return if we're not dragging
if(!isDragging){return;}
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
// calculate the current mouse position
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// how far has the mouse dragged from its previous mousemove position?
var dx=mouseX-startX;
var dy=mouseY-startY;
// move the selected shape by the drag distance
var selectedShape=shapes[selectedShapeIndex];
selectedShape.x+=dx;
selectedShape.y+=dy;
// clear the canvas and redraw all shapes
drawAll();
// update the starting drag position (== the current mouse position)
startX=mouseX;
startY=mouseY;
}
// clear the canvas and
// redraw all shapes in their current positions
function drawAll(){
ctx.clearRect(0,0,cw,ch);
for(var i=0;i<shapes.length;i++){
var shape=shapes[i];
if(shape.radius){
// it's a circle
ctx.beginPath();
ctx.arc(shape.x,shape.y,shape.radius,0,Math.PI*2);
ctx.fillStyle=shape.color;
ctx.fill();
}
}

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>

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>

Resizing Canvas Elements with Mouse Events with EaselJS

I'm trying to draw elements to a canvas and then allow the user to resize them by clicking and dragging them. I've implemented this with Kinetic JS library, but my development team is moving over to Easel JS.
Here is the code with the Kinetic library.
This works well enough.
I get about this far in Easel JS, but this code doesn't do anything. The canvas is totally blank. I realize that I'm not sure how to:
call mouse events -- here I'm trying to attach them to the DOM element, as in this tutorial
where to set the resize function -- separate function? called within the tick() function?
grab the mouse coordinates
var canvas;
var stage;
var rect;
var mousePos = new Point();
var update = true;
var rectX = 10;
var rectY = 10;
var rectW = 100;
var rectH = 50;
var rectXOffset = 50;
var rectYOffset = 50;
var newWidth;
var newHeight;
function init() {
// create stage and point it to the canvas:
canvas = document.getElementById("testCanvas");
stage = new Stage(canvas);
// overlay canvas used to draw target and line
canvasWrapper = $("#testCanvas");
// listen for a mouse down event
canvasWrapper.mousedown(onMouseDown);
// listen for mouse up event
canvasWrapper.mouseup(onMouseUp);
// enable touch interactions if supported on the current device:
if (Touch.isSupported()) { Touch.enable(stage); }
// enabled mouse over / out events
stage.enableMouseOver(10);
// start drawing instructions
var rect = new Shape();
rect.graphics
.setStrokeStyle(1)
.beginStroke(Graphics.getRGB(25,25,112,.7))
.drawRect(rectX,rectY,rectW,rectH);
// add rectangle to stage
stage.addChild(rect);
// render stage
stage.update();
// set the tick interval to 24 fps
Tick.setInterval(1000/24);
// listen for tick event
Tick.addListener(window, true);
// pause
Tick.setPaused(false);
}
//called when user clicks on canvas
function onMouseDown(e) {
mousePos.x = e.stageX;
mousePos.y = e.stageY
// check to see if mouse is within offset of rectangle
if(mousePos.x <= (rectW + rectXOffset) || mousePos.y <= (rectH + rectYOffset)) {
// set update to true
update = true;
// unpause tick
Tick.setPaused(false);
}
}
function onMouseUp(e) {
update = false;
Tick.setPaused(true);
}
function tick() {
// this set makes it so the stage only re-renders when an event handler indicates a change has happened.
if (update) {
if(mousePos.x - rectX < 50)
{
newWidth = 50;
} else {
newWidth = mousePos.x - rectX;
}
if(mousePos.y - rectY < 50)
{
newHeight = 50;
} else {
newHeight = mousePos.y -rectY;
}
rectW = newWidth;
rectH = newHeight;
rect.clear();
rect.graphics
.setStrokeStyle(1)
.beginStroke(Graphics.getRGB(65,65,65,.7))
.drawRect(0,0,rectW,rectH);
// add rectangle to stage
stage.addChild(rect);
update = false; // only update once
stage.update();
}
}
I know it shouldn't be this difficult and would appreciate any help.

Categories