<!-- index.html -->
<body>
  <canvas></canvas>
</body>

body: {
	width: 100%;
}

canvas {
	display: block;
	margin: 50px auto;
}
const chaikin = ({ points, step, isClosed = false }) => {
	if (step <= 0) {
		return { points, isClosed };
	}
	

	const result = [];

	// use the isClosed variable to determine the max for loop index
	const maxIndex = isClosed ? points.length : points.length - 1;
	for (let i = 0; i < maxIndex; i++) {
		const pointA = points[i];
		const pointB = points[(i + 1) % points.length];

		const [xA, yA] = pointA;
		const [xB, yB] = pointB;

		// 25% in point's x and y
		const x1 = 0.25 * xB + 0.75 * xA;
		const y1 = 0.25 * yB + 0.75 * yA;

		// 75% in point's x and y
		const x2 = 0.75 * xB + 0.25 * xA;
		const y2 = 0.75 * yB + 0.25 * yA;

		result.push([x1, y1]);
		result.push([x2, y2]);
	}

	// pass the isClosed value to any future calls
	return chaikin({ points: result, step: step - 1, isClosed });
};

const getBounds = (points) => {
	let minX, minY, maxX, maxY;

	if (!Array.isArray(points) || !points.length) {
		throw "Expected an array with more than zero elements";
	}

	points.forEach(([x, y]) => {
		// set new minimum if value:
		//  1. doesn't exist (but isn't 0)
		//  2. is less than the current minimum
		if ((!minX && minX !== 0) || x <= minX) {
			minX = x;
		}

		// set new maximum if value:
		//  1. doesn't exist (but isn't 0)
		//  2. is more than the current maximum
		if ((!maxX && maxX !== 0) || x >= maxX) {
			maxX = x;
		}

		if ((!minY && minY !== 0) || y <= minY) {
			minY = y;
		}
		if ((!maxY && maxY !== 0) || y >= maxY) {
			maxY = y;
		}
	});

	return {
		min: [minX, minY],
		max: [maxX, maxY]
	};
};

// Set up the canvas
var canvas = document.querySelector("canvas");
var context = canvas.getContext("2d");

// set the size to smaller of window width or height
var size = Math.min(window.innerWidth, window.innerHeight);

canvas.width = size - 100;
canvas.height = size - 100;

// draw a gray rectangle covering the entire canvas
context.rect(0, 0, canvas.width, canvas.height);
context.fillStyle = "rgba(0,0,0, 0.1)";
context.fill();

// create random points
const createRandomPoint = ({ x, y, width, height }) => {
	const randomX = x + Math.random() * width;
	const randomY = y + Math.random() * height;
	return [Math.floor(randomX), Math.floor(randomY)];
};


const points = [];

// NEW! Randomize the number of points in the curve
const pointsMinimum = 5
const pointsVariance = 5
const pointsCount = pointsMinimum + Math.floor(Math.random() * pointsVariance)

for (let i = 0; i < pointsCount; i++) {
	points.push(
		createRandomPoint({
			x: 0,
			y: 0,
			width: canvas.width,
			height: canvas.height
		})
	);
	
}

// NEW! Add an isClosed random property to the chaikin call
const { points: chaikinPoints, isClosed: isChaikinClosed } = chaikin({ points, step: 5, isClosed: Math.random() > 0.5  });


// use the getBounds function to determine the height, width, and center
// of the chaikin curve
const { min: chaikinMin, max: chaikinMax } = getBounds(chaikinPoints);
const [chaikinMinX, chaikinMinY] = chaikinMin;
const [chaikinMaxX, chaikinMaxY] = chaikinMax;
const chaikinWidth = chaikinMaxX - chaikinMinX;
const chaikinHeight = chaikinMaxY - chaikinMinY;
const chaikinCenterX = chaikinMinX + chaikinWidth / 2;
const chaikinCenterY = chaikinMinY + chaikinHeight / 2;

// these values need to correspond to the x, y, width and height
// values we used to create the random points.
const outerBoxCenterX = 0 + canvas.width / 2;
const outerBoxCenterY = 0 + canvas.height / 2;

// find the difference between the outerBoxCenter and chaikinCenter
const xShiftForChaikin = outerBoxCenterX - chaikinCenterX;
const yShiftForChaikin = outerBoxCenterY - chaikinCenterY;

// shift the chaikin curve points by the required shift to center them
const shiftedChaikinPoints = chaikinPoints.map(([x, y]) => {
	return [x + xShiftForChaikin, y + yShiftForChaikin];
});

// draw the new, shifted chaikin curve
context.beginPath();
context.moveTo(shiftedChaikinPoints[0][0], shiftedChaikinPoints[0][1]);
shiftedChaikinPoints.forEach(([x, y]) => {
	context.lineTo(x, y);
});
// NEW! connect back to first point if is a closed curve
if (isChaikinClosed) {
	context.lineTo(shiftedChaikinPoints[0][0], shiftedChaikinPoints[0][1]);
}
context.lineWidth = 5;
context.stroke();

Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.