|
| 1 | +# animate garden of forking data |
| 2 | + |
| 3 | +library(rethinking) |
| 4 | +library(animation) |
| 5 | + |
| 6 | +####### |
| 7 | +# library functions |
| 8 | + |
| 9 | + |
| 10 | +polar2screen <- function( dist, origin, theta ) { |
| 11 | + ## takes dist, angle and origin and returns x and y of destination point |
| 12 | + vx <- cos(theta) * dist; |
| 13 | + vy <- sin(theta) * dist; |
| 14 | + c( origin[1]+vx , origin[2]+vy ); |
| 15 | +} |
| 16 | + |
| 17 | +screen2polar <- function( origin, dest ) { |
| 18 | + ## takes two points and returns distance and angle, from origin to dest |
| 19 | + vx <- dest[1] - origin[1]; |
| 20 | + vy <- dest[2] - origin[2]; |
| 21 | + dist <- sqrt( vx*vx + vy*vy ); |
| 22 | + theta <- asin( abs(vy) / dist ); |
| 23 | + ## correct for quadrant |
| 24 | + if( vx < 0 && vy < 0 ) theta <- pi + theta; # lower-left |
| 25 | + if( vx < 0 && vy > 0 ) theta <- pi - theta; # upper-left |
| 26 | + if( vx > 0 && vy < 0 ) theta <- 2*pi - theta; # lower-right |
| 27 | + if( vx < 0 && vy==0 ) theta <- pi; |
| 28 | + if( vx==0 && vy < 0 ) theta <- 3*pi/2; |
| 29 | + ## return angle and dist |
| 30 | + c( theta, dist ); |
| 31 | +} |
| 32 | + |
| 33 | +point.polar <- function(dist,theta,origin=c(0,0),draw=TRUE,...) { |
| 34 | + # angle theta is in radians |
| 35 | + pt <- polar2screen(dist,origin,theta) |
| 36 | + if ( draw==TRUE ) points( pt[1] , pt[2] , ... ) |
| 37 | + invisible( pt ) |
| 38 | +} |
| 39 | + |
| 40 | +line.polar <- function(dist,theta,origin=c(0,0),...) { |
| 41 | + # dist should be vector of length 2 with start and end points |
| 42 | + # theta is angle |
| 43 | + pt1 <- polar2screen(dist[1],origin,theta) |
| 44 | + pt2 <- polar2screen(dist[2],origin,theta) |
| 45 | + lines( c(pt1[1],pt2[1]), c(pt1[2],pt2[2]) , ... ) |
| 46 | +} |
| 47 | + |
| 48 | +line.short <- function(x,y,short=0.1,...) { |
| 49 | + # shortens the line segment, but retains angle and placement |
| 50 | + pt1 <- c(x[1],y[1]) |
| 51 | + pt2 <- c(x[2],y[2]) |
| 52 | + theta <- screen2polar( pt1 , pt2 )[1] |
| 53 | + dist <- screen2polar( pt1 , pt2 )[2] |
| 54 | + q1 <- polar2screen( short , pt1 , theta ) |
| 55 | + q2 <- polar2screen( dist-short , pt1 , theta ) |
| 56 | + lines( c(q1[1],q2[1]) , c(q1[2],q2[2]) , ... ) |
| 57 | +} |
| 58 | + |
| 59 | +wedge <- function(dist,start,end,pt,hedge=0.1,alpha,lwd=2,draw=TRUE,hit,...) { |
| 60 | + # start: start angle of wedge |
| 61 | + # end: end angle of wedge |
| 62 | + # pt: vector of point bg colors |
| 63 | + n <- length(pt) |
| 64 | + points.save <- matrix(NA,nrow=n,ncol=2) # x,y columns |
| 65 | + span <- abs(end-start) |
| 66 | + span2 <- span*(1 - 2*hedge) |
| 67 | + origin <- start + span*hedge |
| 68 | + gap <- span2/n |
| 69 | + theta <- origin + gap/2 |
| 70 | + border <- rep("black",length(pt)) |
| 71 | + if ( !missing(hit) ) border <- ifelse( hit==1 , 2 , 1 ) |
| 72 | + if ( !missing(alpha) ) { |
| 73 | + pt <- sapply( 1:length(pt) , function(i) col.alpha(pt[i],alpha[i]) ) |
| 74 | + border <- sapply( 1:length(pt) , function(i) col.alpha(border[i],alpha[i]) ) |
| 75 | + } |
| 76 | + for ( i in 1:n ) { |
| 77 | + points.save[i,] <- point.polar( dist , theta , pch=21 , lwd=lwd , bg=pt[i] , col=border[i] , draw=draw, ... ) |
| 78 | + theta <- theta + gap |
| 79 | + } |
| 80 | + points.save |
| 81 | +} |
| 82 | + |
| 83 | +point_on_line <- function( x , y , p ) { |
| 84 | + X <- x[1]*(1-p) + x[2]*p |
| 85 | + Y <- y[1]*(1-p) + y[2]*p |
| 86 | + c(X,Y) |
| 87 | +} |
| 88 | + |
| 89 | +###### |
| 90 | +# garden draw with progression |
| 91 | +# the progression argument is a vector of how deep to draw each layer in garden |
| 92 | + |
| 93 | +garden <- function( arc , possibilities , data , alpha.fade = 0.25 , col.open=2 , col.closed=1 , hedge=0.1 , hedge1=0 , newplot=TRUE , plot.origin=FALSE , cex=1.5 , lwd=2 , adj.cex , adj.lwd , lwd.dat=1 , ring_dist , progression , prog_dat , xlim=c(-1,1) , ylim=c(-1,1) , thresh=0.2 , ... ) { |
| 94 | + |
| 95 | + poss.cols <- ifelse( possibilities==1 , rangi2 , "white" ) |
| 96 | + |
| 97 | + if ( missing(adj.cex) ) adj.cex=rep(1,length(data)) |
| 98 | + if ( missing(adj.lwd) ) adj.lwd=rep(1,length(data)) |
| 99 | + |
| 100 | + if ( newplot==TRUE ) { |
| 101 | + # empty plot |
| 102 | + par(mgp = c(1.5, 0.5, 0), mar = c(1, 1, 1, 1) + 0.1, tck = -0.02) |
| 103 | + plot( NULL , xlim=xlim , ylim=ylim , bty="n" , xaxt="n" , yaxt="n" , xlab="" , ylab="" ) |
| 104 | + } |
| 105 | + |
| 106 | + if ( plot.origin==TRUE ) point.polar( 0 , 0 , pch=16 ) |
| 107 | + |
| 108 | + N <- length(data) |
| 109 | + n_poss <- length(possibilities) |
| 110 | + |
| 111 | + # draw rings |
| 112 | + |
| 113 | + # compute distance out for each ring |
| 114 | + # use golden ratio 1.618 for each successive ring |
| 115 | + goldrat <- 1.618 |
| 116 | + if ( missing(ring_dist) ) { |
| 117 | + ring_dist <- rep(1,N) |
| 118 | + if ( N>1 ) |
| 119 | + for ( i in 2:N ) ring_dist[i] <- ring_dist[i-1]*goldrat |
| 120 | + ring_dist <- ring_dist / sum(ring_dist) |
| 121 | + #ring_dist <- cumsum(ring_dist) |
| 122 | + } |
| 123 | + |
| 124 | + if ( length(alpha.fade)==1 ) alpha.fade <- rep(alpha.fade,N) |
| 125 | + if ( length(col.open)==1 ) col.open <- rep(col.open,N) |
| 126 | + if ( length(col.closed)==1 ) col.closed <- rep(col.closed,N) |
| 127 | + |
| 128 | + draw_wedge <- function(r,hit_prior,arc2,hedge=0.1,hedge1=0,lines_to) { |
| 129 | + |
| 130 | + # is each path clear? |
| 131 | + hit <- hit_prior * ifelse( possibilities==data[r] , 1 , 0 ) |
| 132 | + |
| 133 | + # transparency for blocked paths |
| 134 | + alpha <- ifelse( hit==1 , 1 , alpha.fade[r] ) |
| 135 | + the_col <- ifelse( hit==1 , col.open , col.closed ) |
| 136 | + |
| 137 | + # draw wedge |
| 138 | + hedge_use <- ifelse( r==1 , hedge1 , hedge ) |
| 139 | + |
| 140 | + do_draw <- TRUE |
| 141 | + if ( progression[r] < 1 ) do_draw <- FALSE |
| 142 | + |
| 143 | + rd <- 0 |
| 144 | + for ( rdi in 1:r ) rd <- rd + ring_dist[rdi] |
| 145 | + if ( prog_dat[r] == 1 ) |
| 146 | + pts <- wedge( rd , arc2[1] , arc2[2] , poss.cols , hedge=hedge_use , alpha=alpha , cex=cex*adj.cex[r] , lwd=lwd*adj.lwd[r] , draw=do_draw , hit=hit ) |
| 147 | + else |
| 148 | + pts <- wedge( rd , arc2[1] , arc2[2] , poss.cols , hedge=hedge_use , alpha=alpha , cex=cex*adj.cex[r] , lwd=lwd*adj.lwd[r] , draw=do_draw ) |
| 149 | + |
| 150 | + if ( N > r ) { |
| 151 | + # draw next layer |
| 152 | + span <- abs( arc2[1] - arc2[2] ) / n_poss |
| 153 | + for ( j in 1:n_poss ) { |
| 154 | + # for each possibility, draw the next wedge |
| 155 | + # recursion handles deeper wedges |
| 156 | + new_arc <- c( arc2[1]+span*(j-1) , arc2[1]+span*j ) |
| 157 | + pts2 <- draw_wedge(r+1,hit[j],new_arc,hedge,lines_to=pts[j,]) |
| 158 | + }#j |
| 159 | + }# N>r |
| 160 | + |
| 161 | + # draw lines back to parent point |
| 162 | + if ( !missing(lines_to) ) { |
| 163 | + for ( k in 1:n_poss ) { |
| 164 | + #alpha_l <- ifelse( hit==1 , 1 , alpha.fade[r] ) |
| 165 | + # path line |
| 166 | + |
| 167 | + if ( progression[r] > thresh ) { |
| 168 | + ptend <- point_on_line( c(lines_to[1],pts[k,1]) , c(lines_to[2],pts[k,2]) , progression[r] ) |
| 169 | + line.short( c(lines_to[1],ptend[1]) , c(lines_to[2],ptend[2]) , lwd=lwd*adj.lwd[r] , short=0.04 , col=col.closed[1] ) |
| 170 | + } |
| 171 | + if ( prog_dat[r] > thresh && hit[k]==1 ) { |
| 172 | + ptend <- point_on_line( c(lines_to[1],pts[k,1]) , c(lines_to[2],pts[k,2]) , prog_dat[r] ) |
| 173 | + line.short( c(lines_to[1],ptend[1]) , c(lines_to[2],ptend[2]) , lwd=lwd*adj.lwd[r]+lwd.dat , short=0.04 , col=col.open[1] ) |
| 174 | + } |
| 175 | + } |
| 176 | + } # lines_to |
| 177 | + |
| 178 | + return(pts) |
| 179 | + |
| 180 | + } |
| 181 | + |
| 182 | + pts1 <- draw_wedge(1,1,arc=arc,hedge=hedge,lines_to=c(0,0)) |
| 183 | + |
| 184 | + invisible(pts1) |
| 185 | + |
| 186 | +} |
| 187 | + |
| 188 | + |
| 189 | +goldrat <- 1.618 |
| 190 | +ring_dist <- rep(1,3) |
| 191 | +for ( i in 2:3 ) ring_dist[i] <- ring_dist[i-1]*goldrat |
| 192 | +ring_dist <- ring_dist / sum(ring_dist) |
| 193 | +#ring_dist <- cumsum(ring_dist) |
| 194 | + |
| 195 | +dat <- c(1,0,1) |
| 196 | +dat <- c(-1,-1,-1) |
| 197 | + |
| 198 | +arc <- c( 0 , pi ) |
| 199 | +garden( |
| 200 | + arc = arc, |
| 201 | + possibilities = c(0,0,0,1), |
| 202 | + data = dat, |
| 203 | + hedge = 0.05, |
| 204 | + ring_dist=ring_dist, |
| 205 | + alpha.fade=1, |
| 206 | + progression=c(1,1,0.1) |
| 207 | +) |
| 208 | + |
| 209 | +#### |
| 210 | +# grow garden animation |
| 211 | + |
| 212 | +ani.record(reset = TRUE) |
| 213 | + |
| 214 | +poss <- c(0,1,1,1) |
| 215 | +nsteps <- 20 |
| 216 | +prog_seq <- seq( from=0 , to=1 , length=nsteps ) |
| 217 | +prog <- c(0,0,0) |
| 218 | +for ( rr in 1:3 ) { |
| 219 | + for ( p in prog_seq ) { |
| 220 | + prog[rr] <- p |
| 221 | + garden( |
| 222 | + arc = arc, |
| 223 | + possibilities = poss, |
| 224 | + data = c(1,0,0), |
| 225 | + hedge = 0.05, |
| 226 | + ring_dist=ring_dist, |
| 227 | + alpha.fade=1, |
| 228 | + progression=prog, |
| 229 | + prog_dat=c(0,0,0), |
| 230 | + ylim=c(0,1), |
| 231 | + thresh=0.3 |
| 232 | + ) |
| 233 | + ani.record() |
| 234 | + } |
| 235 | +} |
| 236 | + |
| 237 | +oopts = ani.options(interval = 0.1) |
| 238 | +ani.replay() |
| 239 | + |
| 240 | +# convert -alpha remove -background white -delay 10 -loop 0 frame*.png globe_tossing.gif |
| 241 | +# convert -delay 0 animated.gif animated2.gif |
| 242 | + |
| 243 | +#### |
| 244 | +# data path animation |
| 245 | + |
| 246 | +#ani.record(reset = TRUE) |
| 247 | +prog_seq <- seq( from=0 , to=1 , length=nsteps ) |
| 248 | +prog <- c(1,1,1) |
| 249 | +prog2 <- c(0,0,0) |
| 250 | +for ( rr in 1:3 ) { |
| 251 | + for ( p in prog_seq ) { |
| 252 | + prog2[rr] <- p |
| 253 | + garden( |
| 254 | + arc = arc, |
| 255 | + possibilities = poss, |
| 256 | + data = c(1,0,1), |
| 257 | + hedge = 0.05, |
| 258 | + ring_dist=ring_dist, |
| 259 | + alpha.fade=1, |
| 260 | + progression=prog, |
| 261 | + prog_dat=prog2, |
| 262 | + ylim=c(0,1), |
| 263 | + lwd.dat=2, |
| 264 | + thresh=0.3 |
| 265 | + ) |
| 266 | + ani.record() |
| 267 | + } |
| 268 | +} |
| 269 | + |
| 270 | +oopts = ani.options(interval = 0.1) |
| 271 | +ani.replay() |
| 272 | + |
| 273 | +# ani.saveqz(dpi=150) |
| 274 | +# convert -alpha remove -background white -delay 10 -loop 0 frame*.png garden.gif |
| 275 | + |
0 commit comments