var width = 960, height = 500, padding = 0.5, maxRadius = 10, n = 200, map = {}; /* Projection */ var CELL_RADIUS = 0.1, SIMPLIFICATION = 15; dx = CELL_RADIUS * 2 * Math.sin(Math.PI / 3); dy = CELL_RADIUS * 1.5; path_generator = d3.geo.path().projection(d3.geo.transform({ point: function(x, y, z) { if (z >= SIMPLIFICATION) { return this.stream.point(x * dx / 2, -(y - (2 - (y & 1)) / 3) * dy / 2); } } })); /* Map drawing */ queue() .defer(d3.json, 'ontology.json') .defer(d3.json, 'map.topo.json') .await(function(error, ontology_data, data) { if(error) throw error; ontology.init(ontology_data); _preprocess(data); nodes = topojson.feature(data, data.objects.leaf_regions).features; nodes.forEach(function(d) { d.cx = d.properties.node.x; d.cy = d.properties.node.y; d.x = d.cx; d.y = d.cy; d.radius = Math.sqrt(d.properties.node.area); }); var svg = d3.select("body").append("svg") .attr("width", width) .attr("height", height) .attr("viewBox", "-300 -20 600 150"); var zoom_layer = svg.append('g'); svg.call(d3.behavior.zoom().scaleExtent([0.6, 600]).on('zoom', function() { zoom_layer.attr({ transform: "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")" }); })); var map_layer = zoom_layer.append('g'); /* Regions */ var node_drag = d3.behavior.drag() .on("dragstart", function() {force.stop();}) .on("drag", function(d) { d.px += d3.event.dx; d.py += d3.event.dy; d.x += d3.event.dx; d.y += d3.event.dy; tick({alpha: 0}); // this is the key to make it work together with updating both px,py,x,y on d ! }) .on("dragend", function() {}); var region = map_layer.selectAll('.leaf_region') .data(nodes); var enter_region = region.enter().append('path') .attr("class", 'leaf_region') .attr("d", path_generator) .call(node_drag); /* Circles */ var circle = map_layer.selectAll("circle") .data(nodes); var enter_circle = circle.enter().append("circle") .attr('class', 'node'); enter_circle .attr("r", function(d) { return d.radius; }) .attr("cx", function(d) { return d.cx; }) .attr("cy", function(d) { return d.cy; }); var force = d3.layout.force() .nodes(nodes) .size([width, height]) .gravity(.02) .charge(0) .on('tick', tick) .on('end', function() { console.log('File loaded correctly.'); }) .start(); force.alpha(.05); function tick(e) { region .each(gravity(.2 * e.alpha)) .each(collide(.5)) .attr("transform", function(d) {return "translate(" + (d.x - d.cx) + ", " + (d.y - d.cy) + ")";}); circle .attr("cx", function(d) { return d.x; }) .attr("cy", function(d) { return d.y; }); } // 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 print_nodes() { return nodes.map(function(d) { return {"id": d.properties.class, "x": d.x, "y": d.y, "cx": d.cx, "cy": d.cy} }); } }); _preprocess = function(data) { var geometries, _merge; map.leaf_regions = topojson.feature(data, data.objects.leaf_regions).features; geometries = data.objects.leaf_regions.geometries; /* parse paths into arrays, and extract the class of each leaf region */ map.leaf_regions.forEach(function(f) { f.properties.path = JSON.parse(f.properties.path); return f.properties["class"] = f.properties.path[f.properties.path.length - 1]; }); /* presimplify the topologies (compute the effective area (z) of each point) */ topojson.presimplify(data); /* store all leaf_regions into the ontology tree, and store each node within the feature's properties */ map.leaf_regions.forEach(function(f) { var n; n = ontology.get_node_from_class(f.properties["class"]); n.leaf_region = f; return f.properties.node = n; }); /* compute merged regions from leaf regions */ _merge = function(n, depth) { n.merged_region = topojson.merge(data, geometries.filter(function(g) { return g.properties.path.length > depth && g.properties.path[depth] === n.name; })); if (n.children != null) { return n.children.forEach(function(c) { return _merge(c, depth + 1); }); } }; _merge(ontology.tree, 0); /* compute all region centroids */ ontology.nodes.forEach(function(n) { var _ref; return _ref = path_generator.centroid(n.merged_region), n.x = _ref[0], n.y = _ref[1], _ref; }); /* compute all region areas */ ontology.nodes.forEach(function(n) { return n.area = path_generator.area(n.merged_region); }); /* define readable, plural, multiline labels for level one regions */ _readable_labels = {}; ontology.nodes.forEach(function(d) { _readable_labels[d.name] = d.name.split("/").slice(-1)[0].split("_"); }); ontology.levels[1].forEach(function(n) { return n.readable_label = _readable_labels[n.name]; }); return ontology.leaves.forEach(function(n) { if (n.depth > 1 && (n.leaf_region != null)) { return n.readable_label = _readable_labels[n.name]; } }); };