{"id":13788702,"url":"https://github.com/giocomai/ganttrify","last_synced_at":"2025-10-24T05:04:18.481Z","repository":{"id":43322355,"uuid":"210787641","full_name":"giocomai/ganttrify","owner":"giocomai","description":"Create beautiful Gantt charts with ggplot2","archived":false,"fork":false,"pushed_at":"2024-07-16T20:37:01.000Z","size":4588,"stargazers_count":679,"open_issues_count":24,"forks_count":65,"subscribers_count":11,"default_branch":"master","last_synced_at":"2025-05-06T14:01:50.726Z","etag":null,"topics":["gantt-charts","ggplot2","rstats"],"latest_commit_sha":null,"homepage":"https://ganttrify.europeandatajournalism.eu/","language":"R","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/giocomai.png","metadata":{"files":{"readme":"README.Rmd","changelog":"NEWS.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-09-25T07:54:50.000Z","updated_at":"2025-04-27T03:34:01.000Z","dependencies_parsed_at":"2024-01-30T23:37:06.457Z","dependency_job_id":"cad5b8d1-8f69-47dd-878e-21355a6f9424","html_url":"https://github.com/giocomai/ganttrify","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/giocomai%2Fganttrify","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/giocomai%2Fganttrify/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/giocomai%2Fganttrify/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/giocomai%2Fganttrify/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/giocomai","download_url":"https://codeload.github.com/giocomai/ganttrify/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253667931,"owners_count":21944941,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["gantt-charts","ggplot2","rstats"],"created_at":"2024-08-03T21:00:52.170Z","updated_at":"2025-10-24T05:04:13.447Z","avatar_url":"https://github.com/giocomai.png","language":"R","funding_links":[],"categories":["R","ggplot","List of resources"],"sub_categories":["Additional Plot Types","Visualization"],"readme":"---\noutput: github_document\n---\n\n\u003c!-- README.md is generated from README.Rmd. Please edit that file --\u003e\n\n```{r, include = FALSE}\nknitr::opts_chunk$set(\n  collapse = TRUE,\n  comment = \"#\u003e\",\n  fig.path = \"man/figures/README-\",\n  dpi = 150,\n  out.width = \"100%\"\n)\n```\n\n# ganttrify\n\n\u003c!-- badges: start --\u003e\n[![ganttrify status badge](https://giocomai.r-universe.dev/badges/ganttrify)](https://giocomai.r-universe.dev/ganttrify)\n\u003c!-- badges: end --\u003e\n\n`ganttrify` facilitates the creation of nice-looking Gantt charts, commonly used in project proposals and project management.\n\nIf you just want to check this out in an interactive web interface, [click here and enjoy](https://ganttrify.europeandatajournalism.eu/). Some more [context in this blog post](https://medium.com/european-data-journalism-network/beautiful-gantt-charts-with-ggplot2-80ccd8c2c788). \n\nRead on for more details and examples.\n\n## Motivation\n\nIt is possible to find online documented attempts at facilitating the creation of Gantt charts from R. Some of them (e.g. [this](https://www.molecularecologist.com/2019/01/simple-gantt-charts-in-r-with-ggplot2-and-the-tidyverse/) and [this](https://davetang.org/muse/2017/02/03/gantt-chart-using-r/)) use 'ggplot2', but I feel they do not look very nice. The same goes for the answers I found in the [relevant Stackoverflow question](https://stackoverflow.com/questions/3550341/gantt-charts-with-r).\n\n\nEven [Plotly](https://moderndata.plot.ly/gantt-charts-in-r-using-plotly/) enables the creation of Gantt charts in R, but again, I don't like the end result. \n\nI did find a [solution that was rather visually satisfying](https://insileco.github.io/2017/09/20/gantt-charts-in-r/), but it was in base R, and all the cool kids nowadays know that base plotting in R exists only [for compatibility with S](https://botsin.space/@whydoesr): not an option! (Hey, I'm joking, don't @ me!)\n\nGiven what is evidently my posh taste for Gantt charts, I had no other option than making this package with a pretentious, gentrified name, instead of the obvious \"ganttr\". \n\nPlease welcome `ganttrify`.\n\n## Disclaimer\n\nMore seriously, this has been a quick attempt at making decent-looking Gantt charts.\n\nAnd yes, I will enable all the customisations you like, but first I actually need to submit this project.\n\n[Thanks to all who contributed suggestions via issues and pull requests!]\n\n[This package has been developed as a personal endeavour, originally conceived for grant writing at my employer ([OBCT](https://www.balcanicaucaso.org/)/[CCI](https://www.cci.tn.it/))]. The Shiny interface, now hosted in [a separate repository](), has been developed for [EDJNet](https://www.europeandatajournalism.eu/), the European Data Journalism Network.\n\n## Features\n\nTake an adequately formatted spreadsheet and turn it into a Gantt chart made with ggplot2.\n\n## Installation\n\nYou can install the development version from [GitHub](https://github.com/) with:\n\n```{r eval = FALSE}\n# install.packages(\"remotes\")\nremotes::install_github(\"giocomai/ganttrify\")\n```\n\nOr from R-universe:\n\n```{r eval = FALSE}\ninstall.packages(\"ganttrify\",\n  repos = c(\n    \"https://giocomai.r-universe.dev\",\n    \"https://cloud.r-project.org\"\n  )\n)\n```\n\n## Example\n\nHere is an example project:\n\n```{r echo=FALSE}\nknitr::kable(ganttrify::test_project)\n```\n\n\nMonth since the beginning of the project are used as reference in order to make it easier to change the date when the project starts without needing to change the timing of all activities.\n\nIf you prefer to include dates instead of month numbers, please see additional examples below.\n\n\n```{r gantt_chart}\nlibrary(\"ganttrify\")\n\nganttrify(\n  project = ganttrify::test_project,\n  project_start_date = \"2021-03\",\n  font_family = \"Roboto Condensed\"\n)\n```\n[all examples in this page will use the `Roboto condensed` font; if it is not installed, you can use the default `sans`. See the *Troubleshooting* section at the bottom of this readme.]\n\n\"But what if I wanted to add spot labels for events, deliverables, outputs, milestones, things like that?\", you asked.\n\nJust put them in a table with these column names, and you will be served.\n\n```{r echo=FALSE}\nknitr::kable(ganttrify::test_spots)\n```\n\n```{r gantt_charts_with_events}\nganttrify(\n  project = ganttrify::test_project,\n  spots = ganttrify::test_spots,\n  project_start_date = \"2021-03\",\n  font_family = \"Roboto Condensed\"\n)\n```\n\n\"I can't read the text, can I change the text size?\", I heard. \n\"Also, is it possible to outline quarters?\"\n\nYou're welcome. \n\n```{r gantt_charts_resized_text}\nganttrify(\n  project = ganttrify::test_project,\n  spots = ganttrify::test_spots,\n  project_start_date = \"2021-03\",\n  size_text_relative = 1.2,\n  mark_quarters = TRUE,\n  font_family = \"Roboto Condensed\"\n)\n```\n\nIt appears that some of you don't like having a line for the working package and are fine with just lines for activities. Did you mean it like this? (also, consider setting `hide_activities` to TRUE if on the contrary you want to hide activities and keep only working packages)\n\n```{r gantt_no_wp}\nganttrify(\n  project = ganttrify::test_project,\n  hide_wp = TRUE,\n  font_family = \"Roboto Condensed\"\n)\n```\n\nOr perhaps, you did want to keep the name of working package on the left, but just felt that the horizontal line for the WP is redundant? Let's make the WP lines invisible, and nobody will ever know they're there!\n\n```{r gantt_no_wp_with_title}\nganttrify(\n  project = ganttrify::test_project,\n  alpha_wp = 0,\n  font_family = \"Roboto Condensed\"\n)\n```\n\nI felt that rounded line endings for the working packages, and \"butt\" ending for activities is the best combination of elegance and clarity. \n\nAlso, I like full opacity for the lines, but it's ok if you don't:\n  \n```{r gantt_butt_line_transparency}\nganttrify(\n  project = ganttrify::test_project,\n  project_start_date = \"2021-04\",\n  alpha_wp = 0.9,\n  alpha_activity = 0.6,\n  line_end_wp = \"round\", # alternative values: \"butt\" or \"square\"\n  line_end_activity = \"round\", # alternative values: \"butt\" or \"square\"\n  font_family = \"Roboto Condensed\"\n)\n```\nIf you use spot events, then there's all sorts of opinions you can have about the color and transparency of spot events, as well as the size and padding around the text. Say, you want a larger box around the text, but a semi-transparent background? There you go!\n\n```{r spot_customisations}\nganttrify(\n  project = ganttrify::test_project,\n  spots = ganttrify::test_spots,\n  project_start_date = \"2021-04\",\n  font_family = \"Roboto Condensed\",\n  spot_size_text_relative = 1.5,\n  spot_fill = ggplot2::alpha(c(\"white\"), 0.7),\n  spot_padding = ggplot2::unit(0.4, \"lines\")\n)\n```\nOr perhaps you actually just want the label text, without any background, or perhaps change the text colour. Be mindful that the text may become hard to read on darker backgrounds, especially if reviewers then print your proposal, but... you have been warned:\n\n```{r spot_text}\nganttrify(\n  project = ganttrify::test_project,\n  spots = ganttrify::test_spots,\n  project_start_date = \"2021-04\",\n  font_family = \"Roboto Condensed\",\n  spot_text_colour = \"grey10\",\n  spot_fontface = \"bold\",\n  spot_fill = NA,\n  spot_border = NA\n)\n```\n\nSome of us work on very long projects, and may need to declutter the chart to increase readability. So let's show the month number only once every three months, and hide the thin vertical lines included by default.\n\n\n```{r gantt_36_months}\ntest_36 \u003c- ganttrify::test_project\ntest_36[11, 4] \u003c- 36\n\nganttrify(\n  project = test_36,\n  project_start_date = \"2021-04\",\n  month_breaks = 3,\n  show_vertical_lines = FALSE,\n  font_family = \"Roboto Condensed\"\n)\n```\n\nIf you have many working packages, you may want to adjust the size of the output, and choose palettes with more colours. You can always pass a custom palette (e.g. by setting `colour_palette = c(\"#6ACCEA\", \"#00FFB8\", \"#B90000\", \"#6C919C\")`), but if you're looking for some inspiration, the package [`MetBrewer`](https://github.com/BlakeRMills/MetBrewer) with palettes inspired by works at the Metropolitan Museum of Art in New York has some options that may make your gantt chart even fancier. \n\n```{r lakota, fig.width=7, fig.height=7}\ntest_project_df \u003c- dplyr::bind_rows(\n  ganttrify::test_project,\n  tibble::tibble(\n    wp = ganttrify::test_project$wp %\u003e% stringr::str_replace(pattern = \"1\", replacement = \"4\") %\u003e% stringr::str_replace(pattern = \"2\", replacement = \"5\") %\u003e% stringr::str_replace(pattern = \"3\", replacement = \"6\"),\n    activity = ganttrify::test_project$activity %\u003e% stringr::str_replace(pattern = \"^1\", replacement = \"4\") %\u003e% stringr::str_replace(pattern = \"^2\", replacement = \"5\") %\u003e% stringr::str_replace(pattern = \"^3\", replacement = \"6\"),\n    start_date = test_project$start_date + 12,\n    end_date = test_project$end_date + 12\n  )\n)\n\n\nganttrify(\n  project = test_project_df,\n  size_text_relative = 1.2,\n  month_breaks = 2,\n  project_start_date = \"2023-01\",\n  font_family = \"Roboto Condensed\",\n  colour_palette = MetBrewer::met.brewer(\"Lakota\")\n) # or e.g. colour_palette = c(\"#6ACCEA\", \"#00FFB8\", \"#B90000\", \"#6C919C\")\n``` \n\n\nDoes right-aligned text bother you?\n\n```{r gantt_left_aligned}\nganttrify(\n  project = ganttrify::test_project,\n  spots = ganttrify::test_spots,\n  project_start_date = \"2021-04\",\n  axis_text_align = \"left\",\n  font_family = \"Roboto Condensed\"\n)\n```\n\n```{r gantt_centre_aligned}\nganttrify(\n  project = ganttrify::test_project,\n  spots = ganttrify::test_spots,\n  project_start_date = \"2021-04\",\n  axis_text_align = \"centre\",\n  font_family = \"Roboto Condensed\"\n)\n```\n  \nDo you have *very* long names for your activities? The parameter `label_wrap` is there to help you.\n\n```{r gantt_long_labels}\ntibble::tribble(\n  ~wp, ~activity, ~start_date, ~end_date,\n  \"WP 1\", \"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Aliquet eget sit amet tellus\", 1, 6,\n  \"WP 2\", \"Proin sed libero enim sed faucibus turpis in eu mi. Massa ultricies mi quis hendrerit dolor magna eget est.\", 6, 7,\n  \"WP 2\", \"Proin sed libero enim sed faucibus turpis in eu mi. Massa ultricies mi quis hendrerit dolor magna eget est.\", 3, 4,\n  \"WP 2\", \"Neque laoreet suspendisse interdum consectetur libero id faucibus nisl tincidunt.\", 4, 8\n) %\u003e%\n  ganttrify(\n    label_wrap = 32,\n    project_start_date = \"2023-01\",\n    font_family = \"Roboto Condensed\"\n  )\n```\n\n\nFinally, keep in mind that ganttrify outputs `ggplot` objects. Some theming options may not behave exactly as you expect, but for example adding title, subtitle, and captions can be done as you would normally do with any `ggplot2` graph. \n\n\n```{r gantt_with_text}\nganttrify(\n  project = ganttrify::test_project,\n  spots = ganttrify::test_spots,\n  project_start_date = \"2020-01\",\n  font_family = \"Roboto Condensed\"\n) +\n  ggplot2::labs(\n    title = \"My beautiful plans for 2020\",\n    subtitle = \"I will definitely comply with the exact timing of each and all activities*\",\n    caption = \"* I mean, I'll do my best, but if there's a pandemic or something, it's not my fault really\"\n  )\n```\n\nAnd since it's a `ggplot2` object, you can export it as you would any other ggplot graph, by using `ggplot2::ggsave`. This allows also to customise the size of the final input, running e.g. `ggplot2::ggsave(filename = \"my_gantt.png\", width = 12, height = 8, bg = \"white\")` after you run the `ganttrify` function (if you set the file extension to `pdf` or `svg`, you will get the chart in vector format for more clarity at different zoom levels).\n\nIf you are using this in an `rmarkdown` document, keep in mind that you can set the size at the chunk level, e.g. with something like `{r fig.width=12, fig.height=8}` in the chunk header.\n\n### Markdown, html, images... it's all there\n\nNow, to be honest, nobody asked for this. And I'm not even sure it's a good idea. But if you are interested in more customisations of how the wp and activity labels appear, anything supported by [`ggtext`](https://wilkelab.org/ggtext/) should work here: markdown, basic html tags, and even inline images. \n\n```{r funky_project, fig.height=4}\nfunky_project \u003c- tibble::tribble(\n  ~wp, ~activity, ~start_date, ~end_date,\n  \"\u003cspan style = 'color:red;'\u003eRed\u003c/span\u003e flavour\", \"Considering \u003csup\u003eupper\u003c/sup\u003e styles\", 1, 6,\n  \"\u003cspan style = 'color:red;'\u003eRed\u003c/span\u003e flavour\", \"Or **bold**, or *italic*\", 3, 6,\n  \"Don't forget \u003cspan style = 'font-size:6pt'\u003ethe small things\u003c/span\u003e\", \"Contribute to Wikidata \u003cimg src='https://upload.wikimedia.org/wikipedia/commons/thumb/f/ff/Wikidata-logo.svg/320px-Wikidata-logo.svg.png' width=20\u003e\", 5, 10,\n  \"Don't forget \u003cspan style = 'font-size:6pt'\u003ethe small things\u003c/span\u003e\", \"And to OpenStreetMap \u003cimg src='https://upload.wikimedia.org/wikipedia/commons/thumb/b/b0/Openstreetmap_logo.svg/256px-Openstreetmap_logo.svg.png' width=20\u003e\", 7, 12\n)\n\nganttrify(\n  project = funky_project,\n  project_start_date = \"2024-01\",\n  font_family = \"Roboto Condensed\"\n) +\n  ggplot2::ggtitle(\"Custom activity labels, nothing else to see here\")\n```\n\n\n## Shiny app\n\nIf you prefer interactive web interfaces to coding, you can still have a fancy *ganttrified* chart. In order to reduce dependencies and facilitate checks, this is now available in a separate package, [`shinyganttrify`](https://github.com/giocomai/shinyganttrify).\n\nStep 1: Make sure you have installed both `ganttrify` and `shinyganttrify`\n\n```{r install shinyganttrify, eval=FALSE}\nremotes::install_github(\"giocomai/ganttrify\")\nremotes::install_github(\"giocomai/shinyganttrify\")\n```\n\nThen run:\n\n```{r run shiny_ganttrify, eval=FALSE}\nshinyganttrify::shiny_ganttrify()\n```\n\nYou can check it online with no further ado at the following link:\n\nhttps://ganttrify.europeandatajournalism.eu/\n\n(N.B.: not all features are exposed in the Shiny app)\n\n### Shiny app on Docker\n\nAlright, you don't know like R, but you know how Docker works?\n\nThis is all you need to find yourself a nice web app on `localhost`\n\n```\ndocker run -p 80:80 giocomai/ganttrify\n```\n\nYou can of course build yourself the docker image using the Dockerfile included in this repo.  \n\n## Additional input formats\n\nAlright, you prefer to use dates rather than month numbers from the beginning of the project. You're welcome: just format the date as follows, and remember to include the `month_number_label = FALSE` parameter. You can also use exact dates (e.g. `2021-01-01`), but by default they would still be converted to include the entire month were that given day falls. \n\n```{r}\nknitr::kable(ganttrify::test_project_date_month)\n```\n\n```{r gantt_date_month}\nganttrify(\n  project = ganttrify::test_project_date_month,\n  spots = ganttrify::test_spots_date_month,\n  by_date = TRUE,\n  size_text_relative = 1.2,\n  mark_quarters = TRUE,\n  font_family = \"Roboto Condensed\"\n)\n```\n\nAs it turns out, someone wants more detail: they'd like to be able to input activities with an exact start and end date. I start to suspect that `ganttrify` at this stage may not be exactly what you're looking for, but perhaps this works for you?\n\n```{r}\nknitr::kable(ganttrify::test_project_date_day)\n```\n\n\n```{r gantt_date_day}\nganttrify(\n  project = ganttrify::test_project_date_day,\n  spots = ganttrify::test_spots_date_day,\n  by_date = TRUE,\n  exact_date = TRUE,\n  size_text_relative = 1.2,\n  month_number_label = FALSE,\n  font_family = \"Roboto Condensed\"\n)\n```\n\n## Troubleshooting\n\n### Structure of the input table\n\nAt this stage, the package has strong expectations about the input format, and does not provide meaningful error messages. If you see unexpected results, please consider that ideally:\n\n- no cell in the activity column must be empty\n- an activity cannot be called the same as a wp\n- activities in different wp should have different names (or at least add a space at the end or something so that they look different to the computer).\n\nSome of these limitations have been mitigated in recent versions. For example, it is now possible to have activities with the same name in different working packages, e.g.:\n\n```{r activities_with_same_name_in_different_wp_project}\nactivities_with_same_name_in_different_wp_project \u003c- tibble::tribble(\n  ~wp, ~activity, ~start_date, ~end_date,\n  \"WP1\", \"Admin\", 1, 6,\n  \"WP1\", \"Research\", 3, 6,\n  \"WP1\", \"Dissemination\", 4, 7,\n  \"WP2\", \"Admin\", 5, 10,\n  \"WP2\", \"Research\", 6, 12,\n  \"WP2\", \"Research\", 9, 12,\n  \"WP2\", \"Dissemination\", 3, 5,\n  \"WP2\", \"Dissemination\", 8, 9,\n  \"WP3\", \"Admin\", 6, 9,\n  \"WP3\", \"Admin\", 12, 12\n)\n\nganttrify(\n  project = activities_with_same_name_in_different_wp_project,\n  project_start_date = \"2024-01\",\n  font_family = \"Roboto Condensed\"\n)\n```\n\nBut be mindful that this may lead to unexpected results if you're using `spots`; since their placement is currently based exclusively on the activity name, possible long term fixes would inevitably involve a change in the input format. An easy (if inelegant) workaround is simply to add one or more spaces at the end of activities that need to be differentiated: e.g.to differentiate an activity named admin and present in both WP1 and WP2 you could name it \"Admin\" in WP1, but \"Admin \" in WP2 (notice the additional space in the second case). \n\n### Fonts\n\nBy default, this package uses a generic *sans* font but it is recommended to use a narrow (or condensed font such as *[Roboto Condensed](https://fonts.google.com/specimen/Roboto+Condensed)* font - a free font that can be downloaded and installed on any desktop) as they make more efficient use of text space. \n\nOn Fedora, you can install it with `sudo dnf install google-roboto-condensed-fonts`\n\nOn Debian, you can install it with `sudo apt-get install fonts-roboto-fontface`\n\nAfter installation, you should make sure the font is available to R by installing the `extrafont` package, and running `extrafont::font_import()`.\n\nYou can check available fonts also with the package `systemfonts` and the command `systemfonts::system_fonts()`.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgiocomai%2Fganttrify","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgiocomai%2Fganttrify","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgiocomai%2Fganttrify/lists"}