Skip to content
91 changes: 91 additions & 0 deletions R/hiveNetwork.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
#' Create a D3 JavaScript hive plot representation of a network.
#'
#' @TODO
#' 1. Think about bindings from data.frame to: axis and group
#' 2. Limit to ? axis?
#'
#' @export
hiveNetwork <- function(nodes,
links,
source = NULL,
target = NULL,
linksize = NULL,
linkcolour = NULL,
nodeID = NULL,
x = NULL,
y = NULL,
nodesize = NULL,
nodecolour = NULL,
height = NULL,
width = NULL)
{

# some checks ----
if (!is.data.frame(links))
stop("Links must be a data frame class object.")

if (!is.data.frame(nodes))
stop("Nodes must be a data frame class object.")

# helper - rescale attributes to play nice with axis
.rescale <- function(x) (x - min(x))/(max(x) - min(x))

# axis binding
if (is.null(nodes$x)) {
indeces <- 1:nrow(nodes) - 1
indeces <- cbind( indeces %in% links$source & !indeces %in% links$target,
indeces %in% links$source & indeces %in% links$target,
!indeces %in% links$source & indeces %in% links$target)
nodes$x <- apply(indeces, 1, which)
}

# radius binding
nodes$y <- .rescale(nodes$y)

# node size binding
if (is.null(nodes$nodesize)) {
nodes$nodesize <- 5
}

# node size binding
if (is.null(nodes$nodecolour)) {
nodes$nodecolour <- nodes$x
}

# link size binding
if (is.null(links$linksize)) {
links$linksize <- 0.5
}

# link colour binding
if (is.null(links$linkcolour)) {
links$linkcolour <- links$source
}

# create options ----
options = list()

# create widget
htmlwidgets::createWidget(
name = "hiveNetwork",
x = list(links = links, nodes = nodes, options = options),
width = width,
height = height,
htmlwidgets::sizingPolicy(padding = 10, browser.fill = TRUE),
package = "networkD3"
)
}

#' @rdname networkD3-shiny
#' @export
hiveNetworkOutput <- function(outputId, width = "100%", height = "500px") {
shinyWidgetOutput(outputId, "hiveNetwork", width, height,
package = "networkD3")
}

#' @rdname networkD3-shiny
#' @export
renderHiveNetwork <- function(expr, env = parent.frame(), quoted = FALSE) {
if (!quoted) { expr <- substitute(expr) } # force quoted
shinyRenderWidget(expr, forceNetworkOutput, env, quoted = TRUE)
}
130 changes: 130 additions & 0 deletions inst/htmlwidgets/hiveNetwork.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
HTMLWidgets.widget({

name: "hiveNetwork",

type: "output",

initialize: function(el, width, height) {

var svg = d3.select(el).append("svg")
.attr("viewBox", "0 0 " + width + " " + height)
.attr("preserveAspectRatio", "xMidYMid meet")
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");

return svg;
},

resize: function(el, width, height, instance) {
// handle with viewBox
},

renderValue: function(el, x, svg) {

// Convert radians to degrees
function degrees(radians) {
return radians / Math.PI * 180 - 90;
}

// get the width and height
var width = el.offsetWidth;
var height = el.offsetHeight;
var innerRadius = 40;
var outerRadius = Math.min(width, height)/2 - 20;

var angle = d3.scale.ordinal().domain(d3.range(4)).rangePoints([0, 2 * Math.PI]),
radius = d3.scale.linear().range([innerRadius, outerRadius]),
color = d3.scale.category10().domain(d3.range(20));

// alias options
// var options = x.options;

// convert links and nodes data frames to d3 friendly format
var nodes = HTMLWidgets.dataframeToD3(x.nodes);
var tmp = HTMLWidgets.dataframeToD3(x.links);

// create links associative array from nodes
var links = Array(tmp.length);
for (var i = 0; i < tmp.length; i++) {
links[i] = { "source" : nodes[tmp[i].source],
"target" : nodes[tmp[i].target],
"Linksize" : tmp[i].Linksize,
"Linkcolour" : tmp[i].Linkcolour
};
}

// map elements
svg.selectAll(".axis")
.data(d3.range(3))
.enter().append("line")
.attr("class", "axis")
.attr("transform", function(d) { return "rotate(" + degrees(angle(d)) + ")"; })
.attr("x1", radius.range()[0])
.attr("x2", radius.range()[1])
.style("stroke", '#000')
.style('stroke-width', '2px');

svg.selectAll(".link")
.data(links)
.enter().append("path")
.attr("class", "link")
.attr("d", d3.hive.link()
.angle(function(d) { return angle(d.x); })
.radius(function(d) { return radius(d.y); }))
.style("stroke", function(d) { return color(d.source.x); })
// .style("stroke", "#000")
.style('stroke-width', '0.5px')
.style("fill", "none");

svg.selectAll(".node")
.data(nodes)
.enter().append("circle")
.attr("class", "node")
.attr("transform", function(d) { return "rotate(" + degrees(angle(d.x)) + ")"; })
.attr("cx", function(d) { return radius(d.y); })
.style("fill", function(d) { return color(d.x); })
.attr("r", function(d) { return d.nodesize; })
.style("stroke", '#000')
.on("mouseenter", function(d) {
d3.select(this)
.transition()
.duration(50)
.style("stroke-width", 3)
.attr("r", 15);

d3.selectAll(".link")
.data(links)
.style("stroke-width", function (dl) {
if(dl.source == d){
return 5;
} else if(dl.target == d){
return 5;
} else {
return 0.5;
}
});
/*
.style("stroke", function (dl) {
if(dl.source == d){
return color(d.x);
} else if(dl.target == d){
return color(d.x);
} else {
return "#000";
}
});
*/
})
.on("mouseleave", function(d){
d3.select(this)
.transition()
.duration(50)
.style("stroke-width", 1.5)
.attr("r", function(d) { return d.nodesize; });

d3.selectAll(".link")
.style("stroke-width", 0.5);
// .style("stroke", "#000")
});
},
});
9 changes: 9 additions & 0 deletions inst/htmlwidgets/hiveNetwork.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
dependencies:
- name: d3
version: 3.5.2
src: "htmlwidgets/lib/d3-3.5.2"
script: d3.min.js
- name: hive
version: 1.0
src: "htmlwidgets/lib"
script: d3.hive.min.js
1 change: 1 addition & 0 deletions inst/htmlwidgets/lib/d3.hive.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.