Last active
March 1, 2017 06:14
-
-
Save efekarakus/96adb9f2d3a9653b0c0a393c106a43c9 to your computer and use it in GitHub Desktop.
Venn Diagram with d3js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<meta charset="utf-8"> | |
<style> | |
g.A circle { | |
fill: brown; | |
fill-opacity: .5; | |
stroke: black; | |
} | |
g.B circle { | |
fill: steelblue; | |
fill-opacity: .5; | |
stroke: black; | |
} | |
</style> | |
<body> | |
<script src="https://d3js.org/d3.v4.min.js"></script> | |
<script src="./venn-diagram.js"></script> | |
<script> | |
var vennDiagram = VennDiagram(5, 15, 4) | |
.width(500) | |
.height(250) | |
.margin({top: 5, right: 10, bottom: 5, left: 10}) | |
.threshold(0.00001); | |
d3.select("body") | |
.call(vennDiagram); | |
</script> | |
</body> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function VennDiagram(A, B, AnB) { | |
var width = 720, | |
height = 80, | |
margin = {top: 0, right: 0, bottom: 0, left: 0}, | |
threshold = 0.00001; | |
function chart(selection) { | |
selection.each(function() { | |
var svg = d3.select(this).append("svg") | |
.attr("width", width + margin.left + margin.right) | |
.attr("height", height + margin.top + margin.bottom); | |
var graphic = svg.append("g") | |
.attr("transform", "translate(" + margin.left + "," + margin.top + ")"); | |
var diagram = graphic.append("g"); | |
var distance = getDistanceBetweenCircles(); | |
var circleA = getCircle(A) | |
.attr("class", "A"); | |
var circleB = getCircle(B) | |
.attr("class", "B") | |
.attr("transform", "translate(" + distance + ", 0)"); | |
var xOffset = radius(A) + (width - (radius(A) + distance + radius(B)))/2, | |
yOffset = maxRadius(); | |
diagram.attr("transform", "translate(" + xOffset + ", " + yOffset + ")"); | |
function getCircle(cardinality) { | |
var g = diagram.append("g"); | |
var r = radius(cardinality); | |
g.append("circle") | |
.attr("x", r) | |
.attr("y", r) | |
.attr("r", r); | |
return g; | |
} | |
}); | |
} | |
chart.width = function(_) { | |
if (!arguments.length) return width; | |
width = _; | |
return chart; | |
} | |
chart.height = function(_) { | |
if (!arguments.length) return height; | |
height = _; | |
return chart; | |
} | |
chart.margin = function(_) { | |
if (!arguments.length) return margin; | |
margin = _; | |
return chart; | |
} | |
chart.threshold = function(_) { | |
if (!arguments.length) return threshold; | |
threshold = _; | |
return chart; | |
} | |
function maxRadius() { | |
return height < width ? height / 2 : width / 4; | |
} | |
function maxCardinality() { | |
return Math.max(A, B); | |
} | |
function radius(cardinality) { | |
if (cardinality === maxCardinality()) return maxRadius(); | |
var maxArea = Math.PI * maxRadius() * maxRadius(); | |
return Math.sqrt( (cardinality * maxArea) / (maxCardinality() * Math.PI) ); | |
} | |
function getDistanceBetweenCircles() { | |
if (AnB === 0) { | |
return radius(A) + radius(B); | |
} | |
var r = Math.min(radius(A), radius(B)); | |
var R = maxRadius(); | |
var intersectionArea = AnB * (Math.PI * R * R) / maxCardinality(); | |
var minDistance = 0, | |
maxDistance = (R + r); | |
while (minDistance <= maxDistance) { | |
var distance = minDistance + (maxDistance - minDistance)/2; | |
var lensArea = getLensArea(r, R, distance) | |
if (hasConverged(intersectionArea, lensArea)) { | |
return distance; | |
} | |
if (lensArea < intersectionArea) { | |
maxDistance = distance; | |
} else { | |
minDistance = distance; | |
} | |
} | |
} | |
function hasConverged(intersectionArea, lensArea) { | |
return Math.abs(intersectionArea - lensArea) < (intersectionArea * threshold); | |
} | |
function getLensArea(r, R, d) { | |
var smallLensArea = r * r * Math.acos( ((d*d) + (r*r) - (R*R))/(2*d*r) ); | |
var bigLensArea = R * R * Math.acos( ((d*d) + (R*R) - (r*r))/(2*d*R) ); | |
var overlap = 0.5 * Math.sqrt( (-d + r + R) * (d + r - R) * (d - r + R) * (d + r + R) ); | |
return smallLensArea + bigLensArea - overlap; | |
} | |
return chart; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment