// Generated by CoffeeScript 1.10.0 (function() { d3.csv('words.csv', function(error, words) { var color_i, data, data_tiles, features, final_data, height, j, labels_layer, levels, lod, merging, order, path_generator, precision_multiplier, redraw_regions, redraw_tiles, ref, regions_layer, results, scale, squares, start, svg, tile_size, tiles_layer, topologies, union, update_structures, vis, width, zoom, zoomable_layer; levels = 3; data = d3.nest().key(function(d) { return d.word.slice(0, levels).toLowerCase(); }).entries(words); order = Math.ceil(Math.log(d3.sum(data, function(d) { return d.values.length; })) / Math.log(4)); start = 0; scale = 480; precision_multiplier = 10000; tile_size = scale * precision_multiplier / Math.pow(2, order); path_generator = d3.geo.path().projection(null); features = [[], [], []]; final_data = [[], [], []]; topologies = [[], [], []]; data_tiles = []; /* START, END, Z offsets and QUAD strings calculation */ console.debug('START, END, Z offsets and QUAD strings calculation'); data.forEach(function(cls) { cls.start = start; cls.end = start + cls.values.length; start += cls.values.length; cls.z = get_z(cls); return cls.quads = get_quads(cls, order); }); /* QUADS size and coordinates calculation */ console.debug('QUADS size and coordinates calculation'); data.forEach(function(d) { var quads; quads = d.quads.map(quad_layout(hquad, scale * precision_multiplier)); quads.forEach(function(i) { return i.name = d.key; }); return d.quads = quads; }); /* REGIONS aggregation with Turf.js */ console.debug('REGIONS aggregation with Turf.js'); update_structures = function(level) { final_data[level] = { feature_collection: { collection: { type: "FeatureCollection", features: features[level] } }, spatial: features[level].map(function(f) { return { centroid: path_generator.centroid(f), bbox: turf.envelope(f), "class": f.properties["class"] }; }) }; return topologies[level] = topojson.server.topology(JSON.parse(JSON.stringify(final_data[level].feature_collection)), { 'coordinate-system': 'cartesian', quantization: 0, 'property-transform': function(feature) { return feature.properties; } }); }; union = function(total, next) { return turf.union(total, next); }; merging = function(level, polygons) { var nested_polygons, new_polygons; if (level >= 0) { new_polygons = []; polygons.forEach(function(p) { var result; result = p.polygons.reduce(union); result.properties["class"] = p["class"]; new_polygons.push({ polygons: result, "class": p["class"] }); return features[level].push(result); }); nested_polygons = d3.nest().key(function(d) { return d["class"].slice(0, level); }).entries(new_polygons).map((function(d) { return { "class": d.key, polygons: d.values.map(function(v) { return v.polygons; }) }; }), d3.map); merging(level - 1, nested_polygons); return update_structures(level); } }; squares = []; data.forEach(function(d) { return squares.push({ polygons: d.quads.map(function(q) { return { type: "Feature", properties: {}, geometry: { type: "Polygon", coordinates: [[[q.x, q.y], [q.x + q.dx, q.y], [q.x + q.dx, q.y + q.dy], [q.x, q.y + q.dy], [q.x, q.y]]] } }; }), "class": d.key }); }); merging(levels - 1, squares); color_i = d3.scale.ordinal().domain(final_data[0].feature_collection.collection.features.map(function(d) { return d.properties["class"]; })).range((function() { results = []; for (var j = 0, ref = final_data[0].feature_collection.collection.features.length; 0 <= ref ? j < ref : j > ref; 0 <= ref ? j++ : j--){ results.push(j); } return results; }).apply(this)); /* TILES calculation */ console.debug('TILES calculation'); data.forEach(function(d) { var k, n, n_4, obj, ql, ref1, ref2, tiles; tiles = []; ql = quad_layout(hquad, scale * precision_multiplier); for (n = k = ref1 = d.start, ref2 = d.end; ref1 <= ref2 ? k < ref2 : k > ref2; n = ref1 <= ref2 ? ++k : --k) { n_4 = n.toString(4); n_4 = Array(order - n_4.length + 1).join('0') + n_4; obj = ql(n_4); obj.datum = d.values[n - d.start]; obj.name = d.key; tiles.push(obj); } return data_tiles = data_tiles.concat(tiles); }); /* VISUALIZATION */ width = document.body.getBoundingClientRect().width; height = document.body.getBoundingClientRect().height; svg = d3.select('svg').attr({ width: width, height: height }); zoomable_layer = svg.append('g'); vis = zoomable_layer.append('g').attr({ transform: "translate(" + ((width - scale) / 2) + ", " + ((height - scale) / 2) + ") scale(" + (1 / precision_multiplier) + ")" }); regions_layer = vis.append('g'); tiles_layer = vis.append('g'); labels_layer = vis.append('g'); zoom = d3.behavior.zoom().scaleExtent([1, Infinity]).on('zoom', function() { zoomable_layer.attr({ transform: "translate(" + (zoom.translate()) + ")scale(" + (zoom.scale()) + ")" }); return lod(zoom.translate(), zoom.scale()); }); svg.call(zoom); redraw_tiles = function(x_start, x_end, y_start, y_end) { var _data_tiles, enter_tiles, rect, tiles; if (x_start == null) { _data_tiles = []; } else { _data_tiles = data_tiles.filter(function(d) { return x_start - d.dx <= d.x + d.dx / 2 && x_end + d.dx >= d.x + d.dx / 2 && y_start - d.dy <= d.y + d.dy / 2 && y_end + d.dy >= d.y + d.dy / 2; }); } tiles = tiles_layer.selectAll('.tile').data(_data_tiles, function(d) { return d.digits; }); enter_tiles = tiles.enter().append('g').attr({ "class": 'tile' }); tiles.attr({ transform: function(d) { return "translate(" + d.x + ", " + d.y + ")"; } }); rect = enter_tiles.append('rect').attr({ fill: 'transparent' }); rect.attr({ x: function(d) { return 0; }, y: function(d) { return 0; }, width: function(d) { return d.dx; }, height: function(d) { return d.dy; } }); enter_tiles.append('text').attr({ x: function(d) { return d.dx / 2; }, y: function(d) { return d.dy / 2; }, 'font-size': 800, 'text-anchor': 'middle', dy: '0.35em' }).html(function(d) { return "" + d.datum.word; }); return tiles.exit().remove(); }; /* regions VISUALIZATION */ redraw_regions = function(z, level, x_start, x_end, y_start, y_end) { var enter_regions, geo_json, labels, regions; geo_json = topojson.client.feature(topologies[level], topologies[level].objects.collection).features; regions = regions_layer.selectAll('.region').data(final_data[level].feature_collection.collection.features.filter(function(d, i) { return final_data[level].spatial[i].bbox.geometry.coordinates[0][0][0] <= x_end && final_data[level].spatial[i].bbox.geometry.coordinates[0][1][0] >= x_start && final_data[level].spatial[i].bbox.geometry.coordinates[0][0][1] <= y_end && final_data[level].spatial[i].bbox.geometry.coordinates[0][3][1] >= y_start; }), function(d) { return d.properties["class"] + "_" + level; }); enter_regions = regions.enter().append('g').attr({ "class": 'region' }); enter_regions.append('path').attr({ stroke: function(d, i) { return '#333'; }, fill: function(d, i) { var index; d.i = i; index = color_i(d.properties["class"][0]); return d3.hcl(320 / final_data[0].feature_collection.collection.features.length * index, 60, 60 + (index % 2) * 10); } }).append('title').text(function(d) { return "Words starting with letter " + d.properties["class"]; }); regions.selectAll('path').attr({ d: function(d, i) { return path_generator(topojson.client.merge(topologies[level], topologies[level].objects.collection.geometries.filter(function(g) { return g.properties["class"] === d.properties["class"]; }))); } }); regions.exit().remove(); labels = labels_layer.selectAll('.region_label').data(final_data[level].spatial.filter(function(d) { return d.centroid[0] >= x_start && d.centroid[0] <= x_end && d.centroid[1] >= y_start && d.centroid[1] <= y_end; }), (function(d) { return d["class"] + "_" + level; })); labels.enter().append('text').attr({ "class": 'region_label', 'text-anchor': 'middle' }).text(function(d) { return d["class"][0].toUpperCase() + d["class"].slice(1); }); labels.attr({ transform: function(d) { return "translate(" + d.centroid[0] + ", " + d.centroid[1] + ") scale(" + (z === 0 ? 0 : precision_multiplier / z) + ")"; } }); labels.exit().remove(); }; /* Viewport and Level of details */ lod = function(translation, z) { var dx, dy, level, x, y; if (z < 3) { level = 0; } else if (z < 15) { level = 1; } else { level = 2; } x = (-(width - scale) / 2 - translation[0] / z) * precision_multiplier; y = (-(height - scale) / 2 - translation[1] / z) * precision_multiplier; dx = width * precision_multiplier / z; dy = height * precision_multiplier / z; if (tile_size * 15 > dx) { redraw_tiles(x, x + dx, y, y + dy); return redraw_regions(0, level, x, x + dx, y, y + dy); } else { redraw_tiles(); return redraw_regions(z, level, x, x + dx, y, y + dy); } }; return lod([0, 0], 1); }); }).call(this);