AI Spirograph Generator - Geometric Pattern Art Create intricate hypotrochoid patterns with adjusta

55 views · Dec 12, 2025

Create an "AI Spirograph Generator" - digital spirograph that draws intricate geometric patterns. Us

#xelsedia#xelsedai#xelsedp5flashalpha6
🔀 Remix This 📖 View Docs ⛶ Full Screen ← Back to Gallery
📋 Embed Code

Copy this code to embed this sketch on your website:

<iframe src="https://p5js.ai/embed/8b09e214-ad1a-4ebc-ba09-8482bc989a42" width="400" height="400" frameborder="0" allow="autoplay" title="AI Spirograph Generator - Geometric Pattern Art Create intricate hypotrochoid patterns with adjusta"></iframe>

Adjust width and height as needed. Preview embed →

❓ About This Sketch 3 questions

What type of visual patterns does the AI Spirograph Generator create?

The AI Spirograph Generator creates intricate hypotrochoid patterns, resulting in beautiful geometric designs with a rainbow gradient.

How can users customize their experience with the Spirograph sketch?

Users can interact with the sketch by adjusting sliders for the outer radius, inner radius, and pen distance to create unique designs.

What creative coding concepts are demonstrated in this p5.js sketch?

This sketch demonstrates the use of hypotrochoid equations for generating geometric patterns and interactive UI elements using p5.js.

💬 AI Chat History 4 messages
👤 You
Create an "AI Spirograph Generator" - digital spirograph that draws intricate geometric patterns. Users adjust inner/outer ring radii and pen position with sliders. The AI suggests interesting ratio combinations. Include rainbow color cycling along the path, adjustable drawing speed, and clear canvas button. Visual style: colorful geometric curves on dark background with smooth anti-aliased lines.
🤖 AI
Here’s a complete “AI Spirograph Generator” built in p5.js 1.11.3 with sliders, AI pattern suggestions, rainbow coloring, speed control, and a clear button.

---

```html
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0"
/>
<title>AI Spirograph Generator</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<!-- p5.js core library -->
<script src="https://cdn.jsdelivr.net/npm/p5@1.11.3/lib/p5.min.js"></script>
<!-- main sketch -->
<script src="sketch.js"></script>
</body>
</html>
```

---

```css
/* style.css */
body {
margin: 0;
padding: 0;
overflow: hidden;
background: #050509;
color: #f5f5f5;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
sans-serif;
}

/* p5 canvas */
canvas {
display: block;
position: fixed;
inset: 0;
z-index: 0;
}

/* UI panel */
#ui-panel {
position: absolute;
top: 12px;
left: 12px;
max-width: 320px;
padding: 12px 14px 10px;
border-radius: 10px;
background: rgba(10, 10, 25, 0.92);
color: #ffffff;
font-size: 12px;
box-shadow: 0 12px 30px rgba(0, 0, 0, 0.5);
z-index: 10;
backdrop-filter: blur(8px);
}

#ui-panel h1 {
margin: 0 0 4px;
font-size: 16px;
font-weight: 600;
}

#ui-panel .subtitle {
margin: 0 0 8px;
font-size: 11px;
line-height: 1.4;
color: #c0c4ff;
}

/* Slider groups */
.control-group {
margin-bottom: 8px;
}

.control-header {
display: flex;
justify-content: space-between;
align-items: baseline;
margin-bottom: 2px;
}

.control-label {
font-size: 11px;
color: #e0e3ff;
}

.control-value {
font-size: 11px;
color: #8fe3ff;
}

/* Sliders */
.control-group input[type="range"] {
width: 100%;
cursor: pointer;
accent-color: #ff6fd8;
}

/* Buttons */
.button-row {
margin-top: 6px;
display: flex;
gap: 6px;
flex-wrap: wrap;
}

.ui-button {
border: 1px solid #5050c0;
background: linear-gradient(135deg, #272759, #191932);
color: #f5f5ff;
padding: 6px 10px;
border-radius: 999px;
font-size: 11px;
cursor: pointer;
white-space: nowrap;
transition: background 0.15s ease, transform 0.1s ease,
box-shadow 0.15s ease, border-color 0.15s ease;
}

.ui-button:hover {
background: linear-gradient(135deg, #35358a, #202044);
border-color: #7b7bff;
box-shadow: 0 0 10px rgba(123, 123, 255, 0.4);
transform: translateY(-1px);
}

.ui-button:active {
transform: translateY(0);
box-shadow: 0 0 4px rgba(123, 123, 255, 0.3);
}
```

---

```js
// sketch.js
// AI Spirograph Generator
// Uses hypotrochoid (inner circle rolling inside outer circle)
// Colorful curves on dark background with rainbow gradient along the path.
//
// Key p5 APIs used:
// - createCanvas, resizeCanvas, line, cos, sin: https://p5js.org/reference/#/p5/line
// - createSlider, createButton, createDiv: https://p5js.org/reference/#/p5/createSlider
// - colorMode(HSB) for rainbow colors: https://p5js.org/reference/#/p5/colorMode
// - smooth() for anti-aliased drawing: https://p5js.org/reference/#/p5/smooth

let outerRadiusSlider, innerRadiusSlider, penOffsetSlider, speedSlider;
let outerRadiusValueSpan, innerRadiusValueSpan, penOffsetValueSpan, speedValueSpan;
let aiButton, clearButton, uiPanel;

let outerR = 200;
let innerR = 80;
let penOffset = 60;

let theta = 0;
let thetaMax = 0;
const dThetaBase = 0.01; // radians per segment

let lastX, lastY;
let centerX, centerY;
let hueOffset = 0;

function setup() {
createCanvas(windowWidth, windowHeight);
frameRate(60);

// HSB color space for easy hue cycling
colorMode(HSB, 360, 100, 100, 100);
background(0, 0, 5); // near-black
smooth();
strokeWeight(2);
strokeCap(ROUND);
strokeJoin(ROUND);
noFill();

createUI();
restartFromSliders();
}

function createUI() {
uiPanel = createDiv();
uiPanel.id("ui-panel");

const title = createElement("h1", "AI Spirograph Generator");
title.parent(uiPanel);

const subtitle = createP(
"Adjust radii, pen position, and speed. Use “AI Suggest” for interesting ratios."
);
subtitle.addClass("subtitle");
subtitle.parent(uiPanel);

const maxOuter = floor(min(width, height) / 2 - 40);
const maxInner = maxOuter - 20;

const outerControl = createLabeledSlider(
"Outer radius",
50,
maxOuter,
floor(maxOuter * 0.7),
1,
uiPanel,
() => restartFromSliders()
);
outerRadiusSlider = outerControl.slider;
outerRadiusValueSpan = outerControl.valueSpan;

const innerControl = createLabeledSlider(
"Inner radius",
10,
maxInner,
floor(maxOuter * 0.35),
1,
uiPanel,
() => restartFromSliders()
);
innerRadiusSlider = innerControl.slider;
innerRadiusValueSpan = innerControl.valueSpan;

const penControl = createLabeledSlider(
"Pen offset",
1,
maxInner,
floor(maxOuter * 0.25),
1,
uiPanel,
() => restartFromSliders()
);
penOffsetSlider = penControl.slider;
penOffsetValueSpan = penControl.valueSpan;

const speedControl = createLabeledSlider(
"Drawing speed",
0, // 0 = paused
20,
6,
1,
uiPanel,
null // speed doesn't need full restart
);
speedSlider = speedControl.slider;
speedValueSpan = speedControl.valueSpan;

const buttonRow = createDiv();
buttonRow.addClass("button-row");
buttonRow.parent(uiPanel);

aiButton = createButton("AI Suggest pattern");
aiButton.addClass("ui-button");
aiButton.parent(buttonRow);
aiButton.mousePressed(aiSuggestPattern);

clearButton = createButton("Clear canvas");
clearButton.addClass("ui-button");
clearButton.parent(buttonRow);
clearButton.mousePressed(() => {
restartFromSliders();
});
}

// Helper to build labeled sliders with value readout
function createLabeledSlider(
labelText,
min,
max,
initial,
step,
parent,
onChange
) {
const group = createDiv();
group.addClass("control-group");
group.parent(parent);

const header = createDiv();
header.addClass("control-header");
header.parent(group);

const label = createSpan(labelText);
label.addClass("control-label");
label.parent(header);

const valueSpan = createSpan(String(initial));
valueSpan.addClass("control-value");
valueSpan.parent(header);

const slider = createSlider(min, max, initial, step);
slider.parent(group);
slider.input(() => {
valueSpan.html(slider.value());
if (onChange) onChange();
});

return { slider, valueSpan };
}

// Reset drawing when parameters change
function restartFromSliders() {
outerR = Number(outerRadiusSlider.value());
innerR = Number(innerRadiusSlider.value());
penOffset = Number(penOffsetSlider.value());

const margin = 10;

// Enforce inner < outer
if (innerR >= outerR - margin) {
innerR = outerR - margin;
innerRadiusSlider.value(innerR);
innerRadiusValueSpan.html(innerR);
}

// Pen must stay inside inner circle
const penMax = max(1, innerR - 5);
if (penOffset > penMax) {
penOffset = penMax;
penOffsetSlider.value(penOffset);
penOffsetValueSpan.html(penOffset);
}
penOffsetSlider.attribute("max", penMax);

// Update slider maxes if window changed
const maxOuter = floor(min(width, height) / 2 - 40);
const maxInner = maxOuter - 20;
outerRadiusSlider.attribute("max", maxOuter);
innerRadiusSlider.attribute("max", maxInner);

if (outerR > maxOuter) {
outerR = maxOuter;
outerRadiusSlider.value(outerR);
outerRadiusValueSpan.html(outerR);
}
if (innerR > maxInner) {
innerR = maxInner;
innerRadiusSlider.value(innerR);
innerRadiusValueSpan.html(innerR);
}

centerX = width / 2;
centerY = height / 2;

// Clear canvas
background(0, 0, 5);
theta = 0;
hueOffset = random(360);

// Estimate angle needed for a full closed curve
const Rint = max(1, round(outerR));
const rint = max(1, round(innerR));
const g = gcd(rint, Rint - rint);
thetaMax = g > 0 ? TWO_PI * (rint / g) : TWO_PI * 40; // fallback

// Initial point
const p = spiroPoint(theta, outerR, innerR, penOffset);
lastX = centerX + p.x;
lastY = centerY + p.y;
}

function draw() {
const speed = Number(speedSlider.value());
speedValueSpan.html(speed);

if (speed <= 0) {
// paused
return;
}

if (theta >= thetaMax) {
// finished current pattern
return;
}

const stepsPerFrame = speed;
const stepSize = dThetaBase;

for (let i = 0; i < stepsPerFrame && theta < thetaMax; i++) {
const nextTheta = theta + stepSize;
const p = spiroPoint(nextTheta, outerR, innerR, penOffset);
const x2 = centerX + p.x;
const y2 = centerY + p.y;

// Rainbow color cycling along the curve
const progress = constrain(nextTheta / thetaMax, 0, 1);
const hue = (hueOffset + progress * 360) % 360;
stroke(hue, 90, 100, 100);

line(lastX, lastY, x2, y2);

theta = nextTheta;
lastX = x2;
lastY = y2;
}
}

// Hypotrochoid point (inner circle rolling inside outer circle)
function spiroPoint(t, R, r, d) {
const k = (R - r) / r;
const x = (R - r) * cos(t) + d * cos(k * t);
const y = (R - r) * sin(t) - d * sin(k * t);
return { x, y };
}

// "AI" suggestion: pick interesting integer ratios for R and r
function aiSuggestPattern() {
const maxOuter = Number(outerRadiusSlider.elt.max);
const minOuter = Number(outerRadiusSlider.elt.min);
const unit = 5; // quantize for nicer integer ratios

const minUnits = max(5, floor(minOuter / unit));
const maxUnits = max(minUnits + 1, floor(maxOuter / unit));

let outerUnits = floor(random(minUnits, maxUnits + 1));
outerUnits = max(outerUnits, 5);

// Choose inner units to get a moderate number of cusps
let innerUnits = null;
for (let attempts = 0; attempts < 50; attempts++) {
const candidate = floor(random(1, outerUnits - 1));
const g = gcd(outerUnits, candidate);
const cusps = outerUnits / g; // approximate number of lobes

if (cusps >= 3 && cusps <= 15) {
innerUnits = candidate;
break;
}
}
if (innerUnits === null) {
innerUnits = max(1, outerUnits - 2);
}

const R = outerUnits * unit;
const r = innerUnits * unit;

// Pen offset as a fraction of inner radius for variety
const penRatios = [0.3, 0.45, 0.6, 0.75, 0.9];
const penRatio = random(penRatios);
const d = max(1, floor(r * penRatio));

outerRadiusSlider.value(R);
outerRadiusValueSpan.html(R);
innerRadiusSlider.value(r);
innerRadiusValueSpan.html(r);
penOffsetSlider.value(d);
penOffsetValueSpan.html(d);

// Suggest a comfortable drawing speed
const suggestedSpeed = floor(random(5, 13));
speedSlider.value(suggestedSpeed);
speedValueSpan.html(suggestedSpeed);

restartFromSliders();
}

// Greatest common divisor (for finding closed curves)
function gcd(a, b) {
a = Math.abs(a);
b = Math.abs(b);
while (b !== 0) {
const temp = b;
b = a % b;
a = temp;
}
return a;
}

function windowResized() {
resizeCanvas(windowWidth, windowHeight);
restartFromSliders();
}
```

---

Usage notes:

- Adjust **Outer radius**, **Inner radius**, and **Pen offset** sliders to change the geometry.
- **Drawing speed** controls how many segments are drawn per frame; set to 0 to pause.
- **AI Suggest pattern** picks “interesting” integer ratios for radii and pen offset.
- **Clear canvas** resets and redraws the current slider settings from scratch.

This follows p5.js 1.11.3 best practices, using `createSlider`/`createButton` for UI and `colorMode(HSB)` for smooth rainbow gradients.
👤 You
Generate spirograph code now: use parametric equations x = (R-r)*cos(t) + d*cos((R-r)/r*t), y = (R-r)*sin(t) - d*sin((R-r)/r*t). Add sliders for outer radius R, inner radius r, and pen distance d. Draw the curve with rainbow HSB colors cycling along the path. Dark background, smooth lines.
🤖 AI
```html
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Spirograph (p5.js)</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<!-- p5.js core library -->
<script src="https://cdn.jsdelivr.net/npm/p5@1.11.3/lib/p5.min.js"></script>
<!-- main sketch -->
<script src="sketch.js"></script>
</body>
</html>
```

---

```css
/* style.css */
body {
margin: 0;
padding: 0;
overflow: hidden;
background: #050509; /* dark background */
color: #f5f5f5;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
sans-serif;
}

/* p5 canvas */
canvas {
display: block;
}

/* UI panel */
#controls {
position: absolute;
top: 10px;
left: 10px;
padding: 8px 10px;
border-radius: 8px;
background: rgba(5, 5, 20, 0.9);
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.5);
font-size: 12px;
z-index: 10;
backdrop-filter: blur(6px);
}

#controls h1 {
margin: 0 0 4px;
font-size: 14px;
font-weight: 600;
}

.control-row {
margin-bottom: 6px;
}

.control-row span.label {
display: inline-block;
min-width: 85px;
color: #d0d4ff;
}

.control-row span.value {
color: #8fe3ff;
margin-left: 4px;
}

.control-row input[type="range"] {
width: 100%;
margin-top: 2px;
cursor: pointer;
accent-color: #ff6fd8;
}
```

---

```js
// sketch.js
// Spirograph using hypotrochoid equations:
//
// x(t) = (R - r) * cos(t) + d * cos((R - r)/r * t)
// y(t) = (R - r) * sin(t) - d * sin((R - r)/r * t)
//
// Sliders for R (outer radius), r (inner radius), and d (pen distance).
// Curve is drawn with a rainbow gradient in HSB space on a dark background.
//
// Reference for line(), cos(), sin(), colorMode():
// https://p5js.org/reference/#/p5/line
// https://p5js.org/reference/#/p5/colorMode

let RSlider, rSlider, dSlider;
let RValSpan, rValSpan, dValSpan;
let controlsDiv;

function setup() {
createCanvas(windowWidth, windowHeight);
colorMode(HSB, 360, 100, 100, 100);
smooth(); // ensure anti-aliased drawing
noFill();
frameRate(60);

createUI();
}

function createUI() {
controlsDiv = createDiv();
controlsDiv.id("controls");

const title = createElement("h1", "Spirograph");
title.parent(controlsDiv);

// Reasonable defaults based on window size
const maxR = floor(min(width, height) / 2) - 40;
const initialR = max(80, floor(maxR * 0.6));
const initialr = floor(initialR * 0.45);
const initiald = floor(initialr * 0.75);

// Outer radius R
({ slider: RSlider, valueSpan: RValSpan } = createLabeledSlider(
"Outer R",
40,
maxR,
initialR,
1
));

// Inner radius r
({ slider: rSlider, valueSpan: rValSpan } = createLabeledSlider(
"Inner r",
10,
maxR - 10,
initialr,
1
));

// Pen distance d
({ slider: dSlider, valueSpan: dValSpan } = createLabeledSlider(
"Pen d",
1,
maxR,
initiald,
1
));
}

// Helper to build a slider row with label + numeric value
function createLabeledSlider(labelText, min, max, initial, step) {
const row = createDiv();
row.addClass("control-row");
row.parent(controlsDiv);

const label = createSpan(labelText);
label.addClass("label");
label.parent(row);

const valueSpan = createSpan(String(initial));
valueSpan.addClass("value");
valueSpan.parent(row);

const slider = createSlider(min, max, initial, step);
slider.parent(row);
slider.input(() => {
valueSpan.html(slider.value());
});

return { slider, valueSpan };
}

function draw() {
// Dark, slightly non‑black background
background(0, 0, 5, 100);

// Read parameters from sliders
const R = Number(RSlider.value());
let r = Number(rSlider.value());
const d = Number(dSlider.value());

// Keep r positive and smaller than R to avoid division problems
if (r < 1) r = 1;
if (r >= R) r = R - 1;

// Update value spans in case we clamped r
rValSpan.html(nfc(r, 0));

// Compute angle range for a closed hypotrochoid:
// tMax = 2π * r / gcd(r, |R - r|)
const Rint = max(1, round(R));
const rint = max(1, round(r));
const diff = abs(Rint - rint);
let loops = 1;
if (diff > 0) {
const g = gcd(rint, diff);
loops = rint / g;
}
const tMax = TWO_PI * loops;

// Resolution: more loops → more steps
let steps = floor(loops * 800);
steps = constrain(steps, 1500, 12000);

push();
translate(width / 2, height / 2);
strokeWeight(2);

for (let i = 0; i < steps; i++) {
const t1 = map(i, 0, steps, 0, tMax);
const t2 = map(i + 1, 0, steps, 0, tMax);

const p1 = spiroPoint(t1, R, r, d);
const p2 = spiroPoint(t2, R, r, d);

// Rainbow along the path: hue wraps from 0..360
const hue = map(i, 0, steps, 0, 360);
stroke(hue, 90, 100, 100);

line(p1.x, p1.y, p2.x, p2.y);
}

pop();
}

// Spirograph / hypotrochoid parametric equations
function spiroPoint(t, R, r, d) {
const k = (R - r) / r;
const x = (R - r) * cos(t) + d * cos(k * t);
const y = (R - r) * sin(t) - d * sin(k * t);
return { x, y };
}

// Greatest common divisor
function gcd(a, b) {
a = abs(a);
b = abs(b);
while (b !== 0) {
const temp = b;
b = a % b;
a = temp;
}
return a;
}

function windowResized() {
resizeCanvas(windowWidth, windowHeight);

// Update slider maxes based on new canvas size
const maxR = floor(min(width, height) / 2) - 40;
RSlider.attribute("max", maxR);
rSlider.attribute("max", maxR - 10);
dSlider.attribute("max", maxR);
}
```
🎬 Video Preview