How to tune horizontal node position in d3 sankey.js? - d3.js

I am trying to plot some flow diagrams using d3's sankey.js.
I am stuck at arranging nodes x positions in the diagrams.
t2_a should be in same column as t2_b as they represent quantity from same time period. However by default this is placed at the end which gives wrong interpretation.
I can arrange manually for small number of nodes but its really difficult when number of nodes increase. Any help or suggestion would be highly appreciated.

In sankey.js comment the moveSinksRight call in computeNodeBreadths:
function computeNodeBreadths() {
var remainingNodes = nodes,
nextNodes,
x = 0;
while (remainingNodes.length) {
nextNodes = [];
remainingNodes.forEach(function(node) {
node.x = x;
node.dx = nodeWidth;
node.sourceLinks.forEach(function(link) {
nextNodes.push(link.target);
});
});
remainingNodes = nextNodes;
++x;
}
//
// moveSinksRight(x); <-- comment this
scaleNodeBreadths((width - nodeWidth) / (x - 1));
}

Related

How to separate hundreds of moving circles

How do I separate N number of moving circles? I am making a small game where I have hundreds of moving circles colliding. I have them all stored in a qTree, which I recreate every frame. The collision detection works fine, but the separation is flawed.
If only a small collection of circles collide, the separation works as expected. After a certain amount of circles cluster together tho, many of the circles start overlapping and moving in and out of each other
it looks like this:
code:
resolveIntersection(entity) {
let v = p5.Vector.sub(this.pos, entity.pos);
let sumRadii = this.r + entity.r;
let dir = v.normalize();
this.pos.x = entity.pos.x + (sumRadii + 1) * dir.x;
this.pos.y = entity.pos.y + (sumRadii + 1) * dir.y;
}
intersects(entity) {
let totalRadius = this.r + entity.r;
let v = p5.Vector.sub(this.pos, entity.pos);
return (round(v.magSq()) <= totalRadius * totalRadius)
}
The algorithm works in the followings order:
createQTree();
insertCircles();
let found;
let area;
for(let e of this.enemies) {
e.update(this.qTree);
found = [];
area = new Circle(e.pos.x, e.pos.y, e.r * 2);
this.qTree.query(area, found);
for(let other of found) {
if(other !== e) {
if(e.intersects(other)) {
e.resolveIntersection(other);
}
}
}
}
What I have tried:
Resolving collision by moving each collision partner by separation/2
updating all circles first, then resolving the collision
Recursive separation (seems to work pretty good, but killed the frametime)
Note: I am currently working on a single thread

How can I draw an autoscaling D3.js graph that plots a mathematical function?

I have a working jsfiddle that I made using JSXGraph, a graphing toolkit for mathematical functions. I'd like to port it to D3.js for personal edification, but I'm having a hard time getting started.
The jsfiddle graphs the value of -ke(-x/T) + k, where x is an independent variable and the values of k and t come from sliders.
board.create('functiongraph',
[
// y = -k * e(-x/t) + k
function(x) { return -k.Value()*Math.exp(-x/t.Value()) + k.Value(); },
0
]
);
The three things I'm most stumped on:
Actually drawing the graph and its axes - it's not clear to me which of the many parts of the D3 API I should be using, or what level of abstraction I should be operating at.
Re-rendering the graph when a slider is changed, and making the graph aware of the value of the sliders.
Zooming out the graph so that the asymptote defined by y = k is always visible and not within the top 15% of the graph. I do this now with:
function getAestheticBoundingBox() {
var kMag = k.Value();
var tMag = t.Value();
var safeMinimum = 10;
var limit = Math.max(safeMinimum, 1.15 * Math.max(k.Value(), t.Value()));
return [0, Math.ceil(limit), Math.ceil(limit), 0];
}
What's the right way for me to tackle this problem?
I threw this example together really quick, so don't ding me on the code quality. But it should give you a good starting point for how you'd do something like this in d3. I implemented everything in straight d3, even the sliders.
As #LarKotthoff says, the key is that you have to loop your function and build your data:
// define your function
var func = function(x) {
return -sliders.k() * Math.exp(-x / sliders.t()) + sliders.k();
},
// your step for looping function
step = 0.01;
drawPlot();
function drawPlot() {
// avoid first callback before both sliders are created
if (!sliders.k ||
!sliders.t) return;
// set your limits
var kMag = sliders.k();
var tMag = sliders.t();
var safeMinimum = 10;
var limit = Math.max(safeMinimum, 1.15 * Math.max(kMag, tMag));
// generate your data
var data = [];
for (var i = 0; i < limit; i += step) {
data.push({
x: i,
y: func(i)
})
}
// set our axis limits
y.domain(
[0, Math.ceil(limit)]
);
x.domain(
[0, Math.ceil(limit)]
);
// redraw axis
svg.selectAll("g.y.axis").call(yAxis);
svg.selectAll("g.x.axis").call(xAxis);
// redraw line
svg.select('.myLine')
.attr('d', lineFunc(data))
}

Inversion with ordinal scale

Is there any way to find inversion of ordinal scale?
I am using string value on x axis which is using ordinal scale and i on mouse move i want to find inversion with x axis to find which string is there at mouse position?
Is there any way to find this?
var barLabels = dataset.map(function(datum) {
return datum.image;
});
console.log(barLabels);
var imageScale = d3.scale.ordinal()
.domain(barLabels)
.rangeRoundBands([0, w], 0.1);
// divides bands equally among total width, with 10% spacing.
console.log("imageScale....................");
console.log(imageScale.domain());
.
.
var xPos = d3.mouse(this)[0];
xScale.invert(xPos);
I actually think it doesn't make sense that there isn't an invert method for ordinal scales, but you can figure it out using the ordinal.range() method, which will give you back the start values for each bar, and the ordinal.rangeBand() method for their width.
Example here:
http://fiddle.jshell.net/dMpbh/2/
The relevant code is
.on("click", function(d,i) {
var xPos = d3.mouse(this)[0];
console.log("Clicked at " + xPos);
//console.log(imageScale.invert(xPos));
var leftEdges = imageScale.range();
var width = imageScale.rangeBand();
var j;
for(j=0; xPos > (leftEdges[j] + width); j++) {}
//do nothing, just increment j until case fails
console.log("Clicked on " + imageScale.domain()[j]);
});
I found a shorter implementation here in this rejected pull request which worked perfectly.
var ypos = domain[d3.bisect(range, xpos) - 1];
where domain and range are scale domain and range:
var domain = x.domain(),
range = x.range();
I have in the past reversed the domain and range when this is needed
> var a = d3.scale.linear().domain([0,100]).range([0, w]);
> var b = d3.scale.linear().domain([0,w]).range([0, 100]);
> b(a(5));
5
However with ordinal the answer is not as simple. I have checked the documentation & code and it does not seem to be a simple way. I would start by mapping the items from the domain and working out the start and stop point. Here is a start.
imageScale.domain().map(function(d){
return {
'item':d,
'start':imageScale(d)
};
})
Consider posting your question as a feature request at https://github.com/mbostock/d3/issues?state=open in case
There is sufficient demand for such feature
That I haven't overlooked anything or that there is something more hidden below the documentation that would help in this case
If you just want to know which mouse position corresponds to which data, then d3 is already doing that for you.
.on("click", function(d,i) {
console.log("Clicked on " + d);
});
I have updated the Fiddle from #AmeliaBR http://fiddle.jshell.net/dMpbh/17/
I recently found myself in the same situation as OP.
I needed to get the inverse of a categorical scale for a slider. The slider has 3 discrete values and looks and behaves like a three-way toggle switch. It changes the blending mode on some SVG elements. I created an inverse scale with scaleQuantize() as follows:
var modeArray = ["normal", "multiply", "screen"];
var modeScale = d3.scalePoint()
.domain(modeArray)
.range([0, 120]);
var inverseModeScale = d3.scaleQuantize()
.domain(modeScale.range())
.range(modeScale.domain());
I feed this inverseModeScale the mouse x-position (d3.mouse(this)[0]) on drag:
.call( d3.drag()
.on("start.interrupt", function() { modeSlider.interrupt(); })
.on("start drag", function() { inverseModeScale(d3.mouse(this)[0]); })
)
It returns the element from modeArray that is closest to the mouse's x-position. Even if that value is out of bounds (-400 or 940), it returns the correct element.
Answer may seem a bit specific to sliders but posting anyway because it's valid (I think) and this question is in the top results for " d3 invert ordinal " on Google.
Note: This answer uses d3 v4.
I understand why Mike Bostock may be reluctant to include invert on ordinal scales since you can't return a singular true value. However, here is my version of it.
The function takes a position and returns the surrounding datums. Maybe I'll follow up with a binary search version later :-)
function ordinalInvert(pos, scale) {
var previous = null
var domain = scale.domain()
for(idx in domain) {
if(scale(datum[idx]) > pos) {
return [previous, datum[idx]];
}
previous = datum[idx];
}
return [previous, null];
}
I solved it by constructing a second linear scale with the same domain and range, and then calling invert on that.
var scale = d3.scale.ordinal()
.domain(domain)
.range(range);
var continousScale = d3.scale.linear()
.domain(domain)
.range(range)
var data = _.map(range, function(i) {
return continousScale.invert(i);
});
You can easily get the object's index/data in callback
.on("click", function(d,i) {
console.log("Clicked on index = " + i);
console.log("Clicked on data = " + d);
// d == imageScale.domain()[1]
});
d is the invert value itself.
You don't need to use obj.domain()[index] .

Avoid overlapping of nodes in tree layout in d3.js

I have created a collapsible tree to represent some biological data.
In this tree the size of the node represents the importance of the node. As I have a huge data and also the sizes of the nodes vary,they overlap over each other. I need to specify the distance between the sibling nodes.
I tried tree.separation() method but it didn't work.
Code is as follows :
tree.separation(seperator);
function seperator(a, b)
{
if(a.parent == b.parent)
{
if((a.abundance == (maxAbd)) || (b.abundance == (maxAbd)))
{
return 2;
}
else
{
return 1;
}
}
}
This is giving me error saying:
Unexpected value translate(433.33333333333337,NaN) parsing transform attribute.
I understand that that after adding the separation method it is unable to calculate the x coordinate for the nodes. Can anyone please help me with how to do this?
I also tried modifying the source code as suggested in https://groups.google.com/forum/#!topic/d3-js/7Js0dGrnyek but that did not work either.
Please suggest some solution.
I had the same problem. This is how I solved it. I have width assigned to each node, height for now is the same for all nodes (basically nodes with smaller height than nodeHeight, get centered vertically):
var tree = d3.layout.tree().nodeSize([1, nodeHeight])
.separation(function(a, b) {
var width = a.width + b.width,
distance = width / 2 + 16; // horizontal distance between nodes = 16
return distance;
}),
nodes = tree.nodes(data),
links = tree.links(nodes);
Hope this helps.
SiegS's answer just works fine!
My situation is that: My node is actually some text, which may have various width, which I don't know in advance. So I need to calculate the width of each nodes first.
I have a JSON object json_data as my data.
var tree = d3.layout.tree()
.sort(null)
.size([500,500])
.children( some_function_identify_children );
var nodes = tree.nodes(json_data); //which doesn't consider the node's size;
var links = tree.links(nodes);
// append a svg;
var layoutRoot = d3.select("body")
.append("svg:svg").attr("width","600").attr("height","600")
.append("svg:g")
.attr("class","container");
var nodeGroup = layoutRoot.selectAll("g.node")
.data(nodes)
.enter().append("text").text(function(d){return d.text;});
// now we knows the text of each node.
//calculate each nodes's width by getBBox();
nodeGroup.each(function(d,i){d["width"] = this.getBBox().width;})
//set up a new tree layout which consider the node width.
var newtree = d3.layout.tree()
.size([500,500])
.children(function(d) {return d.children;})
.separation(function(a,b){
return (a.width+b.width)/2+2;
});
//recalcuate the node's x and y by newtree
newtree.nodes(nodes[0]); //nodes[0] is the root
links = newtree.links(nodes);
//redraw the svg using new nodes and links.
...
Hope this will help.

Using d3.js is there a way to zero align two Y Axes with positive and negative values

I am new to d3, learning a lot. I have an issue I cannot find an example for:
I have two y axes with positive and negative values with vastly different domains, one being large dollar amounts the other being percentages.
The resulting graph from cobbling together examples looks really awesome with one slight detail, the zero line for each y axis is in a slightly different position. Does anyone know of a way in d3 to get the zero line to be at the same x position?
I would like these two yScales/axes to share the same zero line
// define yScale
var yScale = d3.scale.linear()
.range([height, 0])
.domain(d3.extent(dataset, function(d) { return d.value_di1; }))
;
// define y2 scale
var yScale2 = d3.scale.linear()
.range([height, 0])
.domain(d3.extent(dataset, function(d) { return d.calc_di1_di2_percent; }))
;
Here is a link to a jsfiddle with sample data:
http://jsfiddle.net/jglover/XvBs3/1/
(the x-axis ticks look horrible in the jsfiddle example)
In general, there's unfortunately no way to do this neatly. D3 doesn't really have a concept of several things lining up and therefore no means of accomplishing it.
In your particular case however, you can fix it quite easily by tweaking the domain of the second y axis:
.domain([d3.min(dataset, function(d) { return d.calc_di1_di2_percent; }), 0.7])
Complete example here.
To make the 0 level the same position, a strategy is to equalize the length/proportion of the y axes.
Here are the concepts to the solution below:
The alignment of baseline depends on the length of the y axes.
To let all value shown in the bar, we need to extend the shorter side of the dimension, which compares to the other, to make the proportion of the two axes equal.
example:
// dummy data
const y1List = [-1000, 120, -130, 1400],
y2List = [-0.1, 0.2, 0.3, -0.4];
// get proportion of the two y axes
const totalY1Length = Math.abs(d3.min(y1List)) + Math.abs(d3.max(y1List)),
totalY2Length = Math.abs(d3.min(y2List)) + Math.abs(d3.max(y2List)),
maxY1ToY2 = totalY2Length * d3.max(y1List) / totalY1Length,
minY1ToY2 = totalY2Length * d3.min(y1List) / totalY1Length,
maxY2ToY1 = totalY1Length * d3.max(y2List) / totalY2Length,
minY2ToY1 = totalY1Length * d3.min(y2List) / totalY2Length;
// extend the shorter side of the upper dimension with corresponding value
let maxY1Domain = d3.max(y1List),
maxY2Domain = d3.max(y2List);
if (maxY1ToY2 > d3.max(y2List)) {
maxY2Domain = d3.max(y2List) + maxY1ToY2 - d3.max(y2List);
} else {
maxY1Domain = d3.max(y1List) + maxY2ToY1 - d3.max(y1List);
}
// extend the shorter side of the lower dimension with corresponding value
let minY1Domain = d3.min(y1List),
minY2Domain = d3.min(y2List);
if (minY1ToY2 < d3.min(y2List)) {
minY2Domain = d3.min(y2List) + minY1ToY2 - d3.min(y2List);
} else {
minY1Domain = d3.min(y1List) + minY2ToY1 - d3.min(y1List);
}
// finally, we get the domains for our two y axes
const y1Domain = [minY1Domain, maxY1Domain],
y2Domain = [minY2Domain, maxY2Domain];

Resources