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"> ×
<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 + ' × ' + 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>