🚀🚀 Github Wrapped 2023 🚀🚀

R
Quarto
Github
Stats
Author

David Munoz Tord

Published

March 19, 2024

Create a personal GitHub wrapped using Quarto Dashboards

The time has come to create a personal GitHub wrapped using Quarto Dashboards. This is a great way to showcase your work and projects.

Setup

  1. Create a Github token (needs read:user access)
  2. add it to .Renviron as GITHUB_TOKEN, you can do that by running usethis::edit_r_environ()
  3. Install a version of Quarto that allows to create dashboards (>1.4.0)
  4. In index.qmd, change the userid parameter to your GitHub username.
  5. quarto render

If you are missing a logo of a programming language, add a svg version to img/logos

Then, we need to create a new Quarto project. We can do this by running the following command in the terminal:

---
page-title: "<YourGithubUser> - GitHub Wrapped 2023"
format: 
  dashboard:
    scrolling: true 
theme: darkly
execute: 
  echo: false
params:
    userid: "<YourGithubUser>"
    highlight: "#DBAC34"
    anti_color: "#DB5934"
    bg_color: "#2D2D2D"
    ignore_lang: "CSS,SCSS,HTML,TeX,Less,Makefile" #the languages you want to ignore
---

Load Packages

library("ghql")
library("jsonlite")
library("tidyverse")
library("patchwork")
library("reactable")

ignore_lang <- str_split(params$ignore_lang, ",")[[1]]

Function to create a donut chart

# source: https://rfortherestofus.com/2022/09/how-to-make-a-donut-chart-in-ggplot
donut_plot <- function(value, max_value, highlight_color, text_color) {
    # Wrangle data to get a data frame in the format we need it in to make our donut chart
    df <- tibble(x = 1, y = value) |>
        mutate(y_negative = max_value - y) |>
        pivot_longer(cols = -x)

    # Create a nicely formatted big number to go in the donut hole
    big_number_text_label <- value

    # Create our plot
    ggplot(
        df,
        aes(
            x = x,
            y = value,
            fill = name
        )
    ) +
        geom_col(show.legend = FALSE) +
        coord_polar(
            theta = "y",
            direction = -1
        ) +
        xlim(c(-2, 2)) +
        scale_fill_manual(values = c(highlight_color, "grey66")) +
        theme_void() +
        annotate("text",
            label = big_number_text_label,
            fontface = "bold",
            color = text_color,
            size = 12,
            x = -2,
            y = 0
        ) +
        theme(plot.title = element_text(color = text_color))
}

Setup Data

token <- Sys.getenv("GITHUB_TOKEN") # !!!!!

con <- GraphqlClient$new(
    url = "https://api.github.com/graphql",
    headers = list(Authorization = paste0("Bearer ", token))
)

variables <- list(
    userid = params$userid
)


# query for the user contributions
con$load_schema()
qry <- Query$new()
qry$query(
    "mydata", ' query getContrib($userid: String!){
  user(login:$userid){
    avatarUrl
    login
    contributionsCollection(
    from: "2023-01-01T00:00:00.000Z"
    to: "2024-01-01T00:00:00.000Z") {
    totalCommitContributions
    totalIssueContributions
    totalRepositoryContributions
    totalRepositoriesWithContributedCommits
    totalPullRequestContributions
    totalPullRequestReviewContributions
    contributionCalendar {
        totalContributions
        weeks {
            contributionDays {
              contributionCount
              date
            }
        }
    }
    commitContributionsByRepository {
        contributions {
            totalCount
        }
        repository {
            name
            owner {
                login
            }
            isPrivate
            languages(first: 5, orderBy: {field: SIZE, direction: DESC}) {
                edges {
                    size
                    node {
                        color
                        name
                        id
                    }
                }
            }
        }
    }
}
  }
}'
)
x <- con$exec(qry$queries$mydata, variables)
res <- jsonlite::fromJSON(x)


#hihih a pun
stargaze <- gh::gh("/users/{username}/starred", username = params$userid, .accept = "application/vnd.github.v3.star+json", per_page = 100)

Prepare the Data

contrib <- res$data$user$contributionsCollection$contributionCalendar$weeks
tbl_contributions <- map_dfr(seq_len(nrow(contrib)), \(x) contrib[x, ][[1]])

contrib_by_repo <- flatten_dfc(res$data$user$contributionsCollection$commitContributionsByRepository)

most_lang <- count(bind_rows(contrib_by_repo$edges)$node, name) |>
    filter(!name %in% ignore_lang) |>
    top_n(3, n) |>
    arrange(-n) |>
    mutate(n = n / sum(n))

most_lang_img <- paste0("img/logos/", most_lang$name, ".svg")

tbl_contributions$wday <- lubridate::wday(tbl_contributions$date, label = TRUE, abbr = TRUE)
tbl_contributions$week <- lubridate::week(tbl_contributions$date)

fill_breaks <- pretty(tbl_contributions$contributionCount)
fill_breaks[1] <- ifelse(fill_breaks[1] == 0, 1, fill_breaks[1])
fill_breaks[length(fill_breaks)] <- max(tbl_contributions$contributionCount)
end <- min(Sys.Date(), as.Date("2023-12-31"))
streak <- (tbl_contributions$contributionCount[tbl_contributions$date <= end] != 0) + 0
y <- rle(streak)
streak_len <- max(y$lengths[y$values == 1])
gap_len <- max(y$lengths[y$values == 0])
wkend <- sum(
    tbl_contributions$contributionCount[tbl_contributions$wday %in% c("Sun", "Sat")]
) / sum(tbl_contributions$contributionCount)

stars <- map_dfr(stargaze, \(x) tibble(
    starred_at = as.POSIXct(x$starred_at),
    repo = x$repo$full_name,
    descr = x$repo$description
)) |>
    filter(lubridate::year(starred_at) == 2023) |>
    nrow()
stars <- ifelse(stars == 100, "100+", stars)

Render the Dashboard

<h1 style="text-align:center;margin-bottom:0.15em"> 🚀🚀🚀 GitHub Wrapped 2023 🚀🚀🚀 </h1>
<span style="text-align:center">
![](`r res$data$user$avatarUrl`){width=10em}
</span>
<h1 style="text-align:center;margin-top:0.15em"> `r params$userid` </h1>

<center>
![](`r most_lang_img[1]`){width=3em} 
![](`r most_lang_img[2]`){width=3em}
![](`r most_lang_img[3]`){width=3em}
</center>

Row

list(
    icon = "stars",
    color = params$highlight,
    value = res$data$user$contributionsCollection$totalCommitContributions
)
list(
    icon = "journal-arrow-up",
    color = "#9BA7C0",
    value = res$data$user$contributionsCollection$totalRepositoryContributions
)
list(
    icon = "bullseye",
    color = "#9BA7C0",
    value = res$data$user$contributionsCollection$totalIssueContributions
)
list(
    icon = "sign-merge-right",
    color = "#9BA7C0",
    value = res$data$user$contributionsCollection$totalPullRequestContributions
)
list(
    icon = "star-fill",
    color = "#9BA7C0",
    value = stars
)

Row

p1 <- tbl_contributions |>
    mutate(contributionCount = ifelse(contributionCount == 0, NA, contributionCount)) |>
    ggplot(aes(x = week, y = fct_rev(wday))) +
    geom_tile(aes(fill = contributionCount), size = 1, color = "white") +
    # coord_fixed() +
    scale_fill_gradient(breaks = fill_breaks, low = "#9BE9A8", high = "#216E39", name = "", na.value = "grey66") +
    scale_x_continuous(breaks = NULL, name = "", limits = c(0, 53), expand = c(0, 0)) +
    scale_y_discrete(labels = c("", "Fri", "", "Wed", "", "Mon", "")) +
    theme_minimal() +
    theme(
        legend.position = "bottom",
        legend.justification = "right",
        legend.text = element_text(color = "white"),
        axis.text = element_text(color = "white"),
        axis.title.y = element_blank(),
        panel.grid.major = element_blank(),
        axis.ticks.y = element_blank()
    ) +
    guides(fill = guide_legend(
        label.position = "bottom",
        direction = "horizontal"
    ))
p2 <- donut_plot(streak_len, streak_len, params$highlight, "white") + labs(title = "longest streak")
p3 <- donut_plot(gap_len, gap_len, params$anti_color, "white") + labs(title = "longest gap")
p4 <- donut_plot(round(100 * wkend), 100, "#216E39", "white") + labs(title = "weekend contribs (%)")
p5 <- donut_plot(sum(streak), 365, "#216E39", "white") + labs(title = "active days")
p <- (p2 | p3 | p5 | p4) / p1 +
    plot_annotation(theme = theme(
        plot.background = element_rect(fill = params$bg_color, color = params$bg_color),
        panel.background = element_rect(fill = params$bg_color)
    ))
p
contrib_by_repo |>
    select(-edges) |>
    filter(!isPrivate) |>
    select(repo = name, login, contributions = totalCount) |>
    reactable(theme = reactableTheme(backgroundColor = params$bg_color))

Row

tbl_contributions |>
    group_by(wday) |>
    summarise(count = sum(contributionCount)) |>
    mutate(type = case_when(
        count == min(count) ~ "min",
        count == max(count) ~ "max",
        TRUE ~ "regular"
    )) |>
    ggplot(aes(x = wday, y = count)) +
    geom_col(aes(fill = type), show.legend = FALSE) +
    geom_text(aes(y = count / 2, label = count), color = params$bg_color) +
    scale_fill_manual(values = c("min" = params$anti_color, "max" = params$highlight, "regular" = "white")) +
    scale_x_discrete(labels = c("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat")) +
    theme_minimal() +
    theme(
        plot.background = element_rect(fill = params$bg_color, color = "white"),
        panel.background = element_rect(fill = params$bg_color),
        panel.grid.major = element_blank(),
        panel.grid.minor = element_blank(),
        axis.text.y = element_blank(),
        axis.text.x = element_text(color = "white"),
        axis.title = element_blank()
    )
tbl_contributions |>
    mutate(month = lubridate::month(date, label = TRUE)) |>
    group_by(month) |>
    summarise(count = sum(contributionCount)) |>
    mutate(type = case_when(
        count == min(count) ~ "min",
        count == max(count) ~ "max",
        TRUE ~ "regular"
    )) |>
    ggplot(aes(x = month, y = count)) +
    geom_col(aes(fill = type), show.legend = FALSE) +
    geom_text(aes(y = count / 2, label = count), color = params$bg_color) +
    scale_fill_manual(values = c("min" = params$anti_color, "max" = params$highlight, "regular" = "white")) +
    theme_minimal() +
    theme(
        plot.background = element_rect(fill = params$bg_color, color = "white"),
        panel.background = element_rect(fill = params$bg_color),
        panel.grid.major = element_blank(),
        panel.grid.minor = element_blank(),
        axis.text.y = element_blank(),
        axis.text.x = element_text(color = "white"),
        axis.title = element_blank()
    )

What it looks like

And here you go! You have created a personal GitHub wrapped using Quarto Dashboards. This is a great way to showcase your work and projects. You can also add more sections to the dashboard, such as a section for your most used programming languages, a section for your most starred repositories, and a section for your most active days. You can also customize the dashboard to fit your personal style and preferences. Have fun creating your own GitHub wrapped!

All the code is available in this GitHub repository