Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Possibility to show quarters instead of months and to regroup by years? #40

Open
dmkaplan2000 opened this issue Feb 6, 2023 · 1 comment
Assignees
Labels
enhancement New feature or request

Comments

@dmkaplan2000
Copy link

Hi,

I would like to know if there is a simple way to display at the top of the Gantt diagram Q1, Q2, ... instead of M1, M4, ... and perhaps to add above the quarters indications of the years?

Thanks,
David

@dmkaplan2000
Copy link
Author

dmkaplan2000 commented Feb 12, 2023

There may be other solutions to this issue (e.g., overwritting axis labels), but I finally decided to write my own Gantt function that labels projects by project quarters and years. The code is pretty specific, but it could probably be generalized to other units of time (e.g., weeks and months), and therefore may be useful for others. I am copying the code here in case it would be useful.

mygantt = function (project, spots = NULL, 
          project_start_date = zoo::as.yearmon(Sys.Date()), 
          colour_palette = wesanderson::wes_palette("Darjeeling1"), 
          font_family = "sans", 
          mark_quarters = FALSE, mark_years = TRUE, 
          size_wp = 6, hide_wp = FALSE, size_activity = 4, size_text_relative = 1, 
          label_wrap = FALSE, month_number_label = TRUE, month_date_label = TRUE, 
          x_axis_position = "top", colour_stripe = "lightgray", alpha_wp = 1, 
          alpha_activity = 1, line_end = "round", 
          show_vertical_lines = FALSE, 
          x_axis_text_align = "left",y_axis_text_align="right") 
{
  # Required libraries ----
  require(zoo)
  require(dplyr)
  require(lubridate)
  
  # Fix some columns ----
  project$wp = as.character(project$wp)
  project$activity = as.character(project$activity)
  
  # Repeat colors in palette ----
  nwp = length(unique(project$wp))
  np = length(as.character(colour_palette))
  colour_palette = colour_palette[rep(1:np,length.out=nwp)]
  
  # Wrap labels if desired ----
  if (label_wrap != FALSE) {
    label_wrap = ifelse(isTRUE(label_wrap),32,label_wrap)
    
    project$wp <- stringr::str_wrap(string = project$wp, 
                                    width = label_wrap)
    project$activity <- stringr::str_wrap(string = project$activity, 
                                          width = label_wrap)
    if (!is.null(spots)) {
      spots$activity <- stringr::str_wrap(string = spots$activity, 
                                          width = label_wrap)
    }
  }
  
  # Determine dates -----
  project_start_date = zoo::as.yearmon(project_start_date) # Force project to start at month start

  project <- project |>
      dplyr::mutate(start_date_yearmon = project_start_date + (1/12) * (start_date-1), 
                    end_date_yearmon = project_start_date + (1/12) * (end_date-1)) |> 
      dplyr::mutate(start_date_date = zoo::as.Date(start_date_yearmon, frac = 0), 
                    end_date_date = zoo::as.Date(end_date_yearmon, frac = 1))
  
  # # Get a sequence of calendar year quarters from start to end
  # seq_q <- seq.Date(from = lubridate::floor_date(min(project$start_date_date),unit="quarter"), 
  #                   to = lubridate::floor_date(max(project$end_date_date),unit="quarter"), by = "3 months")
  
  # Get a sequence of project quarters from start to end
  seq_qn = floor(min(project$start_date-1,0) / 3) : floor(max(project$end_date-1,0) / 3)
  seq_qN = seq_qn %% 4 + 1 # For labeling quarters
  
  seq_qY = floor(seq_qn/4)
  seq_qY = seq_qY + ifelse(seq_qY>=0,1,0) # For labeling years
  
  seq_q = zoo::as.Date(project_start_date + (1/12) * seq_qn * 3,frac=0) # Actual dates of quarters

  # quarter starts and ends for creating shaded rectangles
  quarts_df = data.frame(start=seq_q,end=seq_q %m+% months(3))
  quarts_df2 = quarts_df[seq(1,nrow(quarts_df),2),] # Every other quarter
    
  # # Get a sequence of calendar years from start to end - for year lines
  # s = c(seq_q,max(seq_q) %m+% months(3))
  # seq_y = s[lubridate::month(seq_q) == 1]
  
  # Get a sequence of project years from start to end - for year lines
  seq_y = seq_q[seq_qN == 1]
  # Add a year if the project naturally ends on a year
  if (max(seq_qN)==4) seq_y[length(seq_y)+1] = max(seq_y) %m+% months(12)
  
  # Create data.frame with all the info for labelling quarters
  yl = paste0("Y",seq_qY)
  ql = paste0("Q",seq_qN)
  seq_q_df = data.frame(d=seq_q,q=seq_qN,y=seq_qY,
                        q.lab = paste0(ifelse(seq_qN==1,yl,""),"\n",ql),
                        d.lab = format(seq_q,"%b\n%Y"))
  
  # Add WP to activities ----
  project$row = 1:nrow(project) # Add row ID column
  project.sum = project |> dplyr::group_by(wp) |> 
    dplyr::summarize(activity = wp[1],
                     start_date_date=min(start_date_date),
                     end_date_date=max(end_date_date),
                     row=min(row)-0.5) # Summarize to just WP
  
  project.wp = dplyr::bind_rows(
    activity = project |> select(wp,activity,start_date_date,end_date_date,row),
    wp = project.sum,
    .id = "type"
  ) |> dplyr::arrange(wp,row) # Add in WP and arrange so things group by WP

  # If desired remove WP rows
  if (hide_wp)
    project.wp <- project.wp |> dplyr::filter(type != "wp")
  
  # Set alpha and size of wp and activities segments
  project.wp$alpha <- c(wp=alpha_wp,activity=alpha_activity)[project.wp$type]
  project.wp$size <- c(wp=size_wp,activity=size_activity)[project.wp$type]

  # Turn activity into factor to avoid reordering ----
  project.wp$activity = factor(project.wp$activity,levels = rev(unique(project.wp$activity)))
  # rev essentially reverses y axis of plot.
    
  # Basic Gantt plot with quarter rectangles ----
  gg_gantt <- ggplot2::ggplot(data = project.wp, 
                              mapping = ggplot2::aes(x = start_date_date, y = activity, 
                                                     xend = end_date_date, yend = activity,
                                                     colour = wp)) +
    ggplot2::geom_rect(data = quarts_df2, ggplot2::aes(xmin = start,xmax = end, 
                                                      ymin = -Inf, ymax = Inf), 
                       inherit.aes = FALSE, 
                       alpha = 0.4, fill = colour_stripe)
  
  # Add in lines for quarters and years if desired ----
  if (mark_quarters)
    gg_gantt <- gg_gantt + ggplot2::geom_vline(xintercept = seq_q,colour = "gray50")

  if (mark_years)
    gg_gantt <- gg_gantt + ggplot2::geom_vline(xintercept = seq_y, 
                                               colour = "gray50")
  
  # Add segments ----
  gg_gantt <- gg_gantt + 
    ggplot2::geom_segment(lineend = line_end,
                          size=project.wp$size,alpha=project.wp$alpha)
  
  # Add x axis labels ----
  if (month_number_label && month_date_label) {
    args = list(name="",minor_breaks=NULL,breaks=seq_q_df$d,labels=seq_q_df$d.lab,
                sec.axis = ggplot2::dup_axis(labels=seq_q_df$q.lab))
  } else if (!month_number_label && month_date_label) {
    args = list(name="",minor_breaks=NULL,breaks=seq_q_df$d,labels=seq_q_df$d.lab, 
                position = x_axis_position)
  } else if (month_number_label && !month_date_label) {
    args = list(name="",minor_breaks=NULL,breaks=seq_q_df$d,labels=seq_q_df$q.lab, 
                position = x_axis_position)
  } else {
    args = list(name="")
  }
  gg_gantt <- gg_gantt + do.call(ggplot2::scale_x_date,args)
  
  # Text alignment ----
  xn = switch(x_axis_text_align,
              left=0,right=1,
              center=,centre=0.5,
              stop("Unknown x axis alignment"))
  yn = switch(y_axis_text_align,
              left=0,right=1,
              center=,centre=0.5,
              stop("Unknown y axis alignment"))
  
  # For deciding on bold face for y axis labels
  tt = project.wp |> dplyr::distinct(activity, wp, type) |> dplyr::pull(type)
  tt = rev(ifelse(tt=="wp","bold","plain"))
  
  gg_gantt <- suppressWarnings(
    gg_gantt + ggplot2::scale_y_discrete("") + 
      ggplot2::theme_minimal() + 
      ggplot2::scale_colour_manual(values = colour_palette) + 
      ggplot2::theme(text = ggplot2::element_text(family = font_family), 
                     axis.text.y.left = ggplot2::element_text(face = tt, 
                                                              size = ggplot2::rel(size_text_relative), 
                                                              hjust = yn), 
                     axis.text.x = ggplot2::element_text(size = ggplot2::rel(size_text_relative),
                                                         hjust = xn), 
                     legend.position = "none")
    )
  
  # Spots ----
  if (!is.null(spots)) {
    spots <- spots |> # tidyr::drop_na() |> 
      dplyr::mutate(spot_date = as.numeric(spot_date), 
                    activity = as.character(activity), 
                    spot_type = as.character(spot_type)) |> 
      dplyr::mutate(activity = factor(activity, levels = levels(project.wp$activity)),
                    spot_date_date = zoo::as.Date(project_start_date + (1/12) * (spot_date-1), frac = 0.5),
                    end_date_date=as.Date(NA),wp=NA)
    #browser()
    gg_gantt <- gg_gantt + 
      ggplot2::geom_label(data = spots, 
                          mapping = ggplot2::aes(x = spot_date_date, y = activity, 
                                                 label = spot_type), 
                          colour = "gray30", alpha = 1,
                          fontface = "bold", size = 3 * size_text_relative,
                          family = font_family)
  }
  
  if (!show_vertical_lines)
    gg_gantt <- gg_gantt + ggplot2::theme(panel.grid.major.x = ggplot2::element_line(size = 0))
  
  return(gg_gantt)
}

And some example code to use it would be:

mygantt(ganttrify::test_project,ganttrify::test_spots)

And the output of the function is:

gantt

@giocomai giocomai added the enhancement New feature or request label Apr 19, 2023
@giocomai giocomai self-assigned this Apr 19, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants