-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy patheast-R-art.Rmd
More file actions
558 lines (403 loc) Β· 12.7 KB
/
Copy patheast-R-art.Rmd
File metadata and controls
558 lines (403 loc) Β· 12.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
---
title: "Art with R: <span class='stripy-text'>EastR Edition</span>"
author: "Adam Spannbauer"
date: "`r Sys.Date()`"
output:
html_document:
css: "style.css"
theme: flatly
toc: true
toc_depth: 2
toc_float: true
includes:
in_header: "./favicon.html"
---
# Goals of document
1. <span class='stripy-text'>Have fun</span>
2. Try and make a pretty egg picture
3. Accidentally practice and learn some coding things
A glimpse of where we're going:
<center><img src="imgs/wobbly-eggs.png" width="20%"></center>
# Getting started
## Packages
```{r message=FALSE}
# For some nice easy plotting
library(ggplot2)
# For some "perlin noise" to rotate our eggs.... huh?
library(ambient)
```
## Plotting with ggplot2 basics
```{r}
head(diamonds, 3)
```
`ggplot()` makes your canvas and `geom_<shape>()` puts your art on the canvas.
The `aes()` (aesthetic) is where you tell `ggplot()` your `x` and `y`.
```{r}
ggplot(diamonds, aes(x = carat, y = price)) +
geom_point()
```
You can customize the plots p easily after they're made by `+`-ing things on. Us as beautimus artists might not want our canvas to have all the data looking things. We can use `theme_void()` to clean everything out.
```{r}
ggplot(diamonds, aes(x = carat, y = price)) +
geom_point() +
theme_void()
```
Last 2 `ggplot2` tactics to know about know.
1. If using multiple data sources we can specify `aes()` in the `geom_<shape>()`.
2. We can color!
```{r}
# my diamond is big but cheap
my_diamond <- data.frame(carat = 5, price = 5000)
ggplot() +
geom_point(data = diamonds, aes(x = carat, y = price)) +
geom_point(data = my_diamond, aes(x = carat, y = price), color = "#ff8200") +
theme_void()
```
Okay, using all of this let's make some eggs!
# It's egg time
## Eggs from math o_O?
Hardcore research*:
1. Googled "math formula to draw an egg"
* [See here](https://letmegooglethat.com/?q=math+formula+to+draw+an+egg)
2. Saw some math:
* [See here](https://www.mathematische-basteleien.de/eggcurves.htm#:~:text=You%20can%20develop%20the%20shape,*t(x)%3D1.)
<sub>*i'm oldschool so i used google instead of chatgpt... for this...</sub>
Result of research:
> You can develop the shape of a hen egg, if you change the equation of a oval a little. You multiply y or yΒ² by a suitable term t(x), so that y becomes smaller on the right side of the y-axis and larger on the left side. y(x=0) must not be changed.
> The equation of the ellipse e.g. $\frac{x^2}{9} + \frac{y^2}{4} = 1$ change to $\frac{x^2}{9} + \frac{y^2}{4} \dot{} t(x) = 1$
>
> ...[example t(x)] $t(x) = \frac{1}{1 - 0.2x}$
Okay... we can definitely write a function for $t(x)$
```{r}
# shoutout baker skateboards: https://www.youtube.com/watch?v=cdbfSfnRDX4
t_func <- function(x) {
1 / (1 - 0.2 * x)
}
```
How to make a function from this: $\frac{x^2}{9} + \frac{y^2}{4} \dot{} t(x) = 1$... that umm gets trickier. Maybe we go new school on this. I've ChatGPT'd:
> this is a formula for an egg based on an ellipse formula: $\frac{x^2}{9} + \frac{y^2}{4} \dot{} t(x) = 1$. write an R function that takes in a center point (x and y), an x radius, a y radius, and a function (named t) as parameters to draw an ellipse egg like this
How did I know what to ask? ....
* I know a mathy word for "oval" is ellipse
* I know I want to be able to place these suckers so controlling the center point sounds nice
* I know I want to be able to control the size of em and ellipses have 2 size controls for their 2 main axes
I modified the returned code to suit our needs better.
```{r}
egg_pts <- function(cx, cy, xr = 0.9, yr = 0.6) {
theta <- seq(0, 2 * pi, length.out = 100)
# Calculate x and y coordinates of points on the ellipse
x <- xr * cos(theta)
y <- yr * sin(theta)
# Adjust y coordinates using the t function
y <- y * t_func(x)
pts <- data.frame(x = x + cx, y = y + cy)
return(pts)
}
```
## Drawing our first egg
To use the function we'll pass in a location for our egg to live.
```{r}
pts <- egg_pts(0, 0)
head(pts)
```
Did it work....?
```{r}
ggplot(pts, aes(x = x, y = y)) +
geom_point() +
labs(title = "it's... it's... beautiful!")
```
Holy moly we created egg.
But... we prolly don't want dot eggs... instead of `geom_point()` we can use `geom_polygon()` to nicely connect them.
```{r}
ggplot(pts, aes(x = x, y = y)) +
geom_polygon() +
theme_void() +
labs(title = "BOOM! egg :)")
```
## Extending the idea to more eggs
You know what's cooler than 1 egg....
<center><img src="https://ih1.redbubble.net/image.3908402082.9055/raf,360x360,075,t,fafafa:ca443f4786.jpg" width="10%"></center>
```{r}
pts1 <- egg_pts(0, 0)
pts2 <- egg_pts(1, 2)
ggplot() +
geom_polygon(data = pts1, aes(x = x, y = y)) +
geom_polygon(data = pts2, aes(x = x, y = y)) +
theme_void() +
labs(title = "oh nooo.... we squished em")
```
We can use `coord_fixed(ratio = 1)` to tell ggplot we want an "aspect ratio" of 1 to unsquish.
```{r}
pts1 <- egg_pts(0, 0)
pts2 <- egg_pts(1, 2)
# some changes have to be made here...
ggplot() +
geom_polygon(data = pts1, aes(x = x, y = y)) +
geom_polygon(data = pts2, aes(x = x, y = y)) +
theme_void() +
coord_fixed(ratio = 1)
```
Let's get forward thinking. I want to have as many eggs as I want plotted at once. Let's rewrite our 2 egg code to be more flexible and handle any number of eggs!
```{r}
# some changes have to be made here...
eggs <- list(pts1, pts2)
canvas <- ggplot()
for (egg in eggs) {
canvas <- canvas +
geom_polygon(data = egg, aes(x = x, y = y))
}
canvas +
theme_void() +
coord_fixed(ratio = 1)
```
## Rotating our eggs!
Got dang we're cooking with gas.
Next! I don't want my eggs all laying down! We should rotate these puppies.
Rotating things can look pretty scary at first, but it's a super powerful idea and comes up in some useful stats techniques like PCA ([animation](https://adamspannbauer.github.io/pca_animation/) & [more info](https://towardsdatascience.com/a-one-stop-shop-for-principal-component-analysis-5582fb7e0a9c))!.
Rotating math is of scope for this doc, but [this is a phenomenal video for more on matrix transformations including rotation](https://www.youtube.com/watch?v=kYB8IZa5AuE).
```{r}
rotated_egg_pts <- function(cx, cy, xr = 0.9, yr = 0.6, rotate_degrees = 0) {
theta <- seq(0, 2 * pi, length.out = 100)
# Calculate x and y coordinates of points on the ellipse
x <- xr * cos(theta)
y <- yr * sin(theta)
# Adjust y coordinates using the t function
y <- y * t_func(x)
# Rotate code!
theta <- rotate_degrees * (pi / 180) # convert degress to radians
rotation_matrix <- matrix(
c(
cos(theta), -sin(theta), # say where we want
sin(theta), cos(theta) # our unit vectors to be
),
ncol = 2
)
rotated <- cbind(x, y) %*% rotation_matrix # apply!
x <- rotated[, 1] # pull back out x
y <- rotated[, 2] # pull back out y
# End of Rotate code!
pts <- data.frame(x = x + cx, y = y + cy)
return(pts)
}
```
So... does it... does it... work?
```{r}
pts1 <- rotated_egg_pts(0, 0, rotate_degrees = -90)
pts2 <- rotated_egg_pts(0, 1, rotate_degrees = 180)
eggs <- list(pts1, pts2)
canvas <- ggplot()
for (egg in eggs) {
canvas <- canvas + geom_polygon(data = egg, aes(x = x, y = y))
}
canvas +
theme_void() +
coord_fixed(ratio = 1)
```
<center><img src="https://i.pinimg.com/originals/ef/6b/3d/ef6b3df7817179778a0bc5285aa18295.gif" width="20%"></center>
## Egg grid!!!!
We need more eggs!!
Let's create a for loop that can make a row of eggs. The for loop just needs to update the x location of each egg.
```{r}
xs <- -5:5
n_xs <- length(xs)
eggs <- list()
for (i in 1:n_xs) {
x <- xs[i]
egg <- rotated_egg_pts(x, 0, rotate_degrees = -90)
eggs[[i]] <- egg
}
```
Something happened, that's for sure. Let's plot.
```{r}
canvas <- ggplot()
for (egg in eggs) {
canvas <- canvas +
geom_polygon(data = egg, aes(x = x, y = y))
}
canvas +
theme_void() +
coord_fixed(ratio = 1)
```
MORE! Double for loop for rows and columns!
```{r}
# Multiplication is spacing things out here
xs <- -5:5 * 1.5
ys <- -5:5 * 2.2
n_xs <- length(xs)
n_ys <- length(ys)
eggs <- list()
egg_num <- 1 # keep track of how many eggs we've made
for (i in 1:n_xs) {
for (j in 1:n_ys) {
x <- xs[i]
y <- ys[j]
egg <- rotated_egg_pts(x, y, rotate_degrees = -90)
eggs[[egg_num]] <- egg
egg_num <- egg_num + 1
}
}
length(eggs)
```
Show me them eggs!
```{r}
canvas <- ggplot()
for (egg in eggs) {
canvas <- canvas +
geom_polygon(data = egg, aes(x = x, y = y))
}
canvas +
theme_void() +
coord_fixed(ratio = 1)
```
## Make it more Easter with pastels
Okay... that's something!! It's missing an easter vibe though. We need pastels.
```{r}
palette <- c("#69ffb9", "#76ecfb", "#c1fda0", "#9386e6", "#f298f4")
xs <- -5:5 * 1.5
ys <- -5:5 * 2.2
n_xs <- length(xs)
n_ys <- length(ys)
eggs <- list()
egg_num <- 1 # keep track of how many eggs we've made
for (i in 1:n_xs) {
for (j in 1:n_ys) {
x <- xs[i]
y <- ys[j]
egg <- rotated_egg_pts(x, y, rotate_degrees = -90)
# Pick a random color and save
color <- sample(palette, 1)
egg$color <- color
eggs[[egg_num]] <- egg
egg_num <- egg_num + 1
}
}
```
Modify our plot code to change the fill based on the new color column
```{r}
canvas <- ggplot()
for (egg in eggs) {
canvas <- canvas + geom_polygon(data = egg, aes(x = x, y = y), fill = egg$color)
}
canvas +
theme_void() +
coord_fixed(ratio = 1)
```
This is the end of our plotting code changes. It'd clean things up to hide it in a function.
```{r}
plot_eggs <- function(eggs) {
canvas <- ggplot()
for (egg in eggs) {
canvas <- canvas + geom_polygon(data = egg, aes(x = x, y = y), fill = egg$color)
}
canvas +
theme_void() +
coord_fixed(ratio = 1)
}
plot_eggs(eggs)
```
:) - cute! Where do we go from here? tbh it could be over already but here are some ideas.
## Wobbly eggs :)
Personally, I think being too neat is boring... what about wobbly eggs?
```{r}
xs <- -5:5 * 1.5
ys <- -5:5 * 2.2
n_xs <- length(xs)
n_ys <- length(ys)
eggs <- list()
egg_num <- 1 # keep track of how many eggs we've made
for (i in 1:n_xs) {
for (j in 1:n_ys) {
x <- xs[i]
y <- ys[j]
# Randomize rotation
rotate <- rnorm(1, mean = -90, sd = 10)
egg <- rotated_egg_pts(x, y, rotate_degrees = rotate)
# Pick a random color and save
color <- sample(palette, 1)
egg$color <- color
eggs[[egg_num]] <- egg
egg_num <- egg_num + 1
}
}
plot_eggs(eggs)
```
What if we got wobblier or un-wobblier based on y??
```{r}
xs <- -5:5 * 1.5
ys <- 0:-10 * 2
n_xs <- length(xs)
n_ys <- length(ys)
eggs <- list()
egg_num <- 1 # keep track of how many eggs we've made
for (i in 1:n_xs) {
for (j in 1:n_ys) {
x <- xs[i]
y <- ys[j]
# Randomize rotation
rotate_sd <- abs(y) * 2
rotate <- rnorm(1, mean = -90, sd = rotate_sd)
egg <- rotated_egg_pts(x, y, rotate_degrees = rotate)
# Pick a random color and save
color <- sample(palette, 1)
egg$color <- color
eggs[[egg_num]] <- egg
egg_num <- egg_num + 1
}
}
plot_eggs(eggs)
```
You could have this randomness be a little less random...
One way to end up with some nice organic feeling randomness is to use "noise". In particular, "Perlin noise" is used often. This noise has been used to generate terrain in video game, simulate trees moving in the wind, or we can use it to rotate our eggs!
```{r}
xs <- -10:15 * 1.5
ys <- -10:15 * 2
n_xs <- length(xs)
n_ys <- length(ys)
# Generate the values that will rotate the eggs
noise <- noise_perlin(c(n_xs, n_ys), frequency = 0.1)
eggs <- list()
egg_num <- 1 # keep track of how many eggs we've made
for (i in 1:n_xs) {
for (j in 1:n_ys) {
x <- xs[i]
y <- ys[j]
# Randomize rotation
# (1) pulling noise value, (2) scaling it up, &(3) centering at 90,
rotate <- -90 + noise[i, j] * 120
egg <- rotated_egg_pts(x, y, rotate_degrees = rotate)
# Pick a random color and save
color <- sample(palette, 1)
egg$color <- color
eggs[[egg_num]] <- egg
egg_num <- egg_num + 1
}
}
plot_eggs(eggs)
```
Go vols!
```{r}
palette <- c("#ff8200", "#58595b", "#58595b", "#58595b", "#58595b")
xs <- -10:15 * 1.5
ys <- -10:15 * 2
n_xs <- length(xs)
n_ys <- length(ys)
# Generate the values that will rotate the eggs
noise <- noise_perlin(c(n_xs, n_ys), frequency = 0.1)
eggs <- list()
egg_num <- 1 # keep track of how many eggs we've made
for (i in 1:n_xs) {
for (j in 1:n_ys) {
x <- xs[i]
y <- ys[j]
# Randomize rotation
# (1) pulling noise value, (2) scaling it up, &(3) centering at 90,
rotate <- -90 + noise[i, j] * 120
egg <- rotated_egg_pts(xs[i], ys[j], rotate_degrees = rotate)
# Pick a random color and save
color <- sample(palette, 1)
egg$color <- color
eggs[[egg_num]] <- egg
egg_num <- egg_num + 1
}
}
plot_eggs(eggs)
```