Broadway Boogie Woogie

art
d3
randomness
observable
quarto
Building a Genuine Fake
Author
Published

October 12, 2022

Let’s Boogie

Code
html`
<button id="update">Boogie!</button>
`
Code
{
    const svgRaw = htl.svg`<svg></svg>`
    const svg = d3.select(svgRaw)
    .attr("width",500)
    .attr("height",500)
    .style("background-color","#e5e4f0")

    function randomData() {
        return Array.from({length: 50}, () => Math.floor(Math.random() * 100))
    }

    function colourMap(num) {
        if (num >= 0 && num <= 65) { return "yellow"}
        if (num >= 66 && num <= 80) { return "black"}
        if (num>= 81 && num <=85) {return "red"}
        if (num>= 86 && num <=100) {return "grey"}
    }

    function horizontalStreets(yPosArray) {
        for (let i=0; i<yPosArray.length; i++){
            svg.selectAll("horizontal")
            .data(randomData())
            .join(
                enter => enter.append("rect")
                    .attr("class","horizontal")
                    .attr("x", function(d,i) {return i*10})
                    .attr("y", yPosArray[i])
                    .attr("width", 10)
                    .attr("height", 10)
                    .attr("fill", function(d){return colourMap(d)})
                    .on("mouseover", function(d) {
                        d3.select(this)
                            .raise()
                            .transition()
                            .attr("width",25)
                            .attr("height",25)
                            .attr("stroke","black")
                            
                    })
                    .on("mouseout", function(d) {
                        d3.select(this)
                            .transition()
                            .attr("width",10)
                            .attr("height",10)
                            .attr("stroke","")
                    }),
               update => update,
               exit => exit.remove()
            )
        }
    }

    function verticalStreets(xPosArray) {
        for (let i=0; i<xPosArray.length; i++){
            svg.selectAll("vertical")
            .data(randomData())
            .join(
                enter => enter.append("rect")
                    .attr("class","vertical")
                    .attr("x", xPosArray[i])
                    .attr("y", function(d,i) {return i*10})
                    .attr("width", 10)
                    .attr("height", 10)
                    .attr("fill", function(d){return colourMap(d)})
                    .on("mouseover", function(d) {
                        d3.select(this)
                            .raise()
                            .transition()
                            .attr("width",25)
                            .attr("height",25)
                            .attr("stroke","black")
                            
                    })
                    .on("mouseout", function(d) {
                        d3.select(this)
                            .transition()
                            .attr("width",10)
                            .attr("height",10)
                            .attr("stroke","")
                    }),
               update => update,
               exit => exit.remove()
            )
            
        }
    }

    function moveBlock(d, i) {
        d3.select(this)
        .transition()
            .attr("x", Math.floor(Math.random() * 500))
            .attr("y", Math.floor(Math.random() * 500))
    }

    // Initial draw
    horizontalStreets([10,50,120,150,220,250,280,340,440])
    verticalStreets([10, 30, 70, 140, 300,330,420,440,480,500])
    
    var blue1 = svg.append("rect").raise()
        .attr("class","block")
        .attr("x", 50)
        .attr("y", 80)
        .attr("width", 30)
        .attr("height", 30)
        .style("fill", "blue");
    
    var blue2 = svg.append("rect").raise()
        .attr("class","block")
        .attr("x", 50)
        .attr("y", 350)
        .attr("width", 40)
        .attr("height", 40)
        .style("fill", "blue");

    var blue3 = svg.append("rect").raise()
        .attr("class","block")
        .attr("x", 160)
        .attr("y", 260)
        .attr("width", 30)
        .attr("height", 50)
        .style("fill", "blue");
    
    var blue4 = svg.append("rect").raise()
        .attr("class","block")
        .attr("x", 380)
        .attr("y", 160)
        .attr("width", 50)
        .attr("height", 100)
        .style("fill", "blue");

    var blue5 = svg.append("rect").raise()
        .attr("class","block")
        .attr("x", 410)
        .attr("y", 350)
        .attr("width", 40)
        .attr("height", 40)
        .style("fill", "blue");

    var red1 = svg.append("rect").raise()
        .attr("class","block")
        .attr("x", 80)
        .attr("y", 20)
        .attr("width", 20)
        .attr("height", 90)
        .style("fill", "red");

    var red2 = svg.append("rect").raise()
        .attr("class","block")
        .attr("x", 130)
        .attr("y", 20)
        .attr("width", 40)
        .attr("height", 60)
        .style("fill", "red");

    var red3 = svg.append("rect").raise()
        .attr("class","block")
        .attr("x", 80)
        .attr("y", 270)
        .attr("width", 60)
        .attr("height", 40)
        .style("fill", "red");
    
    var red4 = svg.append("rect").raise()
        .attr("class","block")
        .attr("x", 290)
        .attr("y", 200)
        .attr("width", 60)
        .attr("height", 50)
        .style("fill", "red");

    var red5 = svg.append("rect").raise()
        .attr("class","block")
        .attr("x", 340)
        .attr("y", 300)
        .attr("width", 50)
        .attr("height", 70)
        .style("fill", "red");
    
    var gray1 = svg.append("rect").raise()
        .attr("class","block")
        .attr("x", 140)
        .attr("y", 40)
        .attr("width", 30)
        .attr("height", 30)
        .style("fill", "gray");

    var gray2 = svg.append("rect").raise()
        .attr("class","block")
        .attr("x", 230)
        .attr("y", 110)
        .attr("width", 50)
        .attr("height", 70)
        .style("fill", "gray");

    var gray3 = svg.append("rect").raise()
        .attr("class","block")
        .attr("x", 230)
        .attr("y", 370)
        .attr("width", 30)
        .attr("height", 50)
        .style("fill", "gray");

    var gray4 = svg.append("rect").raise()
        .attr("class","block")
        .attr("x", 370)
        .attr("y", 310)
        .attr("width", 40)
        .attr("height", 20)
        .style("fill", "gray");

    var gray5 = svg.append("rect").raise()
        .attr("class","block")
        .attr("x", 400)
        .attr("y", 20)
        .attr("width", 50)
        .attr("height", 30)
        .style("fill", "gray");
   

    // Updated draws
    d3.select("#update").on("click", function() {
        horizontalStreets([10,50,120,150,220,250,280,340,440])
        verticalStreets([10, 30, 70, 140, 300,330,420,440,480,500])
        d3.selectAll(".block").each(moveBlock)

    })

    yield svg.node();

}

This is a piece of digital art I created. It features bright colours, straight lines and some fun interactivity.

Try clicking the “Boogie!” button

You probably realize that my simple artwork is very similar to Piet Mondrian’s iconic Broadway Boogie Woogie. Broadway Boogie Woogie is considered one of Mondrian’s most emblematic paintings and whose aesthetic is instantly recognised all around the world. The original piece is housed today in New York’s Museum of Modern Art (MOMA).

Piet Mondrian’s original Broadway Boogie Woogie

Indeed, my amateur recreation is inspired by the original Mondrian, but not directly. In fact, it’s probably more accurate to say, my art piece is a derivative of a derivative.

Code
flowchart TD
    A(Mondrian's Broadway Boogie Woogie) -->|inspired| B(Tom Sach's Broadway Boogie Woogie)
    B -->|inspired| C(My Broadway Boogie Woogie)
flowchart TD
    A(Mondrian's Broadway Boogie Woogie) -->|inspired| B(Tom Sach's Broadway Boogie Woogie)
    B -->|inspired| C(My Broadway Boogie Woogie)

Tom Sachs

Tom Sachs is another influential artist born of a later generation. Tom Sachs had a very special path to the world of art, working in many blue-collar jobs such as lift repairman, electrician, plumber and construction worker. As a result, he has a very special view of the relationship between working with physical materials and the art he creates. Like many, Tom Sachs was very enamoured with Mondrian’s Broadway Boogie Woogie. He liked the art piece so much, he created his own.

Tom Sachs is also credited by both Casey and Van Neistat of Youtube fame as being a key inspiration for their growth as an artist.

View this post on Instagram

A post shared by Tom Sachs (@tomsachs)

A Genuine Fake

Tom Sachs explains his thought process here in this interview with Adam Savage and in his Ted Talk. Here are a few choice quotes:

I know I spent more time and enjoyed Broadway Boogie Woogie than the person who bought it. I know I enjoyed it more because I … built one.

Creating my Broadway Boogie Woogie with plywood and gaffers tape, materials that I have a connection with … I am creating authenticity that meets my own standards of what’s important… It’s a Genuine Fake

And this is the most fascinating part of the equation for me. The original art piece is beautiful but in the process of recreating the same art piece using tools that were unique to Tom Sachs, he created his Genuine Fake. It’s a Fake in the sense that it is not the original, but it is also Genuine because Tom Sachs experienced the joy of creating something unique through intense study of the original.

I built my Genuine Fake in hopes that I can experience the same journey. And while I am severely unfamiliar with both oil paint which was used in Mondrian’s original work and plywood used in Tom Sach’s work, I am rather familiar with digital tools and materials, so that is what I decided to use.

Currently, I am taking a course on Data and Visual Analytics, which has a strong focus on D3.js, a javascript visualisation framework, so that is what I used to build my genuine fake.

Interesting things I learnt along the way

Ok, that’s enough rambling about Why I started this project, let me shift gears to list some of my learnings as I worked on How to execute this project.

The Interplay between ObservableJS / D3 / Quarto

Quarto, the publishing framework I keep raving about, supports ObservableJS right out of the box. ObservableJS is built by the same minds behind D3, so naturally, ObservableJS has D3 libraries preloaded.

To write some D3/ObservableJS code in Quarto, you simply have to create a code block like so

```{ojs}
// This creates an SVG element.
svgRaw = htl.svg`<svg></svg>`

// d3 code goes here
d3.select(svgRaw)
    .attr("width",500)
    .attr("height",500)
    ...
```

Above, you can also see some of the niceties ObservableJS brings, such as Hypertext Literals which makes it a breeze to create HTML elements directly in the {ojs} code block without the need to touch the raw HTML file.

Easy art with random data

What makes the entire field of Generative Art remarkable is how random data is transformed into art pieces. I started out my project seeking to do a pure 1-for-1 copy of the original Mondrian, so every street and block would be in the same position and of the same colour. I soon became really bored of creating a 1-for-1 copy and realized I could make a much more interesting project, simply by randomizing the data that would generate the streets and colours. It was so fun, I decided to include a button (“Boogie!”) so the user could cycle through many variants!

In my implementation, I relied on Javascript’s Math.random() random number generator, which I later learnt is pseudo uniform. D3 includes a more fleshed-out d3-random module which exposes more functions from a greater variety of distributions. If I revisit this project again in the future, I would use the d3-random over Math.random().

D3’s Select-Enter-Update pattern

D3 has a really difficult to grok select-enter-update pattern. The goal here is that once you bind your data to the visual elements of your page, following this pattern allows fine-grain control of how new items are added, existing items are updated and redundant items are removed.

It’s incredibly powerful but this was the hardest part of the D3 syntax for me to understand. It does not help that D3 has several versions and I feel the syntax of this pattern goes through pretty major changes in each version.

Transitions and Animations really make things pop!

My favourite part of the project was making things animate and move. It’s pretty addictive once I got it working. It is a reminder that building web-based visualisations, while difficult and finicky, can absolutely create interactive and delightful experiences for the end user. When done right, web visualisations are magic.

Conclusion

Art is not my forte, nor my day job but I enjoyed the challenge of creating my Genuine Fake. Through this process, I have a newfound appreciation for the difficulty and satisfaction that comes from building web visualisations.

Appendix

─ Session info ───────────────────────────────────────────────────────────────
 setting  value
 version  R version 4.3.1 (2023-06-16)
 os       macOS Ventura 13.5
 system   x86_64, darwin20
 ui       X11
 language (EN)
 collate  en_US.UTF-8
 ctype    en_US.UTF-8
 tz       Asia/Singapore
 date     2023-08-10
 pandoc   3.1.6 @ /usr/local/bin/ (via rmarkdown)
 quarto   1.3.433

─ Packages ───────────────────────────────────────────────────────────────────
 package     * version date (UTC) lib source
 sessioninfo * 1.2.2   2021-12-06 [1] CRAN (R 4.3.0)

 [1] /Users/ddanieltan/Code/ddanieltan.com/renv/library/R-4.3/x86_64-apple-darwin20
 [2] /Users/ddanieltan/Library/Caches/org.R-project.R/R/renv/sandbox/R-4.3/x86_64-apple-darwin20/84ba8b13

──────────────────────────────────────────────────────────────────────────────
We must never make experiments to confirm our ideas, but simply to control them – Claude Bernard

Reuse

Citation

BibTeX citation:
@online{tan2022,
  author = {Tan, Daniel},
  title = {Broadway {Boogie} {Woogie}},
  date = {2022-10-12},
  url = {https://www.ddanieltan.com/posts/boogiewoogie},
  langid = {en}
}
For attribution, please cite this work as:
Tan, Daniel. 2022. “Broadway Boogie Woogie.” October 12, 2022. https://www.ddanieltan.com/posts/boogiewoogie.