Skip to content

Instantly share code, notes, and snippets.

@christophe-g
Last active December 1, 2015 17:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save christophe-g/1c43f335c5779377d379 to your computer and use it in GitHub Desktop.
Save christophe-g/1c43f335c5779377d379 to your computer and use it in GitHub Desktop.
venn layout example for d3.js
(function test() {
var width = 600,
height = 600,
colors = d3.scale.category10();
var setChar = 'ABCDEFGHIJKLMN',
charFn = i => setChar[i],
setLength = 4,
sets = d3.range(setLength).map(function(d, i) {
return setChar[i]
})
var opts = {
dataLength: 180,
setLength: 4,
duration: 800,
circleOpacity: 0.4,
innerOpacity: 0.2
};
// Build simple getter and setter Functions
for (var key in opts) {
test[key] = getSet(key, test).bind(opts);
}
function getSet(option, component) {
return function(_) {
if (!arguments.length) {
return this[option];
}
this[option] = _;
return component;
};
}
function refreshInput() {
var sel = d3.select(this),
name = sel.attr("name"),
value = sel.property("value")
test[name](value);
if (name == 'dataLength' || name == 'setLength') {
return refresh(generateData())
}
refresh();
// console.info('refresh', name, value)
}
//set input value accorging to options and handle change of input
d3.selectAll('#inputs input')
.each(function() {
var sel = d3.select(this),
name = sel.attr("name");
sel.property("value", test[name]())
})
.on('input', refreshInput)
var layout = d3.layout.venn().size([width, height]).value(x => 1),
svg = d3.select('svg')
.attr('width', width)
.attr('height', height),
isFirstLayout = true;
function generateData() {
var dataLength = test.dataLength(),
setLength = test.setLength(),
ii = 0;
return d3.range(dataLength).map((d, i) => {
var l = Math.floor((Math.random() * setLength / 3) + 1),
set = [],
c,
i;
for (i = -1; ++i < l;) {
c = charFn(Math.floor((Math.random() * setLength)));
if (set.indexOf(c) == -1) {
set.push(c)
}
}
return {
set: set,
name: 'set_' + ii++
}
});
}
function refresh(data) {
if (data) {
// we recalculate the layout for new data only
layout.nodes(data)
}
var vennArea = svg.selectAll("g.venn-area")
.data(layout.sets(), function(d) {
return d.__key__;
});
var vennEnter = vennArea.enter()
.append('g')
.attr("class", function(d) {
return "venn-area venn-" +
(d.sets.length == 1 ? "circle" : "intersection");
})
.attr('fill', function(d, i) {
return colors(i)
})
vennEnter.append('path')
.attr('class', 'venn-area-path');
vennEnter.append('circle')
.attr('class', 'inner')
.attr('fill', 'grey');
vennEnter.append("text")
.attr("class", "label")
.attr("text-anchor", "middle")
.attr("dy", ".35em")
vennArea.selectAll('path.venn-area-path').transition()
.duration(isFirstLayout ? 0 : test.duration())
.attr('opacity', test.circleOpacity())
.attrTween('d', function(d) {
return d.d
});
//we need to rebind data so that parent data propagetes to child nodes (otherwise, updating parent has no effect on child.__data__ property)
vennArea.selectAll("text.label").data(function(d) {
return [d];
})
.text(function(d) {
return d.__key__;
})
.attr("x", function(d) {
return d.center.x
})
.attr("y", function(d) {
return d.center.y
});
//we need to rebind data so that parent data propagetes to child nodes (otherwise, updating parent has no effect on child.__data__ property)
vennArea.selectAll('circle.inner').data(function(d) {
return [d];
}).transition()
.duration(isFirstLayout ? 0 : test.duration())
.attr('opacity', test.innerOpacity())
.attr("cx", function(d) {
return d.center.x
})
.attr("cy", function(d) {
return d.center.y
})
.attr('r', function(d) {
return d.innerRadius
});
vennArea.exit().transition()
.duration(test.duration())
.attrTween('d', function(d) {
return d.d
})
.remove()
var points = vennArea.selectAll("circle.node")
.data(function(d) {
return d.nodes
}, function(d) {
return d.name
})
var pointsEnter = points.enter()
.append('circle')
.attr('r', 0)
.attr('class', 'node')
points.transition()
.duration(isFirstLayout ? 0 : test.duration())
.attr("cx", function(d) {
return d.x
})
.attr("cy", function(d) {
return d.y
})
.attr('r', 3)
points.exit().transition()
.attr('r', 0)
.remove()
isFirstLayout = false;
return test
}
return refresh(generateData())
})();
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.box {
font: 10px sans-serif;
}
.box line,
.box rect,
.box circle {
fill: #fff;
stroke: #000;
stroke-width: 1.5px;
}
.box .center {
stroke-dasharray: 3,3;
}
.box .outlier {
fill: none;
stroke: #ccc;
}
</style>
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script src="https://raw.githubusercontent.com/christophe-g/vennLayout/master/vennLayout.js"></script>
<form>
<div id="inputs">
<p>
<label for="dataLength">Number of Nodes</label>
<input type="number" min="10" step="10" max="600" name="dataLength" id="dataLength" value="" />
</p>
<p>
<label for="setLength">Number of Circles</label>
<input min="2" max="8" type="number" name="setLength" id="setLength" value="" />
</p>
<p>
<label for="circleOpacity">opacity for Circle</label>
<input min="0.1" max="1" step ="0.1" type="range" name="circleOpacity" id="circleOpacity" value="" />
</p>
<p>
<label for="innerOpacity">opacity for inner Circle</label>
<input min="0" max="1" step ="0.1" type="range" name="innerOpacity" id="innerOpacity" value="" />
</p>
</div>
</form>
<svg id="venn"></svg>
<script type="text/javascript" src="interactive.js"></script>
</body>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment