I need the ability to start the raphael "drag" event on a circle object that is created when left mouse button is clicked.
Details:
I'm working on a piece of code that manipulates a closed path. I need these features:
1. moving existing points around by mouse drag
2. remove points via right-click
3. add points by left-click on the path, resulting in the path splitting at that location
4. if point is created by left-click, allow user to drag it to another location before releasing the mouse and dropping it
Here's my function that creates a new circle on a given raphael "path" object:
// create a new circle wrapped around the given point reference
function make_circle(point, path) {
var c = paper.circle(point[1], point[2], 6)
.attr({fill: "#DDD", stroke: "black"});
// record the point and path reference in the circle to allow updates
c.data("point", point).data("path", path);
// set event handlers
c.drag(dragmove,dragstart);
c.update = update_coordinates_circle;
c.mousemove(handle_mousemove_circle);
c.mouseup(handle_mouseup_circle);
c.mousedown(handle_mousedown_circle);
return c;
}
Then I can do this:
var pt1 = ["M", 0, 0],
pt2 = ["L", 10, 0],
pt3 = ["L", 10, 10],
pt4 = ["L", 0, 10],
point_set = [pt1, pt2, pt3, pt4, ["Z"]],
path = paper.path(point_set),
c1 = make_circle(pt1, path),
c2 = make_circle(pt2, path),
c3 = make_circle(pt3, path),
c4 = make_circle(pt4, path);
When creating new point via clicking on path, I do this:
make_circle(new_point, this).data("just_created", true);
... and the mousemove handler of this circle checks:
if (this.data("just_created")) { ... // follow mouse
My full code here: http://jsfiddle.net/T7XS3/
The problem is that because the circle radius is small, moving the mouse to quickly before releasing a new point breaks the mousemove handler since it's attached to the circle.
When moving an existing circle via its "drag" event, everything works fine. No matter how quickly the mouse moves the circle stays with it.
So again, is it possible to start a raphael "drag" event on an object that is created when the left mouse button is pressed, but has not yet been released?
Solution (http://jsfiddle.net/A27NZ/3/):
var width = 300,
height = 300,
paper_offset = 50,
maxX = width,
maxY = height,
paper = Raphael(paper_offset, paper_offset, 300, 300),
dragging;
make_path([100, 100], [200, 100], [200, 200], [100, 200], "green");
// reset 'dragging' to avoid initial drag
dragging = null;
// some math to determine if a point is between two other points, within some threshold
// based on: http://stackoverflow.com/questions/328107/how-can-you-determine-a-point-is-between-two-other-points-on-a-line-segment
function isBetween(a, b, c) {
var x1 = a[1],
x2 = b[1],
x3 = c[1],
y1 = a[2],
y2 = b[2],
y3 = c[2],
THRESHOLD = 1000;
var dotproduct = (x3 - x1) * (x2 - x1) + (y3 - y1) * (y2 - y1);
if (dotproduct < 0) return false; // early return if possible
var squaredlengthba = (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1);
if (dotproduct > squaredlengthba) return false; // early return if possible
var crossproduct = (y3 - y1) * (x2 - x1) - (x3 - x1) * (y2 - y1);
if (Math.abs(crossproduct) <= THRESHOLD) return true;
else return false;
}
function global_mousemove(e) {
if (dragging) {
handle_mousemove_circle.call(dragging, e);
}
}
function global_mouseup(e) {
dragging = null;
}
if (document.addEventListener) {
document.addEventListener("mousemove", global_mousemove, false);
document.addEventListener("mouseup", global_mouseup, false);
} else {
document.attachEvent('onmousemove', global_mousemove);
document.attachEvent('onmouseup', global_mouseup);
}
// move circle to given coordinates
function update_circle_xy(new_x, new_y) {
var point = this.data("point"),
path = this.data("path");
// don't follow the mouse outside the canvas, we don't want to lose the point
if (new_x <= 0) {
new_x = 0;
} else if (new_x >= maxX) {
new_x = maxX;
}
if (new_y < 0) {
new_y = 0;
} else if (new_y > maxY) {
new_y = maxY;
}
// update circle coords
this.attr({
cx: new_x,
cy: new_y
});
// update the referenced point
point[1] = new_x;
point[2] = new_y;
// redraw the path
path.attr({
path: path.data("point_set")
});
}
// move circle based on mouse event
function handle_mousemove_circle(e) {
var new_x = e.clientX - paper_offset,
new_y = e.clientY - paper_offset;
update_circle_xy.call(this, new_x, new_y);
}
// handle mouse down on circle
// e.which 1 = left click
// e.which 3 = right click
function handle_mousedown_circle(e) {
// remove the target point on right-click
if (e.which === 3) {
var path = this.data("path"),
point_set = path.data("point_set"),
point = this.data("point"),
index = point_set.indexOf(point);
// don't do anything if we only have 2 points left
// (checking if < 4 because last element is not a point ("Z"))
if (point_set.length < 4) return false;
// remove the target point
point_set.splice(index, 1);
// if removed point was head of set, make the following point the new head
if (index === 0) point_set[0][0] = "M";
// redraw the path
path.attr({
path: point_set
});
// finally, remove the circle
this.remove();
} else if (e.which === 1) {
dragging = this;
}
}
// handle mouse click on path
function handle_mousedown_path(e) {
// split on left-click
if (e.which === 1) {
var X = e.clientX - paper_offset,
Y = e.clientY - paper_offset,
new_point = ["L", X, Y],
point_set = this.data("point_set"),
index;
// "open" the path by removing the end ("Z")
point_set.pop();
for (var i = 0; i < point_set.length; i += 1) {
// cur point
var pt1 = point_set[i], // cur point
pt2; // next point
// circular wrap for next point
if (i === point_set.length - 1) {
pt2 = point_set[0];
} else {
pt2 = point_set[i + 1];
}
// check if these are the two points we want to split between
if (isBetween(pt1, pt2, new_point)) {
index = i + 1;
break;
}
}
// we should have found a place to insert the point, put it there
if (index) {
point_set.splice(index, 0, new_point);
} else {
return; // we didn't find a place to put the new point
}
// "close" the path with a ("Z")
point_set.push("Z");
// redraw the path
this.attr({
path: point_set
});
// create new circle to represent the new point
c = make_circle(new_point, this);
}
}
// create a new circle wrapped around the given point reference
function make_circle(point, path) {
var c = paper.circle(point[1], point[2], 6).attr({
fill: "#DDD",
stroke: "black"
});
// record the point and path reference in the circle to allow updates
c.data("point", point).data("path", path);
// set event handlers
c.mousedown(handle_mousedown_circle);
// start dragging the new circle
dragging = c;
return c;
}
// create a new colored path from four point coordinate pairs
function make_path(p1, p2, p3, p4, color) {
// starting points
var pt1 = ["M", p1[0], p1[1]],
pt2 = ["L", p2[0], p2[1]],
pt3 = ["L", p3[0], p3[1]],
pt4 = ["L", p4[0], p4[1]],
point_set = [pt1, pt2, pt3, pt4, ["Z"]],
path = paper.path(point_set).attr({
stroke: color,
"stroke-width": 5,
"stroke-linecap": "round"
});
// keep a reference to the set of points
path.data("point_set", point_set);
// add listener to the path to allow path-splitting
path.mousedown(handle_mousedown_path);
// create the circles that represent the points
make_circle(pt1, path);
make_circle(pt2, path);
make_circle(pt3, path);
make_circle(pt4, path);
}