var width = 960, height = 500, padding = 10, min_padding = 0, max_padding = 50, maxRadius = 120; var circles, nodes, force; d3.json('get_contributors.php', function(data) { var radius_scale = d3.scale.sqrt() .domain([0, d3.max(data, function(d){ return d.contributions; })]) .range([0, maxRadius]); nodes = data.map(function(d, i){ var c = { id: d.name, radius: radius_scale(d.contributions), cx: Math.cos(i*2*Math.PI/data.length)*radius_scale(d.contributions), cy: Math.sin(i*2*Math.PI/data.length)*radius_scale(d.contributions), avatar_id: d.user_github_id }; c.x = c.cx; c.y = c.cy; return c; }); var svg = d3.select("body").append("svg") .attr("width", width) .attr("height", height); svg.append('defs'); create_avatar_patterns(nodes); var vis = svg.append('g') .attr('transform', 'translate('+width/2+','+height/2+')'); circles = vis.selectAll("circle") .data(nodes); var enter_circle = circles.enter().append("circle") .attr('class', 'node'); enter_circle .attr("r", function(d) { return d.radius; }) .attr('transform', function(d) { return 'translate('+d.x+','+d.y+')'; }) .attr('fill', function(d) { return 'url(#user_pattern_'+d.avatar_id+')'; }); force = d3.layout.force() .nodes(nodes) .size([width, height]) .gravity(.02) .charge(0) .start(); for(var i=0; i<1000; i++) { tick(); } force.stop(); circles .attr('transform', function(d) { return 'translate('+d.x+','+d.y+')'; }); }); function tick() { circles .each(gravity(.2 * force.alpha())) .each(collide(.5)); } // Resolve collisions between nodes. function collide(alpha) { var quadtree = d3.geom.quadtree(nodes); return function(d) { var r = d.radius + maxRadius + padding, nx1 = d.x - r, nx2 = d.x + r, ny1 = d.y - r, ny2 = d.y + r; quadtree.visit(function(quad, x1, y1, x2, y2) { if (quad.point && (quad.point !== d)) { var x = d.x - quad.point.x, y = d.y - quad.point.y, l = Math.sqrt(x * x + y * y), r = d.radius + quad.point.radius + padding; if (l < r) { l = (l - r) / l * alpha; d.x -= x *= l; d.y -= y *= l; quad.point.x += x; quad.point.y += y; } } return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1; }); }; } // Move nodes toward cluster focus. function gravity(alpha) { return function(d) { d.y += (d.cy - d.y) * alpha; d.x += (d.cx - d.x) * alpha; }; } function create_avatar_patterns(nodes) { var user_patterns = d3.select('defs').selectAll('.user_patterns') .data(nodes); user_patterns.enter() .append('pattern') .attr('class', 'user_patterns') .attr('id', function(d){ return 'user_pattern_' + d.avatar_id; }) .attr('patternUnits', 'userSpaceOnUse') .attr('x', function(d){ return -d.radius; }) .attr('y', function(d){ return -d.radius; }) .attr('width', function(d){ return 2*d.radius; }) .attr('height', function(d){ return 2*d.radius; }) .append('image') .attr('xlink:href', function(d){ return 'http://avatars3.githubusercontent.com/u/' + d.avatar_id; }) .attr('x', 0) .attr('y', 0) .attr('width', function(d){ return 2*d.radius; }) .attr('height', function(d){ return 2*d.radius; }) .attr('preserveAspectRatio', 'xMidYMid slice'); }