Number Spiral Mandalas with HTML Canvas

Posted on 21st July 2014 by Ryan Somma in Geeking Out
HTML Canvas 12-times Spiral
12-Spiral in HTML Canvas
(Primes highlighted in red)

Mathematics is about exploration, not the rote memorization of algortithms. It’s about finding patterns, appreciating how numbers relate to one another. That’s why I love programming. Much of writing code involves experimentation with mathematics, tweeking a variable to see what comes out of a complex function. It’s wonderful when a program doesn’t give you the answer you designed it to; that means you are about to learn something.

I was really inspired by this post on Moebius Noodles about Joey Grether, who creates spiraling artwork with the number 12 that seeks patterns in the lines of numbers radiating out from the center. Reminiscent of Ulam’s Spiral, building a spiral around a clock, with 12-segments in the rotation, puts multiples of 3 at {3,6,9,12} o’clock, multiples of 4 at {4,8,12} o’clock, multiples of 2 at {2,4,6,8,10,12} o’clock, and primes at {1,5,7,11}.

Knowing that programmers had written Ulam’s and Sack’s Spiral Generators, I wondered if I could do the same with this 12 spiral. In a few hours I was reacquainted with many mathematical concepts, from degrees, to radians, to algorithms for finding primes and their optimization. It’s a great, zenlike feeling to be immersed in the beauty of logic, and, in the interest of sharing that feeling, I decided to write a sort of code-essay to encourage others to download my rough draft, play with it, and maybe even take it further.

HTML Canvas 48-times Spiral
48-Spiral in HTML Canvas
(Primes highlighted in red)

Playing around with this code, I found there’s a symmetry to the spirals that are multiples of 12 {24, 36, 48, 60} that I couldn’t find in other spirals. While {12, 24, 48, 96…} appear to have a mirror symmetry along the left and right, other factors of 12, like 60, have a different kind of symmetry that I don’t quite know how to describe. The number 12 remains my favorite number for it’s simplicity, symmetry, and what it reveals about the relationships between numbers, seeming to categorize them along the spiral branches.

HTML Canvas 48-times Spiral
60-Spiral in HTML Canvas
(Primes highlighted in red, symmetry highlighted yellow)

You don’t need any special environment to run this code on your computer, simply Right-Click –>HERE and select “Save As” to save the html file to your computer, then drag-and-drop it into your browser window or double-click on it to launch it. You can then edit it in notepad or, my favorite, the free Sublime Text Editor. Edit the code, save, and then refresh your browser to see the result. Super simple.

<!DOCTYPE html>
<html>
<title>Mandella Number Spirals</title>
<head>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.5.1/jquery.min.js"></script>

<script type="text/javascript">
//*********************************************************************
// See all these lines starting with "//" those are comments,
// the browser ignores them. You can try adding "//" to hide
// code from the browser to see what happens or to try other things.
//*********************************************************************
// So let's get started--
//
// --but first, an OCTOPUS!!! Because... OCTOPUS!!!
//
//                    .-'   `'.
//                   /         \
//                   |         ;
//                   |         |           ___.--,
//          _.._     |0) ~ (0) |    _.---'`__.-( (_.
//   __.--'`_.. '.__.\    '--. \_.-' ,.--'`     `""`
//  ( ,.--'`   ',__ /./;   ;, '.__.'`    __
//  _`) )  .---.__.' / |   |\   \__..--""  """--.,_
// `---' .'.''-._.-'`_./  /\ '.  \ _.-~~~````~~~-._`-.__.'
//       | |  .' _.-' |  |  \  \  '.               `~---`
//        \ \/ .'     \  \   '. '-._)
//         \/ /        \  \    `=.__`~-.
//    jgs  / /\         `) )    / / `"".`\
//   , _.-'.'\ \        / /    ( (     / /
//    `--~`   ) )    .-'.'      '.'.  | (
//           (/`    ( (`          ) )  '-;
//            `      '-;         (-'
//
// Aquatic ASCII art at:
// http://www.geocities.com/spunk1111/aquatic.htm#octopus
//*********************************************************************

// The following three lines tell the browser to
// run the drawCanvas() function when the page finishes loading.
$().ready(function() {
  drawCanvas();
});

// And this is the drawCanvas() function that calls all the
// other functions to put the image together.
function drawCanvas() {

      // This gets the <canvas class="cframe" id="canvas" /> element out of the HTML
      // This is where all of our drawing will take place.
      var canvas = document.getElementById("canvas");

      // Define the width and height of our canvas.
      // This is in pixels. For a larger space to draw on
      // simply increase the width and height here.
      canvas.width = 1000;
      canvas.height = 1000;

      // The context is the thingy we're going to use to
      // do all our drawing with.
      //
      // For more on the HTML canvas, I recommend w3schools.com
      // in fact, I recommend wschools for learning about any
      // programming language or web technology. They have the
      // best introductions for everything!!!
      // http://www.w3schools.com/tags/ref_canvas.asp
      var context = canvas.getContext('2d');

      // Put our drawing focus at the center of the canvas
      context.translate(canvas.width/2,canvas.height/2);
 
     //************** ARGUMENTS/INPUTS!!! **************
     // Here we define our arguments and inputs (duh),
     // modify things here starting out to play with and
     // just have fun.
     //
     // Once this section gets boring, check out the functions
     // themselves and fall down the rabbit hole of programming wonder!!!
     // BWA-HA-HA-HA-HA!!!
     //************** ARGUMENTS/INPUTS!!! **************

            // How many sides do you want on your polygon?
      var numberOfSidesOnThePolygons = 12;
      // How many polygons do you want to draw?
      var numberOfPolgonsToGenerate = 50;
     
      // How many lines do you want to draw?
      var numberOfLinesToGenerate = 12;
     
      // How many numbers in your clock?
      var numberOfNumbersToGenerate = 12;
      // How many times to spiral out those numbers?
      var numberOfNumbersSpirals = 40;

      // What color do you want things to be?
      // These # plus six-digit numbers are hexidecimal (Base-16) values for colors.
      // They define levels of red, green, and blue (RGB) (#RRGGBB), which mix
      // to form all the colors of the visible spectrum.
      // Here's a great primer on hexidecimal color values:
      // http://www.smashingmagazine.com/2012/10/04/the-code-side-of-color/
      var polygonColor = '#00AA00'; // 0-Red, 121-Green, 0-Blue = Light Green
      var lineColor = '#736AFF'; // Purple!!! Yay!!!
      var numberColor = '#000000'; // 0-Red, 0-Green, 0-Blue = Black
      var primeColor = '#ff0000'; // 256-Red, 0-Green, 0-Blue = Red

            // Font to write our numbers in.
      var numberFont = '12px Arial';
      var primeFont = '12px Arial bold';

      // These variables rotate the context, because all of these functions
      // use the same context, rotating the polygon turns the numbers upside down
      // So I defined different rotations to correct it.
      var polygonRotateAngle = -Math.PI/2;
      var lineRotateAngle = 0; // Not used right now, but could be fun.
      var numberRotateAngle = polygonRotateAngle * 2;

            // Two kinds of radii being defined here for polygons and numbers,
            // the Radius is the circle defining the initial size, while the
            // RadiusIncrement defines how much to extend out each subsequent
            // circle or spiral.
      var polygonRadius = 50;
      var polygoneRadiusIncrement = 20;

      var numberRadius = 25;
      var numberRadiusIncrement = 40;

      //************** FUNCTIONS!!! **************
      // These are the three functions that draw the shapes
      // The comma-separated stuff in parentheses are the
      // arguments/inputs given to each function.
      // Above each function, commented out, is the names
      // of those arguments. You can then scroll to the
      // functions below and see how those arguments/inputs
      // are used by the function.
      //************** FUNCTIONS!!! **************

      // function multiPolygon(context, radius, sides, rotateAngle, numberOfPolygons, color)
      multiPolygon(context, polygonRadius, polygoneRadiusIncrement, numberOfSidesOnThePolygons, polygonRotateAngle, numberOfPolgonsToGenerate, polygonColor);

      // function multiLine(context, numberOfLines, color)
      multiLine(context, numberOfLinesToGenerate, lineColor);

      // function drawNumbers(context, radius, numberOfNumbers, numberOfSpirals, numberFont, numberColor, primeFont, primeColor)
      drawNumbers(context, numberRadius, numberRadiusIncrement, numberOfNumbersToGenerate, numberOfNumbersSpirals, numberFont, numberColor, primeFont, primeColor, numberRotateAngle);

}

//**************************************************************************************************
// This draws the polygons for our mandella. The author of this function does
// some AMAZING stuff with canvas. I highly recommend checking him out at:
// http://krazydad.com/tutorials/circles/
// He's got code for animated spirals, fibonnacci spirals, and other shapes.
// You can waste incredible amounts of time playing with and learning from his examples,
// and remember what John Lennon said, "Time you enjoyed wasting was not wasted."
//**************************************************************************************************
function multiPolygon(context, radius, radiusIncrement, sides, rotateAngle, numberOfPolygons, color) {

  //Set the color of our polygons.
  context.strokeStyle = color;

  // If you're trying to draw a polygon with fewer than
  // three sides, then you don't deserve a polygon.
  if (sides < 3) return;

  // Defining the a variable... looks complicated.
  // I *think* PI times 2 gives us the radians for a
  // complete ciricle, which is then divided by the
  // number of sides we are going to draw around inside it.
  // (See the KrazyDad.com link above)
  var a = (Math.PI * 2)/sides;

  // Loop through the number of polygons requested and generate
  // increasingly larger ones.
  for (var k = 1; k <= numberOfPolygons; k++) {

      context.beginPath();
      context.rotate(rotateAngle);
      context.moveTo(radius,0);

      for (var i = 1; i < sides; i++) {
          // Here's that a variable again.
          // I don't remember my cosine, sin, tangent stuff aside
          // from "Some old horse came a'hopping through our alley"
          // but I don't know how that applies here.
                // (See the KrazyDad.com link above)
          context.lineTo(radius*Math.cos(a*i),radius*Math.sin(a*i));
      }

      context.closePath();
      context.stroke()

      //With each polygon drawn, we make it a little larger.
      radius += radiusIncrement;

  }

}

//**************************************************************************************************
// This draws the lines radiating out from the center of the mandella.
// I adopted the code from someone who was using it to draw the minute-hand
// on a clock.
// http://www.dhtmlgoodies.com/tutorials/canvas-clock/
//**************************************************************************************************
function multiLine(context, numberOfLines, color) {

    // Set the color of our lines
    context.strokeStyle = color;

    // Canvas and mathematicians use radians,
    // while humans were taught degrees, so we
    // need to convert degrees to radians.
    // 360 degrees divided by the number of lines will
    // bring us full-circle, while PI divided by 180
    // gets us 0.0174532925 radians, which equals 1 degree
    radians = (Math.PI / 180) * (360 / numberOfLines);

    // For each line, draw a 1000 pixel line,
    // then rotate the context the number of
    // radians, and draw another line on the
    // next loop.
    for (var i = 1; i <= numberOfLines; i++) {
       
        context.beginPath();
        context.moveTo(0,0);
        context.lineTo(0,-1000);
        context.stroke();

                // Because we rotate full-circle,
                // I don't worry about resetting the
                // rotated context later.
                // BUT you could add a rotate adjustment
                // to this function and offset the lines
                // a bit for fun.
        context.rotate(radians);
       
   }

}

//**************************************************************************************************
// This draws the numbers around in a circle or spiral.
// I adopted it from a technique someone used to put numbers
// on a clock (a different clock from the one above).
// http://www.script-tutorials.com/html5-clocks/
//**************************************************************************************************
function drawNumbers(context, radius, radiusIncrement, numberOfNumbers, numberOfSpirals, numberFont, numberColor, primeFont, primeColor, rotateAngle) {

    // Used to correct for rotation performed by
    // that greedy polygon function.
    context.rotate(rotateAngle);

    // Define the font alignment
    context.textAlign = 'center';
    context.textBaseline = 'middle';
   
    // Outer loop, number of spirals
    // How many times we want to loop through the numberOfNumbers
    for (var i = 0; i <= numberOfSpirals; i++) {
     
      // How many increments in each circle/spiral
      for (var n = 1; n <= numberOfNumbers; n++) {

                    // More math stuff.
                    // Looks like more radians and cosines and sines
                    // and stuff to define the x/y coordinates of
                    // where to place the number.
          var theta = (n - 3) * (Math.PI * 2) / numberOfNumbers;
          var x = radius * 0.7 * Math.cos(theta);
          var y = radius * 0.7 * Math.sin(theta);
         
          // The number we are going to write.
          var numberToWrite = n+(i*numberOfNumbers);

          // Primality check, calls the isPrime()
          // function further below.
          if (isPrime(numberToWrite)) {
            // If prime, change font to bold red
            context.font = primeFont;
            context.fillStyle = primeColor;
          } else {
            // Not prime, keep standard font
            context.font = numberFont;
            context.fillStyle = numberColor;
          }
         
          //Write the number at the x and y coordinate.
          context.fillText(numberToWrite, x, y);
         
          // By increasing the radius slightly with each loop
          // Each number gets pushed out a little more,
          // producing the spiral
          radius += (radiusIncrement / numberOfNumbers);
     
      }
     
      // Commented Out, but alternatively, you can do
      // larger and larger circles instead of spirals by
      // removing the above radius increase and adding this one
      // radius += radiusIncrement;
   
    }

}

// JavaScript does not have a built-in function for testing prime numbers
// This excellent function comes from:
// http://www.javascripter.net/faq/numberisprime.htm
// Where several iterations of the function are presented, each with
// increasing efficiency by eliminating the number of necessary loops.
// From the source: "At most 1/3 of divisors under sqrt(n) are checked"
function isPrime(n) {
    // Make sure n:
    // is a number,
    // is finite,
    // is divisble by 1 without a remainder (% is the modulus operator),
    // and is not less than 2
    if (isNaN(n) || !isFinite(n) || n % 1 || n < 2) {
        return false;
    }
    // Eliminate even numbers
    if (n % 2 == 0) {
        return (n == 2);
    }
    // Eliminate numbers divisible by 3
    if (n % 3 == 0) {
        return (n == 3);
    }
    // m is the upper-bound of numbers we will test, no need to go higher than the square root.
    var m = Math.sqrt(n);
    // Since we have eliminated even numbers and numbers divisible by 3...
    // Loop through every number between 5 and m, incrementing by 6 each loop, so {5,11,17,23,29...}
    for (var i = 5; i <= m; i += 6) {
        if (n % i == 0) {
            return false;
        }
        if (n % (i + 2) == 0) {
            return false;
        }
    }
    return true;
}

</script>

<style>
    body,div,p { font-family: futura,gotham,arial,helvetica,sans-serif; }
    h1 { text-align: center; }
    canvas.cframe { border: 1px solid #444; }
    div.ex { text-align: center; }
</style>

</head>
<body>

<div class="ex">
<canvas class="cframe" id="canvas" />
</div>

</body>
</html>
Comments Off on Number Spiral Mandalas with HTML Canvas

No Comments

No comments yet.

RSS feed for comments on this post.

Sorry, the comment form is closed at this time.