<p><div><div><div><div data-show-faces="false" data-height="21" data-width="100" data-layout="button_count" data-send="true" data-href="https://www.r-bloggers.com/transformr-age-of-spatial/"></div></div><div></div></div><div></div></div>
<div>
(This article was first published on <strong><a href="https://www.data-imaginist.com/2018/transformr-age-of-spatial/"> Data Imaginist</a></strong>, and kindly contributed to <a rel="nofollow" href="https://www.r-bloggers.com/">R-bloggers)</a> </div>
<p><img align="right" src="https://i1.wp.com/www.data-imaginist.com/assets/images/transformr_logo_small.png?w=456&ssl=1" data-recalc-dims="1"></p>
<p>Once again, I gives me great pleasure to announce a new package has joined CRAN.<br>
<code>transformr</code> is the spatial brother of <code>tweenr</code> and as with the <code>tweenr</code> update<br>
a few months ago, this package is very much driven by the infrastructural needs<br>
of <code>gganimate</code>. It is probably the last piece needed before I can begin<br>
preparing <code>gganimate</code> for CRAN, so if you are waiting for that there is indeed<br>
reason for celebration.</p>
<div>
<h2>Becoming Spatial</h2>
<p>As written above, <code>transformr</code> is <code>tweenr</code> for spatial data (spatial being used<br>
in a very broad sense as any data that is partly coordinates). To understand<br>
what this means we’ll briefly have to touch on a core concept of <code>tweenr</code>. What<br>
is never said out loud, but generally implied, is that <code>tweenr</code> treats all<br>
columns of the data frame as independent. This is generally a sound principle as<br>
you don’t want values from other columns to influence how e.g. the colour<br>
transitions between black and blue. As far as spatial is concerned, this<br>
approach also works fine as each row in the data frame encodes a single<br>
independent point in space or if there’s a one-to-one mapping between points<br>
in a polygon. Alas, the devil’s in the detail, and <code>tweenr</code> breaks down in<br>
magnificent ways if you try to tween between more complicated and heterogeneous<br>
shapes, e.g. a star and a circle. This is not something unique to <code>tweenr</code>, mind<br>
you, d3.js also has this limitation. The problems in d3 led<br>
<a rel="nofollow" href="https://github.com/veltman" target="_blank">Noah Veltman</a> to develop the<br>
<a rel="nofollow" href="https://github.com/veltman/flubber" target="_blank">flubber</a> javascript library. His reasons<br>
for developing it is succintly described in the animation below, grabbed from<br>
the readme of flubber</p>
<p><img src="https://i1.wp.com/user-images.githubusercontent.com/2120446/27014160-e0ce7c04-4ea7-11e7-8da4-5dde839290eb.gif?w=456&ssl=1" data-recalc-dims="1"></p>
</div>
<div>
<h2>The Trials of the Polygon</h2>
<p>So, what’s the deal with polygons exactly. Why don’t they just do as you expect<br>
them to and morph naturally from one to the other. That sad state of affair is<br>
that there are multiple reasons for that:</p>
<ol>
<li>There might be discrepancy between the number of points that make up the two<br>
polygons. This may lead to part of the shape simply appearing or disappearing at<br>
the start or end of the tween.</li>
<li>The winding of the polygons may have a different angular offset and/or<br>
direction. This means that the tween will include rotatation and/or inversion,<br>
something that is often undesirable.</li>
<li>There may be a discrepancy in the number of polygons that make up the two<br>
shapes you tween between and/or a discrepancy between the number of holes. As<br>
with 1. this may lead to parts of the shapes suddenly appearing or disappearing<br>
during the tween.</li>
</ol>
</div>
<div>
<h2>Running the Gauntlet</h2>
<p><code>transformr</code> tries to solve the three problems above in much the same way as<br>
flubber does, at least conceptually. There are enough differences between how<br>
Javascript and R (as well as d3 and <code>tweenr</code>) works with data, that I decided to<br>
only take the ideas behind flubber and implement them in my own way, in a manner<br>
fitting for R, rather than doing a direct port of the library. This means that<br>
you cannot expect the two libraries to behave equivalently. Below is, at a very<br>
high level, what <code>transformr</code> does to address the 3 problems outlined above:</p>
<ol>
<li>Points are added along the edges of the shape with the fewest corners until<br>
the number of points matches between the shapes. Points are added so that long<br>
edges will be divided more often than short edges in order to even out the edge<br>
lengths of the final shape. Further, if any shape has fewer than a given number<br>
of corners, points will be added (following the same strategy) until the number<br>
of corners is reached.</li>
<li>After the number of points are evened out, the winding direction is matched<br>
between the shapes (as clockwise), and the last shape is rotated until the<br>
squared distance between point pairs of the two shapes is minimised.</li>
<li>This is adressed first (but is the least prevalent problem so it is mentioned<br>
last). If there are different number of polygons in the two states you wish to<br>
tween between, the polygons in the state with the fewest polygons is cut until<br>
the number matches. Once again, the cuts are distributed so that large polygons<br>
are cut more often than small. After the cutting, polygons between the states<br>
are matched by minimising distance and area difference. If there are differences<br>
in the number of holes in the matched polygons zero-area holes are inserted at<br>
the gravitational center of the polygon with the fewest holes until the number<br>
matches.</li>
</ol>
</div>
<div>
<h2>The Ways of the Transformr</h2>
<p>At this point we have only talked about shapes (and polygons), so let’s get a<br>
bit more concrete. <code>transformr</code> currently recognises three data types:<br>
<em>polygons</em>, <em>paths</em>, and <em>simple features</em>. Polygons encompass simple polygons<br>
as well as polygons with any number of holes. Paths can be either single or<br>
multipaths. Simple features as implemented by the <code>sf</code> package are supported,<br>
currently covering the (multi)point, (multi)path, and (multi)polygon types.</p>
<p>In terms of tween type support, <code>transformr</code> currently extends the<br>
<code>tween_state()</code> API from <code>tweenr</code> but support for the other types of tweeners<br>
will be added with time.</p>
</div>
<div>
<h2>Some Examples</h2>
<p>At this point an example is probably in order. We’ll start with what we first<br>
identified as a problematic case: morphing between a circle and a star:</p>
<pre><code>library(transformr)
library(ggplot2)

# Helpers included in transformer
circle &lt;- poly_circle()
star &lt;- poly_star()

# The data is a simple data.frame as you would feed into ggplot2
head(star)</code></pre>
<pre><code>##              x          y id
## 1 0.000000e+00  1.0000000  1
## 2 2.938926e-01  0.4045085  1
## 3 9.510565e-01  0.3090170  1
## 4 4.755283e-01 -0.1545085  1
## 5 5.877853e-01 -0.8090170  1
## 6 6.123234e-17 -0.5000000  1</code></pre>
<pre><code># We use tween_polygon to morph between the two
morph &lt;- tween_polygon(circle, star, 
                       ease = 'linear',
                       id = id,
                       nframes = 12)

# You get back a data.frame with the same special columns as with tweenr
head(morph)</code></pre>
<pre><code>##            x         y id .id .phase .frame
## 1 0.00000000 1.0000000  1   1    raw      1
## 2 0.01745241 0.9998477  1   1    raw      1
## 3 0.03489950 0.9993908  1   1    raw      1
## 4 0.05233596 0.9986295  1   1    raw      1
## 5 0.06975647 0.9975641  1   1    raw      1
## 6 0.08715574 0.9961947  1   1    raw      1</code></pre>
<pre><code># Let's see the result
ggplot(morph) + 
  geom_polygon(aes(x = x, y = y, group = id), fill = NA, colour = 'black') + 
  facet_wrap(~.frame, labeller = label_both, ncol = 3) + 
  theme_void()</code></pre>
<p><img src="https://i0.wp.com/www.data-imaginist.com/post/2018-12-03-transformr-age-of-spatial_files/figure-html/unnamed-chunk-2-1.png?w=450&ssl=1" data-recalc-dims="1"></p>
<p>What would happen if we upped the stakes a bit? Let’s try with a star with a<br>
hole, morphing into three circles:</p>
<pre><code>circles &lt;- poly_circles()
star_hole &lt;- poly_star_hole()

morph &lt;- tween_polygon(circles, star_hole, 
                       ease = 'linear',
                       id = id, 
                       nframes = 12,
                       match = FALSE)

ggplot(morph) + 
  geom_polygon(aes(x = x, y = y, group = id), fill = NA, colour = 'black') + 
  facet_wrap(~.frame, labeller = label_both, ncol = 3) + 
  theme_void()</code></pre>
<p><img src="https://i1.wp.com/www.data-imaginist.com/post/2018-12-03-transformr-age-of-spatial_files/figure-html/unnamed-chunk-3-1.png?w=450&ssl=1" data-recalc-dims="1"></p>
<p>We introduced a new argument in <code>tween_polygon()</code> here. <code>match</code> is used to<br>
define whether polygons are matched by the value of <code>id</code> or whether all polygons<br>
in the first state should somehow morph into all polygons in the last state. If<br>
we set <code>match = TRUE</code>, we can use the <code>enter</code> and <code>exit</code> argument to define what<br>
should happen to unmatched polygons</p>
<pre><code>morph &lt;- tween_polygon(circles, star_hole, 
                       ease = 'linear',
                       id = id, 
                       nframes = 12,
                       match = TRUE,
                       exit = function(.x) transform(.x, x = mean(x), y = mean(y)))

ggplot(morph) + 
  geom_polygon(aes(x = x, y = y, group = id), fill = NA, colour = 'black') + 
  facet_wrap(~.frame, labeller = label_both, ncol = 3) + 
  theme_void()</code></pre>
<p><img src="https://i2.wp.com/www.data-imaginist.com/post/2018-12-03-transformr-age-of-spatial_files/figure-html/unnamed-chunk-4-1.png?w=450&ssl=1" data-recalc-dims="1"></p>
<p>You’ll see a weird glitch above with the hole in the star reaching out to the<br>
edge, but this is simply <code>ggplot2</code> not knowing how to deal with holed polygons<br>
in <code>geom_polygon()</code> — I’ll handle that in another post…</p>
<p>What is not shown above is that <code>transformr</code> and <code>tween_polygon()</code> works well<br>
together with <code>keep_state()</code> from <code>tweenr</code> and that it is pipe-able, but if you<br>
are used to <code>tween_state()</code> this will all come natural…</p>
<p>While path and sf morphing works in much the same way as shown above, I’ll<br>
quickly show case it for completeness:</p>
<pre><code>spiral &lt;- path_spiral()
waves &lt;- path_waves()

morph &lt;- tween_path(spiral, waves,
                    ease = 'linear',
                    nframes = 12, 
                    id = id,
                    match = FALSE)

ggplot(morph) + 
  geom_path(aes(x = x, y = y, group = id), colour = 'black') + 
  facet_wrap(~.frame, labeller = label_both, ncol = 3) + 
  theme_void()</code></pre>
<p><img src="https://i0.wp.com/www.data-imaginist.com/post/2018-12-03-transformr-age-of-spatial_files/figure-html/unnamed-chunk-5-1.png?w=450&ssl=1" data-recalc-dims="1"></p>
<pre><code>circle_st &lt;- sf::st_sf(geometry = sf::st_sfc(poly_circle(st = TRUE)))
north_carolina &lt;- sf::st_read(system.file(&quot;shape/nc.shp&quot;, package = &quot;sf&quot;), 
                              quiet = TRUE)
north_carolina &lt;- st_normalize(sf::st_combine(north_carolina))
north_carolina &lt;- sf::st_sf(geometry = sf::st_sfc(north_carolina))

morph &lt;- tween_sf(north_carolina, circle_st,
                  ease = 'linear',
                  nframes = 12)

ggplot(morph) + 
  geom_sf(aes(geometry = geometry), colour = 'white', fill = 'black', size = .1) + 
  facet_wrap(~.frame, labeller = label_both, ncol = 3) + 
  coord_sf(datum = NULL) + 
  theme_void()</code></pre>
<p><img src="https://i2.wp.com/www.data-imaginist.com/post/2018-12-03-transformr-age-of-spatial_files/figure-html/unnamed-chunk-6-1.png?w=450&ssl=1" data-recalc-dims="1"></p>
<p>As can be seen, <code>transformr</code> can handle most of the things you choose to to<br>
throw at it, when it comes to morphing between different shapes. It is used<br>
under the hood in <code>gganimate</code> to power polygon, path, and sf geom transitions<br>
(and derivatives thereof), but can just as well be used directly in the same way<br>
as <code>tweenr</code> can…</p>
<p>I do hope you’ll enjoy <code>transformr</code> either simply through the magic of<br>
<code>gganimate</code> or by playing with it directly — the results can be quite<br>
mesmerizing…</p>
</div>
	<p>
<div>
<div>To <strong>leave a comment</strong> for the author, please follow the link and comment on their blog: <strong><a href="https://www.data-imaginist.com/2018/transformr-age-of-spatial/"> Data Imaginist</a></strong>.</div>
<hr>
<a rel="nofollow" href="https://www.r-bloggers.com/">R-bloggers.com</a> offers <strong><a rel="nofollow" href="https://feedburner.google.com/fb/a/mailverify?uri=RBloggers">daily e-mail updates</a></strong> about <a rel="nofollow" href="https://www.r-project.org/" title="The R Project for Statistical Computing">R</a> news and <a rel="nofollow" href="https://www.r-bloggers.com/search/tutorial" title="R tutorials">tutorials</a> on topics such as: <a rel="nofollow" href="https://www.r-bloggers.com/search/data%20science" title="Data science">Data science</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/Big%20Data" title="Big Data">Big Data, <a rel="nofollow" href="https://www.r-users.com/" title="R jobs">R jobs</a>, visualization (<a rel="nofollow" href="https://www.r-bloggers.com/search/ggplot2" title="ggplot and ggplot2 tutorials">ggplot2</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/boxplot" title="Boxplots using lattice and ggplot2 tutorials">Boxplots</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/map" title="Maps and gis">maps</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/animation" title="Animation in R">animation</a>), programming (<a rel="nofollow" href="https://www.r-bloggers.com/search/RStudio" title="RStudio IDE for R">RStudio</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/sweave" title="Sweave and literate programming">Sweave</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/LaTeX" title="LaTeX in R">LaTeX</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/SQL" title="SQL and databases">SQL</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/eclipse" title="Eclipse IDE for R">Eclipse</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/git" title="git and github, Version Control System">git</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/hadoop" title="Large data in R using Hadoop">hadoop</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/Web+Scraping" title="Web Scraping of google, facebook, yahoo, twitter and more using R">Web Scraping</a>) statistics (<a rel="nofollow" href="https://www.r-bloggers.com/search/regression" title="Regressions and ANOVA analysis tutorials">regression</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/PCA" title="principal component analysis tutorial">PCA</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/time+series" title="Time series">time series</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/trading" title="finance trading">trading</a>) and more...
<img width="1" alt="" src="http://feeds.feedburner.com/~r/RBloggers/~4/6xBKDPSrSUc" height="1"></a></div>