Practical guides to digital design and creativityPractical guides to digital design and creativity
Generative Art

From Rows to Canvas: Painting with Data in p5.js

Transform sterile spreadsheet rows into organic, emotive digital paintings using p5.js mapping techniques and easing principles.

Felipe Souza
Felipe SouzaMotion Graphics & Animation Curator7 min read
Editorial image illustrating From Rows to Canvas: Painting with Data in p5.js

We spend our days staring at grids. Financial quarters, weather patterns, server logs—they all end up in the same rigid rows and columns. By 2026, we have automated dashboards for everything, yet we rarely feel the emotional weight of the information these numbers represent. A bar chart tells you when sales dropped, but it does not make you feel the hollowness of that drop. My goal here is to move beyond visualization and into representation. We are going to turn a dry CSV file into a living, breathing digital painting using p5.js.

I often argue that generative art is not just random numbers. It is about the intentional translation of logic into aesthetics. When you map data to visual properties, you stop acting as a statistician and start acting as a curator of perception. The process I use focuses on p5.js, a library that respects the chaos of creative coding while offering enough structure to handle complex datasets. We will focus specifically on mapping data points to color hues and brush stroke sizes, creating a piece that looks like an oil painting rather than an Excel graph.

The Aesthetic Disconnect Between Spreadsheets and Canvas

The core problem with standard data visualization is precision. Charts demand exact values; a line graph is useless if the dot is two pixels off. Art, however, thrives on suggestion and texture. To turn data into a painting, we must abandon the need for exact readability in favor of emotive accuracy.

This approach involves a trade-off. You will lose the ability to read the exact value of row 45, column C at a glance. Instead, you gain the ability to see trends as landscapes, outliers as sudden jagged peaks, and clusters as dense forests of color. We use p5.js because it allows us to treat the canvas as a continuous space rather than a coordinate system with rigid axes. We are not plotting points; we are making marks.

Step 1: Preparing Your Data for Generative Input

Before opening the code editor, you must sanitize your dataset. I prefer working with CSVs that have a clear temporal or sequential element, as this allows for animation. A dataset of "daily temperature fluctuations" or "stock price volatility" works perfectly.

Ensure your CSV has at least three numerical columns. Let's call them ValueA, ValueB, and ValueC.

  1. Open your CSV in a text editor.
  2. Remove any headers that contain special characters or spaces. Keep them simple: timestamp, magnitude, frequency.
  3. Normalize your data if the ranges are drastically different. If ValueA ranges from 0 to 1 and ValueB ranges from 0 to 10,000, the visual output will be skewed. I usually run a quick script to scale everything between 0 and 1 before saving the final file.

This normalization is crucial because p5.js mapping functions work best when they have predictable input ranges. You want the raw material to be pliable, like clay before it goes on the wheel.

Step 2: Constructing the p5.js Environment

With your data prepared, set up a basic p5.js sketch. We do not need complex DOM elements; the entire interface will be the canvas.

Initialize your canvas in the setup function. I recommend a high-resolution canvas, perhaps 2000x2000 pixels, even if you display it smaller on screen. This high pixel density allows the "brush strokes" to retain their texture when rendered.

Load your table using the loadTable function. Ensure this happens in the preload function so the data is ready before the canvas draws. You will iterate through this table row by row in the draw loop. Unlike standard animations that run at 60 frames per second, a data-driven painting often benefits from a controlled frame rate. Set frameRate to something lower, like 30 or even 15, to allow the viewer to perceive the individual strokes being laid down.

Mapping Mechanics: From Numbers to Hues

This is where the translation happens. We need to decide what our numbers represent visually. In a traditional chart, ValueA might be the X-axis and ValueB the Y-axis. In a painting, these values can control texture, opacity, and color temperature.

Photographic detail related to From Rows to Canvas: Painting with Data in p5.js

I typically map the first column (let's say, time or sequence) to the X position, but I add a layer of noise to it. Instead of a straight line, the brush drifts slightly up and down. This prevents the image from looking like a scan line.

The second column, representing magnitude or intensity, should map to the Y position but also to the size of the brush. High magnitude equals a thicker, more aggressive stroke. The third column is where the emotion comes in: map it to the colorMode(HSB). Use the map() function to convert your data range into a hue spectrum (0 to 360).

Here is a specific technique I employ: do not map the color linearly. Use a sine wave or a quadratic curve to map the data to the hue. This compresses the boring middle-ground values and expands the color range for the outliers—the peaks and valleys of your data. This ensures that the "exciting" parts of your data explode with color, while the mundane parts recede into the background.

Adding Life Through Easing and Temporal Density

To satisfy industry-standard easing principles, we cannot simply draw the circles and ellipses instantly. We need the strokes to arrive with weight. When I work on motion graphics, I obsess over the velocity of an object. The same applies here.

Instead of drawing the data point immediately at its target coordinate, calculate an interpolation. The brush should start thin and transparent, grow to its target size, and then fade out. You can achieve this by tracking the "life" of each brush stroke.

If you are drawing the painting over time (rendering row by row), apply an easing function to the transition between the current row's data and the next. If the data jumps from 10 to 100, a linear jump looks like a glitch. An eased jump—starting fast and slowing down—looks like a deliberate brush movement.

I also recommend varying the density. If your dataset has 10,000 rows, drawing every single one might result in a muddy mess. Consider drawing every nth row, or using the modulus operator to introduce variation. Perhaps you draw five thin lines for a low-value row and one thick, opaque line for a high-value row.

The Happy Accident of Generative Noise

Sometimes, the data is too perfect, and the resulting art looks sterile. This is where I introduce controlled chaos. I often reference how a code bug once led me to create a best-selling texture pack. You can mimic this intentionally.

Add a random jitter to your brush positions. In p5.js, add a small random value to your x and y coordinates inside the drawing loop. Use randomGaussian() for a more natural, ink-splatter effect rather than a uniform digital jitter. This mimics the imperfection of the human hand. A straight line is mathematical; a slightly wobbly line is artistic.

Exporting the Composition

Once your loop has finished iterating through the CSV, you will have a canvas filled with thousands of layered strokes. Because we used high-resolution coordinates and HSB color mapping, the image should possess a depth that standard plotting cannot achieve.

Use the saveCanvas function to export the result as a PNG. I usually run the sketch multiple times with different random seeds to generate variations. Since the mapping logic remains constant but the noise and easing timings vary, you get a series of unique "prints" from the same data source.

When the Algorithm Reveals the Narrative

The ultimate payoff of this process is not the final image, but the moment you recognize your data in the abstraction. You might see a dark, turbulent red streak in the upper quadrant and realize that corresponds to the financial crash of 2029. Suddenly, the data is not just numbers; it is a memory embedded in pigment and light.

This method bridges the gap between analytical rigor and creative expression. By treating data points not as coordinates to be hit, but as forces to be felt, you create a document that is informative on a human level. You stop looking at the spreadsheet and start looking at the painting.

Read next