How to correctly make sets draggable in Raphael - svg

One often wants to make a set of objects in Raphael draggable, but using .transform() to do so can be maddening. Say you start like this:
var paper = Raphael(0, 0, 500, 500);
var set = paper.set();
set.push(paper.circle(100,100,35), paper.circle(150,150,15));
set.attr("fill", "orange");
set.data("myset", set);
set.drag(
function(dx, dy, x, y, e) {
this.data('myset').transform("T" + dx + "," + dy);
},
function(x, y, e) {},
function(e) {}
);
If you try this out, you see it works once. But if you drag, stop, then drag again, it resets the position to 0,0 relative to the original position, as you'd expect from .transform(). No good.
A variant of this question has been touched on here, and the respondent suggested prepending transforms with "...". That's all fine and good, but for two things:
you still have to track previous position, since you don't want to
translate by (dx,dy) on every call of dragmove, which will send
the objects flying off the screen.
I worry about creating a monster
transform if an object is dragged many times. (Though maybe I
shouldn't.)
My tentative solution is to track the offset from the original positioning in another key/value pair, like so:
var paper = Raphael(0, 0, 500, 500);
var set = paper.set();
set.push(
paper.circle(100,100,35),
paper.circle(150,150,15)
);
set.attr("fill", "orange");
set.data("myset", set);
set.data("position", [0,0]);
var current_position = [0,0];
set.drag(
function(dx, dy, x, y, e) {
this.data('myset').transform("T" + (this.data("position")[0] + dx) + "," + (this.data("position")[1] + dy));
current_position = [dx,dy];
},
function(x, y, e) {
},
function(e) {
this.data('myset').data("position", [
this.data("position")[0] += current_position[0],
this.data("position")[1] += current_position[1]
]);
}
);
You can see it in action here.
It works, but it feels incredibly sloppy. I must be missing something obvious, right?

My answer is similar to your last variant:
var onmove = function (dx,dy){
this.transform(this.default_transform+'T'+dx+','+dy);
},
onstart = function (){
this.default_transform = this.transform();
},
onend = function(){
this.default_transform = this.transform();
};
set.drag(onmove, onstart, onend);
Don't worry it won't create a long long line of transformations because Raphael converts everything to one whole matrix transformation so it doesn't build up each time you move an object.

Related

PhaserJS: After Rotation of camera dragging a Sprite gives strange coords

Basically the problem is that after you rotate the camera, the points that are given as arguments in the callback for dragging are not what I expected. I'm guessing I have to Rotate the given points also but I just couldn't.
Can Someone explain what is going on, is this some kind of bug or what should I do in order the sprite to follow the mouse cursor?
Easiest way to explain the problem is to reproduce it:
1) Go to Phaser Example Runner
2) Copy- Paste this code:
var config = {
type: Phaser.WEBGL,
parent: 'phaser-example',
scene: {
preload: preload,
create: create
}
};
var game = new Phaser.Game(config);
function preload ()
{
this.load.image('eye', 'assets/pics/lance-overdose-loader-eye.png');
}
function create ()
{
var image = this.add.sprite(200, 300, 'eye').setInteractive();
this.cameras.main.setRotation(Math.PI);
image.on('pointerover', function () {
this.setTint(0x00ff00);
});
image.on('pointerout', function () {
this.clearTint();
});
this.input.setDraggable(image);
this.input.on('dragstart', function (pointer, gameObject) {
gameObject.setTint(0xff0000);
});
this.input.on('drag', function (pointer, gameObject, dragX, dragY) {
console.log(`x: ${dragX}, y: ${dragY}`);
gameObject.x = dragX;
gameObject.y = dragY;
});
this.input.on('dragend', function (pointer, gameObject) {
gameObject.clearTint();
});
}
3) Open the console, drag around the Eye and look at what coordinates are given.
4) If you remove line 24 (the rotation of the camera) Everything works as expected.
(The example is taken from Phaser 3 Official examples and a bit changed for the bug)
According to Phaser's API Documentation on the setRotation() method, the rotation given in radians applies to everything rendered by the camera. Unfortunately, your pointer isn't rendered by the camera so it doesn't get the same rotated coordinates. Not sure if this is a bug with the library or just a poorly documented exception, but I believe there is a workaround.
Create 2 variables to hold an initial position and a final position:
var image = this.add.sprite(200, 300, 'eye').setInteractive();
var initial = [];
var final = [];
Populate the initial position in your .on('dragstart') method:
this.input.on('dragstart', function (pointer, gameObject) {
initial = [
gameObject.x,
gameObject.y,
pointer.x,
pointer.y
];
gameObject.setTint(0xff0000);
});
Then, populate the final variable in your .on('drag') method:
this.input.on('drag', function (pointer, gameObject, dragX, dragY) {
final = [
gameObject.x, // not necessary but keeping for variable shape consistency
gameObject.y, // not necessary but keeping for variable shape consistency
pointer.x,
pointer.y
];
gameObject.x = initial[0] + (initial[2] - final[2]);
gameObject.y = initial[1] + (initial[3] - final[3]);
});
All we're doing here is keeping track of the change in pointer position and mimicking that change in our gameObject.

raphael change animation direction

simple by using rapheal i successfully make animation along path , but i can't reverse the animation direction ,,, just how to make it animate to the other direction when clicking the same path .
var paper = Raphael(0,0,1024,768);
var pathOne = paper.path(['M', 15,15 , 100,75]).attr({'stroke-width':18}).data("id",1);
//and this is just the circle
var circle = paper.circle(0, 0, 13).attr({
fill: '#09c', cursor: 'pointer'
});
//make the path as custom attribute so it can ba accessed
function pathPicker(thatPath){
paper.customAttributes.pathFactor = function(distance) {
var point = thatPath.getPointAtLength(distance * thatPath.getTotalLength());
var dx = point.x,
dy = point.y;
return {
transform: ['t', dx, dy]
};
}
}
//initialize for first move
pathPicker(pathOne);
circle.attr({pathFactor: 0}); // Reset
//Asign first path and first move
function firstMove(){
circle.animate({pathFactor: 1}, 1000});
}
pathOne.click(function(){
firstMove();
});
I couldn't get the original to run, so here is something using the main bits that should suit...
There's not a lot to it, get the length of the path, iterate over the length, draw object at the path. It uses the Raphael customAttributes to be able to animate it. I've added a custom toggle to make it easy to switch between them.
These are the key changes..
var len = pathOne.getTotalLength();
paper.customAttributes.along = function (v) {
var point = pathOne.getPointAtLength(v * len);
return {
transform: "t" + [point.x, point.y] + "r" + point.alpha
};
};
circle.attr({ along: 0 });
function animateThere( val ) {
val = +!val; // toggle
pathOne.click( function() { animateThere( val ) } );
circle.animate({ along: val }, 2000 );
};
pathOne.click( function() { animateThere(0) } );
jsfiddle
For completeness, you may want to do some extra checks like only allow the click if the animation has finished or something, as there may be a problem if you quickly click a lot and it buffering up animations.

How to get web element (id) from the element position in D3.js force graph

I am working with the D3.js force graph but I am not able to find out the element id from the element position (which I know).
I am using Leap motion. I need to simulate a mouse event (a click, a move, a drag, etc.) without a mouse. And, if I am right, in order to be able to do this, I need to find out what is the the element id from the coordinates x and y (these coordinates I know from the Leap motion pointer). So from what you wrote above, I need to find out the ('.node’).
Here is what I already tried but it did not work:
Is it possible to use non-mouse, non-touch events to interact with a D3.js graph? If so, what is the most efficient way to go about it?
So I used this function (see below), but I need to know the element id to make it work correctly:
//graph.simulate(document.getElementById("r_1"), 'dblclick', {pointerX: posX, pointerY: posY});
//here id r_1 is hardcoded, but I need to find out id from x and y coordinates.
this.simulate = function (element, eventName) {
function extend(destination, source) {
for (var property in source)
destination[property] = source[property];
return destination;
}
var eventMatchers = {
'HTMLEvents': /^(?:load|unload|abort|error|select|change|submit|reset|focus|blur|resize|scroll)$/,
'MouseEvents': /^(?:click|dblclick|mouse(?:down|up|over|move|out))$/
};
var defaultOptions = {
pointerX: 0,
pointerY: 0,
button: 0,
ctrlKey: false,
altKey: false,
shiftKey: false,
metaKey: false,
bubbles: true,
cancelable: true
};
var options = extend(defaultOptions, arguments[2] || {});
var oEvent, eventType = null;
for (var name in eventMatchers) {
if (eventMatchers[name].test(eventName)) {
eventType = name;
break;
}
}
if (!eventType)
throw new SyntaxError('Only HTMLEvents and MouseEvents interfaces are supported');
if (document.createEvent) {
oEvent = document.createEvent(eventType);
if (eventType == 'HTMLEvents') {
oEvent.initEvent(eventName, options.bubbles, options.cancelable);
}
else {
oEvent.initMouseEvent(eventName, options.bubbles, options.cancelable, document.defaultView,
options.button, options.pointerX, options.pointerY, options.pointerX, options.pointerY,
options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, options.button, element);
}
element.dispatchEvent(oEvent);
}
else {
options.clientX = options.pointerX;
options.clientY = options.pointerY;
var evt = document.createEventObject();
oEvent = extend(evt, options);
element.fireEvent('on' + eventName, oEvent);
}
return element;
}
Many thanks for your help and ideas.
If you want access to the element, it's implicit in D3's iterators via this.
d3.selectAll('.node').each(function(d) {
console.log(this); // Logs the element attached to d.
});
If you really need access to the id, you can get it with selection.attr():
d3.selectAll('.node').each(function() {
console.log(d3.select(this).attr('id')); // Logs the id attribute.
});
You don't have to use each. Any of the iterators, such as attr or style, etc., have 'this' as the bound element:
d3.selectAll('.node').style('opacity', function(d) {
console.log(this);// Logs the element attached to d.
});
If you want the x and y coordinates of a node, it's part of the data:
d3.selectAll('.node').each(function(d) {
console.log(d.x, d.y); // Logs the x and y position of the datum.
});
If you really need the node attributes themselves, you can use the attr accessor.
d3.selectAll('.node').each(function(d) {
// Logs the cx and cy attributes of a node.
console.log(d3.select(this).attr('cx'), d3.select(this).attr('cy'));
});
EDIT: It looks like you need an element reference, but the only thing you know about the node in context is its position. One solution is to search through all nodes for a node with matching coordinates.
// Brute force search of all nodes.
function search(root, x, y) {
var found;
function recurse(node) {
if (node.x === x && node.y === y)
found = node;
!found && node.children && node.children.forEach(function(child) {
recurse(child);
});
}
recurse(root);
return found;
}
However this only gives you the node object, not the element itself. You will likely need to store the element references on the nodes:
// Give each node a reference to its dom element.
d3.selectAll('.node').each(function(d) {
d.element = this;
});
With that in place, you should be able to access the element and get its id.
var id, node = search(root, x, y);
if (node) {
id = node.element.getAttribute('id');
}
The brute-force search is fine for a small number of nodes, but if you're pushing a large number of nodes you might want to use D3's quadtree (example) to speed up the search.
Use d3.select('#yourElementId')
For more info check this out: https://github.com/mbostock/d3/wiki/Selections

How to smoothen lines using SVG?

I am looking at this example. How this can be done using Raphael for below example ?
Raphael("canvas", function () {
var win = Raphael._g.win,
doc = win.document,
hasTouch = "createTouch" in doc,
M = "M",
L = "L",
d = "d",
COMMA = ",",
// constant for waiting doodle stop
INTERRUPT_TIMEOUT_MS = hasTouch ? 100 : 1,
// offset for better visual accuracy
CURSOR_OFFSET = hasTouch ? 0 : -10,
paper = this,
path = "", // hold doodle path commands
// this element draws the doodle
doodle = paper.path(path).attr({
"stroke": "rgb(255,0,0)"
}),
// this is to capture mouse movements
tracker = paper.rect(0, 0, paper.width, paper.height).attr({
"fill": "rgb(255,255,255)",
"fill-opacity": "0.01"
}),
active = false, // flag to check active doodling
repath = false, // flag to check if a new segment starts
interrupt; // this is to connect jittery touch
tracker.mousedown(function () {
interrupt && (interrupt = clearTimeout(interrupt));
active = true;
repath = true;
});
tracker.mousemove(function (e, x, y) {
// do nothing if doodling is inactive
if (!active) {
return;
}
// Fix for Raphael's touch xy bug
if (hasTouch &&
(e.originalEvent.targetTouches.length === 1)) {
x = e.clientX +
(doc.documentElement.scrollTop || doc.body.scrollTop || 0);
y = e.clientY +
(doc.documentElement.scrollLeft || doc.body.scrollLeft || 0);
e.preventDefault();
}
// Insert move command for a new segment
if (repath) {
path += M + (x + CURSOR_OFFSET) + COMMA +
(y + CURSOR_OFFSET);
repath = false;
}
path += L + (x + CURSOR_OFFSET) + COMMA +
(y + CURSOR_OFFSET); // append line point
// directly access SVG element and set path
doodle.node.setAttribute(d, path);
});
// track window mouse up to ensure mouse up even outside
// paper works.
Raphael.mouseup(function () {
interrupt && (interrupt = clearTimeout(interrupt));
// wait sometime before deactivating doodle
interrupt = setTimeout(function () {
active = false;
}, INTERRUPT_TIMEOUT_MS);
});
Above code is copied from https://stackoverflow.com/a/17781275/1595858
To create a smooth line through several points, use Raphael's Catmull-Rom extension to SVG paths. See: http://raphaeljs.com/reference.html#Paper.path .
// Creating a line:
var line = paper.path("M x0 y0 R x1 y1 x2 y2 x3 y3 x4 y4");
// Adding next point:
line.attr("path", line.attr("path") + " x5 y5");
The easiest way to smoothen the lines in your case (using Raphael) is to use the Raphael._path2curve function.
The place where you are doing doodle.node.setAttribute(d, path); replace it with the following line, thus replacing all line segments with curves.
doodle.node.setAttribute(d, Raphael._path2curve(path));
Note that this will result in degradation of performance. There is a huge scope of improvement of this by realtime converting path to curves instead of converting the entire path every time.

Recalculate points in an SVG path when first or last point changes

I have built a graph with D3.js based on this example of a force-directed graph, but rather than having straight lines between the nodes I am creating curved lines using SVG path elements. The data structure for an individual link includes a source and target which represent the nodes to which the link is connected. Also the link data structure also contains a line element which contains an array of points defining the path. The data structure for a link looks like this:
{
id:4,
type:"link",
fixed:true,
source: {
id:1,
name: "A",
type:"node",
x:226,
y:190,
fixed:1,
index:0,
weight:1,
},
target: {
id:2,
name: "B",
type:"node",
x:910,
y:85,
fixed:1,
index:1,
weight:1,
},
line:[{x:387, y:69}, {x:541.5, y:179}, {x:696, y:179}]
}
Now in my on tick event handler I have the current x and y co-ordinates for for nodes A and B by means of the references d.source.x, d.source.y and d.target.x, d.target.y. I also have the initial position of node A (first element of d.line) and of node B (last element of d.line). What I am trying to do is to recalculate the points in between the first and last points based on the changes made to the positions of nodes A and B.
Here is my on tick event handler:
force.on("tick", function() {
svg.selectAll("g.node").attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
svg.selectAll("g.link .line").attr("d", function(d) {
var xOffset = 0, // Want to calculate this
yOffset = 0; // Want to calculate this
var line = [ ];
for (var i=0; i<d.line.length; i++) {
if (i==0) {
line[line.length] = { x : d.source.x, y: d.source.y }
}
else if (i==d.line.length-1) {
line[line.length] = { x : d.target.x, y: d.target.y }
}
else {
line[line.length] = { x: d.line[i].x + xOffset, y: d.line[i].y + yOffset }
}
}
return self.lineGenerator(line);
})
});
Not offsetting the x and y co-ordinates for the points in the middle of the path results in these points staying static when nodes A or B are dragged. Can anyone explain how to go about calculating xOffset and yOffset so that the path will correctly move/rotate when the nodes are dragged? Or if anyone knows of a better (read "easier"!) way of accomplishing this with D3.js then please let me know.
UPDATE:
Here is my line generator code in the chart constructor:
this.lineGenerator = d3.svg.line()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; })
.interpolate("cardinal");
Let me explain the problem with some images, hopefully they will make the problem that I am having clearer. It is not a matter of the lines being linear, my lines are curved by the line generator, but when I drag a node, I want the curve to drag as well. So all the points on the line must update to reflect the change made to the position of the node. In other words the path must not get distorted by the nodes being moved.
So, in pictures; given this situation:
If I drag node B down and to the left, I want to see something like this:
(Note that this was rotated with image editting software, hence the background is also rotated)
Instead I am getting something like this:
Note how the one intermediary point in the line has stayed static when node B was moved, causing the curve to distort. This is what I am trying to avoid happening.
Hopefully this makes the problem clearer.

Resources