231 lines
7.7 KiB
JavaScript
231 lines
7.7 KiB
JavaScript
|
HTMLWidgets.widget({
|
||
|
|
||
|
name: "sankeyNetwork",
|
||
|
|
||
|
type: "output",
|
||
|
|
||
|
initialize: function(el, width, height) {
|
||
|
|
||
|
d3.select(el).append("svg")
|
||
|
.style("width", "100%")
|
||
|
.style("height", "100%");
|
||
|
|
||
|
return {
|
||
|
sankey: d3.sankey(),
|
||
|
x: null
|
||
|
};
|
||
|
},
|
||
|
|
||
|
resize: function(el, width, height, instance) {
|
||
|
/* handle resizing now through the viewBox
|
||
|
d3.select(el).select("svg")
|
||
|
.attr("width", width)
|
||
|
.attr("height", height + height * 0.05);
|
||
|
|
||
|
this.renderValue(el, instance.x, instance);
|
||
|
*/
|
||
|
},
|
||
|
|
||
|
renderValue: function(el, x, instance) {
|
||
|
|
||
|
// save the x in our instance (for calling back from resize)
|
||
|
instance.x = x;
|
||
|
|
||
|
// alias sankey and options
|
||
|
var sankey = instance.sankey;
|
||
|
var options = x.options;
|
||
|
|
||
|
// convert links and nodes data frames to d3 friendly format
|
||
|
var links = HTMLWidgets.dataframeToD3(x.links);
|
||
|
var nodes = HTMLWidgets.dataframeToD3(x.nodes);
|
||
|
|
||
|
|
||
|
// 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]);
|
||
|
});
|
||
|
|
||
|
// get the width and height
|
||
|
var width = el.getBoundingClientRect().width - margin.right - margin.left;
|
||
|
var height = el.getBoundingClientRect().height - margin.top - margin.bottom;
|
||
|
|
||
|
var color = eval(options.colourScale);
|
||
|
|
||
|
var color_node = function color_node(d){
|
||
|
if (d.group){
|
||
|
return color(d.group.replace(/ .*/, ""));
|
||
|
} else {
|
||
|
return "#cccccc";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var color_link = function color_link(d){
|
||
|
if (d.group){
|
||
|
return color(d.group.replace(/ .*/, ""));
|
||
|
} else {
|
||
|
return "#000000";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var opacity_link = function opacity_link(d){
|
||
|
if (d.group){
|
||
|
return 0.7;
|
||
|
} else {
|
||
|
return 0.2;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
var formatNumber = d3.format(",.0f"),
|
||
|
format = function(d) { return formatNumber(d); }
|
||
|
|
||
|
// create d3 sankey layout
|
||
|
sankey
|
||
|
.nodes(d3.values(nodes))
|
||
|
.links(links)
|
||
|
.size([width, height])
|
||
|
.nodeWidth(options.nodeWidth)
|
||
|
.nodePadding(options.nodePadding)
|
||
|
.layout(32);
|
||
|
|
||
|
// select the svg element and remove existing children
|
||
|
d3.select(el).select("svg").selectAll("*").remove();
|
||
|
// append g for our container to transform by margin
|
||
|
var svg = d3.select(el).select("svg").append("g")
|
||
|
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");;
|
||
|
|
||
|
// draw path
|
||
|
var path = sankey.link();
|
||
|
|
||
|
// draw links
|
||
|
var link = svg.selectAll(".link")
|
||
|
.data(sankey.links())
|
||
|
|
||
|
link.enter().append("path")
|
||
|
.attr("class", "link")
|
||
|
|
||
|
link
|
||
|
.attr("d", path)
|
||
|
.style("stroke-width", function(d) { return Math.max(1, d.dy); })
|
||
|
.style("fill", "none")
|
||
|
.style("stroke", color_link)
|
||
|
.style("stroke-opacity", opacity_link)
|
||
|
.sort(function(a, b) { return b.dy - a.dy; })
|
||
|
.on("mouseover", function(d) {
|
||
|
d3.select(this)
|
||
|
.style("stroke-opacity", function(d){return opacity_link(d) + 0.3});
|
||
|
})
|
||
|
.on("mouseout", function(d) {
|
||
|
d3.select(this)
|
||
|
.style("stroke-opacity", opacity_link);
|
||
|
});
|
||
|
|
||
|
// add backwards class to cycles
|
||
|
link.classed('backwards', function (d) { return d.target.x < d.source.x; });
|
||
|
|
||
|
svg.selectAll(".link.backwards")
|
||
|
.style("stroke-dasharray","9,1")
|
||
|
.style("stroke","#402")
|
||
|
|
||
|
// draw nodes
|
||
|
var node = svg.selectAll(".node")
|
||
|
.data(sankey.nodes())
|
||
|
|
||
|
node.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));
|
||
|
|
||
|
// note: u2192 is right-arrow
|
||
|
link.append("title")
|
||
|
.text(function(d) { return d.source.name + " \u2192 " + d.target.name +
|
||
|
"\n" + format(d.value) + " " + options.units; });
|
||
|
|
||
|
node.append("rect")
|
||
|
.attr("height", function(d) { return d.dy; })
|
||
|
.attr("width", sankey.nodeWidth())
|
||
|
.style("fill", function(d) {
|
||
|
return d.color = color_node(d); })
|
||
|
.style("stroke", function(d) { return d3.rgb(d.color).darker(2); })
|
||
|
.style("opacity", 0.9)
|
||
|
.style("cursor", "move")
|
||
|
.append("title")
|
||
|
.text(function(d) { return d.name + "\n" + format(d.value) +
|
||
|
" " + options.units; });
|
||
|
|
||
|
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; })
|
||
|
.style("font-size", options.fontSize + "px")
|
||
|
.style("font-family", options.fontFamily ? options.fontFamily : "inherit")
|
||
|
.filter(function(d) { return d.x < width / 2; })
|
||
|
.attr("x", 6 + sankey.nodeWidth())
|
||
|
.attr("text-anchor", "start");
|
||
|
|
||
|
|
||
|
// adjust viewBox to fit the bounds of our tree
|
||
|
var s = d3.select(svg[0][0].parentNode);
|
||
|
s.attr(
|
||
|
"viewBox",
|
||
|
[
|
||
|
d3.min(
|
||
|
s.selectAll('g')[0].map(function(d){
|
||
|
return d.getBoundingClientRect().left
|
||
|
})
|
||
|
) - s.node().getBoundingClientRect().left - margin.right,
|
||
|
d3.min(
|
||
|
s.selectAll('g')[0].map(function(d){
|
||
|
return d.getBoundingClientRect().top
|
||
|
})
|
||
|
) - s.node().getBoundingClientRect().top - margin.top,
|
||
|
d3.max(
|
||
|
s.selectAll('g')[0].map(function(d){
|
||
|
return d.getBoundingClientRect().right
|
||
|
})
|
||
|
) -
|
||
|
d3.min(
|
||
|
s.selectAll('g')[0].map(function(d){
|
||
|
return d.getBoundingClientRect().left
|
||
|
})
|
||
|
) + margin.left + margin.right,
|
||
|
d3.max(
|
||
|
s.selectAll('g')[0].map(function(d){
|
||
|
return d.getBoundingClientRect().bottom
|
||
|
})
|
||
|
) -
|
||
|
d3.min(
|
||
|
s.selectAll('g')[0].map(function(d){
|
||
|
return d.getBoundingClientRect().top
|
||
|
})
|
||
|
) + margin.top + margin.bottom
|
||
|
].join(",")
|
||
|
);
|
||
|
|
||
|
|
||
|
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);
|
||
|
}
|
||
|
},
|
||
|
});
|