D3, retrieve the x position of a <g> element and apply multiple transform - svg

With D3.js I want to transform the x of a group many times (eg. by clicking a button a causing a transform for every click), starting from the current position of the group. Problem is I can't retrieve the x of the group and, even if I find it, I can't find any built-in function that lets me change the x of a group starting from its current x. Am I missing something important? It's very strange I can't get the x of an element added to an SVG "stage".
My code is the following (edited for Lars): I created the nodes the way you suggested me yesterday (cloning them).
var m=[5,10,15,20,25,30];
var count=0;
d3.select('svg').on("mouseup", function(d, i){
count+=0.5;
var n = d3.selectAll(".myGroup");
n.each(function(d,i) {
if(i!=0){
// here I need to know the current x of the current n element but I
// can't get it
// this causes error "Uncaught TypeError: undefined is not a function"
// var currentx = d3.transform(this.attr("transform")).translate[0];
this.setAttribute("transform", "translate("+((100/m[i])*count)+",10)");
}
});
});

Getting and changing the position of a g element is relatively straightforward. For example like this:
var g = d3.select("#myG");
// get x position
var currentx = d3.transform(g.attr("transform")).translate[0];
// set x position
g.attr("transform", "translate(" + (currentx + 100) + ",0)");
Edit:
If you have a raw DOM element, select it with D3 first:
var currentx = d3.transform(d3.select(this).attr("transform")).translate[0];

Related

Game Maker - Checking position of multiple objects of same name

How do you check the position values of two of the same object in a room? I have two of the same object in my room and need to find the x and y positioning of each.
with (objName) {
// now x is each objName.x, and y is each objName.x, while to access object from which you called you need to use other.xxx (except VAR defined variables)
}
for example, to get all X positions:
var xpos = [], _a = 0;
with (objName) {
xpos[a++] = x;
}
// xpos[0] is x of first object, xpos[1] of second etc.

Moving one end of a Phaser.Graphics line

For a project I'm trying to draw a moving line in Phaser. I initially drew it using game.debug.geom(line), but that is not really the right way to do it, since it doesn't allow for styling, and because the debugger takes a toll in performance.
Reading the docs, it seems to me that the way to do it would be with a Phaser.Graphics object, but I haven't been able to get it to work. As an example, I tried making a line move as the hand of a clock, with one end fixed and the other moving around it.
I thought it would be fine to create the Graphics object in the middle and then in update use reset to clear it and bring it back to the center, and then lineTo to make the rest of the line. But instead what I get is a line coming outwards from the centre, and then a ring.
Picture for sadness:
I made a pen with my attempts. The code is repeated below. What I would like to have is a line (lines?) coming from the center of the circle to the points in the circumference.
Is a Graphics object the best way to do that? How do I do it?
Demo.prototype = {
create: function() {
this.graphics = game.add.graphics(
game.world.centerX,
game.world.centerY
);
this.graphics.lineStyle(2, 0xffd900);
this.counter = 0;
this.step = Math.PI * 2 / 360;
this.radius = 80;
},
update: function() {
this.graphics.reset(
this.game.world.centerX,
this.game.world.centerY
);
var y = this.radius * Math.sin(this.counter);
var x = this.radius * Math.cos(this.counter);
this.graphics.lineTo(x, y);
this.counter += this.step;
}
};
You may want to check out this Phaser game called Cut It (not my game btw, found it here).
It also draws a variable length dotted line by cleverly using the Phaser.TileSprite, and then changing its width.
TileSprite draws a repeating pattern, and you can use this to draw a line by drawing one bitmap of a linepart segment, use that as background of the TileSprite and make the height of the TileSprite the same as the height of the bitmap.
You can take a look at the game's source code, it's compressed and minified but still somewhat readable. You can look for the variable called cut_line.
I finally understood that the coordinates taken by the Phaser.Graphics object are local, respective to the object's internal coordinate system. Using moveTo(0, 0) has the desired result of moving the object's drawing pointer back to its origin (and not, as I initially thought, to the origin of the game world). Using reset(0, 0), on the other hand, would have the effect of moving the object's origin to the world's origin.
As for deleting the previous lines, the only method I've found is to manually clear the object's graphicsData Array (short of calling destroy() and creating an entirely new object, which is probably not a very good idea).
Replacing the code in the original question with this does the trick:
Demo.prototype = {
create: function() {
this.graphics = game.add.graphics(
game.world.centerX,
game.world.centerY
);
this.graphics.lineStyle(2, 0xffd900);
this.counter = 0;
this.step = Math.PI * 2 / 360;
this.radius = 80;
},
update: function(){
// Erases the previous lines
this.graphics.graphicsData = [];
// Move back to the object's origin
// Coordinates are local!
this.graphics.moveTo( 0, 0 );
var y = this.radius * Math.sin(this.counter);
var x = this.radius * Math.cos(this.counter);
this.graphics.lineTo(x, y);
this.counter += this.step;
}
};

Update path of existing SVG element

I am trying to draw a border on svg element using code.
elem.attr({
'stroke-width' : 2,
'stroke' : '#3fa9f5'
});
elem is already drawn and I dont have control how its drawn . When I see its path ie elem.d , Z is not present at end. Because of that I am not able to draw border at one end.
elem.d="M 323.5 8 L 323.5 40 409.5 40 409.5 8"
Can I dynamically add Z to the above element? Adding Z to elem.d string is not working.
Code for adding Z to elem.d
if(elem.d !== undefined){
if(elem.d.indexOf("Z") === -1){
elem.d += " Z";
}
}
There's no elem.d property. The interface is SVGAnimatedPathData
So it's elem[0].pathSegList.appendItem to append the Z. And createSVGPathSegClosePath to create it which means putting it all together looks like this.
elem[0].pathSegList.appendItem(elem[0].createSVGPathSegClosePath());
The [0] unwraps the jquery wrapper on the element (if elem is a raw DOM element object then the [0] is not necessary)
If you want to test whether the last segment is a close path then you want something like this...
var pathSegList = elem[0].pathSegList;
if (pathSegList.numberOfItems > 0) {
var lastSeg = pathSegList.getItem(pathSegList.numberOfItems - 1);
if (lastSeg.pathSegType != lastSeg.PATHSEG_CLOSEPATH) {
// do whatever
}
}
Alternatively you could do with with attr (jquery) or get/setAttribute (DOM)
elem.attr(d) will get the attribute as a string as will elem[0].getAttribute("d") The SVG DOM above will give better perfomance though.

Snap SVG: Using 'for loop' to Transform/Translate x-position

Using snap.svg.js. Trying to translate the xPos but nothing happens.
Here is the example jsfiddle.net/hswuhdj4
window.objectPool = {
rectQ1: paper.rect(0,0,0, svgHeight).attr({fill:lighterBlue}),
rectQ2: paper.rect(0,0,0, svgHeight).attr({fill:lighterBlue}),
rectQ3: paper.rect(0,0,0, svgHeight).attr({fill:lighterBlue}),
rectQ4: paper.rect(0,0,0, svgHeight).attr({fill:lighterBlue}),
rectQ5: paper.rect(0,0,0, svgHeight).attr({fill:lighterBlue}),
rectQ6: paper.rect(0,0,0, svgHeight).attr({fill:lighterBlue})
}
I use an objectpool so i can reuse my objects to keep performance.
window.rectsQ = [
objectPool.rectQ1,
objectPool.rectQ2,
objectPool.rectQ3,
objectPool.rectQ4,
objectPool.rectQ5,
objectPool.rectQ6
];
pushing them in an Array rectsQ for easy future access
var rectAmount = 6;
var rectWidth = 100;
for(i=0;i<rectAmount;i++){
paper.node.appendChild(window.rectsQ[i].node); //immitates toFront() function of Raphael.
window.rectsQ[i].attr({width:rectWidth}); //assigning a width
window.rectsQ[i].transform('T' + (svgWidth-(rectWidth*(i+1))) + ' 0');
}
First, I call the object back to the front, then assign a width, finally translate the x-pos, to the right side of the svg-tag.
It doesn't seem too difficult, but for some reason, no matter what transform i do, the object doesn't move.
//It stays right at these coordinates:
x = 0,
y = 0
//while it should be moved to:
x = svgWidth-rectWidth,
y = 0
I've tried using a Relative Translation ('t') instead of absolute Translation ('T'). No luck though.
Does anyone have an idea to why these snap objects won't move, and how to fix it?
Removing the 2 extra arguments helped in the JSFiddle i made, but weirdly enough not in my project.
This is the JSFiddle: http://jsfiddle.net/hswuhdj4/3
FIXED!!
What caused the problem was the local snap.svg.js file.
Changing the directory with the link raw.githubusercontent.com/adobe-webplatform/Snap.svg/master/dist/snap.svg-min.js fixed the problem for me.
Does anyone know how this occurred?
Transform usually takes 2 arguments. I.e. T x y you're giving it 4 though.
The documentation says it works like paths, and if so T x y 0 0 would be the same as T x y T 0 0 which would move the rect to x y and then move it back again to 0 0.

How to avoid the overlapping of text elements on the TreeMap when child elements are opened in D3.js?

I created a Tree in D3.js based on Mike Bostock's Node-link Tree. The problem I have and that I also see in Mike's Tree is that the text label overlap/underlap the circle nodes when there isn't enough space rather than extend the links to leave some space.
As a new user I'm not allowed to upload images, so here is a link to Mike's Tree where you can see the labels of the preceding nodes overlapping the following nodes.
I tried various things to fix the problem by detecting the pixel length of the text with:
d3.select('.nodeText').node().getComputedTextLength();
However this only works after I rendered the page when I need the length of the longest text item before I render.
Getting the longest text item before I render with:
nodes = tree.nodes(root).reverse();
var longest = nodes.reduce(function (a, b) {
return a.label.length > b.label.length ? a : b;
});
node = vis.selectAll('g.node').data(nodes, function(d, i){
return d.id || (d.id = ++i);
});
nodes.forEach(function(d) {
d.y = (longest.label.length + 200);
});
only returns the string length, while using
d.y = (d.depth * 200);
makes every link a static length and doesn't resize as beautiful when new nodes get opened or closed.
Is there a way to avoid this overlapping? If so, what would be the best way to do this and to keep the dynamic structure of the tree?
There are 3 possible solutions that I can come up with but aren't that straightforward:
Detecting label length and using an ellipsis where it overruns child nodes. (which would make the labels less readable)
scaling the layout dynamically by detecting the label length and telling the links to adjust accordingly. (which would be best but seems really difficult
scale the svg element and use a scroll bar when the labels start to run over. (not sure this is possible as I have been working on the assumption that the SVG needs to have a set height and width).
So the following approach can give different levels of the layout different "heights". You have to take care that with a radial layout you risk not having enough spread for small circles to fan your text without overlaps, but let's ignore that for now.
The key is to realize that the tree layout simply maps things to an arbitrary space of width and height and that the diagonal projection maps width (x) to angle and height (y) to radius. Moreover the radius is a simple function of the depth of the tree.
So here is a way to reassign the depths based on the text lengths:
First of all, I use the following (jQuery) to compute maximum text sizes for:
var computeMaxTextSize = function(data, fontSize, fontName){
var maxH = 0, maxW = 0;
var div = document.createElement('div');
document.body.appendChild(div);
$(div).css({
position: 'absolute',
left: -1000,
top: -1000,
display: 'none',
margin:0,
padding:0
});
$(div).css("font", fontSize + 'px '+fontName);
data.forEach(function(d) {
$(div).html(d);
maxH = Math.max(maxH, $(div).outerHeight());
maxW = Math.max(maxW, $(div).outerWidth());
});
$(div).remove();
return {maxH: maxH, maxW: maxW};
}
Now I will recursively build an array with an array of strings per level:
var allStrings = [[]];
var childStrings = function(level, n) {
var a = allStrings[level];
a.push(n.name);
if(n.children && n.children.length > 0) {
if(!allStrings[level+1]) {
allStrings[level+1] = [];
}
n.children.forEach(function(d) {
childStrings(level + 1, d);
});
}
};
childStrings(0, root);
And then compute the maximum text length per level.
var maxLevelSizes = [];
allStrings.forEach(function(d, i) {
maxLevelSizes.push(computeMaxTextSize(allStrings[i], '10', 'sans-serif'));
});
Then I compute the total text width for all the levels (adding spacing for the little circle icons and some padding to make it look nice). This will be the radius of the final layout. Note that I will use this same padding amount again later on.
var padding = 25; // Width of the blue circle plus some spacing
var totalRadius = d3.sum(maxLevelSizes, function(d) { return d.maxW + padding});
var diameter = totalRadius * 2; // was 960;
var tree = d3.layout.tree()
.size([360, totalRadius])
.separation(function(a, b) { return (a.parent == b.parent ? 1 : 2) / a.depth; });
Now we can call the layout as usual. There is one last piece: to figure out the radius for the different levels we will need a cumulative sum of the radii of the previous levels. Once we have that we simply assign the new radii to the computed nodes.
// Compute cummulative sums - these will be the ring radii
var newDepths = maxLevelSizes.reduce(function(prev, curr, index) {
prev.push(prev[index] + curr.maxW + padding);
return prev;
},[0]);
var nodes = tree.nodes(root);
// Assign new radius based on depth
nodes.forEach(function(d) {
d.y = newDepths[d.depth];
});
Eh voila! This is maybe not the cleanest solution, and perhaps does not address every concern, but it should get you started. Have fun!

Resources