Color Factors — JS1K 2013 Spring Entry #1376 — 1019 bytes

An animated visualization of number theory: each integer is plotted as a point on a canvas and connected to all of its factors by arcs. Colors follow a spring rainbow; prime numbers are drawn in white because they have no factors.

AuthorPablo Caro Martín
Websitepcaro.es
Competitionclassic
Year2013 (Spring theme)
Bytes1019 / 1024
CompressionCustom Python JSCompressor (based on JSCrush by Aivo Paas)

Click anywhere on the canvas to activate TURBO MODE, which speeds up the animation dramatically.

This entry is notable for including the full, heavily-commented original source on its js1k details page — a rarity in the competition. The original source even includes a variable name legend and three "cool facts" about the code.

How It Works
  • Numbers are added one at a time via S(), each placed at an equal horizontal spacing that shrinks as more numbers appear
  • For each new number n+2, the code checks every smaller number to find factors; matching indices are stored in I[n]
  • If I[n] is empty the number is prime — a count p and last-prime tracker l are updated
  • Arcs are drawn using setTransform to stretch a unit circle into an ellipse connecting each number to its factors
  • The C(x) function maps an index 0–95 to an RGB rainbow, cycling through red → green → blue → red
  • Arc colors use createLinearGradient between the colors of the two connected points
  • The animation counter r runs 0→v each step; setTimeout drives the smooth entry of each new arc
  • Compressed to 1019 bytes (itself a prime number) using a custom Python compressor that finds optimal symbol substitution patterns
Cool Facts (from the author)

#1 — Without leading spaces, every line in the description paragraph is exactly 70 characters long. 70's factors are 35, 14, 10, 7, 5, and 2.

#2 — Primes only connect to points on their right (their multiples), never to the left, because they have no factors besides 1 and themselves.

#3 — The most-used character in the uncompressed source is i with 74 appearances, followed by ( and ) tied at 73 each.

The minified js1k submission — 1019 bytes.
// Control characters (compression tokens) shown as \xNN
_='\x1cm\x1dginN\x1coverflow="hidden";w=c.width\x16Width;h=c.h\x1e\x16H\x1e;Afont="7px \x1dial";I=[];e=w\x1f;n=-1;p=r=f=d=i=kNv=17;C\x18x%=96;return "rgb("+(\x19O||\x1a\x1b64\x17-64+V\x197\x177EO\x04\x1948B<O\x17V\x1964\x1764E48\x04\x19\x1b80\x1796-V\x1a7\x17-7+\x05)"};S\x18d=e-w/(\x10n+2_I\x06([]_if\x07>0)  `\x07+\x11\x1f;i\x10)if(\x07J%MJ\x010)  \x02n]\x06M_rN0\x01\x02nX\x04(p\x10,l=n+2_u\x03;u\x18e-=d/v;\x10r<v?\x0eTimeout(u,\x11:S\x03;\x0eInterval(~jSD="#000";jRect(0Fw,h_Y$(kNk<\x02iX;k\x10){AmoveTo(ZK_Asave(_A\x0eTrans$m(M-^)\x1fF0,(^J\x1f,e*M+^J\x1f,KUA\x1dc(0FeFM%2?1:-\x11*3.14*M\x01n?r/v:\x11,i%2\x01\x12Arestore(_Q*(^+\x11FZ\x12z0,C(^\x13M)_\x14SD=g;\x14\x03QFe*n,\x12`n;i+=O)zi/n,CM\x13\x07)UY0<qg;j(UY0\x01q"#fff";j(_@n\x15\x07J\x0f4\x12@Primes\x15p+"  Last\x15l\x0f8\x123<v\x04@Click $ turbo!"\x0fh-O)},4\x12c.onclick\x18v=v>3?3:17};S()q\x02iX\x04A\x1dc(ZK,4F8_jSD\x18function(x){zg.addColorStop(`$MNi<jAfillU_AbeginPath(_Vx)*O:Qg=AcreateLine\x1dGradient(e^\x02i][k]_);X].lengthY`=n;i\x10)Ze*M+\x11,DtyleE-\x05,"+(\x1aF,0,@jText("Aa.B?255:xM(iN=0;O16J+2)K3*h/5732$for\x14Astroke\x15: "+\x16=inner\x17?(\x10++\x111)\x120_\x13)_z1,C\x1cb.sD.\x1dar\x1eeight\x1f/2\x18=~\x19x<\x1ax>=\x1b80B>=\x04&&\x05V0)+"\x06.push\x07(n\x01==\x02I[\x03()}\x0eset\x0f,O,';for(Y=0;$="\x0f\x0e\x03\x02\x01\x07\x06\x05\x04\x1b\x1a\x19\x18\x1f\x1e\x1d\x1c\x13\x12\x11\x10\x17\x16\x15\x14$7KJONMBA@FEDZYX_^QVUj`z~q"[Y++];)with(_.split($))_=join(pop());eval(_)
Original source submitted by the author — fully commented.
/***************************************************************************

    Color Factors - js1k 2013-Spring
    Pablo Caro Martín - pcaro.es

    Welcome to Color Factors!

    In this world, each point represents a number, starting from 2, to the
    infinite. Each one of these number is connected with its factors by an
    arc. The colors of the points come from a spring-like rainbow, and the
    arcs have a gradient that comes from the colors of the points they are
    connecting. However, all primes are white, just because they are cool.
    The "n" in the top-left corner indicates the last number, "primes" the
    number of prime numbers found, and "last" the last prime number found.
    If you click anywhere in the screen, the TURBO MODE will be activated!

    Cool fact #1: Without the leading spaces, all the lines in the above
        paragraph are 70 chars long. 70's factors are 35, 14, 10, 7, 5 and 2.

    Obvious examples:
        - 10 is connected with 5, 2, and with its multiples (20, 30, 40...)
        - 20 is connected to 10, 5 and 2 (and its multiples)
        - 30 is connected to 10, 6, 5, 3 and 2 (and its multiples)
        - 31 is only connected with it multiples, because it's prime.

    The demo has been tested in OS X, in the following browsers:
        - Chrome: Works nicely.
        - Safari: Works nicely.
        - Firefox: Works, but really slow after a few numbers are added.
        - Opera: Works well, after applying a workaround to avoid a fix
            regarding the transformation of arcs.

    In order to minify the code, I wrote my own "JSCompressor" in Python,
    based on the original JSCrush by Aivo Paas (www.iteral.com/jscrush), but
    with some functionalities that help when trying to find an optimal
    minimizable code, obtaining an even more reduced output. Everything
    else has been done manually, with some help from my JSCompressor script.

    After the compression, the code length is 1019 characters, which is a
    prime number. Nice.

    Cool fact #2: The obvious property of prime numbers (they don't have
        factors besides of 1 and themselves) leads to the fact that they are
        only connected with points in their right side, never in their left
        side.


    Names used (and where can that character be found):

        a: 2d context
        b: document body
        c: canvas element
        C: color function (addColorStop)
        d: dinc (innerWidth)
        e: inc (innerHeight)
        f: ninc (function)

        g: gradient (innerHeight)
        h: height (innerWidth)

        i: loop index (innerWidth)
        I: list of factors (setInterval)
        k: loop index (strokeStyle)
        l: last prime found (fillStyle)
        n: numbers (innerWidth)

        p: primes found (addColorStop)

        r: rinc (innerWidth)
        S: add_point function (addColorStop)
        u: movement function (function)
        v: movement increment
        w: width (innerWidth)
        x: aux (fillText)

    Cool fact #3: The most used character in this script before compression is
        "i", with 74 appearances; followed by "(" and ")", with 73 appearances
        each; and by "=" and ";", tied with 59 appearances each (without
        counting the comments!).

***************************************************************************/


    // No margins. No scroll. Stay cool.
    b.style.margin=0;
    b.style.overflow="hidden";
    w=c.width=innerWidth;
    h=c.height=innerHeight;
    a.font="32px arial";

    // Variable inicialization
    I=[];
    e=w/2;
    n=-1;
    p=r=f=d=i=k=0;
    v=132;

    // Rainbow color!
    C=function(x){
        x%=96;
        return "rgb("
            +(x<16||x>=80?255:x>=64?(-64+x)*16:x<32?(32-x)*16:0)+","
            +(x>=16&&x<48?255:x<16?(x)*16:x<64?(64-x)*16:0)+","
            +(x>=48&&x<80?255:x>=80?(96-x)*16:x>=32?(-32+x)*16:0)+")"
    };

    // Add a new value
    S=function(x){
        d=e-w/(++n+2);
        I.push([]);
        if(n>0)  // Everyone loves 2
        for(i=0;i<(n+1)/2;i++)
            if((n+2)%(i+2)==0)  // Join with every factor
            // if((n+2)%(i+2)==0&&I[i].length==0) // Join with primes only
                I[n].push(i);
        r=0;
        0==I[n].length&&(p++,l=n+2);
        u()
    };

    // Movement
    u=function(x){
        e-=d/v;
        ++r<v?setTimeout(u,1):S()
    };

    // Drawing function
    setInterval(function(x){
        a.fillStyle="#000";
        a.fillRect(0,0,w,h);

        // Arcs
        for(i=0;i<=n;i++)
            for(k=0;k<I[i].length;k++){
                a.moveTo(e*(i+1),3*h/5);
                a.save();
                a.setTransform(
                    (i-I[i][k])/2,0,0,
                    (I[i][k]+2)/2,e*(i+I[i][k]+2)/2,3*h/5);
                a.beginPath();
                a.arc(0,0,e,0,(i%2?1:-1)*3.14*(i==n?r/v:1),i%2==0);
                a.restore();
                g=a.createLinearGradient(e*(I[i][k]+1),0,e*(i+1),0);
                g.addColorStop(0,C(I[i][k]));
                g.addColorStop(1,C(i));
                a.strokeStyle=g;
                a.stroke()
            }

        g=a.createLinearGradient(e,0,e*n,0);
        for(i=0;i<n;i+=16)
            g.addColorStop(i/n,C(i));
        g.addColorStop(1,C(n));

        // Color points (non primes)
        a.beginPath();
        for(i=0;i<=n;i++)
            0<I[i].length&&a.arc(e*(i+1),3*h/5,4,0,8);
        a.fillStyle=g;
        a.fill();

        // White points (primes)
        a.beginPath();
        for(i=0;i<=n;i++)
            0==I[i].length&&a.arc(e*(i+1),3*h/5,4,0,8);
        a.fillStyle="#fff";
        a.fill();

        a.fillText("n: "+(n+2),16,40);
        a.fillText("Primes: "+p+"  Last: "+l,16,80);
        3<v&&a.fillText("Click for turbo!",16,h-16)
    },40);

    c.onclick=function(x){v=v>3?3:132};

    S()
The HTML shim injected into the iframe. The tab’s JavaScript is appended after the closing </script>.
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box }
    body { background: #000 }
    canvas { display: block; position: absolute; top: 0; left: 0 }
  </style>
</head>
<body>
<canvas id="c"></canvas>
<script>
  var c = document.getElementById('c')
  var b = document.body
  var a = c.getContext('2d')
</script>
<!-- tab source injected here as a <script> tag after 3 seconds -->
</body>
</html>