You can draw figures & measure its Minkowski–Bouligand dimension.
About fractal dimension: See Minkowski–Bouligand dimension
| license: mit |
You can draw figures & measure its Minkowski–Bouligand dimension.
About fractal dimension: See Minkowski–Bouligand dimension
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <meta charset="utf-8"> | |
| <title>Fractal Dimension Measurement</title> | |
| <style media="screen"> | |
| body { | |
| font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; | |
| position: relative; | |
| } | |
| canvas { | |
| cursor: pointer; | |
| } | |
| form { | |
| position: absolute; | |
| top: 1em; | |
| left: 1em; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <canvas></canvas> | |
| <form> | |
| <div> | |
| Dim ≈ <input type="text" id="dimension-display" disabled="disabled"> | |
| <input type="button" id="measure-button" value="Measure"> | |
| <input type="button" id="clear-button" value="Clear"> | |
| <input type="button" id="line-button" value="Line"> | |
| <input type="button" id="circumference-button" value="Circumference"> | |
| <input type="button" id="rectangle-button" value="Rectangle"> | |
| <input type="button" id="circle-button" value="Circle"> | |
| <input type="button" id="sierpinski-button" value="Sierpinski Triangle"> | |
| <input type="button" id="random-walk-button" value="Random Walk"> | |
| </form> | |
| </body> | |
| <script> | |
| const $ = document.querySelector.bind(document); | |
| const width = 960; | |
| const height = 500; | |
| const drawView = $('canvas'); | |
| const drawViewContext = drawView.getContext('2d'); | |
| document.body.style.width = width; | |
| document.body.style.height = height; | |
| drawView.width = width; | |
| drawView.height = height; | |
| drawViewContext.lineWidth = 2; | |
| let clicked = false; | |
| let lastX; | |
| let lastY; | |
| drawView.addEventListener('mousedown', (e) => { | |
| clicked = true; | |
| lastX = e.offsetX; | |
| lastY = e.offsetY; | |
| drawViewContext.fillRect(e.offsetX, e.offsetY, 1, 1); | |
| }); | |
| drawView.addEventListener('mouseup', (e) => { | |
| clicked = false; | |
| }); | |
| drawView.addEventListener('mouseout', (e) => { | |
| clicked = false; | |
| }); | |
| drawView.addEventListener('mousemove', (e) => { | |
| if (clicked) { | |
| drawViewContext.beginPath(); | |
| drawViewContext.moveTo(lastX, lastY); | |
| drawViewContext.lineTo(e.offsetX, e.offsetY); | |
| drawViewContext.stroke(); | |
| lastX = e.offsetX; | |
| lastY = e.offsetY; | |
| } | |
| }); | |
| const measureButton = $('#measure-button'); | |
| const dimensionDisplay = $('#dimension-display') | |
| measureButton.addEventListener('click', () => { | |
| const dimension = measure(); | |
| dimensionDisplay.value = dimension.toFixed(4); | |
| }); | |
| const clearButton = $('#clear-button'); | |
| clearButton.addEventListener('click', clear); | |
| function clear () { | |
| drawViewContext.clearRect(0, 0, width, height); | |
| } | |
| const lineButton = $('#line-button'); | |
| lineButton.addEventListener('click', () => { | |
| clear(); | |
| drawViewContext.beginPath(); | |
| drawViewContext.moveTo(100, height / 2); | |
| drawViewContext.lineTo(width - 100, height / 2); | |
| drawViewContext.stroke(); | |
| }); | |
| const circumferenceButton = $('#circumference-button'); | |
| circumferenceButton.addEventListener('click', () => { | |
| clear(); | |
| drawViewContext.beginPath(); | |
| drawViewContext.arc(width / 2, height / 2, 200, 0, 2 * Math.PI, false); | |
| drawViewContext.closePath(); | |
| drawViewContext.stroke(); | |
| }); | |
| const rectangleButton = $('#rectangle-button'); | |
| rectangleButton.addEventListener('click', () => { | |
| clear(); | |
| drawViewContext.fillRect(100, 100, width - 200, height - 200); | |
| }); | |
| const circleButton = $('#circle-button'); | |
| circleButton.addEventListener('click', () => { | |
| clear(); | |
| drawViewContext.beginPath(); | |
| drawViewContext.arc(width / 2, height / 2, 200, 0, 2 * Math.PI, false); | |
| drawViewContext.closePath(); | |
| drawViewContext.fill(); | |
| }); | |
| const sierpinskiButton = $('#sierpinski-button'); | |
| sierpinskiButton.addEventListener('click', () => { | |
| function triangle (x, y, size) { | |
| const xs = [ | |
| x + size * Math.cos(30 * Math.PI / 180), | |
| x + size * Math.cos(150 * Math.PI / 180), | |
| x + size * Math.cos(270 * Math.PI / 180) | |
| ]; | |
| const ys = [ | |
| y + size * Math.sin(30 * Math.PI / 180), | |
| y + size * Math.sin(150 * Math.PI / 180), | |
| y + size * Math.sin(270 * Math.PI / 180) | |
| ]; | |
| drawViewContext.beginPath(); | |
| drawViewContext.moveTo(xs[0], ys[0]); | |
| drawViewContext.lineTo(xs[1], ys[1]); | |
| drawViewContext.lineTo(xs[2], ys[2]); | |
| drawViewContext.closePath(); | |
| drawViewContext.fill(); | |
| } | |
| function drawSierpinski (x, y, size, depth) { | |
| if (depth <= 0) { | |
| triangle(x, y, size); | |
| return; | |
| } | |
| drawSierpinski( | |
| x + size / 2 * Math.cos(30 * Math.PI / 180), | |
| y + size / 2 * Math.sin(30 * Math.PI / 180), | |
| size / 2, | |
| depth - 1 | |
| ); | |
| drawSierpinski( | |
| x + size / 2 * Math.cos(150 * Math.PI / 180), | |
| y + size / 2 * Math.sin(150 * Math.PI / 180), | |
| size / 2, | |
| depth - 1 | |
| ); | |
| drawSierpinski( | |
| x + size / 2 * Math.cos(270 * Math.PI / 180), | |
| y + size / 2 * Math.sin(270 * Math.PI / 180), | |
| size / 2, | |
| depth - 1 | |
| ); | |
| }; | |
| clear(); | |
| drawSierpinski(width / 2, height / 2 + 75, 250, 8); | |
| }); | |
| const randomWalkButton = $('#random-walk-button'); | |
| randomWalkButton.addEventListener('click', () => { | |
| clear(); | |
| let x = width / 2; | |
| let y = height / 2; | |
| drawViewContext.beginPath(); | |
| drawViewContext.moveTo(width / 2, height / 2); | |
| for (let i = 0; i < 1000; i++) { | |
| x += Math.round(10 * (Math.random() * 2 - 1)); | |
| y += Math.round(10 * (Math.random() * 2 - 1)); | |
| drawViewContext.lineTo(x, y); | |
| } | |
| drawViewContext.stroke(); | |
| }); | |
| function countHitGrids (gridSize) { | |
| let count = 0; | |
| const image = drawViewContext.getImageData(0, 0, width, height); | |
| for (let y = 0; y < height; y += gridSize) { | |
| for (let x = 0; x < width; x += gridSize) { | |
| let hit = false; | |
| for (let dy = 0; dy < gridSize; dy++) { | |
| if (hit) { | |
| break; | |
| } | |
| for (let dx = 0; dx < gridSize; dx++) { | |
| if (hit) { | |
| break; | |
| } | |
| const index = width * (y + dy) + (x + dx); | |
| if (image.data[4 * index + 3] !== 0) { | |
| hit = true; | |
| } | |
| } | |
| } | |
| if (hit) { | |
| count++; | |
| } | |
| } | |
| } | |
| return count; | |
| } | |
| function measure () { | |
| const s1 = 10; | |
| const s2 = 5; | |
| return ( | |
| Math.log( | |
| countHitGrids(s1) / | |
| countHitGrids(s2) | |
| ) / | |
| Math.log(s2 / s1) | |
| ); | |
| } | |
| </script> | |
| </html> |