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>.
Related
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
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
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;
}
I'm developing a legend toggling d3.js pie chart application using this jsfiddle as my latest version http://jsfiddle.net/Qh9X5/3328/ .
I am aiming to get a streamlined working example where the legend can toggle the slices, trying to deactivate all slices - resorts in a reset which reactivates all the slices. Splitting up presentation and application layer logic.
Tweening needs improvement too - as the slices pop into existence then re-tween smoothly.
How do I improve/fix the various bugs in this code base?
onLegendClick: function(dt, i){
//_toggle rectangle in legend
var completeData = jQuery.extend(true, [], methods.currentDataSet);
newDataSet = completeData;
if(methods.manipulatedData){
newDataSet = methods.manipulatedData;
}
d3.selectAll('rect')
.data([dt], function(d) {
return d.data.label;
})
.style("fill-opacity", function(d, j) {
var isActive = Math.abs(1-d3.select(this).style("fill-opacity"));
if(isActive){
newDataSet[j].total = completeData[j].total;
}else{
newDataSet[j].total = 0;
}
return isActive;
});
//animate slices
methods.animateSlices(newDataSet);
//stash manipulated data
methods.manipulatedData = newDataSet;
}
Here is the entire js code - I've used the tidyup. I wasn't sure about using the shortcuts as I'm not sure the values will be correct. The latest fiddle - http://jsfiddle.net/Qh9X5/3340/
$(document).ready(function () {
var pieChart = {
el: "",
init: function (el, options) {
var clone = jQuery.extend(true, {}, options["data"]);
pieChart.el = el;
pieChart.setup(clone, options["width"], options["height"], options["r"], options["ir"]);
},
getArc: function (radius, innerradius) {
var arc = d3.svg.arc()
.innerRadius(innerradius)
.outerRadius(radius);
return arc;
},
setup: function (dataset, w, h, r, ir) {
var padding = 80;
this.width = w;
this.height = h;
this.radius = r
this.innerradius = ir;
this.color = d3.scale.category20();
this.pie = d3.layout.pie()
.sort(null)
.value(function (d) {
return d.total;
});
this.arc = this.getArc(this.radius, this.innerradius);
this.svg = d3.select(pieChart.el["selector"]).append("svg")
.attr("width", this.width + padding)
.attr("height", this.height + padding);
this.holder = this.svg.append("g")
.attr("transform", "translate(" + ((this.width / 2) + (padding / 2)) + "," + ((this.height / 2) + (padding / 2)) + ")");
this.piec = this.holder.append("g")
.attr("class", "piechart");
this.segments = this.holder.append("g")
.attr("class", "segments");
this.labels = this.holder.append("g")
.attr("class", "labels");
this.pointers = this.holder.append("g")
.attr("class", "pointers");
this.legend = this.svg.append("g")
.attr("class", "legend")
.attr("transform", "translate(" + -(this.width / 4) + "," + this.height + ")");
},
oldPieData: "",
pieTween: function (r, ir, d, i) {
var that = this;
var theOldDataInPie = pieChart.oldPieData;
// Interpolate the arcs in data space
var s0;
var e0;
if (theOldDataInPie[i]) {
s0 = theOldDataInPie[i].startAngle;
e0 = theOldDataInPie[i].endAngle;
} else if (!(theOldDataInPie[i]) && theOldDataInPie[i - 1]) {
s0 = theOldDataInPie[i - 1].endAngle;
e0 = theOldDataInPie[i - 1].endAngle;
} else if (!(theOldDataInPie[i - 1]) && theOldDataInPie.length > 0) {
s0 = theOldDataInPie[theOldDataInPie.length - 1].endAngle;
e0 = theOldDataInPie[theOldDataInPie.length - 1].endAngle;
} else {
s0 = 0;
e0 = 0;
}
var i = d3.interpolate({
startAngle: s0,
endAngle: e0
}, {
startAngle: d.startAngle,
endAngle: d.endAngle
});
return function (t) {
var b = i(t);
return pieChart.getArc(r, ir)(b);
};
},
removePieTween: function (r, ir, d, i) {
var that = this;
s0 = 2 * Math.PI;
e0 = 2 * Math.PI;
var i = d3.interpolate({
startAngle: d.startAngle,
endAngle: d.endAngle
}, {
startAngle: s0,
endAngle: e0
});
return function (t) {
var b = i(t);
return pieChart.getArc(r, ir)(b);
};
},
animateSlices: function (dataSet) {
var r = $(pieChart.el["selector"]).data("r");
var ir = $(pieChart.el["selector"]).data("ir");
this.piedata = pieChart.pie(dataSet);
//__slices
this.path = pieChart.segments.selectAll("path.pie")
.data(this.piedata, function (d) {
return d.data.label
});
this.path.enter().append("path")
.attr("class", "pie")
.attr("fill", function (d, i) {
return pieChart.color(i);
})
.attr("stroke", "#ffffff")
.transition()
.duration(300)
.attrTween("d", function (d, i) {
return pieChart.pieTween(r, ir, d, i);
});
this.path.transition()
.duration(300)
.attrTween("d", function (d, i) {
return pieChart.pieTween(r, ir, d, i);
});
this.path.exit()
.transition()
.duration(300)
.attrTween("d", function (d, i) {
return pieChart.removePieTween(r, ir, d, i);
})
.remove();
//__slices
//__labels
var labels = pieChart.labels.selectAll("text")
.data(this.piedata, function (d) {
return d.data.label
});
labels.enter()
.append("text")
.attr("text-anchor", "middle")
labels.attr("x", function (d) {
var a = d.startAngle + (d.endAngle - d.startAngle) / 2 - Math.PI / 2;
d.cx = Math.cos(a) * (ir + ((r - ir) / 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;
d.cy = Math.sin(a) * (ir + ((r - ir) / 2));
return d.y = Math.sin(a) * (r + 20);
})
.attr("opacity", function (d) {
var opacityLevel = 1;
if (d.value == 0) {
opacityLevel = 0;
}
return opacityLevel;
})
.text(function (d) {
return d.data.label;
})
.each(function (d) {
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)
labels.transition()
.duration(300)
labels.exit().remove();
//__labels
//__pointers
pieChart.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);
var pointers = pieChart.pointers.selectAll("path.pointer")
.data(this.piedata, function (d) {
return d.data.label
});
pointers.enter()
.append("path")
.attr("class", "pointer")
.style("fill", "none")
.style("stroke", "black")
.attr("marker-end", "url(#circ)");
pointers.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;
}
})
.attr("opacity", function (d) {
var opacityLevel = 1;
if (d.value == 0) {
opacityLevel = 0;
}
return opacityLevel;
})
.transition()
.duration(300)
pointers.transition()
.duration(300)
pointers.exit().remove();
},
onToggle: function (sliceData, index) {
//_toggle rectangle in legend
//_toggle slice
var completeData = jQuery.extend(true, [], pieChart.currentDataSet);
var dataLength = completeData.length;
var newDataSet = completeData;
if (pieChart.manipulatedData) {
newDataSet = pieChart.manipulatedData;
}
d3.selectAll('rect')
.data([sliceData], function (d) {
return d.data.label;
})
.style("fill-opacity", function (d) {
var isActive = Math.abs(1 - d3.select(this).style("fill-opacity"));
if (isActive) {
newDataSet[index].total = completeData[index].total;
newDataSet[index].value = completeData[index].value;
} else {
newDataSet[index].total = 0;
newDataSet[index].value = 0;
}
return isActive;
});
//if all elements are to be not shown - reset to show all slices again.
//animate slices
pieChart.animateSlices(newDataSet);
//stash manipulated data
pieChart.manipulatedData = newDataSet;
},
update: function (el, dataSet) {
var that = this;
pieChart.el = el;
pieChart.svg = d3.select(pieChart.el["selector"] + " .piechart");
pieChart.segments = d3.select(pieChart.el["selector"] + " .segments");
pieChart.labels = d3.select(pieChart.el["selector"] + " .labels");
pieChart.pointers = d3.select(pieChart.el["selector"] + " .pointers");
pieChart.legend = d3.select(pieChart.el["selector"] + " .legend");
dataSet.forEach(function (d) {
d.total = +d.value;
});
pieChart.currentDataSet = dataSet;
pieChart.animateSlices(dataSet);
//__legends
var w = 200;
// add legend
var legend = pieChart.legend; //.append("g")
var legendRects = legend.selectAll('rect')
.data(this.piedata, function (d) {
return d.data.label
});
legendRects.enter()
.append("rect")
.attr("x", w - 65)
.attr("y", function (d, i) {
return i * 20;
})
.attr("width", 10)
.attr("height", 10)
.style("fill", function (d, i) {
return pieChart.color(i);
})
.style("stroke", function (d, i) {
return pieChart.color(i);
})
.on('click', function(d, i){
pieChart.onToggle(d, i);
})
.transition()
.duration(300)
legendRects.style("fill", function (d, i) {
return pieChart.color(i);
})
.style("stroke", function (d, i) {
return pieChart.color(i);
})
.transition()
.duration(300)
legendRects.exit().remove();
var legendText = legend.selectAll('text.label')
.data(this.piedata, function (d) {
return d.data.label
});
legendText.enter()
.append("text")
.attr("class", "label")
.attr("x", w - 52)
.attr("y", function (d, i) {
return i * 20 + 9;
})
.text(function (d) {
return d.data.label;
})
.transition()
.duration(300)
legendText.text(function (d) {
return d.data.label;
})
.transition()
.duration(300)
legendText.exit().remove();
var legendTextVals = legend.selectAll('text.vals')
.data(this.piedata, function (d) {
return d.data.label
});
legendTextVals.enter()
.append("text")
.attr("class", "vals")
.attr("x", w + 20)
.attr("y", function (d, i) {
return i * 20 + 9;
})
.text(function (d) {
return d.data.value;
})
.transition()
.duration(300)
legendTextVals.text(function (d) {
return d.data.value;
})
.transition()
.duration(300)
legendTextVals.exit().remove();
//__pointers
this.oldPieData = this.piedata;
}
};
var dataCharts = [{
"data": [{
"segments": [{
"label": "apple",
"value": 53245
}, {
"label": "cherry",
"value": 145
}, {
"label": "pear",
"value": 2245
}, {
"label": "bananana",
"value": 15325
}]
}]
}, {
"data": [{
"segments": [{
"label": "milk",
"value": 122
}, {
"label": "cheese",
"value": 44
}, {
"label": "grapes",
"value": 533
}]
}]
}, {
"data": [{
"segments": [{
"label": "pineapple",
"value": 1532
}, {
"label": "orange",
"value": 1435
}, {
"label": "grapes",
"value": 22
}]
}]
}, {
"data": [{
"segments": [{
"label": "lemons",
"value": 133
}, {
"label": "mango",
"value": 435
}, {
"label": "melon",
"value": 2122
}]
}]
}];
var clone = jQuery.extend(true, {}, dataCharts);
//__invoke concentric
$('[data-role="piechart"]').each(function (index) {
var selector = "piechart" + index;
$(this).attr("id", selector);
var options = {
data: clone[0].data,
width: $(this).data("width"),
height: $(this).data("height"),
r: $(this).data("r"),
ir: $(this).data("ir")
}
pieChart.init($("#" + selector), options);
pieChart.update($("#" + selector), clone[0].data[0].segments);
});
$(".testers a").on("click", function (e) {
e.preventDefault();
var clone = jQuery.extend(true, {}, dataCharts);
var min = 0;
var max = 3;
//__invoke pie chart
$('[data-role="piechart"]').each(function (index) {
pos = Math.floor(Math.random() * (max - min + 1)) + min;
pieChart.update($("#" + $(this).attr("id")), clone[pos].data[0].segments);
});
});
});
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 ..