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> |