Mondrian Painting demo annotated source
Back to indexA Mondrian-style drawing generator. Of the Torus samples, this is one of the few that may not be much easier to write with plain JavaScript. However, relying on Torus's functional way of describing components does simplify the process of recursively generating DOM here.
6
Bootstrap the required globals from Torus, since we're not bundling
8for (const exportedName in Torus) {
9 window[exportedName] = Torus[exportedName];
10}
11
Color palette, roughly taken after Mondrian's characteristic paintings. There are two of RYB; this is a cheap way to make sure they occur more often than black.
15const MONDRIAN_COLORS = [
16 '#c71b1b', // red
17 '#23238c', // blue
18 '#fbd209', // yellow
19
20 '#c71b1b', // red
21 '#23238c', // blue
22 '#fbd209', // yellow
23
24 '#181818', // black
25];
26
How likely is is that a region is a colored region?
28const COLOR_LIKELIHOOD = 0.3;
29
How likely is it that a region will contain subregions?
31const RECURSION_LIKELIHOOD = 0.88;
32
Minimum level of recursion required. If this is set to lower values, the resulting painting will tend to look quite sparse / boring.
35const RECURSION_MIN = 3;
Maximum level of recursion. This is set based on screen width to avoid paintings that are too crowded.
38const RECURSION_LIMIT = ~~(Math.max(window.innerHeight, window.innerWidth) / 130);
39
Shorthand for generating a random double.
41const rand = () => Math.random();
42
Shorthand for generating a random choice from a list.
44const randOf = list => {
45 return list[~~(Math.random() * list.length)];
46}
47
The Mondrian function component contains the core logic of recursively generating a Mondrian-style drawing. The painting in the app is a single Mondrian component with starting depth 0.
51const Mondrian = depth => {
52
By default, the child of a Mondrian block is null
(a comment).
54 let child = null;
If we're under the recursion limit, then...
56 if (depth < RECURSION_LIMIT && (
If we need to traverse down the recursion tree again, the children of this Mondrian block is two more Mondrian blocks.
59 depth < RECURSION_MIN || rand() < Math.pow(RECURSION_LIKELIHOOD, depth)
60 )) {
61 child = [
62 Mondrian(depth + 1),
63 Mondrian(depth + 1),
64 ];
65 }
66
The default color is an off-white shade.
68 let color = '#f3f3f3';
Given the likelihood of a colored block, generate a block color.
70 if (rand() < COLOR_LIKELIHOOD) {
71 color = randOf(MONDRIAN_COLORS);
72 }
73
Return a Mondrian block with a random split direction, the generated color, and a random flexbox size.
76 return jdom`<div class="block ${randOf(['vertical', 'horizontal'])}"
77 style="background:${color};flex-grow:${randOf([1, 2, 3, 4])}">
78 ${child}
79 </div>`
80}
81
Main component of the gallery UI.
83class App extends StyledComponent {
84
85 init() {
Allow the user to re-generate drawings by hitting the spacebar.
87 document.body.addEventListener('keyup', evt => {
88 if (evt.key === ' ') {
89 this.render();
90 }
91 });
92 }
93
94 styles() {
95 return {
96 'height': '100vh',
97 'width': '100vw',
98 'overflow': 'hidden',
99 'display': 'flex',
100 'flex-direction': 'column',
101 'align-items': 'center',
102 'justify-content': 'space-around',
103 'background': '#f1f1f1',
104 'main': {
105 'height': '90vh',
106 'width': '94vw',
107 'margin': '0',
108 'box-shadow': '0 4px 10px 0px rgba(0, 0, 0, .3)',
109 'border-radius': '3px',
110 'overflow': 'hidden',
111 'display': 'flex',
112 'flex-direction': 'row',
113 },
A block is just a flexbox, where the flex-direction
determines
the direction of split of blocks inside it.
116 '.block': {
117 'display': 'flex',
118 'flex-grow': '1',
119 'flex-shrink': '0',
120 'min-width': '2vw',
121 'min-height': '2vh',
122 '&.vertical': {
123 'flex-direction': 'column',
124 },
125 '&.horizontal': {
126 'flex-direction': 'row',
127 },
128 },
Clever method of adding separator lines between blocks, such that every block has equal sized borders on all sides regardless of recursion depth.
132 '.vertical > .block + .block': {
133 'border-top': '8px solid #181818',
134 },
135 '.horizontal > .block + .block': {
136 'border-left': '8px solid #181818',
137 },
Gallery artwork plaque.
139 '.plaque': {
140 'font-family': '"San Francisco", "Helvetica", "Segoe UI", sans-serif',
141 'display': 'flex',
142 'flex-direction': 'column',
143 'align-items': 'center',
144 'margin': '0',
145 'padding': '.5vh 20px',
146 'background': '#ddd',
147 'border-radius': '4px',
148 'box-shadow': '0 3px 2px rgba(0, 0, 0, .4)',
149 'margin-top': '-2vh',
150 'h1, p': {
151 'font-size': '1.5vh',
152 'margin': '0',
153 },
154 },
155 }
156 }
157
158 compose() {
The app includes a Mondrian block at depth 0.
160 return jdom`<div class="root">
161 <main onclick="${() => this.render()}">
162 ${Mondrian(0)}
163 </main>
164 <div class="plaque">
165 <h1>Untitled</h1>
166 <p>Piet Mondrian (1872 - 1944)</p>
167 </div>
168 </div>`;
169 }
170
171}
172
Create an instance of the app and mount it to the page DOM.
174const app = new App();
175document.body.appendChild(app.node);
176document.body.style.margin = '0';
177
178