var w = 960 , h = 500 , svg = d3.select("#chart").append("svg:svg") .attr("width", w) .attr("height", h) , defs = svg.append('svg:defs') , pref = 'file:' === location.protocol ? '' : 'http://bl.ocks.org/d/1306472/' , load = new Image // preload the images , surl = load.src = pref + 'sprites.png' , char_radius = 33 , char_img_width = 80 , sprite_w = 1025 , sprite_h = 208 , nodes, force , data, items; svg.append("svg:rect") .attr("width", w) .attr("height", h); d3.json(pref + 'recettear-items.json', function(json) { items = json; }); d3.json(pref + 'recettear-data.json', function(json) { data = json; var offs = data.sprites , n = data.characters.length + 1 ; svg.selectAll('circle.character') .data(data.characters) .enter() .append('svg:circle') .attr('cx', function(d, i) { return Math.round((i + 1) * (w / n)); }) .attr('cy', function(d, i) { return Math.round((i & 1) * (h / 3) + (h / 3)); }) .attr('r', char_img_width >> 1) .attr('fill', function(d) { return 'url(#'+ d.id +')'; }) .attr('xlink:href', function(d) { return '#'+ d.name; }) .attr('class', 'character') .each(function(d) { defSprite(surl, sprite_w, sprite_h, offs[d.id], d.id); }); start(); }); function defSprite(url, w, h, o, id) { defs.append('svg:pattern') .attr('id', id) .attr('width', o.w) .attr('height', o.h) .append('svg:svg') .attr('width', o.w) .attr('height', o.h) .attr('viewBox', [o.x, o.y, o.w, o.h].join(' ')) .append('svg:image') .attr('width', w) .attr('height', h) .attr('xlink:href', url); } function color(i) { return data.characters[i].color; } function start() { nodes = d3.range(8).map(function(i) { return { type: Math.random() * 8 | 0 , radius: char_radius , fixed: true , type: i , x: (i + 1) * (w / 9) , y: (i & 1) * (h / 3) + (h / 3) }; }); // color = d3.scale.category10(); force = d3.layout.force() .gravity(0) .charge(0) .nodes(nodes) .size([w, h]); force.start(); force.on("tick", function(e) { var q = d3.geom.quadtree(nodes), k = e.alpha * .1, i = 0, n = nodes.length, o, c; while (++i < n) { o = nodes[i]; if (o.fixed) continue; c = nodes[o.type]; o.x += (c.x - o.x) * k; o.y += (c.y - o.y) * k; q.visit(collide(o)); } svg.selectAll("circle.fly") .attr("cx", function(d) { return d.x; }) .attr("cy", function(d) { return d.y; }); }); var p0; svg.on("mousemove", function() { var p1 = d3.svg.mouse(this) , node = { radius: Math.random() * 12 + 4 , type: Math.random() * 8 | 0 , x: p1[0] , y: p1[1] , px: (p0 || (p0 = p1))[0] , py: p0[1] }; p0 = p1; svg.append("svg:circle") .data([node]) .attr('class', 'fly') .attr("cx", function(d) { return d.x; }) .attr("cy", function(d) { return d.y; }) .attr("r", function(d) { return d.radius - 2; }) .style("fill", function(d) {return color(d.type);}) .transition() .delay(3000) .attr("r", 1e-6) .each("end", function() { nodes.splice(8, 1); }) .remove(); nodes.push(node); force.resume(); }); } function collide(node) { var r = node.radius + 16, nx1 = node.x - r, nx2 = node.x + r, ny1 = node.y - r, ny2 = node.y + r; return function(quad, x1, y1, x2, y2) { if (quad.point && (quad.point !== node)) { var x = node.x - quad.point.x, y = node.y - quad.point.y, l = Math.sqrt(x * x + y * y), r = node.radius + quad.point.radius; if (l < r) { l = (l - r) / l * .5; node.px += x * l; node.py += y * l; } } return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1; }; }