How to extend the Diagonal Object in D3.js? [duplicate] - javascript

I'm very new to d3.js (and SVG in general), and I want to do something simple: a tree/dendrogram with angled connectors.
I have cannibalised the d3 example from here:http://mbostock.github.com/d3/ex/cluster.html
and I want to make it more like the protovis examples here:
http://mbostock.github.com/protovis/ex/indent.html
http://mbostock.github.com/protovis/ex/dendrogram.html
I have made a start here: http://jsbin.com/ugacud/2/edit#javascript,html and I think it's the following snippet that's wrong:
var diagonal = d3.svg.diagonal()
.projection(function(d) { return [d.y, d.x]; });
However there's no obvious replacement, I could use d3.svg.line, but I don't know how to integrate it properly, and ideally I'd like an elbow connector....although I am wondering if I am using the wrong library for this, as a lot of the d3 examples I've seen are using the gravitational force to do graphs of objects instead of trees.

Replace the diagonal function with a custom path generator, using SVG's "H" and "V" path commands.
function elbow(d, i) {
return "M" + d.source.y + "," + d.source.x
+ "V" + d.target.x + "H" + d.target.y;
}
Note that the source and target's coordinates (x and y) are swapped. This example displays the layout with a horizontal orientation, however the layout always uses the same coordinate system: x is the breadth of the tree, and y is the depth of the tree. So, if you want to display the tree with the leaf (bottommost) nodes on the right edge, then you need to swap x and y. That's what the diagonal's projection function does, but in the above elbow implementation I just hard-coded the behavior rather than using a configurable function.
As in:
svg.selectAll("path.link")
.data(cluster.links(nodes))
.enter().append("path")
.attr("class", "link")
.attr("d", elbow);
And a working example:
http://bl.ocks.org/d/2429963/

Related

How to connect two d3 js svg circles with a line or path? [duplicate]

I'm very new to both JS and D3, and I've googled this a tonne but only found examples that are a bit too advanced.
I'm making a simple decision graph implementation, and I'm stuck trying to connect 2 nodes with a line / path. The objects can be moved around with the mouse, and the path should always update to reflect the positions of the objects.
This is my base source of knowledge: https://github.com/mbostock/d3/wiki/SVG-Shapes, but I don't quite understand how to do something smart with it.
Here is what I have so far: http://jsbin.com/AXEFERo/5/edit
Don't need the fancy stuff, just need to understand how to create connectors and have them update dynamically when the objects are being dragged around. Big thanks!
To draw a line between the circles, you don't need anything special -- just the line element.
var line = svg.append("line")
.style("stroke", "black")
.attr("x1", 150)
.attr("y1", 100)
.attr("x2", 250)
.attr("y2", 300);
Updating the position dynamically is a bit more difficult. At the moment, you have no means of distinguishing which of the circles is being dragged. One way of doing this is to add a distinguishing class to the g elements.
var g1 = svg.append("g")
.attr("transform", "translate(" + 150 + "," + 100 + ")")
.attr("class", "first")
...
and similarly for the other one. Now you can switch on the class in your dragmove function and update either the start or the end coordinates of the line.
if(d3.select(this).attr("class") == "first") {
line.attr("x1", x);
line.attr("y1", y);
} else {
line.attr("x2", x);
line.attr("y2", y);
}
Complete example here. There are other, more elegant ways of achieving this. In a real application, you would have data bound to the elements and could use that to distinguish between the different circles.

d3.js: How to rotate an SVG around its center while translating?

The Problem:
I have been trying, to no avail, to find a general solution for simultaneously rotating and translating an SVG with d3.js v4.2.2.
I can get the SVG starting and ending in the correct positions, but the easing in-between is all wrong. Below is a JSFiddle where I have done a rotate+translate on many instances of the same SVG pulled from the DOM, each to a different amount rotation according to the datapoint.
A key thing to note is that I need this solution to work when translating from (100,100) to (500,500) just as well as when I start from (0,0).
https://jsfiddle.net/3jbv23em/
var serializer = new XMLSerializer();
var svgNode = serializer.serializeToString(document.querySelector('#svgs svg defs svg'));
var dataset = [1,8,15,22,29,36,43,50,57,64,71,78,85,92,99,106,113,120,127,134,141,148,155,162,169,176,183,190,197,204,211,218,225,232,239,246,253,260,267,274,281,288,295,302,309,316,323,330,337,344,351,358]
var wrapper = d3
.select('#game svg')
.attr('width', window.innerWidth)
.attr('height', window.innerHeight)
.selectAll('g')
.data(dataset)
.enter()
.append('g')
.html(function(d) { return svgNode; })
.transition()
.duration(1500)
.attr('transform', function(d) {
return 'translate(500,300)' +
'rotate(' + d * 1.8 + ', 63, 54.77)';
});
(Unfortunately, I wasn't able to get D3 v4 working in JSFiddle, so JSFiddle is using v3 here. However, the problem is the same).
My expected behaviour: All of the chickens rotate around the same center, never extending past the dimensions of the circle seen at the end of the animation.
The actual result: All chickens rotate around the translated original position of the top left corner before returning to the correct position at the end.
Solutions That Didn't Work for Me:
How to rotate an object around the center in d3.js
This is about rotating around a fixed point further away, not around a point on the object one is trying to rotate
SVG rotation anchorpoint
This might work, but completely takes me out of the D3 ecosystem
How do I rotate or scale (transform) an SVG path relative to its center point?
I need the solution to start from an arbitrary point, not always the origin # (0,0)
In Conclusion:
I'm pretty bewildered at the moment, and any help is greatly appreciated. Thanks for your time.
Because D3 transitions applies both the translate and the rotate together you get some awkward looking animations even though the end result is what you are looking for.
D3 provides a d3.interpolateString which handles executing the animation the way you would like. d3.interpolateString requires a starting and ending transform. d3.interpolateString is used within attrTween.
Replace
.attr('transform', function(d) {
return 'translate(500,300)' +
'rotate(' + d * 1.8 + ', 63, 54.77)';
});
With
.attrTween('transform', function(d, i, a) {
return d3.interpolateString('translate(0,0) rotate(0)',
'translate(500,300)' +
'rotate(' + d * 1.8 + ', 63, 54.77)');
});
Here is an updated jsfiddle with a tweak to the translation for better viewability for smaller screens.
https://jsfiddle.net/3jbv23em/16/
A helpful link with the same issue is "D3.js animate rotation".

D3.js Dynamic connector between objects

I'm very new to both JS and D3, and I've googled this a tonne but only found examples that are a bit too advanced.
I'm making a simple decision graph implementation, and I'm stuck trying to connect 2 nodes with a line / path. The objects can be moved around with the mouse, and the path should always update to reflect the positions of the objects.
This is my base source of knowledge: https://github.com/mbostock/d3/wiki/SVG-Shapes, but I don't quite understand how to do something smart with it.
Here is what I have so far: http://jsbin.com/AXEFERo/5/edit
Don't need the fancy stuff, just need to understand how to create connectors and have them update dynamically when the objects are being dragged around. Big thanks!
To draw a line between the circles, you don't need anything special -- just the line element.
var line = svg.append("line")
.style("stroke", "black")
.attr("x1", 150)
.attr("y1", 100)
.attr("x2", 250)
.attr("y2", 300);
Updating the position dynamically is a bit more difficult. At the moment, you have no means of distinguishing which of the circles is being dragged. One way of doing this is to add a distinguishing class to the g elements.
var g1 = svg.append("g")
.attr("transform", "translate(" + 150 + "," + 100 + ")")
.attr("class", "first")
...
and similarly for the other one. Now you can switch on the class in your dragmove function and update either the start or the end coordinates of the line.
if(d3.select(this).attr("class") == "first") {
line.attr("x1", x);
line.attr("y1", y);
} else {
line.attr("x2", x);
line.attr("y2", y);
}
Complete example here. There are other, more elegant ways of achieving this. In a real application, you would have data bound to the elements and could use that to distinguish between the different circles.

Controlling d3js pie animations and place labels inside pie

I'm a complete noop to D3 and partly SVG, so I got a few basic questions.
First off, my code in question can be viewed at http://dotnetcarpenter.github.io/d3-test/ and I've used Simple Pie Chart example with D3.js and Pie Chart Update, II as examples to get a running start.
As you can see, the animation gets skewed in the end when the low path values switch to the higher values. This is obviously not what I want. I think I'm getting the order of calculations wrong but I'm not sure what to do. I'm using the code from the last example:
function change() {
//...
path.transition().duration(750).attrTween("d", arcTween); // redraw the arcs
}
// where arcTween is
function arcTween(a) {
var i = d3.interpolate(this._current, a);
this._current = i(0);
return function(t) {
return arc(i(t));
};
}
Another issue is placing labels on the sectors. I've put the update stuff in the change function and is able to read out and only render them if the value is between 0 and 100. I can't however place them in any way. Looking at the first example, I figure that I could do something like this:
text.data(data)
.text(setText)
.attr("transform", function (d) {
// we have to make sure to set these before calling arc.centroid
d.innerRadius = 0;
d.outerRadius = radius;
return "translate(" + arc.centroid(d) + ")";
})
.attr("text-anchor", "middle") //center the text on it's origin
Where text is a d3 selection and arc is: d3.svg.arc().outerRadius(radius)
But I get "Unexpected value translate(NaN,NaN) parsing transform attribute." warning in Firefox and the labels are written on top of each other.
I appreciate any help and hints. Thanks!
I finally figured it out.
Maintain sector order throughout an animation.
You'd be forgiven for thinking that object contancy had something do with it. I did. But it turns out to be much simpler than that.
Every pie chart is by default sorted by value. If you don't want to sort by value but e.g. by data list order, you just have to disable sorting.
var pie = d3.layout.pie() // get a pie object structure
.value(function(d) { // define how to get your data value
return d.value; // (based on your data set)
})
.sort(null); // disable sort-by-value
Positioning labels according to your chart
Basically, you need to calculate your label positions depending on the type of chart or graph, your trying to connect them to. In my case, it's a pie chart. So if I want d3 to help with the calculations, I need to tell centroid the inner and outer radius and, most importantly to my issue, the start and end angles. The latter was missing from my code. Getting these values is as simple as, calling our pie layout above with our dataset and then do a transform.
Note that you don't have to call .data() again if you created the SVG with d3 and already supplied your data wrapped in .pie() structure. That is, that you didn't select any existing SVG from your page.
var svg = d3.select("svg")
// do stuff with your svg
var pie = d3.layout.pie()
// set stuff on your layout
var text = svg.selectAll("text")
.data(pie(dataset)) // where dataset contains your data
.attr("transform", function(d) {
return "translate(" + arc.centroid(d) + ")";
});
I have to give credit to Philip Pedruco for helping me along the way.
Bonus info
Use viewBox if you want to position your SVG cross browser, not transform/translate.
// move pie to center
d3.select("svg").attr("viewBox", -radius + ","+ -radius +"," + size + "," + size)

drag multiple nodes in d3 force directed layout

Precondition: d3 force directed layout;
some nodes are selected by sequential clicking one by one (visually they become bigger and in the code pushed to array)
Is there a way to drag them all by picking one with the mouse the same way as files in the Windows explorer?
P.S. I'm getting very much answers here on stackoverflow without asking for a long time. This is my first question. Thanks in advance for any help!
The way that I impemented the dragging of multiple nodes (based off of children) was by recording the displacement of the dragged node inside of my tick function with a variable whose scope allows the value to still exist the next time tick runs.
You will need an object where the key is a unique indentifier of the node being dragged and the value is a d3 selection of the nodes that you would like to translate/drag when the key node is dragged.
dragObject is the above-mentioned object.
nodeData is the d3 data of the principle node that you are dragging - ( d3.select(node uid).datum() ).
offset.x and offset.y make up the above-mentioned variable that was defined the last time tick was run.
var translateAllChildren = function (nodeData) {
if (dragObject[nodeData.uid]) {
dragObject[nodeData.uid]
.attr("transform", function(d) {
d.x = (d.x + offset.x);;
d.y = (d.y + offset.y);
return "translate(" + d.x + "," + d.y + ")";
});
}
}

Categories