Avoiding Tangles in Long Networks, D3 Force Diagram - d3.js

I am using the D3 Force Diagram and trying to find a way to make a network more "linear" and avoid "tangles" when the network generates, especially on long networks.
Example of Tangles
No Tangles, Better but not Ideal
Ideal result, network in a left to right line
Solutions that won't work:
increasing charge
reducing gravity
The example file I built on VizHub is simple, and although increasing charge may seem to work for small networks, 20-50 items will get tangled even with a very strong charge.
I have attempted to seed the location of the nodes starting locations based on the known "rough" rank order of the network. However, this gets disregarded and overwritten immediately by the on(tick) action. I may likely be doing something wrong and may need to do the seeding at a different point in the code but I was not able to find good examples or documentation on this.
I also attempted anchoring some points (less than ideal) but the links disregarded the seeded nodes locations. Again, quite possible that I didn't do it right and that may be my problem, but I was unable to find good examples of anchoring as well.
node.attr("transform", function (d) {
return "translate(" + d.x_seed + "," + d.y_seed + ")";})
Seeding when on("tick",...) disabled
I am constrained to using D3 version 3.5.17 at the latest so I cannot use some of the more modern force tools (like forceY) that would help "flatten" the network.
Any ideas on a good way to get the "ideal" result? Seeding, anchoring, something else?

I've partially answered my question. These have been integrated into the VizHub example now.
How to seed the locations on load.
for (var i = 0; i < nodesData.length; i++){
var t = nodesData[i];
var x_seed = t.rank*width/totalNodes
t.x_seed = x_seed;
t.x = x_seed;
var y_seed = var y_seed = height/2 + Math.random()*height/20 - height/40; // randomize y starting location a little to prevent nodes getting stuck in line
t.y_seed = y_seed;
t.y = y_seed;
}
Anchoring the first, last, and middle nodes:
force.on("tick", function () {
link
.attr("x1", function (d) {
return d.source.x;})
.attr("y1", function (d) {
return d.source.y;})
.attr("x2", function (d) {
return d.target.x;})
.attr("y2", function (d) {
return d.target.y;});
node
.attr("transform", function (d) {
if(d.rank == totalNodes || d.rank == 1 || d.rank == Math.floor(totalNodes/2)){
d.x = d.x_seed
d.y = d.y_seed
}
return "translate(" + d.x + "," + d.y + ")";
});
});

Related

Voronoi Tessellation] Find out which point I'm referencing

I've got a project using this Voronoi Tessellation plugin with series of coordinates representing locations of temperature sensors - I'm thinking of using JSON to represent their locations and detected temperature values.
What I need to is to display the temperature value of the sensor(point) I'm referencing to(the area where I'm mouse-hovering) when mouse-hovering an area.
https://github.com/mbostock/d3/wiki/Voronoi-Geom
I've been reading this documentation again and again but still can't figure out whether detecting which specific point that the mouse-hovering area belongs to is possible.
Has anyone tried this before? Are there good examples about it?
If I'm understanding your question, you want to display text at the vertices point when a user mouses into the voronoi partition?
You could do this by handling the mouseenter/leave events of each path:
path.enter().append("path")
.attr("class", function(d, i) {
return "q" + (i % 9) + "-9";
})
.attr("d", polygon)
.on("mouseenter", function(d,i){
if (!someTexts[i]) { // get some fake value
someTexts[i] = (Math.random()*100).toFixed(1);
}
// append text
currentText = svg.append("text")
.text(someTexts[i])
.attr("transform","translate(" + vertices[i] + ")")
.attr("text-anchor","middle")
.attr("alignment-baseline", "middle");
})
.on("mouseleave", function(d,i){
// remove text
currentText.remove();
});
Example here.

Smooth transitioning between tree, cluster, radial tree, and radial cluster layouts

For a project, I need to interactively change hierarchical data layout of a visualization - without any change of the underlying data whatsoever. The layouts capable of switching between themselves should be tree, cluster, radial tree, and radial cluster. And transitioning should be preferably an animation.
I thought that would be relatively easy task with D3. I started, but I got lost in translations and rotations, data bindings, and similar, so I am asking you for help. Also, probably I am doing something not in the spirit of D3, which is bad since I am seeking a clean solution.
I put together a jsfidle, but it is just a starting point, with added radio buttons, convenient small data set, and initial cluster layout - just to help anybody who wants to take a look at this. Thanks in advance!
UPDATE:
I wanted to focus on links only, so I temporary disabled other elements. Building on #AmeliaBR method, following animations are obtained:
Here is updated jsfiddle.
UPDATE 2:
Now with circles: (excuse my choice of colors)
{doom-duba-doom}
Here is one more updated jsfiddle.
I don't see why it would be that hard so long as all your layouts have the same overall structure of link-paths, circle nodes and text labels.
Just make sure all your objects, including your link paths, have a good data-key that is independent of the data attributes created by the layout functions. Then for each transition, update the data with the results of the appropriate layout function and draw that layout.
I've got the transition to radial tree implemented here: http://jsfiddle.net/YV2XX/5/
Key code:
//Radial Tree layout//
var diameter = 500;
var radialTree = d3.layout.tree()
.size([360, diameter / 2 ])
.separation(function(a, b) { return (a.parent == b.parent ? 1 : 2) / a.depth; });
var radialDiagonal = d3.svg.diagonal.radial()
.projection(function(d) { return [d.y, d.x / 180 * Math.PI]; });
function transitionToRadialTree() {
var nodes = radialTree.nodes(root), //recalculate layout
links = radialTree.links(nodes);
svg.transition().duration(1500)
.attr("transform", "translate(" + (diameter/2)
+ "," + (diameter/2) + ")");
//set appropriate translation (origin in middle of svg)
link.data(links, function(d){
return d.source.name + d.target.name;})
.transition().duration(1500)
.attr("d", radialDiagonal); //get the new radial path
node.data(nodes, function(d){
return d.name ;})
.transition().duration(1500)
.attr("transform", function(d) { return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")"; })
node.select("circle")
.transition().duration(1500)
.attr("r", 4.5);
node.select("text")
.transition().duration(1500)
.attr("dy", ".31em")
.attr("text-anchor", function(d) { return d.x < 180 ? "start" : "end"; })
.attr("transform", function(d) { return d.x < 180 ? "translate(8)" : "rotate(180)translate(-8)"; });
};
The layout code is all from http://bl.ocks.org/mbostock/4063550, I've just changed it to be an update instead of an initialization.
Also note that I have moved the variable declaration for root outside of the data-reading method, so it can be re-accessed by the transition functions.
Layout still needs some finessing, but you get the idea.
Now, if you wanted one of the transitions to be a partition, treemap or other layout that doesn't use the node-link structure, they it gets more complicated...
I don't have enough reputation to make a comment...so, I am just giving this tiny contribution as a pseudo-answer. After looking at this post, and based on #VividD's perfect comment on how simple the transitions turned out to be, I simply added the Tree Vertical option to the transformations in this fiddle.
The addition is simply this:
var diagonalVertical = d3.svg.diagonal()
.projection(function (d) {
return [d.x, d.y];
});
Anyways, I have bookmarked this highly instructional interaction.

two graph of d3 in same page are getting effected when events happening in other graph

in a html page we have two divs.
one div consist of tree layout and second div consist of other graph .both are written in d3.
d3.select("g").transition().duration(duration).attr("transform",
"translate(" + x + "," + y + ")scale(" + scale + ")");
the above statement d3.select('g') is causing issue,it is trying to select the other div as well and it is effecting it.
tried adding id to each container but didnt worked.
thanks in advance
There are a few things you can do to differentiate between elements.
Give IDs to the divs and use them in the selector. d3.select("#divone > svg > g")
Assign different classes to the g elements. d3.select("g.classone")
Keep references to the SVGs when creating them and select from those.
Here's some example code for this way:
var svg1 = d3.select("#divone").append("svg"),
svg2 = d3.select("#divtwo").append("svg");
// more code
svg1.select("g");
Which way is the best depends entirely on your application, but in general the last solution is the safest one as you're keeping explicit references to your subselections.
Use something like this
function animateFirstStep() {
d3.select(this).transition().delay(0).duration(
100)
.attr("r", function(d) {
return d.r + 4;
});
or pass selector in place of this.
say the name if your function is generateChart(selector)
call the function like this generateChart("#NameofDiv")
it should work
Use something like this
.......
var g = d3.select(this)
redraw(g);
.....
function redraw(g) {
g.selectAll(".resize").attr("transform", function (d) {
"translate(" + x + "," + y + ")scale(" + scale + ")");
});
}

d3 line chart labels overlap

I've created a line chart based on the example found here:
http://bl.ocks.org/mbostock/3884955
However, with my data the line labels (cities) end up overlapping because the final values on the y-axis for different lines are frequently close together. I know that I need to compare the last value for each line and move the label up or down when the values differ by 12 units or less. My thought is to look at the text labels that are written by this bit of code
city.append("text")
.datum(function(d) { return {name: d.name, value: d.values[d.values.length - 1]}; })
.attr("transform", function(d) { return "translate(" + x(d.value.date) + "," + y(d.value.temperature) + ")"; })
.attr("x", 3)
.attr("dy", ".35em")
.text(function(d) { return d.name; });
If the y(d.value.temperature) values differ by 12 or less, move the values apart until they have at least 12 units between them. Any thoughts on how to get this done? This is my first d3 project and the syntax is still giving me fits!
You're probably better off passing in all the labels at once -- this is also more in line with the general d3 idea. You could then have code something like this:
svg.selectAll("text.label").data(data)
.enter()
.append("text")
.attr("transform", function(d, i) {
var currenty = y(d.value.temperature);
if(i > 0) {
var previousy = y(data[i-1].value.temperature),
if(currenty - previousy < 12) { currenty = previousy + 12; }
}
return "translate(" + x(d.value.date) + "," + currenty + ")";
})
.attr("x", 3)
.attr("dy", ".35em")
.text(function(d) { return d.name; });
This does not account for the fact that the previous label may have been moved. You could get the position of the previous label explicitly and move the current one depending on that. The code would be almost the same except that you would need to save a reference to the current element (this) such that it can be accessed later.
All of this will not prevent the labels from being potentially quite far apart from the lines they are labelling in the end. If you need to move every label, the last one will be pretty far away. A better course of action may be to create a legend separately where you can space labels and lines as necessary.
Consider using a D3 force layout to place the labels. See an example here: https://bl.ocks.org/wdickerson/bd654e61f536dcef3736f41e0ad87786
Assuming you have a data array containing objects with a value property, and a scale y:
// Create some nodes
const labels = data.map(d => {
return {
fx: 0,
targetY: y(d.value)
};
});
// Set up the force simulation
const force = d3.forceSimulation()
.nodes(labels)
.force('collide', d3.forceCollide(10))
.force('y', d3.forceY(d => d.targetY).strength(1))
.stop();
// Execute thte simulation
for (let i = 0; i < 300; i++) force.tick();
// Assign values to the appropriate marker
labels.sort((a, b) => a.y - b.y);
data.sort((a, b) => b.value - a.value);
data.forEach((d, i) => d.y = labels[i].y);
Now your data array will have a y property representing its optimal position.
Example uses D3 4.0, read more here: https://github.com/d3/d3-force

Unique symbols for each data set in d3 Scatterplot

I am having trouble using d3's symbol mechanism to specify a unique symbol for each set of data. The data's like this:
[[{x: 1, y:1},{x: 2, y:2},{x: 3, y:3}], [{x: 1, y:1},{x: 2, y:4},{x: 3, y:9}], etc.]
The part of the code that writes out the symbols looks like this:
I create a series group for each vector of points. Then:
series.selectAll("g.points")
//this selects all <g> elements with class points (there aren't any yet)
.data(Object) //drill down into the nested Data
.enter()
.append("g") //create groups then move them to the data location
.attr("transform", function(d, i) {
return "translate(" + xScale(d.x) + "," + yScale(d.y) + ")";
})
.append("path")
.attr("d", function(d,i,j){
return (d3.svg.symbol().type(d3.svg.symbolTypes[j]));
}
);
Or at least that's how I'd like it to work. The trouble is that I can't return the function d3.svg.symbol() from the other function. If I try to just put the function in the "type" argument, then data is no longer scoped correctly to know what j is (the index of the series).
right, but I don't want a unique symbol for each datapoint, I want a unique symbol for each series. The data consists of multiple arrays (series), each of which can have an arbitrary number of points (x,y). I'd like a different symbol for each array, and that's what j should give me. I associate the data (in the example, two arrays shown, so i is 0 then 1 for that) with the series selection. Then I associate the data Object with the points selection, so i becomes the index for the points in each array, and j becomes the index of the original arrays/series of data. I actually copied this syntax from somewhere else, and it works ok for other instances (coloring series of bars in a grouped bar chart for example), but I couldn't tell you exactly why it works...
Any guidance would be appreciated.
Thanks!
What is the question exactly? The code that you give answers your question. My bad, j does return a reference to the series. Simpler example.
var data = [
{id: 1, pts: [{x:50, y:10},{x:50, y:30},{x:50, y:20},{x:50, y:30},{x:50, y:40}]},
{id: 2, pts: [{x:10, y:10},{x:10, y:30},{x:40, y:20},{x:30, y:30},{x:10, y:30}]}
];
var vis = d3.select("svg");
var series = vis.selectAll("g.series")
.data(data, function(d, i) { return d.id; })
.enter()
.append("svg:g")
.classed("series", true);
series.selectAll("g.point")
.data(function(d, i) { return d.pts })
.enter()
.append("svg:path")
.attr("transform", function(d, i) { return "translate(" + d.x + "," + d.y + ")"; })
.attr("d", function(d,i, j) { return d3.svg.symbol().type(d3.svg.symbolTypes[j])(); })
The only difference is that I added parenthesis after d3.svg.symbol().type(currentType)() to return the value rather than the function. D3js uses chaining, jquery style. This let you use symbol().type('circle') to set a value and symbol().type() to get it. Whenever accessors are used, what is returned is a reference to a function that has methods and attributes. Keep in mind that, in Javascript functions are first class objects - What is meant by 'first class object'?. In libraries that use that approach, often, there is an obvious getter for retrieving meaningful data. With symbol, you have to use symbol()().
The code beyond the symbol functionality can be seen at: https://github.com/mbostock/d3/blob/master/src/svg/symbol.js
d3.svg.symbol = function() {
var type = d3_svg_symbolType,
size = d3_svg_symbolSize;
function symbol(d, i) {
return (d3_svg_symbols.get(type.call(this, d, i))
|| d3_svg_symbolCircle)
(size.call(this, d, i));
}
...
symbol.type = function(x) {
if (!arguments.length) return type;
type = d3_functor(x);
return symbol;
};
return symbol;
};
Just in case you haven't. Have you tried?
.append("svg:path")
.attr("d", d3.svg.symbol())
as per https://github.com/mbostock/d3/wiki/SVG-Shapes.

Resources