Generates Fibonacci sequence and corresponding spiral with as many iterations as you see fit.
UPDATE: Animated and only using Sass and Jade
A Pen by Jake Albaugh on CodePen.
| // run the script with 1 iteration. | |
| // increase here and in sass mixin include for extending styles | |
| goldenSpiral(1); | |
| // the initialization script | |
| function goldenSpiral(iterations) { | |
| var seq = [1, 1], // initial sequence | |
| seq_length = seq.length, // initial size of sequence | |
| prev = seq_length - 1, // previous item = zero-index of last item | |
| iteration_size = 8, // squares per iteration | |
| total_count = iterations * iteration_size; // number count to generate | |
| // generating our fibonacci sequence | |
| for (var i = 0; i < (total_count - seq_length); i++) { | |
| // new value is sum of two previous | |
| a = seq[prev-1] + seq[prev]; | |
| // update previous number | |
| prev++; | |
| // add new to sequence | |
| seq.push(a); | |
| } | |
| // setting some base vars | |
| var h = seq[seq.length-1], | |
| w = h + seq[seq.length-2], | |
| // gutter around svg | |
| gtr = 1, | |
| // scale factor to shrink to size | |
| scale = window.innerWidth / w; | |
| // creating base svg | |
| var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"), | |
| // svgNS for creating svg elements | |
| svgNS = svg.namespaceURI; | |
| // svg attributes | |
| svg.setAttribute("class", "goldenSpiral"); | |
| svg.setAttribute("width", "100%"); | |
| svg.setAttribute("height", "100%"); | |
| svg.setAttribute("viewBox", "0 0 " + (w * scale + gtr) + " " + (h * scale + gtr)); | |
| // initial position/size data | |
| var prev_x = (gtr / 2), | |
| prev_y = (gtr / 2), | |
| prev_di = 0; | |
| // svg element groups | |
| var rects = document.createElementNS(svgNS, "g"), | |
| paths = document.createElementNS(svgNS, "g"), | |
| circles = document.createElementNS(svgNS, "g"), | |
| texts = document.createElementNS(svgNS, "g"); | |
| // for each item in sequence | |
| for (var i = (seq.length-1); i >= 0; i--) { | |
| // initial svg elements | |
| var rect = document.createElementNS(svgNS, "rect"), | |
| path = document.createElementNS(svgNS, "path"), | |
| circle = document.createElementNS(svgNS, "circle"), | |
| text = document.createElementNS(svgNS, "text"), | |
| di = seq[i] * scale; | |
| // different cases for each set of four numbers | |
| switch(i % 4) { | |
| // 21, 3 | |
| case 3: | |
| x = prev_x | |
| y = Math.max(prev_y - di, gtr / 2) | |
| // arc bottom left to top right | |
| d = describeArc(x + di, y + di, di, 270, 360) | |
| // circle center bottom right | |
| c = [x + di, y + di, di] | |
| break; | |
| // 13, 2 | |
| case 2: | |
| x = prev_x + prev_di | |
| y = prev_y | |
| // arc top left to bottom right | |
| d = describeArc(x, y + di, di, 0, 90) | |
| // circle center bottom left | |
| c = [x, y + di, di] | |
| break; | |
| // 8, 1 | |
| case 1: | |
| x = prev_x + prev_di - di | |
| y = prev_y + prev_di | |
| // arc top right to bottom left | |
| d = describeArc(x, y, di, 90, 180) | |
| // circle center top left | |
| c = [x, y, di] | |
| break; | |
| // 5, 1 | |
| case 0: | |
| x = prev_x - di | |
| y = prev_y + prev_di - di | |
| // arc bottom right to top left | |
| d = describeArc(x + di, y, di, 180, 270) | |
| // circle center top right | |
| c = [x + di, y, di] | |
| break; | |
| } | |
| // sizing ratio | |
| var ratio = di / (w * scale + gtr); | |
| // building text element | |
| text.textContent = commafy(seq[i]); | |
| text.setAttribute("x", x + di / 2); | |
| text.setAttribute("y", y + di / 2); | |
| text.setAttribute("font-size", ratio * 300); | |
| texts.appendChild(text); | |
| // building rect element | |
| rect.setAttribute("x", x); | |
| rect.setAttribute("y", y); | |
| rect.setAttribute("width", di); | |
| rect.setAttribute("height", di); | |
| rects.appendChild(rect); | |
| // building path element | |
| path.setAttribute("d", d); | |
| paths.appendChild(path); | |
| // building circle element | |
| circle.setAttribute("cx", c[0]); | |
| circle.setAttribute("cy", c[1]); | |
| circle.setAttribute("r", c[2]); | |
| circles.appendChild(circle); | |
| // set previous values | |
| prev_x = x; | |
| prev_y = y; | |
| prev_di = di; | |
| } | |
| // master group | |
| master = document.createElementNS(svgNS, "g"), | |
| master.setAttribute("class", "master"); | |
| // appending our elements to the master group | |
| master.appendChild(rects); | |
| master.appendChild(circles); | |
| master.appendChild(paths); | |
| master.appendChild(texts); | |
| // appending our master group to the svg | |
| svg.appendChild(master); | |
| // adding our svg to the dom | |
| document.body.appendChild(svg); | |
| } | |
| // making long numbers legible (if you amplify the iteration count) | |
| function commafy(x) { | |
| return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); | |
| } | |
| // brilliance on creating quarter circle arcs | |
| // http://stackoverflow.com/questions/5736398/how-to-calculate-the-svg-path-for-an-arc-of-a-circle | |
| function polarToCartesian(centerX, centerY, radius, angleInDegrees) { | |
| var angleInRadians = (angleInDegrees-90) * Math.PI / 180.0; | |
| return { | |
| x: centerX + (radius * Math.cos(angleInRadians)), | |
| y: centerY + (radius * Math.sin(angleInRadians)) | |
| }; | |
| } | |
| function describeArc(x, y, radius, startAngle, endAngle){ | |
| var start = polarToCartesian(x, y, radius, endAngle); | |
| var end = polarToCartesian(x, y, radius, startAngle); | |
| var arcSweep = endAngle - startAngle <= 180 ? "0" : "1"; | |
| var d = [ | |
| "M", start.x, start.y, | |
| "A", radius, radius, 0, arcSweep, 0, end.x, end.y | |
| ].join(" "); | |
| return d; | |
| } |
| // included at very bottom. modify iteration count there. | |
| @mixin goldenSpiral($iterations) { | |
| $total_count: 8 * $iterations; | |
| position: absolute; | |
| top: 50%; left: 50%; | |
| transform: translate3d(-50%, -50%, 0); | |
| display: block; | |
| // each rect different background | |
| @for $i from 1 through $total_count { | |
| $ratio: $i / $total_count; | |
| $color: hsla(($ratio * 360), 30, 50, 1); | |
| rect:nth-child(#{$i}) { fill: $color; } | |
| path:nth-child(#{$i}) { stroke: lighten($color,30%); } | |
| } | |
| // circle fills | |
| circle { | |
| fill: rgba(0,0,0,0.05); | |
| stroke: none; | |
| } | |
| // path styles with marching ants | |
| $dash: 2; | |
| path { | |
| fill: none; | |
| stroke: white; | |
| stroke-width: $dash; | |
| stroke-dasharray: $dash,$dash * 2; | |
| opacity: 0.9; | |
| animation: stroke-offset 2s linear infinite; | |
| } | |
| // marching ants | |
| @keyframes stroke-offset { | |
| 0% { stroke-dashoffset: 0; } | |
| 100% { stroke-dashoffset: $dash * 6; } | |
| } | |
| // text styles for sequence number | |
| text { | |
| alignment-baseline: middle; | |
| text-anchor: middle; | |
| fill: rgba(255,255,255,0.9); | |
| font-weight: 100; | |
| } | |
| } | |
| // new golden spiral with one iteration. | |
| svg.goldenSpiral { | |
| // change here to an in js iteration count | |
| @include goldenSpiral(1); | |
| } | |
| // ignores | |
| body { background: black; } | |
Generates Fibonacci sequence and corresponding spiral with as many iterations as you see fit.
UPDATE: Animated and only using Sass and Jade
A Pen by Jake Albaugh on CodePen.