I'm trying Snap to manipulate SVG but I'm having some issues when dragging a group of elements along a path.
I modified this code that I found here.
start = function () {
this.data("ox", +this.getBBox().cx);
this.data("oy", +this.getBBox().cy);
},
move = function (dx, dy) {
var tmpPt = {
x: this.data('ox') + dx,
y: this.data('oy') + dy
};
// move will be called with dx and dy
l = gradSearch(l, tmpPt);
pt = path.getPointAtLength(l);
if (!isNaN(pt.x) && !isNaN(pt.y)) {
this.transform('t' + (pt.x - this.data("ox")) + ',' + (pt.y - this.data("oy")));
};
},
end = function () {
console.log('End of drag');
}
I can drag the group, it stays where I drop it, but when I start to drag again, the group goes back to it's origin point and if I go crazy it starts to be really buggy.
I'm new to SVG, I tried some tutorials to learn but this stuff seams to be out of my league, I'd really appreciate some insights from you
Edit : It's better with my code, thanks for pointing that out Ian.
I think the problem is that you are resetting ox each time. In the previous examples, they used attr('cx') not ox, so they were always referencing the very original position of the shape to offset from. As you are resetting this each time, the difference will always be zero, so it resets to scratch. So you need a couple of modifications.
So lets all 'ox' the 'original position' before we do anything as our base.
And call 'sx' the 'starting position' that we figure out every drag start. We will need this to add 'dx' (the delta difference to add in a drag).
We can store the original position intitially...and then change the transform later to reference that original position, rather than the start position.
group.drag(move, start, end);
group.data("ox", +group.getBBox().cx);
group.data("oy", +group.getBBox().cy);
start = function () {
this.data("sx", +this.getBBox().cx);
this.data("sy", +this.getBBox().cy);
},
move = function (dx, dy) {
var tmpPt = {
x: this.data('sx') + dx,
y: this.data('sy') + dy
};
l = gradSearch(l, tmpPt);
pt = path.getPointAtLength(l);
// We change this!!!
if (!isNaN(pt.x) && !isNaN(pt.y)) {
this.transform('t' + (pt.x - this.data("ox")) + ',' + (pt.y - this.data("oy")) );
};
},
jsfiddle
Related
I have built a tool in paper.js that allows for the insertion of new control points, which works great, the problem I am running into, is that I must also calculate the handle position for each new control point (unless I am missing something, it does not appear that paper does this for you), and that is proving to be quite the task. The code below is what I currently have working, the point is added with the handles successfully, however it deforms the curve. I want to add the handles in such a way that the curve is not deformed.
gEditor.MoveTool.onMouseDown = function (event) {
gEditor.HitResult = paper.project.hitTest(event.point, gEditor.HitOptions);
var location = gEditor.HitResult.location;
var newPoint = gEditor.HitResult.item.insert(location.index + 1, event.point);
var prevSegment, nextSegment;
if (location.index - 1 >= 0){
prevSegment = gEditor.HitResult.item.segments[location.index - 1];
}
if (location.index + 2 < gEditor.HitResult.item.length) {
nextSegment = gEditor.HitResult.item.segments[location.index + 2];
}
if (prevSegment && nextSegment) {
newPoint.handleIn = {
x: prevSegment.point.x - ((prevSegment.point.x + newPoint.point.x) / 2),
y: prevSegment.point.y - ((prevSegment.point.y + newPoint.point.y) / 2),
};
newPoint.handleOut = {
x: nextSegment.point.x - ((nextSegment.point.x + newPoint.point.x) / 2),
y: nextSegment.point.y - ((nextSegment.point.y + newPoint.point.y) / 2),
};
}
}
I have looked at de Cateljau's algorithm, and assume that what I need is some form of this, but I am at a loss as to how to go about implementing it, since every example I have seen basically draws the curve, not finds the X,Y location of the handles.
Yep, paper.js already has this function: curve.divide(). After you've done your hit-test:
path = HitResult.item;
if (HitResult.type == 'stroke') {
var location = HitResult.location;
path.curves[location.index].divide(location);
}
I'm trying to do a simple animation in Raphael.js in which a paper.text object is moved from its current position to another position. Here is some of my code (there is far too much to post all of it):
songPos = getSongPosition(this, charIndex);
letter.path.animate({x: songPos.x, y: songPos.y, "font-size": this.correctFontSize}, 500, onAnimationComplete);
Here is the Letter object being referenced in the above code:
function Letter(args)
{
this.letter = args.letter || "A";
this.correct = args.correct || false;
this.transformation = args.transformation || "";
this.initX = args.x || 0;
this.initY = args.y || 0;
this.path = null;
}
Letter.prototype.buildPath = function()
{
this.path = paper.text(this.initX, this.initY, this.letter);
if(this.transformation)
{
this.path.transform(this.transformation);
}
return this;
};
The problem is I'm printing the x and y values returned by getSongPosition, and they're correct, but the animate method is sending my text object somewhere off-screen. I've also tried just setting the animation attributes to {x: 0, y: 0}, but I still get the same results. I can post more of the code if it is necessary.
Any help would be greatly appreciated.
UPDATE 1:
Part of my program requires I be able move objects to specific coordinates. Some of the objects will be rotated and others will not, so I wrote this:
Letter.prototype.getMoveTransform = function(x, y)
{
var bbox = this.path.getBBox(true);
var dx = x - bbox.x;
var dy = y - bbox.y;
return "T" + dx + "," + dy + (this.transformation == null ? "" : this.transformation);
};
I believe this is the root of my problem. This function is supposed to generate the transformation required to move a rotated object to (x, y). I'm not sure why I have to re-rotate the object on every animation.
UPDATE 2:
I have somehow solved my problem. I would post my solution, but I don't understand why any of this works/didn't work in the first place anymore.
this.path.getBBox(true); should be this.path.getBBox() or this.path.getBBox(false);
you need get transformed position every time to calculate the dx and dy
In the following fiddle, you can click and drag around the image, and it will not be able to exit the blue border. By clicking the red and green rectangles, you can rotate the image. However when you click and drag a rotated object, the image does not follow the mouse. I would like the image to follow the mouse even if it is rotated.
http://jsfiddle.net/n3Sn5/
I think the issue occurs within my move function
move = function (dx, dy)
{
nowX = Math.min(boundary.attr("x")+boundary.attr("width")-this.attr("width"), this.ox + dx);
nowY = Math.min(boundary.attr("y")+boundary.attr("height")-this.attr("height"), this.oy + dy);
nowX = Math.max(boundary.attr("x"), nowX);
nowY = Math.max(boundary.attr("y"), nowY);
this.attr({x: nowX, y: nowY });
}
One thing to notice is that when you click and drag a rotated object, after you release your mouse click, if you rotate the image, it snaps to where your mouse was when you released the mouse click, even obeying the boundary.
I was able to get the rotated image to drag with the mouse previously, but by adding the boundary rectangle, i had to use a more complex approach.
If anyone has an idea of what I need to change, I would be very grateful!
Thanks!
The required output can be achieved in a bit different way. Please check the fiddle at http://jsfiddle.net/6BbRL/. I have trimmed to code to keep the basic parts for demo.
var paper = Raphael(0, 0, 475, 475),
boxX = 100,
boxY = 100,
boxWidth = 300,
boxHeight = 200,
// EDITED
imgWidth = 50,
imgHeight = 50,
box = paper.rect(boxX, boxY, boxWidth, boxHeight).attr({fill:"#ffffff"}),
// EDITED
html5 = paper.image("http://www.w3.org/html/logo/downloads/HTML5_Badge_512.png",boxX+boxWidth-imgWidth,boxY+boxHeight-imgHeight,imgWidth,imgHeight)
.attr({cursor: "move"}),
elementCounterClockwise = paper.rect(180, 0, 50, 50).attr({fill:"#ff5555", cursor:"pointer"}),
elementClockwise = paper.rect(250, 0, 50, 50).attr({ fill: "#55ff55", cursor: "pointer" }),
boundary = paper.rect(50,50,400,300).attr({stroke: '#3333FF'}),
transform,
// EDITED
xBound = {min: 50 + imgWidth/2, max: 450 - imgWidth/2},
yBound = {min: 50 + imgHeight/2, max: 350 - imgHeight/2};
start = function (x, y) {
// Find min and max values of dx and dy for "html5" element and store them for validating dx and dy in move()
// This is required to impose a rectagular bound on drag movement of "html5" element.
transform = html5.transform();
}
move = function (dx, dy, x, y) {
// To restrict movement of the dragged element, Validate dx and dy before applying below.
// Here, dx and dy are shifts along x and y axes, with respect to drag start position.
// EDITED
var deltaX = x > xBound.max && xBound.max - x || x < xBound.min && xBound.min - x || 0;
deltaY = y > yBound.max && yBound.max - y || y < yBound.min && yBound.min - y || 0;
this.attr({transform: transform + 'T'+ [dx + deltaX, dy + deltaY]});
}
up = function () {
};
html5.drag(move, start, up);
elementClockwise.click(function() {
html5.animate({transform: '...r90'}, 100);
})
elementCounterClockwise.click(function() {
html5.animate({transform: '...r-90'}, 100);
})
Use of '...' to append a transformation to the pre-existing transformation state (Raphael API) is important for the rotational issue. While, for translating the element on drag requires absolute translation, which neglects the rotational state of the element while translating the element.
//EDIT NOTE
Drag bounding is worked on and updated. However, there remains an issue with incorporating the difference between mouse position and image center.
I can help you with your rotation and drag problem, you need to store the rotation and apply it after you have moved the object.
elementClockwise.node.onclick = function()
{
html5.animate({'transform': html5.transform() +'r90'}, 100, onAnimComplete);
}
elementCounterClockwise.node.onclick = function()
{
html5.animate({'transform': html5.transform() +'r-90'}, 100, onAnimComplete);
}
function onAnimComplete(){
default_transform = html5.transform();
}
At present I can't get the boundary to work, but will have a try later.
http://jsfiddle.net/n3Sn5/2/
I asked this question earlier but didn't communicated it clearly, so forgive me for the duplicate. This should be better.
I need to figure the position of a coordinate, given three other coordinates and 2 slopes. Basically, the intersection point of two lines. However, I don't have all of the information normally available to solve this.
I have an arbitrary shape defined by a bunch of vertices. The user may drag a line between these vertices and the shape should react as the diagrams below show.
So in the first example, the user drags line EF from the position on the left to the position on the right (line E2F2). What needs to happen is that line EF grows/shrinks such that it's slope stays the same and that it's beginning and ending coordinates remain on the lines DE and AF respectively. This is shown as line E2F2.
This needs to be generic enough that it can handle any sort of strange or regular angles I throw at it. The second set of shapes shows a simpler approach. The user drags line CD to the position of C2D2. Notice how the slopes stay the same and D2 essentially slides down that diagonal line and B2C2 and C2D2 both extend in length. The result is that all 3 slopes stay the same but lines B2C2 and C2D2 grow in length to stay connected, while line D2E2 shrinks.
You'll need to understand that when dragging line EF, you're actually moving the coordinate "E". So, figuring the first coordinate is easy. And the previous and next one's never change. So I essentially have the slopes of the 3 relevant lines and 3 of the 4 necessary coordinates. I need the 4th, so in my example, F2 or D2.
This code is called on an event every time the coordinate moves. Lets say we're dragging line EF - the coordinate is E then.
var next = this.model.get("next"), // coordinate F
nextNext = next.get("next"), // coordinate A
nextDx = nextNext.get("x") - next.get("x"), // delta X of AF
nextDy = nextNext.get("y") - next.get("y"), // delta Y of AF
prev = this.model.get("prev"), // coordinate D
prevDx = prev.get("x") - this.model.get("x"), // delta X of DF
prevDy = prev.get("y") - this.model.get("y"), // delta Y of DF
selfDx = next.get("x") - this.model.get("x"), // delta X of EF
selfDy = next.get("y") - this.model.get("y"), // delta Y of EF
selfX = this.initialCoords.x + this.shape.getX(), // the new position of E
selfY = this.initialCoords.y + this.shape.getY(),
selfM, selfB, prevM, prevB, nextM, nextB, m, x, y, b;
// check for would-be infinities
if (selfDx == 0) {
// **** THIS WHOLE BLOCK IS CORRECT ****
// i'm vertical
// we can safely assume prev/next aren't also vertical. i think? right?
prevM = prev.get("slope");
prevB = prev.get("y") - prevM * prev.get("x");
var myX = selfX,
myY = prevM * myX + prevB;
this.model.set({
x: myX,
y: myY
});
nextM = next.get("slope");
nextB = next.get("y") - nextM * next.get("x");
var nextX = selfX,
nextY = nextM * nextX + nextB;
next.set({
x: nextX,
y: nextY
});
} else if (selfDy == 0) {
//***** THIS WHOLE BLOCK IS CORRECT **** //
// i'm horizontal
if (prevDx == 0) {
// prev is a vertical line
this.model.set({
y: selfY
});
} else {
prevM = prev.get("slope");
prevB = prev.get("y") - prevM * prev.get("x");
var myY = selfY,
myX = (selfY - prevB) / prevM;
this.model.set({
x: myX,
y: myY
});
}
if (nextDx == 0) {
// next is a vertical line
next.set({
y: selfY
});
} else {
nextM = next.get("slope");
nextB = next.get("y") - nextM * next.get("x");
var nextY = selfY,
nextX = (selfY - nextB) / nextM;
next.set({
x: nextX,
y: nextY
});
}
} else {
// HELP HERE - you've chosen to drag an arbitrarily angled line. Figure out the "next" coordinate given the "current" one.
selfM = this.model.get("slope");
selfB = this.model.get("y") - this.model.get("slope") * this.model.get("x");
if (selfM < 0) {
prevM = prev.get("slope");
prevB = prev.get("y") - prevM * prev.get("x");
var myY = selfY,
myX = (selfY - prevB) / prevM;
// CORRECT, but need "next" position based on this
this.model.set({
x: myX,
y: myY
});
} else {
// CORRECT but need "next" position based on this.
var myX = selfX;
this.model.set({
x: myX
});
}
}
I had a similar situation and had some success using this page as reference :
http://en.wikipedia.org/wiki/Line-line_intersection
You should be able to enumerate all your lines testing for any points where they cross your moving line. These will be the new coordinates.
The equations in the wiki article assume lines of infinite length which you should be aware of but should actually be what you want (I think - there are probably edge cases).
I have a rect rotated by 45degrees when I try and move it in a straight line using the normal move drag functionality it moves with the 45degree rotation. I've seen a lot of posts regarding this and that this is intended to work like this but I haven't found a simple way to fix this.
I know about the raphael.free_transform.js plugin but I don't need 90% of the script.
From other posts I know I'm supposed to use Math.atan2 but alas my Math skills aren't that great.
My current move function looks like this:
function (dx, dy) {
var att = this.type == "rect" ? {x: this.ox + dx, y: this.oy + dy} : {cx: this.ox + dx, cy: this.oy + dy};
this.attr(att);
for (var i = connections.length; i--;) {
r.connection(connections[i]);
}
r.safari();
}
You have to use "transform" method instead of simply changing x-y attrs.
var data = {};
var R = Raphael('raphael', 500, 500);
var rect = R.rect(100, 100, 100, 50).attr({fill: "#aa5555"}).transform('r45');
var default_transform = rect.transform();
var onmove = function (dx, dy) {
rect.transform(default_transform + 'T' + dx + ',' + dy);
};
var onstart = function () {};
var onend = function () {
default_transform = rect.transform();
};
rect.drag(onmove, onstart, onend);
I have created a live demo for you on JSfiddle: http://jsfiddle.net/pybBq/
Please note that you have to use big T letter in transform string (not small t) to make transformation absolute and not relative. Please read Raphael's docs on transform for more info: http://raphaeljs.com/reference.html#Element.transform