js/c10canvasRot.php

<!DOCTYPE html>
<html>
<head>
  <title> <?php echo basename(__file__, '.php'); ?> </title>
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=3.0, user-scalable=yes"/>
  <meta name="HandheldFriendly" content="true" />
  <meta name="apple-mobile-web-app-capable" content="YES" /> 
  <style>
      canvas {
        /* touch-action:none; */
        border: 1px solid black;
      }
  </style>
</head>
<body onload="draw();">

<h1>Canvas: 4 color Rotation in square</h1>
    <p>Layout<p/> 
    <div style="display: inline-block; vertical-align: top;"><canvas id="layout" width="10" height="60"></canvas></div>
    <div style="display: inline-block; vertical-align: top;"><ul style="margin-top: 0;">
        <li style="background-color: #0a0">defining area 
        </li><li style="background-color: #8f8">rotations of defining area
        </li><li style="background-color: #88f">inner fractals
        </li><li style="background-color: #fcc">outer fractals
    </li></ul></div>
<ol><li>defining area size in pixels
    <input type="text" id="dx" value=120 required minlength="1" maxlength="4" size="7"> &times; 
    <input type="text" id="dy" value=30 required minlength="1" maxlength="4" size="7">  
    <span id="dMsg">dMsg</span>
</li><li>color matrix 
    <textarea id="col" rows="5" cols="16" style="vertical-align: top;">0001020</textarea> 
    <span id="colMsg">colMsg</span>
</li><li>fractals 
    <button id="frIn" type="button" value='f'>inside</button><input type='hidden' id='frInHi' value='f'>
    outside 
    <input type="text" id="frOu" value=0 required minlength="1" maxlength="4" size="7">  
    <span id="frMsg">frMsg</span>
</li></ol>
   <div style="display: inline-block; vertical-align: top;"><canvas  id="tutorial" width="150" height="150"></canvas></div>
    <div  style="display: inline-block; width: 150px;" id="canRi">
    <ul><li>inside <span id="fi"></span>
    </li><li>outside <span id="fo"></span>
    </li></ul></div>
<h1 style="clear: both;">Source <?php echo __file__; ?> </h1>
<?php highlight_file(__file__) ?>
</body>

<script>
    const can = document.getElementById("tutorial")
      , ctx = can.getContext("2d")
      , dxE = document.getElementById("dx")
      , dyE = document.getElementById("dy")
      , coE = document.getElementById("col")
      , frInE = document.getElementById("frIn")
      , frInH = document.getElementById("frInHi")               /* use input, because button.value is lost by browser refresh! */
      , frOuE = document.getElementById("frOu")
    ; 
    var dx = Math.round((dxE.value))
      , dy = Math.round((dyE.value))
      , frOu = Math.round((frOuE.value))
      , col = coE.value.replaceAll(' ', '').split(/\r\n|\r|\n/)
      , frIn = frInH.value === 't'
      , size
    ;

    dxE.addEventListener("input", (e) => {                      // dx changed
        const v = Math.round((dxE.value));
        if (isNaN(v))
            return document.getElementById("dMsg").innerHTML = 'is not an integer: ' + v;
        dx = v;
        canSize();
        });

    dyE.addEventListener("input", (e) => {                      // dy changed
        const v = Math.round((dyE.value));
        if (isNaN(v))
            return document.getElementById("dMsg").innerHTML = 'is not an integer: ' + v;
        dy = v;
        canSize();
        });

    coE.addEventListener("input", (e) => {                       // colour matrix changed
        if ( ! coE.value.match(/^[\r\n 0123]+$/))
            return document.getElementById("colMsg").innerHTML = 'invalid color, only 0, 1, 2 or 3';
        col = coE.value.replaceAll(' ', '').split(/\r\n|\r|\n/); // make array
        var lcm = 1; // least common multiple of lengths
        col.forEach(e => lcm = e.length <= 1 ? lcm : lcm * e.length / gcd(lcm, e.length));
        document.getElementById("colMsg").innerHTML = 'col ' + col.length + ' lcm ' + lcm + ' = ' + col;
        draw();
        });


    frInE.addEventListener("click", (e) => {                    // click on fractalInside
        frIn = frInH.value !== 't';
        frInH.value = frIn ? 't' : 'f';
        frInE.style='background-color: ' + (frIn ? 'lightgreen;' : 'unset;');
        document.getElementById("frMsg").innerHTML = 'inside = ' + frIn + ', outside = ' + frOu + ' px';
        document.getElementById("fi").innerHTML = ''
        draw();
        });

    frOuE.addEventListener("input", (e) => {                    // fractalOutside changed
        const v = Math.round((frOuE.value));
        if (isNaN(v) || v < 0)
            return document.getElementById("frMsg").innerHTML = 'is not an integer >= 0: ' + v;
        frOu = v;
        document.getElementById("fo").innerHTML = ''
        canSize();
        });

    window.addEventListener("resize", (e) => { canSize(); });

    layoutDraw(50, 20, 12)    
    frInE.style='background-color: ' + (frIn ? 'lightgreen;' : 'unset;');
    canSize();

    function canSize() {                                        // compute size of canvas
        if (dx < dy || dy < 1)
            return document.getElementById("dMsg").innerHTML = 'please select dx > dy > 0';
        size = dx + dy + 2 * frOu; 
        can.width = can.height = size
        document.getElementById("dMsg").innerHTML = '= ' + dx + ' &times; ' + dy;
        let rw = document.body.clientWidth                      // set width of list after canvas, so its displayes on right side of it, if enough space
                                                                // attention width is for div, as for most elements, only a css attribute, not a html attribute !!!
        document.getElementById("canRi").style.width = sw = rw - size < 100 ? 'auto' : (rw-size-8 + 'px');      // 2*1 for the border, plus some px for spaces
        document.getElementById("frMsg").innerHTML = 'inside = ' + frIn + ', outside = ' + frOu + ' px' 
             //   + ', ww ' + window.innerWidth + ",deW " + document.body.clientWidth + ", riWi " + sw + '>' + document.getElementById("canRi").style.width ;
        draw();
    }

    function draw() {                                           // draw the canvas
        const sL = size - 2 * frOu;
        //a = Object.assign({}, aEv);

        ctx.clearRect(0, 0, size, size);
        // ctx.setTransform(0, 1, -1, 0, 150, 0);
        drawS(frOu, frOu, dx, dy);                              // defining area and rotations
        if (frIn) {                                             // fractals inside
            let d = dy, ma = [];
            while (sL - 2 * d > 0) {                            // draw while still space in the middle
                const h = Math.max(1, Math.round(dy/sL*(sL -2 * d)));
                ma.push(h);
                drawS(d+frOu, d+frOu, sL -2 * d - h, h);
                d += h;
            }
            document.getElementById("fi").innerHTML =  ma.length + ': ' + ma.toString().replaceAll(',', ',<wbr>');
        }
        if (frOu > 0) {                                         // fractals outside
            let ma = [];
            const fr = 1 - dy / (dy+frOu);                      // factor for downscaling height (width is scaled up!!!)
            let x = frOu
              , w = dy;
            while (x > 0) {
                x -= w = Math.max(1, Math.round(w * fr));
                ma.push(w);
                // console.log("fr " + fr + ", w" + w + " x---" + x);
                drawS(x, x, size - 2 * x - w, w);
            }
           document.getElementById("fo").innerHTML = fr.toExponential(2) + ' ' + ma.length + ': ' + ma.toString().replaceAll(',', ',<wbr>');
        }
   }

    function drawS(ax, ay, aw, ah) {                            /* draw the rectangle at ax,ay with dimension aw,ah plus he 3 rotations with color permutation */
        const cls = ["rgb(200, 0, 0)", "rgb(0, 200, 0)", "rgb(0, 0, 200)", "rgb(100, 100, 0)"];
        // const cls = ["rgb(200, 0, 0)", "rgb(50, 150, 0)", "rgb(0, 100, 100)", "rgb(50, 0, 150)"];
        var by = 0
          , bi = 0;
                    
        for (let i=0; i<col.length; i++) {                      // handle each color row => row of rectangles in defining area
            const h = Math.floor((i+1) * ah / col.length - by);
            if (h < 1)
                continue;
                                                                // draw row 
            const c = col[bi]                                   // for colours bi to i (we select first one)
              , y = ay+by;                                      // to y coordinates from by downwards within defining area
            let dx = 0
              , dj = 0;
            for (let j=0; j<c.length; j++) {                    // use each colour for the approriate range in x direction
                let w = Math.floor((j+1) * aw / c.length - dx);
                if (w < 1)
                    continue;

                const c1 = Number(c.charAt(dj))                 // the index of the color
                  , x = ax + dx;                                // the left x coordinate
                drawR(cls[c1 % 4], x, y, w, h);                 // fill rectangle in the defining area
                drawR(cls[(c1+1) % 4], size-y, x, -h, w);       // rotate 90 degrees clockwise with next colour
                drawR(cls[(c1+ 2) % 4], size-x, size-y, -w, -h);// rotate 90 degrees clockwise with next colour
                drawR(cls[(c1+ 3) % 4], y, size-x, h, -w);      // rotate 90 degrees clockwise with next colour
                dj = j+1;                                       // set startPoints for next iteration along x-axes
                dx += w;
            }
            bi = i+1;                                           // set startPoints for next iteration along y-axes
            by += h;
        }
    }

    function drawR(s, x, y, w, h) {                             // fill the rectangle with colour s
        ctx.fillStyle = s;
        ctx.fillRect(x, y, w, h);
    }

    function gcd(a, b) {                                        // greatest common divisor of a and b
        if (! (Number.isInteger(a) && Number.isInteger(b) && a > 0 && b > 0))
            return NaN;
        if (a > b) 
            return gcd(b,a);
        var m = b + ', ' + a;
        while (true) {
            const r = b % a;
            m += ', ' + r;
            if (r === 0)
                return a;
            b = a;
            a = r;
        }
    }

    function layoutDraw(dx, dy, fo) {                           // draw the canvas for the explanation of the layout
        const ar = document.getElementById("layout")
          , c = ar.getContext("2d")
        sz = dx + dy + 2 * fo
        ar.width = ar.height = sz;
        c.fillStyle = '#fcc'
        c.fillRect(0, 0, sz, sz);
        c.fillStyle = '#0a0'
        c.fillRect(fo, fo, dx, dy);
        c.fillStyle = '#8f8'
        c.fillRect(fo+dx, fo, dy, dx);
        c.fillRect(fo+dy, fo+dx, dx, dy);
        c.fillRect(fo, fo+dy, dy, dx);
        c.fillStyle = '#88f'
        c.fillRect(fo+dy, fo+dy, dx-dy, dx-dy);
        c.lineWidth = 2
        c.strokeRect(fo, fo, dx, dy);
        c.strokeRect(fo+dx, fo, dy, dx);
        c.strokeRect(fo+dy, fo+dx, dx, dy);
        c.strokeRect(fo, fo+dy, dy, dx);
        c.beginPath();
        c.arc(sz /2 , sz / 2, dx/2, - Math.PI / 2, Math.PI);
        c.stroke()
        c.fillStyle = 'black'
        let x = sz/2
          , y = fo + dy /2
          , [q, r, s, t] = [x-8, y-5, x-10, y+9]
          ;
        c.beginPath();
        for (let n=0; n < 4; n++) {
            c.moveTo(x, y);
            c.lineTo(q, r);
            c.lineTo(s, t);
            c.closePath();
            [x, y, q, r, s, t] = [sz-y, x, sz-r, q, sz-t, s]
        }
        c.fill();
    }

</script>
</html>