HTMLWidgets.widget({
name: "diagonalNetwork",
type: "output",
initialize: function(el, width, height) {
d3.select(el).append("svg")
.style("width", "100%")
.style("height", "100%")
.append("g")
.attr("transform", "translate(40,0)");
return d3.tree();
},
resize: function(el, width, height, tree) {
// resize now handled by svg viewBox attribute
/*
var s = d3.select(el).selectAll("svg");
s.attr("width", width).attr("height", height);
var margin = {top: 20, right: 20, bottom: 20, left: 20};
width = width - margin.right - margin.left;
height = height - margin.top - margin.bottom;
tree.size([height, width]);
var svg = d3.select(el).selectAll("svg").select("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
*/
},
renderValue: function(el, x, tree) {
// x is a list with two elements, options and root; root must already be a
// JSON array with the d3Tree root data
var s = d3.select(el).selectAll("svg");
// when re-rendering the svg, the viewBox attribute set in the code below, will
// be affected by the previously set viewBox. This line ensures, that the
// viewBox will always be calculated right.
s.attr("viewBox", null);
// margin handling
// set our default margin to be 20
// will override with x.options.margin if provided
var margin = {top: 20, right: 20, bottom: 20, left: 20};
// go through each key of x.options.margin
// use this value if provided from the R side
Object.keys(x.options.margin).map(function(ky){
if(x.options.margin[ky] !== null) {
margin[ky] = x.options.margin[ky];
}
// set the margin on the svg with css style
// commenting this out since not correct
// s.style(["margin",ky].join("-"), margin[ky]);
});
width = s.node().getBoundingClientRect().width - margin.right - margin.left;
height = s.node().getBoundingClientRect().height - margin.top - margin.bottom;
//added Math.max(1, ...) to avoid NaN values when dealing with nodes of depth 0.
tree.size([height, width])
.separation(function(a, b) { return (a.parent == b.parent ? 1 : 2) / Math.max(1, a.depth); });
// select the svg group element and remove existing children
s.attr("pointer-events", "all").selectAll("*").remove();
s.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var svg = d3.select(el).selectAll("g");
var root = d3.hierarchy(x.root);
tree(root);
var diagonal = function(d, i) {
return "M" + d.source.y + "," + d.source.x
+ "C" + (d.source.y + d.target.y) / 2 + "," + d.source.x
+ " " + (d.source.y + d.target.y) / 2 + "," + d.target.x
+ " " + d.target.y + "," + d.target.x;
};
// draw links
var link = svg.selectAll(".link")
.data(root.links())
.enter().append("path")
.style("fill", "none")
.style("stroke", x.options.linkColour)
.style("opacity", "0.55")
.style("stroke-width", "1.5px")
.attr("d", diagonal);
// draw nodes
var node = svg.selectAll(".node")
.data(root.descendants())
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + d.y + "," + d.x + ")";
})
.on("mouseover", mouseover)
.on("mouseout", mouseout);
// node circles
node.append("circle")
.attr("r", 4.5)
.style("fill", x.options.nodeColour)
.style("opacity", x.options.opacity)
.style("stroke", x.options.nodeStroke)
.style("stroke-width", "1.5px");
// node text
node.append("text")
.attr("dx", function(d) { return d.children ? -8 : 8; })
.attr("dy", ".31em")
.attr("text-anchor", function(d) {
return d.children || d._children ? "end" : "start";
})
.style("font", x.options.fontSize + "px " + x.options.fontFamily)
.style("opacity", x.options.opacity)
.style("fill", x.options.textColour)
.text(function(d) { return d.data.name; });
// adjust viewBox to fit the bounds of our tree
s.attr(
"viewBox",
[
d3.min(
s.selectAll('.node text').nodes().map(function(d){
return d.getBoundingClientRect().left
})
) - s.node().getBoundingClientRect().left - margin.right,
d3.min(
s.selectAll('.node text').nodes().map(function(d){
return d.getBoundingClientRect().top
})
) - s.node().getBoundingClientRect().top - margin.top,
d3.max(
s.selectAll('.node text').nodes().map(function(d){
return d.getBoundingClientRect().right
})
) -
d3.min(
s.selectAll('.node text').nodes().map(function(d){
return d.getBoundingClientRect().left
})
) + margin.left + margin.right,
d3.max(
s.selectAll('.node text').nodes().map(function(d){
return d.getBoundingClientRect().bottom
})
) -
d3.min(
s.selectAll('.node text').nodes().map(function(d){
return d.getBoundingClientRect().top
})
) + margin.top + margin.bottom
].join(",")
);
// mouseover event handler
function mouseover() {
d3.select(this).select("circle").transition()
.duration(750)
.attr("r", 9);
d3.select(this).select("text").transition()
.duration(750)
.style("stroke-width", ".5px")
.style("font", "25px " + x.options.fontFamily)
.style("opacity", 1);
}
// mouseout event handler
function mouseout() {
d3.select(this).select("circle").transition()
.duration(750)
.attr("r", 4.5);
d3.select(this).select("text").transition()
.duration(750)
.style("font", x.options.fontSize + "px " + x.options.fontFamily)
.style("opacity", x.options.opacity);
}
},
});