Recently I have moaned about not really knowing what I was doing with the `grid`

package (see here and here). I’m happy to say, not only did I take the time to better understand the `grid`

package, I also wrote my own package around it - the `kandinsky`

package! To generate random Wassily Kandinsky paintings or even make any dataset into one. You’re probably wondering what on earth am I talking about. If you’re not interested in my ramblings just go to the package itself.

# A Kandinsky Painting

So this is Kandinsky’s *Composition VIII*:

Kandinsky was a Russian abstract painter, known for using colorful geometrical shapes in his paintings. Geometrical shapes are relatively easy to make in R, with the `grid`

package, on our way to generating completely random paintings. We just need to specify which shapes exactly are we after. I can describe a few when looking into Kandinsky’s paintings:

- Rectangles
- Circles
- Triangles
- Archs
- Waves
- Criss Cross or “Grills”

If we can draw each of these in `grid`

, using various angles and colors, we already have something worth looking at.

# Rectangles

To get a blank canvas in `grid`

we use the `grid.newpage()`

function:

```
library(grid)
grid.newpage()
```

This canvas has coordinate axes `x`

and `y`

ranging by default from 0 to 1, starting from the bottom-left corner (`x = 0`

, `y = 0`

), to the top-right corner (`x = 1`

, `y = 1`

).

And a rectangle is as simple as calling `grid.rect`

with the proper parameters: `x`

for the rectangle’s center x-location, `y`

for the rectangle’s center y-location, `width`

for the 0 to 1 width of the rectangle and `height`

for the rectangle’s height^{1}.

```
grid.rect(x = 0.4, y = 0.6, width = 0.5, height = 0.4,
gp = gpar(col = "red", lwd = 2))
```

Notice I also specified some well-known base R graphical parameters via the `gp`

parameter and `gpar`

function, i.e. making the rectangle’s outline red, with a line width of 2.

Now how about a tilted rectangle?

Unfortunately there is no magical `angle`

parameter in `grid.rect`

, that’s where the concept of a `viewport`

comes in. A `viewport`

allows you to define a “sub-canvas” within the current canvas, and draw whatever you like in *that* sub-canvas. A viewport *does* have an `angle`

parameter, so drawing a regular rectangle with `grid.rect`

inside a viewport with `angle = 45`

results in a 45 degrees tilted rectangle.

You can do it by “pushing” the new viewport with `pushViewport`

, drawing the rectangle and returning to the current canvas with `upViewport`

:

```
vp1 <- viewport(x = 0.5, y = 0.6, width = 0.8, height = 0.7, angle = 45)
pushViewport(vp1)
grid.rect(x = 0.4, y = 0.6, width = 0.5, height = 0.4,
gp = gpar(col = "red", lwd = 2, fill = "yellow"))
upViewport()
```

In this case it is probably more convenient to enter the viewport `vp1`

as a parameter into `grid.rect`

:

```
vp1 <- viewport(x = 0.5, y = 0.6, width = 0.8, height = 0.7, angle = 45)
grid.rect(x = 0.4, y = 0.6, width = 0.5, height = 0.4,
gp = gpar(col = "red", lwd = 2, fill = "yellow"), vp = vp1)
```

# Circles

Circles are simple with `grid.circle`

:

```
grid.circle(x = 0.8, y = 0.5, r = 0.3,
gp = gpar(lwd = 10, col = "blue", fill = "lightblue"))
```

# Triangles

Triangles are simple with `grid.polygon`

:

```
grid.polygon(x = c(0.1, 0.7, 0.8), y = c(0.2, 0.5, 0.8),
gp = gpar(col = NA, fill = "pink"))
```

# Archs

Archs are simple with `grid.curve`

:

```
grid.curve(x1 = 0.1, y1 = 0.7, x2 = 0.6, y2 = 0.2,
curvature = 0.15, square = FALSE, ncp = 30,
gp = gpar(lwd = 10, col = "navyblue"))
```

# Waves

Ah. There’s no `grid`

function that can take a formula such as `sin(x)/x`

which represents a nice wave and plot it. At least I couldn’t find one. I took inspiration from this great article, to define the `gCurve`

function which wraps `grid.lines`

:

```
gCurve <- function (expr, from, to, n = 101,
gp = gpar(),
default.units = "npc", vp = NULL,
name = NULL, draw = TRUE, xname = "x", ...)
{
sexpr <- substitute(expr)
if (is.name(sexpr)) {
expr <- call(as.character(sexpr), as.name(xname))
}
else {
if (!((is.call(sexpr) || is.expression(sexpr)) && xname %in%
all.vars(sexpr)))
stop(gettextf("'expr' must be a function, or a call or an expression containing '%s'",
xname), domain = NA)
expr <- sexpr
}
x <- seq.int(from, to, length.out = n)
ll <- list(x = x)
y <- eval(expr, envir = ll, enclos = parent.frame())
if (length(y) != length(x))
stop("'expr' did not evaluate to an object of length 'n'")
x <- (x - min(x, na.rm = T))/(max(x, na.rm = T) - min(x, na.rm = T))
y <- (y - min(y, na.rm = T))/(max(y, na.rm = T) - min(y, na.rm = T))
grid.lines(x = x, y = y, default.units = default.units, gp = gp, vp = vp, ...)
invisible(list(x = x, y = y))
}
```

The usage:

`gCurve(sin(x)/x, from = 0.1, to = 20, gp = gpar(lty = 2))`

And of course you can use a viewport to tilt the wave, stretch it etc.

# Criss Cross

Again, no ready-made function, this is DIY:

```
gCrissCross <- function(n = 5, gp = gpar(), vp = NULL, ...) {
x0 <- runif(1, 0, 0.1)
ccGap <- runif(1, 0.3, 0.7)
y0 <- runif(1, 0, 0.5)
for (i in 1:n) {
x0 <- x0 + runif(1, 0.1, 0.2)
y0 <- y0 + runif(1, -0.05, 0.05)
y1 <- y0 + ccGap + runif(1, -0.05, 0.05)
grid.segments(x0, y0, x0 + ccGap, y1, vp = vp, gp = gp, ...)
grid.segments(x0, y1, x0 + ccGap, y0, vp = vp, gp = gp, ...)
}
}
```

The usage:

`gCrissCross()`

# Random Kandinsky

Putting some random rectangles, circles, triangles, archs, waves and grills - we get a really nice “Random Kandinsky”:

```
randomKandinsky <- function(n = 10) {
grid.newpage()
grid.rect(gp=gpar(fill=rgb(runif(1),
runif(1),
runif(1),
runif(1))))
for (i in 1:n) {
grid.rect(x = runif(1), y = runif(1), width = runif(1), height = runif(1),
gp = gpar(col = NA,
fill=rgb(runif(1),
runif(1),
runif(1),
runif(1))))
grid.circle(x = runif(1), y = runif(1), r = runif(1),
gp = gpar(
lwd = runif(1, 0, 100),
col = rgb(runif(1),
runif(1),
runif(1),
runif(1)),
fill=rgb(runif(1),
runif(1),
runif(1),
runif(1))))
grid.polygon(x = runif(3), y = runif(3),
gp = gpar(col = NA,
fill=rgb(runif(1),
runif(1),
runif(1),
runif(1))))
grid.curve(runif(1), runif(1), runif(1), runif(1),
curvature = runif(1, -1, 1), square = FALSE, ncp = sample(100, 1),
gp = gpar(lwd = runif(1, 0, 10),
col = rgb(runif(1),
runif(1),
runif(1),
1)))
vp1 <- viewport(x = runif(1), y = runif(1), width = runif(1), height = runif(1), angle = runif(1) * 360)
grid.rect(x = runif(1), y = runif(1), width = runif(1), height = runif(1),
vp = vp1,
gp = gpar(col = NA,
fill=rgb(runif(1),
runif(1),
runif(1),
runif(1))))
vp2 <- viewport(x = runif(1), y = runif(1), width = runif(1), height = runif(1), angle = runif(1) * 360)
gCurve(sin(x)/(x), sample(5, 1), sample(10:50, 1), vp = vp2,
gp = gpar(lwd = runif(1, 0, 10),
col = rgb(runif(1),
runif(1),
runif(1),
1)))
}
vp3 <- viewport(x = runif(1), y = runif(1), width = runif(1), height = runif(1), clip = "off")
gCrissCross(vp = vp3,
gp = gpar(lwd = runif(1, 0, 10),
col = rgb(runif(1),
runif(1),
runif(1),
1)))
}
randomKandinsky(10)
```

And another one:

`randomKandinsky(50)`

And another one:

`randomKandinsky(100)`

# Paint with Data

A random Kandinsky is nice. But what about turning a dataset, any dataset, into a Kandinsky painting?

If you look closely at the `randomKandinsky`

function, you’ll see it narrows down to inputting a bunch of \(U(0, 1)\) random numbers into all of the functions we’ve seen above. What if we could turn a dataset into these 0 to 1 numbers? We’d have the input we desire to draw a Kandinsky!

One way to do this normalization is by a simple formula: given any numeric \(x\) with \(max(x) > min(x)\), the result of the transformation \(\frac{x - min(x)}{max(x) - min(x)}\) will be bounded beween 0 and 1. So we could do this to each and every numeric column of our dataset. Keeping in mind we could also have `NA`

values, `character`

and `factor`

columns, we get to this function:

```
zeroOneNormalize <- function(x) {
if (!is.numeric(x)) {
if (is.factor(x)) {
x <- as.numeric(unclass(x))
} else {
x <- as.numeric(unclass(as.factor(x)))
}
}
if (length(unique(x)) == 1) {
return(rep(0.5, length(x)))
} else {
x <- (x - min(x, na.rm = TRUE)) /
(max(x, na.rm = TRUE) - min(x, na.rm = TRUE))
x[is.na(x)] <- 0.5
return(x)
}
}
```

I won’t bore you with the final code for all the utility sub-functions, you can see them at the source. I’ll walk you through the final `kandinsky`

function:

```
kandinsky <- function(df = NULL, rv = runif(1000)) {
library(grid)
library(purrr)
if (!is.null(df)) {
rv <- normalizeAndVectorize(df)
}
grid.newpage()
i <<- 0
grid.rect(gp = gpar(fill = rgb(nex(rv), nex(rv), nex(rv), nex(rv))))
nRectangles <- floor(nex(rv) * 10) + 3
nCircles <- floor(nex(rv) * 10) + 3
nTriangles <- floor(nex(rv) * 10) + 3
nArchs <- floor(nex(rv) * 10) + 3
nTiltedRectangles <- floor(nex(rv) * 10) + 3
nWaves <- floor(nex(rv) * 10) + 3
nCrissCross <- floor(nex(rv) * 3) + 1
walk(1:nRectangles, drawRectangle, rv)
walk(1:nCircles, drawCircle, rv)
walk(1:nTriangles, drawTriangle, rv)
walk(1:nArchs, drawArch, rv)
walk(1:nTiltedRectangles, drawTiltedRectangle, rv)
walk(1:nWaves, drawWave, rv)
walk(1:nCrissCross, drawCrissCross, rv)
}
```

We first normalize and vectorize the dataset into the `rv`

numeric vector in the 0 to 1 range. We then set a random background color to our canvas with `grid.rect`

. We then set the number of rectangles, circles, etc. Notice we’re always using the numbers in `rv`

, meaning that we always use the dataset for any random decision. So in a sense, nothing is random, and we’ll *always* get the same painting for a given dataset. Finally we use `walk`

from the `purrr`

package to draw all shapes.

So this is the `mtcars`

dataset as a Kandinsky painting:

```
library(kandinsky)
kandinsky(mtcars)
```

The `iris`

dataset (note this dataset has `factor`

columns):

`kandinsky(iris)`

The `airquality`

dataset:

`kandinsky(airquality)`

The `USArrests`

dataset (note this dataset has `NA`

values):

`kandinsky(USArrests)`

The `fdeaths`

dataset (note this dataset is a `ts`

Time Series object):

`kandinsky(fdeaths)`

```
## Warning in bind_rows_(x, .id): Vectorizing 'ts' elements may not preserve
## their attributes
```

And finally if you want a random Kandinsky painting, you can just leave the `df`

parameter blank and/or supply your own 0 to 1 vector for the `rv`

parameter:

`kandinsky()`

# Finish Your Painting

In my old highschool there were Kandinsky replicas hanging everywhere in the hallways. I hated highschool. I loved those Kandinskys though. To call the above paintings “Kandinskys” is a bit presumptuous, I know. But wasn’t that fun? Go ahead, install the package, paint your data, tell me if and when it breaks.

If you’re thinking these parameters are pretty self-explanatory you’re not wrong…↩