function chordMpr (data) {
var mpr = {}, mmap = {}, n = 0,
matrix = [], filter, accessor;
mpr.setFilter = function (fun) {
mpr.setAccessor = function (fun) {
mpr.getMatrix = function () {
_.each(mmap, function (a) {
if (!matrix[a.id]) matrix[a.id] = [];
_.each(mmap, function (b) {
var recs = _.filter(data, function (row) {
return filter(row, a, b);
matrix[a.id][b.id] = accessor(recs, a, b);
mpr.getMap = function () {
mpr.printMatrix = function () {
_.each(matrix, function (elem) {
mpr.addToMap = function (value, info) {
mmap[value] = { name: value, id: n++, data: info }
mpr.addValuesToMap = function (varName, info) {
var values = _.uniq(_.pluck(data, varName));
_.map(values, function (v) {
mmap[v] = { name: v, id: n++, data: info }
function chordRdr (matrix, mmap) {
i = d.source.index; j = d.target.index;
s = _.where(mmap, {id: i });
t = _.where(mmap, {id: j });
m.sdata = d.source.value;
m.svalue = +d.source.value;
m.stotal = _.reduce(matrix[i], function (k, n) { return k + n }, 0);
m.tdata = d.target.value;
m.tvalue = +d.target.value;
m.ttotal = _.reduce(matrix[j], function (k, n) { return k + n }, 0);
g = _.where(mmap, {id: d.index });
m.mtotal = _.reduce(matrix, function (m1, n1) {
return m1 + _.reduce(n1, function (m2, n2) { return m2 + n2}, 0);
var data = [{ "has": "black", "prefers": "red", "count": "2868" }, { "has": "blonde", "prefers": "brown", "count": "2060" }, { "has": "brown", "prefers": "blonde", "count": "16145" }, { "has": "red", "prefers": "black", "count": "1013" }, { "has": "black", "prefers": "brown", "count": "8916" }, { "has": "blonde", "prefers": "blonde", "count": "10048" }, { "has": "brown", "prefers": "black", "count": "8010" }, { "has": "red", "prefers": "red", "count": "6907" }, { "has": "black", "prefers": "blonde", "count": "5871" }, { "has": "blonde", "prefers": "black", "count": "1951" }, { "has": "brown", "prefers": "red", "count": "8045" }, { "has": "red", "prefers": "brown", "count": "940" }, { "has": "black", "prefers": "black", "count": "11975" }, { "has": "blonde", "prefers": "red", "count": "6171" }, { "has": "brown", "prefers": "brown", "count": "8090" }, { "has": "red", "prefers": "blonde", "count": "990" }];
var mpr = chordMpr(data);
.setFilter(function (row, a, b) {
return (row.has === a.name && row.prefers === b.name)
.setAccessor(function (recs, a, b) {
drawChords(mpr.getMatrix(), mpr.getMap());
function drawChords(matrix, mmap) {
var w = 980, h = 800, r1 = h / 2, r0 = r1 - 100;
var fill = d3.scale.ordinal()
.range(["#000000", "#FFDD89", "#957244", "#F26223"]);
var chord = d3.layout.chord()
.sortSubgroups(d3.descending)
.sortChords(d3.descending);
var svg = d3.select("body").append("svg:svg")
.attr("transform", "translate(" + w / 2 + "," + h / 2 + ")");
var rdr = chordRdr(matrix, mmap);
var g = svg.selectAll("g.group")
.on("mouseover", mouseover)
.on("mouseout", function (d) { d3.select("#tooltip").style("visibility", "hidden") });
.style("stroke", "black")
.style("fill", function (d) { return fill(d.index); })
.each(function (d) { d.angle = (d.startAngle + d.endAngle) / 2; })
.style("font-family", "helvetica, arial, sans-serif")
.style("font-size", "10px")
.attr("text-anchor", function (d) { return d.angle > Math.PI ? "end" : null; })
.attr("transform", function (d) {
return "rotate(" + (d.angle * 180 / Math.PI - 90) + ")"
+ "translate(" + (r0 + 26) + ")"
+ (d.angle > Math.PI ? "rotate(180)" : "");
.text(function (d) { return rdr(d).gname; });
var chordPaths = svg.selectAll("path.chord")
.enter().append("svg:path")
.style("stroke", function (d) { return d3.rgb(fill(d.target.index)).darker(); })
.style("fill", function (d) { return fill(d.target.index); })
.attr("d", d3.svg.chord().radius(r0))
.on("mouseover", function (d) {
.style("visibility", "visible")
.style("top", function () { return (d3.event.pageY - 100) + "px" })
.style("left", function () { return (d3.event.pageX - 100) + "px"; })
.on("mouseout", function (d) { d3.select("#tooltip").style("visibility", "hidden") });
var p = d3.format(".2%"), q = d3.format(",.3r")
return "Chord Info:<br/>"
+ p(d.svalue / d.stotal) + " (" + q(d.svalue) + ") of "
+ d.sname + " prefer " + d.tname
+ (d.sname === d.tname ? "" : ("<br/>while...<br/>"
+ p(d.tvalue / d.ttotal) + " (" + q(d.tvalue) + ") of "
+ d.tname + " prefer " + d.sname))
var p = d3.format(".1%"), q = d3.format(",.3r")
return "Group Info:<br/>"
+ d.gname + " : " + q(d.gvalue) + "<br/>"
+ p(d.gvalue / d.mtotal) + " of Matrix Total (" + q(d.mtotal) + ")"
function mouseover(d, i) {
.style("visibility", "visible")
.style("top", function () { return (d3.event.pageY - 80) + "px" })
.style("left", function () { return (d3.event.pageX - 130) + "px"; })
chordPaths.classed("fade", function (p) {
return p.source.index != i