I am working on a graphics demo using RaphaelJS and jQuery.SVG. Right now I have a dynamically created 8x8 grid of rectangles that users are able to click to select. Once two of the rects are selected, then the selected rects are supposed to switch places with each other (like in Bejeweled.)
I have gotten to the point where I can get the rects to swap, but they are not ending up in the correct positions. According to the code, I am swapping out the x and y coordinates of each selected rect in an animate function. The rects always end up "off the grid" in testing. The visual effect is pretty neat, but I really want the rects to stay lined up.
http://img407.imageshack.us/img407/2222/raphaelerror.jpg
http://imageshack.us/photo/my-images/593/raphaelerror2.jpg/
The debug strings that I am building tell me that the positions of the rects have not changed. Obviously that is not true...what is going on here?
Here's the relevant code for this issue:
// swapTiles
/*
Swaps two tiles on the board that have been set to 'selected' with an animation.
*/
function swapTiles(toSwap) {
var holdX = $(toSwap[0]).attr('x');
var holdY = $(toSwap[0]).attr('y');
// Pre-swap debugging
statusString += "<br><b>BEFORE SWAP</b><br>";
statusString += "<br>First cell: " + selectedCellNames[0];
statusString += "<br>2nd cell: " + selectedCellNames[1];
statusString += "<br>$(toSwap[0]).attr('x'): " + $(toSwap[0]).attr('x').toString();
statusString += "<br>$(toSwap[0]).attr('y'): " + $(toSwap[0]).attr('y').toString();
statusString += "<br>$(toSwap[1]).attr('x'): " + $(toSwap[1]).attr('x').toString();
statusString += "<br>$(toSwap[1]).attr('y'): " + $(toSwap[1]).attr('y').toString();
statusString += "<br>";
/*
This line is using jQuery SVG to grab the DOM elements. This is being used because trying to grab the elements through
raphael was returning undefined errors. There seems to be some DOM scope issue that is causing me to use this and the temp.node
workarounds in order to reference SVG objects created through raphael.
What did not work:
$(toSwap[0]).animate({ x: 50, y: 100, 'stroke-width': 3 }, 250, '<'); --raphael animate function
$(toSwap[0]).node.animate({ x: 50, y: 100, 'stroke-width': 3 }, 250, '<');
When I tried to access the tiles using jQuery and the cellName user-defined attribute, I got '0' added to my debug output.
*/
$(toSwap[0]).animate({ svgX: $(toSwap[1]).attr('x'), svgY: $(toSwap[1]).attr('y'), svgStrokeWidth: 2, svgStroke: "#000" }, 250);
$(toSwap[1]).animate({ svgX: holdX, svgY: holdY, svgStrokeWidth: 2, svgStroke: "#000" }, 250);
// Post-swap debugging
statusString += "<br><b>SWAP COMPLETE</b><br>";
statusString += "<br>$(toSwap[0]).attr('x'): " + $(toSwap[0]).attr('x').toString();
statusString += "<br>$(toSwap[0]).attr('y'): " + $(toSwap[0]).attr('y').toString();
statusString += "<br>$(toSwap[1]).attr('x'): " + $(toSwap[1]).attr('x').toString();
statusString += "<br>$(toSwap[1]).attr('y'): " + $(toSwap[1]).attr('y').toString();
statusString += "<br>";
}
// drawRect
/*
returns a Raphael rectangle object with the inputted options
color = the value that the color attr will be set to for this rect
x, y = x & y position of the top-left corner of the rect
w, h = width and height of the rect
corner = radius for the rounded corners
*/
function drawRect(color, x, y, w, h, corner) {
var temp = canvas.rect(x, y, w, h, corner);
temp.attr("fill", color);
temp.node.setAttribute("selected", "false"); // Will be used to check for selected tiles in main loop
temp.node.setAttribute("cellName", cellName); // Give this rect a name
// Add the mouseover/hover events
temp.node.onmouseover = function () {
temp.animate({ scale: 1.5, stroke: "#FFF", 'stroke-width': 3 }, 250, 'bounce').toFront();
}; // end temp.node.onmouseover
temp.node.onmouseout = function () {
if (temp.node.getAttribute("selected") == "true") {
temp.animate({ scale: 1, stroke: "#FFF", 'stroke-width': 4 }, 250, 'elastic').toFront();
}
else {
temp.animate({ scale: 1, stroke: "#000", 'stroke-width': 2 }, 250, 'elastic').toBack();
}
// Update the status debugging
statusString += "<br>(x: " + x.toString() + ", y: " + y.toString() + ") temp.node.getAttribute('selected') after .node.onmouseout = " + temp.node.getAttribute("selected");
statusString += "<br>selectedCount = " + selectedCount.toString();
statusString += "<br>cellName = " + temp.node.getAttribute("cellName");
statusString += "<br>selectedCells[0] = " + selectedCells[0].toString();
statusString += "<br>selectedCells[1] = " + selectedCells[1].toString();
$("#status").html(statusString);
statusString = "";
}; // end temp.node.onmouseout
// Toggle between selected / not-selected
temp.node.onclick = function () {
// if not selected yet
if (temp.node.getAttribute("selected") != "true") {
temp.animate({ scale: 1, stroke: "#FFF", 'stroke-width': 4 }, 250, 'elastic').toFront();
temp.node.setAttribute("selected", "true");
selectedCount += 1;
// Place the current cell into our array
if (selectedCount == 1) {
selectedCells[0] = temp.node;
selectedCellNames[0] = temp.node.getAttribute("cellName");
}
// We have two cells selected: time to swap
else if (selectedCount == 2) {
selectedCells[1] = temp.node;
selectedCellNames[1] = temp.node.getAttribute("cellName");
swapTiles(selectedCells);
// Clean up selectedCells and the selected attributes
/*
Since selectedCells already contains references to the temp.nodes of each SVG object,
use this format to set the attributes. No jQuery SVG call or additional .node is needed.
*/
selectedCells[0].setAttribute("selected", "false");
selectedCells[1].setAttribute("selected", "false");
selectedCells[0] = "";
selectedCells[1] = "";
selectedCellNames[0] = "";
selectedCellNames[1] = "";
selectedCount = 0;
}
else {
// Something went wrong
statusString += "<br><b>ERROR: selectedCells out of range!</b>";
}
}
// if already selected de-select
else {
temp.animate({ scale: 1, stroke: "#000", 'stroke-width': 2 }, 250, 'elastic').toBack();
temp.node.setAttribute("selected", "false");
selectedCount -= 1;
// Remove the current cell name from our array
if (selectedCount == 0) {
selectedCells[0] = "";
selectedCellNames[0] = "";
}
else if (selectedCount == 1) {
selectedCells[1] = "";
selectedCellNames[1] = "";
}
else {
// Something went wrong
statusString += "<br><b>ERROR: selectedCells out of range!</b>";
}
}
// Update the status debugging
statusString += "<br>(x: " + x.toString() + ", y: " + y.toString() + ") temp.node.getAttribute('selected') after .node.onclick = " + temp.node.getAttribute("selected");
statusString += "<br>selectedCount = " + selectedCount.toString();
statusString += "<br>selectedCells[0] = " + selectedCells[0].toString();
statusString += "<br>selectedCells[1] = " + selectedCells[1].toString();
$("#status").html(statusString);
statusString = "";
}; // end temp.node.onclick
return temp;
/*
NOTES:
temp.node.[function] - the node raphael property lets me directly access the DOM element that I just created
I will need to add all of my onclick and other direct access events for my tiles in this function.
I will get errors if I try to reference the DOM elements from another part of this program, probably because it is only
in this function that I actually create the DOM object. Once this function finishes, then the temp object reference is passed
back into my initialization loop, where at that point I cannot add any more DOM elements.
.animate - Raphael's animation function. Note that the properties that are animated are defined in the first argument,
between a set of {}. The second argument is the time in milliseconds that the animation will take.
.toFront() & .toBack() - These functions move the object to the top or bottom of the drawing stack. In other words, if I use
toFront on an object, it should be drawn on top of everything else, and vice-versa. This eliminates a graphical issue where the
tiles overrlap while expanded.
.node.setAttribute / .getAttribute(key, value) - These are the DOM functions to set and get attributes of specific DOM elements.
I am using these instead of .attr(attr, value) because I can't perform jQuery operations on the DOM elements at this point in the script.
*/
} // end drawRect
// Initialize the grid of rectangles
for (var i = 0; i < grid.length; i++) {
for (var j = 0; j < grid[i].length; j++) {
cellName = "cell " + i + ":" + j;
// drawRect(color, x, y, width, height, corner radius)
grid[i][j] = drawRect(colors[Math.floor(Math.random() * colors.length)], startx + (width * j), starty + (height * i), width, height, cornerRadius);
// used for debugging
debugString += "<br>grid[" + i + "][" + j + "] x: " + (startx + (width * j)) + " y: " + (starty + (height * i));
} // end inner for loop
} // end outer for loop
I'm not a Javascript expert so any help would be appreciated. Thanks!
Related
I'm using the jQuery Flot Charts plugin in one of my projects. I have several charts standing in the same column and what I'm trying to do is: Show the crosshair on all of the charts if you hover on any of them. I'm doing this successfully using the following code.
//graphs - this is an object which contains the references to all of my graphs.
$.each(graphs, function(key, value) {
$(key).bind("plothover", function (event, pos, item) {
$.each(graphs, function(innerKey, innerValue) {
if(key != innerKey) {
innerValue.setCrosshair({x: pos.x});
}
});
if(item) {
var x = item.datapoint[0].toFixed(2),
y = item.datapoint[1].toFixed(2);
console.log("x:" + x + ", " + "y:" + y);
}
});
});
I'm iterating over the graphs, adding the crosshair and bind it to each other. So, now, when you hover over one of the graphs you'll see the crosshair in the same position on all of the others.
No problems with this. However I'm having problems with the second part of my code:
if(item) {
var x = item.datapoint[0].toFixed(2),
y = item.datapoint[1].toFixed(2);
console.log("x:" + x + ", " + "y:" + y);
}
And the problem is that I'm getting the console.log to print values only when I hover the actual point with my mouse while I want to get that value whenever the crosshair crosses that point, not necessarily the mouse pointer. Any clues what I'm doing wrong or maybe there's a setting in the graph options for that to work?
And another thing is that I can get the value for one graph only - the one my mouse is on, I don't seem to be able to get the values for the rest of the graphs where the crosshair is also moving.
The highlighting with
if(item) {
var x = item.datapoint[0].toFixed(2),
y = item.datapoint[1].toFixed(2);
console.log("x:" + x + ", " + "y:" + y);
}
only works when the cursor is near a point (otherwise item is null).
To get the nearest point to the crosshair, you have to do the highlighting manually by searching the nearest point and interpolate (for every graph). The code for that could look like this:
var axes = value.getAxes();
if (pos.x < axes.xaxis.min || pos.x > axes.xaxis.max ||
pos.y < axes.yaxis.min || pos.y > axes.yaxis.max) {
return;
}
$('#output').html("x: " + pos.x.toPrecision(2));
$.each(graphs, function(innerKey, innerValue) {
var i, series = innerValue.getData()[0];
// Find the nearest points, x-wise
for (i = 0; i < series.data.length; ++i) {
if (series.data[i][0] > pos.x) {
break;
}
}
// Now Interpolate
var y,
p1 = series.data[i - 1],
p2 = series.data[i];
if (p1 == null) {
y = p2[1];
} else if (p2 == null) {
y = p1[1];
} else {
y = p1[1] + (p2[1] - p1[1]) * (pos.x - p1[0]) / (p2[0] - p1[0]);
}
$('#output').html($('#output').html() + "<br />" + "y (" + innerValue.getData()[0].label + "): " + y.toPrecision(2));
See this fiddle for a full working example. Some Remarks to the new code and fiddle:
has sine and cosine values as example data, so uses floating point numbers, change accordingly for int numbers and/or date values
uses a <p> element for output instead of the console
point-finding and interpolation code can be optimized further if needed (basic version here taken from the example on the Flot page)
works only if there is only one data series per graph
I have 2 coordinates x and y of a point. I want to calculate the angle between three points, say A,B,C.
Now for the B point I do not have a pixel which contains the 2 coordinates instead I have the pixel, how can I get a single pixel which I can use in my formula.
function find_angle(A,B,C) {
var AB = Math.sqrt(Math.pow(B.x-A.x,2)+ Math.pow(B.y-A.y,2));
var BC = Math.sqrt(Math.pow(B.x-C.x,2)+ Math.pow(B.y-C.y,2));
var AC = Math.sqrt(Math.pow(C.x-A.x,2)+ Math.pow(C.y-A.y,2));
var abc = (BC*BC)+ (AB*AB)-(AC*AC);
var x = abc/(2*BC*AB);
var Angle = FastInt((Math.acos(x) * 180/3.14159));
document.getElementById("Angle").value = Angle;
}
How to proceed with this.
A is changing every time I move the point and I have the updated coordinates as well but I am not able to get the whole pixel I can use in the formula to calculate the new angle.
If I understand what you are asking - you want to create a calculator for the angle formed between
3 dots (A, B middle, C).
Your function should work for the final calculation but you need to recall the function every time
a point has moved.
I created a nice fiddle to demonstrate how you can achieve it with : jQuery, jQuery-ui, html.
I used the draggable() plugin of the UI library to allow the user to manually drag the dots around
And I'm recalculating the angle while dragging.
Take a look: COOL DEMO JSFIDDLE
The CODE ( you will find all HTML & CSS in the demo):
$(function(){
//Def Position values:
var defA = { top:20, left:220 };
var defB = { top:75, left:20 };
var defC = { top:200, left:220 };
//Holds the degree symbol:
var degree_symbol = $('<div>').html('゜').text();
//Point draggable attachment.
$(".point").draggable({
containment: "parent",
drag: function() {
set_result(); //Recalculate
},
stop: function() {
set_result(); //Recalculate
}
});
//Default position:
reset_pos();
//Reset button click event:
$("#reset").click(function(){ reset_pos(); });
//Calculate position of points and updates:
function set_result() {
var A = get_middle("A");
var B = get_middle("B");
var C = get_middle("C");
angle = find_angle(A,B,C);
$("#angle").val(angle + degree_symbol);
connect_line("AB");
connect_line("CB");
}
//Angle calculate:
function find_angle(A,B,C) {
var AB = Math.sqrt(Math.pow(B.x-A.x,2)+ Math.pow(B.y-A.y,2));
var BC = Math.sqrt(Math.pow(B.x-C.x,2)+ Math.pow(B.y-C.y,2));
var AC = Math.sqrt(Math.pow(C.x-A.x,2)+ Math.pow(C.y-A.y,2));
radians = Math.acos((BC*BC+AB*AB-AC*AC)/(2*BC*AB)); //Radians
degree = radians * (180/Math.PI); //Degrees
return degree.toFixed(3);
}
//Default position:
function reset_pos() {
$("#A").css(defA);
$("#B").css(defB);
$("#C").css(defC);
set_result();
}
//Add lines and draw them:
function connect_line(points) {
var off1 = null;
var offB = get_middle("B");
var thickness = 4;
switch (points) {
case "AB": off1 = get_middle("A"); break;
case "CB": off1 = get_middle("C"); break;
}
var length = Math.sqrt(
((offB.x-off1.x) * (offB.x-off1.x)) +
((offB.y-off1.y) * (offB.y-off1.y))
);
var cx = ((off1.x + offB.x)/2) - (length/2);
var cy = ((off1.y + offB.y)/2) - (thickness/2);
var angle = Math.atan2((offB.y-off1.y),(offB.x-off1.x))*(180/Math.PI);
var htmlLine = "<div id='" + points + "' class='line' " +
"style='padding:0px; margin:0px; height:" + thickness + "px; " +
"line-height:1px; position:absolute; left:" + cx + "px; " +
"top:" + cy + "px; width:" + length + "px; " +
"-moz-transform:rotate(" + angle + "deg); " +
"-webkit-transform:rotate(" + angle + "deg); " +
"-o-transform:rotate(" + angle + "deg); " +
"-ms-transform:rotate(" + angle + "deg); " +
"transform:rotate(" + angle + "deg);' />";
$('#testBoard').find("#" + points).remove();
$('#testBoard').append(htmlLine);
}
//Get Position (center of the point):
function get_middle(el) {
var _x = Number($("#" + el).css("left").replace(/[^-\d\.]/g, ''));
var _y = Number($("#" + el).css("top").replace(/[^-\d\.]/g, ''));
var _w = $("#" + el).width();
var _h = $("#" + el).height();
return {
y: _y + (_h/2),
x: _x + (_w/2),
width: _w,
height: _h
};
}
});
This Code requires jQuery & jQuery-UI. Don't forget to include them if you test it locally.
Have fun!
I have a radar that I can zoom into, this all works if I am scrolling in with the mouse or pinch zooming. However, when i double click on an arc within the radar it needs to zoom in to it.
i have removed the default double click with
.on('dblclick.zoom',null);
and I have a double click event on each arc that can be double clicked (so it is not going through the zoom listener). This double click handler calls the zoom listeners scale and translate functions and then the event. I have tried calling the scale then event and then the translate and then the event but I get the same result, nothing zooms
RadarDraw.ZoomListener.scale(6);
RadarDraw.ZoomListener.translate([0, 0]);
RadarDraw.ZoomListener.event(svg.transition().duration(500));
from what I have read this should work. Is there something I am missing here?
Thanks
Mark
my zoom handler function looks like this
function zoom(){
if (d3.event.scale <= 1 || d3.event.scale > 5) {
//on scale 1 (zoomed out) center the radar to its origianl position
//if (RadarDraw.ZoomListener.scale() <= 1 && previousZoom)
//{
// resetZoomOut();
// previousZoom = false;
//}
var scalesize;
if (d3.event.scale <= 1)
{
scalesize = 1;
}
if (d3.event.scale > 5)
{
scalesize = 0.2;
}
$(".isadot").each(function (i, v) {
var tmp = v;
var PrevTransform = tmp.attributes["transform"].nodeValue;
//copy translate only remove other scale
var currentTrans = PrevTransform.split(" ");
tmp.attributes["transform"].nodeValue = currentTrans[0] + " scale(" + scalesize + ")";
});
previousZoomLevel = d3.event.scale;
return;
} else {
// RadarDraw.ZoomListener.translate(d3.event.translate);
//allow panning not zoom between min and max
svg.attr("transform", "translate("
+ (d3.event.translate[0] + (_config.Width / 2)) + "," + (d3.event.translate[1] + (_config.Height / 2))
+ ")scale(" + d3.event.scale + ")");
if (d3.event.scale > 1)
previousZoom = true;
//if we are zoomed in and out previous zoom level is 5 or above then exit zoom (we are either scrolling out or pinching out of zoom)
if (tools.IsArcFocused && previousZoomLevel >= 5) {
tools.ExitZoom(false);
}
//scale the dots by the zom level
var scalesize = 0.6;
//switch (RadarDraw.ZoomListener.scale())
//{
// case ():
// break;
// default:
// scalesize = 0.6;
// break;
//}
if (d3.event.scale <= 1) {
scalesize = 1;
}
if (d3.event.scale >= 5) {
scalesize = 0.4;
}
$(".isadot").each(function (i, v) {
var tmp = v;
var PrevTransform = tmp.attributes["transform"].nodeValue;
//copy translate only remove other scale
var currentTrans = PrevTransform.split(" ");
tmp.attributes["transform"].nodeValue = currentTrans[0] + " scale(" + scalesize + ")";
});
}
}
EDIT
Rob was correct it ended up being .on('dblclick.zoom',null); that was causing the issue in ie 9, after commenting it out it acted as expected. Thanks for the back and forth Rob, got there in the end haha.
I have a paper with 400 x 500 in size. I am trying to divide this into 20 x 20 pixels with below code
var dpH = 500, dpW = 400, drawPad = new Raphael(document.getElementById('p'),
dpW, dpH);
for ( var i = 0; i <= dpH / 20; i++) {
drawPad.path("M" + 1 + " " + ((i * 20) + 1) + "L" + dpW + " "
+ ((i * 20) + 1));
}
for ( var j = 0; j <= (dpW / 20); j++) {
drawPad.path("M" + ((j * 20) + 1) + " " + 1 + "L" + ((j * 20) + 1) + " "
+ dpH);
}
And HTML markup is like below
<div id="p" style="background-image:url(image.png)"> </div>
with same height and width of Background Image.
My original requirement was making the image.png as Rapheal paper. But I was failed to do that. So I made it as background-image to the DIV#P. Then converted the DIv to Paper.
Here are my questions related to above
Does all the pixels of Background-Image and DIV match with each other?
The way I did above is to classify the total paper into 20x20 pixel divisions. Is that correct way of doing?
What is the width of the drawn line?
Please help me on this.
Ok, so if I understand you correctly; What you really want is to get the raw image data for 20x20 squares of the image.
Here's how you can extract image data with Canvas (also on jsFiddle):
var dpH = 500,
dpW = 400,
ctx = document.getElementById('p').getContext('2d'),
exportData = function() {
var data;
for (var y=0, yl=dpH/20; y<yl; y++) {
for (var x=0, xl=dpW/20; x<xl; x++) {
imgData = ctx.getImageData(x*20, y*20, 20, 20).data;
console.log("Image data for " + x*20 + ", " + y*20, imgData);
// data is an array with 4 values pr pixel
// Top left pixel in the 20x20 square
r = imgData[0]; // red
g = imgData[1]; // green
b = imgData[2]; // blue
a = imgData[3]; // alpha
console.log("RGBa of " + x*20 + ", " + y*20 + ": ", r, g, b, a);
}
}
},
drawImage = function() {
ctx.drawImage(this, 0, 0);
exportData(this);
};
var img = new Image();
img.onload = drawImage;
img.src = "image.png"; // has to be on the same domain
** Original answer **
The result is a DIV with an SVG-element inside, and a background image behind it. The browser (if it supports SVG) will render them on top of each other. Do you want to extract pixel values? If so, you have to do this through HTML5 Canvas instead of SVG.
Sorry, I don't understand. What are you trying to accomplish? Do you want the pixel data for 20x20 squares? With Raphael you are just drawing lines on top of the picture.
The defaut with of a path is 1 pixels. You can change this by setting an attribute on the path. Example (also on jsfiddle.net):
var dpH = 500,
dpW = 400,
drawPad = Raphael(document.getElementById('p'), dpW, dpH),
style = {
"stroke" : "#fff", // white
"stroke-width" : 2 // default 1
};
for ( var i = 0; i <= dpH / 20; i++) {
drawPad.path("M" + 1 + " " + ((i * 20) + 1) + "L" + dpW + " "
+ ((i * 20) + 1)).attr(style);
}
for ( var j = 0; j <= (dpW / 20); j++) {
drawPad.path("M" + ((j * 20) + 1) + " " + 1 + "L" + ((j * 20) + 1) + " "
+ dpH).attr(style);
}
It's late and the part of my brain where Douglas Crockford lives is closed. Ive tried a few things but nothing's doing as expected.
I've got a canvas where I draw a 2 lines, then fade them out on a timer but only the last line in the loop is being faded out. Here's my fiddle, look down to line 50ish in the JS, to see it in action drag your mouse around in the bottom right pane:
http://jsfiddle.net/mRsvc/4/
this is the function, basically the timeout only gets the last value in the loop, I've seen this before and I'm sure if I wasn't so delirious it might be simpler. Here's the function in particular:
function update()
{
var i;
this.context.lineWidth = BRUSH_SIZE;
this.context.strokeStyle = "rgba(" + COLOR[0] + ", " + COLOR[1] + ", " + COLOR[2] + ", " + BRUSH_PRESSURE + ")";
for (i = 0; i < scope.painters.length; i++)
{
scope.context.beginPath();
var dx = scope.painters[i].dx;
var dy = scope.painters[i].dy;
scope.context.moveTo(dx, dy);
var dx1 = scope.painters[i].ax = (scope.painters[i].ax + (scope.painters[i].dx - scope.mouseX) * scope.painters[i].div) * scope.painters[i].ease;
scope.painters[i].dx -= dx1;
var dx2 = scope.painters[i].dx;
var dy1 = scope.painters[i].ay = (scope.painters[i].ay + (scope.painters[i].dy - scope.mouseY) * scope.painters[i].div) * scope.painters[i].ease;
scope.painters[i].dy -= dy1;
var dy2 = scope.painters[i].dy;
scope.context.lineTo(dx2, dy2);
scope.context.stroke();
for(j=FADESTEPS;j>0;j--)
{
setTimeout(function()
{
var x=dx,y=dy,x2=dx2,y2=dy2;
scope.context.beginPath();
scope.context.lineWidth=BRUSH_SIZE+1;
scope.context.moveTo(x, y);
scope.context.strokeStyle = "rgba(" + 255 + ", " + 255 + ", " + 255 + ", " + .3 + ")";
scope.context.lineTo(x2, y2);
scope.context.stroke();
scope.context.lineWidth=BRUSH_SIZE;
},
DURATION/j);
}
}
}
The problem is that the variables dx, dy, etc that you refer to in the function you pass to setTimeout() are defined in the surrounding scope and by the time any of the timeouts actually runs these variables all hold the values from the last iteration of the loop(s).
You need to create an extra containing function to close over the values from each iteration. Try something like the following:
for(j=FADESTEPS;j>0;j--) {
(function(x,y,x2,y2) {
setTimeout(function() {
scope.context.beginPath();
scope.context.lineWidth=BRUSH_SIZE+1;
scope.context.moveTo(x, y);
scope.context.strokeStyle = "rgba(" + 255 + ", " + 255 + ", " + 255 + ", " + .3 + ")";
scope.context.lineTo(x2, y2);
scope.context.stroke();
scope.context.lineWidth=BRUSH_SIZE;
},
DURATION/j);
})(dx, dy, dx2, dy2);
}
This creates a new anonymous function for each iteration of the j=FADESTEPS loop, executing it immediately and passing the dx, etc. values as they were at the time each iteration of the loop ran, and moving the x, y, etc. variables out of your existing function and making them parameters of the new one so then by the time the timeout runs it will use the correct values.
You can try something like this:
`<script>
for(j=10;j>0;j--)
{
var fn = function(ind){return function()
{
console.log(ind);
};
}(j);
setTimeout(fn,
1000);
}
</script>`
Or another way (as soon as you do not use IE, but let it learn canvas at first :))
for(j=FADESTEPS;j>0;j--)
{
setTimeout(function(x,y,x2,y2)
{
scope.context.beginPath();
scope.context.lineWidth=BRUSH_SIZE+1;
scope.context.moveTo(x, y);
scope.context.strokeStyle = "rgba(" + 255 + ", " + 255 + ", " + 255 + ", " + .3 + ")";
scope.context.lineTo(x2, y2);
scope.context.stroke();
scope.context.lineWidth=BRUSH_SIZE;
},
DURATION/j,dx,dy,dx2,dy2);
}
ps: there is no need in set of extra functions (the reasons are clear)
First of all j is a global.
Second of all, you never close the paths that you begin, which can cause memory leaks. It seems really slow and this may be why. You need to call closePath() whenever you're done with the paths you start with beginPath()
Next, I think there's some general funniness with how this works. You're fading out by drawing over the last thing with white. I've done something similar to this before, but instead I cleared the whole screen and kept drawing things over and over again. It worked okay for me.
Explanation
The other answers about dx and dy being passed from the higher scope are the right answers though. Async functions defined in synchronous for loops will take the last version of the state.
for (var i = 0; i < 10; i++) setTimeout(function() { console.log(i)}, 10 )
10
10
// ...
I would suggest you to use an array and store the points avoiding setTimeOut call in a loop. Somewhat like this.
this.interval = setInterval(update, REFRESH_RATE);
var _points = [];
function update() {
var i;
this.context.lineWidth = BRUSH_SIZE;
this.context.strokeStyle = "rgba(" + COLOR[0] + ", " + COLOR[1] + ", " + COLOR[2] + ", " + BRUSH_PRESSURE + ")";
for (i = 0; i < scope.painters.length; i++) {
scope.context.beginPath();
var dx = scope.painters[i].dx;
var dy = scope.painters[i].dy;
scope.context.moveTo(dx, dy);
var dx1 = scope.painters[i].ax = (scope.painters[i].ax + (scope.painters[i].dx - scope.mouseX) * scope.painters[i].div) * scope.painters[i].ease;
scope.painters[i].dx -= dx1;
var dx2 = scope.painters[i].dx;
var dy1 = scope.painters[i].ay = (scope.painters[i].ay + (scope.painters[i].dy - scope.mouseY) * scope.painters[i].div) * scope.painters[i].ease;
scope.painters[i].dy -= dy1;
var dy2 = scope.painters[i].dy;
scope.context.lineTo(dx2, dy2);
scope.context.stroke();
_points.push([dx, dy, dx2, dy2]);
clear();
}
}
function clear(){
if(_points.length < FADESTEPS){
return;
}
var p = _points.shift();
if(!p){
return;
}
var x = p[0],
y = p[1],
x2 = p[2],
y2 = p[3];
scope.context.beginPath();
scope.context.lineWidth = BRUSH_SIZE + 1;
scope.context.moveTo(x, y);
scope.context.strokeStyle = "rgba(" + 255 + ", " + 255 + ", " + 255 + ", " + .3 + ")";
scope.context.lineTo(x2, y2);
scope.context.stroke();
scope.context.lineWidth = BRUSH_SIZE;
}
I know this is not exactly what you need, but I think this can be modified to get it.