Venn diagrams are certainly not perfect. However, if you understand their limitations, Venn (Euler) diagrams can provide a useful way to provide context and overview. venn.js from Ben Frederickson makes creating Venn diagrams easy by doing the math to layout the sets and leveraging d3.js for rendering and interactivity.

## Install

d3vennR is not on CRAN and probably never will be unless someone asks me to submit, so please use devtools::install_github() to install.

devtools::install_github("timelyportfolio/d3vennR")

## Examples from venn.js

The examples included with venn.js give us a perfect starting point to see how to make these diagrams in R with d3vennR. I am sure you will quickly learn that possibly the hardest part of creating a Venn diagram is determining the sets and their overlaps. If you ask/demand, then I might get motivated to add a helper function to the library. For now, we’ll just use the friendly pre-computed data provided for us.

Of course, we’ll need d3vennR.

library("d3vennR")

### Simple layout from venn.js Readme.md

d3vennR(
data = list(
list( sets = list("A"), size = 12 )
, list( sets = list("B"), size = 12 )
, list( sets = c("A", "B"), size = 2)
)
)

### Changing the Style from venn.js Readme.md

styled_venn <- d3vennR(
# data from venn.js examples
#   https://github.com/benfred/venn.js/blob/master/examples/medical.jsonp
data = list(
list(sets = list(0), label = 'SE', size = 28),
list(sets = list(1), label = 'Treat', size= 35),
list(sets = list(2), label = 'Anti-CCP', size = 108),
list(sets = list(3), label = 'DAS28', size=106),
list(sets = c(0,1), size=1),
list(sets = c(0,2), size=1),
list(sets = c(0,3), size=14),
list(sets = c(1,2), size=6),
list(sets = c(1,3), size=0),
list(sets = c(2,3), size=1),
list(sets = c(0,2,3), size=1),
list(sets = c(0,1,2), size=0),
list(sets = c(0,1,3), size=0),
list(sets = c(1,2,3), size=0),
list(sets = c(0,1,2,3), size=0)
)
htmlwidgets::JS('
function(){

var colours = ["black", "red", "blue", "green"];

d3.select(this).selectAll(".venn-circle path")
.style("fill-opacity", 0)
.style("stroke-width", 10)
.style("stroke-opacity", .5)
.style("stroke", function(d,i) { return colours[i]; });
d3.select(this).selectAll(".venn-circle text")
.style("fill", function(d,i) { return colours[i]})
.style("font-size", "24px")
.style("font-weight", "100");
}
')
)
)
styled_venn

styled_venn_inverted <- styled_venn
styled_venn_inverted$x$tasks <- list(
htmlwidgets::JS('
function(){
d3.select(this).selectAll(".venn-circle path")
.style("fill-opacity",0.8);

d3.select(this).selectAll("text")
.style("fill","white");
}
')
)
styled_venn_inverted

styled_venn_mono <- styled_venn
styled_venn_mono$x$tasks <- list(
htmlwidgets::JS('
function(){
d3.select(this).selectAll(".venn-circle path")
.style("fill-opacity", 0)
.style("stroke-width", 2)
.style("stroke", "#444");

d3.select(this).selectAll("text")
.style("fill", "#444");
}
')
)
styled_venn_mono

styled_venn_dropshadow <- styled_venn
styled_venn_dropshadow$x$tasks <- list(
htmlwidgets::JS('
function(){
var colours = d3.scale.category10();
var areas = d3.select(this).selectAll("g")
areas.select("path")
.filter(function(d) { return d.sets.length == 1; })
.style("fill-opacity", .1)
.style("stroke-width", 5)
.style("stroke-opacity", .8)
.style("fill", function(d,i) { return colours(i); })
.style("stroke", function(d,i) { return colours(i); });
areas.select("text").style("fill", "#444")
.style("font-size", "22px");
var defs = d3.select(this).select("svg").append("defs");
var filter = defs.append("filter")
filter.append("feGaussianBlur")
.attr("in", "SourceAlpha")
.attr("stdDeviation", 4)
.attr("result", "blur");
filter.append("feOffset")
.attr("in", "blur")
.attr("dx", 5)
.attr("dy", 5)
.attr("result", "offsetBlur");
var feMerge = filter.append("feMerge");
feMerge.append("feMergeNode")
.attr("in", "offsetBlur")
feMerge.append("feMergeNode")
.attr("in", "SourceGraphic");
}
')
)
styled_venn_dropshadow

Tooltips would be a great place for a helper function. This helper function is currently not in the package, but we could define one like below. Of course, there is lots of room for improvement here.

venn_tooltip <- function( venn ){
venn$x$tasks[length(venn$x$tasks)+1] <- list(
htmlwidgets::JS('
function(){
var div = d3.select(this);

var tooltip = d3.select("body").append("div")
.attr("class", "venntooltip")
.style("position", "absolute")
.style("text-align", "center")
.style("width", 128)
.style("height", 16)
.style("background", "#333")
.style("color","#ddd")
.style("border","0px")
.style("opacity",0);

div.selectAll("path")
.style("stroke-opacity", 0)
.style("stroke", "#fff")
.style("stroke-width", 0)

// add listeners to all the groups to display tooltip on mousover
div.selectAll("g")
.on("mouseover", function(d, i) {

// sort all the areas relative to the current item
venn.sortAreas(div, d);

// Display a tooltip with the current size
tooltip.transition().duration(400).style("opacity", .9);
tooltip.text(d.size);

// highlight the current path
var selection = d3.select(this).transition("tooltip").duration(400);
selection.select("path")
.style("stroke-width", 3)
.style("fill-opacity", d.sets.length == 1 ? .4 : .1)
.style("stroke-opacity", 1);
})

.on("mousemove", function() {
tooltip.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})

.on("mouseout", function(d, i) {
tooltip.transition().duration(400).style("opacity", 0);
var selection = d3.select(this).transition("tooltip").duration(400);
selection.select("path")
.style("stroke-width", 0)
.style("fill-opacity", d.sets.length == 1 ? .25 : .0)
.style("stroke-opacity", 0);
});
}
')
)
venn
}
venn_tooltip(d3vennR(
# data from venn.js examples
#   https://github.com/benfred/venn.js/blob/master/examples/lastfm.jsonp
data = list(
list("sets"= list(1), "label"= "Thom Yorke", "size"= 5621),
list("sets"= list(2), "label"= "John Lennon", "size"= 7773),
list("sets"= list(3), "label"= "Kanye West", "size"= 27053),
list("sets"= list(4), "label"= "Eminem", "size"= 19056),
list("sets"= list(5), "label"= "Elvis Presley", "size"= 15839),
list("sets"= list(6), "label"= "Explosions in the Sky", "size"= 10813),
list("sets"= list(7), "label"= "Bach", "size"= 9264),
list("sets"= list(8), "label"= "Mozart", "size"= 3959),
list("sets"= list(9), "label"= "Philip Glass", "size"= 4793),
list("sets"= list(10), "label"= "St. Germain", "size"= 4136),
list("sets"= list(11), "label"= "Morrissey", "size"= 10945),
list("sets"= list(12), "label"= "Outkast", "size"= 8444),
list("sets"= list(0, 1), "size"= 4832),
list("sets"= list(0, 2), "size"= 2602),
list("sets"= list(0, 3), "size"= 6141),
list("sets"= list(0, 4), "size"= 2723),
list("sets"= list(0, 5), "size"= 3177),
list("sets"= list(0, 6), "size"= 5384),
list("sets"= list(0, 7), "size"= 2252),
list("sets"= list(0, 8), "size"= 877),
list("sets"= list(0, 9), "size"= 1663),
list("sets"= list(0, 10), "size"= 899),
list("sets"= list(0, 11), "size"= 4557),
list("sets"= list(0, 12), "size"= 2332),
list("sets"= list(1, 2), "size"= 162),
list("sets"= list(1, 3), "size"= 396),
list("sets"= list(1, 4), "size"= 133),
list("sets"= list(1, 5), "size"= 135),
list("sets"= list(1, 6), "size"= 511),
list("sets"= list(1, 7), "size"= 159),
list("sets"= list(1, 8), "size"= 47),
list("sets"= list(1, 9), "size"= 168),
list("sets"= list(1, 10), "size"= 68),
list("sets"= list(1, 11), "size"= 336),
list("sets"= list(1, 12), "size"= 172),
list("sets"= list(2, 3), "size"= 406),
list("sets"= list(2, 4), "size"= 350),
list("sets"= list(2, 5), "size"= 1335),
list("sets"= list(2, 6), "size"= 145),
list("sets"= list(2, 7), "size"= 347),
list("sets"= list(2, 8), "size"= 176),
list("sets"= list(2, 9), "size"= 119),
list("sets"= list(2, 10), "size"= 46),
list("sets"= list(2, 11), "size"= 418),
list("sets"= list(2, 12), "size"= 146),
list("sets"= list(3, 4), "size"= 5465),
list("sets"= list(3, 5), "size"= 849),
list("sets"= list(3, 6), "size"= 724),
list("sets"= list(3, 7), "size"= 273),
list("sets"= list(3, 8), "size"= 143),
list("sets"= list(3, 9), "size"= 180),
list("sets"= list(3, 10), "size"= 218),
list("sets"= list(3, 11), "size"= 599),
list("sets"= list(3, 12), "size"= 3453),
list("sets"= list(4, 5), "size"= 977),
list("sets"= list(4, 6), "size"= 232),
list("sets"= list(4, 7), "size"= 250),
list("sets"= list(4, 8), "size"= 166),
list("sets"= list(4, 9), "size"= 97),
list("sets"= list(4, 10), "size"= 106),
list("sets"= list(4, 11), "size"= 225),
list("sets"= list(4, 12), "size"= 1807),
list("sets"= list(5, 6), "size"= 196),
list("sets"= list(5, 7), "size"= 642),
list("sets"= list(5, 8), "size"= 336),
list("sets"= list(5, 9), "size"= 165),
list("sets"= list(5, 10), "size"= 143),
list("sets"= list(5, 11), "size"= 782),
list("sets"= list(5, 12), "size"= 332),
list("sets"= list(6, 7), "size"= 262),
list("sets"= list(6, 8), "size"= 85),
list("sets"= list(6, 9), "size"= 284),
list("sets"= list(6, 10), "size"= 68),
list("sets"= list(6, 11), "size"= 363),
list("sets"= list(6, 12), "size"= 218),
list("sets"= list(7, 8), "size"= 1581),
list("sets"= list(7, 9), "size"= 716),
list("sets"= list(7, 10), "size"= 133),
list("sets"= list(7, 11), "size"= 254),
list("sets"= list(7, 12), "size"= 132),
list("sets"= list(8, 9), "size"= 280),
list("sets"= list(8, 10), "size"= 53),
list("sets"= list(8, 11), "size"= 117),
list("sets"= list(8, 12), "size"= 67),
list("sets"= list(9, 10), "size"= 57),
list("sets"= list(9, 11), "size"= 184),
list("sets"= list(9, 12), "size"= 89),
list("sets"= list(10, 11), "size"= 51),
list("sets"= list(10, 12), "size"= 115),
list("sets"= list(11, 12), "size"= 162),
list("sets"= list(0, 1, 6), "size"= 480),
list("sets"= list(0, 1, 9), "size"= 152),
list("sets"= list(0, 2, 7), "size"= 112),
list("sets"= list(0, 3, 4), "size"= 715),
list("sets"= list(0, 3, 12), "size"= 822),
list("sets"= list(0, 4, 5), "size"= 160),
list("sets"= list(0, 5, 11), "size"= 292),
list("sets"= list(0, 6, 12), "size"= 122),
list("sets"= list(0, 7, 11), "size"= 118),
list("sets"= list(0, 9, 10), "size" =13),
list("sets"= list(2, 7, 8), "size"= 72)
)
))

## MDS Layout from venn.js Readme.md

See this blog post from more on when to use the MDS layout.

d3vennR(
data = list(
list(sets= list('A'), size= 9),
list(sets= list('B'), size= 15),
list(sets= list('C'), size= 8),
list(sets= list('D'), size= 6),
list(sets= list('E'), size= 9),
list(sets= list('F'), size= 9),
list(sets= list('A','B'), size= 3),
list(sets= list('A','C'), size= 0),
list(sets= list('A','D'), size= 0),
list(sets= list('A','E'), size= 0),
list(sets= list('A','F'), size= 3),
list(sets= list('B','C'), size= 3),
list(sets= list('B','D'), size= 2),
list(sets= list('B','E'), size= 0),
list(sets= list('B','F'), size= 3),
list(sets= list('C','D'), size= 2),
list(sets= list('C','E'), size= 0),
list(sets= list('C','F'), size= 0),
list(sets= list('D','E'), size= 1),
list(sets= list('D','F'), size= 0),
list(sets= list('E','F'), size= 1)
)
,layoutFunction = '
function(d) { return venn.venn(d, { initialLayout: venn.classicMDSLayout });}
'
)

## Example from VennDiagram R package

As usual with R, a lot has been done before. Let’s “recreate” some of the examples from VennDiagram.

### draw.triple.venn

library("VennDiagram")
## Loading required package: grid
grid.newpage()
grid.draw(draw.triple.venn(
area1 = 65,
area2 = 75,
area3 = 85,
n12 = 35,
n23 = 15,
n13 = 25,
n123 = 5,
category = c("First", "Second", "Third"),
fill = c("blue", "red", "green"),
lty = "blank",
cex = 2,
cat.cex = 2,
cat.col = c("blue", "red", "green")
))

venn_tooltip(
d3vennR(
data = list(
list( sets = list("First"), size = 65),
list( sets = list("Second"), size = 75),
list( sets = list("Third"), size = 85),
list( sets = list( "First", "Second"), size = 35),
list( sets = list( "Second", "Third" ), size = 15),
list( sets = list( "First", "Third" ), size = 25),
list( sets = list( "First", "Second", "Third" ), size = 5)
)
)
)

### draw.quintuple.venn

library("VennDiagram")
grid.newpage()
grid.draw(draw.quintuple.venn(
area1 = 301,
area2 = 321,
area3 = 311,
area4 = 321,
area5 = 301,
n12 = 188,
n13 = 191,
n14 = 184,
n15 = 177,
n23 = 194,
n24 = 197,
n25 = 190,
n34 = 190,
n35 = 173,
n45 = 186,
n123 = 112,
n124 = 108,
n125 = 108,
n134 = 111,
n135 = 104,
n145 = 104,
n234 = 111,
n235 = 107,
n245 = 110,
n345 = 100,
n1234 = 61,
n1235 = 60,
n1245 = 59,
n1345 = 58,
n2345 = 57,
n12345 = 31,
category = c("A", "B", "C", "D", "E"),
fill = c("dodgerblue", "goldenrod1", "darkorange1", "seagreen3", "orchid3"),
cat.col = c("dodgerblue", "goldenrod1", "darkorange1", "seagreen3", "orchid3"),
cat.cex = 2,
margin = 0.05,
cex = c(1.5, 1.5, 1.5, 1.5, 1.5, 1, 0.8, 1, 0.8, 1, 0.8, 1, 0.8, 1, 0.8,
1, 0.55, 1, 0.55, 1, 0.55, 1, 0.55, 1, 0.55, 1, 1, 1, 1, 1, 1.5),
ind = TRUE
))

venn_tooltip(d3vennR(
data = list(
list(sets = list("A"), size = 301),
list(sets = list("B"), size = 321),
list(sets = list("C"), size = 311),
list(sets = list("D"), size = 321),
list(sets = list("E"), size = 301),
list(sets = list("A","B"), size = 188),
list(sets = list("A","C"), size = 191),
list(sets = list("A","D"), size = 184),
list(sets = list("A","E"), size = 177),
list(sets = list("B","C"), size = 194),
list(sets = list("B","D"), size = 197),
list(sets = list("B","E"), size = 190),
list(sets = list("C","D"), size = 190),
list(sets = list("C","E"), size = 173),
list(sets = list("D","E"), size = 186),
list(sets = list("A","B","C"), size = 112),
list(sets = list("A","B","D"), size = 108),
list(sets = list("A","B","E"), size = 108),
list(sets = list("A","C","D"), size = 111),
list(sets = list("A","C","E"), size = 104),
list(sets = list("A","D","E"), size = 104),
list(sets = list("B","C","D"), size = 111),
list(sets = list("B","C","E"), size = 107),
list(sets = list("B","D","E"), size = 110),
list(sets = list("C","D","E"), size = 100),
list(sets = list("A","B","C","D"), size = 61),
list(sets = list("A","B","C","E"), size = 60),
list(sets = list("A","B","D","E"), size = 59),
list(sets = list("A","C","D","E"), size = 58),
list(sets = list("B","C","D","E"), size = 57),
list(sets = list("A","B","C","D","E"), size = 31)
)
# feel free to skip this complicated part to match colors
, colours = htmlwidgets::JS(sprintf('
d3.scale.category10().range(%s.map(function(col){return eval(col)}))
',
jsonlite::toJSON(lapply(
c("dodgerblue", "goldenrod1", "darkorange1", "seagreen3", "orchid3")
,function(color){
rgb <- t(col2rgb(color))
sprintf("d3.rgb(%s)",paste0(rgb,collapse=","))
}
), auto_unbox=T)
)
)
,layoutFunction = '
function(d) { return venn.venn(d, { initialLayout: venn.classicMDSLayout });}
'
))

## Thanks

Thanks so much

• Ben Frederickson for venn.js on which this is based
• d3.js from Mike Bostock
• Ramnath Vaidyanathan and RStudio for htmlwidgets
• all the contributors to R and JavaScript