SVG dragging for group - javascript

I am trying to achieve group and individual dragging inside the group. In the group there are 3 circles. Blue and grey circles has to drag individually (by onmousedown), and orange one is for group moving (by onclick).
The problem is that after dragging whole group, but you have to try at http://www.atarado.com/stackOF/drag-with%20problems.svg and see code.
Any help would be appreciate. Thanks.

I think I've fixed your problem: https://codepen.io/petercollingridge/full/djBjKm/
The issue was that the single dragging was altering the circle's cx and cy attributes, but the group drag was effecting the transformation of the whole group. I've simplified things so it all works using transformations and you only need a single set of functions for both:
function startMove(evt, moveType){
x1 = evt.clientX;
y1 = evt.clientY;
document.documentElement.setAttribute("onmousemove","moveIt(evt)")
if (moveType == 'single'){
C = evt.target;
}
else {
C = evt.target.parentNode;
}
}
function moveIt(evt){
translation = C.getAttributeNS(null, "transform").slice(10,-1).split(' ');
sx = parseInt(translation[0]);
sy = parseInt(translation[1]);
C.setAttributeNS(null, "transform", "translate(" + (sx + evt.clientX - x1) + " " + (sy + evt.clientY - y1) + ")");
x1 = evt.clientX;
y1 = evt.clientY;
}
function endMove(){
document.documentElement.setAttributeNS(null, "onmousemove",null)
}
Now you call startMove(evt, 'single') to move an single object, or startMove(evt, 'group') to move the group it belongs to.

Related

Dragging a rectangle in Raphael JS bounces back and forth

In this jsFiddle I have a Raphael JS grid with a rectangle that I need to move snapped to a grid. The rectangle has four red handles in each corner, and when it moves the handles move as well.
So far I can move the rectangle around (that works fine) but when I try to move it to the left the rectangle flickers and bounces back and forth. Could the problem be that I'm setting the x rectangle coordinate inside the drag move function? Any ideas will be greatly appreciated.
var move = function(dx, dy) {
rect.attr("x", ox + dx);
rect.attr("y", oy + dy);
leftTop.attr("x", ox1 + dx);
leftTop.attr("y", oy1 + dy);
rightTop.attr("x", ox2 + dx);
rightTop.attr("y", oy2 + dy);
leftBottom.attr("x", ox3 + dx);
leftBottom.attr("y", oy3 + dy);
rightBottom.attr("x", ox4 + dx);
rightBottom.attr("y", oy4 + dy);
if ((dx - lastdx) < 0)
seeMoveLeft(rect, leftTop, rightTop,
leftBottom, rightBottom);
lastdx = dx;
};
var up = function() {};
rect.drag(move, start, up);
var seeMoveLeft = function (rect, leftTop, rightTop,
leftBottom, rightBottom){
var left = rect.attr('x');
// find next left grid
var found = false;
var min = left - 40;
for (var i=left; i>=min; i--){
if (i % 40 == 0) {
found = true;
break;
}
}
if (found) {
var diff = left - i;
rect.attr('x', i);
var lt = leftTop.attr('x');
leftTop.attr('x', lt - diff);
var rt = rightTop.attr('x');
rightTop.attr('x', rt - diff);
var lb = leftBottom.attr('x');
leftBottom.attr('x', lb - diff);
var rb = rightBottom.attr('x');
rightBottom.attr('x', rb - diff);
}
}
The problem is that you set the new x of rect here:
rect.attr("x", ox + dx);
Then you modify it here:
if ((dx - lastdx) < 0)
seeMoveLeft(rect, leftTop, rightTop,
leftBottom, rightBottom);
But since move function is based on the mousemove event, sometimes, you'll get the same dx value 2 times in a row. This is normal with mousemove event, especially if you move slow. You can try it below, each time the clientX stays the same, the window will turn green.
var lastClientX;
window.addEventListener('mousemove', (e) => {
if (lastClientX && e.clientX === lastClientX) {
document.body.style.backgroundColor = 'green';
} else {
document.body.style.backgroundColor = 'yellow';
}
lastClientX = e.clientX;
})
body {
width: 100%;
height 100%;
}
What happens in your case when you have 2 consecutive identical dx is that x - lastdx won't validate and so the position won't be adjusted and will stay at rect.attr("x", ox + dx);. So first move event, the position is adjusted to the grid with seeMoveLeft, next, the position changes but isn't adjusted because dx is not smaller than lastdx, it's equal. Hence the flicker you see.
Simplest way to correct, would be to skip the positionning if dx is the same as lastdx. Like this:
if(lastdx !== dx){
rect.attr("x", ox + dx);
leftTop.attr("x", ox1 + dx);
rightTop.attr("x", ox2 + dx);
leftBottom.attr("x", ox3 + dx);
rightBottom.attr("x", ox4 + dx);
}
https://jsfiddle.net/ce7sh9ov/1/

How can I remove drag event on one element in a draggable group?

I have a draggable group, and one element in that group have called click event. Now I want to remove drag event on that element. See http://jsfiddle.net/gLvyouct/1/
I have tried these methods:
Add circle.call(drag).on(".drag", null);
Not working and I don't why.
Only call drag events on rects
Will affect the fluency of drag events. Elements are shaking when being dragged.
In definition of drag behavior if (d3.event.sourceEvent.target.nodeName == "circle") return;
Not working. The group still moves when your mouse is on the edge of circle.
This is a tricky one, because if you bind the drag event to the rect and then move the g that the rect belongs in, the calculated dx and dy values in the d3.event object will be messed up after each event fires, since those calculated values have been transformed into the local co-ordinate system of the element that the drag behaviour is bound to. So, we can do something similar ourselves to work around this. The trick here is knowing that we really just need to figure out the change in the x and y position of the mouse during the drag action. And that d3 makes these available in the sourceEvent property of the d3.event object.
So use the underlying sourceEvent in the d3.event object to get the clientX and clientY position and just calculate the difference between the last values and the current values, which will tell you how far extra you need to translate the g element. You also need to initialise the values that you're comparing to so that you don't get a random jump at the start. Luckily, we can use the dragstart event to do just that.
Once we've figured that out, we can then bind the drag handler to the rect and we should be good to go.
Here is an example doing exactly what I've described.
var g = d3.select("#mysvg").append("g");
var rect = g.append("rect").attr("height", 100).attr("width", 100);
var circle = g.append("circle").attr("cx", 200).attr("cy", 200).attr("r", 50).attr("fill", "black");
var transX = 0, transY = 0;
var lastX = 0, lastY = 0;
var drag = d3.behavior.drag().on("drag", function() {
transX += d3.event.sourceEvent.clientX - lastX;
transY += d3.event.sourceEvent.clientY - lastY;
g.attr("transform", "translate(" + transX + "," + transY + ")");
lastX = d3.event.sourceEvent.clientX;
lastY = d3.event.sourceEvent.clientY;
}).on("dragstart", function() {
lastX = d3.event.sourceEvent.clientX;
lastY = d3.event.sourceEvent.clientY;
});
rect.call(drag);
circle.on("click", function() {
var now_color = d3.select(this).attr("fill");
if (now_color == "black")
d3.select(this).attr("fill", "green");
else
d3.select(this).attr("fill", "black");
});
//circle.call(drag).on(".drag", null);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<svg id="mysvg" height="800" width="800"></svg>

Snap SVG: Dragging group doesn't update elements

JSFiddle here: JSFiddle
When dragging a group of objects, the individual objects' location attributes don't seem to be getting updated. This occurs whether I use the default drag() handler or define my own. Even the group BBox operation doesn't seem to update. Code:
var s = Snap("#svg");
var move = function (dx, dy, posx, posy) {
this.attr({
x: posx,
y: posy
});
//this.transform("t" + dx + "," + dy);
};
var block = s.rect(100, 100, 100, 100);
var circle = s.circle(100, 100, 50);
var group = s.g(block, circle);
//group.drag(move, function () {}, function () {});
group.drag();
//block.drag(move, function () {}, function () {});
//just a way to keep info coming w/o an interminable script
document.addEventListener('mousemove', function (e) {
bbox = block.getBBox();
block_x = block.attr("x");
block_y = block.attr("y");
gbbox = group.getBBox();
console.log("block is at " + block_x + "," + block_y,
" Block Bbbox is at " + bbox.x + "," + bbox.y,
" Group Bbbox is at " + gbbox.x + "," + gbbox.y);
}, false);
If I define only one object (say, a rect) and leave it out of a group, and pass my own "move" function to the call to drag, and include setting the "x" and "y" attributes explicitly, then that works. But if I include the rect in a group, then...I can't figure out how to do it, and I've tried a few ways (see the multiple commented-out lines showing things I've tried). I need to know where the rect sub-group element ends up after the drag, or at least the BBox of the whole group. Neither of these seem to be getting updated -- i.e. the console log I put in shows the same numbers forever, no matter where I move the object(s).
Can anyone help?
JSFiddle here: JSFiddle
I think this is because they are two different things, so they aren't actually interchangable.
The drag handler uses transforms. A transform doesn't affect any other attributes, its just an attribute on an element (in this case the group element).
getBBox will work in its current transform space, note this may be different to the clients (eg if the svg were zoomed in/out). So they are two slightly different methods, that do different things.
Use getBoundingClientRect if you need a bounding box relative to the client window. Use getBBox if you need a bounding box in the elements current coordinate space.
Code is using snap.svg.zpd as well, so zoom is possible. Problem is at onStopMove function. Events are fired when group is moved arround. In group is one circle(this.select('#main-inner-circle')) which does not have predefined location inside group. Im trying to get correct cx and cy of that inner circle after moving group.
self.onMove = function (dx, dy, ev, x, y) {
var clientX, clientY;
var tdx, tdy;
if ((typeof dx == 'object') && (dx.type == 'touchmove')) {
clientX = dx.changedTouches[0].clientX;
clientY = dx.changedTouches[0].clientY;
dx = clientX - this.data('ox');
dy = clientY - this.data('oy');
}
var snapInvMatrix = this.transform().diffMatrix.invert();
snapInvMatrix.e = snapInvMatrix.f = 0;
tdx = snapInvMatrix.x(dx, dy);
tdy = snapInvMatrix.y(dx, dy);
this.transform("t" + [tdx, tdy] + this.data('ot'));
}
self.onStartMove = function (x, y, ev) {
if ((typeof x == 'object') && (x.type == 'touchstart')) {
x.preventDefault();
this.data('ox', x.changedTouches[0].clientX);
this.data('oy', x.changedTouches[0].clientY);
}
this.data('ot', this.transform().local);
if (callbacks.onStartMove) {
callbacks.onStartMove();
}
}
self.onStopMove = function () {
var self = this.select('#main-inner-circle');
this.data('ot', this.transform().local);
//self.data('ot', self.transform().local);
console.log(self.getTransformedBBox());
console.log(this.getBBox());
//console.log($(self.node).offset().left - $(self.node).parent().offset().left);
var bBox = this.getBBox();
//var x = bBox.x + $(self.node).offset().left - $(self.node).parent().offset().left + self.getBBox().width / 2;
//var y = bBox.y + $(self.node).offset().top - $(self.node).parent().offset().top + self.getBBox().height / 2;
model.updateElementCoordinates(index, $(this.node).attr("rel"), { x: self.getTransformedBBox().cx, y: self.getTransformedBBox().cy });
if (callbacks.onStopMove) {
callbacks.onStopMove();
}
}
In order to post this question, I'd created the JSFiddle but left out the crucial snap.svg definitions...
<script src="http://snapsvg.io/assets/js/snap.svg-min.js"></script>
...with that, then indeed the group.getBBox() method actually works. However:
Apparently, using getBBox() is incredibly slow -- much slower than just accessing a "x" attribute of something like I was doing before grouping objects. All I know is that my code slows to a crawl if I use getBBox() (I have a lot of objects on the screen).
Further down in the same post mentioned earier ["Get coordinates of svg group on drag with snap.svg"1 recommended getBoundingClientRect(), which also works fine AND is fast enough! My new, working Fiddle showing all of these methods is here: New JSFiddle.
So, future users: use .node.getBoundingClientRect().

How to constrain a text element within an square with RaphaelJS?

I'm trying to constrain a text element with custom font within a square. I'm having difficulties to let the constrainment take place.
My code looks like this for the move function:
if (this.attr("y") > offsetY || this.attr("x") > offsetX) { // keep dragging & storing original x and y
this.attr({
x : this.ox + dx,
y : this.oy + dy
});
} else {
nowX = Math.min(offsetX, this.ox + dx);
nowY = Math.min(offsetY, this.oy + dy);
nowX = Math.max(0, nowX);
nowY = Math.max(0, nowY);
this.attr({
x : nowX,
y : nowY
});
}
The constrainment never takes place. However, if I use two squares with this code, it works. What am I overlooking here?
Thanks for your answers :)
If you used the default text-anchor value of 'middle' when you called paper.text(), the x and y attrs will return the coordinates of the center of the text span -- not its upper left corner, as it would with a rect.
Rather than using the x and y attributes, you should get your coordinates via element.getBBox(), and then use the x and y from the resulting object. That should enable your existing logic to work unimpeded.

straight line between two points

On a HTML canvas I have multiple points starting from 1 to N, this is basically a connect the dots application and is activated on touchstart.
There is validation so that they can only connect the dots from 1 and go to 2 (.. n). The issue is that right now is there is no validation that the line is a straight line and I am looking for an algorithm to do this, Here is what I have thought so far
For 2 points (x1,y1) to (x2,y2) get all the coordinates by finding the slope and using the formula y = mx + b
on touchmove get the x,y co-oridnates and make sure it is one of the points from the earlier step and draw a line else do not draw the line.
Is there a better way to do this or are there any different approaches that I can take ?
Edit: I originally misunderstood the question, it seems.
As far as validating the path: I think it would be easier just to have a function that determines whether a point is valid than calculating all of the values beforehand. Something like:
function getValidatorForPoints(x1, y1, x2, y2) {
var slope = (y2 - y1) / (x2 - x1);
return function (x, y) {
return (y - y1) == slope * (x - x1);
}
}
Then, given two points, you could do this:
var isValid = getValidatorForPoints(x1, y1, x2, y2);
var x = getX(), y = getY();// getX and getY get the user's new point.
if (isValid(x, y)) {
// Draw
}
This approach also gives you some flexibility—you could always modify the function to be less precise to accommodate people who don't quite draw a straight line but are tolerably close.
Precision:
As mentioned in my comment, you can change the way the function behaves to make it less exacting. I think a good way to do this is as follows:
Right now, we are using the formula (y - y1) == slope * (x - x1). This is the same as (slope * (x - x1)) - (y - y1) == 0. We can change the zero to some positive number to make it accept points "near" the valid line as so:
Math.abs((slope * (x - x1)) - (y - y1)) <= n
Here n changes how close the point has to be to the line in order to count.
I'm pretty sure this works as advertised and helps account for people's drawing the line a little crooked, but somebody should double check my math.
function drawGraphLine(x1, y1, x2, y2, color) {
var dist = Math.ceil(Math.sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2)));
var angle = Math.atan2(y2-y1, x2-x1)*180/Math.PI;
var xshift = dist - Math.abs(x2-x1);
var yshift = Math.abs(y1-y2)/2;
var div = document.createElement('div');
div.style.backgroundColor = color;
div.style.position = 'absolute';
div.style.left = (x1 - xshift/2) + 'px';
div.style.top = (Math.min(y1,y2) + yshift) + 'px';
div.style.width = dist+'px';
div.style.height = '3px';
div.style.WebkitTransform = 'rotate('+angle+'deg)';
div.style.MozTransform = 'rotate('+angle+'deg)';
}
// By Tomer Almog

Categories