/* compute a Lindenmayer system given an axiom, a number of steps and rules */ (function() { var fractalize, hex_coords, new_hex; fractalize = function(config) { var char, i, input, output, _i, _len, _ref; input = config.axiom; for (i = 0, _ref = config.steps; 0 <= _ref ? i < _ref : i > _ref; 0 <= _ref ? i++ : i--) { output = ''; for (_i = 0, _len = input.length; _i < _len; _i++) { char = input[_i]; if (char in config.rules) { output += config.rules[char]; } else { output += char; } } input = output; } return output; }; /* convert a Lindenmayer string into an array of hexagonal coordinates */ hex_coords = function(config) { var char, current, dir, dir_i, directions, path, _i, _len, _ref; directions = [ { x: +1, y: -1, z: 0 }, { x: +1, y: 0, z: -1 }, { x: 0, y: +1, z: -1 }, { x: -1, y: +1, z: 0 }, { x: -1, y: 0, z: +1 }, { x: 0, y: -1, z: +1 } ]; /* start the walk from the origin cell, facing east */ path = [ { x: 0, y: 0, z: 0 } ]; dir_i = 0; _ref = config.fractal; for (_i = 0, _len = _ref.length; _i < _len; _i++) { char = _ref[_i]; if (char === '+') { dir_i = (dir_i + 1) % directions.length; } else if (char === '-') { dir_i = dir_i - 1; if (dir_i === -1) dir_i = 5; } else if (char === 'F') { dir = directions[dir_i]; current = path[path.length - 1]; path.push({ x: current.x + dir.x, y: current.y + dir.y, z: current.z + dir.z }); } } return path; }; window.main = function() { var colorify, coords, dx, dy, e, gosper, height, hexes, i, path_generator, radius, seq, svg, vis, width; width = 960; height = 500; svg = d3.select('body').append('svg').attr('width', width).attr('height', height); vis = svg.append('g').attr('transform', 'translate(540,10)'); /* create the input sequence (random length between 2000 and 2401) */ /* give the element a random class (A is more likely, C is less likely) */ seq = (function() { var _ref, _results; _results = []; for (i = 0, _ref = 2000 + Math.floor(Math.random() * 401); 0 <= _ref ? i <= _ref : i >= _ref; 0 <= _ref ? i++ : i--) { _results.push(['A', 'A', 'A', 'A', 'B', 'B', 'C', 'D', 'D'][Math.floor(Math.random() * 9)]); } return _results; })(); /* sort the sequence by class */ seq.sort(); /* create the Gosper curve */ gosper = fractalize({ axiom: 'A', steps: 4, rules: { A: 'A+BF++BF-FA--FAFA-BF+', B: '-FA+BFBF++BF+FA--FA-B' } }); /* convert the curve into coordinates of hex cells */ coords = hex_coords({ fractal: gosper }); /* create the GeoJSON hexes */ hexes = { type: 'FeatureCollection', features: (function() { var _len, _results; _results = []; for (i = 0, _len = seq.length; i < _len; i++) { e = seq[i]; _results.push(new_hex(coords[i], e)); } return _results; })() }; /* custom projection to make hexagons appear regular (y axis is also flipped) */ radius = 5; dx = radius * 2 * Math.sin(Math.PI / 3); dy = radius * 1.5; path_generator = d3.geo.path().projection(d3.geo.transform({ point: function(x, y) { return this.stream.point(x * dx / 2, -(y - (2 - (y & 1)) / 3) * dy / 2); } })); /* define a color scale (Colorbrewer's Set3) */ colorify = d3.scale.ordinal().domain(['A', 'B', 'C', 'D']).range(["#8dd3c7", "#ffffb3", "#bebada", "#fb8072"]); /* draw the cells */ return vis.selectAll('.hex').data(hexes.features).enter().append('path').attr('class', 'hex').attr('d', path_generator).attr('fill', function(d) { return colorify(d.properties['class']); }).attr('stroke', function(d) { return colorify(d.properties['class']); }); }; /* create a new hexagon */ new_hex = function(c, e) { /* conversion from hex coordinates to rect */ var x, y; x = 2 * (c.x + c.z / 2.0); y = 2 * c.z; return { type: 'Feature', geometry: { type: 'Polygon', coordinates: [[[x, y + 2], [x + 1, y + 1], [x + 1, y], [x, y - 1], [x - 1, y], [x - 1, y + 1], [x, y + 2]]] }, properties: { 'class': e } }; }; }).call(this);