I am currently learning canvas and if I wanted to store my shape and create lets say 4 of them but position them at different locations or with different colors how would I do that?
http://jsfiddle.net/bp0bxgbz/50/
var x = 0;
var y = 15;
var speed = 5;
function animate() {
reqAnimFrame = window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame ||
window.oRequestAnimationFrame
;
reqAnimFrame(animate);
x += speed;
if(x <= 0 || x >= 475){
speed = -speed;
}
draw();
}
function draw() {
var canvas = document.getElementById("ex1");
var context = canvas.getContext("2d");
context.clearRect(0, 0, 500, 170);
context.beginPath();
context.moveTo(x,y);
context.lineTo(x + 105,y + 25);
context.lineTo(x+25,y+105);
context.fillStyle="red";
context.fill();
}
animate();
Create 4 objects--one for each triangle.
Each object holds the current x,y position and the current speed for its 1 triangle.
You can use the information inside any 1 object in the draw() function to draw that 1 triangle at its current x,y position.
In the animation function you can use the information inside each of the 4 objects to change the x position of each triangle.
var shapes=[];
shapes.push({x:10,y:10,speed:2});
shapes.push({x:10,y:125,speed:4});
shapes.push({x:10,y:250,speed:6});
shapes.push({x:10,y:375,speed:8});
In the animation loop, iterate through the array and draw each of the 4 objects by feeding them individually into the draw function.
context.clearRect(0, 0, 500, 170);
for(var i=0; i<shapes.length;i++){
var s=shapes[i];
s.x+=s.speed;
if(s.x <= 0 || s.x >= 475){
s.speed*=-1;
}
draw(s);
}
The draw function should take the specified object and draw according to its specified x,y & speed values.
// create canvas & context variables once at the beginning of the script
var canvas = document.getElementById("ex1");
var context = canvas.getContext("2d");
function draw(s) {
context.beginPath();
context.moveTo(s.x,s.y);
context.lineTo(s.x + 105,s.y + 25);
context.lineTo(s.x+25,s.y+105);
context.fillStyle="red";
context.fill();
}
Note: you can create the canvas & context variables once at the beginning of your script. No need to recreate those variables with each call to draw. Also, if all the drawings will be red-filled, then you could set that once at the beginning of the script, too.
Example code and a Demo:
var context=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var shapes=[];
shapes.push({x:10,y:10,speed:2,color:'red'});
shapes.push({x:10,y:125,speed:4,color:'green'});
shapes.push({x:10,y:250,speed:6,color:'blue'});
shapes.push({x:10,y:375,speed:8,color:'gold'});
animate();
function animate(){
context.clearRect(0,0,cw,ch);
for(var i=0; i<shapes.length;i++){
var s=shapes[i];
s.x+=s.speed;
if(s.x <= 0 || s.x >= cw){
s.speed*=-1;
}
draw(s);
}
requestAnimationFrame(animate);
}
function draw(s) {
context.beginPath();
context.moveTo(s.x,s.y);
context.lineTo(s.x + 105,s.y + 25);
context.lineTo(s.x+25,s.y+105);
context.fillStyle=s.color
context.fill();
}
body{ background-color: ivory; padding:10px; }
#canvas{border:1px solid red;}
<canvas id="canvas" width=300 height=450></canvas>
Related
I'm having multiple issues.
Everytime I click the animation goes faster. SOLVED #Jorge Fuentes González
Everytime I click the
last animation stops moving SOLVED #Kaiido
I have changed about everything I could think of around and still the same issue. Any help would be appreciated. Thanks!
function drawFrame(frameX, frameY, canvasX, canvasY) {
ctx.drawImage(img,
frameX * width, frameY * height,
width, height,
x_click, y_click,
scaledWidth, scaledHeight);
}
// Number of frames in animation
var cycleLoop = [3, 2, 1, 0, 7, 6, 5];
// Position of sprite in sheet
var currentLoopIndex = 0;
var frameCount = 0;
function step() {
frameCount++;
if (frameCount < 30) {
window.requestAnimationFrame(step);
return;
}
frameCount = 0;
// ctx.clearRect(0, 0, canvas.width, canvas.height);
drawFrame(cycleLoop[currentLoopIndex++], 0, 0, 0);
// Starts animation over
if (currentLoopIndex >= cycleLoop.length) {
// If you want to loop back in oposite direction after full animation
cycleLoop.reverse();
// Reseting position of which sprite to use
currentLoopIndex = 0;
}
window.requestAnimationFrame(step);
}
canvas.addEventListener("mousedown", getPosition, false);
function getPosition(event) {
x_click = event.x;
y_click = event.y;
x_click -= canvas.offsetLeft * 10;
y_click -= canvas.offsetTop * 10;
step();
}
==============================
JS Fiddle:
https://jsfiddle.net/HYUTS/q4fazt6L/9/
=======================================
Each time you click, you call step();, which will call window.requestAnimationFrame(step);, which will call step() the next animation frame. I don't see any stop point so the loop will be called forever.
So, when you call step() the first time, step() will be called continuously for ever, and if you click again, another step() "line" will be called a second time which will call window.requestAnimationFrame(step); for ever again, so now you will have two "lines" calling step(). That's why the animation goes faster, because on each animation frame step() will be called twice, doubling the calculations.
What you have to do is to check if the animation is already running (with a flag) and do not run it again, or to window.cancelAnimationFrame(ID) before starting the step() loop again. Note that on each click you must restart the variables that control the animation, like frameCount and currentLoopIndex
function drawFrame(frameX, frameY, canvasX, canvasY) {
ctx.drawImage(img,
frameX * width, frameY * height,
width, height,
x_click, y_click,
scaledWidth, scaledHeight);
}
// Number of frames in animation
var cycleLoop = [3, 2, 1, 0, 7, 6, 5];
// Position of sprite in sheet
var currentLoopIndex = 0;
var frameCount = 0;
var animationid = null;
function step() {
frameCount++;
if (frameCount < 30) {
animationid = window.requestAnimationFrame(step);
return;
}
frameCount = 0;
// ctx.clearRect(0, 0, canvas.width, canvas.height);
drawFrame(cycleLoop[currentLoopIndex++], 0, 0, 0);
// Starts animation over
if (currentLoopIndex >= cycleLoop.length) {
// If you want to loop back in oposite direction after full animation
cycleLoop.reverse();
// Reseting position of which sprite to use
currentLoopIndex = 0;
}
animationid = window.requestAnimationFrame(step);
}
canvas.addEventListener("mousedown", getPosition, false);
function getPosition(event) {
x_click = event.x;
y_click = event.y;
x_click -= canvas.offsetLeft * 10;
y_click -= canvas.offsetTop * 10;
frameCount = currentLoopIndex = 0;
window.cancelAnimationFrame(animationid);
step();
}
First step in your situation, is to create different objects for every animatables, so they can be drawn and updated independently.
After, you will have to split your logic in several parts.
A basic setup is to have one main loop that runs constantly in the background, and which will call all higher level objects update function, then all the drawing functions.
It's in these higher level methods that you will do the checks as to whether they should actually be discarded or not. The main loop doesn't have to take care of it.
In the example below, I created a class for your animatable objects. These objects will now have their own status, and will be able to update as they wish independently of others.
With this setup, adding a new Object in the scene is just a matter of pushing it in an Array.
// Our Animatable class (ES5 style...)
// Each object as its own frameCount and its own loopIndex
function Animatable(x, y) {
this.x = x;
this.y = y;
this.frameCount = 0;
this.loopIndex = 0;
this.cycleLoop = [3, 2, 1, 0, 7, 6, 5];
}
Animatable.prototype = {
update: function() {
this.frameCount++;
if (this.frameCount < 30) {
return;
}
this.frameCount = 0;
this.loopIndex++
if (this.loopIndex >= this.cycleLoop.length) {
// If you want to loop back in oposite direction after full animation
this.cycleLoop.reverse();
// Reseting position of which sprite to use
this.loopIndex = 0;
}
},
draw: function() {
// check the image is loaded
if (!img.naturalWidth) return;
var frameX = this.cycleLoop[this.loopIndex];
ctx.drawImage(img,
frameX * width, 0,
width, height,
this.x - scaledWidth/2, this.y - scaledHeight/2,
scaledWidth, scaledHeight);
}
};
// the main anim loop, independent
function startAnimLoop() {
animloop();
function animloop() {
requestAnimationFrame(animloop);
// updates
animatables.forEach(update);
// drawings
ctx.clearRect(0,0,canvas.width, canvas.height);
animatables.forEach(draw);
}
function update(animatable) {
animatable.update();
}
function draw(animatable) {
animatable.draw();
}
}
// one image for all
var img = new Image();
img.src = 'https://imgur.com/u2hjhwq.png';
img.onload = startAnimLoop;
// here we will hold all our objects
var animatables = [new Animatable(50, 50)]; // start with a single one
var canvas = document.querySelector('canvas');
var ctx = canvas.getContext('2d');
// some constant from OP's fiddle
var scale = 1.5;
var width = 100; // Bigger numbers push left <-, smaller right ->
var height = 100;
var scaledWidth = scale * width;
var scaledHeight = scale * height;
canvas.onclick = function(evt) {
var rect = canvas.getBoundingClientRect();
var x = evt.clientX - rect.left;
var y = evt.clientY - rect.top;
// we simply create a new object ;-)
animatables.push(new Animatable(x, y));
};
canvas{border:1px solid}
<canvas id="canvas" width="500" height="500"></canvas>
window.requestAnimationFrame is still running when you click again, and when you click you add another tick per frame to your animation, doubling your speed, as step() is called two times each frame now. You should cancel the previous animation frame when clicking again, using window.cancelAnimationFrame()
https://developer.mozilla.org/en-US/docs/Web/API/Window/cancelAnimationFrame
Like this:
...
var animationID;
//in step() save the id in every call
function step() {
...
animationID = window.requestAnimationFrame(step);
...
}
//In getPosition cancel the current animation
function.getPosition(event) {
...
window.cancelAnimationFrame(animationId);
...
}
And if you want multiple animations running, create an object for each and make the function step() their property, then run window.requestAnimationFrame(this.step) inside of step(). You'd also have to save every variable needed for the animation like currentLoopIndex as part of the object.
I am trying to create a pannable image viewer which also allows magnification. If the zoom factor or the image size is such that the image no longer paints over the entire canvas then I wish to have the area of the canvas which does not contain the image painted with a specified background color.
My current implementation allows for zooming and panning but with the unwanted effect that the image leaves a tiled trail after it during a pan operation (much like the cards in windows Solitaire when you win a game). How do I clean up my canvas such that the image does not leave a trail and my background rectangle properly renders in my canvas?
To recreate the unwanted effect set magnification to some level at which you see the dark gray background show and then pan the image with the mouse (mouse down and drag).
Code snippet added below and Plnkr link for those who wish to muck about there.
http://plnkr.co/edit/Cl4T4d13AgPpaDFzhsq1
<!DOCTYPE html>
<html>
<head>
<style>
canvas{
border:solid 5px #333;
}
</style>
</head>
<body>
<button onclick="changeScale(0.10)">+</button>
<button onclick="changeScale(-0.10)">-</button>
<div id="container">
<canvas width="700" height="500" id ="canvas1"></canvas>
</div>
<script>
var canvas = document.getElementById('canvas1');
var context = canvas.getContext("2d");
var imageDimensions ={width:0,height:0};
var photo = new Image();
var isDown = false;
var startCoords = [];
var last = [0, 0];
var windowWidth = canvas.width;
var windowHeight = canvas.height;
var scale=1;
photo.addEventListener('load', eventPhotoLoaded , false);
photo.src = "http://www.html5rocks.com/static/images/cors_server_flowchart.png";
function eventPhotoLoaded(e) {
imageDimensions.width = photo.width;
imageDimensions.height = photo.height;
drawScreen();
}
function changeScale(delta){
scale += delta;
drawScreen();
}
function drawScreen(){
context.fillRect(0,0, windowWidth, windowHeight);
context.fillStyle="#333333";
context.drawImage(photo,0,0,imageDimensions.width*scale,imageDimensions.height*scale);
}
canvas.onmousedown = function(e) {
isDown = true;
startCoords = [
e.offsetX - last[0],
e.offsetY - last[1]
];
};
canvas.onmouseup = function(e) {
isDown = false;
last = [
e.offsetX - startCoords[0], // set last coordinates
e.offsetY - startCoords[1]
];
};
canvas.onmousemove = function(e)
{
if(!isDown) return;
var x = e.offsetX;
var y = e.offsetY;
context.setTransform(1, 0, 0, 1,
x - startCoords[0], y - startCoords[1]);
drawScreen();
}
</script>
</body>
</html>
You need to reset the transform.
Add context.setTransform(1,0,0,1,0,0); just before you clear the canvas and that will fix your problem. It sets the current transform to the default value. Then befor the image is draw set the transform for the image.
UPDATE:
When interacting with user input such as mouse or touch events it should be handled independently of rendering. The rendering will fire only once per frame and make visual changes for any mouse changes that happened during the previous refresh interval. No rendering is done if not needed.
Dont use save and restore if you don't need to.
var canvas = document.getElementById('canvas1');
var ctx = canvas.getContext("2d");
var photo = new Image();
var mouse = {}
mouse.lastY = mouse.lastX = mouse.y = mouse.x = 0;
mouse.down = false;
var changed = true;
var scale = 1;
var imageX = 0;
var imageY = 0;
photo.src = "http://www.html5rocks.com/static/images/cors_server_flowchart.png";
function changeScale(delta){
scale += delta;
changed = true;
}
// Turns mouse button of when moving out to prevent mouse button locking if you have other mouse event handlers.
function mouseEvents(event){ // do it all in one function
if(event.type === "mouseup" || event.type === "mouseout"){
mouse.down = false;
changed = true;
}else
if(event.type === "mousedown"){
mouse.down = true;
}
mouse.x = event.offsetX;
mouse.y = event.offsetY;
if(mouse.down) {
changed = true;
}
}
canvas.addEventListener("mousemove",mouseEvents);
canvas.addEventListener("mouseup",mouseEvents);
canvas.addEventListener("mouseout",mouseEvents);
canvas.addEventListener("mousedown",mouseEvents);
function update(){
requestAnimationFrame(update);
if(photo.complete && changed){
ctx.setTransform(1,0,0,1,0,0);
ctx.fillStyle="#333";
ctx.fillRect(0,0, canvas.width, canvas.height);
if(mouse.down){
imageX += mouse.x - mouse.lastX;
imageY += mouse.y - mouse.lastY;
}
ctx.setTransform(scale, 0, 0, scale, imageX,imageY);
ctx.drawImage(photo,0,0);
changed = false;
}
mouse.lastX = mouse.x
mouse.lastY = mouse.y
}
requestAnimationFrame(update);
canvas{
border:solid 5px #333;
}
<button onclick="changeScale(0.10)">+</button><button onclick="changeScale(-0.10)">-</button>
<canvas width="700" height="500" id ="canvas1"></canvas>
Nice Code ;)
You are seeing the 'tiled' effect in your demonstration because you are painting the scaled image to the canvas on top of itself each time the drawScreen() function is called while dragging. You can rectify this in two simple steps.
First, you need to clear the canvas between calls to drawScreen() and second, you need to use the canvas context.save() and context.restore() methods to cleanly reset the canvas transform matrix between calls to drawScreen().
Given your code as is stands:
Create a function to clear the canvas. e.g.
function clearCanvas() {
context.clearRect(0, 0, canvas.width, canvas.height);
}
In the canavs.onmousemove() function, call clearCanvas() and invoke context.save() before redefining the transform matrix...
canvas.onmousemove = function(e) {
if(!isDown) return;
var x = e.offsetX;
var y = e.offsetY;
/* !!! */
clearCanvas();
context.save();
context.setTransform(
1, 0, 0, 1,
x - startCoords[0], y - startCoords[1]
);
drawScreen();
}
... then conditionally invoke context.restore() at the end of drawScreen() ...
function drawScreen() {
context.fillRect(0,0, windowWidth, windowHeight);
context.fillStyle="#333333";
context.drawImage(photo,0,0,imageDimensions.width*scale,imageDimensions.height*scale);
/* !!! */
if (isDown) context.restore();
}
Additionally, you may want to call clearCanvas() before rescaling the image, and the canvas background could be styled with CSS rather than .fillRect() (in drawScreen()) - which could give a performance gain on low spec devices.
Edited in light of comments from Blindman67 below
See Also
Canvas.context.save : https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/save
Canvas.context.restore : https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/restore
requestAnimationFrame : https://developer.mozilla.org/en-US/docs/Web/API/Window/requestAnimationFrame
Paul Irish, requestAnimationFrame polyfill : http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
Call context.save to save the transformation matrix before you call context.fillRect.
Then whenever you need to draw your image, call context.restore to restore the matrix.
For example:
function drawScreen(){
context.save();
context.fillStyle="#333333";
context.fillRect(0,0, windowWidth, windowHeight);
context.restore();
context.drawImage(photo,0,0,imageDimensions.width*scale,imageDimensions.height*scale);
}
Also, to further optimize, you only need to set fillStyle once until you change the size of canvas.
Here is the fiddle.
Small rectangle will be created to simulate a bullet when the spacebar(keycode 32) is pressed. I encountered some problems: How to move them yo the top (decrease the y coordinate)?
Can anyone help me? Thx!
window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");
var cw = canvas.width;
var ch = canvas.height;
var ps = false;
init();
function init(){
context.rect((cw-5)/2, ch-5, 5, 5);
context.fill();
update();
}
function update(){
if(ps){
playerShoot();
}
requestAnimationFrame(update);
}
function playerShoot(){
var b = new bullet(2);
}
function bullet(speed){
this.speed = speed;
speed++;
context.ellipse((cw-1)/2, ch-10-speed, 1, 3, 0, 0, Math.PI*2);
context.fill();
}
document.addEventListener("keydown", function(e){
switch(e.keyCode){
case 32:
ps = true;
break;
};
});
document.addEventListener("keyup", function(e){
switch(e.keyCode){
case 32:
ps = false;
break;
};
});
I've explained a lot of the code in the comments in the code itself.
A couple of other points:
Some browsers (including mine, i.e. Firefox v44.0.2) don't draw ellipses. So I've made your bullet another rectangle.
I used fillRect instead of rect just because I know that better.
I redrew the bullet by drawing over the old one with the opaque background color. However, you could also clear the rectangle around the previous bullet if you wanted.
You incremented speed in your example. That's probably not what you want from a conceptual point of view, even if you had gotten the visual results that you want. I suspect you want your bullets to move at a constant speed. Therefore, the speed variable should be constant, i.e. not change. Rather, you should use the speed constant to regularly change the position of the bullet. I changed bulletY, which is the vertical position of the bullet.
For simplicity, I've only allowed one bullet on the screen at a time.
I've limited the code to running 500 cycles. That's mostly to just not annoy Stack Overflow users who try the code...they don't want an infinite loop happening.
window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");
var cw = canvas.width;
var ch = canvas.height;
var ps = false;
// some new variables
var bulletShowing = false; // is a bullet currently showing?
var bulletY; // the vertical position of the bullet
var speed = 8; // the bullet speed
var time = 500; // the time remaining
init();
function init() {
// draw background
context.fillStyle = "yellow";
context.fillRect(0, 0, cw, ch);
// draw gun
context.fillStyle = "black";
context.fillRect((cw - 5) / 2, ch - 5, 5, 5);
// update the scene
update();
}
function update() {
if (ps) {
playerShoot();
}
// if a bullet is supposed to be showing then, well, show it
if (bulletShowing) {
// redraw the bullet (erase the old, draw the new)
drawBullet();
// if the bullet has gone off-screen, allow a new shot
if (bulletY < -5) {
bulletShowing = false;
}
}
// give a visual indicator of time remaining
document.querySelector("div").innerHTML = "Time: " + time;
// decrement the time
time -= 1;
// if there is still time remaining, do it all again
if (time >= 0) {
requestAnimationFrame(update);
}
}
function playerShoot() {
// indicate a bullet will now be showing
bulletShowing = true;
// start the bullet out near the gun
bulletY = ch - 10;
}
function drawBullet() {
// erase the old bullet by drawing over it with the background color
// this rectangle is slightly larger than the bullet itself
// to ensure the entire old bullet is drawn over
context.fillStyle = "yellow";
context.fillRect((cw - 1) / 2 - 2, bulletY - 1, 5, 7);
// move the bullet position
bulletY -= speed;
// draw the new bullet
context.fillStyle = "black";
context.fillRect((cw - 1) / 2 - 1, bulletY, 3, 5);
}
document.addEventListener("keydown", function(e) {
switch (e.keyCode) {
case 32:
// only allow one bullet on the screen at a time
// (for the sake of coding simplicity)
if (!bulletShowing) {
ps = true;
}
break;
};
});
document.addEventListener("keyup", function(e) {
switch (e.keyCode) {
case 32:
ps = false;
break;
};
});
#myCanvas {
position: absolute;
top: 0;
left: 50%;
transform: translate(-50%, 5%);
background-color: #cccccc;
z-index: -1;
}
<p>Click on the canvas, then use the space bar to fire bullets one at a time.</p>
<div></div>
<canvas id="myCanvas" width=300 height=150></canvas>
Hello i create program like a paint on HTML5 canvas. I have problem i need create few tools drawing and zoom. I don't have idea how to create zoom without delay. Drawing example: http://jsfiddle.net/x5rrvcr0/
How i can zooming my drawings?
drawing code:
<style>
canvas {
background-color: #CECECE;
}
html, body {
background-color: #FFFFFF;
}
</style>
<script>
$(document).ready(function () {
var paintCanvas = document.getElementById("paintCanvas");
var paintCtx = paintCanvas.getContext("2d");
var size = 500;
paintCanvas.width = size;
paintCanvas.height = size;
var draw = false;
var prevMouseX = 0;
var prevMouseY = 0;
function getMousePos(canvas, evt) {
evt = evt.originalEvent || window.event || evt;
var rect = canvas.getBoundingClientRect();
if (evt.clientX !== undefined && evt.clientY !== undefined) {
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
}
}
$("#paintCanvas").on("mousedown", function(e) {
draw = true;
var coords = getMousePos(paintCanvas);
prevMouseX = coords.x;
prevMouseY = coords.y;
});
$("#paintCanvas").on("mousemove", function(e) {
if(draw) {
var coords = getMousePos(paintCanvas, e);
paintCtx.beginPath();
paintCtx.lineWidth = 10;
paintCtx.strokeStyle = "#000000";
paintCtx.moveTo(prevMouseX, prevMouseY);
paintCtx.lineTo(coords.x, coords.y);
paintCtx.stroke();
prevMouseX = coords.x;
prevMouseY = coords.y;
}
});
$("#paintCanvas").on("mouseup", function(e) {
draw = false;
});
});
</script>
<body>
<canvas id="paintCanvas"></canvas>
</body>
If you want to keep the pixelated effect in the zoom, you need to draw on a temp canvas, then only after copy that temp canvas to the main screen.
You no longer need to zoom in the temp canvas, just draw on 1:1 scale always. When copying to the view canvas, then you apply the zoom (and maybe translate) that you want.
Keep in mind that drawings are anti-aliased, so you when zooming you will see some shades of grey when drawing in black, for instance.
I kept the recording code of #FurqanZafar since it is a good idea to record things in case you want to perform undo : in that case just delete the last record entry and redraw everything.
http://jsfiddle.net/gamealchemist/x5rrvcr0/4/
function updatePaintCanvas() {
paintContext.clearRect(0, 0, paintContext.canvas.width, paintContext.canvas.height);
paintContext.save();
paintContext.translate(cvSize * 0.5, cvSize * 0.5);
paintContext.scale(scale, scale);
paintContext.drawImage(drawCanvas, -cvSize * 0.5, -cvSize * 0.5);
paintContext.restore();
}
Heres the updated fiddle: http://jsfiddle.net/x5rrvcr0/2/ with basic zooming functionality
If you draw multiple paths on mouse move then your sketch will appear broken or disconnected, instead you should only stroke a single path until "mouseup" event.
You can then store these paths in an array and later redraw them at different zoom levels:
function zoom(context, paths, styles, scale) {
context.clearRect(0, 0, context.canvas.width, context.canvas.height);
context.save();
applyStyles(context, styles);
scaleFromCenter(context, scale);
for (var i = 0; i < paths.length; i++) {
context.beginPath();
context.moveTo(paths[i][0].x, paths[i][0].y);
for (var j = 1; j < paths[i].length; j++)
context.lineTo(paths[i][j].x, paths[i][j].y);
context.stroke();
}
context.restore();
};
My need is to draw a ECG graph on canvas for socket data per every data iteration.
I tried to look into several graph plugins which use canvas to plot graphs, tried http://www.flotcharts.org/ but didn't succeed.
I Tried to plot graph using below basic html5 canvas drawline with sample data.
var fps = 60;
var n = 1;
drawWave();
function drawWave() {
setTimeout(function() {
requestAnimationFrame(drawWave2);
ctx.lineWidth = "2";
ctx.strokeStyle = 'green';
// Drawing code goes here
n += 1;
if (n >= data.length) {
n = 1;
}
ctx.beginPath();
ctx.moveTo(n - 1, data[n - 1] * 2);
ctx.lineTo(n, data[n] * 2);
ctx.stroke();
ctx.clearRect(n + 1, 0, 10, canvas.height);
}, 1000 / fps);
}
But it is not giving me the exact graph view as attached image. I'm not able to understand how to achieve graph like ecg graph. Please help me to get rid of this problem.
The characteristics with an ECG is that is plots the signal horizontally headed by a blank gap. When the end of the right side is reached is returns to left side and overdraw the existing graph.
DEMO
Setup
var ctx = demo.getContext('2d'),
w = demo.width,
h = demo.height,
/// plot x and y, old plot x and y, speed and scan bar width
speed = 3,
px = 0, opx = 0,
py = h * 0.8, opy = py,
scanBarWidth = 20;
ctx.strokeStyle = '#00bd00';
ctx.lineWidth = 3;
/// for demo we use mouse as signal input source
demo.onmousemove = function(e) {
var r = demo.getBoundingClientRect();
py = e.clientY - r.top;
}
loop();
The main loop:
The loop will plot whatever the signal amplitude is at any moment. You can inject a sinus or some other signal or read from an actual sensor over Web socket etc.
function loop() {
/// move forward at defined speed
px += speed;
/// clear ahead (scan bar)
ctx.clearRect(px,0, scanBarWidth, h);
/// draw line from old plot point to new
ctx.beginPath();
ctx.moveTo(opx, opy);
ctx.lineTo(px, py);
ctx.stroke();
/// update old plot point
opx = px;
opy = py;
/// check if edge is reached and reset position
if (opx > w) {
px = opx = -speed;
}
requestAnimationFrame(loop);
}
To inject a value simply update py (outside loop).
It would be far more helpful, if you included an image of what it does produce, rather than stating that it doesn't do that. Anyhow, it looks like you're only drawing a single line per frame. You need to run a loop with lineTo inside, iterating through all values of n.
Something along the lines of the below except from a sound-synthesizer. Just pay attention to the fact that there's a drawing-loop. In my case, there are often 40,000 or 50,000 samples that need to be drawn on a canvas of only a few hundred pixels wide. It seems like redundant drawing in my case, but doing th intuitive thing, of a single point per pixel results in an inaccurate image. The output of this looks something (88200 samples per 1024 pixels)
function drawFloatArray(samples, canvas)
{
var i, n = samples.length;
var dur = (n / 44100 * 1000)>>0;
canvas.title = 'Duration: ' + dur / 1000.0 + 's';
var width=canvas.width,height=canvas.height;
var ctx = canvas.getContext('2d');
ctx.strokeStyle = 'yellow';
ctx.fillStyle = '#303030';
ctx.fillRect(0,0,width,height);
ctx.moveTo(0,height/2);
ctx.beginPath();
for (i=0; i<n; i++)
{
x = (i*width) / n;
y = (samples[i]*height/2)+height/2;
ctx.lineTo(x, y);
}
ctx.stroke();
ctx.closePath();
}
Since your live data is streaming non-stop, you need a plan to deal with a graph that overflows the canvas.
Here's one solution that pans the canvas to always show only the most recent data.
Demo: http://jsfiddle.net/m1erickson/f5sT4/
Here's starting code illustrating this solution:
<!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 ctx=canvas.getContext("2d");
// capture incoming socket data in an array
var data=[];
// TESTING: fill data with some test values
for(var i=0;i<5000;i++){
data.push(Math.sin(i/10)*70+100);
}
// x is your most recent data-point in data[]
var x=0;
// panAtX is how far the plot will go rightward on the canvas
// until the canvas is panned
var panAtX=250;
var continueAnimation=true;
animate();
function animate(){
if(x>data.length-1){return;}
if(continueAnimation){
requestAnimationFrame(animate);
}
if(x++<panAtX){
ctx.fillRect(x,data[x],1,1);
}else{
ctx.clearRect(0,0,canvas.width,canvas.height);
// plot data[] from x-PanAtX to x
for(var xx=0;xx<panAtX;xx++){
var y=data[x-panAtX+xx];
ctx.fillRect(xx,y,1,1)
}
}
}
$("#stop").click(function(){continueAnimation=false;});
}); // end $(function(){});
</script>
</head>
<body>
<button id="stop">Stop</button><br>
<canvas id="canvas" width=300 height=300></canvas>
</body>
</html>
I think this web component is both prettier and easier to use. It using canvas as a backend of draw. If you going use it all you need to do is call bang() on every appearing beat
document.body.innerHTML += '<ecg-line></ecg-line>';
ecgLine((bang) => setInterval(() => bang(), 1000));
Do you mean something like this?
var canvas = document.getElementById("dm_graphs");
var ctx = canvas.getContext("2d");
ls = 0;
d = 7; // sesitivity
function updateFancyGraphs() {
var gh = canvas.height;
var gh2 = gh / 2;
ctx.drawImage(canvas, -1, 0);
ctx.fillRect(graphX, 0, 1, canvas.height);
var size = Math.max(-gh, Math.min((3 * (Math.random() * 10 - 5)) * d, gh));
ctx.beginPath();
ctx.moveTo(graphX - 1, gh2 + ls / 2);
ctx.lineTo(graphX, gh2 + size / 2);
ctx.stroke();
ls = size;
}
function resizeCanvas() {
var w = window.innerWidth || document.body.offsetWidth;
canvas.width = w / 1.5;
canvas.height = window.innerHeight / 1.5;
graphX = canvas.width - 1;
ctx.lineWidth = 1; // 1.75 is nicer looking but loses a lot of information.
ctx.strokeStyle = "Lime";
ctx.fillStyle = "black";
}
window.addEventListener("resize", resizeCanvas);
resizeCanvas();
z = setInterval(() => updateFancyGraphs(), 20)
body {
min-height: 100ev;
}
body,
html,
canvas {
font: 15px sans-serif;
height: 100hv;
width: 100%;
margin: 0;
padding: 0;
background: #113355;
color: white;
overflow: hidden;
}
#dm_status,
footer {
text-align: center;
}
#dm_graphs {
image-rendering: optimizeSpeed;
background: rgba(0, 0, 0, 0.7);
}
<html>
<head>
<title>Zibri's Graph</title>
<meta name="viewport" content="width=device-width">
</head>
<body>
<canvas id="dm_graphs"></canvas>
</body>
</html>