Skip to content

Commit

Permalink
Merge pull request #1 from AdeelK93/dev
Browse files Browse the repository at this point in the history
Added tooltips
  • Loading branch information
AdeelK93 authored Mar 17, 2017
2 parents 50c1890 + 7e80e2f commit 311cd1e
Show file tree
Hide file tree
Showing 14 changed files with 150 additions and 64 deletions.
4 changes: 3 additions & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
Package: collapsibleTree
Type: Package
Title: Interactive Collapsible Tree Diagrams using D3.js
Version: 0.1.2
Version: 0.1.3
Author: Adeel Khan
Maintainer: Adeel Khan <[email protected]>
Description: Interactive Reingold–Tilford tree diagram created using D3.js, where every node can be expanded and collapsed by clicking on it.
License: GPL (>= 3)
URL: https://github.com/AdeelK93/collapsibleTree
BugReports: https://github.com/AdeelK93/collapsibleTree/issues
Encoding: UTF-8
Depends:
R (>= 3.0.0)
Expand All @@ -20,4 +21,5 @@ RoxygenNote: 6.0.1
Suggests:
colorspace,
RColorBrewer,
dplyr,
testthat
29 changes: 25 additions & 4 deletions R/collapsibleTree.R
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
#' along with all of its parents.
#' @param width width in pixels (optional, defaults to automatic sizing)
#' @param height height in pixels (optional, defaults to automatic sizing)
#' @param attribute numeric column not listed in hierarchy that will be used
#' for tooltips, if applicable. Defaults to 'leafCount',
#' which is the cumulative count of a node's children
#' @param fill either a single color or a vector of colors the same length
#' as the number of nodes. By default, vector should be ordered by level,
#' such that the root color is described first, then all the children's colors,
Expand All @@ -21,6 +24,7 @@
#' \code{FALSE}: Filling by order; will assign fill values to nodes horizontally.
#' @param linkLength length of the horizontal links that connect nodes in pixels
#' @param fontSize font size of the label text in pixels
#' @param tooltip tooltip shows the node's label and attribute value.
#'
#' @examples
#' collapsibleTree(warpbreaks, c("wool", "tension", "breaks"))
Expand All @@ -47,12 +51,16 @@
#' @import htmlwidgets
#' @importFrom data.tree ToListExplicit
#' @importFrom data.tree as.Node
#' @importFrom data.tree Traverse
#' @importFrom data.tree Do
#' @importFrom data.tree Aggregate
#' @importFrom stats complete.cases
#' @export
collapsibleTree <- function(df, hierarchy, root = deparse(substitute(df)),
inputId = NULL, width = NULL, height = NULL,
fill = "lightsteelblue", fillByLevel = TRUE,
linkLength = 180, fontSize = 10) {
attribute = "leafCount", fill = "lightsteelblue",
fillByLevel = TRUE, linkLength = 180,
fontSize = 10, tooltip = FALSE) {

# preserve this name before evaluating df
root <- root
Expand All @@ -63,14 +71,17 @@ collapsibleTree <- function(df, hierarchy, root = deparse(substitute(df)),
if(!is.character(fill)) stop("fill must be a character vector")
if(length(hierarchy) <= 1) stop("hierarchy vector must be greater than length 1")
if(!all(hierarchy %in% colnames(df))) stop("hierarchy column names are incorrect")
if(!(attribute %in% c(colnames(df), "leafCount"))) stop("attribute column name is incorrect")
if(sum(complete.cases(df[hierarchy])) != nrow(df)) stop("NAs in data frame")

# create a list that contains the options
options <- list(
hierarchy = hierarchy,
input = inputId,
attribute = attribute,
linkLength = linkLength,
fontSize = fontSize
fontSize = fontSize,
tooltip = tooltip
)

# the hierarchy that will be used to create the tree
Expand All @@ -93,9 +104,19 @@ collapsibleTree <- function(df, hierarchy, root = deparse(substitute(df)),
options$fill <- fill
}

# only necessary to perform these calculations if there is a tooltip
if(tooltip) {
# traverse down the tree and compute the weights of each node for the tooltip
t <- data.tree::Traverse(node, "pre-order")
data.tree::Do(t, function(x) {
x$WeightOfNode <- data.tree::Aggregate(x, attribute, sum)
})
jsonFields <- c("fill", "WeightOfNode")
} else jsonFields <- "fill"

# keep only the fill attribute in the final JSON
json <- htmlwidgets:::toJSON(
data.tree::ToListExplicit(node, unname = TRUE, keepOnly = "fill")
data.tree::ToListExplicit(node, unname = TRUE, keepOnly = jsonFields)
)

# pass the data and options using 'x'
Expand Down
23 changes: 14 additions & 9 deletions R/collapsibleTreeSummary.R
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#' children.
#' @param linkLength length of the horizontal links that connect nodes in pixels
#' @param fontSize font size of the label text in pixels
#' @param tooltip tooltip shows the node's label and attribute value.
#' @param ... other arguments passed on to \code{fillFun}, such declaring a
#' palette for \link[RColorBrewer]{brewer.pal}
#'
Expand Down Expand Up @@ -55,9 +56,9 @@
#' @export
collapsibleTreeSummary <- function(df, hierarchy, root = deparse(substitute(df)),
inputId = NULL, width = NULL, height = NULL,
attribute = "leafCount",
fillFun = colorspace::heat_hcl, maxPercent = 25,
linkLength = 180, fontSize = 10, ...) {
attribute = "leafCount", fillFun = colorspace::heat_hcl,
maxPercent = 25, linkLength = 180,
fontSize = 10, tooltip = TRUE, ...) {

# preserve this name before evaluating df
root <- root
Expand All @@ -75,8 +76,10 @@ collapsibleTreeSummary <- function(df, hierarchy, root = deparse(substitute(df))
options <- list(
hierarchy = hierarchy,
input = inputId,
attribute = attribute,
linkLength = linkLength,
fontSize = fontSize
fontSize = fontSize,
tooltip = tooltip
)

# the hierarchy that will be used to create the tree
Expand All @@ -92,14 +95,14 @@ collapsibleTreeSummary <- function(df, hierarchy, root = deparse(substitute(df))
# traverse down the tree and compute the weights of each node
t <- data.tree::Traverse(node, "pre-order")
data.tree::Do(t, function(x) {
x$Weight <- data.tree::Aggregate(x, attribute, sum)
x$WeightOfNode <- data.tree::Aggregate(x, attribute, sum)
})
data.tree::Do(t, function(x) {
x$WeightOfParent <- round(100*(x$Weight / x$parent$Weight))
x$WeightOfParent <- round(100*(x$WeightOfNode / x$parent$WeightOfNode))
})

# Sort the tree by weight
data.tree::Sort(node, "Weight", recursive = TRUE, decreasing = TRUE)
data.tree::Sort(node, "WeightOfNode", recursive = TRUE, decreasing = TRUE)

# vector of colors to choose from, up to the maxPercent
fill <- rev(fillFun(maxPercent, ...))
Expand All @@ -114,9 +117,11 @@ collapsibleTreeSummary <- function(df, hierarchy, root = deparse(substitute(df))
else self$fill <- fill[self$WeightOfParent+1]
})

# keep only the fill attribute in the final JSON
# keep only the JSON fields that are necessary
if(tooltip) jsonFields <- c("fill", "WeightOfNode")
else jsonFields <- "fill"
json <- htmlwidgets:::toJSON(
data.tree::ToListExplicit(node, unname = TRUE, keepOnly = "fill")
data.tree::ToListExplicit(node, unname = TRUE, keepOnly = jsonFields)
)

# pass the data and options using 'x'
Expand Down
6 changes: 4 additions & 2 deletions README.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ knitr::opts_chunk$set(
)
```

## Overview
## collapsibleTree `r packageVersion("collapsibleTree")`

### Overview

collapsibleTree is an R htmlwidget that allows you to create interactive collapsible Reingold–Tilford tree diagram using D3.js. Turn your data frame into a hierarchical visualization without worrying about nested lists or JSON objects!

Expand Down Expand Up @@ -66,7 +68,7 @@ collapsibleTree(

[![Collapsible Tree Colored](README-example-2.png)](https://adeelk93.github.io/collapsibleTree/)

Gradients can be mapped to a column in the data frame to help visualize relative weightings of nodes.
Gradients can be mapped to a column in the data frame to help visualize relative weightings of nodes. Node weighting can also be mapped to a tooltip.

```{r eval=FALSE}
collapsibleTreeSummary(
Expand Down
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<!-- README.md is generated from README.Rmd. Please edit that file -->
Overview
--------
collapsibleTree 0.1.3
---------------------

### Overview

collapsibleTree is an R htmlwidget that allows you to create interactive collapsible Reingold–Tilford tree diagram using D3.js. Turn your data frame into a hierarchical visualization without worrying about nested lists or JSON objects!

Expand Down Expand Up @@ -52,7 +54,7 @@ collapsibleTree(

[![Collapsible Tree Colored](README-example-2.png)](https://adeelk93.github.io/collapsibleTree/)

Gradients can be mapped to a column in the data frame to help visualize relative weightings of nodes.
Gradients can be mapped to a column in the data frame to help visualize relative weightings of nodes. Node weighting can also be mapped to a tooltip.

``` r
collapsibleTreeSummary(
Expand Down Expand Up @@ -80,7 +82,7 @@ shiny::runApp(paste0(system.file(package="collapsibleTree"),"/examples/03shiny")
``` r
library(collapsibleTree)
date()
#> [1] "Fri Mar 17 00:09:20 2017"
#> [1] "Fri Mar 17 18:07:46 2017"

testthat::test_dir("tests/testthat")
#> Basic functionality:
Expand Down
6 changes: 3 additions & 3 deletions docs/index.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -109,16 +109,16 @@ collapsibleTree(

Using `dplyr` and `colorspace` again, we can create a new column in the source data frame for the total number of countries on each continent, and map that column to the fill gradient of the nodes. `collapsibleTreeSummary` serves as a convenience function around `collapsibleTree`.

Looking at this chart, you can tell that Africa has roughly the same number of countries as Europe, and that most countries are... countries.
Looking at this chart, you can tell that Africa has roughly the same number of countries as Europe, and that most countries are... countries. Hovering over the node can confirm this fact.

```{r plotsummary, warning=FALSE}
df %>%
group_by(continent, type) %>%
summarise(numberOfCountries = n()) %>%
summarise(`Number of Countries` = n()) %>%
collapsibleTreeSummary(
hierarchy = c("continent", "type"),
root = "Geography",
width = 800,
attribute = "numberOfCountries"
attribute = "Number of Countries"
)
```
28 changes: 14 additions & 14 deletions docs/index.html

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions inst/examples/01rmd/Example01.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -96,16 +96,16 @@ collapsibleTree(

Using `dplyr` and `colorspace` again, we can create a new column in the source data frame for the total number of countries on each continent, and map that column to the fill gradient of the nodes. `collapsibleTreeSummary` serves as a convenience function around `collapsibleTree`.

Looking at this chart, you can tell that Africa has roughly the same number of countries as Europe, and that most countries are... countries.
Looking at this chart, you can tell that Africa has roughly the same number of countries as Europe, and that most countries are... countries. Hovering over the node can confirm this fact.

```{r plotsummary, warning=FALSE}
df %>%
group_by(continent, type) %>%
summarise(numberOfCountries = n()) %>%
summarise(`Number of Countries` = n()) %>%
collapsibleTreeSummary(
hierarchy = c("continent", "type"),
root = "Geography",
width = 800,
attribute = "numberOfCountries"
attribute = "Number of Countries"
)
```
28 changes: 14 additions & 14 deletions inst/examples/01rmd/Example01.html

Large diffs are not rendered by default.

45 changes: 39 additions & 6 deletions inst/htmlwidgets/collapsibleTree.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ HTMLWidgets.widget({
.attr('transform', 'translate('
+ margin.left + ',' + margin.top + ')');

// Define the div for the tooltip
var tooltip = d3.select(el).append('div')
.attr('class', 'tooltip')
.style('opacity', 0);

var i = 0,
duration = 750;

Expand Down Expand Up @@ -51,7 +56,9 @@ HTMLWidgets.widget({
.attr('transform', function(d) {
return 'translate(' + source.y0 + ',' + source.x0 + ')';
})
.on('click', click);
.on('click', click)
.on('mouseover', mouseover)
.on('mouseout', mouseout);

// Add Circle for the nodes
nodeEnter.append('circle')
Expand Down Expand Up @@ -172,10 +179,14 @@ HTMLWidgets.widget({
d._children = null;
}
update(root, d, options || null);
// Hide the tooltip after clicking
tooltip.transition()
.duration(100)
.style('opacity', 0)
// Update Shiny inputs, if applicable
if (options.input!==null) {
var nest = {},
obj = d;
obj = d; // is this necessary?
// Navigate down the list and recursively find parental nodes
for (var n = d.depth; n > 0; n--) {
nest[options.hierarchy[n-1]] = obj.data.name
Expand All @@ -184,6 +195,31 @@ HTMLWidgets.widget({
Shiny.onInputChange(options.input, nest)
}
}

// Show tooltip on mouseover
function mouseover(d) {
if (!options.tooltip) {return}
tooltip.transition()
.duration(200)
.style('opacity', .9);

tooltip.html(
d.data.name + '<br>' +
options.attribute + ': ' + d.data.WeightOfNode
)
// Make the tooltip font size just a little bit bigger
.style('font-size', (options.fontSize + 1) + 'px')
.style('left', (d3.event.layerX) + 'px')
.style('top', (d3.event.layerY - 30) + 'px');
}
// Hide tooltip on mouseout
function mouseout(d) {
if (options.tooltip) {
tooltip.transition()
.duration(500)
.style('opacity', 0);
}
}
}

return {
Expand Down Expand Up @@ -216,11 +252,8 @@ HTMLWidgets.widget({
// Update the treemap to fit the new canvas size
treemap = d3.tree().size([height, width]);
},

// Make the svg object available as a property on the widget
// instance we're returning from factory(). This is generally a
// good idea for extensibility--it helps users of this widget
// interact directly with the svg, if needed.
// instance we're returning from factory().
svg: svg
};
}
Expand Down
2 changes: 1 addition & 1 deletion inst/htmlwidgets/collapsibleTree.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ dependencies:
src: "htmlwidgets/lib/d3-4.5.0"
script: d3.min.js
- name: collapsibleTree
version: 0.1.2
version: 0.1.3
src: "htmlwidgets/lib"
stylesheet: collapsibleTree.css
12 changes: 12 additions & 0 deletions inst/htmlwidgets/lib/collapsibleTree.css
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,15 @@
stroke: #ccc;
stroke-width: 1.5px;
}

div.tooltip {
position: absolute;
text-align: center;
padding: 3px;
font: 10px sans-serif;
background: #fff;
border: 2px;
border-radius: 8px;
border-style: solid;
pointer-events: none;
}
11 changes: 9 additions & 2 deletions man/collapsibleTree.Rd

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

Loading

0 comments on commit 311cd1e

Please sign in to comment.