collatzConjecture: initial adding

clusterFilter: fixing package.json
This commit is contained in:
Sheldan
2025-08-08 23:45:28 +02:00
parent 8ef5e61588
commit 7337a4c917
12 changed files with 1237 additions and 604 deletions

View File

@@ -0,0 +1,60 @@
<!doctype html>
<html class="no-js" lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>collatzConjecture</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
html, body, div, canvas {
margin: 0;
padding: 0;
}
html, body { width:100%; height:100%; }
canvas { display:block; }
/*.controls {
position: absolute;
top: 0px;
right: 10px
}*/
</style>
</head>
<body>
<label for="heightChange">Size of tree</label>
<input type="range" value="25" min="1" max="50" id="heightChange" oninput="setNewHeightChange(this.value)">
<label for="spread">Spread</label>
<input type="range" value="5" min="1" id="spread" oninput="setNewSpread(this.value)" max="30">
<label for="firstNumber">First number</label>
<input type="range" value="2" min="1" max="50" id="firstNumber" oninput="setNewFirstNumber(this.value)">
<label for="secondNumber">Second number</label>
<input type="range" value="3" min="1" id="secondNumber" oninput="setNewSecondNumber(this.value)" max="50">
<label for="size">Canvas Size:</label>
<input type="number" id="size" max="10000" onblur="updateCanvasConfig(this.value)">
<input type="button" value="reinitialize colors" onclick="generateColors(); updateCanvas()">
<br>
Explanation of first and second number:
The typical conjecture is in the form of: 3n + 1 with the property of dividing by two if odd etc.... (Explanation <a href="https://en.wikipedia.org/wiki/Collatz_conjecture">here</a>). <br> The 'firstNumber' is the 2 in the formula and the 'secondNumber' the 3.
Beware of ... interesting outcomes as this can cause the created tree to become very large... often too much for the browser.
<br>
<span>
Height:
<span id="heightValue"></span>
Spread:
<span id="spreadValue"></span>
Firstnumber:
<span id="firstValue"></span>
Secondnumber:
<span id="secondValue"></span>
</span>
<br>
<a id="download" onclick="downloadCanvasCommon('collatzConjecture', canvas)">Download</a>
<br>
<canvas id="canvas"></canvas>
<script type="module" src="js/main.js"></script>
</body>
</html>

View File

@@ -0,0 +1,242 @@
import {
docReady, d2h, toRad, downloadCanvasWithoutButton
} from "canvas-common";
var ctx = {};
var canvas = {};
var used = {};
var colors = [];
var collatzTree;
var heightChange;
var sizeInput;
var spreadInput;
var heightValue;
var spreadValue;
var firstValue;
var secondValue;
var startCol = {r: 0, g: 0, b: 0};
var finalCol = {r: 0, g: 0, b: 0};
var colorPoints = [];
function setToPinkishColors() {
colorPoints.push({r: 38, g: 17, b: 43});
colorPoints.push({r: 86, g: 37, b: 81});
colorPoints.push({r: 132, g: 60, b: 107});
colorPoints.push({r: 171, g: 88, b: 126});
colorPoints.push({r: 202, g: 122, b: 142});
colorPoints.push({r: 224, g: 159, b: 161});
colorPoints.push({r: 238, g: 198, b: 189});
colorPoints.push({r: 249, g: 237, b: 229});
finalCol = colorPoints[colorPoints.length - 1];
}
var config = {
size: {
size: 1500
},
conjectureConfig: {
firstNumber: 2,
secondNumber: 3,
maxDepth: 30,
heightChange: 25,
dirChange: 10
}
};
function buildStructure(input, depth) {
if (depth > config.conjectureConfig.maxDepth) return undefined;
if (input in used) {
return undefined
}
depth++;
used[input] = 1;
var element = {};
element.num = input;
var child1 = buildStructure(input * config.conjectureConfig.firstNumber, depth);
var child2 = undefined;
var inputSmaller = input - 1;
var probableNumber = (inputSmaller) / config.conjectureConfig.secondNumber;
if ((inputSmaller) % config.conjectureConfig.secondNumber == 0 && (probableNumber % config.conjectureConfig.firstNumber != 0)) {
child2 = buildStructure(probableNumber, depth);
}
element.child1 = child1;
element.child2 = child2;
return element;
}
function setNewHeightChange(newValue) {
config.conjectureConfig.heightChange = parseInt(newValue);
heightValue.innerText = config.conjectureConfig.heightChange;
updateCanvas();
}
function setNewSpread(newValue) {
config.conjectureConfig.dirChange = parseInt(newValue);
spreadValue.innerText = config.conjectureConfig.dirChange;
updateCanvas();
}
// TODO fix with different amount of colors
function drawElement(input, dir, parentPos, depth) {
if (depth >= config.conjectureConfig.maxDepth || input in used) return;
//console.log('__________________')
ctx.beginPath();
ctx.moveTo(parentPos.x, parentPos.y);
var numberOfStepsEachColor = Math.round(config.conjectureConfig.maxDepth / colorPoints.length);
// often... this returns a higher color, but the ratio is still in above 0.5, so the color changes drastically
var whereToMoveColorWise = (~~((depth) / config.conjectureConfig.maxDepth * 0.995 * colorPoints.length) + 1);
var ratio = (depth % (numberOfStepsEachColor)) / ((numberOfStepsEachColor));
// sometimes a color to high is chosen with a high ratio, this causes the color change to be wrong (and very drastic)
// we need to go a step back
if (ratio >= 1) {
whereToMoveColorWise--;
}
else if (ratio > 0.5) {
whereToMoveColorWise = (~~((depth - 1) / config.conjectureConfig.maxDepth * 0.995 * colorPoints.length) + 1);
}
var targetColor = colorPoints[whereToMoveColorWise];
if (whereToMoveColorWise == colorPoints.length) {
targetColor = finalCol;
}
var whereWeComeColorWise = whereToMoveColorWise - 1;
//console.log(whereWeComeColorWise + '_' + whereToMoveColorWise)
//console.log(depth + '_' + numberOfStepsEachColor)
//console.log(ratio + '_' + depth / config.conjectureConfig.maxDepth)
var baseColor = colorPoints[whereWeComeColorWise];
var newColor = {
r: targetColor.r * ratio + baseColor.r * (1 - ratio),
g: targetColor.g * ratio + baseColor.g * (1 - ratio),
b: targetColor.b * ratio + baseColor.b * (1 - ratio)
};
ctx.strokeStyle = '#' + d2h(~~newColor.r) + d2h(~~newColor.g) + d2h(~~newColor.b);
var newXOffset = config.conjectureConfig.heightChange * Math.cos(toRad(dir));
//console.log(ctx.strokeStyle)
//console.log(newColor)
//console.log(baseColor)
//console.log(targetColor)
var newYOffset = config.conjectureConfig.heightChange * Math.sin(toRad(dir));
//ctx.fillText((ctx.strokeStyle) + ' ', parentPos.x, parentPos.y);
ctx.lineTo(parentPos.x + newXOffset, parentPos.y + newYOffset);
ctx.stroke();
// experiment to use two parallel lines represented in the video, doesn't work that well, because the line can cross the other line and then... stuff happens
// also lines overlay each other
//ctx.strokeStyle = colors[depth + 1];
//ctx.moveTo(parentPos.x - Math.cos(toRad(90 - dir)) * config.width / 2, parentPos.y - Math.sin(toRad(90 - dir)) * config.width / 2);
//ctx.lineTo(parentPos.x + config.heightChange * Math.cos(toRad(dir)) - Math.cos(toRad(90 - dir)) * config.width / 2,
// parentPos.y + config.heightChange * Math.sin(toRad(dir)) - Math.sin(toRad(90 - dir)) * config.width / 2);
//
//ctx.moveTo(parentPos.x + Math.cos(toRad(90 - dir)) * config.width / 2, parentPos.y + Math.sin(toRad(90 - dir)) * config.width / 2);
//ctx.lineTo(parentPos.x + config.heightChange * Math.cos(toRad(dir)) + Math.cos(toRad(90 - dir)) * config.width / 2,
// parentPos.y + config.heightChange * Math.sin(toRad(dir)) + Math.sin(toRad(90 - dir)) * config.width / 2);
//ctx.stroke();
var newParentPos = {
x: parentPos.x + newXOffset,
y: parentPos.y + newYOffset
};
depth++;
used[input] = 1;
drawElement(input * config.conjectureConfig.firstNumber, dir + config.conjectureConfig.dirChange, newParentPos, depth);
var inputSmaller = input - 1;
var probableNumber = (inputSmaller) / config.conjectureConfig.secondNumber;
if ((inputSmaller) % config.conjectureConfig.secondNumber == 0 && (probableNumber % config.conjectureConfig.firstNumber != 0)) {
drawElement(probableNumber, dir - config.conjectureConfig.dirChange, newParentPos, depth);
}
else {
ctx.stroke();
}
}
function generateColors() {
colors = [];
colorPoints = [];
colorPoints.push(startCol);
var maxInc = (255 / config.conjectureConfig.maxDepth);
for (var i = 0; i < 3; i++) {
var red = ~~(Math.random() * 255);
var green = ~~(Math.random() * 255);
var blue = ~~(Math.random() * 255);
//colors.push('#' + (red).toString(16) + (green).toString(16) + (blue).toString(16));
colorPoints.push({r: red, g: green, b: blue});
}
}
function updateCanvasConfig(newValue) {
config.size.size = parseInt(newValue);
initCanvas();
updateCanvas();
}
function updateCanvas() {
used = {};
ctx.clearRect(0, 0, config.size.size, config.size.size);
drawElement(1, -180, config.size, 0);
}
function setNewFirstNumber(newVal) {
config.conjectureConfig.firstNumber = parseInt(newVal);
firstValue.innerText = config.conjectureConfig.firstNumber;
updateCanvas();
}
function setNewSecondNumber(newVal) {
config.conjectureConfig.secondNumber = parseInt(newVal);
secondValue.innerText = config.conjectureConfig.secondNumber;
updateCanvas();
}
function initCanvas() {
ctx = canvas.getContext("2d");
canvas.height = config.size.size;
canvas.width = config.size.size;
config.size.x = config.size.size / 2;
config.size.y = config.size.size / 2;
}
docReady(function () {
canvas = document.getElementById('canvas');
initCanvas();
ctx.font = "8px Verdana";
ctx.lineWidth = 1;
sizeInput = document.getElementById('size');
heightChange = document.getElementById('heightChange');
spreadInput = document.getElementById('spread');
heightValue = document.getElementById('heightValue');
heightValue.innerText = config.conjectureConfig.heightChange;
spreadValue = document.getElementById('spreadValue');
spreadValue.innerText = config.conjectureConfig.dirChange;
firstValue = document.getElementById('firstValue');
firstValue.innerText = config.conjectureConfig.firstNumber;
secondValue = document.getElementById('secondValue');
secondValue.innerText = config.conjectureConfig.secondNumber;
sizeInput.value = config.size.size;
spreadInput.value =config.conjectureConfig.dirChange;
heightChange.value = config.conjectureConfig.heightChange;
setToPinkishColors();
//generateColors();
drawElement(1, -180, config.size, 0);
//initStructure();
});
function downloadCanvasCollatz() {
downloadCanvasWithoutButton('collatzConjecture', canvas);
}
window.setNewHeightChange = setNewHeightChange;
window.setNewSpread = setNewSpread;
window.setNewFirstNumber = setNewFirstNumber;
window.setNewSecondNumber = setNewSecondNumber;
window.updateCanvasConfig = updateCanvasConfig;
window.generateColors = generateColors;
window.updateCanvas = updateCanvas;
window.downloadCanvasCommon = downloadCanvasCollatz;

View File

@@ -0,0 +1,24 @@
// Avoid `console` errors in browsers that lack a console.
(function() {
var method;
var noop = function () {};
var methods = [
'assert', 'clear', 'count', 'debug', 'dir', 'dirxml', 'error',
'exception', 'group', 'groupCollapsed', 'groupEnd', 'info', 'log',
'markTimeline', 'profile', 'profileEnd', 'table', 'time', 'timeEnd',
'timeline', 'timelineEnd', 'timeStamp', 'trace', 'warn'
];
var length = methods.length;
var console = (window.console = window.console || {});
while (length--) {
method = methods[length];
// Only stub undefined methods.
if (!console[method]) {
console[method] = noop;
}
}
}());
// Place any jQuery/helper plugins in here.