| @@ -0,0 +1,3 @@ | |||
| ^LICENSE\.md$ | |||
| ^.*\.Rproj$ | |||
| ^\.Rproj\.user$ | |||
| @@ -0,0 +1,41 @@ | |||
| Type: Package | |||
| Package: tidyexplain | |||
| Title: Animated Explanations of Tidyverse Verbs | |||
| Version: 0.0.1.9000 | |||
| Date: 2018-08-27 | |||
| Authors@R: | |||
| c(person(given = "Garrick", | |||
| family = "Aden-Buie", | |||
| role = c("aut", "cre"), | |||
| email = "g.adenbuie@gmail.com"), | |||
| person(given = "David", | |||
| family = "Zimmermann", | |||
| role = "aut", | |||
| email = "david_j_zimmermann@hotmail.com"), | |||
| person(given = "Tyler Grant", | |||
| family = "Smith", | |||
| role = "ctb")) | |||
| Description: Animated explanations of the verbs in the tidyverse | |||
| using gganimate and ggplot2. | |||
| License: MIT + file LICENSE | |||
| Depends: | |||
| gganimate (>= 0.9.9.9999), | |||
| ggplot2 (>= 3.0.0) | |||
| Imports: | |||
| dplyr, | |||
| magrittr, | |||
| purrr, | |||
| rlang (>= 0.1.2), | |||
| scales, | |||
| tidyr, | |||
| tidyselect | |||
| Suggests: | |||
| knitr, | |||
| roxygen2, | |||
| testthat, | |||
| viridis | |||
| VignetteBuilder: | |||
| knitr | |||
| Encoding: UTF-8 | |||
| Roxygen: list(markdown = TRUE) | |||
| RoxygenNote: 6.1.1 | |||
| @@ -1,116 +1,2 @@ | |||
| CC0 1.0 Universal | |||
| Statement of Purpose | |||
| The laws of most jurisdictions throughout the world automatically confer | |||
| exclusive Copyright and Related Rights (defined below) upon the creator and | |||
| subsequent owner(s) (each and all, an "owner") of an original work of | |||
| authorship and/or a database (each, a "Work"). | |||
| Certain owners wish to permanently relinquish those rights to a Work for the | |||
| purpose of contributing to a commons of creative, cultural and scientific | |||
| works ("Commons") that the public can reliably and without fear of later | |||
| claims of infringement build upon, modify, incorporate in other works, reuse | |||
| and redistribute as freely as possible in any form whatsoever and for any | |||
| purposes, including without limitation commercial purposes. These owners may | |||
| contribute to the Commons to promote the ideal of a free culture and the | |||
| further production of creative, cultural and scientific works, or to gain | |||
| reputation or greater distribution for their Work in part through the use and | |||
| efforts of others. | |||
| For these and/or other purposes and motivations, and without any expectation | |||
| of additional consideration or compensation, the person associating CC0 with a | |||
| Work (the "Affirmer"), to the extent that he or she is an owner of Copyright | |||
| and Related Rights in the Work, voluntarily elects to apply CC0 to the Work | |||
| and publicly distribute the Work under its terms, with knowledge of his or her | |||
| Copyright and Related Rights in the Work and the meaning and intended legal | |||
| effect of CC0 on those rights. | |||
| 1. Copyright and Related Rights. A Work made available under CC0 may be | |||
| protected by copyright and related or neighboring rights ("Copyright and | |||
| Related Rights"). Copyright and Related Rights include, but are not limited | |||
| to, the following: | |||
| i. the right to reproduce, adapt, distribute, perform, display, communicate, | |||
| and translate a Work; | |||
| ii. moral rights retained by the original author(s) and/or performer(s); | |||
| iii. publicity and privacy rights pertaining to a person's image or likeness | |||
| depicted in a Work; | |||
| iv. rights protecting against unfair competition in regards to a Work, | |||
| subject to the limitations in paragraph 4(a), below; | |||
| v. rights protecting the extraction, dissemination, use and reuse of data in | |||
| a Work; | |||
| vi. database rights (such as those arising under Directive 96/9/EC of the | |||
| European Parliament and of the Council of 11 March 1996 on the legal | |||
| protection of databases, and under any national implementation thereof, | |||
| including any amended or successor version of such directive); and | |||
| vii. other similar, equivalent or corresponding rights throughout the world | |||
| based on applicable law or treaty, and any national implementations thereof. | |||
| 2. Waiver. To the greatest extent permitted by, but not in contravention of, | |||
| applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and | |||
| unconditionally waives, abandons, and surrenders all of Affirmer's Copyright | |||
| and Related Rights and associated claims and causes of action, whether now | |||
| known or unknown (including existing as well as future claims and causes of | |||
| action), in the Work (i) in all territories worldwide, (ii) for the maximum | |||
| duration provided by applicable law or treaty (including future time | |||
| extensions), (iii) in any current or future medium and for any number of | |||
| copies, and (iv) for any purpose whatsoever, including without limitation | |||
| commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes | |||
| the Waiver for the benefit of each member of the public at large and to the | |||
| detriment of Affirmer's heirs and successors, fully intending that such Waiver | |||
| shall not be subject to revocation, rescission, cancellation, termination, or | |||
| any other legal or equitable action to disrupt the quiet enjoyment of the Work | |||
| by the public as contemplated by Affirmer's express Statement of Purpose. | |||
| 3. Public License Fallback. Should any part of the Waiver for any reason be | |||
| judged legally invalid or ineffective under applicable law, then the Waiver | |||
| shall be preserved to the maximum extent permitted taking into account | |||
| Affirmer's express Statement of Purpose. In addition, to the extent the Waiver | |||
| is so judged Affirmer hereby grants to each affected person a royalty-free, | |||
| non transferable, non sublicensable, non exclusive, irrevocable and | |||
| unconditional license to exercise Affirmer's Copyright and Related Rights in | |||
| the Work (i) in all territories worldwide, (ii) for the maximum duration | |||
| provided by applicable law or treaty (including future time extensions), (iii) | |||
| in any current or future medium and for any number of copies, and (iv) for any | |||
| purpose whatsoever, including without limitation commercial, advertising or | |||
| promotional purposes (the "License"). The License shall be deemed effective as | |||
| of the date CC0 was applied by Affirmer to the Work. Should any part of the | |||
| License for any reason be judged legally invalid or ineffective under | |||
| applicable law, such partial invalidity or ineffectiveness shall not | |||
| invalidate the remainder of the License, and in such case Affirmer hereby | |||
| affirms that he or she will not (i) exercise any of his or her remaining | |||
| Copyright and Related Rights in the Work or (ii) assert any associated claims | |||
| and causes of action with respect to the Work, in either case contrary to | |||
| Affirmer's express Statement of Purpose. | |||
| 4. Limitations and Disclaimers. | |||
| a. No trademark or patent rights held by Affirmer are waived, abandoned, | |||
| surrendered, licensed or otherwise affected by this document. | |||
| b. Affirmer offers the Work as-is and makes no representations or warranties | |||
| of any kind concerning the Work, express, implied, statutory or otherwise, | |||
| including without limitation warranties of title, merchantability, fitness | |||
| for a particular purpose, non infringement, or the absence of latent or | |||
| other defects, accuracy, or the present or absence of errors, whether or not | |||
| discoverable, all to the greatest extent permissible under applicable law. | |||
| c. Affirmer disclaims responsibility for clearing rights of other persons | |||
| that may apply to the Work or any use thereof, including without limitation | |||
| any person's Copyright and Related Rights in the Work. Further, Affirmer | |||
| disclaims responsibility for obtaining any necessary consents, permissions | |||
| or other rights required for any use of the Work. | |||
| d. Affirmer understands and acknowledges that Creative Commons is not a | |||
| party to this document and has no duty or obligation with respect to this | |||
| CC0 or use of the Work. | |||
| For more information, please see | |||
| <http://creativecommons.org/publicdomain/zero/1.0/> | |||
| YEAR: 2018 | |||
| COPYRIGHT HOLDER: Garrick Aden-Buie | |||
| @@ -0,0 +1,21 @@ | |||
| # MIT License | |||
| Copyright (c) 2018 Garrick Aden-Buie | |||
| Permission is hereby granted, free of charge, to any person obtaining a copy | |||
| of this software and associated documentation files (the "Software"), to deal | |||
| in the Software without restriction, including without limitation the rights | |||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
| copies of the Software, and to permit persons to whom the Software is | |||
| furnished to do so, subject to the following conditions: | |||
| The above copyright notice and this permission notice shall be included in all | |||
| copies or substantial portions of the Software. | |||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
| SOFTWARE. | |||
| @@ -0,0 +1,41 @@ | |||
| # Generated by roxygen2: do not edit by hand | |||
| S3method(print,anim_opts) | |||
| export("%>%") | |||
| export(anim_options) | |||
| export(animate_anti_join) | |||
| export(animate_full_join) | |||
| export(animate_gather) | |||
| export(animate_inner_join) | |||
| export(animate_intersect) | |||
| export(animate_left_join) | |||
| export(animate_right_join) | |||
| export(animate_semi_join) | |||
| export(animate_setdiff) | |||
| export(animate_spread) | |||
| export(animate_union) | |||
| export(animate_union_all) | |||
| export(get_font_size) | |||
| export(is.anim_opts) | |||
| export(set_anim_options) | |||
| export(set_font_size) | |||
| importFrom(dplyr,anti_join) | |||
| importFrom(dplyr,arrange) | |||
| importFrom(dplyr,bind_cols) | |||
| importFrom(dplyr,bind_rows) | |||
| importFrom(dplyr,data_frame) | |||
| importFrom(dplyr,filter) | |||
| importFrom(dplyr,full_join) | |||
| importFrom(dplyr,group_by) | |||
| importFrom(dplyr,inner_join) | |||
| importFrom(dplyr,left_join) | |||
| importFrom(dplyr,mutate) | |||
| importFrom(dplyr,pull) | |||
| importFrom(dplyr,right_join) | |||
| importFrom(dplyr,row_number) | |||
| importFrom(dplyr,select) | |||
| importFrom(dplyr,semi_join) | |||
| importFrom(dplyr,slice) | |||
| importFrom(magrittr,"%>%") | |||
| importFrom(tidyr,gather) | |||
| importFrom(tidyr,spread) | |||
| @@ -1,37 +0,0 @@ | |||
| # Animated dplyr joins with gganimate | |||
| # * Garrick Aden-Buie | |||
| # * garrickadenbuie.com | |||
| # * MIT License: https://opensource.org/licenses/MIT | |||
| library(tidyverse) | |||
| library(gganimate) | |||
| if (!getOption("tidy_verb_anim.font_registered", FALSE)) { | |||
| source(here::here("R", "01_register-fonts.R")) | |||
| } | |||
| if (!getOption("tidy_verb_anim.functions_loaded", FALSE)) { | |||
| source(here::here("R", "02_functions.R")) | |||
| } | |||
| source(here::here("R", "03_check-folders.R")) | |||
| plot_data_join <- function(x, title = "", xlims = xlim(0.5, 5.5), ylims = ylim(-3.5, -0.5)) { | |||
| plot_data(x, title) + | |||
| xlims + ylims | |||
| } | |||
| # Data ---- | |||
| x <- data_frame( | |||
| id = 1:3, | |||
| x = paste0("x", 1:3) | |||
| ) | |||
| y <- data_frame( | |||
| id = (1:4)[-3], | |||
| y = paste0("y", (1:4)[-3]) | |||
| ) | |||
| initial_join_dfs <- proc_data(x, "x") %>% | |||
| bind_rows(mutate(proc_data(y, "y"), .x = .x + 3)) %>% | |||
| mutate(frame = 1) | |||
| @@ -1,50 +0,0 @@ | |||
| # Animated dplyr set opertaions with gganimate | |||
| # * Contributed by Tyler Grant Smith <https://github.com/TylerGrantSmith> | |||
| # * and Garrick Aden-Buie <https://www.garrickadenbuie.com> | |||
| # * MIT License: https://opensource.org/licenses/MIT | |||
| library(tidyverse) | |||
| library(gganimate) | |||
| if (!getOption("tidy_verb_anim.font_registered", FALSE)) { | |||
| source(here::here("R", "01_register-fonts.R")) | |||
| } | |||
| if (!getOption("tidy_verb_anim.functions_loaded", FALSE)) { | |||
| source(here::here("R", "02_functions.R")) | |||
| } | |||
| source(here::here("R", "03_check-folders.R")) | |||
| # Initialize data processing function ---- | |||
| proc_data_set <- function(x, .id = "x") { | |||
| proc_data(x, .id, colorize_row_id, "before") | |||
| } | |||
| plot_data_set <- function(x, title = "", xlims = xlim(1.5, 6.5), ylims = ylim(-3.5, -0.5)) { | |||
| filter(x, label != "id") %>% | |||
| plot_data(title) + | |||
| xlims + ylims | |||
| } | |||
| # Data ---- | |||
| x <- tibble::tribble( | |||
| ~id, ~x, ~y, | |||
| 1, "1", "a", | |||
| 2, "1", "b", | |||
| 3, "2", "a" | |||
| ) | |||
| y <- tibble::tribble( | |||
| ~id, ~x, ~y, | |||
| 1, "1", "a", | |||
| 4, "2", "b" | |||
| ) | |||
| initial_set_dfs <- bind_rows( | |||
| proc_data_set(x, "x"), | |||
| proc_data_set(y, "y") %>% mutate(.x = .x + 3) | |||
| ) %>% | |||
| mutate(frame = 1) | |||
| @@ -1,28 +0,0 @@ | |||
| # Animated dplyr joins with gganimate | |||
| # * Garrick Aden-Buie | |||
| # * garrickadenbuie.com | |||
| # * MIT License: https://opensource.org/licenses/MIT | |||
| library(tidyverse) | |||
| library(gganimate) | |||
| if (!getOption("tidy_verb_anim.font_registered", FALSE)) { | |||
| source(here::here("R", "01_register-fonts.R")) | |||
| } | |||
| if (!getOption("tidy_verb_anim.functions_loaded", FALSE)) { | |||
| source(here::here("R", "02_functions.R")) | |||
| } | |||
| source(here::here("R", "03_check-folders.R")) | |||
| # Data ---- | |||
| set.seed(42) | |||
| wide <- data_frame( | |||
| id = rep(1:2), | |||
| x = letters[1:2], | |||
| y = letters[3:4], | |||
| z = letters[5:6] | |||
| ) | |||
| long <- tidyr::gather(wide, key, val, x:z) | |||
| @@ -1,7 +0,0 @@ | |||
| # Note: I used Fira Sans and Mono (downloaded here from Google Fonts). | |||
| # Feel free to change font names below for desired fonts. | |||
| sysfonts::font_add_google("Fira Sans") | |||
| sysfonts::font_add_google("Fira Mono") | |||
| showtext::showtext_auto() | |||
| options(tidy_verb_anim.font_registered = TRUE) | |||
| @@ -1,112 +0,0 @@ | |||
| proc_data <- function(x, .id = "x", color_fun = colorize_keys, color_when = c("after", "before"), ...) { | |||
| color_when <- match.arg(color_when) | |||
| n_colors <- max(x$id) | |||
| if (color_when == "before") x <- color_fun(x, n_colors, ...) | |||
| x <- x %>% | |||
| mutate(.y = -row_number()) %>% | |||
| tidyr::gather("label", "value", setdiff(colnames(x), c(".y", "color"))) %>% | |||
| mutate(value = as.character(value)) %>% | |||
| group_by(.y) %>% | |||
| mutate( | |||
| .x = 1:n(), | |||
| .id = .id, | |||
| .width = 1 | |||
| ) %>% | |||
| ungroup(.y) | |||
| if (color_when == "after") x <- color_fun(x, n_colors, ...) | |||
| x | |||
| } | |||
| colorize_keys <- function(df, n_colors, key_col = "id", color_other = "#d0d0d0", color_missing = "#ffffff") { | |||
| # Assumes that key_col is integer | |||
| colors <- scales::brewer_pal(type = "qual", "Set1")(n_colors) | |||
| mutate( | |||
| df, | |||
| color = ifelse(label == key_col, value, n_colors + 1), | |||
| color = colors[as.integer(color)], | |||
| color = ifelse(is.na(color), "#d0d0d0", color), | |||
| color = ifelse(is.na(value), "#ffffff", color) | |||
| ) | |||
| } | |||
| colorize_row_id <- function(df, n_colors, key_col = "id") { | |||
| # Assumes that key_col is integer | |||
| colors <- scales::brewer_pal(type = "qual", "Set1")(n_colors) | |||
| df$color <- colors[df[[key_col]]] | |||
| df | |||
| } | |||
| colorize_wide_tidyr <- function(df, n_colors, key_col = "id") { | |||
| n_colors <- n_colors + length(setdiff(unique(df$label), key_col)) | |||
| colors <- scales::brewer_pal(type = "qual", "Set1")(n_colors) | |||
| df$value_int <- as.integer(gsub("[a-zA-Z]", "0", df$value)) | |||
| max_id_color <- max(df$value_int) | |||
| df %>% | |||
| bind_rows( | |||
| filter(df, .y == "-1") %>% mutate(.y = 0) | |||
| ) %>% | |||
| mutate( | |||
| idcp = max_id_color - 1L, | |||
| idc = case_when( | |||
| label == "id" ~ value_int, | |||
| TRUE ~ map_int(label, ~which(. == unique(label))) + idcp | |||
| ) | |||
| ) %>% | |||
| select(-idcp, -value_int) %>% | |||
| mutate( | |||
| idc = ifelse(.y == 0 & label == "id", 100, idc), | |||
| value = ifelse(.y == 0, label, value), | |||
| .id = ifelse(.y == 0, "n", .id), | |||
| color = colors[idc], | |||
| ) %>% | |||
| filter(!is.na(color)) %>% | |||
| mutate(alpha = ifelse(label != "id" & .y < 0, 0.6, 1.0)) %>% | |||
| select(-idc) | |||
| } | |||
| plot_data <- function(x, title = "") { | |||
| if (!"alpha" %in% colnames(x)) x$alpha <- 1 | |||
| if (!".text_color" %in% colnames(x)) x$`.text_color` <- "white" | |||
| if (!".text_size" %in% colnames(x)) x$`.text_size` <- 12 | |||
| ggplot(x) + | |||
| aes(.x, .y, fill = color, label = value) + | |||
| geom_tile(aes(alpha = alpha), width = 0.9, height = 0.9) + | |||
| geom_text(aes(x = .x, color = .text_color, size = .text_size), hjust = 0.5, family = "Fira Sans") + | |||
| scale_fill_identity() + | |||
| scale_alpha_identity() + | |||
| scale_color_identity() + | |||
| scale_size_identity() + | |||
| coord_equal() + | |||
| ggtitle(title) + | |||
| theme_void() + | |||
| theme(plot.title = element_text(family = "Fira Mono", hjust = 0.5, size = 24)) + | |||
| guides(fill = FALSE) | |||
| } | |||
| animate_plot <- function(x, transition_length = 2, state_length = 1) { | |||
| x + | |||
| transition_states(frame, transition_length, state_length) + | |||
| enter_fade() + | |||
| exit_fade() + | |||
| ease_aes("sine-in-out") | |||
| } | |||
| save_static_plot <- function(g, filename, formats = c("png", "svg")) { | |||
| filenames <- formats %>% | |||
| purrr::set_names() %>% | |||
| purrr::map_chr(static_plot_filename, x = filename) %>% | |||
| purrr::iwalk( | |||
| ~ ggsave(filename = .x, plot = g, dev = .y) | |||
| ) | |||
| } | |||
| static_plot_filename <- function(x, ext) { | |||
| here::here("images", "static", ext, paste0(x, ".", ext)) | |||
| } | |||
| options(tidy_verb_anim.functions_loaded = TRUE) | |||
| @@ -1,5 +0,0 @@ | |||
| if (!dir.exists(here::here("images"))) dir.create(here::here("images")) | |||
| png_path <- here::here("images", "static", "png") | |||
| svg_path <- here::here("images", "static", "svg") | |||
| if (!dir.exists(png_path)) dir.create(png_path, recursive = TRUE) | |||
| if (!dir.exists(svg_path)) dir.create(svg_path, recursive = TRUE) | |||
| @@ -0,0 +1,150 @@ | |||
| #' Animates a join operation | |||
| #' | |||
| #' Functions to visualise the join operations either static as a ggplot, or | |||
| #' dynamic as a gif. | |||
| #' | |||
| #' @param x the x dataset | |||
| #' @param y the y dataset | |||
| #' @param by the by arguments for the join | |||
| #' @param export the export type, either gif, first or last. The latter two | |||
| #' export ggplots of the first/last state of the join | |||
| #' @param ... further arguments passed to anim_options() | |||
| #' | |||
| #' @return either a gif or a ggplot | |||
| #' | |||
| #' @seealso \code{\link[dplyr]{join}} | |||
| #' | |||
| #' @name animate_join | |||
| #' @examples | |||
| #' x <- data_frame(id = 1:3, x = paste0("x", 1:3)) | |||
| #' y <- data_frame(id = (1:4)[-3], y = paste0("y", (1:4)[-3])) | |||
| #' | |||
| #' # Animate the first or last state of the join | |||
| #' animate_full_join(x, y, by = "id", export = "first") | |||
| #' animate_full_join(x, y, by = "id", export = "last") | |||
| #' | |||
| #' # animate the transition as a gif (default) | |||
| #' \donttest{ | |||
| #' animate_full_join(x, y, by = "id", export = "gif") | |||
| #' } | |||
| #' | |||
| #' # different options include | |||
| #' \donttest{ | |||
| #' animate_full_join(x, y, by = "id") | |||
| #' animate_inner_join(x, y, by = "id") | |||
| #' animate_left_join(x, y, by = "id") | |||
| #' animate_right_join(x, y, by = "id") | |||
| #' animate_semi_join(x, y, by = "id") | |||
| #' animate_anti_join(x, y, by = "id") | |||
| #' | |||
| #' # further arguments can be passed to all animate_* functions, see also ?anim_options | |||
| #' animate_full_join( | |||
| #' x, y, by = "id", export = "last", | |||
| #' text_size = 5, title_size = 25, | |||
| #' color_header = "black", | |||
| #' color_other = "lightblue", | |||
| #' color_fun = viridis::viridis | |||
| #' ) | |||
| #' } | |||
| #' | |||
| #' # Save the results | |||
| #' \donttest{ | |||
| #' # to save the ggplot, use | |||
| #' fj <- animate_full_join(x, y, by = "id", export = "last") | |||
| #' ggsave("full-join.pdf", fj) | |||
| #' | |||
| #' # to save the gif, use | |||
| #' fj <- animate_full_join(x, y, by = "id", export = "gif") | |||
| #' anim_save(fj, "full-join.gif") | |||
| #' } | |||
| animate_join <- function( | |||
| x, | |||
| y, | |||
| by, | |||
| type = c("full_join", "inner_join", "left_join", "right_join", | |||
| "semi_join", "anti_join"), | |||
| export = c("gif", "first", "last"), | |||
| ... | |||
| ) { | |||
| type <- match.arg(type) | |||
| export <- match.arg(export) | |||
| x_name <- get_input_text(x) | |||
| y_name <- get_input_text(y) | |||
| data <- make_named_data(x, y) | |||
| by_args <- if (length(by) == 1) sprintf("\"%s\"", by) else | |||
| sprintf("c(\"%s\")", paste(by, collapse = "\", \"")) | |||
| title <- sprintf(paste0(type, "(%s, %s, by = %s)"), x_name, y_name, by_args) | |||
| if (type %in% c("semi_join", "anti_join")) { | |||
| # for semi and anti_joins, there is no adding of multiple rows | |||
| data$y <- dplyr::distinct(data$y) | |||
| } | |||
| ll <- process_join(data$x, data$y, by, ...) | |||
| step0 <- bind_rows(ll$x, ll$y) %>% mutate(.frame = 0, .alpha = 1) | |||
| step1 <- move_together(ll$x, ll$y, type) %>% mutate(.frame = 1) | |||
| all <- bind_rows(step0, step1) | |||
| if (export == "gif") { | |||
| animate_plot(all, title, ...) | |||
| } else if (export == "first") { | |||
| title <- "" | |||
| static_plot(step0, title, ...) | |||
| } else if (export == "last") { | |||
| static_plot(step1, title, ...) | |||
| } | |||
| } | |||
| #' @rdname animate_join | |||
| #' @export | |||
| animate_full_join <- function(x, y, by, export = "gif", ...) { | |||
| x <- rlang::enquo(x) | |||
| y <- rlang::enquo(y) | |||
| animate_join(x, y, by, type = "full_join", export = export, ...) | |||
| } | |||
| #' @rdname animate_join | |||
| #' @export | |||
| animate_inner_join <- function(x, y, by, export = "gif", ...) { | |||
| x <- rlang::enquo(x) | |||
| y <- rlang::enquo(y) | |||
| animate_join(x, y, by, type = "inner_join", export = export, ...) | |||
| } | |||
| #' @rdname animate_join | |||
| #' @export | |||
| animate_left_join <- function(x, y, by, export = "gif", ...) { | |||
| x <- rlang::enquo(x) | |||
| y <- rlang::enquo(y) | |||
| animate_join(x, y, by, type = "left_join", export = export, ...) | |||
| } | |||
| #' @rdname animate_join | |||
| #' @export | |||
| animate_right_join <- function(x, y, by, export = "gif", ...) { | |||
| x <- rlang::enquo(x) | |||
| y <- rlang::enquo(y) | |||
| animate_join(x, y, by, type = "right_join", export = export, ...) | |||
| } | |||
| #' @rdname animate_join | |||
| #' @export | |||
| animate_semi_join <- function(x, y, by, export = "gif", ...) { | |||
| x <- rlang::enquo(x) | |||
| y <- rlang::enquo(y) | |||
| animate_join(x, y, by, type = "semi_join", export = export, ...) | |||
| } | |||
| #' @rdname animate_join | |||
| #' @export | |||
| animate_anti_join <- function(x, y, by, export = "gif", ...) { | |||
| x <- rlang::enquo(x) | |||
| y <- rlang::enquo(y) | |||
| animate_join(x, y, by, type = "anti_join", export = export, ...) | |||
| } | |||
| @@ -0,0 +1,244 @@ | |||
| #' Animation Options | |||
| #' | |||
| #' Helper function to set animation and plotting options to be passed to | |||
| #' [animate_plot()] and [static_plot()]. | |||
| #' | |||
| #' @param color_header Color of the header row. | |||
| #' @param color_other Color of the cells that are not highlighted otherwise. | |||
| #' @param color_missing Color of the missing cells. | |||
| #' @param color_fun A function that generates the colors for the highlighted | |||
| #' cells, default is [scales::brewer_pal()] Set1. | |||
| #' @param text_color Color of the text of the cells, default is a black or | |||
| #' white, based on the background color of the cell. | |||
| #' @param text_family Font family for the plot text, default is "Fira Mono". Use | |||
| #' [set_font_size()] to set global default font sizes. | |||
| #' @param title_family Font family for the plot title, default is "Fira Mono". | |||
| #' Use [set_font_size()] to set global default font sizes. | |||
| #' @param text_size Font size of the plot text, default is 5. | |||
| #' @param title_size Font size of the plot title, default is 17. | |||
| #' @param cell_width Width of a cell, default is 1. | |||
| #' @param cell_height Height of a cell, default is 1. | |||
| #' @param ease_default Default aes easing function. See [tweenr::display_ease()] | |||
| #' for more options. The tidyexplain default value is `sine-in-out`. | |||
| #' @param ease_other Additional aes easing options, specified as a named list. | |||
| #' List entries are named with the aesthetic to which the easeing should be | |||
| #' applied, consistent with [gganimate::ease_aes()]. E.g. `list(color = | |||
| #' "sine")`. | |||
| #' @param enter Enter fading function applied to objects in the animation. See | |||
| #' [gganimate::enter_exit] for a complete list of options. The tidyexplain | |||
| #' default is [gganimate::enter_fade()]. | |||
| #' @param exit Exit fading function applied to objects in the animation. See | |||
| #' [gganimate::enter_exit] for a complete list of options. The tidyexplain | |||
| #' default is [gganimate::exit_fade()]. | |||
| #' @inheritParams gganimate::transition_states | |||
| #' @export | |||
| anim_options <- function( | |||
| transition_length = NULL, | |||
| state_length = NULL, | |||
| ease_default = NULL, | |||
| ease_other = NULL, | |||
| enter = NULL, | |||
| exit = NULL, | |||
| text_family = NULL, | |||
| title_family = NULL, | |||
| text_size = NULL, | |||
| title_size = NULL, | |||
| color_header = NULL, | |||
| color_other = NULL, | |||
| color_missing = NULL, | |||
| color_fun = NULL, | |||
| text_color = NULL, | |||
| cell_width = NULL, | |||
| cell_height = NULL, | |||
| ... | |||
| ){ | |||
| enter_name <- if (!missing(enter)) rlang::quo_name(rlang::enquo(enter)) | |||
| exit_name <- if (!missing(exit)) rlang::quo_name(rlang::enquo(exit)) | |||
| ao <- list( | |||
| transition_length = transition_length, | |||
| state_length = state_length, | |||
| ease_default = ease_default, | |||
| ease_other = ease_other, | |||
| enter = if (!is.null(enter)) setNames(list(enter), enter_name), | |||
| exit = if (!is.null(exit)) setNames(list(exit), exit_name), | |||
| text_family = text_family, | |||
| text_size = text_size, | |||
| title_family = title_family, | |||
| title_size = title_size, | |||
| color_header = color_header, | |||
| color_other = color_other, | |||
| color_missing = color_missing, | |||
| color_fun = color_fun, | |||
| text_color = text_color, | |||
| cell_width = cell_width, | |||
| cell_height = cell_height, | |||
| ... | |||
| ) | |||
| ao <- purrr::compact(ao) | |||
| structure(ao, class = "anim_opts") | |||
| } | |||
| # Global Animation Options Setters and Getters ---------------------------- | |||
| #' @describeIn anim_options Set default animation options for the current session. | |||
| #' @param anim_opts An [anim_options()] options list. | |||
| #' @export | |||
| set_anim_options <- function(anim_opts = anim_options()) { | |||
| stopifnot(is.anim_opts(anim_opts)) | |||
| ao_old <- plot_settings$anim_opts | |||
| plot_settings$anim_opts <- merge(anim_opts, plot_settings$anim_opts) | |||
| invisible(ao_old) | |||
| } | |||
| get_anim_opt <- function(anim_opt = NULL) { | |||
| if (is.null(anim_opt)) return(plot_settings$anim_opts) | |||
| if (anim_opt %in% c("text_size", "title_size")) rlang::abort( | |||
| "Use get_text_size() or get_title_size()" | |||
| ) | |||
| plot_settings$anim_opts[[anim_opt]] %||% plot_settings$default[[anim_opt]] | |||
| } | |||
| # Animation Options Methods ----------------------------------------------- | |||
| #' @export | |||
| print.anim_opts <- function(x) { | |||
| # Replace ggproto (enter/exit functions) with their names | |||
| if ("enter" %in% names(x)) x$enter <- paste("ggproto:", names(x$enter)) | |||
| if ("exit" %in% names(x)) x$exit <- paste("ggproto:", names(x$exit)) | |||
| anim_opts <- capture.output(str(x, no.list = TRUE)) | |||
| cat( | |||
| paste0("<anim_options: ", length(x), " options>"), | |||
| anim_opts, sep = "\n" | |||
| ) | |||
| } | |||
| #' @export | |||
| is.anim_opts <- function(ao) inherits(ao, "anim_opts") | |||
| # Fill, Validate, Merge Animation Options --------------------------------- | |||
| # Fills in default animation options | |||
| fill_anim_opts <- function(ao) { | |||
| ao$transition_length <- ao$transition_length %||% get_anim_opt("transition_length") | |||
| ao$state_length <- ao$state_length %||% get_anim_opt("state_length") | |||
| ao$ease_default <- ao$ease_default %||% get_anim_opt("ease_default") | |||
| ao$ease_other <- ao$ease_other %||% get_anim_opt("ease_other") | |||
| ao$enter <- ao$enter %||% get_anim_opt("enter") | |||
| ao$exit <- ao$exit %||% get_anim_opt("exit") | |||
| ao$text_family <- ao$text_family %||% get_anim_opt("text_family") | |||
| ao$title_family <- ao$title_family %||% get_anim_opt("title_family") | |||
| ao$color_header <- ao$color_header %||% get_anim_opt("color_header") | |||
| ao$color_other <- ao$color_other %||% get_anim_opt("color_other") | |||
| ao$color_missing <- ao$color_missing %||% get_anim_opt("color_missing") | |||
| ao$color_fun <- ao$color_fun %||% get_anim_opt("color_fun") | |||
| ao$text_color <- ao$text_color %||% get_anim_opt("text_color") | |||
| ao$cell_width <- ao$cell_width %||% get_anim_opt("cell_width") | |||
| ao$cell_height <- ao$cell_height %||% get_anim_opt("cell_height") | |||
| ao | |||
| } | |||
| validate_anim_opts <- function(ao, quiet = FALSE, strict = getOption("tidyexplain.strict_dots", FALSE)) { | |||
| if (!inherits(ao, "anim_opts")) { | |||
| rlang::warn("Use `anim_options()` to set `anim_opts`") | |||
| } | |||
| ao <- fill_anim_opts(ao) | |||
| stopifnot(is.ggproto(ao$enter[[1]]), is.ggproto(ao$exit[[1]])) | |||
| extra_names <- setdiff(names(ao), names(formals(anim_options))) | |||
| if (!quiet && length(extra_names)) { | |||
| extra_names <- paste0(sprintf("`%s`", extra_names), collapse = ", ") | |||
| msg <- paste("Unknown animation options will be ignored:", extra_names) | |||
| if (isTRUE(strict)) rlang::abort(msg) else rlang::warn(msg) | |||
| } | |||
| invisible(ao) | |||
| } | |||
| merge.anim_opts <- function(ao_new, ao_base = anim_options()) { | |||
| ao_new <- purrr::discard(ao_new, is.null) | |||
| ao_base <- purrr::discard(ao_base, is.null) | |||
| unique_base <- setdiff(names(ao_base), names(ao_new)) | |||
| ao <- append(ao_new, ao_base[unique_base]) | |||
| ao <- ao[names(formals(anim_options))] | |||
| ao <- purrr::discard(ao, is.null) | |||
| class(ao) <- "anim_opts" | |||
| ao | |||
| } | |||
| # Default Animation Options for Verb Families ----------------------------- | |||
| default_anim_opts <- function(family, ao_custom = NULL) { | |||
| family_options <- c("join", "set", "gather", "spread") | |||
| family <- match.arg(family, family_options, several.ok = FALSE) | |||
| ao_default <- switch( | |||
| family, | |||
| "gather" = anim_options(enter = enter_fade(), exit = exit_fade(), | |||
| ease_default = "sine-in-out", | |||
| ease_other = list(y = "cubic-out", x = "cubic-in")), | |||
| "spread" = anim_options(enter = enter_fade(), exit = exit_fade(), | |||
| ease_default = "sine-in-out", | |||
| ease_other = list(y = "cubic-out", x = "cubic-in")), | |||
| anim_options() | |||
| ) | |||
| if (is.null(ao_custom)) { | |||
| # User set globals override defaults | |||
| ao_custom <- get_anim_opt() | |||
| } else { | |||
| # Opts from function call override user-set globals | |||
| ao_custom <- merge(ao_custom, get_anim_opt()) | |||
| } | |||
| # function > user-set global > default (> global default) | |||
| if (!is.null(ao_custom)) merge(ao_custom, ao_default) else ao_default | |||
| } | |||
| # Font Size Setters and Getters ------------------------------------------- | |||
| #' Set Default Text Sizes for Animation Plots | |||
| #' | |||
| #' Sets the default text sizes for the animated and static plots produced by | |||
| #' this package during the current session. | |||
| #' | |||
| #' @param text_size Font size of value labels inside the data frame squares | |||
| #' @param title_size Font size of the function call or plot title | |||
| #' @export | |||
| set_font_size <- function(text_size = NULL, title_size = NULL) { | |||
| old <- list() | |||
| if (!is.null(text_size)) old$text_size <- set_text_size(text_size) | |||
| if (!is.null(title_size)) old$title_size <- set_title_size(title_size) | |||
| invisible(old) | |||
| } | |||
| #' @describeIn set_font_size Get current global font sizes | |||
| #' @export | |||
| get_font_size <- function() { | |||
| list("text_size" = get_text_size(), "title_size" = get_title_size()) | |||
| } | |||
| set_text_size <- function(size) { | |||
| old <- plot_settings$text_size | |||
| set_anim_options(anim_options(text_size = size)) | |||
| invisible(old) | |||
| } | |||
| set_title_size <- function(size) { | |||
| old <- plot_settings$title_size | |||
| set_anim_options(anim_options(title_size = size)) | |||
| invisible(old) | |||
| } | |||
| get_text_size <- function(x = NULL) { | |||
| if (!is.null(x)) return(x) | |||
| plot_settings$anim_opts$text_size %||% | |||
| getFromNamespace("theme_void", "ggplot2")()$text$size %||% | |||
| plot_settings$default$text_size | |||
| } | |||
| get_title_size <- function(x = NULL) { | |||
| if (!is.null(x)) return(x) | |||
| plot_settings$anim_opts$title_size %||% | |||
| getFromNamespace("theme_void", "ggplot2")()$plot.title$size %||% | |||
| plot_settings$default$title_size | |||
| } | |||
| @@ -0,0 +1,133 @@ | |||
| #' Animates a set operation | |||
| #' | |||
| #' Functions to visualise the set operations either static as a ggplot, or | |||
| #' dynamic as a gif. | |||
| #' | |||
| #' @param x the x dataset | |||
| #' @param y the y dataset | |||
| #' @param export the export type, either gif, first or last. The latter two | |||
| #' export ggplots of the first/last state of the join | |||
| #' @param type type of the set, i.e., intersect, setdiff, etc. | |||
| #' @param ... further argument passed to anim_options() | |||
| #' | |||
| #' @return either a gif or a ggplot | |||
| #' | |||
| #' @seealso \code{\link[dplyr]{setops}} | |||
| #' | |||
| #' @examples | |||
| #' x <- data_frame(x = c(1, 1, 2), y = c("a", "b", "a")) | |||
| #' y <- data_frame(x = c(1, 2), y = c("a", "b")) | |||
| #' | |||
| #' # Animate the first or last state of the set | |||
| #' animate_union(x, y, export = "first") | |||
| #' animate_union(x, y, export = "last") | |||
| #' | |||
| #' # animate the transition as a gif (default) | |||
| #' \donttest{ | |||
| #' animate_union(x, y, export = "gif") | |||
| #' } | |||
| #' | |||
| #' # different options include | |||
| #' \donttest{ | |||
| #' animate_union(x, y) | |||
| #' animate_union_all(x, y) | |||
| #' animate_intersect(x, y) | |||
| #' animate_setdiff(x, y) | |||
| #' | |||
| #' # further arguments can be passed to all animate_* functions | |||
| #' animate_union( | |||
| #' x, y, | |||
| #' text_size = 5, title_size = 25, | |||
| #' color_header = "black", | |||
| #' color_fun = viridis::viridis | |||
| #' ) | |||
| #' } | |||
| #' | |||
| #' # Save the results | |||
| #' \dontrun{ | |||
| #' # to save the ggplot, use | |||
| #' un <- animate_union(x, y, by = "id", export = "last") | |||
| #' ggsave("union.pdf", un) | |||
| #' | |||
| #' animate_union(x, y, by = "id", export = "gif") | |||
| #' # to save the gif, use | |||
| #' un <- animate_union(x, y, by = "id", export = "gif") | |||
| #' anim_save(un, "union.gif") | |||
| #' } | |||
| animate_set <- function( | |||
| x, y, | |||
| type = c("union", "union_all", "intersect", "setdiff"), | |||
| export = c("gif", "first", "last"), | |||
| ... | |||
| ) { | |||
| type <- match.arg(type) | |||
| export <- match.arg(export) | |||
| x_name <- get_input_text(x) | |||
| y_name <- get_input_text(y) | |||
| data <- make_named_data(x, y) | |||
| col_names <- purrr::map(data, names) | |||
| if (!all(names(data$x) %in% names(data$y)) && ncol(data$x) == ncol(data$y)) | |||
| stop("x and y must have the same variables/column-names") | |||
| title <- sprintf(paste0(type, "(%s, %s)"), x_name, y_name) | |||
| if (type %in% c("union", "intersect", "setdiff")) { | |||
| data <- purrr::map(data, dplyr::distinct) | |||
| } | |||
| if (type == "union_all") { | |||
| ll <- process_join(data$x, data$y, by = names(data$x), fill = FALSE, ...) | |||
| ll <- purrr::map(ll, ~ mutate(., .id_long = paste(.id_long, .side, sep = "-"))) | |||
| } else { | |||
| ll <- process_join(data$x, data$y, by = names(data$x), ...) | |||
| } | |||
| step0 <- bind_rows(ll$x, ll$y) %>% mutate(.frame = 0, .alpha = 1) | |||
| step1 <- move_together(ll$x, ll$y, type) %>% mutate(.frame = 1) | |||
| all <- bind_rows(step0, step1) | |||
| if (export == "gif") { | |||
| animate_plot(all, title, ...) | |||
| } else if (export == "first") { | |||
| title <- "" | |||
| static_plot(step0, title, ...) | |||
| } else if (export == "last") { | |||
| static_plot(step1, title, ...) | |||
| } | |||
| } | |||
| #' @rdname animate_set | |||
| #' @export | |||
| animate_union <- function(x, y, export = "gif", ...) { | |||
| x <- rlang::enquo(x) | |||
| y <- rlang::enquo(y) | |||
| animate_set(x, y, type = "union", export = export, ...) | |||
| } | |||
| #' @rdname animate_set | |||
| #' @export | |||
| animate_union_all <- function(x, y, export = "gif", ...) { | |||
| x <- rlang::enquo(x) | |||
| y <- rlang::enquo(y) | |||
| animate_set(x, y, type = "union_all", export = export, ...) | |||
| } | |||
| #' @rdname animate_set | |||
| #' @export | |||
| animate_intersect <- function(x, y, export = "gif", ...) { | |||
| x <- rlang::enquo(x) | |||
| y <- rlang::enquo(y) | |||
| animate_set(x, y, type = "intersect", export = export, ...) | |||
| } | |||
| #' @rdname animate_set | |||
| #' @export | |||
| animate_setdiff <- function(x, y, export = "gif", ...) { | |||
| x <- rlang::enquo(x) | |||
| y <- rlang::enquo(y) | |||
| animate_set(x, y, type = "setdiff", export = export, ...) | |||
| } | |||
| @@ -0,0 +1,123 @@ | |||
| #' Animates the gather function | |||
| #' | |||
| #' @param w a data_frame in the wide format | |||
| #' @param key the key | |||
| #' @param value the value | |||
| #' @param ... further arguments passed to [tidyr::gather()], [process_wide()], | |||
| #' or [process_long()] | |||
| #' @param detailed boolean value if the animation should show one step for each | |||
| #' key value | |||
| #' @inheritParams animate_join | |||
| #' @inheritParams anim_options | |||
| #' | |||
| #' @return a gif or a ggplot | |||
| #' @export | |||
| #' | |||
| #' @examples | |||
| #' wide <- data_frame( | |||
| #' year = 2010:2011, | |||
| #' Alice = c(105, 110), | |||
| #' Bob = c(100, 97), | |||
| #' Charlie = c(90, 95) | |||
| #' ) | |||
| #' animate_gather(wide, "person", "sales", -year, export = "first") | |||
| #' animate_gather(wide, "person", "sales", -year, export = "last") | |||
| #' | |||
| #' \donttest{ | |||
| #' animate_gather(wide, "person", "sales", -year, export = "gif") | |||
| #' # if you want to have a less detailed animation, you can also use | |||
| #' animate_gather(wide, "person", "sales", -year, export = "gif", detailed = FALSE) | |||
| #' } | |||
| animate_gather <- function(w, key, value, ..., export = "gif", detailed = TRUE, anim_opts = anim_options()) { | |||
| anim_opts <- default_anim_opts("gather", anim_opts) | |||
| lhs <- w | |||
| rhs <- tidyr::gather(w, !!key, !!value, ...) | |||
| # construct the title sequence | |||
| wname <- deparse(substitute(w)) | |||
| tidyr_selection <- get_quos_names(...) | |||
| ids <- setdiff(colnames(w), tidyselect::vars_select(colnames(w), ...)) | |||
| id_string <- paste0(", ", paste(sprintf("%s", tidyr_selection), collapse = ", ")) | |||
| sequence <- c( | |||
| current_state = "wide", | |||
| final_state = "long", | |||
| operation = sprintf("gather(%s, %s, %s%s)", | |||
| wname, | |||
| dput_parser(key), | |||
| dput_parser(value), | |||
| id_string), | |||
| reverse_operation = sprintf("spread(%s, %s, %s)", | |||
| "long", | |||
| dput_parser(key), | |||
| dput_parser(value)) | |||
| ) | |||
| key_values <- rhs %>% pull(key) %>% unique() | |||
| lhs_proc <- process_wide(lhs, ids, key, key_values, value, ...) | |||
| rhs_proc <- process_long(rhs, ids, key, value, ...) | |||
| gather_spread(lhs_proc, rhs_proc, sequence = sequence, key_values = key_values, | |||
| export = export, detailed = detailed, ..., anim_opts = anim_opts) | |||
| } | |||
| #' Animates the spread function | |||
| #' | |||
| #' @param l a data_frame in the long/tidy format | |||
| #' @param ... further arguments passed to [process_long] or [process_wide] | |||
| #' @inheritParams animate_gather | |||
| #' @inheritParams animate_join | |||
| #' @inheritParams anim_options | |||
| #' | |||
| #' @return a ggplot or a gif | |||
| #' @export | |||
| #' | |||
| #' @examples | |||
| #' long <- data_frame( | |||
| #' year = c(2010L, 2011L, 2010L, 2011L, 2010L, 2011L), | |||
| #' person = c("Alice", "Alice", "Bob", "Bob", "Charlie", "Charlie"), | |||
| #' sales = c(105, 110, 100, 97, 90, 95) | |||
| #' ) | |||
| #' animate_spread(long, key = "person", value = "sales", export = "first") | |||
| #' animate_spread(long, key = "person", value = "sales", export = "last") | |||
| #' | |||
| #' \donttest{ | |||
| #' animate_spread(long, key = "person", value = "sales", export = "gif") | |||
| #' # if you want to have a less detailed animation, you can also use | |||
| #' animate_spread(long, key = "person", value = "sales", export = "gif", detailed = FALSE) | |||
| #' } | |||
| animate_spread <- function(l, key, value, export = "gif", detailed = TRUE, ..., anim_opts = anim_options()) { | |||
| anim_opts <- default_anim_opts("spread", anim_opts) | |||
| lhs <- l | |||
| rhs <- tidyr::spread(l, key = key, value = value) | |||
| # construct the title sequence | |||
| lname <- deparse(substitute(l)) | |||
| ids <- names(lhs) | |||
| ids <- ids[!ids %in% c(key, value)] | |||
| id_string <- paste0(", ", paste(sprintf("-%s", ids), collapse = ", ")) | |||
| sequence <- c( | |||
| current_state = "long", | |||
| final_state = "wide", | |||
| operation = sprintf("spread(%s, %s, %s)", | |||
| lname, | |||
| dput_parser(key), | |||
| dput_parser(value)), | |||
| reverse_operation = sprintf("gather(%s, %s, %s%s)", | |||
| "wide", | |||
| dput_parser(key), | |||
| dput_parser(value), | |||
| id_string) | |||
| ) | |||
| lhs_proc <- process_long(lhs, ids, key, value, ...) | |||
| rhs_proc <- process_wide(rhs, ids, key, value, ...) | |||
| key_values <- lhs %>% pull(key) %>% unique() | |||
| gather_spread(lhs_proc, rhs_proc, sequence, key_values, export, detailed, ..., anim_opts = anim_opts) | |||
| } | |||
| @@ -1,48 +0,0 @@ | |||
| source(here::here("R/00_base_join.R")) | |||
| initial_join_dfs <- initial_join_dfs %>% | |||
| arrange(.x, .y) %>% | |||
| mutate(.obj = row_number(), .obj = .obj + 90 * as.integer(.id == "y")) | |||
| aj_step2 <- initial_join_dfs %>% | |||
| filter(.id == "x" | value %in% paste(1:2)) %>% | |||
| mutate(frame = 2, | |||
| .x = ifelse(.id == "y", 2.5, .x + 1.5), | |||
| alpha = case_when( | |||
| .x > 3 && .id == "x" ~ 0.5, | |||
| .y > -2.5 ~ 0.25, | |||
| TRUE ~ 1 | |||
| )) | |||
| aj_step3 <- aj_step2 %>% | |||
| filter(alpha == 1) %>% | |||
| mutate(frame = 3) | |||
| aj_step4 <- aj_step2 %>% | |||
| filter(alpha == 1) %>% | |||
| mutate(frame = 4, .y = -1) | |||
| aj <- bind_rows( | |||
| initial_join_dfs, | |||
| aj_step2, | |||
| aj_step3, | |||
| aj_step4 | |||
| ) %>% | |||
| mutate( | |||
| alpha = ifelse(is.na(alpha), 1, alpha), | |||
| .obj = ifelse(value == 4, 0, .obj) | |||
| ) %>% | |||
| arrange(.obj, frame) %>% | |||
| plot_data("anti_join(x, y)") %>% | |||
| animate_plot(transition_length = c(2, 1, 2), | |||
| state_length = c(1, 0, 0, 1)) | |||
| aj <- animate(aj) | |||
| anim_save(here::here("images", "anti-join.gif"), aj) | |||
| aj_g <- anti_join(x, y, by = "id") %>% | |||
| proc_data() %>% | |||
| mutate(.x = .x + 1.5) %>% | |||
| plot_data_join("anti_join(x, y)") | |||
| save_static_plot(aj_g, "anti-join") | |||
| @@ -1,29 +0,0 @@ | |||
| source(here::here("R/00_base_join.R")) | |||
| fj_joined_df <- full_join(x, y, "id") %>% | |||
| proc_data("x") %>% | |||
| mutate(.id = ifelse(value %in% c("4", "y4"), "y", .id)) %>% | |||
| mutate(frame = 2, .x = .x + 1) | |||
| fj_extra_blocks <- inner_join(x, y, "id") %>% | |||
| select(id) %>% | |||
| proc_data("y") %>% | |||
| mutate(frame = 2, .x = .x + 1) | |||
| fj <- initial_join_dfs %>% | |||
| bind_rows(fj_joined_df, fj_extra_blocks) %>% | |||
| plot_data("full_join(x, y)") + | |||
| transition_states(frame, transition_length = 2, state_length = 1) + | |||
| enter_appear() + | |||
| exit_disappear(early = TRUE) + | |||
| ease_aes("sine-in-out") | |||
| fj <- animate(fj) | |||
| anim_save(here::here("images", "full-join.gif"), fj) | |||
| fj_g <- full_join(x, y, "id") %>% | |||
| proc_data() %>% | |||
| mutate(.x = .x + 1) %>% | |||
| plot_data_join("full_join(x, y)", ylims = ylim(-4.5, -0.5)) | |||
| save_static_plot(fj_g, "full-join") | |||
| @@ -1,29 +0,0 @@ | |||
| source(here::here("R/00_base_join.R")) | |||
| ij_joined_df <- inner_join(x, y, "id") | |||
| ij_joined_df <- bind_rows( | |||
| proc_data(ij_joined_df, "x"), | |||
| proc_data(ij_joined_df, "y") | |||
| ) %>% | |||
| filter(!(label == "x" & .id == "y") & !(label == "y" & .id == "x")) %>% | |||
| mutate(frame = 2, .x = .x + 1) | |||
| ij <- bind_rows( | |||
| initial_join_dfs, | |||
| ij_joined_df | |||
| ) %>% | |||
| mutate(removed = value %in% c("3", "4", "x3", "y4"), | |||
| removed = as.integer(removed)) %>% | |||
| arrange(desc(frame), removed, desc(.id)) %>% | |||
| plot_data("inner_join(x, y)") %>% | |||
| animate_plot() | |||
| ij <- animate(ij) | |||
| anim_save(here::here("images", "inner-join.gif"), ij) | |||
| ij_g <- inner_join(x, y, by = "id") %>% | |||
| proc_data() %>% | |||
| mutate(.x = .x + 1) %>% | |||
| plot_data_join("inner_join(x, y)") | |||
| save_static_plot(ij_g, "inner-join") | |||
| @@ -1,28 +0,0 @@ | |||
| source(here::here("R/00_base_set.R")) | |||
| ins_df <- intersect(x,y) | |||
| ins_step2 <- | |||
| bind_rows( | |||
| proc_data_set(ins_df, "x"), | |||
| proc_data_set(ins_df, "y") | |||
| ) %>% | |||
| filter(.y == -1) %>% | |||
| mutate(frame = 2, .x = .x + 1.5) | |||
| ins <- | |||
| initial_set_dfs %>% | |||
| bind_rows(ins_step2) %>% | |||
| arrange(desc(frame)) %>% | |||
| plot_data_set("intersect(x, y)") %>% | |||
| animate_plot() | |||
| ins <- animate(ins) | |||
| anim_save(here::here("images", "intersect.gif"), ins) | |||
| ins_g <- intersect(x, y) %>% | |||
| proc_data_set() %>% | |||
| mutate(.x = .x + 1.5) %>% | |||
| plot_data_set("intersect(x, y)") | |||
| save_static_plot(ins_g, "intersect") | |||
| @@ -1,27 +0,0 @@ | |||
| source(here::here("R/00_base_join.R")) | |||
| lj_joined_dfs <- left_join(x, y, "id") %>% | |||
| proc_data("x") %>% | |||
| mutate(frame = 2, .x = .x + 1) | |||
| lj_extra_blocks <- inner_join(x, y, "id") %>% | |||
| select(id) %>% | |||
| proc_data("y") %>% | |||
| mutate(frame = 2, .x = .x + 1) | |||
| lj <- bind_rows( | |||
| initial_join_dfs, | |||
| lj_joined_dfs, | |||
| lj_extra_blocks | |||
| ) %>% | |||
| mutate(color = ifelse(is.na(value), "#ffffff", color)) %>% | |||
| arrange(value) %>% | |||
| plot_data("left_join(x, y)") %>% | |||
| animate_plot() | |||
| lj <- animate(lj) | |||
| anim_save(here::here("images", "left-join.gif"), lj) | |||
| lj_g <- plot_data_join(lj_joined_dfs, "left_join(x, y)") | |||
| save_static_plot(lj_g, "left-join") | |||
| @@ -1,71 +0,0 @@ | |||
| source(here::here("R/00_base_join.R")) | |||
| y_extra <- bind_rows(y, data_frame(id = 2, y = "y5")) | |||
| # I manually linked objects together, it was late and this was easier... | |||
| anim_df <- tibble::tribble( | |||
| ~.y, ~label, ~value, ~.x, ~.id, ~color, ~frame, ~obj, | |||
| -1L, "id", "1", 1, "x", "#E41A1C", 1, 1, | |||
| -2L, "id", "2", 1, "x", "#377EB8", 1, 2, | |||
| -2L, "id", "2", 1, "x", "#377EB8", 1, 3, | |||
| -3L, "id", "3", 1, "x", "#4DAF4A", 1, 4, | |||
| -1L, "x", "x1", 2, "x", "#d0d0d0", 1, 5, | |||
| -2L, "x", "x2", 2, "x", "#d0d0d0", 1, 6, | |||
| -3L, "x", "x3", 2, "x", "#d0d0d0", 1, 8, | |||
| -2L, "x", "x2", 2, "x", "#d0d0d0", 1, 7, | |||
| -1L, "id", "1", 4, "y", "#E41A1C", 1, 9, | |||
| -2L, "id", "2", 4, "y", "#377EB8", 1, 10, | |||
| -3L, "id", "4", 4, "y", "#984EA3", 1, 99, | |||
| -4L, "id", "2", 4, "y", "#377EB8", 1, 11, | |||
| -1L, "y", "y1", 5, "y", "#d0d0d0", 1, 12, | |||
| -2L, "y", "y2", 5, "y", "#d0d0d0", 1, 13, | |||
| -3L, "y", "y4", 5, "y", "#d0d0d0", 1, 98, | |||
| -4L, "y", "y5", 5, "y", "#d0d0d0", 1, 14, | |||
| -1L, "id", "1", 2, "x", "#E41A1C", 2, 1, | |||
| -2L, "id", "2", 2, "x", "#377EB8", 2, 2, | |||
| -3L, "id", "2", 2, "x", "#377EB8", 2, 3, | |||
| -4L, "id", "3", 2, "x", "#4DAF4A", 2, 4, | |||
| -1L, "x", "x1", 3, "x", "#d0d0d0", 2, 5, | |||
| -2L, "x", "x2", 3, "x", "#d0d0d0", 2, 6, | |||
| -3L, "x", "x2", 3, "x", "#d0d0d0", 2, 7, | |||
| -4L, "x", "x3", 3, "x", "#d0d0d0", 2, 8, | |||
| -1L, "y", "y1", 4, "x", "#d0d0d0", 2, 12, | |||
| -2L, "y", "y2", 4, "x", "#d0d0d0", 2, 13, | |||
| -3L, "y", "y5", 4, "x", "#d0d0d0", 2, 14, | |||
| -1L, "id", "1", 2, "y", "#E41A1C", 2, 9, | |||
| -2L, "id", "2", 2, "y", "#377EB8", 2, 10, | |||
| -3L, "id", "2", 2, "y", "#377EB8", 2, 11 | |||
| ) | |||
| lj_extra <- anim_df %>% | |||
| arrange(obj, frame) %>% | |||
| plot_data("left_join(x, y)") %>% | |||
| animate_plot() | |||
| lj_extra <- animate(lj_extra) | |||
| anim_save(here::here("images", "left-join-extra.gif"), lj_extra) | |||
| ## Save static images | |||
| df_names <- data_frame( | |||
| .x = c(1.5, 4.5), .y = 0.25, | |||
| value = c("x", "y"), | |||
| size = 12, | |||
| color = "black" | |||
| ) | |||
| g_input <- proc_data(y_extra) %>% | |||
| mutate(.x = .x + 3) %>% | |||
| bind_rows(proc_data(x)) %>% | |||
| plot_data() + | |||
| geom_text(data = df_names, family = "Fira Mono", size = 24) + | |||
| annotate("text", label = "↑ duplicate keys in y", x = 4.5, y = -4.75, | |||
| family = "Fira Sans", color = "grey45") | |||
| save_static_plot(g_input, "left-join-extra-input") | |||
| lj_g <- left_join(x, y_extra, by = "id") %>% | |||
| proc_data() %>% | |||
| mutate(.x = .x + 1) %>% | |||
| plot_data_join("left_join(x, y)", ylims = ylim(-4.5, -0.5)) | |||
| save_static_plot(lj_g, "left-join-extra") | |||
| @@ -0,0 +1,105 @@ | |||
| #' Combines two processed datasets and combines them for a given method | |||
| #' | |||
| #' @param lhs the left-hand side dataset | |||
| #' @param rhs the righ-hand side dataset | |||
| #' @param type a string of the desired combination method, allowed are all dplyr | |||
| #' joins or sets | |||
| #' | |||
| #' @return processed dataset of the combined values | |||
| #' | |||
| #' @examples | |||
| #' NULL | |||
| move_together <- function(lhs, rhs, type) { | |||
| all <- bind_rows(lhs, rhs) | |||
| # separate column and row-filter (ids) | |||
| x_cols <- dplyr::distinct(lhs, .col) | |||
| y_cols <- dplyr::distinct(rhs, .col) | |||
| # separate header columns from ids and treat them as columns | |||
| x_ids <- dplyr::distinct(lhs, .id, .id_long) | |||
| y_ids <- dplyr::distinct(rhs, .id, .id_long) | |||
| x_headers <- filter(x_ids, grepl("^\\.header", .id_long)) | |||
| y_headers <- filter(y_ids, grepl("^\\.header", .id_long)) | |||
| x_ids <- x_ids %>% filter(!grepl("^\\.header", .id_long)) | |||
| y_ids <- y_ids %>% filter(!grepl("^\\.header", .id_long)) | |||
| # assign two combiner functions depending on the type | |||
| # one for combining the columns (col_combiner) | |||
| # one for combining the rows (row_combiner) | |||
| if (type == "full_join") { | |||
| col_combiner <- dplyr::full_join | |||
| row_combiner <- dplyr::full_join | |||
| } else if (type == "inner_join") { | |||
| col_combiner <- dplyr::full_join | |||
| row_combiner <- dplyr::inner_join | |||
| } else if (type == "left_join") { | |||
| col_combiner <- dplyr::full_join | |||
| row_combiner <- dplyr::left_join | |||
| } else if (type == "right_join") { | |||
| col_combiner <- dplyr::full_join | |||
| row_combiner <- dplyr::right_join | |||
| } else if (type == "semi_join") { | |||
| col_combiner <- dplyr::left_join | |||
| row_combiner <- dplyr::semi_join | |||
| } else if (type == "anti_join") { | |||
| col_combiner <- dplyr::left_join | |||
| row_combiner <- dplyr::anti_join | |||
| } else if (type == "union") { | |||
| col_combiner <- dplyr::full_join | |||
| row_combiner <- dplyr::union | |||
| } else if (type == "union_all") { | |||
| col_combiner <- dplyr::full_join | |||
| row_combiner <- dplyr::union_all | |||
| } else if (type == "intersect") { | |||
| col_combiner <- dplyr::full_join | |||
| row_combiner <- dplyr::intersect | |||
| } else if (type == "setdiff") { | |||
| col_combiner <- dplyr::full_join | |||
| row_combiner <- dplyr::anti_join | |||
| } else if (type == "bind_rows") { | |||
| col_combiner <- dplyr::full_join | |||
| row_combiner <- dplyr::bind_rows | |||
| } else if (type == "bind_cols") { | |||
| col_combiner <- dplyr::full_join | |||
| row_combiner <- dplyr::left_join | |||
| } else { | |||
| stop("Unknown func") | |||
| } | |||
| take_cols <- col_combiner(x_cols, y_cols, by = ".col") | |||
| take_ids <- row_combiner(x_ids, y_ids, by = c(".id", ".id_long")) | |||
| take_headers <- col_combiner(x_headers, y_headers, by = c(".id", ".id_long")) | |||
| take_ids <- bind_rows(take_headers, take_ids) | |||
| take <- tidyr::crossing(take_ids, take_cols) | |||
| mid <- (2 + length(unique(lhs$.col)) + length(unique(rhs$.col))) / 2 | |||
| xvals <- 1:nrow(take_cols) | |||
| xvals <- xvals - mean(xvals) + mid | |||
| names(xvals) <- pull(take_cols, .col) | |||
| yvals <- cumsum(ifelse(grepl("^\\.header", take_ids$.id_long), 0, -1)) | |||
| names(yvals) <- pull(take_ids, .id_long) | |||
| take_vals <- semi_join(all, take %>% select(".id", ".col"), | |||
| by = c(".id", ".col")) %>% | |||
| mutate(.alpha = 1, | |||
| .x = xvals[.col], | |||
| .y = yvals[.id_long]) | |||
| bind_rows( | |||
| # take, | |||
| take_vals, | |||
| # fade in place: | |||
| all %>% filter(!.id_long %in% take_ids$.id_long) %>% mutate(.alpha = 0), | |||
| # moving fade or fade in place as well: | |||
| all %>% filter(.id_long %in% take_ids$.id_long & !.col %in% take_cols$.col) %>% | |||
| mutate(.alpha = 0) | |||
| ) | |||
| } | |||
| @@ -0,0 +1,76 @@ | |||
| #' Animate a Plot | |||
| #' | |||
| #' @param d a processed dataset | |||
| #' @param title the title of the plot | |||
| #' @param anim_opts Animation options generated with [anim_options()]. Overrides | |||
| #' any options set in `...`. | |||
| #' @return a `gganim` object | |||
| #' @examples | |||
| #' NULL | |||
| animate_plot <- function( | |||
| d, | |||
| title = "", | |||
| ..., | |||
| anim_opts = anim_options(...) | |||
| ) { | |||
| ao <- validate_anim_opts(anim_opts) | |||
| ease_opts <- if (!is.null(ao$ease_other)) { | |||
| ao$ease_other$default <- ao$ease_default | |||
| ao$ease_other | |||
| } else list(default = ao$ease_default) | |||
| ao_ease_aes <- do.call(ease_aes, ease_opts) | |||
| static_plot(d, title, anim_opts = ao) + | |||
| transition_states(.frame, ao$transition_length, ao$state_length) + | |||
| ao$enter[[1]] + | |||
| ao$exit[[1]] + | |||
| ao_ease_aes | |||
| } | |||
| #' Prints the tiles for a processed dataset statically | |||
| #' | |||
| #' @inheritParams animate_plot | |||
| #' @inheritDotParams anim_options | |||
| #' | |||
| #' @return a ggplot | |||
| #' | |||
| #' @examples | |||
| #' NULL | |||
| static_plot <- function( | |||
| d, | |||
| title = "", | |||
| ..., | |||
| anim_opts = anim_options(...) | |||
| ) { | |||
| ao <- validate_anim_opts(anim_opts) | |||
| text_size <- get_text_size(ao$text_size) | |||
| title_size <- get_title_size(ao$title_size) | |||
| if (!".alpha" %in% names(d)) d <- d %>% mutate(.alpha = 1) | |||
| if (!".textcolor" %in% names(d)) | |||
| d <- d %>% mutate(.textcolor = choose_text_color(.color)) | |||
| if (".id_long" %in% names(d)) { | |||
| d <- d %>% mutate(.item_id = paste(.id_long, .col, sep = "-")) | |||
| } else { | |||
| # tidyr | |||
| d <- d %>% mutate(.item_id = .id) | |||
| } | |||
| width <- ao$cell_width %||% 1 | |||
| height <- ao$cell_height %||% 1 | |||
| ggplot(d, aes(x = .x * width, y = .y * height, fill = .color, alpha = .alpha, | |||
| group = .item_id)) + | |||
| geom_tile(width = 0.9 * width, height = 0.9 * height) + | |||
| coord_equal() + | |||
| geom_text(data = d %>% filter(!is.na(.val)), aes(label = .val, color = .textcolor), | |||
| family = ao$text_family, size = text_size) + | |||
| scale_fill_identity() + | |||
| scale_color_identity() + | |||
| scale_alpha_identity() + | |||
| labs(title = title) + | |||
| theme_void() + | |||
| theme(plot.title = element_text(family = ao$title_family, hjust = 0.5, size = title_size)) | |||
| } | |||
| @@ -0,0 +1,155 @@ | |||
| #' Preprocess data | |||
| #' | |||
| #' @param x a left dataset | |||
| #' @param y a right dataset | |||
| #' @param by a by argument for joins / set operations | |||
| #' @param fill if missing ids should be filled | |||
| #' @param ... further arguments passed to add_color | |||
| #' @param ao anim_options() | |||
| #' | |||
| #' @return a preprocessed dataset | |||
| #' | |||
| #' @examples | |||
| #' NULL | |||
| process_join <- function(x, y, by, fill = TRUE, ..., | |||
| ao = anim_options(...)) { | |||
| #' test for | |||
| #' a <- c("unique", "mult", "mult", "also unique") | |||
| #' add_duplicate_number(a) | |||
| add_duplicate_number <- function(a) { | |||
| data_frame(v = a) %>% | |||
| group_by(v) %>% | |||
| mutate(id = paste(v, 1:n(), sep = "-")) %>% | |||
| pull(id) | |||
| } | |||
| x <- x %>% | |||
| tidyr::unite(dplyr::one_of(by), col = ".id", remove = FALSE) %>% | |||
| mutate(.id_long = add_duplicate_number(.id)) | |||
| y <- y %>% | |||
| tidyr::unite(dplyr::one_of(by), col = ".id", remove = FALSE) %>% | |||
| mutate(.id_long = add_duplicate_number(.id)) | |||
| ids <- dplyr::union(x %>% dplyr::select(.id, .id_long), | |||
| y %>% dplyr::select(.id, .id_long)) | |||
| x_ <- process_data_join(x, ids, by, fill = fill, ao = ao) | |||
| y_ <- process_data_join(y, ids, by, fill = fill, ao = ao) %>% | |||
| mutate(.x = .x + ncol(x) - 1) | |||
| list(x = x_, y = y_) | |||
| } | |||
| #' Processes the data | |||
| #' | |||
| #' @param x a preprocessed dataset | |||
| #' @param ids a data_frame of ids (.id and .id_long) | |||
| #' @param by a vector of by-arguments | |||
| #' @param width the width of the tiles | |||
| #' @param side the side (x or y, lhs or rhs, etc) | |||
| #' @param fill if missing ids should be filled | |||
| #' @param ... further arguments passed to add_color | |||
| #' @param ao anim_options | |||
| #' | |||
| #' @return a data_frame including all necessary information | |||
| #' | |||
| #' @examples | |||
| #' NULL | |||
| process_data_join <- function(x, ids, by, width = 1, side = NA, fill = TRUE, | |||
| ..., | |||
| ao = anim_options(...)) { | |||
| if (is.na(side)) side <- deparse(substitute(x)) | |||
| x_names <- names(x)[grepl("^[^\\.]", names(x))] | |||
| x_keys <- 1:length(x_names) | |||
| names(x_keys) <- x_names | |||
| special_vars <- names(x)[grepl("^\\.", names(x))] | |||
| x <- x %>% | |||
| mutate(.r = row_number()) %>% | |||
| tidyr::gather_(key = ".col", value = ".val", names(x)[grepl("^[^.]", names(x))]) %>% | |||
| mutate(.x = x_keys[.col], | |||
| .y = -.r) %>% | |||
| bind_rows(data_frame(.id = ".header", | |||
| .id_long = paste(".header", x_names, sep = "_"), | |||
| .r = 0, | |||
| .col = x_names, | |||
| .val = x_names, | |||
| .x = x_keys, .y = 0), .) %>% | |||
| mutate(.width = width, | |||
| .side = side) | |||
| # if there are multiple values in the ids (-2, -3 etc) but they are not present | |||
| # in x, because it is in the second/other dataset, add these values here | |||
| id_long <- ids$.id_long | |||
| mis_ids <- id_long[!id_long %in% x$.id_long] | |||
| # if the missing value is a -1, that means the missing value comes not from | |||
| # missing dublicate ids | |||
| mis_ids <- mis_ids[grepl("[^-1]$", mis_ids)] | |||
| if (length(mis_ids) > 0 && fill) { | |||
| mis_ids_short <- gsub("-[0-9]+$", "", mis_ids) | |||
| # insert the missing ids at the right place | |||
| for (i in mis_ids_short) { | |||
| irow <- (1:nrow(x))[x$.id == i] | |||
| irow <- irow[1] | |||
| x <- bind_rows( | |||
| x %>% slice(1:irow), | |||
| x %>% filter(.id %in% mis_ids_short) %>% mutate(.id_long = mis_ids), | |||
| x %>% slice((irow + 1):nrow(x)) | |||
| ) | |||
| } | |||
| } | |||
| add_color_join(x, rev(ids$.id), by, ao) | |||
| } | |||
| #' Adds Color to a processed data_frame | |||
| #' | |||
| #' @param x a processed data_frame | |||
| #' @param ids a vector of ids for the color-matching | |||
| #' @param by a vector of column names that constitute the by-argument of joins/sets | |||
| #' @param color_header color for the header | |||
| #' @param color_other color for "inactive" values | |||
| #' @param color_missing color for missing values | |||
| #' @param color_fun the function to generate the colors | |||
| #' @param text_color the color for the text inside the tiles, | |||
| #' defaults to white/black depending on tile color | |||
| #' @param ... | |||
| #' | |||
| #' @return the processed data_frame with a new column .color | |||
| #' | |||
| #' @examples | |||
| #' NULL | |||
| add_color_join <- function(x, ids, by, ao, ...) { | |||
| color_header <- ao$color_header %||% get_anim_opt("color_header") | |||
| color_other <- ao$color_other %||% get_anim_opt("color_other") | |||
| color_missing <- ao$color_missing %||% get_anim_opt("color_missing") | |||
| color_fun <- ao$color_fun %||% get_anim_opt("color_fun") | |||
| text_color <- ao$text_color %||% get_anim_opt("text_color") | |||
| colors <- c(color_header, color_fun(length(ids))) | |||
| names(colors) <- c(".header", ids) | |||
| res <- x %>% | |||
| mutate( | |||
| .color = ifelse(is.na(.val), | |||
| color_missing, | |||
| ifelse(.col %in% by, | |||
| colors[.id], | |||
| color_other)), | |||
| .color = ifelse(.id == ".header", color_header, .color), | |||
| .textcolor = text_color) | |||
| if (is.na(text_color)) | |||
| res <- res %>% mutate(.textcolor = choose_text_color(.color)) | |||
| return(res) | |||
| } | |||
| @@ -1,30 +0,0 @@ | |||
| source(here::here("R/00_base_join.R")) | |||
| rj_joined_dfs <- right_join(x, y, "id") %>% | |||
| proc_data("y") %>% | |||
| mutate(frame = 2, .x = .x + 1) | |||
| rj_extra_blocks <- inner_join(x, y, "id") %>% | |||
| select(id) %>% | |||
| proc_data("x") %>% | |||
| mutate(frame = 2, .x = .x + 1) | |||
| rj <- bind_rows( | |||
| initial_join_dfs, | |||
| rj_joined_dfs, | |||
| rj_extra_blocks | |||
| ) %>% | |||
| filter(!is.na(value)) %>% | |||
| mutate( | |||
| .id = ifelse(label == "x", label, .id), | |||
| removed = as.integer(grepl("3", value)) | |||
| ) %>% | |||
| arrange(removed, value, .id, frame) %>% | |||
| plot_data("right_join(x, y)") %>% | |||
| animate_plot() | |||
| rj <- animate(rj) | |||
| anim_save(here::here("images", "right-join.gif"), rj) | |||
| rj_g <- plot_data(rj_joined_dfs, "right_join(x, y)") | |||
| save_static_plot(rj_g, "right-join") | |||
| @@ -1,30 +0,0 @@ | |||
| source(here::here("R/00_base_join.R")) | |||
| sj_joined_df <- semi_join(x, y, "id") %>% | |||
| proc_data("x") %>% | |||
| mutate(frame = 2, .x = .x + 1.5) | |||
| sj_extra_blocks <- inner_join(x, y, "id") %>% | |||
| select(id) %>% | |||
| proc_data("y") %>% | |||
| mutate(frame = 2, .x = .x + 1.5) | |||
| sj <- bind_rows( | |||
| initial_join_dfs, | |||
| sj_joined_df, | |||
| sj_extra_blocks | |||
| ) %>% | |||
| arrange(value) %>% | |||
| plot_data("semi_join(x, y)") %>% | |||
| animate_plot() | |||
| sj <- animate(sj) | |||
| anim_save(here::here("images", "semi-join.gif"), sj) | |||
| # Static Images | |||
| sj_g <- semi_join(x, y, "id") %>% | |||
| proc_data() %>% | |||
| mutate(.x = .x + 1.5) %>% | |||
| plot_data_join("semi_join(x, y)") | |||
| save_static_plot(sj_g, "semi-join") | |||
| @@ -1,102 +0,0 @@ | |||
| source(here::here("R/00_base_set.R")) | |||
| # ---- setdiff(x, y) ---- | |||
| # Dim elements unique to y | |||
| setd_step2 <- initial_set_dfs %>% | |||
| mutate( | |||
| frame = 2, | |||
| alpha = case_when( | |||
| .y == -1 ~ 0.55, | |||
| .id == "y" ~ 0.15, | |||
| TRUE ~ 1 | |||
| ) | |||
| ) | |||
| # Merge, dim overlapping elements | |||
| setd_step3 <- initial_set_dfs %>% | |||
| filter(!(.id == "y" & .y == -2)) %>% | |||
| mutate( | |||
| frame = 3, | |||
| alpha = ifelse(.y == -1, 0.25, 1), | |||
| .x = ifelse(.id == "y", .x - 3, .x), | |||
| .x = .x + 1.5 | |||
| ) | |||
| # Result of setdiff | |||
| setd_step4 <- setdiff(x, y) %>% | |||
| proc_data_set("xy") %>% | |||
| mutate(frame = 4, .x = .x + 1.5) | |||
| setd <- bind_rows( | |||
| initial_set_dfs, | |||
| setd_step2, | |||
| setd_step3, | |||
| setd_step4 | |||
| ) %>% | |||
| mutate(alpha = ifelse(is.na(alpha), 1, alpha)) %>% | |||
| arrange(frame, desc(.y), desc(.id)) %>% | |||
| plot_data_set(., "setdiff(x, y)") %>% | |||
| animate_plot() | |||
| setd <- animate(setd) | |||
| anim_save(here::here("images", "setdiff.gif"), setd) | |||
| setd_g <- setdiff(x, y) %>% | |||
| proc_data_set() %>% | |||
| mutate(.x = .x + 1.5) %>% | |||
| plot_data_set("setdiff(x, y)") | |||
| save_static_plot(setd_g, "setdiff") | |||
| # ---- setdiff(y, x) ---- | |||
| # Dim elements unique to x | |||
| setd2_step2 <- initial_set_dfs %>% | |||
| mutate( | |||
| frame = 2, | |||
| alpha = case_when( | |||
| .y == -1 ~ 0.55, | |||
| .id == "x" ~ 0.15, | |||
| TRUE ~ 1 | |||
| ) | |||
| ) | |||
| # Merge, dim overlapping elements | |||
| setd2_step3 <- initial_set_dfs %>% | |||
| filter(!(.id == "x" & .y <= -2)) %>% | |||
| mutate( | |||
| frame = 3, | |||
| alpha = ifelse(.y == -1, 0.25, 1), | |||
| .x = ifelse(.id == "y", .x - 3, .x), | |||
| .x = .x + 1.5 | |||
| ) | |||
| # Result of setdiff | |||
| setd2_step4 <- setdiff(y, x) %>% | |||
| proc_data_set("xy") %>% | |||
| mutate(frame = 4, .x = .x + 1.5) | |||
| setd2 <- bind_rows( | |||
| initial_set_dfs, | |||
| setd2_step2, | |||
| setd2_step3, | |||
| setd2_step4 | |||
| ) %>% | |||
| mutate(alpha = ifelse(is.na(alpha), 1, alpha)) %>% | |||
| arrange(frame, desc(.y), .id) %>% | |||
| plot_data_set(., "setdiff(y, x)") %>% | |||
| animate_plot() | |||
| setd2 <- animate(setd2) | |||
| anim_save(here::here("images", "setdiff-rev.gif"), setd2) | |||
| setd2_g <- setdiff(x, y) %>% | |||
| proc_data_set() %>% | |||
| mutate(.x = .x + 1.5) %>% | |||
| plot_data_set("setdiff(y, x)") | |||
| save_static_plot(setd2_g, "setdiff-rev") | |||
| @@ -0,0 +1,355 @@ | |||
| #' Gets the ... names | |||
| #' | |||
| #' Used to get the -year | |||
| #' | |||
| #' @param ... arguments | |||
| #' | |||
| #' @return a vector of the names of ... | |||
| #' | |||
| #' @examples | |||
| #' x <- 1:10 | |||
| #' y <- 1 | |||
| #' get_quos_names(-x) | |||
| #' get_quos_names(x:y) | |||
| get_quos_names <- function(...) { | |||
| q <- rlang::quos(...) | |||
| purrr::map_chr(q, rlang::quo_name) | |||
| } | |||
| #' Parses a simple vector so that it looks like its input | |||
| #' | |||
| #' @param x a vector | |||
| #' | |||
| #' @return a string | |||
| #' | |||
| #' @examples | |||
| #' dput_parser("x") | |||
| #' dput_parser(c("x", "y")) | |||
| dput_parser <- function(x) UseMethod("dput_parser") | |||
| dput_parser.character <- function(x) { | |||
| if (length(x) == 1) { | |||
| sprintf('"%s"', x) | |||
| } else { | |||
| x <- capture.output(dput(x)) | |||
| paste(x, collapse = "") | |||
| } | |||
| } | |||
| #' Adds color to processed tidy data | |||
| #' | |||
| #' @param x a processed data-frame as outputted by process_long or process_wide | |||
| #' @param key_values the unique key-values | |||
| #' @param color_fun the color function | |||
| #' @param color_header the color for the header | |||
| #' @param ... not used | |||
| #' | |||
| #' @return a data-frame with the colors | |||
| #' | |||
| #' @examples | |||
| #' NULL | |||
| add_color_tidyr <- function(x, key_values, | |||
| color_fun = scales::brewer_pal(type = "qual", "Set1"), | |||
| color_header = "#737373", | |||
| color_id = "#d0d0d0") { | |||
| color_dict <- color_fun(3) | |||
| names(color_dict) <- c("id", "key", "value") | |||
| x %>% mutate(.color = color_dict[.type]) | |||
| } | |||
| #' Processes a wide dataframe and converts it into a dataset that can be plotted | |||
| #' | |||
| #' @param x a wide data frame | |||
| #' @param ids a vector of id-variables that are already in the tidy-format | |||
| #' @param key a vector of key-variables | |||
| #' @param color_id the color for the id-body | |||
| #' @param ... | |||
| #' | |||
| #' @return TODO | |||
| #' | |||
| #' @examples | |||
| #' wide <- data_frame( | |||
| #' year = 2010:2011, | |||
| #' Alice = c(105, 110), | |||
| #' Bob = c(100, 97), | |||
| #' Charlie = c(90, 95) | |||
| #' ) | |||
| #' process_wide(wide, ids = "year", key = "person") | |||
| #' process_wide(wide, ids = "year", key = "person") %>% static_plot | |||
| process_wide <- function(x, ids, key, color_id = "lightgray", ...) { | |||
| if (!all(ids %in% names(x))) | |||
| stop("all ids must be in x") | |||
| nr <- nrow(x) | |||
| nc <- ncol(x) | |||
| key_values <- names(x) | |||
| key_values <- key_values[!key_values %in% ids] | |||
| id_values <- x %>% select(dplyr::one_of(ids)) | |||
| id_values <- id_values %>% tidyr::gather(key = ".key_map", value = ".id_map") | |||
| x <- x %>% mutate(.r = row_number()) %>% | |||
| tidyr::unite(dplyr::one_of(ids), col = ".id_map", remove = F) | |||
| x <- x %>% | |||
| gather(key = ".col", value = ".val", names(x)[grepl("^[^\\.]", names(x))]) %>% | |||
| mutate(.key_map = .col, | |||
| .type = ifelse(.col %in% ids, "id", "value"), | |||
| .val = as.character(.val), | |||
| .x = rep(1:nc, each = nr), | |||
| .y = -rep(1:nr, nc), | |||
| .header = F) | |||
| # make sure that we have one id value per key | |||
| tmp <- x %>% filter(.key_map %in% ids) | |||
| x <- bind_rows( | |||
| left_join(tmp %>% select(-.key_map), | |||
| tmp %>% select(.id_map) %>% tidyr::crossing(.key_map = key_values), | |||
| by = ".id_map"), | |||
| x %>% filter(!.key_map %in% ids) | |||
| ) | |||
| # add header: | |||
| crosser <- tidyr::crossing(.id_map = as.character(id_values$.id_map), | |||
| .key_map = key_values) | |||
| key_header <- data_frame( | |||
| .key_map = key_values, | |||
| .r = 0, | |||
| .col = key_values, | |||
| .val = key_values, | |||
| .type = "key", | |||
| .x = length(ids) + 1:length(key_values), | |||
| .y = 0, | |||
| .header = TRUE) %>% | |||
| left_join(crosser, by = ".key_map") | |||
| id_header <- left_join( | |||
| data_frame(.id_map = ids, | |||
| .r = 0, | |||
| .col = ids, | |||
| .val = ids, | |||
| .type = "id", | |||
| .x = 1:length(ids), | |||
| .y = 0, | |||
| .header = TRUE), | |||
| tidyr::crossing(.id_map = ids, .key_map = key_values), | |||
| by = ".id_map" | |||
| ) | |||
| x <- bind_rows(id_header, key_header, x) | |||
| x <- x %>% tidyr::unite(.key_map, .id_map, .val, col = ".id", remove = F) | |||
| x %>% | |||
| add_color_tidyr(key_values = key_values) %>% | |||
| mutate(.alpha = ifelse(.header == TRUE, 1, 0.6)) | |||
| } | |||
| #' Processes a long dataframe and converts it into a dataset that can be plotted | |||
| #' | |||
| #' @param x a long data frame | |||
| #' @param ids a vector of id-variables that are already in the tidy-format | |||
| #' @param key a vector of key-variables | |||
| #' @param ... | |||
| #' | |||
| #' @return TODO | |||
| #' | |||
| #' @examples | |||
| #' long <- data_frame( | |||
| #' year = c(2010L, 2011L, 2010L, 2011L, 2010L, 2011L), | |||
| #' person = c("Alice", "Alice", "Bob", "Bob", "Charlie", "Charlie"), | |||
| #' sales = c(105, 110, 100, 97, 90, 95) | |||
| #' ) | |||
| #' process_long(long, ids = "year", key = "person", value = "sales") | |||
| #' process_long(long, ids = "year", key = "person", value = "sales") %>% static_plot | |||
| process_long <- function(x, ids, key, value, ...) { | |||
| if (!all(c(ids, key, value) %in% names(x))) | |||
| stop("all ids, key, and value must be names of x") | |||
| nr <- nrow(x) | |||
| nc <- ncol(x) | |||
| xn <- names(x) | |||
| x <- x %>% mutate(.r = row_number()) %>% | |||
| tidyr::unite(ids, col = ".id_map", remove = F) %>% | |||
| tidyr::unite(key, col = ".key_map", remove = F) | |||
| key_values <- x %>% pull(key) %>% unique() | |||
| type_dict <- c(rep("id", length(ids)), rep("key", length(key)), rep("value", length(value))) | |||
| names(type_dict) <- c(ids, key, value) | |||
| x_dict <- 1:nc | |||
| names(x_dict) <- xn | |||
| x <- x %>% | |||
| tidyr::gather(key = ".col", value = ".val", names(x)[grepl("^[^\\.]", names(x))]) %>% | |||
| mutate( | |||
| .x = x_dict[.col], | |||
| .y = -rep(1:nr, nc), | |||
| .type = type_dict[.col], | |||
| .val = as.character(.val), | |||
| .header = FALSE | |||
| ) | |||
| # add headers: | |||
| id_headers <- tidyr::crossing(.id_map = ids, # x$.id_map %>% unique() | |||
| .key_map = key_values, | |||
| ) %>% | |||
| mutate( | |||
| .r = 0, | |||
| .col = "id", | |||
| .val = .id_map, | |||
| .x = x_dict[.val], | |||
| .y = 0, | |||
| .type = "id", | |||
| .header = TRUE | |||
| ) | |||
| x <- x %>% | |||
| dplyr::add_row( | |||
| .before = T, | |||
| .id_map = c(rep("key", length(key)), rep("value", length(value))), | |||
| .key_map = c(rep("key", length(key)), rep("value", length(value))), | |||
| .r = 0, | |||
| .col = c(rep("key", length(key)), rep("value", length(value))), | |||
| .val = c(key, value), | |||
| .x = length(ids) + 1:length(c(key, value)), | |||
| .y = 0, | |||
| .type = c(rep("key", length(key)), rep("value", length(value))), | |||
| .header = TRUE | |||
| ) | |||
| x <- bind_rows(id_headers, x) | |||
| x <- x %>% | |||
| tidyr::unite(.key_map, .id_map, .val, col = ".id", remove = F) | |||
| x %>% add_color_tidyr(key_values = key_values) %>% | |||
| mutate(.alpha = ifelse(.header == TRUE, 1, 0.6)) | |||
| } | |||
| #' Animates a gather or spread function | |||
| #' | |||
| #' internally used by animate_spread and animate_gather | |||
| #' | |||
| #' @param lhs the (processed) dataset on the left-side | |||
| #' @param rhs the (processed) dataset on the right-side | |||
| #' @param sequence a named vector of the sequence titles | |||
| #' (current_state, final_state, operation, and reverse_operation) | |||
| #' @param key_values the unique key-values | |||
| #' @param export the export type, either gif, first or last. The latter two | |||
| #' export ggplots of the first/last state of the join | |||
| #' @param detailed boolean value if the animation should show one step for each | |||
| #' key value | |||
| #' @param ... further arguments passed to animate_plot | |||
| #' | |||
| #' @return the plot or the gif | |||
| #' | |||
| #' @examples | |||
| #' NULL | |||
| gather_spread <- function(lhs, rhs, sequence, key_values, export, detailed, ..., | |||
| anim_opts = anim_options(...)) { | |||
| # lhs is the one state of the df | |||
| # rhs is the target state | |||
| # animate the four steps: inital with sequence[["current_state]], | |||
| # transformations by the unique key-values with sequence[["operation"]], | |||
| # final with sequence[["final_state"]] | |||
| # and back transformation with sequence[["reverse_operation]] | |||
| # have lhs and rhs in the right format: preprocessed with ids, .x, .y etc. | |||
| # have a color function that makes coloring easier | |||
| # transformations: for each key-variable: respective ids "fly in", keys fly in and ids fly in (all in one step for one key. i.e., Alice) | |||
| # how much is the rhs to the left of lhs? | |||
| if (!detailed) { | |||
| anim_df <- bind_rows( | |||
| lhs %>% mutate(.frame = 0), | |||
| rhs %>% mutate(.frame = 1) | |||
| ) | |||
| frame_labels <- c(sequence[["operation"]], sequence[["reverse_operation"]]) | |||
| title_string <- "{ifelse(transitioning, previous_state, ifelse(grepl('gather', next_state), 'Wide', 'Long'))}" | |||
| tl <- 2 | |||
| sl <- 1 | |||
| } else { | |||
| xshift <- 2 | |||
| rhs <- rhs %>% mutate(.x = .x + max(lhs$.x) + xshift) | |||
| # the header rows | |||
| header_start <- lhs %>% filter(.header == TRUE, !.key_map %in% key_values) | |||
| header_end <- rhs %>% filter(.header == TRUE) | |||
| state_start <- lhs %>% mutate(.frame = 0) | |||
| state_end <- rhs %>% mutate(.frame = length(key_values) + 2) | |||
| step_0 <- lhs %>% mutate(.frame = 1) | |||
| # for each unique key-value move the respective entries | |||
| keys_remaining <- lhs %>% filter(.key_map %in% key_values) | |||
| keys_shifted <- lhs[0, ] | |||
| key_steps <- lhs[0, ] | |||
| f <- 1 | |||
| ids_remaining <- lhs %>% filter(.type == "id" & .header == FALSE) | |||
| for (keyval in key_values) { | |||
| f <- f + 1 | |||
| move_rhs <- rhs %>% filter(.key_map == keyval) | |||
| keys_remaining <- keys_remaining %>% filter(.key_map != keyval) | |||
| if (keyval == key_values[length(key_values)]) { | |||
| header_start <- NULL | |||
| } | |||
| hd <- header_end %>% filter(.key_map == keyval | | |||
| (.type %in% c("key", "value") & | |||
| .col %in% c("key", "value"))) | |||
| keys_shifted <- bind_rows(keys_shifted, move_rhs) | |||
| round_n <- bind_rows(header_start, hd, | |||
| keys_remaining, keys_shifted) %>% | |||
| mutate(.frame = f) | |||
| key_steps <- bind_rows(key_steps, round_n) | |||
| } | |||
| anim_df <- bind_rows(state_start, step_0, key_steps, state_end) | |||
| # form the .frame as proper factors | |||
| frame_labels <- c( | |||
| sequence[["current_state"]], | |||
| paste(sequence[["operation"]], key_values), | |||
| sequence[["final_state"]], | |||
| sequence[["reverse_operation"]] | |||
| ) | |||
| title_string <- "{gsub('\\\\) [a-zA-Z]+$', ')', previous_state)}" | |||
| tl <- length(unique(anim_df$.frame)) * 2 | |||
| sl <- 1 | |||
| } | |||
| frame_levels <- anim_df$.frame %>% unique() | |||
| anim_df <- anim_df %>% | |||
| mutate(.frame = factor(.frame, | |||
| levels = frame_levels, | |||
| labels = frame_labels)) | |||
| if (export == "gif") { | |||
| animate_plot(anim_df, title = title_string, anim_opts = anim_opts) | |||
| } else if (export == "first") { | |||
| static_plot(state_start, anim_opts = anim_opts) #.... | |||
| } else if (export == "last") { | |||
| static_plot(state_end, anim_opts = anim_opts) #.... | |||
| } | |||
| # open issues: ... doesnt work properly. | |||
| # especially if the id-arguments are passed in the gather-style, i.e., -year, or year:var | |||
| } | |||
| @@ -1,97 +0,0 @@ | |||
| source(here::here("R", "00_base_tidyr.R")) | |||
| sg_wide <- wide %>% | |||
| proc_data("0-wide", colorize_wide_tidyr) %>% | |||
| mutate(frame = 1, .id = "0-wide") | |||
| sg_long <- wide %>% | |||
| tidyr::gather("key", "val", -id) %>% | |||
| proc_data("3-tall", color_fun = function(x, y) x) %>% | |||
| split(.$label) | |||
| sg_long$id <- | |||
| sg_wide %>% | |||
| filter(label == "id") %>% | |||
| select(value, color) %>% | |||
| left_join(sg_long$id, ., by = "value") %>% | |||
| mutate(alpha = 1) | |||
| sg_long$key <- | |||
| sg_wide %>% | |||
| filter(label != "id") %>% | |||
| select(label, color) %>% | |||
| left_join(sg_long$key, ., by = c("value" = "label")) %>% | |||
| distinct() %>% | |||
| mutate(alpha = 1) | |||
| sg_long$val <- | |||
| sg_wide %>% | |||
| filter(label != "id", .y < 0) %>% | |||
| select(value, color) %>% | |||
| left_join(sg_long$val, ., by = "value") %>% | |||
| mutate(alpha = 0.6) | |||
| sg_long <- bind_rows(sg_long) %>% mutate(frame = 2) | |||
| sg_long_labels <- data_frame(id = 1, a = "id", x = "key", y = "val") %>% | |||
| proc_data("4-label") %>% | |||
| filter(label != "id") %>% | |||
| mutate(color = "#FFFFFF", .y = 0, .x = .x -1, frame = 2, alpha = 1, label = recode(label, "a" = "id")) | |||
| sg_wide_labels <- data_frame(id = 1, a = "id") %>% | |||
| proc_data("2-label") %>% | |||
| filter(label != "id") %>% | |||
| mutate(color = "#FFFFFF", .y = 0, .x = .x -1, frame = 1, alpha = 1, label = recode(label, "a" = "id")) | |||
| sg_long_extra_keys <- map_dfr( | |||
| seq_len(nrow(wide) - 1), | |||
| ~ filter(sg_wide, .y > -1) # Extra key blocks in long column | |||
| ) | |||
| n_key_cols <- length(setdiff(colnames(wide), "id")) | |||
| sg_long_extra_id <- map_dfr( | |||
| seq_len(n_key_cols - 1), | |||
| ~ filter(sg_wide, .x == 1) # Extra id column blocks for long column | |||
| ) | |||
| sg_data <- bind_rows( | |||
| sg_wide, | |||
| sg_wide_labels, | |||
| sg_long, | |||
| sg_long_labels, | |||
| sg_long_extra_keys, | |||
| sg_long_extra_id | |||
| ) %>% | |||
| mutate( | |||
| label = ifelse(value %in% setdiff(colnames(wide), "id"), "key", label), | |||
| label = ifelse(value %in% c("key", "val"), "zzz", label), | |||
| .text_color = ifelse(grepl("label", .id), "black", "white"), | |||
| .text_size = ifelse(grepl("label", .id), 8, 12) | |||
| ) %>% | |||
| arrange(label, .id, value) %>% | |||
| mutate(frame = factor(frame, labels = c('spread(long, key, val)', 'gather(wide, key, val, x:z)'))) %>% | |||
| select(.x, .y, everything()) | |||
| sg_static <- | |||
| sg_data %>% | |||
| split(.$frame) %>% | |||
| imap(~ plot_data(.x, .y) + | |||
| ylim(-6.5, 0.5) + | |||
| labs(subtitle = "returns") + | |||
| theme(plot.subtitle = element_text(family = "Fira Sans", size = 14, color = "grey50", hjust = 0.5, margin = margin(25))) | |||
| ) | |||
| save_static_plot(sg_static[[1]], "tidyr-spread") | |||
| save_static_plot(sg_static[[2]], "tidyr-gather") | |||
| sg_anim <- | |||
| sg_data %>% | |||
| plot_data() %>% | |||
| animate_plot() + | |||
| view_follow() + | |||
| labs(title = "{ifelse(transitioning, next_state, ifelse(grepl('gather', next_state), 'long', 'wide'))}") + | |||
| ease_aes("sine-in-out", x = "exponential-out") | |||
| sg_anim <- animate(sg_anim) | |||
| anim_save(here::here("images", "tidyr-spread-gather.gif"), sg_anim) | |||
| @@ -1,42 +0,0 @@ | |||
| source(here::here("R/00_base_set.R")) | |||
| # ---- union(x, y) ---- | |||
| uxy <- bind_rows( | |||
| initial_set_dfs, | |||
| union(x, y) %>% proc_data_set("xy") %>% mutate(frame = 2, .x = .x + 1.5), | |||
| intersect(x, y) %>% proc_data_set("xy") %>% mutate(frame = 2, .y = -4, .x = .x + 1.5) | |||
| ) %>% | |||
| plot_data_set("union(x, y)", ylims = ylim(-4.5, -0.5)) %>% | |||
| animate_plot() | |||
| uxy <- animate(uxy) | |||
| anim_save(here::here("images", "union.gif"), uxy) | |||
| uxy_g <- union(x, y) %>% | |||
| proc_data_set() %>% | |||
| mutate(.x = .x + 1.5) %>% | |||
| plot_data_set("union(x, y)", ylims = ylim(-0.5, -4.5)) | |||
| save_static_plot(uxy_g, "union") | |||
| # ---- union(y, x) ---- | |||
| uyx <- bind_rows( | |||
| initial_set_dfs, | |||
| union(y, x) %>% proc_data_set("xy") %>% mutate(frame = 2, .x = .x + 1.5), | |||
| intersect(y, x) %>% proc_data_set("xy") %>% mutate(frame = 2, .y = -4, .x = .x + 1.5) | |||
| ) %>% | |||
| plot_data_set("union(y, x)", ylims = ylim(-4.5, -0.5)) %>% | |||
| animate_plot() | |||
| uyx <- animate(uyx) | |||
| anim_save(here::here("images", "union-rev.gif"), uyx) | |||
| uyx_g <- union(y, x) %>% | |||
| proc_data_set() %>% | |||
| mutate(.x = .x + 1.5) %>% | |||
| plot_data_set("union(y, x)", ylims = ylim(-4.5, -0.5)) | |||
| save_static_plot(uyx_g, "union-rev") | |||
| @@ -1,23 +0,0 @@ | |||
| source(here::here("R/00_base_set.R")) | |||
| ua <- bind_rows( | |||
| initial_set_dfs, | |||
| initial_set_dfs %>% mutate(frame = 2, .y = ifelse(.id == "y", .y - 3, .y)), # fly y down | |||
| proc_data_set(x, "ux") %>% mutate(frame = 3, .x = .x + 1.5), # merge | |||
| proc_data_set(y, "uy") %>% mutate(frame = 3, .x = .x + 1.5, .y = .y - 3), # un-merge | |||
| initial_set_dfs %>% mutate(frame = 4, .y = ifelse(.id == "y", .y - 3, .y)) # fly y up | |||
| ) %>% | |||
| arrange(desc(frame)) %>% | |||
| plot_data_set("union_all(x, y)", ylims = ylim(-5.5, -0.5)) + | |||
| transition_states(frame, 1, c(1, 0, 1, 0)) | |||
| ua <- animate(ua) | |||
| anim_save(here::here("images", "union-all.gif"), ua) | |||
| ua_g <- union_all(x, y) %>% | |||
| proc_data_set() %>% | |||
| mutate(.x = .x + 1.5) %>% | |||
| plot_data_set("union_all(x, y)", ylims = ylim(-5.5, -0.5)) | |||
| save_static_plot(ua_g, "union-all") | |||
| @@ -0,0 +1,11 @@ | |||
| #' Pipe operator | |||
| #' | |||
| #' See \code{magrittr::\link[magrittr]{\%>\%}} for details. | |||
| #' | |||
| #' @name %>% | |||
| #' @rdname pipe | |||
| #' @keywords internal | |||
| #' @export | |||
| #' @importFrom magrittr %>% | |||
| #' @usage lhs \%>\% rhs | |||
| NULL | |||
| @@ -0,0 +1,26 @@ | |||
| `%||%` <- function(x, y) if (is.null(x)) y else x | |||
| choose_text_color <- function(x, black = "#000000", white = "#FFFFFF") { | |||
| # x = color_hex | |||
| color_rgb <- col2rgb(x) | |||
| # modified from https://stackoverflow.com/a/3943023/2022615 | |||
| # following W3 guidelines: https://www.w3.org/TR/WCAG20/#relativeluminancedef | |||
| color_rgb <- color_rgb / 255 | |||
| color_rgb[color_rgb <= 0.03928] <- color_rgb[color_rgb <= 0.03928]/12.92 | |||
| color_rgb[color_rgb > 0.03928] <- ((color_rgb[color_rgb > 0.03928] + 0.055)/1.055)^2.4 | |||
| lum <- t(color_rgb) %*% c(0.2126, 0.7152, 0.0722) | |||
| lum <- lum[,1] | |||
| # threshold is supposed to be 0.179 but 1/3 seems to work better for our plots | |||
| ifelse(lum > 1/3, black, white) | |||
| } | |||
| get_input_text <- function(x) { | |||
| if (!rlang::is_quosure(x)) x <- rlang::enquo(x) | |||
| rlang::quo_name(x) | |||
| } | |||
| make_named_data <- function(x, y, data_names = c("x", "y")) { | |||
| ll <- rlang::eval_tidy(rlang::quo(list(!!x, !!y))) | |||
| names(ll) <- data_names | |||
| ll | |||
| } | |||
| @@ -0,0 +1,27 @@ | |||
| #' @importFrom dplyr left_join right_join full_join inner_join semi_join anti_join | |||
| #' @importFrom dplyr mutate select filter arrange bind_rows bind_cols group_by pull slice data_frame row_number | |||
| #' @importFrom tidyr gather spread | |||
| #' @keywords internal | |||
| "_PACKAGE" | |||
| plot_settings <- new.env(parent = emptyenv()) | |||
| plot_settings$default <- list( | |||
| transition_length = 2, | |||
| state_length = 1, | |||
| ease_default = "sine-in-out", | |||
| ease_other = NULL, | |||
| enter = setNames(list(enter_fade()), "enter_fade()"), | |||
| exit = setNames(list(exit_fade()), "exit_fade()"), | |||
| text_family = "Fira Mono", | |||
| title_family = "Fira Mono", | |||
| text_size = 5, | |||
| title_size = 17, | |||
| color_header = "#737373", | |||
| color_other = "#d0d0d0", | |||
| color_missing = "#ffffff", | |||
| color_fun = scales::brewer_pal(type = "qual", "Set1"), | |||
| text_color = NA, | |||
| cell_width = 1, | |||
| cell_height = 1 | |||
| ) | |||
| @@ -1,5 +1,7 @@ | |||
| --- | |||
| output: github_document | |||
| editor_options: | |||
| chunk_output_type: console | |||
| --- | |||
| <!-- README.md is generated from README.Rmd. Please edit that file --> | |||
| @@ -8,47 +10,45 @@ output: github_document | |||
| knitr::opts_chunk$set( | |||
| collapse = TRUE, | |||
| comment = "#>", | |||
| echo = FALSE, | |||
| echo = TRUE, | |||
| warning = FALSE, | |||
| message = FALSE, | |||
| fig.path = "man/figures/tidyexplain-", | |||
| cache = TRUE | |||
| ) | |||
| library(tidyexplain) | |||
| set_font_size(11, 26) | |||
| ``` | |||
| [gganimate]: https://github.com/thomasp85/gganimate#README | |||
| [dplyr-two-table]: https://dplyr.tidyverse.org/articles/two-table.html | |||
| [r4ds]: http://r4ds.had.co.nz/ | |||
| [r4ds-relational]: http://r4ds.had.co.nz/relational-data.html | |||
| [r4ds-set-ops]: http://r4ds.had.co.nz/relational-data.html#set-operations | |||
| [r4ds-tidy-data]: http://r4ds.had.co.nz/tidy-data.html#tidy-data-1 | |||
| [tidyverse]: https://tidyverse.org | |||
| [tidyr]: https://tidyr.tidyverse.org | |||
| [r4ds-set-ops]: http://r4ds.had.co.nz/relation-data.html#set-operations | |||
| # Tidy Animated Verbs | |||
| Garrick Aden-Buie -- [@grrrck](https://twitter.com/grrrck) -- [garrickadenbuie.com](https://www.garrickadenbuie.com). Set operations contributed by [Tyler Grant Smith](https://github.com/TylerGrantSmith). | |||
| Garrick Aden-Buie -- [@grrrck](https://twitter.com/grrrck) -- [garrickadenbuie.com](https://www.garrickadenbuie.com). | |||
| David Zimmermann -- [@dav_zim](https://twitter.com/dav_zim) -- [datashenanigan.wordpress.com](https://datashenanigan.wordpress.com/) | |||
| Set operations contributed by [Tyler Grant Smith](https://github.com/TylerGrantSmith). | |||
| [](https://mybinder.org/v2/gh/gadenbuie/tidy-animated-verbs/master?urlpath=rstudio) | |||
| [_-CC0-green.svg)](https://creativecommons.org/publicdomain/zero/1.0/) | |||
| [_-MIT-green.svg)](https://opensource.org/licenses/MIT) | |||
| - [**Mutating Joins**](#mutating-joins) — [`inner_join()`](#inner-join), [`left_join()`](#left-join), | |||
| [`right_join()`](#right-join), [`full_join()`](#full-join) | |||
| - [**Filtering Joins**](#filtering-joins) — [`semi_join()`](#semi-join), [`anti_join()`](#anti-join) | |||
| - Mutating Joins: [`inner_join()`](#inner-join), [`left_join()`](#left-join), | |||
| [`right_join()`](#right-join), [`full_join()`](#full-join) | |||
| - Filtering Joins: [`semi_join()`](#semi-join), [`anti_join()`](#anti-join) | |||
| - [**Set Operations**](#set-operations) — [`union()`](#union), [`union_all()`](#union-all), [`intersect()`](#intersect), [`setdiff()`](#setdiff) | |||
| - Set Operations: [`union()`](#union), [`union_all()`](#union-all), [`intersect()`](#intersect), [`setdiff()`](#setdiff) | |||
| - [**Tidy Data**](#tidy-data) — [`spread()` and `gather()`](#spread-and-gather) | |||
| - Tidyr Operations: [`gather()`](#gather), [`spread()`](#spread) | |||
| - Learn more about | |||
| - [Using the animations and images](#usage) | |||
| - [Relational Data](#relational-data) | |||
| - [gganimate](#gganimate) | |||
| ## Background | |||
| ### Usage | |||
| Please feel free to use these images for teaching or learning about action verbs from the [tidyverse](https://tidyverse.org). | |||
| You can directly download the [original animations](images/) or static images in [svg](images/static/svg/) or [png](images/static/png/) formats, or you can use the [scripts](R/) to recreate the images locally. | |||
| @@ -56,46 +56,35 @@ You can directly download the [original animations](images/) or static images in | |||
| Currently, the animations cover the [dplyr two-table verbs][dplyr-two-table] and I'd like to expand the animations to include more verbs from the tidyverse. | |||
| [Suggestions are welcome!](https://github.com/gadenbuie/tidy-animated-verbs/issues) | |||
| ### Relational Data | |||
| The [Relational Data][r4ds-relational] chapter of the | |||
| [R for Data Science][r4ds] book by Garrett Grolemund and Hadley Wickham | |||
| is an excellent resource for learning more about relational data. | |||
| ## Installing | |||
| The [dplyr two-table verbs vignette][dplyr-two-table] | |||
| and Jenny Bryan's [Cheatsheet for dplyr join functions](http://stat545.com/bit001_dplyr-cheatsheet.html) | |||
| are also great resources. | |||
| The in-development version of `tidyexplain` can be installed with `devtools`: | |||
| ### gganimate | |||
| ```r | |||
| # install.package("devtools") | |||
| devtools::install_github("gadenbuie/tidy-animated-verbs") | |||
| The animations were made possible by the newly re-written [gganimate] package by | |||
| [Thomas Lin Pedersen](https://github.com/thomasp85) | |||
| (original by [Dave Robinson](https://github.com/dgrtwo)). | |||
| The [package readme][gganimate] provides an excellent (and quick) introduction to gganimte. | |||
| library(tidyexplain) | |||
| ``` | |||
| ## Mutating Joins | |||
| > A mutating join allows you to combine variables from two tables. It first matches observations by their keys, then copies across variables from one table to the other. | |||
| > [R for Data Science: Mutating joins](http://r4ds.had.co.nz/relational-data.html#mutating-joins) | |||
| ```{r intial-dfs} | |||
| source("R/00_base_join.R") | |||
| df_names <- data_frame( | |||
| .x = c(1.5, 4.5), .y = 0.25, | |||
| value = c("x", "y"), | |||
| size = 12, | |||
| color = "black" | |||
| x <- dplyr::data_frame( | |||
| id = 1:3, | |||
| x = paste0("x", 1:3) | |||
| ) | |||
| g <- plot_data(initial_join_dfs) + | |||
| geom_text(data = df_names, family = "Fira Mono", size = 24) | |||
| y <- dplyr::data_frame( | |||
| id = (1:4)[-3], | |||
| y = paste0("y", (1:4)[-3]) | |||
| ) | |||
| save_static_plot(g, "original-dfs") | |||
| animate_full_join(x, y, by = c("id"), export = "first") | |||
| ``` | |||
| <img src="images/static/png/original-dfs.png" width="480px" /> | |||
| ```{r echo=TRUE} | |||
| ```{r} | |||
| x | |||
| y | |||
| ``` | |||
| @@ -105,13 +94,12 @@ y | |||
| > All rows from `x` where there are matching values in `y`, and all columns from `x` and `y`. | |||
| ```{r inner-join} | |||
| source("R/inner_join.R") | |||
| animate_inner_join(x, y, by = "id") | |||
| ``` | |||
|  | |||
| ```{r echo=TRUE} | |||
| inner_join(x, y, by = "id") | |||
| ```{r} | |||
| dplyr::inner_join(x, y, by = "id") | |||
| ``` | |||
| ### Left Join | |||
| @@ -119,13 +107,12 @@ inner_join(x, y, by = "id") | |||
| > All rows from `x`, and all columns from `x` and `y`. Rows in `x` with no match in `y` will have `NA` values in the new columns. | |||
| ```{r left-join} | |||
| source("R/left_join.R") | |||
| animate_left_join(x, y, by = "id") | |||
| ``` | |||
|  | |||
| ```{r echo=TRUE} | |||
| left_join(x, y, by = "id") | |||
| ```{r} | |||
| dplyr::left_join(x, y, by = "id") | |||
| ``` | |||
| ### Left Join (Extra Rows in y) | |||
| @@ -133,14 +120,14 @@ left_join(x, y, by = "id") | |||
| > ... If there are multiple matches between `x` and `y`, all combinations of the matches are returned. | |||
| ```{r left-join-extra} | |||
| source("R/left_join_extra.R") | |||
| ``` | |||
| y_extra <- dplyr::bind_rows(y, dplyr::data_frame(id = 2, y = "y5")) | |||
| y_extra # has multiple rows with the key from `x` | |||
|  | |||
| animate_left_join(x, y_extra, by = "id", title_size = 22) | |||
| ``` | |||
| ```{r echo=TRUE} | |||
| y_extra # has multiple rows with the key from `x` | |||
| left_join(x, y_extra, by = "id") | |||
| ```{r} | |||
| dplyr::left_join(x, y_extra, by = "id") | |||
| ``` | |||
| ### Right Join | |||
| @@ -148,13 +135,12 @@ left_join(x, y_extra, by = "id") | |||
| > All rows from y, and all columns from `x` and `y`. Rows in `y` with no match in `x` will have `NA` values in the new columns. | |||
| ```{r right-join} | |||
| source("R/right_join.R") | |||
| animate_right_join(x, y, by = "id") | |||
| ``` | |||
|  | |||
| ```{r echo=TRUE} | |||
| right_join(x, y, by = "id") | |||
| ```{r} | |||
| dplyr::right_join(x, y, by = "id") | |||
| ``` | |||
| ### Full Join | |||
| @@ -162,34 +148,27 @@ right_join(x, y, by = "id") | |||
| > All rows and all columns from both `x` and `y`. Where there are not matching values, returns `NA` for the one missing. | |||
| ```{r full-join} | |||
| source("R/full_join.R") | |||
| animate_full_join(x, y, by = "id") | |||
| ``` | |||
|  | |||
| ```{r echo=TRUE} | |||
| full_join(x, y, by = "id") | |||
| ```{r} | |||
| dplyr::full_join(x, y, by = "id") | |||
| ``` | |||
| ## Filtering Joins | |||
| > Filtering joins match observations in the same way as mutating joins, but affect the observations, not the variables. | |||
| > ... Semi-joins are useful for matching filtered summary tables back to the original rows. | |||
| > ... Anti-joins are useful for diagnosing join mismatches. | |||
| > [R for Data Science: Filtering Joins](http://r4ds.had.co.nz/relational-data.html#filtering-joins) | |||
| ### Semi Join | |||
| > All rows from `x` where there are matching values in `y`, keeping just columns from `x`. | |||
| ```{r semi-join} | |||
| source("R/semi_join.R") | |||
| animate_semi_join(x, y, by = "id") | |||
| ``` | |||
|  | |||
| ```{r echo=TRUE} | |||
| semi_join(x, y, by = "id") | |||
| ```{r} | |||
| dplyr::semi_join(x, y, by = "id") | |||
| ``` | |||
| ### Anti Join | |||
| @@ -197,45 +176,31 @@ semi_join(x, y, by = "id") | |||
| > All rows from `x` where there are not matching values in `y`, keeping just columns from `x`. | |||
| ```{r anti-join} | |||
| source("R/anti_join.R") | |||
| animate_anti_join(x, y, by = "id") | |||
| ``` | |||
|  | |||
| ```{r echo=TRUE} | |||
| anti_join(x, y, by = "id") | |||
| ```{r} | |||
| dplyr::anti_join(x, y, by = "id") | |||
| ``` | |||
| ## Set Operations | |||
| > Set operations are occasionally useful when you want to break a single complex filter into simpler pieces. | |||
| > All these operations work with a complete row, comparing the values of every variable. | |||
| > These expect the x and y inputs to have the same variables, and treat the observations like sets. | |||
| > [R for Data Science: Set operations](http://r4ds.had.co.nz/relational-data.html#set-operations) | |||
| ```{r intial-dfs-so} | |||
| source("R/00_base_set.R") | |||
| df_names <- data_frame( | |||
| .x = c(2.5, 5.5), .y = 0.25, | |||
| value = c("x", "y"), | |||
| size = 12, | |||
| color = "black" | |||
| x <- dplyr::data_frame( | |||
| x = c(1, 1, 2), | |||
| y = c("a", "b", "a") | |||
| ) | |||
| y <- dplyr::data_frame( | |||
| x = c(1, 2), | |||
| y = c("a", "b") | |||
| ) | |||
| g <- plot_data_set(initial_set_dfs, "", NULL, NULL) + | |||
| geom_text(data = df_names, family = "Fira Mono", size = 24) | |||
| save_static_plot(g, "original-dfs-set-ops") | |||
| ``` | |||
| ```{r remove-set-ops-ids} | |||
| x <- x %>% select(-id) | |||
| y <- y %>% select(-id) | |||
| animate_union(x, y, export = "first") | |||
| ``` | |||
| <img src="images/static/png/original-dfs-set-ops.png" width="480px" /> | |||
| ```{r echo=TRUE} | |||
| ```{r} | |||
| x | |||
| y | |||
| ``` | |||
| @@ -245,20 +210,20 @@ y | |||
| > All unique rows from `x` and `y`. | |||
| ```{r union} | |||
| source("R/union.R") | |||
| <<remove-set-ops-ids>> | |||
| animate_union(x, y) | |||
| ``` | |||
|  | |||
| ```{r echo=TRUE} | |||
| union(x, y) | |||
| ```{r} | |||
| dplyr::union(x, y) | |||
| ``` | |||
|  | |||
| ```{r echo=TRUE} | |||
| union(y, x) | |||
| ```{r union-y-x} | |||
| animate_union(y, x) | |||
| dplyr::union(y, x) | |||
| ``` | |||
| ### Union All | |||
| @@ -266,15 +231,13 @@ union(y, x) | |||
| > All rows from `x` and `y`, keeping duplicates. | |||
| ```{r union-all} | |||
| source("R/union_all.R") | |||
| <<remove-set-ops-ids>> | |||
| animate_union_all(x, y) | |||
| ``` | |||
|  | |||
| ```{r echo=TRUE} | |||
| union_all(x, y) | |||
| ```{r} | |||
| dplyr::union_all(x, y) | |||
| ``` | |||
| @@ -283,14 +246,12 @@ union_all(x, y) | |||
| > Common rows in both `x` and `y`, keeping just unique rows. | |||
| ```{r intersect} | |||
| source("R/intersect.R") | |||
| <<remove-set-ops-ids>> | |||
| animate_intersect(x, y) | |||
| ``` | |||
|  | |||
| ```{r echo=TRUE} | |||
| intersect(x, y) | |||
| ```{r} | |||
| dplyr::intersect(x, y) | |||
| ``` | |||
| ### Set Difference | |||
| @@ -298,72 +259,91 @@ intersect(x, y) | |||
| > All rows from `x` which are not also rows in `y`, keeping just unique rows. | |||
| ```{r setdiff} | |||
| source("R/setdiff.R") | |||
| <<remove-set-ops-ids>> | |||
| animate_setdiff(x, y) | |||
| ``` | |||
|  | |||
| ```{r echo=TRUE} | |||
| setdiff(x, y) | |||
| ```{r} | |||
| dplyr::setdiff(x, y) | |||
| ``` | |||
|  | |||
| ```{r echo=TRUE} | |||
| setdiff(y, x) | |||
| ```{r setdiff-y-x} | |||
| animate_setdiff(y, x) | |||
| dplyr::setdiff(y, x) | |||
| ``` | |||
| ## Tidy Data | |||
| ## Tidy Data and `gather()`, `spread()` functionality | |||
| [Tidy data][r4ds-tidy-data] follows the following three rules: | |||
| [Tidy data](http://r4ds.had.co.nz/tidy-data.html#tidy-data-1) follows | |||
| the following three rules: | |||
| 1. Each variable has its own column. | |||
| 1. Each observation has its own row. | |||
| 1. Each value has its own cell. | |||
| 1. Each variable has its own column. | |||
| 2. Each observation has its own row. | |||
| 3. Each value has its own cell. | |||
| Many of the tools in the [tidyverse] expect data to be formatted as a tidy dataset and the [tidyr] package provides functions to help you organize your data into tidy data. | |||
| Many of the tools in the [tidyverse](https://tidyverse.org) expect data | |||
| to be formatted as a tidy dataset and the | |||
| [tidyr](https://tidyr.tidyverse.org) package provides functions to help | |||
| you organize your data into tidy data. | |||
| ```{r tidyr-wide-long} | |||
| source("R/tidyr_spread_gather.R") | |||
| ```{r} | |||
| long <- dplyr::data_frame( | |||
| year = c(2010, 2011, 2010, 2011, 2010, 2011), | |||
| person = c("Alice", "Alice", "Bob", "Bob", "Charlie", "Charlie"), | |||
| sales = c(105, 110, 100, 97, 90, 95) | |||
| ) | |||
| wide <- dplyr::data_frame( | |||
| year = 2010:2011, | |||
| Alice = c(105, 110), | |||
| Bob = c(100, 97), | |||
| Charlie = c(90, 95) | |||
| ) | |||
| ``` | |||
| tidy_plots <- list() | |||
| tidy_plots$wide <- bind_rows(sg_wide, sg_wide_labels) | |||
| tidy_plots$long <- bind_rows(sg_long, sg_long_labels) | |||
| ### Gather | |||
| tidy_plots <- map(tidy_plots, ~ mutate(., | |||
| .text_color = ifelse(grepl("id|key|val", value), "black", "white"), | |||
| .text_size = ifelse(grepl("id|key|val", value), 6, 10) | |||
| )) %>% | |||
| imap(~ plot_data(.x, .y)) | |||
| > Gather takes multiple columns and collapses into key-value pairs, duplicating all other columns as needed. You use gather() when you notice that your column names are not names of variables, but values of a variable. | |||
| tidy_plots$wide <- tidy_plots$wide + ylim(-6.5, 0.5) | |||
| ```{r gather} | |||
| set_font_size(4, 15) | |||
| set_anim_options(anim_options(cell_width = 2)) | |||
| animate_gather(wide, key = "person", value = "sales", -year) | |||
| ``` | |||
| save_static_plot(cowplot::plot_grid(plotlist = tidy_plots, axis = "t"), "original-dfs-tidy") | |||
| ```{r} | |||
| tidyr::gather(wide, key = "person", value = "sales", -year) | |||
| ``` | |||
|  | |||
| ### Spread | |||
| > Spread a key-value pair across multiple columns. Use it when an a column contains observations from multiple variables. | |||
| ```{r spread} | |||
| animate_spread(long, key = "person", value = "sales") | |||
| ``` | |||
| ```{r echo=TRUE} | |||
| wide | |||
| long | |||
| ```{r} | |||
| tidyr::spread(long, key = "person", value = "sales") | |||
| ``` | |||
| ### Spread and Gather | |||
| `spread(data, key, value)` | |||
| ## Learn More | |||
| > Spread a key-value pair across multiple columns. | |||
| > Use it when an a column contains observations from multiple variables. | |||
| ### Relational Data | |||
| `gather(data, key = "key", value = "value", ...)` | |||
| The [Relational Data](http://r4ds.had.co.nz/relation-data.html) chapter of the | |||
| [R for Data Science](http://r4ds.had.co.nz/) book by Garrett Grolemund and Hadley Wickham | |||
| is an excellent resource for learning more about relational data. | |||
| > Gather takes multiple columns and collapses into key-value pairs, duplicating all other columns as needed. | |||
| > You use `gather()` when you notice that your column names are not names of variables, but *values* of a variable. | |||
| The [dplyr two-table verbs vignette][dplyr-two-table] | |||
| and Jenny Bryan's [Cheatsheet for dplyr join functions](http://stat545.com/bit001_dplyr-cheatsheet.html) | |||
| are also great resources. | |||
|  | |||
| ### gganimate | |||
| ```{r echo=TRUE} | |||
| gather(wide, key, val, x:z) | |||
| spread(long, key, val) | |||
| ``` | |||
| The animations were made possible by the newly re-written [gganimate] package by | |||
| [Thomas Lin Pedersen](https://github.com/thomasp85) | |||
| (original by [Dave Robinson](https://github.com/dgrtwo)). | |||
| The [package readme][gganimate] provides an excellent (and quick) introduction to gganimte. | |||
| @@ -4,38 +4,35 @@ | |||
| # Tidy Animated Verbs | |||
| Garrick Aden-Buie – [@grrrck](https://twitter.com/grrrck) – | |||
| [garrickadenbuie.com](https://www.garrickadenbuie.com). Set operations | |||
| contributed by [Tyler Grant | |||
| [garrickadenbuie.com](https://www.garrickadenbuie.com). | |||
| David Zimmermann – [@dav\_zim](https://twitter.com/dav_zim) – | |||
| [datashenanigan.wordpress.com](https://datashenanigan.wordpress.com/) | |||
| Set operations contributed by [Tyler Grant | |||
| Smith](https://github.com/TylerGrantSmith). | |||
| [](https://mybinder.org/v2/gh/gadenbuie/tidy-animated-verbs/master?urlpath=rstudio) | |||
| [_-CC0-green.svg)](https://creativecommons.org/publicdomain/zero/1.0/) | |||
| [_-MIT-green.svg)](https://opensource.org/licenses/MIT) | |||
| - [**Mutating Joins**](#mutating-joins) — | |||
| [`inner_join()`](#inner-join), [`left_join()`](#left-join), | |||
| [`right_join()`](#right-join), [`full_join()`](#full-join) | |||
| - Mutating Joins: [`inner_join()`](#inner-join), | |||
| [`left_join()`](#left-join), [`right_join()`](#right-join), | |||
| [`full_join()`](#full-join) | |||
| - [**Filtering Joins**](#filtering-joins) — | |||
| [`semi_join()`](#semi-join), [`anti_join()`](#anti-join) | |||
| - Filtering Joins: [`semi_join()`](#semi-join), | |||
| [`anti_join()`](#anti-join) | |||
| - [**Set Operations**](#set-operations) — [`union()`](#union), | |||
| [`union_all()`](#union-all), [`intersect()`](#intersect), | |||
| [`setdiff()`](#setdiff) | |||
| - Set Operations: [`union()`](#union), [`union_all()`](#union-all), | |||
| [`intersect()`](#intersect), [`setdiff()`](#setdiff) | |||
| - [**Tidy Data**](#tidy-data) — [`spread()` and | |||
| `gather()`](#spread-and-gather) | |||
| - Tidyr Operations: [`gather()`](#gather), [`spread()`](#spread) | |||
| - Learn more about | |||
| - [Using the animations and images](#usage) | |||
| - [Relational Data](#relational-data) | |||
| - [gganimate](#gganimate) | |||
| ## Background | |||
| ### Usage | |||
| Please feel free to use these images for teaching or learning about | |||
| action verbs from the [tidyverse](https://tidyverse.org). You can | |||
| directly download the [original animations](images/) or static images in | |||
| @@ -48,37 +45,35 @@ to expand the animations to include more verbs from the tidyverse. | |||
| [Suggestions are | |||
| welcome\!](https://github.com/gadenbuie/tidy-animated-verbs/issues) | |||
| ### Relational Data | |||
| ## Installing | |||
| The [Relational Data](http://r4ds.had.co.nz/relational-data.html) | |||
| chapter of the [R for Data Science](http://r4ds.had.co.nz/) book by | |||
| Garrett Grolemund and Hadley Wickham is an excellent resource for | |||
| learning more about relational data. | |||
| The in-development version of `tidyexplain` can be installed with | |||
| `devtools`: | |||
| The [dplyr two-table verbs | |||
| vignette](https://dplyr.tidyverse.org/articles/two-table.html) and Jenny | |||
| Bryan’s [Cheatsheet for dplyr join | |||
| functions](http://stat545.com/bit001_dplyr-cheatsheet.html) are also | |||
| great resources. | |||
| ### gganimate | |||
| ``` r | |||
| # install.package("devtools") | |||
| devtools::install_github("gadenbuie/tidy-animated-verbs") | |||
| The animations were made possible by the newly re-written | |||
| [gganimate](https://github.com/thomasp85/gganimate#README) package by | |||
| [Thomas Lin Pedersen](https://github.com/thomasp85) (original by [Dave | |||
| Robinson](https://github.com/dgrtwo)). The [package | |||
| readme](https://github.com/thomasp85/gganimate#README) provides an | |||
| excellent (and quick) introduction to gganimte. | |||
| library(tidyexplain) | |||
| ``` | |||
| ## Mutating Joins | |||
| > A mutating join allows you to combine variables from two tables. It | |||
| > first matches observations by their keys, then copies across variables | |||
| > from one table to the other. | |||
| > [R for Data Science: Mutating | |||
| > joins](http://r4ds.had.co.nz/relational-data.html#mutating-joins) | |||
| ``` r | |||
| x <- dplyr::data_frame( | |||
| id = 1:3, | |||
| x = paste0("x", 1:3) | |||
| ) | |||
| y <- dplyr::data_frame( | |||
| id = (1:4)[-3], | |||
| y = paste0("y", (1:4)[-3]) | |||
| ) | |||
| <img src="images/static/png/original-dfs.png" width="480px" /> | |||
| animate_full_join(x, y, by = c("id"), export = "first") | |||
| ``` | |||
| <!-- --> | |||
| ``` r | |||
| x | |||
| @@ -102,10 +97,14 @@ y | |||
| > All rows from `x` where there are matching values in `y`, and all | |||
| > columns from `x` and `y`. | |||
|  | |||
| ``` r | |||
| animate_inner_join(x, y, by = "id") | |||
| ``` | |||
| <!-- --> | |||
| ``` r | |||
| inner_join(x, y, by = "id") | |||
| dplyr::inner_join(x, y, by = "id") | |||
| #> # A tibble: 2 x 3 | |||
| #> id x y | |||
| #> <int> <chr> <chr> | |||
| @@ -118,10 +117,14 @@ inner_join(x, y, by = "id") | |||
| > All rows from `x`, and all columns from `x` and `y`. Rows in `x` with | |||
| > no match in `y` will have `NA` values in the new columns. | |||
|  | |||
| ``` r | |||
| animate_left_join(x, y, by = "id") | |||
| ``` | |||
| <!-- --> | |||
| ``` r | |||
| left_join(x, y, by = "id") | |||
| dplyr::left_join(x, y, by = "id") | |||
| #> # A tibble: 3 x 3 | |||
| #> id x y | |||
| #> <int> <chr> <chr> | |||
| @@ -135,9 +138,8 @@ left_join(x, y, by = "id") | |||
| > … If there are multiple matches between `x` and `y`, all combinations | |||
| > of the matches are returned. | |||
|  | |||
| ``` r | |||
| y_extra <- dplyr::bind_rows(y, dplyr::data_frame(id = 2, y = "y5")) | |||
| y_extra # has multiple rows with the key from `x` | |||
| #> # A tibble: 4 x 2 | |||
| #> id y | |||
| @@ -146,7 +148,14 @@ y_extra # has multiple rows with the key from `x` | |||
| #> 2 2 y2 | |||
| #> 3 4 y4 | |||
| #> 4 2 y5 | |||
| left_join(x, y_extra, by = "id") | |||
| animate_left_join(x, y_extra, by = "id", title_size = 22) | |||
| ``` | |||
| <!-- --> | |||
| ``` r | |||
| dplyr::left_join(x, y_extra, by = "id") | |||
| #> # A tibble: 4 x 3 | |||
| #> id x y | |||
| #> <dbl> <chr> <chr> | |||
| @@ -161,10 +170,14 @@ left_join(x, y_extra, by = "id") | |||
| > All rows from y, and all columns from `x` and `y`. Rows in `y` with no | |||
| > match in `x` will have `NA` values in the new columns. | |||
|  | |||
| ``` r | |||
| animate_right_join(x, y, by = "id") | |||
| ``` | |||
| <!-- --> | |||
| ``` r | |||
| right_join(x, y, by = "id") | |||
| dplyr::right_join(x, y, by = "id") | |||
| #> # A tibble: 3 x 3 | |||
| #> id x y | |||
| #> <int> <chr> <chr> | |||
| @@ -178,10 +191,14 @@ right_join(x, y, by = "id") | |||
| > All rows and all columns from both `x` and `y`. Where there are not | |||
| > matching values, returns `NA` for the one missing. | |||
|  | |||
| ``` r | |||
| animate_full_join(x, y, by = "id") | |||
| ``` | |||
| <!-- --> | |||
| ``` r | |||
| full_join(x, y, by = "id") | |||
| dplyr::full_join(x, y, by = "id") | |||
| #> # A tibble: 4 x 3 | |||
| #> id x y | |||
| #> <int> <chr> <chr> | |||
| @@ -193,22 +210,19 @@ full_join(x, y, by = "id") | |||
| ## Filtering Joins | |||
| > Filtering joins match observations in the same way as mutating joins, | |||
| > but affect the observations, not the variables. … Semi-joins are | |||
| > useful for matching filtered summary tables back to the original rows. | |||
| > … Anti-joins are useful for diagnosing join mismatches. | |||
| > [R for Data Science: Filtering | |||
| > Joins](http://r4ds.had.co.nz/relational-data.html#filtering-joins) | |||
| ### Semi Join | |||
| > All rows from `x` where there are matching values in `y`, keeping just | |||
| > columns from `x`. | |||
|  | |||
| ``` r | |||
| animate_semi_join(x, y, by = "id") | |||
| ``` | |||
| <!-- --> | |||
| ``` r | |||
| semi_join(x, y, by = "id") | |||
| dplyr::semi_join(x, y, by = "id") | |||
| #> # A tibble: 2 x 2 | |||
| #> id x | |||
| #> <int> <chr> | |||
| @@ -221,10 +235,14 @@ semi_join(x, y, by = "id") | |||
| > All rows from `x` where there are not matching values in `y`, keeping | |||
| > just columns from `x`. | |||
|  | |||
| ``` r | |||
| animate_anti_join(x, y, by = "id") | |||
| ``` | |||
| <!-- --> | |||
| ``` r | |||
| anti_join(x, y, by = "id") | |||
| dplyr::anti_join(x, y, by = "id") | |||
| #> # A tibble: 1 x 2 | |||
| #> id x | |||
| #> <int> <chr> | |||
| @@ -233,92 +251,114 @@ anti_join(x, y, by = "id") | |||
| ## Set Operations | |||
| > Set operations are occasionally useful when you want to break a single | |||
| > complex filter into simpler pieces. All these operations work with a | |||
| > complete row, comparing the values of every variable. These expect the | |||
| > x and y inputs to have the same variables, and treat the observations | |||
| > like sets. | |||
| > [R for Data Science: Set | |||
| > operations](http://r4ds.had.co.nz/relational-data.html#set-operations) | |||
| ``` r | |||
| x <- dplyr::data_frame( | |||
| x = c(1, 1, 2), | |||
| y = c("a", "b", "a") | |||
| ) | |||
| y <- dplyr::data_frame( | |||
| x = c(1, 2), | |||
| y = c("a", "b") | |||
| ) | |||
| animate_union(x, y, export = "first") | |||
| ``` | |||
| <img src="images/static/png/original-dfs-set-ops.png" width="480px" /> | |||
| <!-- --> | |||
| ``` r | |||
| x | |||
| #> # A tibble: 3 x 2 | |||
| #> x y | |||
| #> <chr> <chr> | |||
| #> 1 1 a | |||
| #> 2 1 b | |||
| #> 3 2 a | |||
| #> x y | |||
| #> <dbl> <chr> | |||
| #> 1 1 a | |||
| #> 2 1 b | |||
| #> 3 2 a | |||
| y | |||
| #> # A tibble: 2 x 2 | |||
| #> x y | |||
| #> <chr> <chr> | |||
| #> 1 1 a | |||
| #> 2 2 b | |||
| #> x y | |||
| #> <dbl> <chr> | |||
| #> 1 1 a | |||
| #> 2 2 b | |||
| ``` | |||
| ### Union | |||
| > All unique rows from `x` and `y`. | |||
|  | |||
| ``` r | |||
| animate_union(x, y) | |||
| ``` | |||
| <!-- --> | |||
| ``` r | |||
| union(x, y) | |||
| dplyr::union(x, y) | |||
| #> # A tibble: 4 x 2 | |||
| #> x y | |||
| #> <chr> <chr> | |||
| #> 1 2 b | |||
| #> 2 2 a | |||
| #> 3 1 b | |||
| #> 4 1 a | |||
| #> x y | |||
| #> <dbl> <chr> | |||
| #> 1 2 b | |||
| #> 2 2 a | |||
| #> 3 1 b | |||
| #> 4 1 a | |||
| ``` | |||
| ``` r | |||
| animate_union(y, x) | |||
| ``` | |||
|  | |||
| <!-- --> | |||
| ``` r | |||
| union(y, x) | |||
| dplyr::union(y, x) | |||
| #> # A tibble: 4 x 2 | |||
| #> x y | |||
| #> <chr> <chr> | |||
| #> 1 2 a | |||
| #> 2 1 b | |||
| #> 3 2 b | |||
| #> 4 1 a | |||
| #> x y | |||
| #> <dbl> <chr> | |||
| #> 1 2 a | |||
| #> 2 1 b | |||
| #> 3 2 b | |||
| #> 4 1 a | |||
| ``` | |||
| ### Union All | |||
| > All rows from `x` and `y`, keeping duplicates. | |||
|  | |||
| ``` r | |||
| animate_union_all(x, y) | |||
| ``` | |||
| <!-- --> | |||
| ``` r | |||
| union_all(x, y) | |||
| dplyr::union_all(x, y) | |||
| #> # A tibble: 5 x 2 | |||
| #> x y | |||
| #> <chr> <chr> | |||
| #> 1 1 a | |||
| #> 2 1 b | |||
| #> 3 2 a | |||
| #> 4 1 a | |||
| #> 5 2 b | |||
| #> x y | |||
| #> <dbl> <chr> | |||
| #> 1 1 a | |||
| #> 2 1 b | |||
| #> 3 2 a | |||
| #> 4 1 a | |||
| #> 5 2 b | |||
| ``` | |||
| ### Intersection | |||
| > Common rows in both `x` and `y`, keeping just unique rows. | |||
|  | |||
| ``` r | |||
| animate_intersect(x, y) | |||
| ``` | |||
| <!-- --> | |||
| ``` r | |||
| intersect(x, y) | |||
| dplyr::intersect(x, y) | |||
| #> # A tibble: 1 x 2 | |||
| #> x y | |||
| #> <chr> <chr> | |||
| #> 1 1 a | |||
| #> x y | |||
| #> <dbl> <chr> | |||
| #> 1 1 a | |||
| ``` | |||
| ### Set Difference | |||
| @@ -326,28 +366,37 @@ intersect(x, y) | |||
| > All rows from `x` which are not also rows in `y`, keeping just unique | |||
| > rows. | |||
|  | |||
| ``` r | |||
| animate_setdiff(x, y) | |||
| ``` | |||
| <!-- --> | |||
| ``` r | |||
| setdiff(x, y) | |||
| dplyr::setdiff(x, y) | |||
| #> # A tibble: 2 x 2 | |||
| #> x y | |||
| #> <chr> <chr> | |||
| #> 1 1 b | |||
| #> 2 2 a | |||
| #> x y | |||
| #> <dbl> <chr> | |||
| #> 1 1 b | |||
| #> 2 2 a | |||
| ``` | |||
|  | |||
| ``` r | |||
| animate_setdiff(y, x) | |||
| ``` | |||
| <!-- --> | |||
| ``` r | |||
| setdiff(y, x) | |||
| dplyr::setdiff(y, x) | |||
| #> # A tibble: 1 x 2 | |||
| #> x y | |||
| #> <chr> <chr> | |||
| #> 1 2 b | |||
| #> x y | |||
| #> <dbl> <chr> | |||
| #> 1 2 b | |||
| ``` | |||
| ## Tidy Data | |||
| ## Tidy Data and `gather()`, `spread()` functionality | |||
| [Tidy data](http://r4ds.had.co.nz/tidy-data.html#tidy-data-1) follows | |||
| the following three rules: | |||
| @@ -361,58 +410,88 @@ to be formatted as a tidy dataset and the | |||
| [tidyr](https://tidyr.tidyverse.org) package provides functions to help | |||
| you organize your data into tidy data. | |||
|  | |||
| ``` r | |||
| long <- dplyr::data_frame( | |||
| year = c(2010, 2011, 2010, 2011, 2010, 2011), | |||
| person = c("Alice", "Alice", "Bob", "Bob", "Charlie", "Charlie"), | |||
| sales = c(105, 110, 100, 97, 90, 95) | |||
| ) | |||
| wide <- dplyr::data_frame( | |||
| year = 2010:2011, | |||
| Alice = c(105, 110), | |||
| Bob = c(100, 97), | |||
| Charlie = c(90, 95) | |||
| ) | |||
| ``` | |||
| ### Gather | |||
| > Gather takes multiple columns and collapses into key-value pairs, | |||
| > duplicating all other columns as needed. You use gather() when you | |||
| > notice that your column names are not names of variables, but values | |||
| > of a variable. | |||
| ``` r | |||
| wide | |||
| #> # A tibble: 2 x 4 | |||
| #> id x y z | |||
| #> <int> <chr> <chr> <chr> | |||
| #> 1 1 a c e | |||
| #> 2 2 b d f | |||
| long | |||
| #> # A tibble: 6 x 3 | |||
| #> id key val | |||
| #> <int> <chr> <chr> | |||
| #> 1 1 x a | |||
| #> 2 2 x b | |||
| #> 3 1 y c | |||
| #> 4 2 y d | |||
| #> 5 1 z e | |||
| #> 6 2 z f | |||
| set_font_size(4, 15) | |||
| set_anim_options(anim_options(cell_width = 2)) | |||
| animate_gather(wide, key = "person", value = "sales", -year) | |||
| ``` | |||
| ### Spread and Gather | |||
| <!-- --> | |||
| `spread(data, key, value)` | |||
| ``` r | |||
| tidyr::gather(wide, key = "person", value = "sales", -year) | |||
| #> # A tibble: 6 x 3 | |||
| #> year person sales | |||
| #> <int> <chr> <dbl> | |||
| #> 1 2010 Alice 105 | |||
| #> 2 2011 Alice 110 | |||
| #> 3 2010 Bob 100 | |||
| #> 4 2011 Bob 97 | |||
| #> 5 2010 Charlie 90 | |||
| #> 6 2011 Charlie 95 | |||
| ``` | |||
| ### Spread | |||
| > Spread a key-value pair across multiple columns. Use it when an a | |||
| > column contains observations from multiple variables. | |||
| `gather(data, key = "key", value = "value", ...)` | |||
| > Gather takes multiple columns and collapses into key-value pairs, | |||
| > duplicating all other columns as needed. You use `gather()` when you | |||
| > notice that your column names are not names of variables, but *values* | |||
| > of a variable. | |||
| ``` r | |||
| animate_spread(long, key = "person", value = "sales") | |||
| ``` | |||
|  | |||
| <!-- --> | |||
| ``` r | |||
| gather(wide, key, val, x:z) | |||
| #> # A tibble: 6 x 3 | |||
| #> id key val | |||
| #> <int> <chr> <chr> | |||
| #> 1 1 x a | |||
| #> 2 2 x b | |||
| #> 3 1 y c | |||
| #> 4 2 y d | |||
| #> 5 1 z e | |||
| #> 6 2 z f | |||
| spread(long, key, val) | |||
| tidyr::spread(long, key = "person", value = "sales") | |||
| #> # A tibble: 2 x 4 | |||
| #> id x y z | |||
| #> <int> <chr> <chr> <chr> | |||
| #> 1 1 a c e | |||
| #> 2 2 b d f | |||
| #> year Alice Bob Charlie | |||
| #> <dbl> <dbl> <dbl> <dbl> | |||
| #> 1 2010 105 100 90 | |||
| #> 2 2011 110 97 95 | |||
| ``` | |||
| ## Learn More | |||
| ### Relational Data | |||
| The [Relational Data](http://r4ds.had.co.nz/relation-data.html) chapter | |||
| of the [R for Data Science](http://r4ds.had.co.nz/) book by Garrett | |||
| Grolemund and Hadley Wickham is an excellent resource for learning more | |||
| about relational data. | |||
| The [dplyr two-table verbs | |||
| vignette](https://dplyr.tidyverse.org/articles/two-table.html) and Jenny | |||
| Bryan’s [Cheatsheet for dplyr join | |||
| functions](http://stat545.com/bit001_dplyr-cheatsheet.html) are also | |||
| great resources. | |||
| ### gganimate | |||
| The animations were made possible by the newly re-written | |||
| [gganimate](https://github.com/thomasp85/gganimate#README) package by | |||
| [Thomas Lin Pedersen](https://github.com/thomasp85) (original by [Dave | |||
| Robinson](https://github.com/dgrtwo)). The [package | |||
| readme](https://github.com/thomasp85/gganimate#README) provides an | |||
| excellent (and quick) introduction to gganimte. | |||
| @@ -0,0 +1,160 @@ | |||
| library(tidyexplain) | |||
| library(here) | |||
| library(stringr) | |||
| set_font_size(title_size = 20) | |||
| check_and_create <- function(ff) { | |||
| if (!dir.exists(ff)) dir.create(ff, recursive = T) | |||
| } | |||
| check_and_create(here("images", "static", "png")) | |||
| check_and_create(here("images", "static", "svg")) | |||
| check_and_create(here("images", "gif")) | |||
| ### Animate Joins | |||
| x <- dplyr::data_frame( | |||
| id = 1:3, | |||
| x = paste0("x", 1:3) | |||
| ) | |||
| y <- dplyr::data_frame( | |||
| id = (1:4)[-3], | |||
| y = paste0("y", (1:4)[-3]) | |||
| ) | |||
| joins <- c( | |||
| full_join = animate_full_join, | |||
| inner_join = animate_inner_join, | |||
| left_join = animate_left_join, | |||
| right_join = animate_right_join, | |||
| semi_join = animate_semi_join | |||
| ) | |||
| a <- sapply(1:length(joins), function(i) { | |||
| nam <- names(joins)[i] | |||
| nam <- str_replace(nam, "_", "-") | |||
| cat(nam, "\n") | |||
| width <- 7 | |||
| height <- 7 | |||
| gif_ <- joins[[i]](x, y, by = "id") | |||
| first_ <- joins[[i]](x, y, by = "id", export = "first") | |||
| last_ <- joins[[i]](x, y, by = "id", export = "last") | |||
| save_animation(animate(gif_), here("images", "gif", paste0(nam, ".gif"))) | |||
| ggsave(here("images", "static", "png", paste0(nam, "-first.png")), first_, | |||
| height = height, width = width) | |||
| ggsave(here("images", "static", "svg", paste0(nam, "-first.svg")), first_, | |||
| height = height, width = width) | |||
| ggsave(here("images", "static", "png", paste0(nam, "-last.png")), last_, | |||
| height = height, width = width) | |||
| ggsave(here("images", "static", "svg", paste0(nam, "-last.svg")), last_, | |||
| height = height, width = width) | |||
| }) | |||
| ### Animate Sets | |||
| x <- tibble::tribble( | |||
| ~x, ~y, | |||
| "1", "a", | |||
| "1", "b", | |||
| "2", "a" | |||
| ) | |||
| y <- tibble::tribble( | |||
| ~x, ~y, | |||
| "1", "a", | |||
| "2", "b" | |||
| ) | |||
| sets <- c( | |||
| union = animate_union, | |||
| union_all = animate_union_all, | |||
| intersect = animate_intersect, | |||
| setdiff = animate_setdiff | |||
| ) | |||
| a <- sapply(1:length(sets), function(i) { | |||
| nam <- names(sets)[i] | |||
| nam <- str_replace(nam, "_", "-") | |||
| cat(nam, "\n") | |||
| width <- 7 | |||
| height <- 7 | |||
| gif_ <- sets[[i]](x, y) | |||
| first_ <- sets[[i]](x, y, export = "first") | |||
| last_ <- sets[[i]](x, y, export = "last") | |||
| save_animation(animate(gif_), here("images", "gif", paste0(nam, ".gif"))) | |||
| ggsave(here("images", "static", "png", paste0(nam, "-first.png")), first_, | |||
| height = height, width = width) | |||
| ggsave(here("images", "static", "svg", paste0(nam, "-first.svg")), first_, | |||
| height = height, width = width) | |||
| ggsave(here("images", "static", "png", paste0(nam, "-last.png")), last_, | |||
| height = height, width = width) | |||
| ggsave(here("images", "static", "svg", paste0(nam, "-last.svg")), last_, | |||
| height = height, width = width) | |||
| }) | |||
| ### Animate Gather Spread | |||
| set_font_size(text_size = 4) | |||
| set_anim_options(anim_options(cell_width = 2)) | |||
| # Gather | |||
| wide <- dplyr::data_frame( | |||
| year = 2010:2011, | |||
| Alice = c(105, 110), | |||
| Bob = c(100, 97), | |||
| Charlie = c(90, 95) | |||
| ) | |||
| nam <- "gather" | |||
| cat(nam, "\n") | |||
| width <- 7 | |||
| height <- 7 | |||
| gif_ <- animate_gather(wide, key = "person", value = "sales", -year, cell_width = 2) | |||
| first_ <- animate_gather(wide, key = "person", value = "sales", -year, export = "first") | |||
| last_ <- animate_gather(wide, key = "person", value = "sales", -year, export = "last") | |||
| save_animation(animate(gif_), here("images", "gif", paste0(nam, ".gif"))) | |||
| ggsave(here("images", "static", "png", paste0(nam, "-first.png")), first_, | |||
| height = height, width = width) | |||
| ggsave(here("images", "static", "svg", paste0(nam, "-first.svg")), first_, | |||
| height = height, width = width) | |||
| ggsave(here("images", "static", "png", paste0(nam, "-last.png")), last_, | |||
| height = height, width = width) | |||
| ggsave(here("images", "static", "svg", paste0(nam, "-last.svg")), last_, | |||
| height = height, width = width) | |||
| # Spread | |||
| long <- dplyr::data_frame( | |||
| year = c(2010, 2011, 2010, 2011, 2010, 2011), | |||
| person = c("Alice", "Alice", "Bob", "Bob", "Charlie", "Charlie"), | |||
| sales = c(105, 110, 100, 97, 90, 95) | |||
| ) | |||
| nam <- "spread" | |||
| cat(nam, "\n") | |||
| width <- 7 | |||
| height <- 7 | |||
| gif_ <- animate_spread(long, key = "person", value = "sales") | |||
| first_ <- animate_spread(long, key = "person", value = "sales", export = "first") | |||
| last_ <- animate_spread(long, key = "person", value = "sales", export = "last") | |||
| save_animation(animate(gif_), here("images", "gif", paste0(nam, ".gif"))) | |||
| ggsave(here("images", "static", "png", paste0(nam, "-first.png")), first_, | |||
| height = height, width = width) | |||
| ggsave(here("images", "static", "svg", paste0(nam, "-first.svg")), first_, | |||
| height = height, width = width) | |||
| ggsave(here("images", "static", "png", paste0(nam, "-last.png")), last_, | |||
| height = height, width = width) | |||
| ggsave(here("images", "static", "svg", paste0(nam, "-last.svg")), last_, | |||
| height = height, width = width) | |||
| @@ -1,44 +0,0 @@ | |||
| <?xml version='1.0' encoding='UTF-8' ?> | |||
| <svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' viewBox='0 0 504.00 360.00'> | |||
| <defs> | |||
| <style type='text/css'><![CDATA[ | |||
| line, polyline, path, rect, circle { | |||
| fill: none; | |||
| stroke: #000000; | |||
| stroke-linecap: round; | |||
| stroke-linejoin: round; | |||
| stroke-miterlimit: 10.00; | |||
| } | |||
| ]]></style> | |||
| </defs> | |||
| <rect width='100%' height='100%' style='stroke: none; fill: #FFFFFF;'/> | |||
| <defs> | |||
| <clipPath id='cpMHw1MDR8MzQzLjY0OXw0MS4yNDkx'> | |||
| <rect x='0.00' y='41.25' width='504.00' height='302.40' /> | |||
| </clipPath> | |||
| </defs> | |||
| <rect x='164.95' y='59.58' width='82.47' height='82.47' style='stroke-width: 0.21; stroke: none; stroke-linecap: butt; fill: #4DAF4A;' clip-path='url(#cpMHw1MDR8MzQzLjY0OXw0MS4yNDkx)' /> | |||
| <rect x='256.58' y='59.58' width='82.47' height='82.47' style='stroke-width: 0.21; stroke: none; stroke-linecap: butt; fill: #D0D0D0;' clip-path='url(#cpMHw1MDR8MzQzLjY0OXw0MS4yNDkx)' /> | |||
| <path d='M 205.35 89.39 L 205.35 89.39 L 205.76 89.40 L 206.17 89.42 L 206.56 89.46 L 206.94 89.52 L 207.31 89.59 L 207.66 89.67 L 208.01 89.78 L 208.34 89.89 L 208.66 90.03 L 208.96 90.18 L 208.96 90.18 L 209.26 90.34 L 209.55 90.51 L 209.82 90.70 L 210.07 90.89 L 210.31 91.10 L 210.54 91.32 L 210.75 91.54 L 210.94 91.78 L 211.12 92.03 L 211.29 92.29 L 211.29 92.29 L 211.44 92.56 L 211.58 92.84 L 211.70 93.11 L 211.81 93.40 L 211.90 93.69 L 211.97 93.98 L 212.03 94.28 L 212.07 94.58 L 212.10 94.88 L 212.11 95.20 L 212.11 95.20 L 212.09 95.61 L 212.06 96.00 L 212.00 96.38 L 211.91 96.75 L 211.80 97.10 L 211.66 97.44 L 211.50 97.76 L 211.32 98.07 L 211.11 98.36 L 210.88 98.64 L 210.88 98.64 L 210.63 98.90 L 210.37 99.14 L 210.09 99.37 L 209.79 99.57 L 209.48 99.76 L 209.15 99.94 L 208.80 100.09 L 208.44 100.23 L 208.06 100.35 L 207.67 100.45 L 207.67 100.45 L 208.12 100.51 L 208.55 100.59 L 208.96 100.69 L 209.36 100.81 L 209.74 100.97 L 210.10 101.14 L 210.45 101.34 L 210.78 101.57 L 211.09 101.82 L 211.39 102.09 L 211.39 102.09 L 211.66 102.39 L 211.90 102.71 L 212.12 103.05 L 212.31 103.40 L 212.46 103.78 L 212.59 104.18 L 212.69 104.60 L 212.77 105.04 L 212.81 105.50 L 212.82 105.99 L 212.82 105.99 L 212.81 106.38 L 212.78 106.76 L 212.74 107.14 L 212.67 107.51 L 212.58 107.87 L 212.48 108.23 L 212.35 108.57 L 212.21 108.91 L 212.05 109.25 L 211.87 109.57 L 211.87 109.57 L 211.67 109.88 L 211.45 110.19 L 211.22 110.47 L 210.97 110.75 L 210.71 111.00 L 210.42 111.25 L 210.13 111.48 L 209.81 111.70 L 209.48 111.91 L 209.14 112.10 L 209.14 112.10 L 208.77 112.27 L 208.40 112.42 L 208.02 112.55 L 207.63 112.67 L 207.22 112.76 L 206.81 112.84 L 206.38 112.90 L 205.94 112.95 L 205.50 112.98 L 205.04 112.98 L 205.04 112.98 L 204.62 112.98 L 204.22 112.95 L 203.82 112.92 L 203.43 112.86 L 203.04 112.80 L 202.66 112.71 L 202.29 112.62 L 201.92 112.50 L 201.57 112.38 L 201.21 112.23 L 201.21 112.23 L 200.87 112.07 L 200.54 111.89 L 200.21 111.69 L 199.89 111.49 L 199.58 111.26 L 199.28 111.02 L 198.99 110.77 L 198.71 110.50 L 198.44 110.21 L 198.18 109.91 L 200.05 108.17 L 200.05 108.17 L 200.28 108.40 L 200.51 108.61 L 200.73 108.81 L 200.96 109.00 L 201.19 109.18 L 201.42 109.34 L 201.65 109.49 L 201.88 109.63 L 202.11 109.76 L 202.34 109.88 L 202.34 109.88 L 202.58 109.98 L 202.83 110.07 L 203.08 110.16 L 203.33 110.23 L 203.59 110.29 L 203.85 110.34 L 204.11 110.37 L 204.38 110.40 L 204.66 110.42 L 204.94 110.42 L 204.94 110.42 L 205.36 110.41 L 205.76 110.38 L 206.14 110.32 L 206.51 110.23 L 206.86 110.13 L 207.18 109.99 L 207.50 109.84 L 207.79 109.66 L 208.06 109.46 L 208.32 109.23 L 208.32 109.23 L 208.55 108.98 L 208.76 108.70 L 208.94 108.42 L 209.10 108.11 L 209.24 107.80 L 209.35 107.46 L 209.43 107.11 L 209.50 106.74 L 209.53 106.35 L 209.54 105.95 L 209.54 105.95 L 209.53 105.51 L 209.50 105.10 L 209.44 104.72 L 209.36 104.36 L 209.25 104.03 L 209.13 103.73 L 208.98 103.45 L 208.80 103.20 L 208.60 102.97 L 208.38 102.78 L 208.38 102.78 L 208.14 102.60 L 207.88 102.44 L 207.59 102.31 L 207.29 102.19 L 206.96 102.08 L 206.61 102.00 L 206.24 101.94 L 205.85 101.89 L 205.44 101.86 L 205.00 101.85 L 203.30 101.85 L 203.67 99.43 L 204.83 99.43 L 204.83 99.43 L 205.18 99.42 L 205.52 99.39 L 205.84 99.33 L 206.16 99.26 L 206.45 99.17 L 206.74 99.05 L 207.02 98.91 L 207.28 98.75 L 207.53 98.57 L 207.77 98.37 L 207.77 98.37 L 208.00 98.15 L 208.20 97.91 L 208.38 97.66 L 208.53 97.39 L 208.67 97.10 L 208.77 96.79 L 208.86 96.47 L 208.92 96.13 L 208.95 95.77 L 208.96 95.40 L 208.96 95.40 L 208.95 95.09 L 208.92 94.79 L 208.87 94.50 L 208.80 94.23 L 208.71 93.97 L 208.60 93.72 L 208.46 93.49 L 208.31 93.27 L 208.13 93.07 L 207.94 92.87 L 207.94 92.87 L 207.73 92.69 L 207.50 92.53 L 207.26 92.39 L 207.01 92.26 L 206.74 92.16 L 206.45 92.07 L 206.15 92.00 L 205.84 91.96 L 205.52 91.93 L 205.17 91.92 L 205.17 91.92 L 204.91 91.92 L 204.65 91.94 L 204.39 91.96 L 204.14 91.99 L 203.89 92.04 L 203.65 92.09 L 203.41 92.15 L 203.18 92.22 L 202.94 92.31 L 202.72 92.40 L 202.72 92.40 L 202.50 92.50 L 202.28 92.61 L 202.05 92.74 L 201.83 92.88 L 201.61 93.03 L 201.38 93.19 L 201.15 93.37 L 200.92 93.55 L 200.69 93.75 L 200.46 93.97 L 198.82 92.09 L 198.82 92.09 L 199.41 91.58 L 200.01 91.12 L 200.63 90.71 L 201.26 90.36 L 201.91 90.07 L 202.56 89.82 L 203.24 89.63 L 203.93 89.50 L 204.63 89.42 L 205.35 89.39 Z' style='fill-rule: evenodd; fill: #FFFFFF; stroke-width: 0.75; stroke: none;' clip-path='url(#cpMHw1MDR8MzQzLjY0OXw0MS4yNDkx)' /> | |||
| <path d='M 291.16 102.95 L 297.41 112.58 L 293.62 112.58 L 289.21 104.96 L 284.74 112.58 L 281.19 112.58 L 287.47 103.08 L 281.91 94.58 L 285.59 94.58 L 289.35 101.17 L 293.14 94.58 L 296.69 94.58 L 291.16 102.95 Z' style='fill-rule: evenodd; fill: #FFFFFF; stroke-width: 0.75; stroke: none;' clip-path='url(#cpMHw1MDR8MzQzLjY0OXw0MS4yNDkx)' /> | |||
| <path d='M 305.26 89.39 L 305.26 89.39 L 305.68 89.40 L 306.08 89.42 L 306.48 89.46 L 306.86 89.52 L 307.22 89.59 L 307.58 89.67 L 307.92 89.78 L 308.26 89.89 L 308.57 90.03 L 308.88 90.18 L 308.88 90.18 L 309.18 90.34 L 309.47 90.51 L 309.73 90.70 L 309.99 90.89 L 310.23 91.10 L 310.45 91.32 L 310.66 91.54 L 310.86 91.78 L 311.04 92.03 L 311.20 92.29 L 311.20 92.29 L 311.36 92.56 L 311.50 92.84 L 311.62 93.11 L 311.73 93.40 L 311.82 93.69 L 311.89 93.98 L 311.95 94.28 L 311.99 94.58 L 312.01 94.88 L 312.02 95.20 L 312.02 95.20 L 312.01 95.61 L 311.97 96.00 L 311.91 96.38 L 311.83 96.75 L 311.71 97.10 L 311.58 97.44 L 311.42 97.76 L 311.24 98.07 L 311.03 98.36 L 310.79 98.64 L 310.79 98.64 L 310.55 98.90 L 310.28 99.14 L 310.00 99.37 L 309.71 99.57 L 309.39 99.76 L 309.06 99.94 L 308.72 100.09 L 308.36 100.23 L 307.98 100.35 L 307.58 100.45 L 307.58 100.45 L 308.03 100.51 L 308.46 100.59 L 308.88 100.69 L 309.28 100.81 L 309.66 100.97 L 310.02 101.14 L 310.37 101.34 L 310.70 101.57 L 311.01 101.82 L 311.30 102.09 L 311.30 102.09 L 311.58 102.39 L 311.82 102.71 L 312.04 103.05 L 312.22 103.40 L 312.38 103.78 L 312.51 104.18 L 312.61 104.60 L 312.68 105.04 L 312.72 105.50 L 312.74 105.99 L 312.74 105.99 L 312.73 106.38 L 312.70 106.76 L 312.65 107.14 L 312.59 107.51 L 312.50 107.87 L 312.39 108.23 L 312.27 108.57 L 312.13 108.91 L 311.96 109.25 L 311.78 109.57 L 311.78 109.57 L 311.58 109.88 L 311.37 110.19 L 311.14 110.47 L 310.89 110.75 L 310.62 111.00 L 310.34 111.25 L 310.04 111.48 L 309.73 111.70 L 309.40 111.91 L 309.05 112.10 L 309.05 112.10 L 308.69 112.27 L 308.32 112.42 L 307.94 112.55 L 307.54 112.67 L 307.14 112.76 L 306.72 112.84 L 306.30 112.90 L 305.86 112.95 L 305.41 112.98 L 304.95 112.98 L 304.95 112.98 L 304.54 112.98 L 304.13 112.95 L 303.74 112.92 L 303.34 112.86 L 302.96 112.80 L 302.58 112.71 L 302.21 112.62 L 301.84 112.50 L 301.48 112.38 L 301.13 112.23 L 301.13 112.23 L 300.79 112.07 L 300.45 111.89 L 300.13 111.69 L 299.81 111.49 L 299.50 111.26 L 299.20 111.02 L 298.91 110.77 L 298.63 110.50 L 298.36 110.21 L 298.09 109.91 L 299.97 108.17 L 299.97 108.17 L 300.19 108.40 L 300.42 108.61 L 300.65 108.81 L 300.88 109.00 L 301.10 109.18 L 301.33 109.34 L 301.56 109.49 L 301.79 109.63 L 302.02 109.76 L 302.26 109.88 L 302.26 109.88 L 302.50 109.98 L 302.74 110.07 L 302.99 110.16 L 303.25 110.23 L 303.50 110.29 L 303.76 110.34 L 304.03 110.37 L 304.30 110.40 L 304.57 110.42 L 304.85 110.42 L 304.85 110.42 L 305.27 110.41 L 305.68 110.38 L 306.06 110.32 L 306.43 110.23 L 306.77 110.13 L 307.10 109.99 L 307.41 109.84 L 307.70 109.66 L 307.98 109.46 L 308.23 109.23 L 308.23 109.23 L 308.47 108.98 L 308.67 108.70 L 308.86 108.42 L 309.02 108.11 L 309.15 107.80 L 309.26 107.46 L 309.35 107.11 L 309.41 106.74 L 309.45 106.35 L 309.46 105.95 L 309.46 105.95 L 309.45 105.51 L 309.41 105.10 L 309.36 104.72 L 309.28 104.36 L 309.17 104.03 L 309.04 103.73 L 308.89 103.45 L 308.72 103.20 L 308.52 102.97 L 308.30 102.78 L 308.30 102.78 L 308.06 102.60 L 307.79 102.44 L 307.51 102.31 L 307.20 102.19 L 306.87 102.08 L 306.53 102.00 L 306.16 101.94 L 305.77 101.89 L 305.35 101.86 L 304.92 101.85 L 303.21 101.85 L 303.59 99.43 L 304.75 99.43 L 304.75 99.43 L 305.10 99.42 L 305.43 99.39 L 305.76 99.33 L 306.07 99.26 L 306.37 99.17 L 306.66 99.05 L 306.93 98.91 L 307.20 98.75 L 307.45 98.57 L 307.69 98.37 L 307.69 98.37 L 307.91 98.15 L 308.12 97.91 L 308.30 97.66 L 308.45 97.39 L 308.58 97.10 L 308.69 96.79 L 308.77 96.47 L 308.83 96.13 L 308.87 95.77 L 308.88 95.40 L 308.88 95.40 L 308.87 95.09 L 308.84 94.79 L 308.79 94.50 L 308.72 94.23 L 308.62 93.97 L 308.51 93.72 L 308.38 93.49 L 308.23 93.27 L 308.05 93.07 L 307.86 92.87 L 307.86 92.87 L 307.64 92.69 L 307.42 92.53 L 307.18 92.39 L 306.92 92.26 L 306.65 92.16 L 306.37 92.07 L 306.07 92.00 L 305.76 91.96 L 305.43 91.93 L 305.09 91.92 L 305.09 91.92 L 304.83 91.92 L 304.57 91.94 L 304.31 91.96 L 304.06 91.99 L 303.81 92.04 L 303.57 92.09 L 303.33 92.15 L 303.09 92.22 L 302.86 92.31 L 302.63 92.40 L 302.63 92.40 L 302.41 92.50 L 302.19 92.61 L 301.97 92.74 L 301.75 92.88 L 301.52 93.03 L 301.30 93.19 L 301.07 93.37 L 300.84 93.55 L 300.61 93.75 L 300.38 93.97 L 298.74 92.09 L 298.74 92.09 L 299.33 91.58 L 299.93 91.12 L 300.55 90.71 L 301.18 90.36 L 301.82 90.07 L 302.48 89.82 L 303.15 89.63 L 303.84 89.50 L 304.54 89.42 L 305.26 89.39 Z' style='fill-rule: evenodd; fill: #FFFFFF; stroke-width: 0.75; stroke: none;' clip-path='url(#cpMHw1MDR8MzQzLjY0OXw0MS4yNDkx)' /> | |||
| <defs> | |||
| <clipPath id='cpMHw1MDR8MzYwfDA='> | |||
| <rect x='0.00' y='0.00' width='504.00' height='360.00' /> | |||
| </clipPath> | |||
| </defs> | |||
| <path d='M 155.47 35.61 L 155.47 35.61 L 155.47 35.75 L 155.48 35.89 L 155.49 36.02 L 155.51 36.14 L 155.53 36.25 L 155.56 36.36 L 155.59 36.46 L 155.63 36.55 L 155.67 36.63 L 155.71 36.71 L 155.71 36.71 L 155.76 36.78 L 155.82 36.84 L 155.88 36.90 L 155.95 36.95 L 156.03 37.00 L 156.11 37.05 L 156.20 37.10 L 156.30 37.14 L 156.40 37.18 L 156.50 37.21 L 156.00 38.65 L 156.00 38.65 L 155.64 38.59 L 155.32 38.51 L 155.02 38.39 L 154.74 38.25 L 154.50 38.08 L 154.28 37.89 L 154.09 37.67 L 153.93 37.43 L 153.80 37.15 L 153.70 36.85 L 153.70 36.85 L 153.56 37.02 L 153.41 37.18 L 153.26 37.34 L 153.10 37.48 L 152.93 37.62 L 152.76 37.74 L 152.58 37.86 L 152.39 37.98 L 152.19 38.08 L 151.99 38.17 L 151.99 38.17 L 151.79 38.26 L 151.58 38.34 L 151.37 38.41 L 151.15 38.47 L 150.92 38.52 L 150.70 38.56 L 150.46 38.59 L 150.23 38.61 L 149.98 38.63 L 149.74 38.63 L 149.74 38.63 L 149.36 38.62 L 149.01 38.59 L 148.67 38.54 L 148.35 38.46 L 148.04 38.37 L 147.75 38.25 L 147.47 38.11 L 147.21 37.95 L 146.96 37.77 L 146.74 37.57 L 146.74 37.57 L 146.53 37.36 L 146.35 37.12 L 146.19 36.88 L 146.04 36.61 L 145.93 36.34 L 145.83 36.05 L 145.75 35.74 L 145.70 35.42 L 145.67 35.09 L 145.66 34.74 L 145.66 34.74 L 145.67 34.36 L 145.72 34.00 L 145.79 33.66 L 145.90 33.33 L 146.03 33.02 L 146.20 32.73 L 146.40 32.46 L 146.62 32.20 L 146.88 31.96 L 147.17 31.74 L 147.17 31.74 L 147.49 31.54 L 147.84 31.36 L 148.21 31.20 L 148.61 31.07 L 149.03 30.95 L 149.49 30.86 L 149.97 30.78 L 150.47 30.73 L 151.00 30.70 L 151.56 30.69 L 153.43 30.69 L 153.43 29.65 L 153.43 29.65 L 153.42 29.39 L 153.40 29.15 L 153.36 28.92 L 153.31 28.70 L 153.24 28.50 L 153.16 28.32 L 153.06 28.15 L 152.94 28.00 L 152.81 27.86 L 152.66 27.73 L 152.66 27.73 L 152.50 27.62 L 152.33 27.52 L 152.14 27.43 L 151.94 27.35 L 151.72 27.28 L 151.49 27.23 L 151.25 27.19 L 150.99 27.16 L 150.72 27.14 L 150.43 27.13 L 150.43 27.13 L 150.14 27.14 L 149.84 27.16 L 149.53 27.19 L 149.21 27.24 L 148.89 27.30 L 148.56 27.37 L 148.22 27.45 L 147.87 27.55 L 147.51 27.66 L 147.14 27.78 L 146.59 26.25 L 146.59 26.25 L 147.04 26.10 L 147.47 25.96 L 147.90 25.84 L 148.33 25.74 L 148.75 25.65 L 149.16 25.58 L 149.56 25.53 L 149.96 25.49 L 150.36 25.46 L 150.74 25.45 L 150.74 25.45 L 151.19 25.47 L 151.62 25.50 L 152.03 25.55 L 152.41 25.63 L 152.77 25.72 L 153.11 25.84 L 153.43 25.98 L 153.72 26.15 L 154.00 26.33 L 154.25 26.53 L 154.25 26.53 L 154.48 26.76 L 154.69 27.00 L 154.87 27.26 L 155.03 27.53 L 155.17 27.82 L 155.28 28.13 L 155.36 28.46 L 155.42 28.80 L 155.46 29.16 L 155.47 29.53 L 155.47 35.61 ZM 150.24 37.12 L 150.24 37.12 L 150.43 37.11 L 150.61 37.10 L 150.79 37.08 L 150.97 37.04 L 151.15 37.00 L 151.33 36.95 L 151.50 36.88 L 151.68 36.81 L 151.85 36.73 L 152.02 36.64 L 152.02 36.64 L 152.19 36.54 L 152.35 36.43 L 152.51 36.32 L 152.66 36.20 L 152.80 36.07 L 152.94 35.93 L 153.07 35.79 L 153.20 35.64 L 153.32 35.48 L 153.43 35.32 L 153.43 32.08 L 151.61 32.08 L 151.61 32.08 L 151.22 32.09 L 150.85 32.11 L 150.50 32.14 L 150.18 32.19 L 149.87 32.25 L 149.59 32.32 L 149.34 32.41 L 149.10 32.51 L 148.89 32.62 L 148.70 32.75 L 148.70 32.75 L 148.54 32.89 L 148.38 33.04 L 148.25 33.21 L 148.14 33.39 L 148.04 33.58 L 147.96 33.78 L 147.90 34.00 L 147.85 34.22 L 147.82 34.47 L 147.82 34.72 L 147.82 34.72 L 147.84 35.17 L 147.91 35.58 L 148.03 35.94 L 148.20 36.25 L 148.42 36.52 L 148.69 36.73 L 149.00 36.90 L 149.37 37.02 L 149.78 37.09 L 150.24 37.12 Z' style='fill-rule: evenodd; fill: #000000; stroke-width: 0.75; stroke: none;' clip-path='url(#cpMHw1MDR8MzYwfDA=)' /> | |||
| <path d='M 160.80 25.72 L 162.53 25.72 L 162.70 27.49 L 162.70 27.49 L 162.85 27.31 L 163.01 27.13 L 163.18 26.96 L 163.35 26.80 L 163.54 26.64 L 163.73 26.50 L 163.93 26.36 L 164.13 26.23 L 164.35 26.12 L 164.57 26.01 L 164.57 26.01 L 164.80 25.90 L 165.03 25.81 L 165.26 25.72 L 165.49 25.65 L 165.72 25.59 L 165.95 25.54 L 166.18 25.50 L 166.41 25.48 L 166.64 25.46 L 166.87 25.45 L 166.87 25.45 L 167.55 25.49 L 168.15 25.60 L 168.68 25.79 L 169.15 26.05 L 169.54 26.39 L 169.86 26.80 L 170.10 27.29 L 170.28 27.85 L 170.39 28.49 L 170.42 29.20 L 170.42 38.37 L 168.41 38.37 L 168.41 30.69 L 168.41 30.69 L 168.41 30.41 L 168.40 30.15 L 168.39 29.90 L 168.38 29.66 L 168.37 29.44 L 168.35 29.23 L 168.33 29.03 L 168.30 28.85 L 168.27 28.68 L 168.24 28.53 L 168.24 28.53 L 168.21 28.38 L 168.17 28.24 L 168.13 28.11 L 168.07 27.99 L 168.02 27.88 L 167.95 27.77 L 167.89 27.67 L 167.81 27.58 L 167.73 27.50 L 167.64 27.42 L 167.64 27.42 L 167.54 27.35 L 167.44 27.29 L 167.32 27.24 L 167.20 27.19 L 167.06 27.15 L 166.92 27.12 L 166.77 27.09 L 166.60 27.08 L 166.43 27.07 L 166.25 27.06 L 166.25 27.06 L 166.04 27.07 L 165.84 27.09 L 165.64 27.12 L 165.44 27.16 L 165.25 27.22 L 165.05 27.29 L 164.86 27.37 L 164.67 27.46 L 164.49 27.57 L 164.30 27.69 L 164.30 27.69 L 164.12 27.81 L 163.95 27.95 L 163.79 28.08 L 163.63 28.22 L 163.48 28.37 L 163.33 28.52 L 163.19 28.68 L 163.06 28.84 L 162.93 29.00 L 162.82 29.17 L 162.82 38.37 L 160.80 38.37 L 160.80 25.72 Z' style='fill-rule: evenodd; fill: #000000; stroke-width: 0.75; stroke: none;' clip-path='url(#cpMHw1MDR8MzYwfDA=)' /> | |||
| <path d='M 185.26 37.72 L 185.26 37.72 L 185.12 37.80 L 184.99 37.88 L 184.84 37.96 L 184.69 38.03 L 184.54 38.10 L 184.37 38.17 L 184.21 38.23 L 184.03 38.29 L 183.86 38.34 L 183.67 38.39 L 183.67 38.39 L 183.49 38.44 L 183.31 38.48 L 183.12 38.51 L 182.94 38.54 L 182.76 38.57 L 182.58 38.59 L 182.40 38.61 L 182.21 38.62 L 182.03 38.63 L 181.85 38.63 L 181.85 38.63 L 181.46 38.62 L 181.09 38.59 L 180.73 38.54 L 180.39 38.47 L 180.07 38.37 L 179.77 38.26 L 179.48 38.12 L 179.21 37.97 L 178.96 37.79 L 178.73 37.60 L 178.73 37.60 L 178.51 37.38 L 178.32 37.15 L 178.15 36.91 L 178.01 36.65 L 177.88 36.38 L 177.78 36.10 L 177.70 35.80 L 177.65 35.49 L 177.61 35.17 L 177.60 34.84 L 177.60 27.30 L 174.70 27.30 L 174.70 25.72 L 177.60 25.72 L 177.60 22.86 L 179.62 22.62 L 179.62 25.72 L 184.01 25.72 L 183.74 27.30 L 179.62 27.30 L 179.62 34.81 L 179.62 34.81 L 179.62 35.02 L 179.64 35.22 L 179.67 35.41 L 179.71 35.59 L 179.76 35.76 L 179.82 35.91 L 179.90 36.06 L 179.98 36.19 L 180.08 36.31 L 180.19 36.42 L 180.19 36.42 L 180.31 36.52 L 180.45 36.61 L 180.60 36.69 L 180.76 36.76 L 180.94 36.82 L 181.13 36.87 L 181.34 36.90 L 181.56 36.93 L 181.79 36.95 L 182.04 36.95 L 182.04 36.95 L 182.30 36.94 L 182.55 36.93 L 182.80 36.90 L 183.05 36.85 L 183.29 36.80 L 183.53 36.73 L 183.77 36.66 L 184.01 36.57 L 184.24 36.46 L 184.46 36.35 L 185.26 37.72 Z' style='fill-rule: evenodd; fill: #000000; stroke-width: 0.75; stroke: none;' clip-path='url(#cpMHw1MDR8MzYwfDA=)' /> | |||
| <path d='M 194.40 19.81 L 194.40 19.81 L 194.53 19.82 L 194.66 19.83 L 194.78 19.85 L 194.90 19.88 L 195.01 19.92 L 195.11 19.96 L 195.21 20.01 L 195.31 20.08 L 195.40 20.14 L 195.48 20.22 L 195.48 20.22 L 195.56 20.30 L 195.63 20.39 L 195.69 20.47 L 195.74 20.57 L 195.79 20.67 L 195.82 20.77 L 195.85 20.88 L 195.87 20.99 L 195.88 21.11 L 195.89 21.23 L 195.89 21.23 L 195.88 21.35 L 195.87 21.47 L 195.85 21.59 L 195.82 21.69 L 195.79 21.80 L 195.74 21.90 L 195.69 22.00 L 195.63 22.09 L 195.56 22.18 L 195.48 22.26 L 195.48 22.26 L 195.40 22.34 L 195.31 22.40 L 195.21 22.46 L 195.11 22.51 L 195.01 22.55 L 194.90 22.58 L 194.78 22.61 L 194.66 22.63 L 194.53 22.64 L 194.40 22.65 L 194.40 22.65 L 194.27 22.64 L 194.15 22.63 L 194.03 22.61 L 193.92 22.58 L 193.81 22.55 L 193.71 22.51 L 193.61 22.46 L 193.52 22.40 L 193.43 22.34 L 193.34 22.26 L 193.34 22.26 L 193.27 22.18 L 193.21 22.09 L 193.15 22.00 L 193.10 21.90 L 193.06 21.80 L 193.02 21.69 L 192.99 21.59 L 192.98 21.47 L 192.96 21.35 L 192.96 21.23 L 192.96 21.23 L 192.96 21.11 L 192.98 20.99 L 192.99 20.88 L 193.02 20.77 L 193.06 20.67 L 193.10 20.57 L 193.15 20.47 L 193.21 20.39 L 193.27 20.30 L 193.34 20.22 L 193.34 20.22 L 193.43 20.14 L 193.52 20.08 L 193.61 20.01 L 193.71 19.96 L 193.81 19.92 L 193.92 19.88 L 194.03 19.85 L 194.15 19.83 L 194.27 19.82 L 194.40 19.81 ZM 196.01 36.76 L 199.56 36.76 L 199.56 38.37 L 190.10 38.37 L 190.10 36.76 L 193.99 36.76 L 193.99 27.33 L 190.22 27.33 L 190.22 25.72 L 196.01 25.72 L 196.01 36.76 Z' style='fill-rule: evenodd; fill: #000000; stroke-width: 0.75; stroke: none;' clip-path='url(#cpMHw1MDR8MzYwfDA=)' /> | |||
| <path d='M 202.80 40.96 L 214.80 40.96 L 214.80 42.73 L 202.80 42.73 L 202.80 40.96 Z' style='fill-rule: evenodd; fill: #000000; stroke-width: 0.75; stroke: none;' clip-path='url(#cpMHw1MDR8MzYwfDA=)' /> | |||
| <path d='M 224.81 19.81 L 224.81 19.81 L 224.94 19.82 L 225.06 19.83 L 225.18 19.85 L 225.29 19.88 L 225.40 19.92 L 225.50 19.96 L 225.60 20.01 L 225.69 20.08 L 225.78 20.14 L 225.86 20.22 L 225.86 20.22 L 225.94 20.30 L 226.01 20.39 L 226.07 20.47 L 226.13 20.57 L 226.17 20.67 L 226.21 20.77 L 226.24 20.88 L 226.26 20.99 L 226.27 21.11 L 226.27 21.23 L 226.27 21.23 L 226.27 21.35 L 226.26 21.47 L 226.24 21.59 L 226.21 21.69 L 226.17 21.80 L 226.13 21.90 L 226.07 22.00 L 226.01 22.09 L 225.94 22.18 L 225.86 22.26 L 225.86 22.26 L 225.78 22.34 L 225.69 22.40 L 225.60 22.46 L 225.50 22.51 L 225.40 22.55 L 225.29 22.58 L 225.18 22.61 L 225.06 22.63 L 224.94 22.64 L 224.81 22.65 L 224.81 22.65 L 224.68 22.64 L 224.56 22.63 L 224.44 22.61 L 224.33 22.58 L 224.22 22.55 L 224.12 22.51 L 224.02 22.46 L 223.92 22.40 L 223.84 22.34 L 223.75 22.26 L 223.75 22.26 L 223.67 22.18 L 223.61 22.09 L 223.54 22.00 L 223.49 21.90 L 223.45 21.80 L 223.41 21.69 L 223.38 21.59 L 223.36 21.47 L 223.35 21.35 L 223.34 21.23 L 223.34 21.23 L 223.35 21.11 L 223.36 20.99 L 223.38 20.88 L 223.41 20.77 L 223.45 20.67 L 223.49 20.57 L 223.54 20.47 L 223.61 20.39 L 223.67 20.30 L 223.75 20.22 L 223.75 20.22 L 223.84 20.14 L 223.92 20.08 L 224.02 20.01 L 224.12 19.96 L 224.22 19.92 L 224.33 19.88 L 224.44 19.85 L 224.56 19.83 L 224.68 19.82 L 224.81 19.81 ZM 226.68 36.35 L 226.68 36.35 L 226.66 36.95 L 226.60 37.51 L 226.50 38.06 L 226.35 38.57 L 226.17 39.06 L 225.95 39.52 L 225.68 39.95 L 225.37 40.35 L 225.03 40.73 L 224.64 41.08 L 224.64 41.08 L 224.22 41.41 L 223.75 41.72 L 223.25 42.01 L 222.71 42.28 L 222.13 42.52 L 221.51 42.75 L 220.85 42.96 L 220.15 43.14 L 219.42 43.31 L 218.64 43.45 L 218.30 41.89 L 218.30 41.89 L 218.90 41.79 L 219.47 41.66 L 220.01 41.52 L 220.52 41.37 L 221.00 41.20 L 221.46 41.01 L 221.89 40.81 L 222.29 40.60 L 222.66 40.37 L 223.01 40.12 L 223.01 40.12 L 223.32 39.86 L 223.60 39.57 L 223.85 39.27 L 224.07 38.94 L 224.25 38.58 L 224.40 38.21 L 224.51 37.81 L 224.60 37.38 L 224.65 36.94 L 224.66 36.47 L 224.66 27.33 L 219.41 27.33 L 219.41 25.72 L 226.68 25.72 L 226.68 36.35 Z' style='fill-rule: evenodd; fill: #000000; stroke-width: 0.75; stroke: none;' clip-path='url(#cpMHw1MDR8MzYwfDA=)' /> | |||
| <path d='M 237.62 25.45 L 237.62 25.45 L 238.13 25.47 L 238.62 25.53 L 239.08 25.61 L 239.52 25.74 L 239.93 25.90 L 240.32 26.09 L 240.69 26.32 L 241.03 26.59 L 241.34 26.89 L 241.63 27.23 L 241.63 27.23 L 241.90 27.59 L 242.14 27.98 L 242.35 28.40 L 242.54 28.84 L 242.69 29.31 L 242.82 29.80 L 242.92 30.32 L 242.99 30.86 L 243.03 31.43 L 243.05 32.03 L 243.05 32.03 L 243.03 32.61 L 242.99 33.17 L 242.92 33.71 L 242.82 34.23 L 242.69 34.72 L 242.53 35.19 L 242.34 35.63 L 242.13 36.05 L 241.88 36.45 L 241.61 36.83 L 241.61 36.83 L 241.31 37.17 L 240.99 37.48 L 240.65 37.75 L 240.29 37.98 L 239.90 38.18 L 239.49 38.34 L 239.05 38.47 L 238.59 38.56 L 238.11 38.61 L 237.60 38.63 L 237.60 38.63 L 237.09 38.61 L 236.60 38.56 L 236.14 38.47 L 235.70 38.35 L 235.28 38.19 L 234.89 38.00 L 234.53 37.77 L 234.18 37.51 L 233.86 37.21 L 233.57 36.88 L 233.57 36.88 L 233.30 36.51 L 233.06 36.12 L 232.85 35.70 L 232.66 35.25 L 232.51 34.78 L 232.38 34.29 L 232.28 33.77 L 232.21 33.22 L 232.17 32.65 L 232.15 32.05 L 232.15 32.05 L 232.17 31.47 L 232.21 30.90 L 232.28 30.36 L 232.38 29.85 L 232.51 29.35 L 232.66 28.89 L 232.85 28.44 L 233.06 28.02 L 233.30 27.63 L 233.57 27.25 L 233.57 27.25 L 233.87 26.91 L 234.19 26.61 L 234.54 26.34 L 234.91 26.10 L 235.30 25.90 L 235.72 25.74 L 236.16 25.62 L 236.62 25.53 L 237.11 25.47 L 237.62 25.45 ZM 237.62 27.11 L 237.62 27.11 L 237.30 27.12 L 237.00 27.16 L 236.71 27.22 L 236.43 27.31 L 236.18 27.42 L 235.94 27.55 L 235.72 27.71 L 235.51 27.89 L 235.32 28.10 L 235.15 28.33 L 235.15 28.33 L 235.00 28.59 L 234.86 28.87 L 234.74 29.18 L 234.63 29.52 L 234.54 29.88 L 234.47 30.26 L 234.41 30.67 L 234.37 31.11 L 234.34 31.57 L 234.34 32.05 L 234.34 32.05 L 234.34 32.54 L 234.37 33.00 L 234.41 33.44 L 234.46 33.85 L 234.53 34.23 L 234.62 34.59 L 234.72 34.93 L 234.84 35.23 L 234.98 35.52 L 235.13 35.77 L 235.13 35.77 L 235.30 36.00 L 235.49 36.21 L 235.69 36.39 L 235.92 36.54 L 236.15 36.67 L 236.41 36.78 L 236.68 36.87 L 236.97 36.93 L 237.28 36.96 L 237.60 36.97 L 237.60 36.97 L 237.92 36.96 L 238.23 36.93 L 238.52 36.87 L 238.79 36.78 L 239.04 36.67 L 239.28 36.54 L 239.50 36.39 L 239.70 36.21 L 239.88 36.00 L 240.05 35.77 L 240.05 35.77 L 240.20 35.52 L 240.34 35.23 L 240.46 34.92 L 240.57 34.59 L 240.66 34.23 L 240.73 33.84 L 240.79 33.43 L 240.83 32.99 L 240.86 32.52 L 240.86 32.03 L 240.86 32.03 L 240.86 31.54 L 240.83 31.08 L 240.79 30.65 L 240.73 30.24 L 240.66 29.86 L 240.57 29.50 L 240.46 29.17 L 240.34 28.87 L 240.20 28.59 L 240.05 28.33 L 240.05 28.33 L 239.88 28.10 L 239.70 27.89 L 239.50 27.71 L 239.28 27.55 L 239.05 27.42 L 238.80 27.31 L 238.53 27.22 L 238.24 27.16 L 237.94 27.12 L 237.62 27.11 Z' style='fill-rule: evenodd; fill: #000000; stroke-width: 0.75; stroke: none;' clip-path='url(#cpMHw1MDR8MzYwfDA=)' /> | |||
| <path d='M 252.00 19.81 L 252.00 19.81 L 252.13 19.82 L 252.26 19.83 L 252.38 19.85 L 252.50 19.88 L 252.61 19.92 L 252.71 19.96 L 252.81 20.01 L 252.91 20.08 L 253.00 20.14 L 253.08 20.22 L 253.08 20.22 L 253.16 20.30 L 253.23 20.39 L 253.29 20.47 L 253.34 20.57 L 253.39 20.67 L 253.42 20.77 L 253.45 20.88 L 253.47 20.99 L 253.48 21.11 L 253.49 21.23 L 253.49 21.23 L 253.48 21.35 L 253.47 21.47 L 253.45 21.59 L 253.42 21.69 L 253.39 21.80 L 253.34 21.90 L 253.29 22.00 L 253.23 22.09 L 253.16 22.18 L 253.08 22.26 L 253.08 22.26 L 253.00 22.34 L 252.91 22.40 L 252.81 22.46 L 252.71 22.51 L 252.61 22.55 L 252.50 22.58 L 252.38 22.61 L 252.26 22.63 L 252.13 22.64 L 252.00 22.65 L 252.00 22.65 L 251.87 22.64 L 251.75 22.63 L 251.63 22.61 L 251.52 22.58 L 251.41 22.55 L 251.31 22.51 L 251.21 22.46 L 251.12 22.40 L 251.03 22.34 L 250.94 22.26 L 250.94 22.26 L 250.87 22.18 L 250.81 22.09 L 250.75 22.00 L 250.70 21.90 L 250.66 21.80 L 250.62 21.69 L 250.59 21.59 L 250.58 21.47 L 250.56 21.35 L 250.56 21.23 L 250.56 21.23 L 250.56 21.11 L 250.58 20.99 L 250.59 20.88 L 250.62 20.77 L 250.66 20.67 L 250.70 20.57 L 250.75 20.47 L 250.81 20.39 L 250.87 20.30 L 250.94 20.22 L 250.94 20.22 L 251.03 20.14 L 251.12 20.08 L 251.21 20.01 L 251.31 19.96 L 251.41 19.92 L 251.52 19.88 L 251.63 19.85 L 251.75 19.83 L 251.87 19.82 L 252.00 19.81 ZM 253.61 36.76 L 257.16 36.76 L 257.16 38.37 L 247.70 38.37 L 247.70 36.76 L 251.59 36.76 L 251.59 27.33 L 247.82 27.33 L 247.82 25.72 L 253.61 25.72 L 253.61 36.76 Z' style='fill-rule: evenodd; fill: #000000; stroke-width: 0.75; stroke: none;' clip-path='url(#cpMHw1MDR8MzYwfDA=)' /> | |||
| <path d='M 261.60 25.72 L 263.33 25.72 L 263.50 27.49 L 263.50 27.49 L 263.65 27.31 L 263.81 27.13 L 263.98 26.96 L 264.15 26.80 L 264.34 26.64 L 264.53 26.50 L 264.73 26.36 L 264.93 26.23 L 265.15 26.12 L 265.37 26.01 L 265.37 26.01 L 265.60 25.90 L 265.83 25.81 L 266.06 25.72 L 266.29 25.65 L 266.52 25.59 L 266.75 25.54 L 266.98 25.50 L 267.21 25.48 L 267.44 25.46 L 267.67 25.45 L 267.67 25.45 L 268.35 25.49 L 268.95 25.60 L 269.48 25.79 L 269.95 26.05 L 270.34 26.39 L 270.66 26.80 L 270.90 27.29 L 271.08 27.85 L 271.19 28.49 L 271.22 29.20 L 271.22 38.37 L 269.21 38.37 L 269.21 30.69 L 269.21 30.69 L 269.21 30.41 L 269.20 30.15 L 269.19 29.90 L 269.18 29.66 L 269.17 29.44 L 269.15 29.23 L 269.13 29.03 L 269.10 28.85 L 269.07 28.68 L 269.04 28.53 L 269.04 28.53 L 269.01 28.38 L 268.97 28.24 L 268.93 28.11 L 268.87 27.99 L 268.82 27.88 L 268.75 27.77 L 268.69 27.67 L 268.61 27.58 L 268.53 27.50 L 268.44 27.42 L 268.44 27.42 L 268.34 27.35 L 268.24 27.29 L 268.12 27.24 L 268.00 27.19 L 267.86 27.15 L 267.72 27.12 L 267.57 27.09 L 267.40 27.08 L 267.23 27.07 L 267.05 27.06 L 267.05 27.06 L 266.84 27.07 L 266.64 27.09 L 266.44 27.12 L 266.24 27.16 L 266.05 27.22 L 265.85 27.29 L 265.66 27.37 L 265.47 27.46 L 265.29 27.57 L 265.10 27.69 L 265.10 27.69 L 264.92 27.81 L 264.75 27.95 L 264.59 28.08 L 264.43 28.22 L 264.28 28.37 L 264.13 28.52 L 263.99 28.68 L 263.86 28.84 L 263.73 29.00 L 263.62 29.17 L 263.62 38.37 L 261.60 38.37 L 261.60 25.72 Z' style='fill-rule: evenodd; fill: #000000; stroke-width: 0.75; stroke: none;' clip-path='url(#cpMHw1MDR8MzYwfDA=)' /> | |||
| <path d='M 278.71 29.97 L 278.71 29.97 L 278.72 30.45 L 278.73 30.92 L 278.76 31.38 L 278.80 31.82 L 278.85 32.26 L 278.91 32.68 L 278.98 33.09 L 279.07 33.48 L 279.16 33.87 L 279.26 34.24 L 279.26 34.24 L 279.38 34.60 L 279.51 34.95 L 279.64 35.29 L 279.79 35.63 L 279.95 35.96 L 280.12 36.29 L 280.30 36.61 L 280.49 36.92 L 280.69 37.23 L 280.90 37.53 L 280.90 37.53 L 281.12 37.82 L 281.37 38.12 L 281.62 38.42 L 281.89 38.72 L 282.18 39.03 L 282.48 39.34 L 282.79 39.66 L 283.12 39.98 L 283.47 40.30 L 283.82 40.62 L 282.79 41.73 L 282.79 41.73 L 282.38 41.39 L 281.98 41.05 L 281.59 40.71 L 281.23 40.37 L 280.87 40.03 L 280.53 39.70 L 280.21 39.36 L 279.90 39.03 L 279.61 38.70 L 279.34 38.37 L 279.34 38.37 L 279.08 38.03 L 278.83 37.69 L 278.60 37.34 L 278.38 36.98 L 278.17 36.61 L 277.97 36.24 L 277.78 35.86 L 277.61 35.47 L 277.45 35.07 L 277.30 34.67 L 277.30 34.67 L 277.16 34.26 L 277.05 33.83 L 276.94 33.39 L 276.85 32.94 L 276.77 32.47 L 276.71 32.00 L 276.66 31.51 L 276.63 31.01 L 276.61 30.49 L 276.60 29.97 L 276.60 29.97 L 276.61 29.44 L 276.63 28.93 L 276.66 28.42 L 276.71 27.93 L 276.77 27.46 L 276.85 26.99 L 276.94 26.54 L 277.05 26.10 L 277.16 25.68 L 277.30 25.26 L 277.30 25.26 L 277.45 24.86 L 277.61 24.46 L 277.78 24.07 L 277.97 23.69 L 278.17 23.32 L 278.38 22.95 L 278.60 22.59 L 278.83 22.24 L 279.08 21.90 L 279.34 21.57 L 279.34 21.57 L 279.61 21.23 L 279.90 20.90 L 280.21 20.57 L 280.53 20.23 L 280.87 19.90 L 281.23 19.56 L 281.59 19.22 L 281.98 18.89 L 282.38 18.55 L 282.79 18.21 L 283.82 19.31 L 283.82 19.31 L 283.46 19.64 L 283.11 19.96 L 282.78 20.27 L 282.46 20.59 L 282.16 20.90 L 281.87 21.21 L 281.60 21.51 L 281.34 21.81 L 281.10 22.11 L 280.87 22.41 L 280.87 22.41 L 280.66 22.70 L 280.46 23.01 L 280.27 23.32 L 280.09 23.64 L 279.92 23.96 L 279.77 24.29 L 279.62 24.63 L 279.48 24.98 L 279.36 25.33 L 279.24 25.69 L 279.24 25.69 L 279.14 26.06 L 279.05 26.44 L 278.97 26.83 L 278.90 27.24 L 278.84 27.66 L 278.80 28.10 L 278.76 28.54 L 278.73 29.00 L 278.72 29.48 L 278.71 29.97 Z' style='fill-rule: evenodd; fill: #000000; stroke-width: 0.75; stroke: none;' clip-path='url(#cpMHw1MDR8MzYwfDA=)' /> | |||
| <path d='M 289.34 38.37 L 294.00 31.69 L 289.90 25.72 L 292.30 25.72 L 295.22 30.42 L 298.18 25.72 L 300.50 25.72 L 296.40 31.60 L 301.06 38.37 L 298.61 38.37 L 295.13 32.94 L 291.65 38.37 L 289.34 38.37 Z' style='fill-rule: evenodd; fill: #000000; stroke-width: 0.75; stroke: none;' clip-path='url(#cpMHw1MDR8MzYwfDA=)' /> | |||
| <path d='M 309.60 34.41 L 309.60 34.41 L 309.76 34.41 L 309.92 34.43 L 310.07 34.46 L 310.21 34.50 L 310.35 34.55 L 310.48 34.61 L 310.61 34.69 L 310.74 34.77 L 310.85 34.87 L 310.97 34.98 L 310.97 34.98 L 311.08 35.10 L 311.18 35.21 L 311.26 35.34 L 311.34 35.46 L 311.40 35.59 L 311.45 35.73 L 311.49 35.87 L 311.52 36.02 L 311.54 36.17 L 311.54 36.33 L 311.54 36.33 L 311.54 36.51 L 311.53 36.69 L 311.50 36.87 L 311.47 37.06 L 311.43 37.24 L 311.38 37.44 L 311.32 37.63 L 311.25 37.82 L 311.17 38.02 L 311.09 38.22 L 309.14 42.66 L 307.32 42.66 L 308.45 37.93 L 308.45 37.93 L 308.38 37.88 L 308.32 37.82 L 308.26 37.76 L 308.20 37.70 L 308.14 37.63 L 308.09 37.56 L 308.04 37.48 L 307.99 37.40 L 307.94 37.32 L 307.90 37.24 L 307.90 37.24 L 307.85 37.15 L 307.82 37.06 L 307.79 36.98 L 307.76 36.89 L 307.73 36.80 L 307.71 36.71 L 307.70 36.62 L 307.69 36.53 L 307.68 36.44 L 307.68 36.35 L 307.68 36.35 L 307.69 36.19 L 307.70 36.03 L 307.73 35.88 L 307.77 35.74 L 307.82 35.60 L 307.88 35.47 L 307.95 35.34 L 308.03 35.21 L 308.13 35.10 L 308.23 34.98 L 308.23 34.98 L 308.35 34.87 L 308.46 34.77 L 308.59 34.69 L 308.72 34.61 L 308.85 34.55 L 308.99 34.50 L 309.13 34.46 L 309.28 34.43 L 309.44 34.41 L 309.60 34.41 Z' style='fill-rule: evenodd; fill: #000000; stroke-width: 0.75; stroke: none;' clip-path='url(#cpMHw1MDR8MzYwfDA=)' /> | |||
| <path d='M 339.70 38.44 L 339.70 38.44 L 339.54 38.87 L 339.38 39.28 L 339.20 39.67 L 339.01 40.04 L 338.81 40.40 L 338.60 40.74 L 338.38 41.06 L 338.15 41.36 L 337.91 41.65 L 337.66 41.92 L 337.66 41.92 L 337.39 42.17 L 337.10 42.39 L 336.79 42.60 L 336.45 42.79 L 336.10 42.95 L 335.73 43.09 L 335.34 43.22 L 334.92 43.32 L 334.49 43.40 L 334.03 43.45 L 333.74 41.85 L 333.74 41.85 L 333.99 41.80 L 334.23 41.75 L 334.46 41.70 L 334.68 41.64 L 334.88 41.57 L 335.08 41.50 L 335.27 41.43 L 335.44 41.35 L 335.61 41.26 L 335.76 41.17 L 335.76 41.17 L 335.91 41.09 L 336.05 40.99 L 336.19 40.89 L 336.32 40.79 L 336.44 40.68 L 336.56 40.57 L 336.67 40.45 L 336.77 40.33 L 336.87 40.20 L 336.96 40.07 L 336.96 40.07 L 337.05 39.94 L 337.13 39.79 L 337.21 39.64 L 337.29 39.49 L 337.37 39.32 L 337.45 39.15 L 337.53 38.96 L 337.61 38.77 L 337.68 38.57 L 337.75 38.37 L 337.08 38.37 L 332.69 25.72 L 334.82 25.72 L 338.45 36.83 L 342.02 25.72 L 344.11 25.72 L 339.70 38.44 Z' style='fill-rule: evenodd; fill: #000000; stroke-width: 0.75; stroke: none;' clip-path='url(#cpMHw1MDR8MzYwfDA=)' /> | |||
| <path d='M 354.89 29.97 L 354.89 29.97 L 354.88 29.48 L 354.87 29.00 L 354.84 28.54 L 354.80 28.10 L 354.75 27.66 L 354.69 27.24 L 354.62 26.83 L 354.53 26.44 L 354.44 26.06 L 354.34 25.69 L 354.34 25.69 L 354.22 25.33 L 354.10 24.98 L 353.97 24.63 L 353.82 24.29 L 353.66 23.96 L 353.50 23.64 L 353.31 23.32 L 353.12 23.01 L 352.92 22.70 L 352.70 22.41 L 352.70 22.41 L 352.48 22.11 L 352.24 21.81 L 351.99 21.51 L 351.72 21.21 L 351.43 20.90 L 351.13 20.59 L 350.82 20.27 L 350.48 19.96 L 350.14 19.64 L 349.78 19.31 L 350.81 18.21 L 350.81 18.21 L 351.22 18.55 L 351.62 18.89 L 352.00 19.22 L 352.37 19.56 L 352.72 19.90 L 353.06 20.23 L 353.38 20.57 L 353.68 20.90 L 353.97 21.23 L 354.24 21.57 L 354.24 21.57 L 354.50 21.90 L 354.75 22.24 L 354.99 22.59 L 355.21 22.95 L 355.42 23.32 L 355.62 23.69 L 355.80 24.07 L 355.98 24.46 L 356.13 24.86 L 356.28 25.26 L 356.28 25.26 L 356.42 25.68 L 356.54 26.10 L 356.65 26.54 L 356.74 26.99 L 356.82 27.46 L 356.88 27.93 L 356.94 28.42 L 356.97 28.93 L 356.99 29.44 L 357.00 29.97 L 357.00 29.97 L 356.99 30.49 L 356.97 31.01 L 356.94 31.51 L 356.88 32.00 L 356.82 32.47 L 356.74 32.94 L 356.65 33.39 L 356.54 33.83 L 356.42 34.26 L 356.28 34.67 L 356.28 34.67 L 356.13 35.07 L 355.98 35.47 L 355.80 35.86 L 355.62 36.24 L 355.42 36.61 L 355.21 36.98 L 354.99 37.34 L 354.75 37.69 L 354.50 38.03 L 354.24 38.37 L 354.24 38.37 L 353.97 38.70 L 353.68 39.03 L 353.38 39.36 L 353.06 39.70 L 352.72 40.03 L 352.37 40.37 L 352.00 40.71 L 351.62 41.05 L 351.22 41.39 L 350.81 41.73 L 349.78 40.62 L 349.78 40.62 L 350.13 40.30 L 350.48 39.98 L 350.80 39.66 L 351.12 39.34 L 351.41 39.03 L 351.70 38.72 L 351.97 38.42 L 352.22 38.12 L 352.46 37.82 L 352.68 37.53 L 352.68 37.53 L 352.90 37.23 L 353.10 36.92 L 353.29 36.61 L 353.47 36.29 L 353.65 35.96 L 353.81 35.63 L 353.96 35.29 L 354.09 34.95 L 354.22 34.60 L 354.34 34.24 L 354.34 34.24 L 354.44 33.87 L 354.53 33.48 L 354.62 33.09 L 354.69 32.68 L 354.75 32.26 L 354.80 31.82 L 354.84 31.38 L 354.87 30.92 L 354.88 30.45 L 354.89 29.97 Z' style='fill-rule: evenodd; fill: #000000; stroke-width: 0.75; stroke: none;' clip-path='url(#cpMHw1MDR8MzYwfDA=)' /> | |||
| </svg> | |||