This post is a submission to the 2024 Plotnine Contest, a global contest to encourage interesting plots created with Plotnine, a visualization library that brings the Layered Grammar of Graphics to Python!
The Inspiration
As always, I find that inspiration can come from the most unexpected places. I was browsing Linkedin on a Saturday morning and saw this post from Michael Chow:
Michael’s post is a great example that visualisation libraries such as Plotnine, can not only be used for insights and analytics but also for art and recreation! I was reminded of the time where I was inspired to make some modern art as I was learning d3.js. Naturally, I found myself curious if I could do the same using Plotnine.
Piet Mondrian
I am neither an art expert nor aficionado 👨🎨, but I have always enjoyed Piet Mondrian’s art. His art work has a distinctive style—characterized by bold lines, primary colours, and geometric shapes—has and is usually used in pop culture as the iconic example of the Modernism movement.
For me, his art has always been unique, beautiful and approachable.I think that’s why I find myself always drawn to recreating his artwork with the tools that I’m more familiar with!
The Decision Tree is a well-known tool in the Machine Learning toolbox and in many textbooks, the prediction space that is partitioned by a decision tree is visualised this way. (Source)
I find that the parallels between decision tree partitions and Mondrian’s geometric abstractions are striking. Both divide a space into rectangles of varying sizes, creating a visually compelling arrangement that conveys information—whether it’s a machine learning model’s decision boundaries or an artist’s abstract representation of reality.
And so, my approach to building a Mondrian-inspired artwork begins with representing the rectangles as a Tree data structure:
Our Node class is the building block of our Mondrian tree. Each node represents a rectangular region in our canvas, defined by its x_range and y_range. The depth attribute helps us control the complexity of our “painting,” while left and right keeps track of its children Nodes in a Tree-like structure.
We turn our creation into Generative Art with is_vertical and split_value. These introduce the element of randomness that gives our visualizations their Mondrian-like quality. is_vertical determines whether we’ll split our rectangle vertically or horizontally, while split_value determines where that split occurs.
The generate_tree() function is where add elements of generative art. It recursively builds our tree, making decisions about splitting at each step. Here’s how it works:
We start with a single rectangle (our canvas).
At each node, we decide whether to split vertically or horizontally (is_vertical).
We then choose a random point along that axis to make the split (split_value).
This process continues until we reach our max_depth or our rectangles become too small (min_size).
To add more variety, we’ve included a 10% chance of early termination for any branch.
This approach ensures that our final visualization will have the characteristic Mondrian look: a mix of larger and smaller rectangles, some split vertically, others horizontally, all arranged in a seemingly random yet aesthetically pleasing manner.
Perceiving Randomness
In some of my early attempts, I churned out pieces that looked like this.
At first glance, this image might seem like a failed attempt at recreating Mondrian’s style. We see a few large rectangles that haven’t been split into smaller ones, creating an imbalance that doesn’t quite capture the essence of Mondrian’s work.
But there is nothing wrong with the code that generated this artwork. Our algorithm uses a probabilistic approach to decide whether to split a rectangle further. In this particular instance, several large rectangles weren’t chosen for splitting. From a purely statistical standpoint, this outcome is entirely possible and, indeed, random.
As human observers, we tend to perceive this result as “not random enough.” This discrepancy highlights a crucial concept in both data science and cognitive psychology: humans have an inherent bias in perceiving randomness!
In my case, I’d like to side step this perception paradox and create an art piece that looks “justifiably random” for most people. Therefore, this initial_splits() function was introduced to force our tree to always start with this initial, balanced tree.
Root
/ \
L R
/ \ / \
LL LR RL RR
This initial structure ensures that we always start with at least four rectangles, and while it does not eliminate the type of large-rectangle pieces we wanted to provide, it does make it more likely that our final piece has a more interesting Mondrian-style composition.
Plotnine Specifics
The final product is produced by the draw() function and here’s the most important snippet - using Plotnine to visualise!
colours = pl.Series(name="colour",values=np.random.choice([colour.value for colour in MondrianColour], size=len(rectangles)))df = pl.DataFrame(rectangles).with_columns(colours)plot = (ggplot(df, aes(xmin='xmin', xmax='xmax', ymin='ymin', ymax='ymax', fill='colour'))+ geom_rect(color='black', size=2)+ scale_fill_manual(values=[colour.value for colour in MondrianColour])+ theme_minimal()+ theme(legend_position ="none", aspect_ratio=1, axis_text=element_blank(), axis_ticks=element_blank(), panel_grid=element_blank(), figure_size=(10,10)))
The most critical piece of code of the entire art piece is Plotnine’s geom_rect() method. This geom takes in the coordinates of a rectangle (xmin,xmax,ymin,ymax) which our graph data structure provides. Every rectangle on the final art piece is rendered as a geom_rect on the canvas.
Earlier in the code block, I created a palette of Mondrian colours MondrianColour and using scale_fill_manual() I can ensure my art piece to uses these fill values from that palette. Finally, the color and size parameter controls the thick bold outlines for each rectangle.
Using the minimal theme template and with additional tweaks in theme(), I removed the legend, axis text, ticks, and grid lines. I also set the aspect ratio to 1 and the figure size to 10x10, ensuring a perfect square canvas reminiscent of many of Mondrian’s works.
Conclusion
I really enjoyed the simplicity and flexibility of the Plotnine library. I felt that it’s straight-forwardness (especially if you are familiar with ggplot2) really helped me focus on the creative aspects of the visualization. I am already looking forward to see what other visualisations will be submitted to this year’s competition.