(function() { var angle, apothem, arc_generator, bins, bottom_left, bottom_right, classes, color, distributions, dots, hexbin, max, max_tot, outer_polygon_generator, pie_layout, pies, points, polygon_generator, radius, radius_scale, side, subplots, svg, upper_left, upper_middle, upper_right; side = 320; distributions = [ d3.range(1500).map(function() { return { x: d3.random.normal(side / 2, 80)(), y: d3.random.normal(side / 2, 80)() }; }), d3.range(1500).map(function() { return { x: d3.random.normal(side / 2, 80)() - 50, y: 100 + d3.random.normal(side / 2, 80)() }; }), d3.range(1000).map(function() { return { x: d3.random.normal(3.5 * side / 4, 80)(), y: d3.random.normal(side / 2, 80)() }; }) ]; classes = distributions.length; points = _.chain(distributions).map(function(distribution, klass) { distribution.forEach(function(point) { return point["class"] = klass; }); return distribution; }).flatten(true).value(); radius = 26; apothem = Math.sqrt(3) / 2 * radius; hexbin = d3.hexbin().size([side, side]).radius(radius).x(function(d) { return d.x; }).y(function(d) { return d.y; }); bins = _.chain(hexbin(points)).forEach(function(bin) { return bin.classes_count = _.chain(_.range(classes)).map(function(klass) { return bin.filter(function(point) { return point["class"] === klass; }).length; }).value(); }).value(); max_tot = d3.max(bins, function(bin) { return bin.length; }); max = d3.max(bins, function(bin) { return d3.max(bin.classes_count); }); angle = 2 * Math.PI / classes; svg = d3.select('svg'); upper_left = svg.append('g').attr('id', 'pies').attr('clip-path', 'url(#square_window)'); upper_middle = svg.append('g').attr('id', 'dots').attr('clip-path', 'url(#square_window)').attr('transform', "translate(" + side + ",0)"); upper_right = svg.append('g').attr('id', 'radar').attr('clip-path', 'url(#square_window)').attr('transform', "translate(" + (2 * side) + ",0)"); bottom_left = svg.append('g').attr('id', 'polar_length').attr('clip-path', 'url(#square_window)').attr('transform', "translate(" + (side / 2) + "," + side + ")"); bottom_right = svg.append('g').attr('id', 'polar_area').attr('clip-path', 'url(#square_window)').attr('transform', "translate(" + (3 * side / 2) + "," + side + ")"); svg.append('line').attr({ "class": 'separator', x1: 0, x2: 3 * side, y1: side, y2: side }); svg.append('line').attr({ "class": 'separator', x1: side, x2: side, y1: 0, y2: side }); svg.append('line').attr({ "class": 'separator', x1: 2 * side, x2: 2 * side, y1: 0, y2: side }); svg.append('line').attr({ "class": 'separator', x1: side / 2, x2: side / 2, y1: side, y2: 2 * side }); svg.append('line').attr({ "class": 'separator', x1: 3 * side / 2, x2: 3 * side / 2, y1: side, y2: 2 * side }); svg.append('line').attr({ "class": 'separator', x1: 5 * side / 2, x2: 5 * side / 2, y1: side, y2: 2 * side }); color = d3.scale.category10(); dots = upper_middle.selectAll('.dot').data(points); dots.enter().append('circle').attr({ "class": 'dot', r: 1, cx: function(p) { return p.x; }, cy: function(p) { return p.y; }, fill: function(d) { return color(d["class"]); } }); subplots = upper_left.selectAll('.subplot').data(bins); subplots.enter().append('g').attr({ "class": 'subplot', transform: function(bin) { return "translate(" + bin.x + "," + bin.y + ")"; } }); pie_layout = d3.layout.pie().sort(null); radius_scale = d3.scale.sqrt().domain([0, max_tot]).range([0, apothem]); arc_generator = d3.svg.arc().outerRadius(function(count) { return radius_scale(d3.select(this.parentNode).datum().length); }).innerRadius(0); pies = subplots.selectAll('.pie').data(function(bin) { return pie_layout(bin.classes_count); }); pies.enter().append('path').attr({ "class": 'pie', d: arc_generator, fill: function(count, klass) { return color(klass); } }); subplots = upper_right.selectAll('.subplot').data(bins); subplots.enter().append('g').attr({ "class": 'subplot', transform: function(bin) { return "translate(" + bin.x + "," + bin.y + ")"; } }); radius_scale = d3.scale.linear().domain([0, max]).range([0, apothem]); outer_polygon_generator = d3.svg.line().x(function(count, klass) { return apothem * Math.cos(klass * angle - Math.PI / 2); }).y(function(count, klass) { return apothem * Math.sin(klass * angle - Math.PI / 2); }); polygon_generator = d3.svg.line().x(function(count, klass) { return radius_scale(count) * Math.cos(klass * angle - Math.PI / 2); }).y(function(count, klass) { return radius_scale(count) * Math.sin(klass * angle - Math.PI / 2); }); subplots.append('path').attr({ "class": 'outer_polygon', d: function(bin) { return outer_polygon_generator(bin.classes_count) + 'z'; } }); subplots.append('path').attr({ "class": 'polygon', d: function(bin) { return polygon_generator(bin.classes_count) + 'z'; } }); subplots = bottom_left.selectAll('.subplot').data(bins); subplots.enter().append('g').attr({ "class": 'subplot', transform: function(bin) { return "translate(" + bin.x + "," + bin.y + ")"; } }); radius_scale = d3.scale.linear().domain([0, max]).range([0, apothem]); arc_generator = d3.svg.arc().innerRadius(0).outerRadius(function(count) { return radius_scale(count); }).startAngle(function(count, klass) { return klass * angle - angle / 2; }).endAngle(function(count, klass) { return klass * angle + angle / 2; }); pies = subplots.selectAll('.pie').data(function(bin) { return bin.classes_count; }); pies.enter().append('path').attr({ "class": 'pie', d: arc_generator, fill: function(count, klass) { return color(klass); } }); subplots = bottom_right.selectAll('.subplot').data(bins); subplots.enter().append('g').attr({ "class": 'subplot', transform: function(bin) { return "translate(" + bin.x + "," + bin.y + ")"; } }); radius_scale = d3.scale.sqrt().domain([0, max]).range([0, apothem]); arc_generator = d3.svg.arc().innerRadius(0).outerRadius(function(count) { return radius_scale(count); }).startAngle(function(count, klass) { return klass * angle - angle / 2; }).endAngle(function(count, klass) { return klass * angle + angle / 2; }); pies = subplots.selectAll('.pie').data(function(bin) { return bin.classes_count; }); pies.enter().append('path').attr({ "class": 'pie', d: arc_generator, fill: function(count, klass) { return color(klass); } }); }).call(this);