Get visible root node in zoomable sunburst - javascript

I made a zoomable sunburst visualisation with labels (see in action, or check out the code). When clicking on an item, the innermost visible node has its label turned sideways. I'd like to fix the label just for this one node, but I haven't found a way to do this.
Is there a way to say "if (current node is the root of visible nodes)"? Any other ideas?
The full visualization:
Zoomed in after click. I'd like to make the 'Calm' node text horizontal:

While not perfect, this modified version of the code you were using adjusts the text of the currently selected node as it animates and makes it horizontal.
var width = 960,
height = 700,
radius = (Math.min(width, height) / 2) - 10;
var formatNumber = d3.format(",d");
var x = d3.scaleLinear()
.range([0, 2 * Math.PI]);
var y = d3.scaleLinear()
.range([0, radius]);
var color = d3.scaleOrdinal(d3.schemeCategory20);
var partition = d3.partition();
function startAngle(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x0))); }
function endAngle(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x1))); }
function innerRadius(d) { return Math.max(0, y(d.y0)); }
function outerRadius(d) { return Math.max(0, y(d.y1)); }
var arc = d3.arc()
.startAngle( function(d) { return startAngle(d); })
.endAngle( function(d) { return endAngle(d); })
.innerRadius(function(d) { return innerRadius(d); })
.outerRadius(function(d) { return outerRadius(d); })
var texttransform = function(d) {
var translation = y(d.y0);
var rotation = computeTextRotation(d);
if (rotation > 90 && rotation < 270) {
rotation = rotation + 180;
translation = -translation;
}
return (
"rotate(" + rotation + ")" +
"translate(" + translation + ",0)"
);
}
var transition = {};
function calcTransitionPercentage(){
var now = Date.now()-transition.clockNow;
if(!transition.delay || now > transition.delay){
return Math.min(1,(now-(transition.delay||0))/transition.duration);
}
return 0;
}
function computeTextRotation(d) {
if (d.depth === 0) {
return 0;
}
var current = x((d.x0 + d.x1)/2);
var angle = (current - Math.PI / 2) / Math.PI * 180;
if(transition.node === d){
angle -= 90 * calcTransitionPercentage();
}
return (angle > 90 || angle < 270) ? angle : 180 + angle ;
}
var textanchor = function(d) {
if (d.depth === 0) {
return "middle";
}
var rotation = computeTextRotation(d);
return (rotation > 90 && rotation < 270) ? "end" : "start";
}
var textdx = function(d) {
if (d.depth === 0) {
return 0;
}
var rotation = computeTextRotation(d);
return (rotation > 90 && rotation < 270) ? -6 : 6;
}
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + (height / 2) + ")");
function calcFontSize(d) {
const xFactor = 12, yFactor = 7.5 ; // stub
if (d.depth === 0) {
return "30px";
}
// use inner arc len as text height delimiter
var innerArc = (endAngle(d) - startAngle(d)) * 2 * Math.PI * innerRadius(d);
var len = (d.y1-d.y0) * radius;
return Math.min(innerArc / yFactor, len / d.data.textlen * xFactor) + "px";
}
function click(d = { x0: 0, x1: 1, y0: 0, y1: 1 }) {
transition = {clockNow: Date.now(), duration: 750, node: d }
var trans = svg.transition().duration(750);
trans.selectAll("path")
.attrTween("d", function(n) { return function() { return arc(n); }; })
.tween("scale", function() {
var xd = d3.interpolate(x.domain(), [d.x0, d.x1]),
yd = d3.interpolate(y.domain(), [d.y0, 1]),
yr = d3.interpolate(y.range(), [d.y0 ? 20 : 0, radius]);
return function(t) {
x.domain(xd(t));
y.domain(yd(t)).range(yr(t));
};
});
trans.selectAll("text")
.attrTween("transform", function(n) { return function() { return texttransform(n); }; })
.attrTween("text-anchor", function(n) { return function() { return textanchor(n); }; })
.attrTween("dx", function(n) { return function() { return textdx(n); }; })
.styleTween("font-size", function(n) { return function() { return calcFontSize(n); }; });
trans.selectAll("text")
.delay(400)
.attrTween("opacity", function(n) { return function() {
if (d === n || n.ancestors().includes(d)) {
return 1;
} else {
return 0;
}
}; });
}
d3.text('https://raw.githubusercontent.com/manooh/NVSee/master/data/feelings_EN.txt', function(error, raw){
if (error) throw error;
// replace two-space indentation with pipes
raw = raw.replace(new RegExp(' ', 'g'), '|');
//read pipe-delimited data
var dsv = d3.dsvFormat('|');
var flatData = dsv.parse(raw);
var rData = currentNode = tree(flatData);
rData = d3.hierarchy(rData);
var nodes = partition(rData
.sum(function(d) { return 1; }) // each leaf gets a size of 1
.sort(function(a, b) { d3.ascending(a.name, b.name) }) // not working?
)
.descendants();
g = svg.selectAll("path")
.data(nodes)
.enter().append("g");
path = g.append("path")
.attr("d", arc)
.style("fill", function(d, i) {
var c;
if (d.depth === 0) {
return "white";
} else if (d.depth === 1) {
c = color((d.children ? d : d.parent).data.name);
} else if (d.depth > 1) {
c = d3.color(d.parent.data.color).darker();
}
d.data.color = c;
return c;
})
.on("click", click)
.append("title")
.text(function(d) { return d.data.name });
text = g.append("text")
.style("fill", function(d) {
if (d.depth === 0) {
return "#CCC";
} else {
return "#FFF";
}})
.attr("class", "svglabel")
.attr("transform", texttransform)
.attr("text-anchor", textanchor)
.attr("dx", textdx)
.attr("dy", ".35em") // vertical-align
.text(function(d) { return d.data.name; })
.style("font-size", function(d) {
// hack. save text len as property to make accessible in transiton
d.data.textlen = this.getComputedTextLength();
return calcFontSize(d);
});
});
function tree(nodes) {
var curr, parent, root;
var lev = 1;
nodes.forEach(function(d) {
if (!root) {
// handle root (first node)
curr = {
name: d.d1,
children: []
};
root = curr;
parent = curr;
} else {
if (d['d' + (lev+1)]) {
// handle children
lev = lev+1;
parent = curr;
} else if (d['d' + (lev-1)]) {
// handle moving up the hierarchy
lev = lev-1;
parent = parent.parent;
} else if (!d['d' + lev]) {
// if it's neither child, nor moving up, nor a sibling, handle exception
throw "unhandled tree level";
}
curr = {
name: d['d' + lev],
children: []
};
curr.parent = parent;
parent.children.push(curr);
}
});
return root;
}
.svglabel {
font-family: sans-serif;
pointer-events: none;
}
body {
-webkit-touch-callout: none; /* iOS Safari */
-webkit-user-select: none; /* Safari */
-khtml-user-select: none; /* Konqueror HTML */
-moz-user-select: none; /* Firefox */
-ms-user-select: none; /* Internet Explorer/Edge */
user-select: none; /* Non-prefixed version, currently
supported by Chrome and Opera */
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.js"></script>

Related

vanilla javascript to Reactjs component?

I have a little vanilla javascript code, and am hoping to convert it into a React component. I really dont know where to begin, and I have a few questions that seem be very simple.
First, how do i even get started converting this to React? Do i place all of these variables in the constructor, or in the render (before the return) Is all of this inline styling sufficient, without any external CSS?
This is the class (d3-based KMeans clustering visualization):
var flag = false;
var WIDTH = d3.select("#kmeans")[0][0].offsetWidth - 20;
var HEIGHT = Math.max(300, WIDTH * .7);
var svg = d3.select("#kmeans svg")
.attr('width', WIDTH)
.attr('height', HEIGHT)
.style('padding', '10px')
.style('background', '#223344')
.style('cursor', 'pointer')
.style('-webkit-user-select', 'none')
.style('-khtml-user-select', 'none')
.style('-moz-user-select', 'none')
.style('-ms-user-select', 'none')
.style('user-select', 'none')
.on('click', function() {
d3.event.preventDefault();
step();
});
d3.selectAll("#kmeans button")
.style('padding', '.5em .8em');
d3.selectAll("#kmeans label")
.style('display', 'inline-block')
.style('width', '15em');
var lineg = svg.append('g');
var dotg = svg.append('g');
var centerg = svg.append('g');
d3.select("#step")
.on('click', function() { step(); draw(); });
d3.select("#restart")
.on('click', function() { restart(); draw(); });
d3.select("#reset")
.on('click', function() { init(); draw(); });
var groups = [], dots = [];
function step() {
d3.select("#restart").attr("disabled", null);
if (flag) {
moveCenter();
draw();
} else {
updateGroups();
draw();
}
flag = !flag;
}
function init() {
d3.select("#restart").attr("disabled", "disabled");
//var N = parseInt(d3.select('#N')[0][0].value, 10);
//var K = parseInt(d3.select('#K')[0][0].value, 10);
var N = 700;
var K = 9;
groups = [];
for (var i = 0; i < K; i++) {
var g = {
dots: [],
color: 'hsl(' + (i * 360 / K) + ',100%,50%)',
center: {
x: Math.random() * WIDTH,
y: Math.random() * HEIGHT
},
init: {
center: {}
}
};
g.init.center = {
x: g.center.x,
y: g.center.y
};
groups.push(g);
}
dots = [];
flag = false;
for (i = 0; i < N; i++) {
var dot ={
x: Math.random() * WIDTH,
y: Math.random() * HEIGHT,
group: undefined
};
dot.init = {
x: dot.x,
y: dot.y,
group: dot.group
};
dots.push(dot);
}
}
function restart() {
flag = false;
d3.select("#restart").attr("disabled", "disabled");
groups.forEach(function(g) {
g.dots = [];
g.center.x = g.init.center.x;
g.center.y = g.init.center.y;
});
for (var i = 0; i < dots.length; i++) {
var dot = dots[i];
dots[i] = {
x: dot.init.x,
y: dot.init.y,
group: undefined,
init: dot.init
};
}
}
function draw() {
var circles = dotg.selectAll('circle')
.data(dots);
circles.enter()
.append('circle');
circles.exit().remove();
circles
.transition()
.duration(500)
.attr('cx', function(d) { return d.x; })
.attr('cy', function(d) { return d.y; })
.attr('fill', function(d) { return d.group ? d.group.color : '#ffffff'; })
.attr('r', 5);
if (dots[0].group) {
var l = lineg.selectAll('line')
.data(dots);
var updateLine = function(lines) {
lines
.attr('x1', function(d) { return d.x; })
.attr('y1', function(d) { return d.y; })
.attr('x2', function(d) { return d.group.center.x; })
.attr('y2', function(d) { return d.group.center.y; })
.attr('stroke', function(d) { return d.group.color; });
};
updateLine(l.enter().append('line'));
updateLine(l.transition().duration(500));
l.exit().remove();
} else {
lineg.selectAll('line').remove();
}
var c = centerg.selectAll('path')
.data(groups);
var updateCenters = function(centers) {
centers
.attr('transform', function(d) { return "translate(" + d.center.x + "," + d.center.y + ") rotate(45)";})
.attr('fill', function(d,i) { return d.color; })
.attr('stroke', '#aabbcc');
};
c.exit().remove();
updateCenters(c.enter()
.append('path')
.attr('d', d3.svg.symbol().type('cross'))
.attr('stroke', '#aabbcc'));
updateCenters(c
.transition()
.duration(500));}
function moveCenter() {
groups.forEach(function(group, i) {
if (group.dots.length == 0) return;
// get center of gravity
var x = 0, y = 0;
group.dots.forEach(function(dot) {
x += dot.x;
y += dot.y;
});
group.center = {
x: x / group.dots.length,
y: y / group.dots.length
};
});
}
function updateGroups() {
groups.forEach(function(g) { g.dots = []; });
dots.forEach(function(dot) {
// find the nearest group
var min = Infinity;
var group;
groups.forEach(function(g) {
var d = Math.pow(g.center.x - dot.x, 2) + Math.pow(g.center.y - dot.y, 2);
if (d < min) {
min = d;
group = g;
}
});
// update group
group.dots.push(dot);
dot.group = group;
});
}
init(); draw();
Any help in understanding (or or course providing converted code) would be so helpful.
If you're beginning from scratch, set up a boiler plate.For e.g create-react-app
Install d3 library in this project
Create a component for this and import and render that component in your app.js
Make these variables as part of the state of the component and method inside the component as class methods

Fixing vertical alignment of sink nodes in d3 js Sankey

I was trying to get the sink nodes to align centrally in the vertical direction in d3 JS Sankey implementation. Toward the top it is (almost) properly aligned like this
Correct Alignment
but towards the bottom, the sink nodes are no longer centrally aligned when compared to their inputs
Wrong Alignment
Here's the code `
sankey.nodeWidth = function (_) {
if (!arguments.length) return nodeWidth;
nodeWidth = +_;
return sankey;
};
sankey.nodePadding = function (_) {
if (!arguments.length) return nodePadding;
nodePadding = +_;
return sankey;
};
sankey.nodes = function (_) {
if (!arguments.length) return nodes;
nodes = _;
return sankey;
};
sankey.links = function (_) {
if (!arguments.length) return links;
links = _;
return sankey;
};
sankey.size = function (_) {
if (!arguments.length) return size;
size = _;
return sankey;
};
sankey.layout = function (iterations) {
computeNodeLinks();
computeNodeValues();
computeNodeBreadths();
computeNodeDepths(iterations);
computeLinkDepths();
return sankey;
};
sankey.relayout = function () {
computeLinkDepths();
return sankey;
};
sankey.link = function () {
//Original value of 0.5
var curvature = 0.5;
function link(d) {
var x0 = d.source.x + d.source.dx,
x1 = d.target.x,
xi = d3.interpolateNumber(x0, x1),
x2 = xi(curvature),
x3 = xi(1 - curvature),
y0 = d.source.y + d.sy + d.dy / 2,
y1 = d.target.y + d.ty + d.dy / 2;
return "M" + x0 + "," + y0 + "C" + x2 + "," + y0 + " " + x3 + "," + y1 + " " + x1 + "," + y1;
}
link.curvature = function (_) {
if (!arguments.length) return curvature;
curvature = +_;
return link;
};
return link;
};
// Populate the sourceLinks and targetLinks for each node.
// Also, if the source and target are not objects, assume they are indices.
function computeNodeLinks() {
nodes.forEach(function (node) {
node.sourceLinks = [];
node.targetLinks = [];
});
links.forEach(function (link) {
var source = link.source,
target = link.target;
if (typeof source == "number")
source = link.source = nodes[link.source];
if (typeof target == "number")
target = link.target = nodes[link.target];
source.sourceLinks.push(link);
target.targetLinks.push(link);
});
}
// Compute the value (size) of each node by summing the associated links.
function computeNodeValues() {
nodes.forEach(function (node) {
node.value = 15;
//Original code
//node.value = Math.max(
// d3.sum(node.sourceLinks, value),
//d3.sum(node.targetLinks, value));
});
}
// Iteratively assign the breadth (x-position) for each node.
// Nodes are assigned the maximum breadth of incoming neighbors plus one;
// nodes with no incoming links are assigned breadth zero, while
// nodes with no outgoing links are assigned the maximum breadth.
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;
//Original value of x+=1
x++;
}
//
moveSinksRight(x);
scaleNodeBreadths((size[0] - nodeWidth) / (x - 1));
}
function moveSourcesRight() {
nodes.forEach(function (node) {
if (!node.targetLinks.length) {
node.x = d3.min(node.sourceLinks, function (d) {
return d.target.x;
}) - 1;
}
});
}
function moveSinksRight(x) {
nodes.forEach(function (node) {
if (!node.sourceLinks.length) {
node.x = x - 1;
}
});
}
function scaleNodeBreadths(kx) {
nodes.forEach(function (node) {
node.x *= kx;
});
}
function computeNodeDepths(iterations) {
var nodesByBreadth = d3.nest()
.key(function (d) {
return d.x;
})
.sortKeys(d3.ascending)
.entries(nodes)
.map(function (d) {
return d.values;
});
//
initializeNodeDepth();
resolveCollisions();
for (var alpha = 1; iterations > 0; --iterations) {
relaxRightToLeft(alpha *= .99);
resolveCollisions();
relaxLeftToRight(alpha);
resolveCollisions();
}
function initializeNodeDepth() {
var ky = d3.min(nodesByBreadth, function (nodes) {
return (size[1] - (nodes.length - 1) * nodePadding) / d3.sum(nodes, value);
});
nodesByBreadth.forEach(function (nodes) {
nodes.forEach(function (node, i) {
node.y = i;
node.dy = node.value * ky;
});
});
links.forEach(function (link) {
link.dy = link.value * ky;
});
}
function relaxLeftToRight(alpha) {
nodesByBreadth.forEach(function (nodes, breadth) {
nodes.forEach(function (node) {
if (node.targetLinks.length) {
var y = d3.sum(node.targetLinks, weightedSource) / d3.sum(node.targetLinks, value);
node.y += (y - center(node)) * alpha;
}
});
});
function weightedSource(link) {
return center(link.source) * link.value;
}
}
function relaxRightToLeft(alpha) {
nodesByBreadth.slice().reverse().forEach(function (nodes) {
nodes.forEach(function (node) {
if (node.sourceLinks.length) {
var y = d3.sum(node.sourceLinks, weightedTarget) / d3.sum(node.sourceLinks, value);
node.y += (y - center(node)) * alpha;
}
});
});
function weightedTarget(link) {
return center(link.target) * link.value;
}
}
function resolveCollisions() {
nodesByBreadth.forEach(function (nodes) {
var node,
dy,
y0 = 0,
n = nodes.length,
i;
// Push any overlapping nodes down.
nodes.sort(ascendingDepth);
for (i = 0; i < n; ++i) {
node = nodes[i];
dy = y0 - node.y;
//Make some changes here
//Originally there in code
if (dy > 0) node.y += dy;
y0 = node.y + node.dy + nodePadding;
}
// If the bottommost node goes outside the bounds, push it back up.
dy = y0 - nodePadding - size[1];
if (dy > 0) {
y0 = node.y -= dy;
// Push any overlapping nodes back up.
for (i = n - 2; i >= 0; --i) {
node = nodes[i];
dy = node.y + node.dy + nodePadding - y0;
if (dy > 0) node.y -= dy;
y0 = node.y;
}
}
});
}
function ascendingDepth(a, b) {
return a.y - b.y;
}
}
function computeLinkDepths() {
nodes.forEach(function (node) {
node.sourceLinks.sort(ascendingTargetDepth);
node.targetLinks.sort(ascendingSourceDepth);
});
nodes.forEach(function (node) {
var sy = 0,
ty = 0;
node.sourceLinks.forEach(function (link) {
link.sy = sy;
sy += link.dy;
});
node.targetLinks.forEach(function (link) {
link.ty = ty;
ty += link.dy;
});
});
function ascendingSourceDepth(a, b) {
return a.source.y - b.source.y;
}
function ascendingTargetDepth(a, b) {
return a.target.y - b.target.y;
}
}
function center(node) {
//return 0;
//Original code +node.dy/2
return node.y+node.dy/2 ;
// return node.y ;
}
function value(link) {
return link.value;
}
return sankey;
`
and here are the user parameters that are usually set
var units = "Widgets";
var VariableHeight = graphData.nodes.length*25; //Change to suit the needs of the
graph,
//reduce factor of 100 for sleeker design
var margin = {top: 10, right: 10, bottom: 10, left: 10},
//Original Values are 700 and 300, 2700 is definitely a dangerous value for width
width = 1200 - margin.left - margin.right,
height = VariableHeight - margin.top - margin.bottom;
var formatNumber = d3.format(",.0f"), // zero decimal places
format = function(d) { return formatNumber(d) + " " + units; },
color = d3.scale.category20();
// append the svg canvas to the html page
var svg = d3.select("#sankeyContainer").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
// Set the sankey diagram properties
var sankey = d3sankey()
.nodeWidth(36)
.nodePadding(40)
.size([width, height]);
//Changes to connect links to centre of nodes
//Original Value
//var path = sankey.link();
var path = d3.svg.diagonal()
.source(function(d) {
return {"x":d.source.y + d.source.dy / 2,
"y":d.source.x + sankey.nodeWidth()/2};
})
.target(function(d) {
return {"x":d.target.y + d.target.dy / 2,
"y":d.target.x + sankey.nodeWidth()/2};
})
.projection(function(d) { return [d.y, d.x]; });
// load the data
var graph = graphData;
sankey.nodes(graph.nodes)
.links(graph.links)
.layout(32);
// add in the links
var link = svg.append("g").selectAll(".link")
.data(graph.links)
.enter().append("path")
.attr("class", "link")
.attr("d", path)
.style("fill", "none")
.style("stroke", "black")
.style("stroke-opacity", ".1")
//.style("stroke-opacity", ".2")
.on("mouseover", function() { d3.select(this).style("stroke-opacity", ".4") } )
.on("mouseout", function() { d3.select(this).style("stroke-opacity", ".1") } )
.style("stroke-width", function (d) {
return 15;
//return Math.max(1, Math.sqrt(d.dy));
//Original value
//return Math.max(1, d.dy);
})
.sort(function (a, b) {
return b.dy - a.dy;
});
// add the link titles
link.append("title")
.text(function (d) {
return d.source.name + " → " + d.target.name + "\n" + format(d.value);
});
// add in the nodes
var node = svg.append("g").selectAll(".node")
.data(graph.nodes).enter().append("g").attr("class", "node")
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
}).on("click",function(d){
if (d3.event.defaultPrevented) {
document.getElementById("ErrorDisplay").innerHTML="";
return;}
document.getElementById("ErrorDisplay").innerHTML="You Have Clicked "+ d.name;
}).call(d3.behavior.drag().origin(function(d) {
return d;
}).on("dragstart", function() {
//Removing the following line's comment status will make nodes unclickable
//this.parentNode.appendChild(this);
}).on("drag", dragmove));
// add the rectangles for the nodes, Original Code
/*
node.append("rect")
.attr("height", function (d) {
//Changed to make sure all node heights are the same
//Original Value
//return d.dy;
return 15;
})
.attr("width", sankey.nodeWidth())
*/// add the circles for the nodes
node.append("circle")
.attr("cx", sankey.nodeWidth()/2)
.attr("cy", function (d) {
return d.dy/2;
})
.attr("r", function (d) {
return Math.sqrt(d.dy);
})
.style("fill", function (d) {
return d.color = color(d.name.replace(/ .*/, ""));
})
.style("fill-opacity", ".9")
.style("shape-rendering", "crispEdges")
.style("stroke", function (d) {
return d3.rgb(d.color).darker(2);
})
.append("title")
.text(function (d) {
return d.name + "\n" + format(d.value);
});
// add in the title for the nodes
node.append("text")
.attr("x", -6)
.attr("y", function (d) {
return d.dy / 2+15; //Original value of only d.dy/2
})
.attr("dy", ".35em")
.attr("text-anchor", "end")
.attr("text-shadow", "0 1px 0 #fff")
.attr("transform", null)
.text(function (d) {
return d.name;
})
.filter(function (d) {
return d.x < width / 2;
})
.attr("x", 6 + sankey.nodeWidth())
.attr("text-anchor", "start");
// the function for moving the nodes
function dragmove(d) {
d3.select(this).attr("transform",
"translate(" + (
d.x = Math.max(0, Math.min(width - d.dx, d3.event.x))) + "," + (
d.y = Math.max(0, Math.min(height - d.dy, d3.event.y))) + ")");
sankey.relayout();
link.attr("d", path);
};
`
What I have tried until now
1) Make the center(node) function node return 0, which doesnt work out well
2) Tried removing +node.dy in the expression y0 = node.y + node.dy + nodePadding in nodes.sort, which results in the opposite happening, the bottom half is aligned, but the top alignment is out of order. How do I make the sink nodes align with the center of the corresponding source nodes?
Okay, I got it (quite by chance), in the relaxRightToLeft function, simply comment out the node.y += (y - center(node)) * alpha; line, which causes the sink nodes to misalign

d3 sankey diagram - how to set the y position

I am trying to set the position of nodes (by name) either on the top or the bottom of the Sankey Diagram. For example, if I had a node named "New" and another node named "Dropped", and I wanted to keep the New node at the top of the diagram always, and Dropped nodes at the bottom, how would I accomplish this?
I am looking for something similar to this jsFiddle for setting the x axis position, but for the y position:
//////////////////////// sankey.js /////////////////////////
d3.sankey = function() {
var sankey = {},
nodeWidth = 24,
nodePadding = 8,
size = [1, 1],
nodes = [],
links = [];
sankey.nodeWidth = function(_) {
if (!arguments.length) return nodeWidth;
nodeWidth = +_;
return sankey;
};
sankey.nodePadding = function(_) {
if (!arguments.length) return nodePadding;
nodePadding = +_;
return sankey;
};
sankey.nodes = function(_) {
if (!arguments.length) return nodes;
nodes = _;
return sankey;
};
sankey.links = function(_) {
if (!arguments.length) return links;
links = _;
return sankey;
};
sankey.size = function(_) {
if (!arguments.length) return size;
size = _;
return sankey;
};
sankey.layout = function(iterations) {
computeNodeLinks();
computeNodeValues();
computeNodeBreadths();
computeNodeDepths(iterations);
computeLinkDepths();
return sankey;
};
sankey.relayout = function() {
computeLinkDepths();
return sankey;
};
sankey.link = function() {
var curvature = .5;
function link(d) {
var x0 = d.source.x + d.source.dx,
x1 = d.target.x,
xi = d3.interpolateNumber(x0, x1),
x2 = xi(curvature),
x3 = xi(1 - curvature),
y0 = d.source.y + d.sy + d.dy / 2,
y1 = d.target.y + d.ty + d.dy / 2;
return "M" + x0 + "," + y0
+ "C" + x2 + "," + y0
+ " " + x3 + "," + y1
+ " " + x1 + "," + y1;
}
link.curvature = function(_) {
if (!arguments.length) return curvature;
curvature = +_;
return link;
};
return link;
};
// Populate the sourceLinks and targetLinks for each node.
// Also, if the source and target are not objects, assume they are indices.
function computeNodeLinks() {
nodes.forEach(function(node) {
node.sourceLinks = [];
node.targetLinks = [];
});
links.forEach(function(link) {
var source = link.source,
target = link.target;
if (typeof source === "number") source = link.source = nodes[link.source];
if (typeof target === "number") target = link.target = nodes[link.target];
source.sourceLinks.push(link);
target.targetLinks.push(link);
});
}
// Compute the value (size) of each node by summing the associated links.
function computeNodeValues() {
nodes.forEach(function(node) {
node.value = Math.max(
d3.sum(node.sourceLinks, value),
d3.sum(node.targetLinks, value)
);
});
}
// Iteratively assign the breadth (x-position) for each node.
// Nodes are assigned the maximum breadth of incoming neighbors plus one;
// nodes with no incoming links are assigned breadth zero, while
// nodes with no outgoing links are assigned the maximum breadth.
function computeNodeBreadths() {
var remainingNodes = nodes,
nextNodes,
x = 0;
while (remainingNodes.length) {
nextNodes = [];
remainingNodes.forEach(function(node) {
if (node.xPos)
node.x = node.xPos;
else
node.x = x;
node.dx = nodeWidth;
node.sourceLinks.forEach(function(link) {
nextNodes.push(link.target);
});
});
remainingNodes = nextNodes;
++x;
}
//
moveSinksRight(x);
scaleNodeBreadths((width - nodeWidth) / (x - 1));
}
function moveSourcesRight() {
nodes.forEach(function(node) {
if (!node.targetLinks.length) {
node.x = d3.min(node.sourceLinks, function(d) { return d.target.x; }) - 1;
}
});
}
function moveSinksRight(x) {
nodes.forEach(function(node) {
if (!node.sourceLinks.length) {
node.x = x - 1;
}
});
}
function scaleNodeBreadths(kx) {
nodes.forEach(function(node) {
node.x *= kx;
});
}
function computeNodeDepths(iterations) {
var nodesByBreadth = d3.nest()
.key(function(d) { return d.x; })
.sortKeys(d3.ascending)
.entries(nodes)
.map(function(d) { return d.values; });
//
initializeNodeDepth();
resolveCollisions();
for (var alpha = 1; iterations > 0; --iterations) {
relaxRightToLeft(alpha *= .99);
resolveCollisions();
relaxLeftToRight(alpha);
resolveCollisions();
}
function initializeNodeDepth() {
var ky = d3.min(nodesByBreadth, function(nodes) {
return (size[1] - (nodes.length - 1) * nodePadding) / d3.sum(nodes, value);
});
nodesByBreadth.forEach(function(nodes) {
nodes.forEach(function(node, i) {
node.y = i;
node.dy = node.value * ky;
});
});
links.forEach(function(link) {
link.dy = link.value * ky;
});
}
function relaxLeftToRight(alpha) {
nodesByBreadth.forEach(function(nodes, breadth) {
nodes.forEach(function(node) {
if (node.targetLinks.length) {
var y = d3.sum(node.targetLinks, weightedSource) / d3.sum(node.targetLinks, value);
node.y += (y - center(node)) * alpha;
}
});
});
function weightedSource(link) {
return center(link.source) * link.value;
}
}
function relaxRightToLeft(alpha) {
nodesByBreadth.slice().reverse().forEach(function(nodes) {
nodes.forEach(function(node) {
if (node.sourceLinks.length) {
var y = d3.sum(node.sourceLinks, weightedTarget) / d3.sum(node.sourceLinks, value);
node.y += (y - center(node)) * alpha;
}
});
});
function weightedTarget(link) {
return center(link.target) * link.value;
}
}
function resolveCollisions() {
nodesByBreadth.forEach(function(nodes) {
var node,
dy,
y0 = 0,
n = nodes.length,
i;
// Push any overlapping nodes down.
nodes.sort(ascendingDepth);
for (i = 0; i < n; ++i) {
node = nodes[i];
dy = y0 - node.y;
if (dy > 0) node.y += dy;
y0 = node.y + node.dy + nodePadding;
}
// If the bottommost node goes outside the bounds, push it back up.
dy = y0 - nodePadding - size[1];
if (dy > 0) {
y0 = node.y -= dy;
// Push any overlapping nodes back up.
for (i = n - 2; i >= 0; --i) {
node = nodes[i];
dy = node.y + node.dy + nodePadding - y0;
if (dy > 0) node.y -= dy;
y0 = node.y;
}
}
});
}
function ascendingDepth(a, b) {
return a.y - b.y;
}
}
function computeLinkDepths() {
nodes.forEach(function(node) {
node.sourceLinks.sort(ascendingTargetDepth);
node.targetLinks.sort(ascendingSourceDepth);
});
nodes.forEach(function(node) {
var sy = 0, ty = 0;
node.sourceLinks.forEach(function(link) {
link.sy = sy;
sy += link.dy;
});
node.targetLinks.forEach(function(link) {
link.ty = ty;
ty += link.dy;
});
});
function ascendingSourceDepth(a, b) {
return a.source.y - b.source.y;
}
function ascendingTargetDepth(a, b) {
return a.target.y - b.target.y;
}
}
function center(node) {
return node.y + node.dy / 2;
}
function value(link) {
return link.value;
}
return sankey;
};
///////////////////////////////////////////
function getData() {
return {
"nodes": [{
"node": 0,
"name": "node0"
}, {
"node": 1,
"name": "node1"
}, {
"node": 2,
"name": "node2",
"xPos": 1
}, {
"node": 3,
"name": "node3"
}],
"links": [{
"source": 0,
"target": 1,
"value": 5
}, {
"source": 1,
"target": 3,
"value": 2
}, {
"source": 2,
"target": 3,
"value": 3
}]};
}
var margin = {top: 1, right: 1, bottom: 6, left: 1},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var formatNumber = d3.format(",.0f"),
format = function(d) { return formatNumber(d) + " TWh"; },
color = d3.scale.category20();
var svg = d3.select("#chart").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var sankey = d3.sankey()
.nodeWidth(15)
.nodePadding(10)
.size([width, height]);
var path = sankey.link();
var renderSankey = function(energy) {
window.width = 500;
sankey
.nodes(energy.nodes)
.links(energy.links)
.layout(32);
var link = svg.append("g").selectAll(".link")
.data(energy.links)
.enter().append("path")
.attr("class", "link")
.attr("d", path)
.style("stroke-width", function(d) { return Math.max(1, d.dy); })
.sort(function(a, b) { return b.dy - a.dy; });
link.append("title")
.text(function(d) { return d.source.name + " → " + d.target.name + "\n" + format(d.value); });
var node = svg.append("g").selectAll(".node")
.data(energy.nodes)
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
.call(d3.behavior.drag()
.origin(function(d) { return d; })
.on("dragstart", function() { this.parentNode.appendChild(this); })
.on("drag", dragmove));
node.append("rect")
.attr("height", function(d) { return d.dy; })
.attr("width", sankey.nodeWidth())
.style("fill", function(d) { return d.color = color(d.name.replace(/ .*/, "")); })
.style("stroke", function(d) { return d3.rgb(d.color).darker(2); })
.append("title")
.text(function(d) { return d.name + "\n" + format(d.value); });
node.append("text")
.attr("x", -6)
.attr("y", function(d) { return d.dy / 2; })
.attr("dy", ".35em")
.attr("text-anchor", "end")
.attr("transform", null)
.text(function(d) { return d.name; })
.filter(function(d) { return d.x < width / 2; })
.attr("x", 6 + sankey.nodeWidth())
.attr("text-anchor", "start");
function dragmove(d) {
d3.select(this).attr("transform", "translate(" + d.x + "," + (d.y = Math.max(0, Math.min(height - d.dy, d3.event.y))) + ")");
sankey.relayout();
link.attr("d", path);
}
}
renderSankey(getData());
Try to change the target sort. Instead of function ascendingTargetDepth(a, b) change it to function ascendingTargetDepth(b, a). IE sort by name in descending order.
function ascendingTargetDepth(b, a) {
return a.target.y - b.target.y;
}

D3 data format like zoomable sunburst chart

I have my data formatted like flare.json that's used in this example :
I am just wondering what function the d3 zoomable chart uses to get the data in this format
In flare.json it's like this
{
name: "stuff",
children: [
....
]
}
and it's converted to this in the example. Which line does this?
{
children: Array[17]
depth: 1
dx: 0.6028744305756647
dy: 0.25
name: "A name would appear here"
parent: Object
value: 39850000.06
x: 0
y: 0.25
}
Chart
var total_revenue = json.total_revenue;
json = json.chart_data;
var width = 840,
height = width,
radius = width / 2,
x = d3.scale.linear().range([0, 2 * Math.PI]),
y = d3.scale.pow().exponent(1.3).domain([0, 1]).range([0, radius]),
padding = 5,
duration = 1000;
var div = d3.select("#chart_render");
div.select("img").remove();
var vis = div.append("svg")
.attr("width", width + padding * 2)
.attr("height", height + padding * 2)
.append("g")
.attr("transform", "translate(" + [radius + padding, radius + padding] + ")");
var partition = d3.layout.partition()
.value(function(d) { return d.size });
var arc = d3.svg.arc()
.startAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x))); })
.endAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))); })
.innerRadius(function(d) { return Math.max(0, d.y ? y(d.y) : d.y); })
.outerRadius(function(d) { return Math.max(0, y(d.y + d.dy)); });
console.log(json);
var nodes = partition.nodes({children: json});
var path = vis.selectAll("path").data(nodes);
path.enter().append("path")
.attr("id", function(d, i) { return "path-" + i; })
.attr("d", arc)
.attr("fill-rule", "evenodd")
.style("fill", colour)
.on("click", click);
var text = vis.selectAll("text").data(nodes);
var textEnter = text.enter().append("text")
.style("fill-opacity", function(d) {
var relative_percent = 0;
var relative_total = 0;
//console.log(d);
if (d.depth != 0) {
for(var i = 0; i < d.parent.children.length; i++) {
relative_total += d.parent.children[i].value;
}
//console.log(relative_total);
relative_percent = d.value/total_revenue*100;
if (relative_percent > 1) {
return '1';
} else {
return '0';
}
}
})
.style("fill", function(d) {
return "#fff";
})
.attr("text-anchor", function(d) {
return x(d.x + d.dx / 2) > Math.PI ? "end" : "start";
})
.attr("dy", ".2em")
.attr("transform", function(d) {
var multiline = (d.name || "").split(" ").length > 1,
angle = x(d.x + d.dx / 2) * 180 / Math.PI - 90,
rotate = angle + (multiline ? -.5 : 0);
return "rotate(" + rotate + ")translate(" + (y(d.y) + padding) + ")rotate(" + (angle > 90 ? -180 : 0) + ")";
})
.on("click", click);
textEnter.append("tspan")
.attr("x", 0)
.text(function(d) { return d.depth ? d.name.split(" ")[0] : ""; });
textEnter.append("tspan")
.attr("x", 0)
.attr("dy", "1em")
.text(function(d) { return d.depth ? d.name.split(" ")[1] || "" : ""; });
function click(d) {
path.transition()
.duration(duration)
.attrTween("d", arcTween(d));
// Somewhat of a hack as we rely on arcTween updating the scales.
text.style("visibility", function(e) {
return isParentOf(d, e) && e.value > 1500000 ? null : d3.select(this).style("visibility");
})
.transition()
.duration(duration)
.attrTween("text-anchor", function(d) {
return function() {
return x(d.x + d.dx / 2) > Math.PI ? "end" : "start";
};
})
.attrTween("transform", function(d) {
var multiline = (d.name || "").split(" ").length > 1;
return function() {
var angle = x(d.x + d.dx / 2) * 180 / Math.PI - 90,
rotate = angle + (multiline ? -.5 : 0);
return "rotate(" + rotate + ")translate(" + (y(d.y) + padding) + ")rotate(" + (angle > 90 ? -180 : 0) + ")";
};
})
.style("fill-opacity", function(e) { return isParentOf(d, e) ? 1 : 1e-6; })
.each("end", function(e) {
d3.select(this).style("visibility", function (d) {
// var relative_total = 0;
// var relative_percent = 0;
// for(var i = 0; i < d.parent.children.length; i++) {
// relative_total += d.parent.children[i].value;
// }
// console.log(relative_total);
// relative_percent = d.value/relative_total*100;
// console.log(relative_percent);
return isParentOf(d, e) && e.value > 1500000 ? null : "hidden";
})
});
}
function isParentOf(p, c) {
if (p === c) return true;
if (p.children) {
return p.children.some(function(d) {
return isParentOf(d, c);
});
}
return false;
}
function colour(d) {
if (d.depth == 0) {
return "rgb(250, 250, 250)";
} else if (d.depth == 1) {
return 'rgb(86, 135, 209)';
} else if (d.depth == 2) {
return 'rgb(222, 120, 59)';
} else if (d.depth == 3) {
return 'rgb(106, 185, 117)';
}
// if (d.children) {
// // There is a maximum of two children!
// var colours = d.children.map(colour),
// a = d3.hsl(colours[0]),
// b = d3.hsl(colours[1]);
// // L*a*b* might be better here...
// return d3.hsl((a.h + b.h) / 2, a.s * 1.2, a.l / 1.2);
// }
// return d.colour || "#fff";
}
// Interpolate the scales!
function arcTween(d) {
var my = maxY(d),
xd = d3.interpolate(x.domain(), [d.x, d.x + d.dx]),
yd = d3.interpolate(y.domain(), [d.y, my]),
yr = d3.interpolate(y.range(), [d.y ? 20 : 0, radius]);
return function(d) {
return function(t) { x.domain(xd(t)); y.domain(yd(t)).range(yr(t)); return arc(d); };
};
}
function maxY(d) {
return d.children ? Math.max.apply(Math, d.children.map(maxY)) : d.y + d.dy;
}
// http://www.w3.org/WAI/ER/WD-AERT/#color-contrast
function brightness(rgb) {
return rgb.r * .299 + rgb.g * .587 + rgb.b * .114;
}
This line:
var nodes = partition.nodes({children: json});
Explanation of code that sets up sunburst diagram
In D3 parlance, sunburst diagram is based on D3 "partition layout". Actually, D3 "partition layout" is in a way more general term, since it can be used for displaying not only sunburst diagram, but also others based on the idea of "partitioning" parents (hence the name "partition"). This is also a useful example for noticing difference between "layout" and "diagram" (in D3 mindset), but this is another story.
Following 2 lines are first steps in initializing partition layout:
var partition = d3.layout.partition()
.value(function(d) { return d.size });
This line does all calculations:
var nodes = partition.nodes({children: json});
Then variable nodes can be used for defining actual visual appearance of svg elements (arcs and labels):
var path = vis.selectAll("path").data(nodes);
and
var text = vis.selectAll("text").data(nodes);
These two lines represent something which is called "data binding" often. They enable programmers to use data to drive visual elements, like in the following line:
.text(function(d) { return d.depth ? d.name.split(" ")[0] : ""; });
Here, d.name originates from data, and d.depth is added by partition layout. They are both actually part of nodes.
I tried to explain in simple terms, but probably there are some confusing points to you - don't worry, it will be crystal clear to you soon, if you read the right docs and tutorials. ;)

D3js - Trying to update links in a Force Layout

I'd like to update links and nodes of a force directed layout at run-time. But the behavior is strange, because sometimes it does not add new links and sometimes it does not remove old links. Do you have any suggestions?
Any help would be appreciated.
Here is my code:
network.js
// Network View size
var width = 1280,
height = 500
var radius = 200;
var robj = 8;
var scaled_radius = d3.scale.linear()
.domain([0, 7000000000000])
.range([10, 40]);
var svg_network = d3.select(document.createElementNS(d3.ns.prefix.svg, "svg"))
.attr("width", width)
.attr("height", height);
var force = d3.layout.force()
.gravity(1.0)
.distance(100)
.charge(-60)
.size([width, height]);
var timestamp_info = svg_network.append("text")
.attr("dx", 10)
.attr("dy", 10);
function graph_network_start(flowz)
{
/* ------------------------ */
/* DATA PREPROCESSING */
/* ------------------------ */
flowz = network_preprocess(flowz)
/* ------------------------ */
/* NODE POSITIONING */
/* ------------------------ */
nodePositioning(flowz.activeNodes);
var x = d3.scale.linear().domain([0, flowz.activeNodes.length]).range([0, 180]);
// Bind link and node data to DOM elements
var link = svg_network.selectAll(".link").data(flowz.flow, function(d) { return d.source + "-" + d.target; });
var node = svg_network.selectAll("g.node").data(flowz.activeNodes)//, function(d,i) {return i;});
/* ------------------------ */
/* UPDATE LINKS */
/* ------------------------ */
link.exit().transition().duration(10).remove();
link.enter().append("line")
.attr("id",function(d){return d.source.ID + "-" + d.target.ID;})
.attr("class", "link")
.style("opacity",0)
.transition()
.duration(1000)
.style("opacity",1);
/* ------------------------ */
/* UPDATE NODES */
/* ------------------------ */
node.exit().transition().duration(100).remove();
var newNode = node.enter().append("svg:g")
.attr("id", function(d) {return d.ID})
.attr("class", "node")
.call(force.drag).on("click", function(d){
if (nodes[d.ID].name == "Workstations Site 1") {
selectedSite=1; bubble_visualize(selectedTimestamp, selectedSite);showMode(2);
} else if (nodes[d.ID].name == "Workstations Site 2") {
selectedSite=2; bubble_visualize(selectedTimestamp, selectedSite);showMode(2);
} else if (nodes[d.ID].name == "Workstations Site 3") {
selectedSite=3; bubble_visualize(selectedTimestamp, selectedSite);showMode(2);
}})
.on("mouseover", fade(.1)).on("mouseout", fade(1))
newNode.append("circle")
.attr("r", 8) //function(d){return scaled_radius(d.output)})
.style("fill", color)
.style("stroke", "black" )
.style("opacity",0)
.transition()
.duration(1000)
.style("opacity",1);
newNode.append("text")
.attr("dx", robj + 2)
.attr("dy", ".1em")
.text(function(d) { return nodes[d.ID].name })
.style("opacity",0)
.transition()
.duration(1000)
.style("opacity",1);
//if (nodes[d.ID].name == "Workstations Site" || nodes[d.ID].name == "Workstations Site 2" || nodes[d.ID].name == "Workstations Site 3") return nodes[d.ID].name });
/* ------------------------ */
/* UPDATE INFOS */
/* ------------------------ */
timestamp_info.text(function(d) { return new Date(flowz.timestamp).toString()});
force
.nodes(flowz.activeNodes)
.links(flowz.flow)
.on("tick", tick);
force.start();
var linkedByIndex = {};
flowz.flow.forEach(function(d) {
linkedByIndex[d.source + "," + d.target] = 1;
});
function color(d){
return "steelblue"
}
function isConnected(a, b) {
return linkedByIndex[a.index + "," + b.index] || linkedByIndex[b.index + "," + a.index] || a.index == b.index;
}
function tick()
{
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) { return "translate(" + d.x + "," + d.y + ")"; });
}
function fade(opacity) {
return function(d) {
node.style("stroke-opacity", function(o) {
thisOpacity = isConnected(d, o) ? 1 : opacity;
this.setAttribute('fill-opacity', thisOpacity);
return thisOpacity;
});
link.style("stroke-opacity", function(o) {
return o.source === d || o.target === d ? 1 : opacity;
});
};
}
function nodePositioning(nodesparam){
for (var i=0; i<nodesparam.length; i++)
{
if(nodes[nodesparam[i].ID].name == "Internet")
{
nodesparam[i].x=width/2;
nodesparam[i].y=30;
}
else if (nodes[nodesparam[i].ID].name == "Firewall")
{
nodesparam[i].x=width/2;
nodesparam[i].y=50;
}
else if (nodes[nodesparam[i].ID].name == "172.0.0.1")
{
nodesparam[i].x=width/2;
nodesparam[i].y=120;
}
else
{
nodesparam[i].x = width/2 + (radius - robj) * Math.cos(Math.PI + i * (Math.PI / nodesparam.length +1)) ;
nodesparam[i].y = height/2 + (radius - robj) * Math.sin(Math.PI + i * (Math.PI / nodesparam.length +1)) ;
}
nodesparam[i].fixed = true;
}
}
function positionX(node, index)
{
if(node.name == "Internet" || node.name == "Firewall" || node.name == "127.0.0.1")
{
return width/2;
}
else
{
return (width/2 + (radius - robj) * Math.cos(Math.PI + index * (Math.PI / nodes.length +1))) ;
}
}
function positionY(node, index)
{
if(node.name == "Internet")
{
return 30;
}
else if (node.name == "Firewall")
{
return 50;
}
else if (node.name == "172.0.0.1")
{
return 120;
}
else
{
return (height/2 + (radius - robj) * Math.sin(Math.PI + index * (Math.PI / nodes.length + 1))) ;;
}
}
function network_preprocess(flowz){
for(var i=+0; i<flowz.flow.length;i++)
{
for(var j=0; j<flowz.activeNodes.length;j++)
{
if(flowz.activeNodes[j].ID == flowz.flow[i].source)
flowz.flow[i].source = j;
if(flowz.activeNodes[j].ID == flowz.flow[i].target)
flowz.flow[i].target = j;
}
}
return flowz
}
}
links.json excerpt
[
{
"timestamp": 1364795760000,
"flow": [
{
"source": 0,
"target": 1,
"value": 15540
}
],
"activeNodes": [
{
"output": 15540,
"ID": 0
},
{
"output": 0,
"ID": 1
}
]
},
{
"timestamp": 1364795880000,
"flow": [
{
"source": 2,
"target": 1,
"value": 2960
},
{
"source": 0,
"target": 1,
"value": 14800
}
],
"activeNodes": [
{
"output": 14800,
"ID": 0
},
{
"output": 0,
"ID": 1
},
{
"output": 2960,
"ID": 2
}
]
}
]
EDIT: 1
I could narrow it down. The links.source and links.target elements are being destroyed by my code. For some reason they are changed from int to objects:
Output at time[0]:
Object {source: 3, target: 4, value: 213143231}
Object {source: 5, target: 4, value: 448560}
and when I go to another dataset and back to time[0]:
Object {source: Object, target: Object, value: "213143231"}
Object {source: Object, target: Object, value: "448560"}
Even the values get changed into strings. Maybe it happens inside of this code excerpt:
function graph_network_start(flowz)
{
flowz = network_preprocess(flowz);
...
var link = svg_network.selectAll(".link").data(flowz.flow, function(d) { console.log(d); return d.source + "-" + d.target; });
var node = svg_network.selectAll("g.node").data(flowz.activeNodes, function(d) {return d.ID})
...
force
.nodes(flowz.activeNodes)
.links(flowz.flow)
.on("tick", tick);
force.start();
function network_preprocess(flowz){
/*
We have to remove not existing nodes and add new nodes to our network nodes
*/
// Remove every node which does not exist anymore
var activeSet = new HashSet();
activeSet.addAll(flowz.activeNodes);
var toRemove = network_nodes.complement(activeSet).values();
for(var i=0; i<toRemove.length; i++)
{
network_nodes.remove(toRemove[i]);
}
// Add new nodes
var activenodes = flowz.activeNodes;
for(var i=0; i<activenodes.length; i++)
{
if (! network_nodes.contains(activenodes[i]) )
{
network_nodes.add(activenodes[i])
}
}
// Order nodes
var ufzuffu = network_nodes.values();
ufzuffu.sort(function(a,b){
var keya = a.ID;
var keyb = b.ID;
if(keya < keyb) return -1;
if(keya > keyb) return 1;
return 0;
});
flowz.activeNodes = ufzuffu;
// Edit links
for(var i=0; i<flowz.flow.length;i++)
{
for(var j=0; j<flowz.activeNodes.length;j++)
{
if(flowz.activeNodes[j].ID == flowz.flow[i].source)
{
flowz.flow[i].source = +j;
continue;
}
if(flowz.activeNodes[j].ID == flowz.flow[i].target)
{
flowz.flow[i].target = +j;
}
}
}
return flowz
}
}
EDIT 2
Well .. looks like I found a workaround:
var link = svg_network.selectAll(".link").data(flowz.flow, function(d) {
if(d.source.hasOwnProperty("ID"))
return d.source.index + "-" + d.target.index;
else
return d.source + "-" + d.target;
});
It works but I still dont know why it randomly(?) generates objects out of integers ..

Categories