d3 sankey diagram - how to set the y position - javascript

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;
}

Related

Display a diagram (using d3.js)

I get the js file below and I have to do some changes on it,
This is a Javascript code using the d3.js lib, the goal is to get a diagram like this screenshot from JSON data.
First of all, I would like to display the diagram.
I know that the javascript code is working, the problem must be in my html file but i don't really understand why nothing is happening.
Thank you for your help !
var orgChart = (function() {
var oldSelNode = null,
oldSelID = null,
oldSelColor = null,
selNode = null,
_x = 0,
_y = 0,
nodeID = -1,
nodePID = -1;
var _margin = {
top: 20,
right: 20,
bottom: 20,
left: 20
},
_root = {},
_nodes = [],
_counter = 0,
_svgroot = null,
_svg = null,
_tree = null,
_diagonal = null,
_lineFunction = null,
_loadFunction = null,
/* Configuration */
_duration = 500,
/* Duration of the animations */
_rectW = 250,
/* Width of the rectangle */
_rectH = 50,
/* Height of the rectangle */
_rectSpacing = 20 /* Spacing between the rectangles */
_fixedDepth = 40, /* Height of the line for child nodes */
_mode = "diagonal", /* Choose the values "line" or "diagonal" */
_callerNode = null,
_callerMode = 0,
_scale = 0.3,
_zoomEnabled = false,
_dragEnabled = true,
defLinearGradient = function(id, x1, y1, x2, y2, stopsdata) {
var gradient = _svgroot.append("svg:defs")
.append("svg:linearGradient")
.attr("id", id)
.attr("x1", x1)
.attr("y1", y1)
.attr("x2", x2)
.attr("y2", y2)
.attr("spreadMethod", "pad");
$.each(stopsdata, function(index, value) {
gradient.append("svg:stop")
.attr("offset", value.offset)
.attr("stop-color", value.color)
.attr("stop-opacity", value.opacity);
});
},
defBoxShadow = function(id) {
var filter = _svgroot.append("svg:defs")
.append("svg:filter")
.attr("id", id).attr("height", "150%").attr("width", "150%");
filter.append("svg:feOffset")
.attr("dx", "2").attr("dy", "2").attr("result", "offOut"); // how much to offset
filter.append("svg:feGaussianBlur")
.attr("in", "offOut").attr("result", "blurOut").attr("stdDeviation", "2"); // stdDeviation is how much to blur
filter.append("svg:feBlend")
.attr("in", "SourceGraphic").attr("in2", "blurOut").attr("mode", "normal");
},
collapse = function(d) {
if (d.children) {
}
},
update = function(source) {
// Compute the new tree layout.
_nodes = _tree.nodes(_root).reverse();
var links = _tree.links(_nodes);
// Normalize for fixed-depth.
_nodes.forEach(function(d) {
d.y = d.depth * _fixedDepth;
});
// Update the nodes
var node = _svg.selectAll("g.node")
.data(_nodes, function(d) {
return d.id || (d.id = ++_counter);
});
// Enter any new nodes at the parent's previous position.
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + source.x0 + "," + source.y0 + ")";
})
.on("click", nodeclick)
.on("contextmenu", contextmenuclick);
nodeEnter.append("rect")
.attr("width", _rectW)
.attr("height", _rectH)
.attr("fill", "#898989")
.attr("filter", "url(#boxShadow)");
nodeEnter.append("rect")
.attr("width", _rectW)
.attr("height", _rectH)
.attr("id", function(d) {
return d.id;
})
.attr("fill", function(d) {
return d.fill
})
.style("cursor", "pointer")
.attr("class", "box");
nodeEnter.append("text")
.attr("x", _rectW / 2)
.attr("y", _rectH / 2)
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.attr("fill", "#FFFF")
.style("cursor", "pointer")
.text(function(d) {
return d.desc;
});
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(_duration)
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
nodeUpdate.select("#_" + _canvasObj.nm + " rect.box")
.attr("fill", function(d) {
return (d.fill);
});
// Transition exiting nodes to the parent's new position.
var nodeExit = node.exit().transition()
.duration(_duration)
.attr("transform", function(d) {
return "translate(" + source.x + "," + source.y + ")";
})
.remove();
// Update the links
var link = _svg.selectAll("path.link")
.data(links, function(d) {
return d.target.id;
});
if (_mode === "line") {
// Enter any new links at the parent's previous position.
link.enter().append("path", "g")
.attr("class", "link")
.attr("d", function(d) {
var u_line = (function(d) {
var u_linedata = [{
"x": d.source.x0 + parseInt(_rectW / 2),
"y": d.source.y0 + _rectH + 2
}, {
"x": d.source.x0 + parseInt(_rectW / 2),
"y": d.source.y0 + _rectH + 2
}, {
"x": d.source.x0 + parseInt(_rectW / 2),
"y": d.source.y0 + _rectH + 2
}, {
"x": d.source.x0 + parseInt(_rectW / 2),
"y": d.source.y0 + _rectH + 2
}];
return u_linedata;
})(d);
return _lineFunction(u_line);
});
// Transition links to their new position.
link.transition()
.duration(_duration)
.attr("d", function(d) {
var u_line = (function(d) {
var u_linedata = [{
"x": d.source.x + parseInt(_rectW / 2),
"y": d.source.y + _rectH
}, {
"x": d.source.x + parseInt(_rectW / 2),
"y": d.target.y - _margin.top / 2
}, {
"x": d.target.x + parseInt(_rectW / 2),
"y": d.target.y - _margin.top / 2
}, {
"x": d.target.x + parseInt(_rectW / 2),
"y": d.target.y
}];
return u_linedata;
})(d);
return _lineFunction(u_line);
});
// Transition exiting nodes to the parent's new position.
link.exit().transition()
.duration(_duration)
.attr("d", function(d) {
/* This is needed to draw the lines right back to the caller */
var u_line = (function(d) {
var u_linedata = [{
"x": _callerNode.x + parseInt(_rectW / 2),
"y": _callerNode.y + _rectH + 2
}, {
"x": _callerNode.x + parseInt(_rectW / 2),
"y": _callerNode.y + _rectH + 2
}, {
"x": _callerNode.x + parseInt(_rectW / 2),
"y": _callerNode.y + _rectH + 2
}, {
"x": _callerNode.x + parseInt(_rectW / 2),
"y": _callerNode.y + _rectH + 2
}];
return u_linedata;
})(d);
return _lineFunction(u_line);
}).each("end", function() {
_callerNode = null; /* After transition clear the caller node variable */
});
} else if (_mode === "diagonal") {
// Enter any new links at the parent's previous position.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("x", _rectW / 2)
.attr("y", _rectH / 2)
.attr("d", function(d) {
var o = {
x: source.x0,
y: source.y0
};
return _diagonal({
source: o,
target: o
});
});
// Transition links to their new position.
link.transition()
.duration(_duration)
.attr("d", _diagonal);
// Transition exiting nodes to the parent's new position.
link.exit().transition()
.duration(_duration)
.attr("d", function(d) {
var o = {
x: source.x,
y: source.y
};
return _diagonal({
source: o,
target: o
});
})
.remove();
}
// Stash the old positions for transition.
_nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
},
// Toggle children on click.
nodeclick = function(d) {
selNode = d;
d3.selectAll("#_" + _canvasObj.nm + " rect.box").style("fill", function(d) {
return d.fill
});
d3.select(this.childNodes[1]).style("fill", "red");
oldSelNode = this.childNodes[1];
nodeID = d.id;
if (d.parent && d.parent.id) {
nodePID = d.parent.id
} else {
nodePID = -1
};
ajaxRequest(_canvasObj, "_nodeclick", ["nodeID=" + nodeID, "nodePID=" + nodePID, "nodeText=" + d.desc], false);
pn = d.parent;
dd = function(d) {
Ext.get(pn.id).dom.style.fill = "yellow";
}
while (pn) {
dd(pn);
pn = pn.parent;
}
},
// contextmenu
contextmenuclick = function(d) {
selNode = d;
d3.selectAll("#_" + _canvasObj.nm + " rect.box").style("fill", function(d) {
return d.fill
});
function getScreenCoords(x, y, ctm) {
var xn = ctm.e + x * ctm.a + y * ctm.c;
var yn = ctm.f + x * ctm.b + y * ctm.d;
return {
x: xn,
y: yn
};
}
var _node = this,
cx = +_node.getAttribute('cx'),
cy = +_node.getAttribute('cy'),
ctm = _node.getCTM(),
coords = getScreenCoords(cx, cy, ctm);
_x = parseInt(coords.x) + parseInt(_canvasObj.x) + parseInt(_rectW + 5),
_y = parseInt(coords.y) + parseInt(_rectH + 25);
d3.select(this.childNodes[1]).style("fill", "red");
oldSelNode = this.childNodes[1];
nodeID = d.id;
if (d.parent && d.parent.id) {
nodePID = d.parent.id
} else {
nodePID = -1
};
ajaxRequest(_canvasObj, "_contextmenu", ["x=" + _x, "y=" + _y, "nodeID=" + nodeID, "nodePID=" + nodePID, "nodeText=" + d.desc], false);
pn = d.parent;
dd = function(d) {
Ext.get(pn.id).dom.style.fill = "yellow";
}
while (pn) {
dd(pn);
pn = pn.parent;
}
},
// delete Node
deleteNode = function(event) {
if (selNode.parent) {
var tSelNode = selNode;
selNode = selNode.parent;
addRemoveNode(event, -1);
selNode = tSelNode;
simulateClick(selNode.parent);
}
}
// rename Node
renameNode = function(newValue) {
d = selNode;
nodeID = d.id;
d.desc = newValue;
d3.selectAll("#_" + _canvasObj.nm + " .node")[0].forEach(function(n) {
if (n.childNodes[1].id == nodeID) n.childNodes[2].textContent = newValue
})
}
// add Node
addNode = function(event, id) {
if (selNode.parent) {
var tSelNode = selNode;
selNode = selNode.parent;
addRemoveNode(event, id);
selNode = tSelNode;
}
}
// simulateClick
function simulateClick(elem, id) {
var evt = document.createEvent("MouseEvents");
evt.initMouseEvent(
"click", /* type */
true, /* canBubble */
true, /* cancelable */
window, /* view */
0, /* detail */
0, /* screenX */
0, /* screenY */
0, /* clientX */
0, /* clientY */
false, /* ctrlKey */
false, /* altKey */
false, /* shiftKey */
false, /* metaKey */
0, /* button */
null); /* relatedTarget */
if (elem != null) {
d3.selectAll("#_" + _canvasObj.nm + " .node")[0].forEach(function(n) {
if (n.childNodes[1].id == elem.id) n.dispatchEvent(evt)
});
} else {
d3.selectAll("#_" + _canvasObj.nm + " .node")[0].forEach(function(n) {
if (n.childNodes[1].id == id) n.dispatchEvent(evt)
})
}
}
// add Child Node
addRemoveNode = function(event, id) {
var xhrPopup = new XMLHttpRequest();
var formDataEmpty = new FormData();
d = selNode;
nodeID = d.id;
xhrPopup.open("POST", "/HandleEvent?Evt=" + event + "&IsEvent=1&Obj=" + _canvasObj.nm + "&" + _S_ID + "&nodeID=" + nodeID, false);
xhrPopup.onreadystatechange = function() {
if (xhrPopup.readyState == 4) {
var _res = xhrPopup.response;
var response = {
id: d.id,
desc: d.desc,
children: JSON.parse(_res)
};
response.children.forEach(function(child) {
if (!_tree.nodes(d)[0]._children) {
_tree.nodes(d)[0]._children = [];
}
child.x = d.x;
child.y = d.y;
child.x0 = d.x0;
child.y0 = d.y0;
_tree.nodes(d)[0]._children.push(child);
});
_callerNode = null;
_callerMode = 1; // Expand
d.children = d._children;
d._children = null;
update(d);
if (id != -1) {
simulateClick(null, id)
}
}
};
xhrPopup.send(formDataEmpty);
},
//refill
refill = function(v) {
selNode.fill = v;
},
//Redraw for zoom
redraw = function() {
_svg.attr("transform", "translate(" + d3.event.translate + ")" +
" scale(" + d3.event.scale.toFixed(_scale) + ")");
},
initTree = function(options) {
//oldSelColor = null;
//oldSelNode = null;
oldSelID = -1;
var u_opts = $.extend({
id: "",
data: {},
modus: "line",
canvasObj: null,
rectW: 250,
rectH: 50,
loadFunc: function() {}
},
options),
id = u_opts.id;
_loadFunction = u_opts.loadFunc;
_mode = u_opts.modus;
_root = u_opts.data;
_rectW = u_opts.rectW;
_rectH = u_opts.rectH;
_fixedDepth = _rectH * 2.5;
_canvasObj = u_opts.canvasObj;
/*if(_mode == "line") {
_fixedDepth = 80;
} else {
_fixedDepth = 110;
}*/
$(id).html(""); // Reset
var width = $(id).innerWidth() - _margin.left - _margin.right,
height = $(id).innerHeight() - _margin.top - _margin.bottom;
_tree = d3.layout.tree().nodeSize([_rectW + _rectSpacing, _rectH + _rectSpacing]);
/* Basic Setup for the diagonal function. _mode = "diagonal" */
_diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.x + _rectW / 2, d.y + _rectH / 2];
});
/* Basic setup for the line function. _mode = "line" */
_lineFunction = d3.svg.line()
.x(function(d) {
return d.x;
})
.y(function(d) {
return d.y;
})
.interpolate("linear");
var u_childwidth = parseInt((_root.children.length * _rectW) / 2);
_svgroot = d3.select(id).append("svg").attr("width", width).attr("height", height).attr("id", "_" + _canvasObj.nm) //.attr("viewBox", "0,0,150,420")
//.call(zm = d3.behavior.zoom().scaleExtent([0.15,3]).on("zoom", null));
.call(zm = d3.behavior.zoom().scaleExtent([0.15, 3]).on("zoom", redraw)).on('dblclick.zoom', null);;
_svg = _svgroot.append("g")
.attr("transform", "translate(" + parseInt(u_childwidth + ((width - u_childwidth * 2) / 2) - _margin.left / 2) + "," + 20 + ")");
var u_stops = [{
offset: "0%",
color: "#03A9F4",
opacity: 1
}, {
offset: "100%",
color: "#0288D1",
opacity: 1
}];
defLinearGradient("gradientnochilds", "0%", "0%", "0%", "100%", u_stops);
var u_stops = [{
offset: "0%",
color: "#8BC34A",
opacity: 1
}, {
offset: "100%",
color: "#689F38",
opacity: 1
}];
defLinearGradient("gradientchilds", "0%", "0%", "0%", "100%", u_stops);
defBoxShadow("boxShadow");
//necessary so that zoom knows where to zoom and unzoom from
zm.translate([parseInt(u_childwidth + ((width - u_childwidth * 2) / 2) - _margin.left / 2), 20]);
//zm.translate([0, 10]);
_root.x0 = 0; // the root is already centered
_root.y0 = 0; // draw & animate from center
_root.children.forEach(collapse);
update(_root);
d3.select(id).style("height", height + _margin.top + _margin.bottom);
};
return {
initTree: initTree,
addRemoveNode: addRemoveNode,
renameNode: renameNode,
addNode: addNode,
deleteNode: deleteNode,
refill: refill
};
})();
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Titre</title>
<script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
<script src="d3.v3.min.js"></script>
<script src="orgChart.js"></script>
<script type="text/javascript">
var canv = document.createElement('canvas');
canv.id = "CursorLayer";
canv.width = 1224;
canv.height = 768;
canv.style.zIndex = 8;
canv.style.position = "absolute";
canv.style.border = "1px solid";
var _jsonstr = JSON.parse("{\"id\":\"11\", \"desc\":\"5\", \"children\":[{\"id\":\"12\", \"desc\":\"5.1\"},{\"id\":\"13\", \"desc\":\"5.2\"},{\"id\":\"14\", \"desc\":\"5.3\", \"children\":[{\"id\":\"15\", \"desc\":\"5.3.1\"},{\"id\":\"16\", \"desc\":\"5.3.2\", \"children\":[{\"id\":\"17\", \"desc\":\"5.3.2.1\"},{\"id\":\"18\", \"desc\":\"5.3.2.2\"}]},{\"id\":\"19\", \"desc\":\"5.3.3\"}]},{\"id\":\"20\", \"desc\":\"5.4\", \"children\":[{\"id\":\"21\", \"desc\":\"5.4.1\"},{\"id\":\"22\", \"desc\":\"5.4.2\", \"children\":[{\"id\":\"23\", \"desc\":\"5.4.2.1\"},{\"id\":\"24\", \"desc\":\"5.4.2.2\", \"children\":[{\"id\":\"25\", \"desc\":\"5.4.2.2.1\"},{\"id\":\"26\", \"desc\":\"5.4.2.2.2\", \"children\":[{\"id\":\"27\", \"desc\":\"5.4.2.2.2.1\"}]}]},{\"id\":\"28\", \"desc\":\"5.4.2.3\"},{\"id\":\"29\", \"desc\":\"5.4.2.4\"}]},{\"id\":\"30\", \"desc\":\"5.4.3\"}]}]}");
orgChart.initTree({
id: "#O13_id-innerCt",
data: _jsonstr,
modus: "diagonal",
canvasObj: canv,
loadFunc: function() {}
});
</script>
</head>
<body>
</body>
</html>
Here are first steps to get something to show up (all changes are inside the last <script> tag):
1) Despite the name, it looks like canvasObj is expected to be some container element for <svg>. So, replace
var canv = document.createElement('canvas');
with
var canv = document.createElement('div');
2) By looking at the following line: d3.selectAll("#_" + _canvasObj.nm + " rect.box") we can infer, that canvas id must start with _ and .nm attribute must be equal to that id without _, so you can declare it like this:
canv.id = '_CursorLayer';
canv.nm = 'CursorLayer';
You also have to replace id: "#O13_id-innerCt", with id: "#_CursorLayer"
3) SVG size is taken from canv size. Since div does not has width and height attributes, you should specify them by css:
canv.style.width = '1224px';
canv.style.height = '768px';
4) We need to append canv to body to see it:
document.body.appendChild(canv);
In order for document.body to be available during the last <script> execution, I suggest to move the last <script> tag inside <body>.

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

d3js arc height tweening

I am trying to tween the heights of the various arcs in this chart
jsfiddle
http://jsfiddle.net/0ht35rpb/193/
I've seen this example but not sure how to start implementing it.
d3 how to tween inner radius for pie chart
I've seen this sample on various tweening maths. http://andyshora.com/tweening-shapes-paths-d3-js.html
var $this = $("#chart");
var data = [{
"label": "Overall Stress",
"value": 89
},{
"label": "Emotional Stress",
"value": 1
},{
"label": "Behavioural difficulties",
"value": 29
},{
"label": "hyperactivity and concetration",
"value": 89
},{
"label": "Getting along with others",
"value": 19
},{
"label": "Keen and helpful behaviour",
"value": 45
}];
var w = 350;
var h = 350;
function colores_google(n) {
var colores_g = ["#f7b363", "#448875", "#c12f39", "#2b2d39", "#f8dd2f", "#c12fff"];
return colores_g[n % colores_g.length];
}
var arcGenerator = {
radius: 70,
oldData: "",
init: function(el, data, w, h){
var stardata = [
{
"segments": data
}
];
this.el = el;
var clone = $.extend(true, {}, stardata);
this.oldData = this.setData(clone, false);
this.setup(el, this.setData(stardata, true), w, h);
},
update: function(data){
var clone = $.extend(true, {}, data);
this.animate(this.setData(data, true));
this.oldData = this.setData(clone, false);
},
animate: function(data){
var that = this;
var chart = d3.select(this.el);
that.generateArcs(chart, data);
},
setData: function(data, isSorted){
var diameter = 2 * Math.PI * this.radius;
var localData = new Array();
var displacement = 0;
var oldBatchLength = 0;
$.each(data, function(index, value) {
var riseLevels = value.segments;
var riseLevelCount = riseLevels.length;
if(oldBatchLength !=undefined){
displacement+=oldBatchLength;
}
var arcBatchLength = 2*Math.PI;
var arcPartition = arcBatchLength/riseLevelCount;
$.each(riseLevels, function( ri, value ) {
var startAngle = (ri*arcPartition);
var endAngle = ((ri+1)*arcPartition);
if(index!=0){
startAngle+=displacement;
endAngle+=displacement;
}
riseLevels[ri]["startAngle"] = startAngle;
riseLevels[ri]["endAngle"] = endAngle;
});
oldBatchLength = arcBatchLength;
localData.push(riseLevels);
});
var finalArray = new Array();
$.each(localData, function(index, value) {
$.each(localData[index], function(i, v) {
finalArray.push(v);
});
});
return finalArray;
},
generateArcs: function(chart, data){
var that = this;
//_arc paths
//append previous value to it.
$.each(data, function(index, value) {
if(that.oldData[index] != undefined){
data[index]["previousEndAngle"] = that.oldData[index].endAngle;
}
else{
data[index]["previousEndAngle"] = 0;
}
});
var arcpaths = that.arcpaths.selectAll("path")
.data(data);
arcpaths.enter().append("svg:path")
.style("fill", function(d, i){
return colores_google(i);
})
.transition()
.ease(d3.easeElastic)
.duration(750)
.attrTween("d", arcTween);
arcpaths.transition()
.ease(d3.easeElastic)
.style("fill", function(d, i){
return colores_google(i);
})
.duration(750)
.attrTween("d",arcTween);
arcpaths.exit().transition()
.ease(d3.easeBounce)
.duration(750)
.attrTween("d", arcTween)
.remove();
function arcTween(b) {
var prev = JSON.parse(JSON.stringify(b));
prev.endAngle = b.previousEndAngle;
var i = d3.interpolate(prev, b);
return function(t) {
return that.getArc()(i(t));
};
}
//_arc paths
var r = that.radius + 40;
var ir = that.radius - 30;
var legendHeight = this.legendPaddingTop;
var ySpace = 18;
var labelPadding = 3;
//draw labels legends
var labels = that.label_group.selectAll("text.labels")
.data(data);
labels.enter().append("svg:text")
.attr("class", "labels")
.attr("dy", function(d, i) {
legendHeight+=ySpace;
return (ySpace * i) + labelPadding;
})
.attr("text-anchor", function(d) {
return "start";
})
.text(function(d) {
return d.label;
});
labels.exit().remove();
var legend = that.legend_group.selectAll("circle").data(data);
legend.enter().append("svg:circle")
.attr("cx", 100)
.attr("cy", function(d, i) {
return ySpace * i;
})
.attr("r", 7)
.attr("width", 18)
.attr("height", 18)
.style("fill", function(d, i) {
return colores_google(i);
});
legend.exit().remove();
//reset legend height
//console.log("optimum height for legend", legendHeight);
$this.find('.legend').attr("height", legendHeight);
/*
//__labels
var starlabels = that.starlabels.selectAll("text")
.data(data);
starlabels.enter()
.append("text")
.attr("text-anchor", "middle")
starlabels.text(function(d) {
return d.label;
})
.each(function(d) {
var a = d.startAngle + (d.endAngle - d.startAngle)/2 - Math.PI/2;
d.cx = Math.cos(a) * (ir+((r-ir)/2));
d.cy = Math.sin(a) * (ir+((r-ir)/2));
d.x = d.x || Math.cos(a) * (r + 20);
d.y = d.y || Math.sin(a) * (r + 20);
var bbox = this.getBBox();
d.sx = d.x - bbox.width/2 - 2;
d.ox = d.x + bbox.width/2 + 2;
d.sy = d.oy = d.y + 5;
})
.transition()
.duration(300)
.attr("x", function(d) {
var a = d.startAngle + (d.endAngle - d.startAngle)/2 - Math.PI/2;
return d.x = Math.cos(a) * (r + 20);
})
.attr("y", function(d) {
var a = d.startAngle + (d.endAngle - d.startAngle)/2 - Math.PI/2;
return d.y = Math.sin(a) * (r + 20);
});
starlabels.exit().remove();
//__labels
//__pointers
that.pointers.append("defs").append("marker")
.attr("id", "circ")
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("refX", 3)
.attr("refY", 3)
.append("circle")
.attr("cx", 3)
.attr("cy", 3)
.attr("r", 3)
.style("fill", "#005a70");
var pointers = that.pointers.selectAll("path.pointer")
.data(data);
pointers.enter()
.append("path")
.attr("class", "pointer")
.style("fill", "none")
.style("stroke", "#005a70")
.attr("marker-end", "url(#circ)");
pointers
.transition()
.duration(300)
.attr("d", function(d) {
if(d.cx > d.ox) {
return "M" + d.sx + "," + d.sy + "L" + d.ox + "," + d.oy + " " + d.cx + "," + d.cy;
} else {
return "M" + d.ox + "," + d.oy + "L" + d.sx + "," + d.sy + " " + d.cx + "," + d.cy;
}
});
pointers.exit().remove();
//__pointers
*/
},
setup: function(el, data, w, h){
var chart = d3.select(el).append("svg")
.attr("class", "chart")
.attr("width", w)
.attr("height", h)
var arcchart = chart.append("g")
.attr("class", "starchart")
.attr("transform", "translate("+w/4+","+h/2+")");
this.arcpaths = arcchart.append("g")
.attr("class", "arcpaths");
this.starlabels = arcchart.append("g")
.attr("class", "labels");
this.pointers = arcchart.append("g")
.attr("class", "pointer");
var margin = 25;
var padding = 15;
this.legendPaddingTop = 30;
var legend = chart.append("g")
.attr("class", "legend")
.attr("width", w/3)
.attr("height", h - 50)
.attr("transform", "translate(" + (w-20) + "," + (h/4) + ")");
this.label_group = legend.append("g")
.attr("class", "label_group")
.attr("transform", "translate(" + (-(w / 3) + 20) + "," + 0 + ")");
this.legend_group = legend.append("g")
.attr("class", "legend_group")
.attr("transform", "translate(" + (-(w / 3) - 100) + "," + 0 + ")");
var radiusControl = 16;
this.dataset = "big";//more than 2 results
if(data.length <=2){
radiusControl = 65;//make the radius smaller to compromise with there being less results
this.dataset = "small";
}
this.radius = w/4 - radiusControl;
this.generateArcs(chart, data);
},
getArc: function(){
var that = this;
var arc = d3.arc()
.innerRadius(function(d, i){
var threshold = 50;
if(that.dataset == "small"){
threshold = 20;
}
return that.radius-threshold;//negative makes it deeper
})
.outerRadius(function(d){
var maxHeight = 120;
var ratio = (d.value/maxHeight * 100)+that.radius;
return ratio;
})
.startAngle(function(d, i){
return d.startAngle;
})
.endAngle(function(d, i){
return d.endAngle;
});
return arc;
}
}
arcGenerator.init($this[0], data, w, h);
In your attrTween function instead of interpolating angle interpolate the value:
function arcTween(b) {
var prev = JSON.parse(JSON.stringify(b));
prev.endAngle = b.previousEndAngle; <-- incorrect
var i = d3.interpolate(prev, b);
Interpolate outer radius like below
function arcTween(b) {
var prev = JSON.parse(JSON.stringify(b));
prev.value = 0;
var i = d3.interpolate(prev, b);
working code here

d3 Sunburst reduce parent node inner and outer radius on click

I am trying to reduce the size of the parent level inner and outer radius' when I click on one of its children nodes. You can view my current diagram here: https://jsfiddle.net/2heLd2b1/. As you can see, when a child node is clicked and the distorts to display the selected node and its path, the parent layers take up too much space. I am looking for any suggestions as to how I could reduce or shrink the parent nodes width.
ar width = 960,
height = 750,
radius = (Math.min(width, height) / 2) - 10;
var color = d3.scale.category20c();
var x = d3.scale.linear()
.range([0, 2 * Math.PI]);
var y = d3.scale.linear()
.range([0, radius]);
function percent(d) {
var percentage = (d.value / 956129) * 100;
return percentage.toFixed(2);
}
// var tip = d3.tip()
// .attr('class', 'd3-tip')
// .offset([-10, 0])
// .html(function(d) {
// return "<strong>" + d.name + "</strong> <span style='color:red'>" + percent(d) + "%</span>";
// })
var partition = d3.layout.partition()
// .value(function(d) { return d.size; });
.value(function(d) { return 1; });
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, y(d.y)) })
.outerRadius(function(d) { return Math.max(0, y(d.y + d.dy)) })
.cornerRadius(function(d) { return 5;});
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")")
.append("g")
.classed("inner", true);
// svg.call(tip);
d3.json("flare.json", function(error, root) {
if (error) throw error;
var g = svg.selectAll("g")
.data(partition.nodes(root))
.enter().append("g");
path = g.append("path")
.attr("d", arc)
.attr('stroke', 'white')
.attr("fill", function(d) { return color((d.children ? d : d.parent).name); })
.on("click", magnify)
// .on('mouseover', tip.show)
// .on('mouseout', tip.hide)
.each(stash);
var text = g.append("text")
.attr("x", function(d) { return d.x; })
.attr("dx", "6") // margin
.attr("dy", ".35em") // vertical-align
.text(function(d) {
return d.name;
})
.attr('font-size', function(d) {
return '10px';
})
.attr("text-anchor", "middle")
.attr("transform", function(d) {
if (d.depth > 0) {
return "translate(" + arc.centroid(d) + ")" +
"rotate(" + getStartAngle(d) + ")";
} else {
return null;
}
})
.on("click", magnify);
var innerG = d3.selectAll("g.inner");
// Distort the specified node to 80% of its parent.
function magnify(node) {
// get and store parent sequence
var parentSequence = getAncestors(node)
text.transition().attr("opacity", 0);
spin(node);
// check if node has a parent. If so, iterate throught parentSequence and update the size of each node in the sequence
if (node.parent) {
for (var p = 0; p < parentSequence.length; p++) {
if (parent = parentSequence[p].parent) {
var parent,
x = parent.x,
k = 0.95;
parent.children.forEach(function(sibling) {
x += reposition(sibling, x, sibling === parentSequence[p]
? parent.dx * k / parentSequence[p].value
: parent.dx * (1 - k) / (parent.value - parentSequence[p].value));
});
} else {
reposition(parentSequence[p], 0, parentSequence[p].dx / parentSequence[p].value);
}
}
// if node does not have parent (center node) reset all values to original
} else {
if (parent = node.parent) {
var parent,
x = parent.x,
k = 0.95;
parent.children.forEach(function(sibling) {
x += reposition(sibling, x, sibling === node
? parent.dx * k / node.value
: parent.dx * (1 - k) / (parent.value - node.value));
});
} else {
reposition(node, 0, node.dx / node.value);
}
}
path.transition()
.duration(750)
.attrTween("d", arcTween)
.each("end", function(e, i) {
// check if the animated element's data e lies within the visible angle span given in node
if (e.x >= node.x && e.x < (node.x + node.dx)) {
// get a selection of the associated text element
var arcText = d3.select(this.parentNode).select("text");
// fade in the text element and recalculate positions
arcText.transition().duration(750)
.attr("opacity", 1)
.attr("x", function(d) {
return d.x;
})
.attr("transform", function(d) {
if (d.depth > 0) {
return "translate(" + arc.centroid(d) + ")" +
"rotate(" + getNewAngle(d) + ")";
} else {
return null;
}
});
}
});
}
function spin(d) {
var spin1 = new Promise (function(resolve, reject) {
var newAngle = - x(d.x + d.dx / 2);
// console.log('newAngle', newAngle)
innerG
.transition()
.duration(1500)
.attr("transform", "rotate(" + ((180 / Math.PI * newAngle)) + ")");
resolve("Success!");
});
spin1.then(function() {
var newerAngle = - x(d.x + d.dx / 2);
// console.log('newerAngle', newerAngle)
innerG
.transition()
.duration(1500)
.attr("transform", "rotate(" + ((180 / Math.PI * newerAngle)) + ")");
})
path
.classed("selected", function (x) { return d.name == x.name; });
}
// Recursively reposition the node at position x with scale k.
function reposition(node, x, k) {
// console.log(k)
node.x = x;
if (node.children && (n = node.children.length)) {
var i = -1, n;
while (++i < n) x += reposition(node.children[i], x, k);
}
return node.dx = node.value * k;
}
// Stash the old values for transition.
function stash(d) {
d.x0 = d.x;
d.dx0 = d.dx;
}
// Interpolate the arcs in data space.
function arcTween(a) {
var i = d3.interpolate({x: a.x0, dx: a.dx0}, a);
return function(t) {
var b = i(t);
a.x0 = b.x;
a.dx0 = b.dx;
return arc(b);
};
};
});
function getStartAngle(d) {
// Offset the angle by 90 deg since the '0' degree axis for arc is Y axis, while
// for text it is the X axis.
var thetaDeg = (180 / Math.PI * (arc.startAngle()(d) + arc.endAngle()(d)) / 2 - 90);
// If we are rotating the text by more than 90 deg, then "flip" it.
// This is why "text-anchor", "middle" is important, otherwise, this "flip" would
// a little harder.
return (thetaDeg > 90) ? thetaDeg - 180 : thetaDeg;
}
function getNewAngle(d) {
var thetaDeg = (180 / Math.PI * (arc.startAngle()(d) + arc.endAngle()(d)) / 2 - 90);
return (thetaDeg < 90) ? thetaDeg - 180 : thetaDeg;
}
function getAncestors(node) {
var path = [];
var current = node;
while (current.parent) {
path.unshift(current);
current = current.parent;
}
return path;
}
I managed to figure it out by combining the tween methods from my original jsFiddle link, https://jsfiddle.net/2heLd2b1/, with the traditional tween used by a zoomable sunburst.You can see the implementation here: https://jsfiddle.net/6e4y0s11/
I altered my innerRadius and outerRadius from:
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, y(d.y)) })
.outerRadius(function(d) { return Math.max(0, y(d.y + d.dy)) })
.cornerRadius(function(d) { return 5;});
to:
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(d.depth * 20, y(d.y)) })
.outerRadius(function(d) { return Math.max(100, y(d.y + d.dy)) })
.cornerRadius(function(d) { return 5;});
I also added:
function arcTweenZoom(d) {
var yd = d3.interpolate(y.domain(), [d.y, 1]),
yr = d3.interpolate(y.range(), [d.y ? 20 : 0, radius]);
return function(d, i) {
return i
? function(t) { return arc(d); }
: function(t) {
y.domain(yd(t)).range(yr(t));
return arc(d);
};
};
}
so that I could interpolate the scales. The final result allows the inner parent level nodes to shrink without actually disappearing. The maintain a minimum radius based on their d.y values.

d3.js and Sankey diagram error

I'm struggling to get my first Sankey made the data is pretty simple:
Discharge OBS ADM FULL ADM Total
Station 1 1725 610 708 3043
Station 2 1095 424 464 1983
Station 3 1652 24 27 1703
So, I was thinking of creating the nodes and links in the following fashion - which was unsuccessful:
{
"nodes":[
{"name":"All Patients"},
{"name":"Station 1"},
{"name":"Station 2"},
{"name":"Station 3"},
{"name":"Discharge"},
{"name":"Obs Admission"},
{"name":"Full Admission"}
],
"links":[
{"source":"All Patients","target":"Station 1","value":3.043},
{"source":"All Patients","target":"Station 2","value":1.983},
{"source":"All Patients","target":"Station 3","value":1.703},
{"source":"Station 1","target":"Discharge","value":1.725},
{"source":"Station 2","target":"Discharge","value":1.095},
{"source":"Station 3","target":"Discharge","value":1.652},
{"source":"Station 1","target":"Obs Admission","value":.610},
{"source":"Station 2","target":"Obs Admission","value":.424},
{"source":"Station 3","target":"Obs Admission","value":.024},
{"source":"Station 1","target":"Full Admission","value":.708},
{"source":"Station 2","target":"Full Admission","value":.464},
{"source":"Station 3","target":"Full Admission","value":.027},
]}
Using this index.html:
<!DOCTYPE html>
<meta charset="utf-8">
<title>SANKEY Experiment</title>
<style>
.node rect {
cursor: move;
fill-opacity: .9;
shape-rendering: crispEdges;
}
.node text {
pointer-events: none;
text-shadow: 0 1px 0 #fff;
}
.link {
fill: none;
stroke: #000;
stroke-opacity: .2;
}
.link:hover {
stroke-opacity: .5;
}
</style>
<body>
<p id="chart">
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="sankey.js"></script>
<script>
var units = "Widgets";
var margin = {top: 10, right: 10, bottom: 10, left: 10},
width = 700 - margin.left - margin.right,
height = 300 - 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 page
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 + ")");
// Set the sankey diagram properties
var sankey = d3.sankey()
.nodeWidth(36)
.nodePadding(40)
.size([width, height]);
var path = sankey.link();
// load the data
d3.json("sankey-formatted-names.json", function(error, graph) {
var nodeMap = {};
graph.nodes.forEach(function(x) { nodeMap[x.name] = x; });
graph.links = graph.links.map(function(x) {
return {
source: nodeMap[x.source],
target: nodeMap[x.target],
value: x.value
};
});
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("stroke-width", function(d) { 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 + ")"; })
.call(d3.behavior.drag()
.origin(function(d) { return d; })
.on("dragstart", function() {
this.parentNode.appendChild(this); })
.on("drag", dragmove));
// add the rectangles for the nodes
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); });
// add in the title for the nodes
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");
// the function for moving the nodes
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);
}
});
</script>
</body>
</html>
And this 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) {
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;
};
It didn't work... not sure why. Any advice is greatly appreciated.
Alfa
Unfortunately, looks like couple very minor mistakes.
1) You have an extra comma at the end of your links {"source":"Station 3","target":"Full Admission","value":.027}**,** so use updated sankey-formatted-names.json file without extra comma below. I like to use JSON validator https://jsonformatter.curiousconcept.com/ to confirm if valid JSON file.
2) Also, I looked at an example JSON file at http://bl.ocks.org/d3noob/5028304 and they have the values in double quotes. Since you have source and target as strings, then looks like value must also be a string. If you have source and target as numeric, then value can be numeric.
{
"nodes":[
{"name":"All Patients"},
{"name":"Station 1"},
{"name":"Station 2"},
{"name":"Station 3"},
{"name":"Discharge"},
{"name":"Obs Admission"},
{"name":"Full Admission"}
],
"links":[
{"source":"All Patients","target":"Station 1","value":"3.043"},
{"source":"All Patients","target":"Station 2","value":"1.983"},
{"source":"All Patients","target":"Station 3","value":"1.703"},
{"source":"Station 1","target":"Discharge","value":"1.725"},
{"source":"Station 2","target":"Discharge","value":"1.095"},
{"source":"Station 3","target":"Discharge","value":"1.652"},
{"source":"Station 1","target":"Obs Admission","value":"0.610"},
{"source":"Station 2","target":"Obs Admission","value":"0.424"},
{"source":"Station 3","target":"Obs Admission","value":"0.024"},
{"source":"Station 1","target":"Full Admission","value":"0.708"},
{"source":"Station 2","target":"Full Admission","value":"0.464"},
{"source":"Station 3","target":"Full Admission","value":"0.027"}
]}

Categories