Overview


In this tutorial, participants will learn to build generative art tools using Observable JavaScript notebooks. In doing so, they will build interfaces that control the inputs to art rendering functions. As a starting point, consider the work of Vera Molnár. Her algorithmic renderings of simple geometric shapes in many ways set the groundwork for contemporary generative artists.

Structure de Quadrilatères, 1986 (source)
The generative aspect of the above work is the randomness or noise in the rendering of the shapes (quadrilaterals). While a computer generates the randomness, there are a variety of design decisions made by the artist, such as: To more easily explore the design space of such work, we can create generative art tools that control such inputs.

In this tutorial, participants will learn how to identify the pertinent inputs to generative art systems, and build user-interfaces for controlling those parameters.

Designing Generative Art Tools


As an example, we can recreate a similar work to Molnár's above:
A simplified version of Structure de Quadrilatères
In this example, both the number of shapes drawn and the amount of noise are a function of the column. If you want to further augment this, you need to build an art tool that allows you to configure the visual attributes of the work (you can think of these as the inputs). For example, you might want to choose the number of columns in your work.

As you can see, exposing design decisions as function parameters allows you to easily experiment with different designs. You may similarly want to experiment with augmenting how much randomness the computational system injects into your work:

Coding Generative Art Tools


The code underlying such systems is rather straightforward, though requires an understanding of how <path> elements are drawn in <svg> elements:

                
// Render a single shape, given four corners
function renderQuad(corners = [{ x: 0, y: 0 }, { x: 100, y: 0 }, { x: 100, y: 100 }, { x: 0, y: 100 }]) {
    const [c1, c2, c3, c4] = corners; // create four variables for more expressive code

    // Return an element (this syntax is how Observable allows rendering of an SVG element)
    return svg`<path style="fill:none; stroke: black; strokeWidth: 1px;"
                    d = "M" + c1.x + " " + c1.y +  // move to top left corner
                        " L" + c2.x + " " + c2.y +  // line to top right corner
                        " L" + c3.x + " " + c3.y +  // line to bottom right corner
                        " L" + c4.x + " " + c4.y +  // line to bottom left corner
                        " Z"                        // close the path
    />`;
}
                
            
The randomness comes from a fairly straightforward function that can easiliy be expanded upon:
                
// Compute corners of a quad
function getCorners(edgeLength, distortion) {
    // A helper function to get *different* random distortions for each edge
    const getOffset = d => (Math.random() - 1) * d;
    let c1 = { x: getOffset(distortion), y: getOffset(distortion) };                     // top left
    let c2 = { x: c1.x + len + getOffset(distortion), y: c1.y + getOffset(distortion) }; // top right
    let c3 = { x: c2.x + getOffset(distortion), y: c2.y + len + getOffset(distortion) }; // bottom right
    let c4 = { x: c3.x - len - getOffset(distortion), y: c3.y + getOffset(distortion) }; // bottom left
    return [c1, c2, c3, c4];
}
                
            

To see more options, see this notebook. And to learn more, attend the tutorial!