why does canvas object keep blinking during animation? - object
I made an animation in javascript for a house with rising smoke. the smoke is 3 functions for each part of the smoke that flow upwards from the chimney. they are controlled by a slider that toggles the speed at which the smoke exists the chimney. Everything works except when the slider is toggled left to right, the smoke blinks while rising. Could anyone tell me why that is?
Thanks
html:
<!DOCTYPE html>
<html>
<head>
<title>Carrey, Justin, Myshkin, Rost</title>
<meta charset="utf-8"/>
<link rel="stylesheet" href="style.css"/>
</head>
<body>
<canvas id="canvas" width="500" height="500">Get a new Browser!</canvas>
<script src="script.js" ></script>
<form>
<input type="range" min="10" max="250" value="100" id="speedCont"/>
<p>
Rostislav Myshkin A00787633 rmyshkin#my.bcit.ca
<br />
Completed:3-D house, smoke, animation for smoke, slider for speed.
<br />
Challanges: animating the smoke.
</p>
</form>
</body>
</html>
javascript:
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
ctx.lineWidth = 4;
ctx.strokeLinecap = 'round';
var a = 1;
var speed = 100;
var posY = 100,
posY2 = 120,
posY3 = 140,
posX = 100,
vx = 5,
vy = 5;
function foundation() {
//grass
ctx.fillStyle = "green";
ctx.strokeStyle = "#000000";
ctx.beginPath();
ctx.moveTo(25, 375);
ctx.lineTo(125, 325);
ctx.lineTo(471, 325);
ctx.lineTo(400, 375);
ctx.lineTo(25, 375);
ctx.fill();
ctx.stroke();
//front face ground
ctx.fillStyle = "#873600";
ctx.strokeStyle = "#000000";
ctx.beginPath();
ctx.moveTo(25, 375); //top left
ctx.lineTo(25, 425); //bottom left
ctx.lineTo(400, 425); //bottom right
ctx.lineTo(400, 375); //top right
ctx.lineTo(25, 375); //top line
ctx.fill();
ctx.stroke();
//east face ground
ctx.fillStyle = "#872000";
ctx.strokeStyle = "#000000";
ctx.beginPath();
ctx.moveTo(475, 325); //top right
ctx.lineTo(475, 375); //bottom right
ctx.lineTo(400, 425); //bottom line
ctx.lineTo(400, 375); //top left
ctx.lineTo(475, 325); //top right
ctx.fill();
ctx.stroke();
}
function house() {
//front face
ctx.fillStyle = "#2980B9";
ctx.strokeStyle = "#000000";
ctx.beginPath();
ctx.moveTo(110, 365);
ctx.lineTo(110, 200);
ctx.lineTo(375, 200);
ctx.lineTo(375, 365);
ctx.lineTo(110, 365);
ctx.fill();
ctx.stroke();
//east face
ctx.fillStyle = "#1760B4";
ctx.strokeStyle = "#000000";
ctx.beginPath();
ctx.moveTo(375, 200); //lower left
ctx.lineTo(415, 180); //
ctx.lineTo(415, 340);
ctx.lineTo(375, 365);
ctx.lineTo(375, 200);
ctx.fill();
ctx.stroke();
//roof front face
ctx.fillStyle = "#B41717";
ctx.strokeStyle = "#000000";
ctx.beginPath();
ctx.moveTo(95, 210);
ctx.lineTo(160, 140);
ctx.lineTo(395, 140);
ctx.lineTo(365, 210);
ctx.lineTo(365, 210);
ctx.lineTo(95, 210);
ctx.fill();
ctx.stroke();
//roof east face
ctx.fillStyle = "darkred";
ctx.strokeStyle = "#000000";
ctx.beginPath();
ctx.moveTo(365, 210);
ctx.lineTo(425, 190);
ctx.lineTo(395, 140);
ctx.lineTo(365, 210);
ctx.fill();
ctx.stroke();
//door
ctx.fillStyle = "darkred";
ctx.strokeStyle = "#000000";
ctx.beginPath();
ctx.moveTo(300, 365);
ctx.lineTo(300, 295);
ctx.lineTo(250, 295);
ctx.lineTo(250, 365);
ctx.lineTo(300, 365);
ctx.fill();
ctx.stroke();
//doorknob
ctx.fillStyle = "yellow";
ctx.strokeStyle = "#000000";
ctx.beginPath();
ctx.arc(290, 335, 5, 0, 2 * Math.PI, false);
ctx.fill();
ctx.stroke();
//walkway
ctx.fillStyle = "gray";
ctx.strokeStyle = "#000000";
ctx.beginPath();
ctx.moveTo(250, 365); //left point
ctx.lineTo(240, 375); //left side
ctx.lineTo(290, 375);
ctx.lineTo(300, 365);
ctx.fill();
ctx.stroke();
//window living room
ctx.fillStyle = "blue";
ctx.strokeStyle = "#000000";
ctx.beginPath();
ctx.moveTo(143, 347);
ctx.lineTo(143, 295);
ctx.lineTo(212, 295);
ctx.lineTo(212, 347);
ctx.lineTo(143, 347);
ctx.fill();
ctx.stroke();
//window top left
ctx.fillStyle = "blue";
ctx.strokeStyle = "#000000";
ctx.beginPath();
ctx.moveTo(143, 275);
ctx.lineTo(143, 225);
ctx.lineTo(212, 225);
ctx.lineTo(212, 275);
ctx.lineTo(143, 275);
ctx.fill();
ctx.stroke();
//window top right
ctx.fillStyle = "blue";
ctx.strokeStyle = "#000000";
ctx.beginPath();
ctx.moveTo(263, 275);
ctx.lineTo(263, 225);
ctx.lineTo(332, 225);
ctx.lineTo(332, 275);
ctx.lineTo(263, 275);
ctx.fill();
ctx.stroke();
//chimney front
ctx.fillStyle = "black";
ctx.strokeStyle = "#000000";
ctx.beginPath();
ctx.moveTo(170, 130); //top left
ctx.lineTo(170, 180); //left side line
ctx.lineTo(200, 180); //bottom line
ctx.lineTo(200, 130); //right side line
ctx.lineTo(170, 130); //top side line
ctx.fill();
ctx.stroke();
//chimney east
ctx.fillStyle = "black";
ctx.strokeStyle = "#000000";
ctx.beginPath();
ctx.moveTo(200, 130); //top left
ctx.lineTo(215, 123); //top side line
ctx.lineTo(215, 170); //right side line
ctx.lineTo(200, 180); //
ctx.fill();
ctx.stroke();
//chimney top
ctx.fillStyle = "black";
ctx.strokeStyle = "#000000";
ctx.beginPath();
ctx.moveTo(170, 130); //top left
ctx.lineTo(185, 122); //left side
ctx.lineTo(210, 122); //top side
ctx.lineTo(200, 130);
ctx.fill();
ctx.stroke();
}
function smoke1(){
posY += -vy;
posX += vx;
if (posY < -15) posY = 100;
ctx.fillStyle = "aqua";
ctx.fillRect(0,0, 220, 127);
ctx.fillStyle = "rgba(0,0,0,0.5)";
ctx.beginPath();
ctx.arc(200, posY, 15, 0, Math.PI*2, true);
ctx.fill();
}
function smoke2(){
posY2 += -vy;
posX += vx;
if (posY2 < -13) posY2 = 110;
ctx.fillStyle = "rgba(0,0,0,0.5)";
ctx.beginPath();
ctx.arc(185, posY2, 10, 0, Math.PI*2, true);
ctx.fill();
}
function smoke3(){
posY3 += -vy;
posX += vx;
if (posY3 < -13) posY3 = 110;
ctx.fillStyle = "rgba(0,0,0,0.5s)";
ctx.beginPath();
ctx.arc(210, posY3, 6, 0, Math.PI*2, true);
ctx.fill();
}
function animate() {
smoke1();
var speed = document.getElementById('speedCont').value;
window.setTimeout(animate, speed);
}
function animate2() {
smoke2();
var speed = document.getElementById('speedCont').value;
window.setTimeout(animate2, speed);
}
function animate3() {
smoke3();
var speed = document.getElementById('speedCont').value;
window.setTimeout(animate3, speed);
}
/** if (a == 1) {
ctx.clearRect(0, 0, 260, 105);
smoke();
a++;
} else if (a == 2) {
ctx.clearRect(0, 0, 260, 105);
smokeMed();
a++;
} else if (a == 3) {
ctx.clearRect(0, 0, 260, 105);
smokeBig();
a = 1;
} else {
ctx.clearRect(0, 0, 260, 105);
}
window.setTimeout(animate2, speed);
}
**/
window.onload = function all() {
foundation();
house();
animate();
animate2();
animate3();
}
window.addEventListener("load", all, false);
//window.setInterval(animate2, 1000);
//window.setTimeout(animate2, speed);
css:
#canvas {
background-color: aqua;
border: 1px solid black;
margin-bottom: 10px ;
}
body {
background-color: gray;
}
input[type=range] {
-webkit-appearance: none;
border: 3px solid black;
width: 500px;
border-radius: 20px;
}
input[type=range]::-webkit-slider-runnable-track {
width: 500px;
height: 10px;
background: #ddd;
border: none;
border-radius: 20px;
}
input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none;
border: 3px solid black;
height: 30px;
width: 30px;
border-radius: 50%;
background: red;
margin-top: -8px;
}
input[type=range]:focus {
outline: none;
}
input[type=range]:focus::-webkit-slider-runnable-track {
background: #ccc;
}
The flickr has to do with having multiple setTimeout functions. Since you delete the previous state of the smoke on smoke1(), once you change the speed there is going to be a discrepancy. If you use only one setTimeout it should work fine in your particular case. Here a JSFIDDLE as an example.
Backbuffers & requestAnimationFrame
All rendering to the DOM is double buffered. Rendering is always done to the backBuffer, it is not drawn to the screen. Presenting to the screen should be done when the scene is fully rendered.
When a function exits (returns to idle) the DOM assumes that you have completed all the rendering and what to display the results. This is when the backbuffer is moved to where the screen can see it.
If you have only rendered part of the scene and exit the DOM does not know you have not yet finished rendering and will present the backbuffer as is. The screen refreshes at a much slower rate than you can write to the backbuffer. When the DOM presents the backbuffer to the screen the display hardware may be at any point of scanning out the pixels onto the display hardware. The result is inconsistent flickering and shearing.
You can fix the problem by using only one function to do all the rendering. The problem still will be that when the function exits it may be at any point of the display scan. You will still get some shearing.
To keep in sync with the display hardware and prevent any flickering and shearing use requestAnimationFrame (there are endless answers about it in SO) to do all your rendering.
The DOM treats all callback functions called by requestAnimationFrame as special and will delay any DOM visual changes being moved from the backbuffer to the screen until the display hardware is in its vertical refresh phase. At that point all changes since the last vertical refresh are moved from back buffers to the screen. (This applies to all visual FX not just the canvas).
Fixing your code
As you want a fixed update speed.
var speed = 100;
var nextUpdateTime;
function updateAll(time){
if(nextUpdateTime=== undefined){
nextUpdateTime= time - speed; // first update now
}
if(time >= nextUpdateTime){
nextUpdateTime= time + (speed - (time - nextUpdateTime)); // get time of next update
smoke1();
smoke2();
smoke3()
}
requestAnimationFrame(updateAll);
}
requestAnimationFrame(updateAll);
If you want each FX to have its own speed you will have to create a array of speed and nextUpdateTime values. Requested animation frames generally run at 60fps. Time is always the first argument of the requested callback function.
Better yet run at 60fps
I would suggest that rather than use a slow update speed (100ms is 10fps) you modify your code to slow the animation down so that it runs smoothly when sped up to 60fps
The following will modify your animation to play the same but at a higher frame rate.
var speed = 100; // the old frame update delay
var frameRate = 60; // requestAnimationFrame frame rate
// vx and vy where the update delta move vectors for the smoke
// that need to be adjusted for the new frame rate.
vx = (vx * (1000 / speed)) / frameRate;
vy = (vy * (1000 / speed)) / frameRate;
function updateAll(time){
smoke1();
smoke2();
smoke3()
requestAnimationFrame(updateAll);
}
requestAnimationFrame(updateAll);
NOTE if you present a lot of rendering work the browser may not be able to keep up. If it cant do it in 1/60th of a second and it will not display anything until the next vertical refresh the frame rate will drop from 60fps to 30fps.
If you suspect this will happen then you should monitor the time between frames and use that to calculate the new smoke position every frame.
Related
HTML5 Canvas - How to render a transparent line with colored border
I'm currently using Canvas to draw some curved lines I know how to draw colored lines, but I don't find a way to render them with only a colored border.. For example : const canvas = document.getElementsByTagName('canvas')[0], ctx = canvas.getContext('2d'); ctx.beginPath(); ctx.moveTo(20, 20); ctx.quadraticCurveTo(100, 10, 150, 75); ctx.quadraticCurveTo(200, 140, 300, 100); // this draws a 20px thick red line ctx.lineCap = 'round'; ctx.lineWidth = 20; ctx.strokeStyle = '#f00'; ctx.stroke(); <canvas width="350" height="150" style="border: 1px #303030 solid"></canvas> I tried with clip(), but it's not built to cut a line... Is there a way to draw a real border around a thick line, to avoid filling it ? I don't want to re-draw a thinner colored line over it, because I'm using a transparent canvas within a parent DOM element filled with an image (and don't want to move this background into the canvas)
I found a not perfect solution in a another post, using globalCompositeOperation const canvas = document.getElementsByTagName('canvas')[0], ctx = canvas.getContext('2d'), borderWidth = 1; ctx.beginPath(); ctx.moveTo(20, 20); ctx.quadraticCurveTo(100, 10, 150, 75); ctx.quadraticCurveTo(200, 140, 300, 100); // this draws a 20px thick red line ctx.lineCap = 'round'; ctx.lineWidth = 20; ctx.strokeStyle = '#f00'; ctx.stroke(); // just add this ctx.globalCompositeOperation = "destination-out"; ctx.lineWidth = 20 - borderWidth * 2; ctx.stroke(); ctx.globalCompositeOperation = "source-over"; <canvas width="350" height="150" style="border: 1px #303030 solid"></canvas> In my case, it cut a double-hole into the canvas ; and it doesn't work if you have another background into the canvas...
You can do this too! <!DOCTYPE html> <html> <body> <canvas id="myCanvas" width="300" height="150" style="border:1px solid #d3d3d3;"> Your browser does not support the HTML5 canvas tag.</canvas> <script> var c = document.getElementById("myCanvas"); var ctx = c.getContext("2d"); ctx.beginPath(); ctx.moveTo(20, 20); ctx.quadraticCurveTo(100, 10, 150, 75); ctx.quadraticCurveTo(200, 140, 275, 100); ctx.quadraticCurveTo(285, 110,275, 120); ctx.quadraticCurveTo(200, 160, 150, 95); ctx.quadraticCurveTo(100, 30, 20, 40); ctx.quadraticCurveTo(10, 30, 20, 20); ctx.lineWidth = 2; //ctx.fillStyle = '#f00'; ctx.stroke(); //ctx.fill(); </script> </body> </html>
drawing translated paths in wrong coordinates
I am trying to build a simple game for educational purposes and i am having problem with drawing object in correct coordinates. I had svg spaceship that i wanted to utilise, so i converted it to canvas paths system using this tool SVG to Canvas converter. Since svg was originally designed on 1000x1000 plane i had to apply scale to reach desired size // calculate scale, if size (r) has to be 40px then 40px / original size (1000) = 0.04; var size = r / 1000; ctx.scale(size, size); // apply desired size It seemed to work but then when it comes to rendering it in set coordinates (x,y), it clearly is off the mark. In the demo you can see that ship is rotating and moving outside of where it should be, also disproportion seems to vary depending on location which clearly means something is wrong. Maybe someone could find the cause and how i can fix it? The aim ofc is for the ship to be rendered in the center of helper box. This is demo code: var cvs = document.querySelector('canvas'), ctx = cvs.getContext('2d'), w = cvs.width = 1000, h = cvs.height = 1000, helper = document.querySelector('.helper'); var ship = function(x, y, r, a) { var size = r / 1000; // calculate scale, if size (r) has to be 40px then 40px / original size (1000) = 0.04; x -= r / 2; // go back half of its width to center ship on passed x coord y -= r / 2; // go back half of its height to center ship on passed y coord /* draw original ship ----> */ ctx.save(); ctx.translate(x, y); ctx.scale(size, size); // apply desired size ctx.rotate((a + 90) * Math.PI / 180); // rotate ship on its center ctx.translate(-x, -y); ctx.beginPath(); ctx.moveTo(341.4,856.1); ctx.lineTo(173.2,881.8000000000001); ctx.bezierCurveTo(159.79999999999998,883.8000000000001,146.5,877.7,139.39999999999998,866.2); ctx.lineTo(76.69999999999997,764.2); ctx.bezierCurveTo(73.19999999999997,758.5,71.59999999999998,752,71.79999999999997,745.6); ctx.bezierCurveTo(71.79999999999997,745.1,71.69999999999997,744.5,71.69999999999997,744); ctx.lineTo(71.69999999999997,528.2); ctx.bezierCurveTo(71.69999999999997,509.6,86.79999999999997,494.50000000000006,105.39999999999998,494.50000000000006); ctx.bezierCurveTo(123.99999999999997,494.50000000000006,139.09999999999997,509.6000000000001,139.09999999999997,528.2); ctx.lineTo(139.09999999999997,617.7); ctx.lineTo(273.79999999999995,377.80000000000007); ctx.lineTo(341.49999999999994,493.30000000000007); ctx.lineTo(341.49999999999994,856.1); ctx.closePath(); ctx.moveTo(894.7,494.5); ctx.bezierCurveTo(876.1,494.5,861,509.6,861,528.2); ctx.lineTo(861,617.7); ctx.lineTo(726.3,377.8); ctx.lineTo(658.5999999999999,493.3); ctx.lineTo(658.5999999999999,856.1); ctx.lineTo(826.8,881.8000000000001); ctx.bezierCurveTo(840.1999999999999,883.8000000000001,853.5,877.7,860.5999999999999,866.1); ctx.lineTo(923.3,764.1); ctx.bezierCurveTo(926.8,758.4,928.4,751.9,928.1999999999999,745.5); ctx.bezierCurveTo(928.1999999999999,745,928.3,744.4,928.3,743.9); ctx.lineTo(928.3,528.2); ctx.bezierCurveTo(928.3,509.6,913.3,494.5,894.7,494.5); ctx.closePath(); ctx.moveTo(591.2,857.6); ctx.lineTo(533.7,900.5); ctx.lineTo(533.7,956.4); ctx.bezierCurveTo(533.7,975,518.6,990.1,500.00000000000006,990.1); ctx.bezierCurveTo(481.40000000000003,990.1,466.30000000000007,975,466.30000000000007,956.4); ctx.lineTo(466.30000000000007,900.5); ctx.lineTo(408.80000000000007,857.6); ctx.lineTo(408.80000000000007,484.2); ctx.bezierCurveTo(408.80000000000007,478.2,407.20000000000005,472.3,404.20000000000005,467.2); ctx.lineTo(312.00000000000006,309.79999999999995); ctx.lineTo(470.6,27.2); ctx.bezierCurveTo(476.6,16.6,487.8,10,500,10); ctx.bezierCurveTo(512.2,10,523.4,16.6,529.4,27.2); ctx.lineTo(688.0999999999999,309.8); ctx.lineTo(595.8999999999999,467.20000000000005); ctx.bezierCurveTo(592.8999999999999,472.40000000000003,591.2999999999998,478.20000000000005,591.2999999999998,484.20000000000005); ctx.lineTo(591.2999999999998,857.6); ctx.closePath(); ctx.moveTo(591,318.2); ctx.bezierCurveTo(605.3,306.4,607.4,285.09999999999997,595.5,270.8); ctx.bezierCurveTo(591.6,266.1,555.8,224.60000000000002,500.8,224.60000000000002); ctx.bezierCurveTo(446.3,224.60000000000002,409.1,265.40000000000003,405,270.1); ctx.bezierCurveTo(392.7,284.1,394.1,305.5,408.1,317.70000000000005); ctx.bezierCurveTo(414.5,323.30000000000007,422.40000000000003,326.1,430.3,326.1); ctx.bezierCurveTo(439.6,326.1,448.90000000000003,322.20000000000005,455.5,314.70000000000005); ctx.bezierCurveTo(461.2,308.40000000000003,480.4,292.1,500.8,292.1); ctx.bezierCurveTo(524.7,292.1,543.6,313.8,543.6,313.8); ctx.bezierCurveTo(555.5,328,576.7,330.1,591,318.2); ctx.closePath(); ctx.fillStyle = '#000'; ctx.strokeStyle = '#000'; ctx.lineWidth = 1; ctx.fill(); ctx.stroke(); ctx.restore(); /* <---- draw original ship */ }; var c = { x: 100, y: 100, a: 270, r: 40 }; var rotator = 1; var render = function() { ctx.clearRect(0, 0, w, h); ctx.fillStyle = '#ccc'; ctx.fillRect(0, 0, w, h); ship(c.x, c.y, c.r, c.a); c.x += rotator; c.y += rotator; c.a += rotator; if(c.x >= 950 || c.y >= 950 || c.x <= 50 || c.y <= 50) { rotator *= -1; } /* helper debug section ----> */ ctx.save(); ctx.beginPath(); ctx.moveTo(c.x, 0); ctx.lineTo(c.x, 1000); ctx.closePath(); ctx.stroke(); ctx.beginPath(); ctx.moveTo(0, c.y); ctx.lineTo(1000, c.y); ctx.closePath(); ctx.stroke(); ctx.beginPath(); ctx.moveTo(c.x, c.y); ctx.lineTo(c.x, c.y - 20); ctx.lineTo(c.x - 20, c.y - 20); ctx.lineTo(c.x - 20, c.y); ctx.lineTo(c.x - 20, c.y + 20); ctx.lineTo(c.x, c.y + 20); ctx.lineTo(c.x + 20, c.y + 20); ctx.lineTo(c.x + 20, c.y); ctx.lineTo(c.x + 20, c.y - 20); ctx.lineTo(c.x, c.y - 20); ctx.closePath(); ctx.stroke(); ctx.restore(); /* <---- helper debug section */ requestAnimationFrame(render); }; render(); canvas { position:absolute; top:0; left:0; } <canvas></canvas>
A nice thing to know is that you can draw svg paths in canvas using Path2D as I did in the next demo. Also I've recalculated your path. The path you are using is 1000/1000 units. I've recalculated your path to begin at -500 both in x and y, putting the center in the origin: point {x:0,y:0}. let svgPath =`M-158.60000000000002,356.1L-326.8,381.79999999999995C-340.2,383.79999999999995,-353.5,377.70000000000005,-360.6,366.20000000000005L-423.3,264.20000000000005C-426.8,258.5,-428.4,252,-428.2,245.60000000000002,-428.2,245.10000000000002,-428.3,244.5,-428.3,244L-428.3,28.200000000000045C-428.3,9.600000000000023,-413.2,-5.5,-394.6,-5.5,-376,-5.5,-360.9,9.600000000000023,-360.99,28.200000000000045L-360.9,117.70000000000005,-226.2,-122.19999999999999,-158.5,-6.699999999999989,-158.5,356.1 M394.70000000000005,-5.5C376.1,-5.5,361,9.600000000000023,361,28.200000000000045L361,117.70000000000005,226.29999999999995,-122.19999999999999,158.69000000000005,-6.699999999999989,158.60000000000002,356.1,326.79999999999995,381.79999999999995C340.20000000000005,383.79999999999995,353.5,377.70000000000005,360.6,366.1L423.29999999999995,264.1C426.79999999999995,258.4,428.4,251.89999999999998,428.20000000000005,245.5,428.20000000000005,245,428.29999999999995,244.39999999999998,428.29999999999995,243.89999999999998,428.29999999999995,9.600000000000023,413.29999999999995,-5.5,394.70000000000005,-5.5z M91.20000000000005,357.6L33.700000000000045,400.5,33.700000000000045,456.4C33.700000000000045,475,18.600000000000023,490.1,0,490.1,-18.600000000000023,490.1,-33.69999999999999,475,-33.69999999999999,456.4L-33.69999999999999,400.5,-91.19999999999999,357.6,-91.13,-15.800000000000011C-91.19999999999999,-21.80000000000001,-92.80000000000001,-27.69999999999999,-95.80000000000001,-32.80000000000001L-188,-190.2,-29.399999999999977,-472.8C-23.399999999999977,-483.4,-12.199999999999989,-490,0,-490,12.200000000000045,-490,23.399999999999977,-483.4,29.399999999999977,-472.8L188.10000000000002,-190.2,95.89999999999998,-32.80000000000001C92.89999999999998,-27.569999999999993,91.29999999999995,-21.80000000000001,91.29999999999995,-15.800000000000011L91.29999999999995,357.6z M91,-181.8C105.29999999999995,-193.60000000000002,107.39999999999998,-214.89999999999998,95.5,-229.2,91.60000000000002,-233.89999999999998,55.799999999999955,-275.4,0.8000000000000114,-275.4,-53.69999999999999,-275.4,-90.89999999999998,-234.60000000000002,-95,-229.89999999999998,-107.30000000000001,-215.89999999999998,-105.89999999999998,-194.5,-91.89999999999998,-182.3,-85.5,-176.7,-77.60000000000002,-173.89999999999998,-69.69999999999999,-173.89999999999998,-60.39999999999998,-173.89999999999998,-51.10000000000002,-177.8,-44.5,-185.3,-38.80000000000001,-191.60000000000002,-19.600000000000023,-207.89999999999998,0.8000000000000114,-207.89999999999998,24.700000000000045,-207.89999999999998,43.60000000000002,-186.2,43.60000000000002,-186.2,55.5,-172,76.70000000000005,-169.89999999999998,91,-181.8z`; var cvs = document.querySelector('canvas'), ctx = cvs.getContext('2d'), w = cvs.width = window.innerWidth, h = cvs.height = window.innerHeight; ctx.fillStyle = "black"; let shuttle = new Path2D(svgPath); let angle = 0; let x = 0; let y = 0; let increment = 1 let the_scale = .1; //helper size //1000 is the size of the svg path let hs = (1000 * the_scale) / 2; function frame(){ window.requestAnimationFrame(frame); ctx.clearRect(0, 0, w,h ) angle += increment/100; x+=increment; if(y < h + hs){y+=increment;}else{y = -hs; x=-hs;} ctx.save(); ctx.fillStyle="#333" ctx.translate(x,y); ctx.scale(the_scale,the_scale); ctx.rotate(angle); ctx.fill(shuttle); ctx.restore(); helper(x,y) } frame(); function helper(x,y){ ctx.strokeStyle="red" ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, h); ctx.closePath(); ctx.stroke(); ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(w, y); ctx.closePath(); ctx.stroke(); ctx.beginPath(); ctx.moveTo(x, y); ctx.lineTo(x, y - hs); ctx.lineTo(x - hs, y - hs); ctx.lineTo(x - hs, y); ctx.lineTo(x - hs, y + hs); ctx.lineTo(x, y + hs); ctx.lineTo(x + hs, y + hs); ctx.lineTo(x + hs, y); ctx.lineTo(x + hs, y - hs); ctx.lineTo(x, y - hs); ctx.closePath(); ctx.stroke(); } *{margin:0;padding:0} canvas{background:#d9d9d9;} <canvas></canvas>
why is that gl.clear(gl.COLOR_BUFFER_BIT) and requestAnimationFrame will clear all the primitives I drew before
Hi guys I been leanring WebGL and trying to make a Tetris game out of it. I have a couple of questions I'd like to ask: For this game I wanted to first draw the grid as the background. However I noticed that after I drew the line, if I use gl.clear(gl.COLOR_BUFFER_BIT ); after, it will clear all the lines I drew before. I understand that gl.clear(gl.COLOR_BUFFER_BIT ); is about clearing the color buffer (and you probably will ask why I would want to do that. Just bear with me. ). Then I tried use gl.uniform4f( uColor, 0, 0, 0, 1); to send the color again to the fragment shader but it doesn't help. The snippet is like this window.onload = function(){ getGLContext(); initShaders(); drawLines( 0, 0, 400,400 ); gl.clear(gl.COLOR_BUFFER_BIT ); gl.uniform4f( uColor, 0, 0, 0, 1); } For the game I need the grid as background and I need requestAnimationFrame for the game loop and will render Tetrominos inside the loop. Therefore after drawing the line I used this draw() to draw other Tetrominos. However it removes the line I drew before. And when I comment out gl.clear(gl.COLOR_BUFFER_BIT ); inside draw(), it will remove the line along with background color. function draw() { gl.clear(gl.COLOR_BUFFER_BIT ); gl.drawArrays(gl.TRIANGLES, 0, index*6); requestAnimationFrame(draw); } Here is the demo: https://codepen.io/zhenghaohe/pen/LqxpjB Hope you could answer these two questions. Thanks!
This is generally the way WebGL works. WebGL is just draws into a rectangle of pixels. There is no memory of primitives. There is no structure. There is just code and the resulting canvas which is an rectangle of pixels. Most WebGL programs/pages clear the entire canvas every frame and redraw 100% of the things they want to show every time they draw. For tetris the general code might be something like function render() { clear the canvas draw the grid draw all the stable pieces draw the current piece draw the next piece draw the effects draw the score } Any knowledge of primitives or other structure is entirely up to your code. If you want the grid lines to be static then either set a static background with CSS or use another canvas Using a background: const gl = document.querySelector('#c').getContext('webgl'); function render(time) { time *= 0.001; gl.clearColor(0, 0, 0, 0); gl.clear(gl.COLOR_BUFFER_BIT); drawBlocks(gl, time); requestAnimationFrame(render); } requestAnimationFrame(render); // --- below this line not important to the answer function drawBlocks(gl, time) { gl.enable(gl.SCISSOR_TEST); const numBlocks = 5; for (let i = 0; i < numBlocks; ++i) { const u = i / numBlocks; gl.clearColor(i / 5, i / 2 % 1, i / 3 % 1, 1); const x = 150 + Math.sin(time + u * Math.PI * 2) * 130; const y = 75 + Math.cos(time + u * Math.PI * 2) * 55; gl.scissor(x, y, 20, 20); gl.clear(gl.COLOR_BUFFER_BIT); } gl.disable(gl.SCISSOR_TEST); } #c { background-image: url(https://i.imgur.com/ZCfccZh.png); } <canvas id="c"></canvas> Using 2 canvases // this is the context for the back canvas. It could also be webgl // using a 2D context just to make the sample simpler const ctx = document.querySelector('#back').getContext('2d'); drawGrid(ctx); // this is the context for the front canvas const gl = document.querySelector('#front').getContext('webgl'); function render(time) { time *= 0.001; gl.clearColor(0, 0, 0, 0); gl.clear(gl.COLOR_BUFFER_BIT); drawBlocks(gl, time); requestAnimationFrame(render); } requestAnimationFrame(render); // --- below this line not important to the answer function drawBlocks(gl, time) { gl.enable(gl.SCISSOR_TEST); const numBlocks = 5; for (let i = 0; i < numBlocks; ++i) { const u = i / numBlocks; gl.clearColor(i / 5, i / 2 % 1, i / 3 % 1, 1); const x = 150 + Math.sin(time + u * Math.PI * 2) * 130; const y = 75 + Math.cos(time + u * Math.PI * 2) * 55; gl.scissor(x, y, 20, 20); gl.clear(gl.COLOR_BUFFER_BIT); } gl.disable(gl.SCISSOR_TEST); } function drawGrid(ctx) { // draw grid ctx.translate(-10.5, -5.5); ctx.beginPath(); for (let i = 0; i < 330; i += 20) { ctx.moveTo(0, i); ctx.lineTo(330, i); ctx.moveTo(i, 0); ctx.lineTo(i, 300); } ctx.strokeStyle = "blue"; ctx.stroke(); } #container { position: relative; /* required so we can position child elements */ } #front { position: absolute; left: 0; top: 0; } <div id="container"> <canvas id="back"></canvas> <canvas id="front"></canvas> </div> As for why it clears even if you didn't call clear that's because that's whqt the spec says it's supposed to do See: Why WebGL 'clear' draw to front buffer?
Multiple WebGL models on the same page
My research lab is working on a webpage that displays a long scrollable list of 3d models, about 50 or so. Our first idea was to do this with separate THREE.js WebGL contexts, but it seems this isn't advisable given the architecture of WebGL, and browsers seem to limit the number of contexts on a page to about 2^4. I don't need these contexts to do anything very impressive: the individual geometries only have a few hundred triangles, with no textures, and only one at a time ever animates when using the mouse to rotate its camera. Can I persuade WebGL to do what I want in a way that the browser won't complain about? I thought perhaps of having a single big geometry with all my individual models lined up next to each other, and separate canvases with viewports showing just one model each. But it seems that isn't supported. (Multiple views are allowed in the same context, but that's not very useful for me.) Thanks for any ideas!
It's not clear why you think you need multiple webgl contexts. I'm guessing because you want a list like this 1. [img] description description 2. [img] description description 3. [img] description description Or something? Some ideas make one canvas big enough for the screen, set its CSS so it doesn't scroll with the rest of the page. Draw the models aligned with whatever other HTML you want that does scroll. make an offscreen webgl canvas and use canvas2d elements to display. For each model render the model and then call someCanvas2DContextForElementN.drawImage(webGLcanvasElement, ...); Given there are probably only ever a few canvases visible you only need to update those ones. In fact it's probably a good idea to recycle them. In other words, rather than make 12000 canvaes or a 12000 element list make just enough to fit on the screen and update them as you scroll. Personally I'd probably pick #1 if my page design allowed it. Seems to work, see below. It turned out to be really easy. I just took this sample that was drawing 100 objects and made it draw one object at a time. After clearing the screen turn on the scissor test gl.enable(gl.SCISSOR_TEST); Then, for each object // get the element that is a place holder for where we want to // draw the object var viewElement = obj.viewElement; // get its position relative to the page's viewport var rect = viewElement.getBoundingClientRect(); // check if it's offscreen. If so skip it if (rect.bottom < 0 || rect.top > gl.canvas.clientHeight || rect.right < 0 || rect.left > gl.canvas.clientWidth) { return; // it's off screen } // set the viewport var width = rect.right - rect.left; var height = rect.bottom - rect.top; var left = rect.left; var bottom = gl.canvas.clientHeight - rect.bottom - 1; gl.viewport(left, bottom, width, height); gl.scissor(left, bottom, width, height); I'm not 100% sure if I need to add 1 the width and height or not. I suppose I should look that up. In any case I compute a new projection matrix for every rendered object just to make the code generic. The placeholder divs could be different sizes. Update: the solution originally posted here used position: fixed on the canvas to keep it from scrolling. The new solution uses position: absolute and updates the transform just before rendering like this gl.canvas.style.transform = `translateY(${window.scrollY}px)`; With the previous solution the shapes getting re-drawn in their matching positions could lag behind the scrolling. With the new solution the canvas scrolls until we get time to update it. That means shapes might be missing for a few frames if we can't draw quick enough but it looks much better than the scrolling not matching. The sample below is the updated solution. "use strict"; // using twgl.js because I'm lazy twgl.setAttributePrefix("a_"); var m4 = twgl.m4; var gl = twgl.getWebGLContext(document.getElementById("c")); // compiles shaders, links program, looks up locations var programInfo = twgl.createProgramInfo(gl, ["vs", "fs"]); // calls gl.creatBuffer, gl.bindBuffer, gl.bufferData for each shape // for positions, normals, texcoords var shapes = [ twgl.primitives.createCubeBufferInfo(gl, 2), twgl.primitives.createSphereBufferInfo(gl, 1, 24, 12), twgl.primitives.createPlaneBufferInfo(gl, 2, 2), twgl.primitives.createTruncatedConeBufferInfo(gl, 1, 0, 2, 24, 1), twgl.primitives.createCresentBufferInfo(gl, 1, 1, 0.5, 0.1, 24), twgl.primitives.createCylinderBufferInfo(gl, 1, 2, 24, 2), twgl.primitives.createDiscBufferInfo(gl, 1, 24), twgl.primitives.createTorusBufferInfo(gl, 1, 0.4, 24, 12), ]; function rand(min, max) { return min + Math.random() * (max - min); } // Shared values var lightWorldPosition = [1, 8, -10]; var lightColor = [1, 1, 1, 1]; var camera = m4.identity(); var view = m4.identity(); var viewProjection = m4.identity(); var tex = twgl.createTexture(gl, { min: gl.NEAREST, mag: gl.NEAREST, src: [ 255, 255, 255, 255, 192, 192, 192, 255, 192, 192, 192, 255, 255, 255, 255, 255, ], }); var randColor = function() { var color = [Math.random(), Math.random(), Math.random(), 1]; color[Math.random() * 3 | 0] = 1; // make at least 1 bright return color; }; var objects = []; var numObjects = 100; var list = document.getElementById("list"); var listItemTemplate = document.getElementById("list-item-template").text; for (var ii = 0; ii < numObjects; ++ii) { var listElement = document.createElement("div"); listElement.innerHTML = listItemTemplate; listElement.className = "list-item"; var viewElement = listElement.querySelector(".view"); var uniforms = { u_lightWorldPos: lightWorldPosition, u_lightColor: lightColor, u_diffuseMult: randColor(), u_specular: [1, 1, 1, 1], u_shininess: 50, u_specularFactor: 1, u_diffuse: tex, u_viewInverse: camera, u_world: m4.identity(), u_worldInverseTranspose: m4.identity(), u_worldViewProjection: m4.identity(), }; objects.push({ ySpeed: rand(0.1, 0.3), zSpeed: rand(0.1, 0.3), uniforms: uniforms, viewElement: viewElement, programInfo: programInfo, bufferInfo: shapes[ii % shapes.length], }); list.appendChild(listElement); } var showRenderingArea = false; function render(time) { time *= 0.001; twgl.resizeCanvasToDisplaySize(gl.canvas); gl.canvas.style.transform = `translateY(${window.scrollY}px)`; gl.enable(gl.DEPTH_TEST); gl.disable(gl.SCISSOR_TEST); gl.clearColor(0, 0, 0, 0); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); gl.enable(gl.SCISSOR_TEST); if (showRenderingArea) { gl.clearColor(0, 0, 1, 1); } var eye = [0, 0, -8]; var target = [0, 0, 0]; var up = [0, 1, 0]; m4.lookAt(eye, target, up, camera); m4.inverse(camera, view); objects.forEach(function(obj, ndx) { var viewElement = obj.viewElement; // get viewElement's position var rect = viewElement.getBoundingClientRect(); if (rect.bottom < 0 || rect.top > gl.canvas.clientHeight || rect.right < 0 || rect.left > gl.canvas.clientWidth) { return; // it's off screen } var width = rect.right - rect.left; var height = rect.bottom - rect.top; var left = rect.left; var bottom = gl.canvas.clientHeight - rect.bottom - 1; gl.viewport(left, bottom, width, height); gl.scissor(left, bottom, width, height); if (showRenderingArea) { gl.clear(gl.COLOR_BUFFER_BIT); } var projection = m4.perspective(30 * Math.PI / 180, width / height, 0.5, 100); m4.multiply(projection, view, viewProjection); var uni = obj.uniforms; var world = uni.u_world; m4.identity(world); m4.rotateY(world, time * obj.ySpeed, world); m4.rotateZ(world, time * obj.zSpeed, world); m4.transpose(m4.inverse(world, uni.u_worldInverseTranspose), uni.u_worldInverseTranspose); m4.multiply(viewProjection, uni.u_world, uni.u_worldViewProjection); gl.useProgram(obj.programInfo.program); // calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer twgl.setBuffersAndAttributes(gl, obj.programInfo, obj.bufferInfo); // calls gl.bindTexture, gl.activeTexture, gl.uniformXXX twgl.setUniforms(obj.programInfo, uni); // calls gl.drawArrays or gl.drawElements twgl.drawBufferInfo(gl, obj.bufferInfo); }); } if (true) { // animated var renderContinuously = function(time) { render(time); requestAnimationFrame(renderContinuously); } requestAnimationFrame(renderContinuously); } else { var requestId; var renderRequest = function(time) { render(time); requestId = undefined; } // If animated var queueRender = function() { if (!requestId) { requestId = requestAnimationFrame(renderRequest); } } window.addEventListener('resize', queueRender); window.addEventListener('scroll', queueRender); queueRender(); } * { box-sizing: border-box; -moz-box-sizing: border-box; } body { font-family: monospace; margin: 0; } #c { position: absolute; top: 0; width: 100vw; height: 100vh; } #outer { width: 100%; z-index: 2; position: absolute; top: 0px; } #content { margin: auto; padding: 2em; } #b { width: 100%; text-align: center; } .list-item { border: 1px solid black; margin: 2em; padding: 1em; width: 200px; display: inline-block; } .list-item .view { width: 100px; height: 100px; float: left; margin: 0 1em 1em 0; } .list-item .description { padding-left: 2em; } #media only screen and (max-width : 500px) { #content { width: 100%; } .list-item { margin: 0.5em; } .list-item .description { padding-left: 0em; } } <script src="//twgljs.org/dist/4.x/twgl-full.min.js"></script> <body> <canvas id="c"></canvas> <div id="outer"> <div id="content"> <div id="b">item list</div> <div id="list"></div> </div> </div> </body> <script id="list-item-template" type="notjs"> <div class="view"></div> <div class="description">Lorem ipsum dolor sit amet, conse ctetur adipi scing elit. </div> </script> <script id="vs" type="notjs"> uniform mat4 u_worldViewProjection; uniform vec3 u_lightWorldPos; uniform mat4 u_world; uniform mat4 u_viewInverse; uniform mat4 u_worldInverseTranspose; attribute vec4 a_position; attribute vec3 a_normal; attribute vec2 a_texcoord; varying vec4 v_position; varying vec2 v_texCoord; varying vec3 v_normal; varying vec3 v_surfaceToLight; varying vec3 v_surfaceToView; void main() { v_texCoord = a_texcoord; v_position = (u_worldViewProjection * a_position); v_normal = (u_worldInverseTranspose * vec4(a_normal, 0)).xyz; v_surfaceToLight = u_lightWorldPos - (u_world * a_position).xyz; v_surfaceToView = (u_viewInverse[3] - (u_world * a_position)).xyz; gl_Position = v_position; } </script> <script id="fs" type="notjs"> precision mediump float; varying vec4 v_position; varying vec2 v_texCoord; varying vec3 v_normal; varying vec3 v_surfaceToLight; varying vec3 v_surfaceToView; uniform vec4 u_lightColor; uniform vec4 u_diffuseMult; uniform sampler2D u_diffuse; uniform vec4 u_specular; uniform float u_shininess; uniform float u_specularFactor; vec4 lit(float l ,float h, float m) { return vec4(1.0, abs(l),//max(l, 0.0), (l > 0.0) ? pow(max(0.0, h), m) : 0.0, 1.0); } void main() { vec4 diffuseColor = texture2D(u_diffuse, v_texCoord) * u_diffuseMult; vec3 a_normal = normalize(v_normal); vec3 surfaceToLight = normalize(v_surfaceToLight); vec3 surfaceToView = normalize(v_surfaceToView); vec3 halfVector = normalize(surfaceToLight + surfaceToView); vec4 litR = lit(dot(a_normal, surfaceToLight), dot(a_normal, halfVector), u_shininess); vec4 outColor = vec4(( u_lightColor * (diffuseColor * litR.y + u_specular * litR.z * u_specularFactor)).rgb, diffuseColor.a); gl_FragColor = outColor; } </script> If you have a phone you can see a similar one fullscreen here.
Replacing a d3.js path transition with a new one?
I've seen this question and many other examples, but it isn't helping. I'm trying the last example on this page and after 5 seconds, I want the curved path that is being drawn, to completely disappear and 5 more seconds later, I want a new path to be created. I've tried the below code, but although the entire svg element itself is removed, when I use appendGraph() to created the svg and the path again, the same old path re-appears. How can I ensure that the old path is completely removed and that the tick function also does not get called when the graph is removed? The fiddle is here: http://jsfiddle.net/nav9/5uygqj9v/ And the code is: <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <style> svg { font: 10px sans-serif; } .noselect { /* these are to disable text selection */ -webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } .axis path, .axis line { fill: none; stroke: #000; opacity: 0.5; shape-rendering: crispEdges; } rect.zoom { stroke: steelblue; fill-opacity: 0.3; } #placeholder {margin: 10px 5px 15px 70px;} </style> <script type="text/javascript" src="http://d3js.org/d3.v3.js"></script> </head> <body> <div id="placeholder" ></div> <script> //---------globals var timer = null, interval = 500, value = 0; var value1 = 0; var n = 143, duration = interval, now = new Date(Date.now() - duration), count = 0, data = d3.range(n).map(function() { return 0; }); var margin = {top: 20, right: 40, bottom: 50, left: 60}, width = 580 - margin.right, height = 420 - margin.top - margin.bottom; var x = d3.time.scale().domain([now - (n - 2) * duration, now - duration]).range([0, width]); var y = d3.scale.linear().domain([-1, 1]).range([height, 0]); var line = d3.svg.line().interpolate("basis") .x(function(d, i) { return x(now - (n - 1 - i) * duration); }) .y(function(d, i) { return y(d); }); var svg, path, yaxis, axis; //--------program starts appendGraph(); tick(); value1 = 0; setTimeout(function() {removeGraph();}, 5000); setTimeout(function() {addGraphAgain();}, 10000); //-------------------------------functions ------------------------------- function appendGraph() { svg = d3.select("body").select("#placeholder").append("p").append("svg:svg") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom) .attr("id", "mainSVG") .style("margin-left", -margin.left + "px") .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); svg.append("defs").append("clipPath") .attr("id", "clip") .append("rect") .attr("width", width) .attr("height", height); axis = svg.append("g") .attr("class", "x axis") .attr("transform", "translate(0," + height + ")") .call(x.axis = d3.svg.axis().scale(x).orient("bottom")); yaxis = svg.append("g") .attr("class", "y axis") .call(y.axis = d3.svg.axis().scale(y).orient("left")); path = svg.append("g") .attr("clip-path", "url(#clip)") .append("path") .data([data]) .attr("id", "line1") .attr("fill", "none") .attr("stroke", "black") .attr("stroke-width", "1.5px") .style("visibility","visible"); }//appendGraph //TODO: These tick functions could be simplified to handle more lines on the graph function tick() { // push the accumulated count onto the back, and reset the count value1 = Math.random() * 100; if (value1 >= 0) {data.push(value1);} else {data.push(0);}//ensure that no NaN or undefined values corrupt the range // update the domains now = new Date(); x.domain([now - (n - 2) * duration, now - duration]); count = 0; // redraw the lines svg.select("#line1").attr("d", line).attr("transform", null); // slide the line left path.transition().duration(duration).ease("linear").attr("transform", "translate(" + x(now - (n - 1) * duration) + ")").each("end", tick); y.domain([0, 100]); y = d3.scale.linear().domain([0, 100]).range([height, 0]); yaxis.call(y.axis = d3.svg.axis().scale(y).orient("left")); // pop the old data point off the front data.shift(); console.log("tick being called"); } function removeGraph() { path.transition().duration(0).each(function() { this.__transition__.active = 0; });//at least this is stopping tick from being called svg.selectAll("*").remove(); //-------tried these too // d3.select("#mainSVG").remove("svg"); // d3.select("#line1").remove("path"); // path.remove(); //d3.selectAll("path").attr("d", "Z"); console.log("REMOVED"); }//removeGraph function addGraphAgain() { appendGraph(); tick(); value1 = 0; console.log("ADDED AGAIN"); }//addGraphAgain </script> </body> </html>
Not an exact answer to this question, but since the reason I asked this was because I wanted to have phases where I wanted to send null inputs to the graph and there seemed no other way to do it other than to remove the line and replace it with a new line. The trick to handle null or NaN data or missing data in d3.js or to simply not display data for a while is to use defined. A working example of it here and in the line transition, it's like this: I supply a random number at if (counter%5==0) ran = null;data.push(ran); and .defined(function(d) { return d != null; }) takes care of the null, by not drawing a line there. <!DOCTYPE html> <meta charset="utf-8"> <style> svg { font: 10px sans-serif; } .line { fill: none; stroke: #000; stroke-width: 1.5px; } .axis path, .axis line { fill: none; stroke: #000; shape-rendering: crispEdges; } </style> <body> <script src="http://d3js.org/d3.v3.min.js"></script> <script> var n = 40, random = d3.random.normal(0, .2), data = d3.range(n).map(random); var margin = {top: 20, right: 20, bottom: 20, left: 40}, width = 960 - margin.left - margin.right, height = 500 - margin.top - margin.bottom; var x = d3.scale.linear() .domain([0, n - 1]) .range([0, width]); var y = d3.scale.linear() .domain([-1, 1]) .range([height, 0]); var line = d3.svg.line() .defined(function(d) { return d != null; }) .x(function(d, i) { return x(i); }) .y(function(d, i) { return y(d); }); var svg = d3.select("body").append("svg") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom) .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); svg.append("defs").append("clipPath") .attr("id", "clip") .append("rect") .attr("width", width) .attr("height", height); svg.append("g") .attr("class", "x axis") .attr("transform", "translate(0," + y(0) + ")") .call(d3.svg.axis().scale(x).orient("bottom")); svg.append("g") .attr("class", "y axis") .call(d3.svg.axis().scale(y).orient("left")); var path = svg.append("g") .attr("clip-path", "url(#clip)") .append("path") .datum(data) .attr("class", "line") .attr("d", line); var counter = 0; tick(); function tick() { // push a new data point onto the back var ran = random(); counter++; if (counter%5==0) ran = null; data.push(ran); // redraw the line, and slide it to the left path .attr("d", line) .attr("transform", null) .transition() .duration(500) .ease("linear") .attr("transform", "translate(" + x(-1) + ",0)") .each("end", tick); // pop the old data point off the front data.shift(); } </script>