Building an Interactive Generative Art with p5.js, Tweakpane, and Watercolor Effects

Building an Interactive Generative Art with p5.js, Tweakpane, and Watercolor Effects
Watercolor abstract art we will build using p5.js

In the previous tutorial, we looked at how we can build an interactive piece of digital art using p5.js and we talked a bit about the math behind it.

Mastering Creative Coding: Building Interactive Radial Strokes with p5.js
Hello friends, and welcome to another creative coding tutorial! Today, we’re going to create an interactive piece of digital art using p5.js. You can interact with this artwork by changing parameters in the top right corner, and watch as the digital art transforms in real-time. Getting Started with JavaScript

This tutorial walks through creating a customizable generative art grid using p5.js, Tweakpane, and the p5.brush library for watercolor textures. Whether you're new to creative coding or looking to expand your generative art toolkit, this project blends structure, randomness, and interactivity to produce unique visual compositions.

We’ll also look at how this sketch became the foundation for an interactive artwork generator available at canvas.alexcodesart.com, where answering a few simple questions creates a one-of-a-kind digital artwork.


What We're Building

This project creates a grid of rectangles that can be styled with watercolor fills, hatching, and strokes. All visual aspects are tweakable through a user interface. It’s a perfect introduction to:

  • Generative art techniques
  • Creative coding with p5.js
  • Using Tweakpane for live parameter editing
  • Building with classes in JavaScript
  • Integrating p5.brush for painterly effects

It’s beginner-friendly and great for anyone learning to code through art.

Setting Up the Canvas

We start by creating a WEBGL canvas with high pixel density for crisp visuals:

function setup() {
  createCanvas(600, 600, WEBGL);
  pixelDensity(3);
  setupTweakpane();
}

WEBGL is used to support advanced drawing and p5.brush functionality. The brush setup allows us to achieve textures that mimic ink and watercolor.

Interactive Parameters with Tweakpane

Tweakpane provides a clean UI to adjust parameters like grid size, padding, margins, fill strategy, and visual styles like stroke and hatch.

Here's a snippet showing how we allow the user to choose color variation style:

pane.addInput(params, "colorVariation", {
  options: {
    Tint: COLOR_VARIATION_TINT,
    Shade: COLOR_VARIATION_SHADE,
  },
});

Other customizable parameters include number of rows and columns, padding, margin, fill strategies (random, row, column), and visual enhancements like stroke weight and hatch angle.

pane.addInput(params, "rows", { min: 1, max: 30, step: 1 });
pane.addInput(params, "cols", { min: 1, max: 30, step: 1 });
pane.addInput(params, "strokeStyleColor", { view: "color" });

Every change triggers a redraw by setting parametersChanged = true, ensuring instant visual feedback.

The Grid Class: Structure Meets Style

The Grid class is the heart of the sketch. It handles layout, color assignment, sizing logic, and rendering. Here's how it works:

Grid Initialization

constructor(width, height, rows, cols) {
  this.width = width;
  this.height = height;
  this.rows = rows;
  this.cols = cols;
  this.#occupied = Array(rows).fill().map(() => Array(cols).fill(false));
}

We use an internal #occupied array to manage which grid cells are already taken when shapes span multiple cells.

Setting Fill Color

getColorFill(i, j) {
  if (this.colorFillStrategy === Grid.COLOR_FILL_ROW) {
    return this.colors[j % this.colors.length];
  } else if (this.colorFillStrategy === Grid.COLOR_FILL_COLUMN) {
    return this.colors[i % this.colors.length];
  } else {
    return this.colors[int(random(0, this.colors.length))];
  }
}

This lets us decide how color gets applied—across rows, columns, or randomly.

Size Variation

getSizeFactor(i, j) {
  let maxW = 1;
  let maxH = 1;
  while (j + maxW < this.cols && !this.#occupied[i][j + maxW] && maxW < this.itemMaxWidthFactor) maxW++;
  while (i + maxH < this.rows && !this.#occupied[i + maxH][j] && maxH < this.itemMaxHeightFactor) maxH++;
  return [floor(random(1, maxW + 1)), floor(random(1, maxH + 1))];
}

This gives visual variety by allowing some shapes to span multiple grid cells.

Adding Texture with p5.brush

Using the p5.brush library, we simulate watercolor fills and pencil-like hatching:

setupFillAndStroke(fillColor) {
  if (this.colorFillStyle === Grid.COLOR_FILL_STYLE_WATERCOLOR) {
    brush.fill(fillColor, 75);
    brush.bleed(0.03, "out");
    brush.fillTexture(0.6, 0.4);
    brush.stroke(this.strokeStyle.color);
    brush.strokeWeight(this.strokeStyle.weight);
  } else {
    fill(fillColor);
    stroke(this.strokeStyle.color);
    strokeWeight(this.strokeStyle.weight);
  }
}

We also add hatch lines for texture:

if (this.hatchStyle.enabled) {
  brush.hatch(this.hatchStyle.distance, this.hatchStyle.angle);
  brush.setHatch('hatch_brush', this.hatchStyle.color, this.hatchStyle.weight);
}

This creates a rich visual texture that sets this apart from flat, digital visuals.

Drawing the Grid

The main draw() function brings everything together. Here's a simplified view:

function draw() {
  if (!parametersChanged) return;
  parametersChanged = false;

  background(240);
  apply2DTransformations();

  let colorGen = new ColorGenerator(params.startColor);
  let colors = params.colorVariation === COLOR_VARIATION_TINT
    ? colorGen.getTints(params.cols)
    : colorGen.getShades(params.cols);

  grid = new Grid(width, height, params.rows, params.cols);
  grid.setColors(colors);
  grid.setColorFillStrategy(params.colorFillStrategy);
  grid.setColorFillStyle(params.colorFillStyle);
  grid.draw();
}

Each rectangle is rendered according to all the styles and logic you configure via the UI.


From Sketch to Artwork Generator

Inspired by this sketch, I built an interactive generative art experience:

Create Your Unique Artwork | Alex Codes Art
Generate personalized digital artwork based on your personality and preferences.

Instead of tweaking sliders, users answer a few simple questions. Their answers feed into the code to generate a completely unique piece of generative art—no two are the same. This bridges code art with personalization, creating a fun and accessible entry point into creative coding.

Why This Is Great for Beginners

  • No advanced math or graphics knowledge required
  • Real-time feedback helps you learn by doing
  • Teaches essential JavaScript and p5.js concepts
  • Easily expandable: add animation, export, or save features

Whether you're just learning to code or looking to experiment with generative visuals, this is a practical, rewarding project.


Final Thoughts

Creative coding is one of the most exciting ways to learn programming. With tools like p5.js and libraries like p5.brush, you can create expressive, organic visuals with just a few lines of code.

As always, the full code can be found here:

Lesson 9 by alex.codes.art -p5.js Web Editor
A web editor for p5.js, a JavaScript library with the goal of making coding accessible to artists, designers, educators, and beginners.

This generative art grid is a great starting point. Play with the code, tweak the UI, change the layout—see what you can come up with.

Then, take it further. Make it yours. Happy coding!